1 // Licensed under Apache License version 2.0
2 package javax.jmdns.impl;
3 
4 import java.util.Collection;
5 import java.util.concurrent.ConcurrentHashMap;
6 import java.util.concurrent.ConcurrentMap;
7 import java.util.concurrent.Semaphore;
8 import java.util.concurrent.TimeUnit;
9 import java.util.concurrent.locks.ReentrantLock;
10 import java.util.logging.Level;
11 import java.util.logging.Logger;
12 
13 import javax.jmdns.impl.constants.DNSState;
14 import javax.jmdns.impl.tasks.DNSTask;
15 
16 /**
17  * Sets of methods to manage the state machine.<br/>
18  * <b>Implementation note:</b> This interface is accessed from multiple threads. The implementation must be thread safe.
19  *
20  * @author Pierre Frisch
21  */
22 public interface DNSStatefulObject {
23 
24     /**
25      * 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.
26      * <p>
27      * Implementation note: this class is based on {@link java.util.concurrent.Semaphore} so that they can be released by the timeout timer.
28      * </p>
29      *
30      * @author Pierre Frisch
31      */
32     public static final class DNSStatefulObjectSemaphore {
33         private static Logger                          logger = Logger.getLogger(DNSStatefulObjectSemaphore.class.getName());
34 
35         private final String                           _name;
36 
37         private final ConcurrentMap<Thread, Semaphore> _semaphores;
38 
39         /**
40          * @param name
41          *            Semaphore name for debugging purposes.
42          */
DNSStatefulObjectSemaphore(String name)43         public DNSStatefulObjectSemaphore(String name) {
44             super();
45             _name = name;
46             _semaphores = new ConcurrentHashMap<Thread, Semaphore>(50);
47         }
48 
49         /**
50          * Blocks the current thread until the event arrives or the timeout expires.
51          *
52          * @param timeout
53          *            wait period for the event
54          */
waitForEvent(long timeout)55         public void waitForEvent(long timeout) {
56             Thread thread = Thread.currentThread();
57             Semaphore semaphore = _semaphores.get(thread);
58             if (semaphore == null) {
59                 semaphore = new Semaphore(1, true);
60                 semaphore.drainPermits();
61                 _semaphores.putIfAbsent(thread, semaphore);
62             }
63             semaphore = _semaphores.get(thread);
64             try {
65                 semaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS);
66             } catch (InterruptedException exception) {
67                 logger.log(Level.FINER, "Exception ", exception);
68             }
69         }
70 
71         /**
72          * Signals the semaphore when the event arrives.
73          */
signalEvent()74         public void signalEvent() {
75             Collection<Semaphore> semaphores = _semaphores.values();
76             for (Semaphore semaphore : semaphores) {
77                 semaphore.release();
78                 semaphores.remove(semaphore);
79             }
80         }
81 
82         @Override
toString()83         public String toString() {
84             StringBuilder aLog = new StringBuilder(1000);
85             aLog.append("Semaphore: ");
86             aLog.append(this._name);
87             if (_semaphores.size() == 0) {
88                 aLog.append(" no semaphores.");
89             } else {
90                 aLog.append(" semaphores:\n");
91                 for (Thread thread : _semaphores.keySet()) {
92                     aLog.append("\tThread: ");
93                     aLog.append(thread.getName());
94                     aLog.append(' ');
95                     aLog.append(_semaphores.get(thread));
96                     aLog.append('\n');
97                 }
98             }
99             return aLog.toString();
100         }
101 
102     }
103 
104     public static class DefaultImplementation extends ReentrantLock implements DNSStatefulObject {
105         private static Logger                    logger           = Logger.getLogger(DefaultImplementation.class.getName());
106 
107         private static final long                serialVersionUID = -3264781576883412227L;
108 
109         private volatile JmDNSImpl               _dns;
110 
111         protected volatile DNSTask               _task;
112 
113         protected volatile DNSState              _state;
114 
115         private final DNSStatefulObjectSemaphore _announcing;
116 
117         private final DNSStatefulObjectSemaphore _canceling;
118 
DefaultImplementation()119         public DefaultImplementation() {
120             super();
121             _dns = null;
122             _task = null;
123             _state = DNSState.PROBING_1;
124             _announcing = new DNSStatefulObjectSemaphore("Announce");
125             _canceling = new DNSStatefulObjectSemaphore("Cancel");
126         }
127 
128         /**
129          * {@inheritDoc}
130          */
131         @Override
getDns()132         public JmDNSImpl getDns() {
133             return this._dns;
134         }
135 
setDns(JmDNSImpl dns)136         protected void setDns(JmDNSImpl dns) {
137             this._dns = dns;
138         }
139 
140         /**
141          * {@inheritDoc}
142          */
143         @Override
associateWithTask(DNSTask task, DNSState state)144         public void associateWithTask(DNSTask task, DNSState state) {
145             if (this._task == null && this._state == state) {
146                 this.lock();
147                 try {
148                     if (this._task == null && this._state == state) {
149                         this.setTask(task);
150                     }
151                 } finally {
152                     this.unlock();
153                 }
154             }
155         }
156 
157         /**
158          * {@inheritDoc}
159          */
160         @Override
removeAssociationWithTask(DNSTask task)161         public void removeAssociationWithTask(DNSTask task) {
162             if (this._task == task) {
163                 this.lock();
164                 try {
165                     if (this._task == task) {
166                         this.setTask(null);
167                     }
168                 } finally {
169                     this.unlock();
170                 }
171             }
172         }
173 
174         /**
175          * {@inheritDoc}
176          */
177         @Override
isAssociatedWithTask(DNSTask task, DNSState state)178         public boolean isAssociatedWithTask(DNSTask task, DNSState state) {
179             this.lock();
180             try {
181                 return this._task == task && this._state == state;
182             } finally {
183                 this.unlock();
184             }
185         }
186 
setTask(DNSTask task)187         protected void setTask(DNSTask task) {
188             this._task = task;
189         }
190 
191         /**
192          * @param state
193          *            the state to set
194          */
setState(DNSState state)195         protected void setState(DNSState state) {
196             this.lock();
197             try {
198                 this._state = state;
199                 if (this.isAnnounced()) {
200                     _announcing.signalEvent();
201                 }
202                 if (this.isCanceled()) {
203                     _canceling.signalEvent();
204                     // clear any waiting announcing
205                     _announcing.signalEvent();
206                 }
207             } finally {
208                 this.unlock();
209             }
210         }
211 
212         /**
213          * {@inheritDoc}
214          */
215         @Override
advanceState(DNSTask task)216         public boolean advanceState(DNSTask task) {
217             boolean result = true;
218             if (this._task == task) {
219                 this.lock();
220                 try {
221                     if (this._task == task) {
222                         this.setState(this._state.advance());
223                     } else {
224                         logger.warning("Trying to advance state whhen not the owner. owner: " + this._task + " perpetrator: " + task);
225                     }
226                 } finally {
227                     this.unlock();
228                 }
229             }
230             return result;
231         }
232 
233         /**
234          * {@inheritDoc}
235          */
236         @Override
revertState()237         public boolean revertState() {
238             boolean result = true;
239             if (!this.willCancel()) {
240                 this.lock();
241                 try {
242                     if (!this.willCancel()) {
243                         this.setState(this._state.revert());
244                         this.setTask(null);
245                     }
246                 } finally {
247                     this.unlock();
248                 }
249             }
250             return result;
251         }
252 
253         /**
254          * {@inheritDoc}
255          */
256         @Override
cancelState()257         public boolean cancelState() {
258             boolean result = false;
259             if (!this.willCancel()) {
260                 this.lock();
261                 try {
262                     if (!this.willCancel()) {
263                         this.setState(DNSState.CANCELING_1);
264                         this.setTask(null);
265                         result = true;
266                     }
267                 } finally {
268                     this.unlock();
269                 }
270             }
271             return result;
272         }
273 
274         /**
275          * {@inheritDoc}
276          */
277         @Override
closeState()278         public boolean closeState() {
279             boolean result = false;
280             if (!this.willClose()) {
281                 this.lock();
282                 try {
283                     if (!this.willClose()) {
284                         this.setState(DNSState.CLOSING);
285                         this.setTask(null);
286                         result = true;
287                     }
288                 } finally {
289                     this.unlock();
290                 }
291             }
292             return result;
293         }
294 
295         /**
296          * {@inheritDoc}
297          */
298         @Override
recoverState()299         public boolean recoverState() {
300             boolean result = false;
301             this.lock();
302             try {
303                 this.setState(DNSState.PROBING_1);
304                 this.setTask(null);
305             } finally {
306                 this.unlock();
307             }
308             return result;
309         }
310 
311         /**
312          * {@inheritDoc}
313          */
314         @Override
isProbing()315         public boolean isProbing() {
316             return this._state.isProbing();
317         }
318 
319         /**
320          * {@inheritDoc}
321          */
322         @Override
isAnnouncing()323         public boolean isAnnouncing() {
324             return this._state.isAnnouncing();
325         }
326 
327         /**
328          * {@inheritDoc}
329          */
330         @Override
isAnnounced()331         public boolean isAnnounced() {
332             return this._state.isAnnounced();
333         }
334 
335         /**
336          * {@inheritDoc}
337          */
338         @Override
isCanceling()339         public boolean isCanceling() {
340             return this._state.isCanceling();
341         }
342 
343         /**
344          * {@inheritDoc}
345          */
346         @Override
isCanceled()347         public boolean isCanceled() {
348             return this._state.isCanceled();
349         }
350 
351         /**
352          * {@inheritDoc}
353          */
354         @Override
isClosing()355         public boolean isClosing() {
356             return this._state.isClosing();
357         }
358 
359         /**
360          * {@inheritDoc}
361          */
362         @Override
isClosed()363         public boolean isClosed() {
364             return this._state.isClosed();
365         }
366 
willCancel()367         private boolean willCancel() {
368             return this._state.isCanceled() || this._state.isCanceling();
369         }
370 
willClose()371         private boolean willClose() {
372             return this._state.isClosed() || this._state.isClosing();
373         }
374 
375         /**
376          * {@inheritDoc}
377          */
378         @Override
waitForAnnounced(long timeout)379         public boolean waitForAnnounced(long timeout) {
380             if (!this.isAnnounced() && !this.willCancel()) {
381                 _announcing.waitForEvent(timeout);
382             }
383             if (!this.isAnnounced()) {
384                 if (this.willCancel() || this.willClose()) {
385                     logger.fine("Wait for announced cancelled: " + this);
386                 } else {
387                     logger.warning("Wait for announced timed out: " + this);
388                 }
389             }
390             return this.isAnnounced();
391         }
392 
393         /**
394          * {@inheritDoc}
395          */
396         @Override
waitForCanceled(long timeout)397         public boolean waitForCanceled(long timeout) {
398             if (!this.isCanceled()) {
399                 _canceling.waitForEvent(timeout);
400             }
401             if (!this.isCanceled() && !this.willClose()) {
402                 logger.warning("Wait for canceled timed out: " + this);
403             }
404             return this.isCanceled();
405         }
406 
407         /**
408          * {@inheritDoc}
409          */
410         @Override
toString()411         public String toString() {
412             return (_dns != null ? "DNS: " + _dns.getName() : "NO DNS") + " state: " + _state + " task: " + _task;
413         }
414 
415     }
416 
417     /**
418      * Returns the DNS associated with this object.
419      *
420      * @return DNS resolver
421      */
getDns()422     public JmDNSImpl getDns();
423 
424     /**
425      * Sets the task associated with this Object.
426      *
427      * @param task
428      *            associated task
429      * @param state
430      *            state of the task
431      */
associateWithTask(DNSTask task, DNSState state)432     public void associateWithTask(DNSTask task, DNSState state);
433 
434     /**
435      * Remove the association of the task with this Object.
436      *
437      * @param task
438      *            associated task
439      */
removeAssociationWithTask(DNSTask task)440     public void removeAssociationWithTask(DNSTask task);
441 
442     /**
443      * Checks if this object is associated with the task and in the same state.
444      *
445      * @param task
446      *            associated task
447      * @param state
448      *            state of the task
449      * @return <code>true</code> is the task is associated with this object, <code>false</code> otherwise.
450      */
isAssociatedWithTask(DNSTask task, DNSState state)451     public boolean isAssociatedWithTask(DNSTask task, DNSState state);
452 
453     /**
454      * Sets the state and notifies all objects that wait on the ServiceInfo.
455      *
456      * @param task
457      *            associated task
458      * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise.
459      * @see DNSState#advance()
460      */
advanceState(DNSTask task)461     public boolean advanceState(DNSTask task);
462 
463     /**
464      * Sets the state and notifies all objects that wait on the ServiceInfo.
465      *
466      * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise.
467      * @see DNSState#revert()
468      */
revertState()469     public boolean revertState();
470 
471     /**
472      * Sets the state and notifies all objects that wait on the ServiceInfo.
473      *
474      * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise.
475      */
cancelState()476     public boolean cancelState();
477 
478     /**
479      * Sets the state and notifies all objects that wait on the ServiceInfo.
480      *
481      * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise.
482      */
closeState()483     public boolean closeState();
484 
485     /**
486      * Sets the state and notifies all objects that wait on the ServiceInfo.
487      *
488      * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise.
489      */
recoverState()490     public boolean recoverState();
491 
492     /**
493      * Returns true, if this is a probing state.
494      *
495      * @return <code>true</code> if probing state, <code>false</code> otherwise
496      */
isProbing()497     public boolean isProbing();
498 
499     /**
500      * Returns true, if this is an announcing state.
501      *
502      * @return <code>true</code> if announcing state, <code>false</code> otherwise
503      */
isAnnouncing()504     public boolean isAnnouncing();
505 
506     /**
507      * Returns true, if this is an announced state.
508      *
509      * @return <code>true</code> if announced state, <code>false</code> otherwise
510      */
isAnnounced()511     public boolean isAnnounced();
512 
513     /**
514      * Returns true, if this is a canceling state.
515      *
516      * @return <code>true</code> if canceling state, <code>false</code> otherwise
517      */
isCanceling()518     public boolean isCanceling();
519 
520     /**
521      * Returns true, if this is a canceled state.
522      *
523      * @return <code>true</code> if canceled state, <code>false</code> otherwise
524      */
isCanceled()525     public boolean isCanceled();
526 
527     /**
528      * Returns true, if this is a closing state.
529      *
530      * @return <code>true</code> if closing state, <code>false</code> otherwise
531      */
isClosing()532     public boolean isClosing();
533 
534     /**
535      * Returns true, if this is a closed state.
536      *
537      * @return <code>true</code> if closed state, <code>false</code> otherwise
538      */
isClosed()539     public boolean isClosed();
540 
541     /**
542      * Waits for the object to be announced.
543      *
544      * @param timeout
545      *            the maximum time to wait in milliseconds.
546      * @return <code>true</code> if the object is announced, <code>false</code> otherwise
547      */
waitForAnnounced(long timeout)548     public boolean waitForAnnounced(long timeout);
549 
550     /**
551      * Waits for the object to be canceled.
552      *
553      * @param timeout
554      *            the maximum time to wait in milliseconds.
555      * @return <code>true</code> if the object is canceled, <code>false</code> otherwise
556      */
waitForCanceled(long timeout)557     public boolean waitForCanceled(long timeout);
558 
559 }
560