// 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.
* Implementation note: 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. *

* Implementation note: this class is based on {@link java.util.concurrent.Semaphore} so that they can be released by the timeout timer. *

* * @author Pierre Frisch */ public static final class DNSStatefulObjectSemaphore { private static Logger logger = Logger.getLogger(DNSStatefulObjectSemaphore.class.getName()); private final String _name; private final ConcurrentMap _semaphores; /** * @param name * Semaphore name for debugging purposes. */ public DNSStatefulObjectSemaphore(String name) { super(); _name = name; _semaphores = new ConcurrentHashMap(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 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 true is the task is associated with this object, false 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 truefalse otherwise. * @see DNSState#advance() */ public boolean advanceState(DNSTask task); /** * Sets the state and notifies all objects that wait on the ServiceInfo. * * @return truefalse otherwise. * @see DNSState#revert() */ public boolean revertState(); /** * Sets the state and notifies all objects that wait on the ServiceInfo. * * @return truefalse otherwise. */ public boolean cancelState(); /** * Sets the state and notifies all objects that wait on the ServiceInfo. * * @return truefalse otherwise. */ public boolean closeState(); /** * Sets the state and notifies all objects that wait on the ServiceInfo. * * @return truefalse otherwise. */ public boolean recoverState(); /** * Returns true, if this is a probing state. * * @return true if probing state, false otherwise */ public boolean isProbing(); /** * Returns true, if this is an announcing state. * * @return true if announcing state, false otherwise */ public boolean isAnnouncing(); /** * Returns true, if this is an announced state. * * @return true if announced state, false otherwise */ public boolean isAnnounced(); /** * Returns true, if this is a canceling state. * * @return true if canceling state, false otherwise */ public boolean isCanceling(); /** * Returns true, if this is a canceled state. * * @return true if canceled state, false otherwise */ public boolean isCanceled(); /** * Returns true, if this is a closing state. * * @return true if closing state, false otherwise */ public boolean isClosing(); /** * Returns true, if this is a closed state. * * @return true if closed state, false otherwise */ public boolean isClosed(); /** * Waits for the object to be announced. * * @param timeout * the maximum time to wait in milliseconds. * @return true if the object is announced, false otherwise */ public boolean waitForAnnounced(long timeout); /** * Waits for the object to be canceled. * * @param timeout * the maximum time to wait in milliseconds. * @return true if the object is canceled, false otherwise */ public boolean waitForCanceled(long timeout); }