// 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 true
false otherwise.
* @see DNSState#advance()
*/
public boolean advanceState(DNSTask task);
/**
* Sets the state and notifies all objects that wait on the ServiceInfo.
*
* @return true
false otherwise.
* @see DNSState#revert()
*/
public boolean revertState();
/**
* Sets the state and notifies all objects that wait on the ServiceInfo.
*
* @return true
false otherwise.
*/
public boolean cancelState();
/**
* Sets the state and notifies all objects that wait on the ServiceInfo.
*
* @return true
false otherwise.
*/
public boolean closeState();
/**
* Sets the state and notifies all objects that wait on the ServiceInfo.
*
* @return true
false 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);
}