summaryrefslogtreecommitdiff
path: root/src/javax/jmdns/impl/DNSStatefulObject.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/javax/jmdns/impl/DNSStatefulObject.java')
-rw-r--r--src/javax/jmdns/impl/DNSStatefulObject.java559
1 files changed, 559 insertions, 0 deletions
diff --git a/src/javax/jmdns/impl/DNSStatefulObject.java b/src/javax/jmdns/impl/DNSStatefulObject.java
new file mode 100644
index 0000000..aadd6f1
--- /dev/null
+++ b/src/javax/jmdns/impl/DNSStatefulObject.java
@@ -0,0 +1,559 @@
+// 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);
+
+}