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 }