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