1// Licensed under Apache License version 2.0
2package javax.jmdns.impl;
3
4import java.util.Collection;
5import java.util.concurrent.ConcurrentHashMap;
6import java.util.concurrent.ConcurrentMap;
7import java.util.concurrent.Semaphore;
8import java.util.concurrent.TimeUnit;
9import java.util.concurrent.locks.ReentrantLock;
10import java.util.logging.Level;
11import java.util.logging.Logger;
12
13import javax.jmdns.impl.constants.DNSState;
14import 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 */
22public 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}
560