| // Licensed under Apache License version 2.0 |
| package javax.jmdns.impl; |
| |
| import java.util.Collection; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.locks.ReentrantLock; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import javax.jmdns.impl.constants.DNSState; |
| import javax.jmdns.impl.tasks.DNSTask; |
| |
| /** |
| * Sets of methods to manage the state machine.<br/> |
| * <b>Implementation note:</b> This interface is accessed from multiple threads. The implementation must be thread safe. |
| * |
| * @author Pierre Frisch |
| */ |
| public interface DNSStatefulObject { |
| |
| /** |
| * This class define a semaphore. On this multiple threads can wait the arrival of one event. Thread wait for a maximum defined by the timeout. |
| * <p> |
| * Implementation note: this class is based on {@link java.util.concurrent.Semaphore} so that they can be released by the timeout timer. |
| * </p> |
| * |
| * @author Pierre Frisch |
| */ |
| public static final class DNSStatefulObjectSemaphore { |
| private static Logger logger = Logger.getLogger(DNSStatefulObjectSemaphore.class.getName()); |
| |
| private final String _name; |
| |
| private final ConcurrentMap<Thread, Semaphore> _semaphores; |
| |
| /** |
| * @param name |
| * Semaphore name for debugging purposes. |
| */ |
| public DNSStatefulObjectSemaphore(String name) { |
| super(); |
| _name = name; |
| _semaphores = new ConcurrentHashMap<Thread, Semaphore>(50); |
| } |
| |
| /** |
| * Blocks the current thread until the event arrives or the timeout expires. |
| * |
| * @param timeout |
| * wait period for the event |
| */ |
| public void waitForEvent(long timeout) { |
| Thread thread = Thread.currentThread(); |
| Semaphore semaphore = _semaphores.get(thread); |
| if (semaphore == null) { |
| semaphore = new Semaphore(1, true); |
| semaphore.drainPermits(); |
| _semaphores.putIfAbsent(thread, semaphore); |
| } |
| semaphore = _semaphores.get(thread); |
| try { |
| semaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS); |
| } catch (InterruptedException exception) { |
| logger.log(Level.FINER, "Exception ", exception); |
| } |
| } |
| |
| /** |
| * Signals the semaphore when the event arrives. |
| */ |
| public void signalEvent() { |
| Collection<Semaphore> semaphores = _semaphores.values(); |
| for (Semaphore semaphore : semaphores) { |
| semaphore.release(); |
| semaphores.remove(semaphore); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder aLog = new StringBuilder(1000); |
| aLog.append("Semaphore: "); |
| aLog.append(this._name); |
| if (_semaphores.size() == 0) { |
| aLog.append(" no semaphores."); |
| } else { |
| aLog.append(" semaphores:\n"); |
| for (Thread thread : _semaphores.keySet()) { |
| aLog.append("\tThread: "); |
| aLog.append(thread.getName()); |
| aLog.append(' '); |
| aLog.append(_semaphores.get(thread)); |
| aLog.append('\n'); |
| } |
| } |
| return aLog.toString(); |
| } |
| |
| } |
| |
| public static class DefaultImplementation extends ReentrantLock implements DNSStatefulObject { |
| private static Logger logger = Logger.getLogger(DefaultImplementation.class.getName()); |
| |
| private static final long serialVersionUID = -3264781576883412227L; |
| |
| private volatile JmDNSImpl _dns; |
| |
| protected volatile DNSTask _task; |
| |
| protected volatile DNSState _state; |
| |
| private final DNSStatefulObjectSemaphore _announcing; |
| |
| private final DNSStatefulObjectSemaphore _canceling; |
| |
| public DefaultImplementation() { |
| super(); |
| _dns = null; |
| _task = null; |
| _state = DNSState.PROBING_1; |
| _announcing = new DNSStatefulObjectSemaphore("Announce"); |
| _canceling = new DNSStatefulObjectSemaphore("Cancel"); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public JmDNSImpl getDns() { |
| return this._dns; |
| } |
| |
| protected void setDns(JmDNSImpl dns) { |
| this._dns = dns; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void associateWithTask(DNSTask task, DNSState state) { |
| if (this._task == null && this._state == state) { |
| this.lock(); |
| try { |
| if (this._task == null && this._state == state) { |
| this.setTask(task); |
| } |
| } finally { |
| this.unlock(); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void removeAssociationWithTask(DNSTask task) { |
| if (this._task == task) { |
| this.lock(); |
| try { |
| if (this._task == task) { |
| this.setTask(null); |
| } |
| } finally { |
| this.unlock(); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isAssociatedWithTask(DNSTask task, DNSState state) { |
| this.lock(); |
| try { |
| return this._task == task && this._state == state; |
| } finally { |
| this.unlock(); |
| } |
| } |
| |
| protected void setTask(DNSTask task) { |
| this._task = task; |
| } |
| |
| /** |
| * @param state |
| * the state to set |
| */ |
| protected void setState(DNSState state) { |
| this.lock(); |
| try { |
| this._state = state; |
| if (this.isAnnounced()) { |
| _announcing.signalEvent(); |
| } |
| if (this.isCanceled()) { |
| _canceling.signalEvent(); |
| // clear any waiting announcing |
| _announcing.signalEvent(); |
| } |
| } finally { |
| this.unlock(); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean advanceState(DNSTask task) { |
| boolean result = true; |
| if (this._task == task) { |
| this.lock(); |
| try { |
| if (this._task == task) { |
| this.setState(this._state.advance()); |
| } else { |
| logger.warning("Trying to advance state whhen not the owner. owner: " + this._task + " perpetrator: " + task); |
| } |
| } finally { |
| this.unlock(); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean revertState() { |
| boolean result = true; |
| if (!this.willCancel()) { |
| this.lock(); |
| try { |
| if (!this.willCancel()) { |
| this.setState(this._state.revert()); |
| this.setTask(null); |
| } |
| } finally { |
| this.unlock(); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean cancelState() { |
| boolean result = false; |
| if (!this.willCancel()) { |
| this.lock(); |
| try { |
| if (!this.willCancel()) { |
| this.setState(DNSState.CANCELING_1); |
| this.setTask(null); |
| result = true; |
| } |
| } finally { |
| this.unlock(); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean closeState() { |
| boolean result = false; |
| if (!this.willClose()) { |
| this.lock(); |
| try { |
| if (!this.willClose()) { |
| this.setState(DNSState.CLOSING); |
| this.setTask(null); |
| result = true; |
| } |
| } finally { |
| this.unlock(); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean recoverState() { |
| boolean result = false; |
| this.lock(); |
| try { |
| this.setState(DNSState.PROBING_1); |
| this.setTask(null); |
| } finally { |
| this.unlock(); |
| } |
| return result; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isProbing() { |
| return this._state.isProbing(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isAnnouncing() { |
| return this._state.isAnnouncing(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isAnnounced() { |
| return this._state.isAnnounced(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isCanceling() { |
| return this._state.isCanceling(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isCanceled() { |
| return this._state.isCanceled(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isClosing() { |
| return this._state.isClosing(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isClosed() { |
| return this._state.isClosed(); |
| } |
| |
| private boolean willCancel() { |
| return this._state.isCanceled() || this._state.isCanceling(); |
| } |
| |
| private boolean willClose() { |
| return this._state.isClosed() || this._state.isClosing(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean waitForAnnounced(long timeout) { |
| if (!this.isAnnounced() && !this.willCancel()) { |
| _announcing.waitForEvent(timeout); |
| } |
| if (!this.isAnnounced()) { |
| if (this.willCancel() || this.willClose()) { |
| logger.fine("Wait for announced cancelled: " + this); |
| } else { |
| logger.warning("Wait for announced timed out: " + this); |
| } |
| } |
| return this.isAnnounced(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean waitForCanceled(long timeout) { |
| if (!this.isCanceled()) { |
| _canceling.waitForEvent(timeout); |
| } |
| if (!this.isCanceled() && !this.willClose()) { |
| logger.warning("Wait for canceled timed out: " + this); |
| } |
| return this.isCanceled(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String toString() { |
| return (_dns != null ? "DNS: " + _dns.getName() : "NO DNS") + " state: " + _state + " task: " + _task; |
| } |
| |
| } |
| |
| /** |
| * Returns the DNS associated with this object. |
| * |
| * @return DNS resolver |
| */ |
| public JmDNSImpl getDns(); |
| |
| /** |
| * Sets the task associated with this Object. |
| * |
| * @param task |
| * associated task |
| * @param state |
| * state of the task |
| */ |
| public void associateWithTask(DNSTask task, DNSState state); |
| |
| /** |
| * Remove the association of the task with this Object. |
| * |
| * @param task |
| * associated task |
| */ |
| public void removeAssociationWithTask(DNSTask task); |
| |
| /** |
| * Checks if this object is associated with the task and in the same state. |
| * |
| * @param task |
| * associated task |
| * @param state |
| * state of the task |
| * @return <code>true</code> is the task is associated with this object, <code>false</code> otherwise. |
| */ |
| public boolean isAssociatedWithTask(DNSTask task, DNSState state); |
| |
| /** |
| * Sets the state and notifies all objects that wait on the ServiceInfo. |
| * |
| * @param task |
| * associated task |
| * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise. |
| * @see DNSState#advance() |
| */ |
| public boolean advanceState(DNSTask task); |
| |
| /** |
| * Sets the state and notifies all objects that wait on the ServiceInfo. |
| * |
| * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise. |
| * @see DNSState#revert() |
| */ |
| public boolean revertState(); |
| |
| /** |
| * Sets the state and notifies all objects that wait on the ServiceInfo. |
| * |
| * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise. |
| */ |
| public boolean cancelState(); |
| |
| /** |
| * Sets the state and notifies all objects that wait on the ServiceInfo. |
| * |
| * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise. |
| */ |
| public boolean closeState(); |
| |
| /** |
| * Sets the state and notifies all objects that wait on the ServiceInfo. |
| * |
| * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise. |
| */ |
| public boolean recoverState(); |
| |
| /** |
| * Returns true, if this is a probing state. |
| * |
| * @return <code>true</code> if probing state, <code>false</code> otherwise |
| */ |
| public boolean isProbing(); |
| |
| /** |
| * Returns true, if this is an announcing state. |
| * |
| * @return <code>true</code> if announcing state, <code>false</code> otherwise |
| */ |
| public boolean isAnnouncing(); |
| |
| /** |
| * Returns true, if this is an announced state. |
| * |
| * @return <code>true</code> if announced state, <code>false</code> otherwise |
| */ |
| public boolean isAnnounced(); |
| |
| /** |
| * Returns true, if this is a canceling state. |
| * |
| * @return <code>true</code> if canceling state, <code>false</code> otherwise |
| */ |
| public boolean isCanceling(); |
| |
| /** |
| * Returns true, if this is a canceled state. |
| * |
| * @return <code>true</code> if canceled state, <code>false</code> otherwise |
| */ |
| public boolean isCanceled(); |
| |
| /** |
| * Returns true, if this is a closing state. |
| * |
| * @return <code>true</code> if closing state, <code>false</code> otherwise |
| */ |
| public boolean isClosing(); |
| |
| /** |
| * Returns true, if this is a closed state. |
| * |
| * @return <code>true</code> if closed state, <code>false</code> otherwise |
| */ |
| public boolean isClosed(); |
| |
| /** |
| * Waits for the object to be announced. |
| * |
| * @param timeout |
| * the maximum time to wait in milliseconds. |
| * @return <code>true</code> if the object is announced, <code>false</code> otherwise |
| */ |
| public boolean waitForAnnounced(long timeout); |
| |
| /** |
| * Waits for the object to be canceled. |
| * |
| * @param timeout |
| * the maximum time to wait in milliseconds. |
| * @return <code>true</code> if the object is canceled, <code>false</code> otherwise |
| */ |
| public boolean waitForCanceled(long timeout); |
| |
| } |