View Javadoc

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           */
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           */
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           */
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
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 
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
132         public JmDNSImpl getDns() {
133             return this._dns;
134         }
135 
136         protected void setDns(JmDNSImpl dns) {
137             this._dns = dns;
138         }
139 
140         /**
141          * {@inheritDoc}
142          */
143         @Override
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
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
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 
187         protected void setTask(DNSTask task) {
188             this._task = task;
189         }
190 
191         /**
192          * @param state
193          *            the state to set
194          */
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
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
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
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
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
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
315         public boolean isProbing() {
316             return this._state.isProbing();
317         }
318 
319         /**
320          * {@inheritDoc}
321          */
322         @Override
323         public boolean isAnnouncing() {
324             return this._state.isAnnouncing();
325         }
326 
327         /**
328          * {@inheritDoc}
329          */
330         @Override
331         public boolean isAnnounced() {
332             return this._state.isAnnounced();
333         }
334 
335         /**
336          * {@inheritDoc}
337          */
338         @Override
339         public boolean isCanceling() {
340             return this._state.isCanceling();
341         }
342 
343         /**
344          * {@inheritDoc}
345          */
346         @Override
347         public boolean isCanceled() {
348             return this._state.isCanceled();
349         }
350 
351         /**
352          * {@inheritDoc}
353          */
354         @Override
355         public boolean isClosing() {
356             return this._state.isClosing();
357         }
358 
359         /**
360          * {@inheritDoc}
361          */
362         @Override
363         public boolean isClosed() {
364             return this._state.isClosed();
365         }
366 
367         private boolean willCancel() {
368             return this._state.isCanceled() || this._state.isCanceling();
369         }
370 
371         private boolean willClose() {
372             return this._state.isClosed() || this._state.isClosing();
373         }
374 
375         /**
376          * {@inheritDoc}
377          */
378         @Override
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
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
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
557     public boolean waitForCanceled(long timeout);
558 
559 }