001package com.github.theholywaffle.teamspeak3.api; 002 003/* 004 * #%L 005 * TeamSpeak 3 Java API 006 * %% 007 * Copyright (C) 2014 Bert De Geyter 008 * %% 009 * Permission is hereby granted, free of charge, to any person obtaining a copy 010 * of this software and associated documentation files (the "Software"), to deal 011 * in the Software without restriction, including without limitation the rights 012 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 013 * copies of the Software, and to permit persons to whom the Software is 014 * furnished to do so, subject to the following conditions: 015 * 016 * The above copyright notice and this permission notice shall be included in 017 * all copies or substantial portions of the Software. 018 * 019 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 020 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 021 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 022 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 023 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 024 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 025 * THE SOFTWARE. 026 * #L% 027 */ 028 029import com.github.theholywaffle.teamspeak3.TS3ApiAsync; 030import com.github.theholywaffle.teamspeak3.api.exception.TS3CommandFailedException; 031import com.github.theholywaffle.teamspeak3.api.wrapper.QueryError; 032 033import java.util.Arrays; 034import java.util.Collection; 035import java.util.Iterator; 036import java.util.List; 037import java.util.concurrent.CancellationException; 038import java.util.concurrent.Future; 039import java.util.concurrent.TimeUnit; 040import java.util.concurrent.TimeoutException; 041import java.util.concurrent.atomic.AtomicInteger; 042 043/** 044 * Represents the result of an asynchronous execution of a query command. 045 * <p> 046 * Basically, this class is a container for a server response which will 047 * arrive at some time in the future. It also accounts for the possibility 048 * that a command might fail and that a future might be cancelled by a user. 049 * </p> 050 * A {@code CommandFuture} can therefore have 4 different states: 051 * <ul> 052 * <li><b>Waiting</b> - No response from the server has arrived yet</li> 053 * <li><b>Cancelled</b> - A user cancelled this future before a response from the server could arrive</li> 054 * <li><b>Failed</b> - The server received the command but responded with an error message</li> 055 * <li><b>Succeeded</b> - The server successfully processed the command and sent back a result</li> 056 * </ul> 057 * You can check the state of the future using the methods {@link #isDone()}, 058 * {@link #isSuccessful()}, {@link #isFailed()} and {@link #isCancelled()}. 059 * <p> 060 * A {@code CommandFuture}'s value can be retrieved by calling {@link #get()} 061 * or {@link #get(long, TimeUnit)}, which block the current thread until the 062 * server response arrives. The method with a timeout should be preferred 063 * as there's no guarantee that a proper response (or an error message) 064 * will ever arrive, e.g. in case of a permanent disconnect. 065 * There are also variations of these methods which ignore thread interrupts, 066 * {@link #getUninterruptibly()} and {@link #getUninterruptibly(long, TimeUnit)}. 067 * </p><p> 068 * Note that these methods all wait for the response to arrive and thereby 069 * revert to synchronous execution. If you want to handle the server response 070 * asynchronously, you need to register success and failure listeners. 071 * These listeners will be called in a separate thread once a response arrives. 072 * </p><p> 073 * Each {@code CommandFuture} can only ever have one {@link SuccessListener} and 074 * one {@link FailureListener} registered. All {@link TS3ApiAsync} methods are 075 * guaranteed to return a {@code CommandFuture} with no listeners registered. 076 * </p><p> 077 * To set the value of a {@code CommandFuture}, the {@link #set(Object)} method is used; 078 * to notify it of a failure, {@link #fail(QueryError)} is used. You usually 079 * shouldn't call these methods yourself, however. That's the job of the API. 080 * </p><p> 081 * {@code CommandFuture}s are thread-safe. All state-changing methods are synchronized. 082 * </p> 083 * 084 * @param <V> 085 * the type of the value 086 * 087 * @see TS3ApiAsync 088 */ 089public class CommandFuture<V> implements Future<V> { 090 091 private enum FutureState { 092 WAITING, 093 CANCELLED, 094 FAILED, 095 SUCCEEDED 096 } 097 098 /** 099 * Just a plain object, used to signal a change to {@link #value} to any 100 * threads waiting in {@link #get()} and {@link #getUninterruptibly()} methods. 101 */ 102 private final Object signal = new Object(); 103 104 private volatile FutureState state = FutureState.WAITING; 105 private volatile V value = null; 106 private volatile QueryError queryError = null; 107 private volatile SuccessListener<? super V> successListener = null; 108 private volatile FailureListener failureListener = null; 109 110 /** 111 * Waits indefinitely until the command completes 112 * and returns the result of the command. 113 * <p> 114 * If the thread is interrupted while waiting for the command 115 * to complete, this method will throw an {@code InterruptedException} 116 * and the thread's interrupt flag will be cleared. 117 * </p> 118 * 119 * @return the server response to the command 120 * 121 * @throws InterruptedException 122 * if the method is interrupted by calling {@link Thread#interrupt()}. 123 * The interrupt flag will be cleared 124 * @throws CancellationException 125 * if the {@code CommandFuture} was cancelled before the command completed 126 * @throws TS3CommandFailedException 127 * if the command fails 128 */ 129 @Override 130 public V get() throws InterruptedException { 131 while (state == FutureState.WAITING) { 132 synchronized (signal) { 133 signal.wait(); 134 } 135 } 136 137 checkForFailure(); 138 return value; 139 } 140 141 /** 142 * Waits for at most the given time until the command completes 143 * and returns the result of the command. 144 * <p> 145 * If the thread is interrupted while waiting for the command 146 * to complete, this method will throw an {@code InterruptedException} 147 * and the thread's interrupt flag will be cleared. 148 * </p> 149 * 150 * @param timeout 151 * the maximum amount of the given time unit to wait 152 * @param unit 153 * the time unit of the timeout argument 154 * 155 * @return the server response to the command 156 * 157 * @throws InterruptedException 158 * if the method is interrupted by calling {@link Thread#interrupt()}. 159 * The interrupt flag will be cleared 160 * @throws TimeoutException 161 * if the given time elapsed without the command completing 162 * @throws CancellationException 163 * if the {@code CommandFuture} was cancelled before the command completed 164 * @throws TS3CommandFailedException 165 * if the command fails 166 */ 167 @Override 168 public V get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { 169 final long end = System.currentTimeMillis() + unit.toMillis(timeout); 170 while (state == FutureState.WAITING && System.currentTimeMillis() < end) { 171 synchronized (signal) { 172 signal.wait(end - System.currentTimeMillis()); 173 } 174 } 175 176 if (state == FutureState.WAITING) throw new TimeoutException(); 177 checkForFailure(); 178 return value; 179 } 180 181 /** 182 * Waits indefinitely until the command completes 183 * and returns the result of the command. 184 * <p> 185 * If the thread is interrupted while waiting for the command 186 * to complete, the interrupt is simply ignored and no 187 * {@link InterruptedException} is thrown. 188 * </p> 189 * 190 * @return the server response to the command 191 * 192 * @throws CancellationException 193 * if the {@code CommandFuture} was cancelled before the command completed 194 * @throws TS3CommandFailedException 195 * if the command fails 196 */ 197 public V getUninterruptibly() { 198 boolean interrupted = false; 199 while (state == FutureState.WAITING) { 200 synchronized (signal) { 201 try { 202 synchronized (signal) { 203 signal.wait(); 204 } 205 } catch (InterruptedException e) { 206 interrupted = true; 207 } 208 } 209 } 210 211 if (interrupted) { 212 // Restore the interrupt for the caller 213 Thread.currentThread().interrupt(); 214 } 215 216 checkForFailure(); 217 return value; 218 } 219 220 /** 221 * Waits for at most the given time until the command completes 222 * and returns the result of the command. 223 * <p> 224 * If the thread is interrupted while waiting for the command 225 * to complete, the interrupt is simply ignored and no 226 * {@link InterruptedException} is thrown. 227 * </p> 228 * 229 * @param timeout 230 * the maximum amount of the given time unit to wait 231 * @param unit 232 * the time unit of the timeout argument 233 * 234 * @return the server response to the command 235 * 236 * @throws TimeoutException 237 * if the given time elapsed without the command completing 238 * @throws CancellationException 239 * if the {@code CommandFuture} was cancelled before the command completed 240 * @throws TS3CommandFailedException 241 * if the command fails 242 */ 243 public V getUninterruptibly(long timeout, TimeUnit unit) throws TimeoutException { 244 final long end = System.currentTimeMillis() + unit.toMillis(timeout); 245 boolean interrupted = false; 246 while (state == FutureState.WAITING && System.currentTimeMillis() < end) { 247 try { 248 synchronized (signal) { 249 signal.wait(end - System.currentTimeMillis()); 250 } 251 } catch (InterruptedException e) { 252 interrupted = true; 253 } 254 } 255 256 if (interrupted) { 257 // Restore the interrupt for the caller 258 Thread.currentThread().interrupt(); 259 } 260 261 if (state == FutureState.WAITING) throw new TimeoutException(); 262 checkForFailure(); 263 return value; 264 } 265 266 /** 267 * Throws an exception if the future was either cancelled or the command failed. 268 * 269 * @throws CancellationException 270 * if the future was cancelled 271 * @throws TS3CommandFailedException 272 * if the command failed 273 */ 274 private void checkForFailure() { 275 if (state == FutureState.CANCELLED) { 276 throw new CancellationException(); 277 } else if (state == FutureState.FAILED) { 278 throw new TS3CommandFailedException(queryError); 279 } 280 } 281 282 @Override 283 public boolean isDone() { 284 return state != FutureState.WAITING; 285 } 286 287 /** 288 * Returns {@code true} if this command completed successfully, 289 * i.e. the future wasn't cancelled and the command completed without throwing an exception. 290 * 291 * @return {@code true} if the command completed successfully 292 */ 293 public boolean isSuccessful() { 294 return state == FutureState.SUCCEEDED; 295 } 296 297 @Override 298 public boolean isCancelled() { 299 return state == FutureState.CANCELLED; 300 } 301 302 /** 303 * Returns {@code true} if the command failed and threw a {@link TS3CommandFailedException}. 304 * 305 * @return {@code true} if the command failed 306 */ 307 public boolean isFailed() { 308 return state == FutureState.FAILED; 309 } 310 311 /** 312 * Sets the value of this future. This will mark the future as successful. 313 * <p> 314 * Furthermore, this will run the {@link SuccessListener}, if one is registered. 315 * All exceptions thrown from the body of the {@code SuccessListener} are caught 316 * so no exceptions can leak into user code. 317 * </p><p> 318 * Note that a future's value can only be set once. Subsequent calls to 319 * this method will be ignored. 320 * </p> 321 * 322 * @param value 323 * the value to set this future to 324 * 325 * @return {@code true} if the command was marked as successful 326 */ 327 public synchronized boolean set(V value) { 328 if (isDone()) return false; // Ignore 329 330 this.state = FutureState.SUCCEEDED; 331 this.value = value; 332 synchronized (signal) { 333 signal.notifyAll(); 334 } 335 336 if (successListener != null) { 337 try { 338 successListener.handleSuccess(value); 339 } catch (Throwable t) { 340 t.printStackTrace(); 341 } 342 } 343 return true; 344 } 345 346 /** 347 * Notifies this future that the command has failed. 348 * <p> 349 * Furthermore, this will run the {@link FailureListener}, if one is registered. 350 * All exceptions thrown from the body of the {@code FailureListener} are caught 351 * so no exceptions can leak into user code. 352 * </p><p> 353 * Note that a future can only fail once. Subsequent calls to this method will be ignored. 354 * </p> 355 * 356 * @param error 357 * the error that was returned from the server 358 * 359 * @return {@code true} if the command was marked as failed 360 */ 361 public synchronized boolean fail(QueryError error) { 362 if (isDone()) return false; // Ignore 363 364 this.state = FutureState.FAILED; 365 this.queryError = error; 366 synchronized (signal) { 367 signal.notifyAll(); 368 } 369 370 if (failureListener != null) { 371 try { 372 failureListener.handleFailure(queryError); 373 } catch (Throwable t) { 374 // Whatever happens, we do not want a user error to leak into our logic 375 t.printStackTrace(); 376 } 377 } 378 return true; 379 } 380 381 /** 382 * {@inheritDoc} 383 * <p> 384 * Cancelling a {@code CommandFuture} will <b>not</b> actually cancel the 385 * execution of the command which was sent to the TeamSpeak server. 386 * </p><p> 387 * It will, however, prevent the {@link SuccessListener} and the 388 * {@link FailureListener} from firing, provided a response from the 389 * server has not yet arrived. 390 * </p> 391 */ 392 @Override 393 public synchronized boolean cancel(boolean mayInterruptIfRunning) { 394 if (isDone()) return false; // Ignore 395 396 this.state = FutureState.CANCELLED; 397 synchronized (signal) { 398 signal.notifyAll(); 399 } 400 return true; 401 } 402 403 /** 404 * Sets a {@link SuccessListener} which will be notified when this future 405 * succeeded and a value has been set. 406 * <p> 407 * If this future has already succeeded, this method will immediately call 408 * the listener method, which will be executed synchronously. 409 * </p> 410 * 411 * @param listener 412 * the listener to notify of a success 413 * 414 * @return this object for chaining 415 */ 416 public synchronized CommandFuture<V> onSuccess(SuccessListener<? super V> listener) { 417 if (successListener != null) { 418 throw new IllegalStateException("Listener already set"); 419 } 420 421 successListener = listener; 422 if (state == FutureState.SUCCEEDED) { 423 listener.handleSuccess(value); 424 } 425 426 return this; 427 } 428 429 /** 430 * Sets a {@link FailureListener} which will be notified when this future 431 * fails because of a error returned by the TeamSpeak server. 432 * <p> 433 * If this future has already failed, this method will immediately call 434 * the listener method, which will be executed synchronously. 435 * </p> 436 * 437 * @param listener 438 * the listener to notify of a failure 439 * 440 * @return this object for chaining 441 */ 442 public synchronized CommandFuture<V> onFailure(FailureListener listener) { 443 if (failureListener != null) { 444 throw new IllegalStateException("Listener already set"); 445 } 446 447 failureListener = listener; 448 if (state == FutureState.FAILED) { 449 listener.handleFailure(queryError); 450 } 451 452 return this; 453 } 454 455 /** 456 * Forwards a success to another future by calling {@link #set(Object)} on 457 * that future with the value this future was set to. 458 * <p> 459 * This will register a {@link SuccessListener}, meaning that you will not 460 * be able to register another {@code SuccessListener}. 461 * </p> 462 * 463 * @param otherFuture 464 * the future to forward a success to 465 * 466 * @return this object for chaining 467 */ 468 public synchronized CommandFuture<V> forwardSuccess(final CommandFuture<? super V> otherFuture) { 469 return onSuccess(new SuccessListener<V>() { 470 @Override 471 public void handleSuccess(V result) { 472 otherFuture.set(result); 473 } 474 }); 475 } 476 477 /** 478 * Forwards a failure to another future by calling {@link #fail(QueryError)} 479 * on that future with the error that caused this future to fail. 480 * <p> 481 * This will register a {@link FailureListener}, meaning that you will not 482 * be able to register another {@code FailureListener}. 483 * </p> 484 * 485 * @param otherFuture 486 * the future to forward a failure to 487 * 488 * @return this object for chaining 489 */ 490 public synchronized CommandFuture<V> forwardFailure(final CommandFuture<?> otherFuture) { 491 return onFailure(new FailureListener() { 492 @Override 493 public void handleFailure(QueryError error) { 494 otherFuture.fail(error); 495 } 496 }); 497 } 498 499 /** 500 * Forwards both a success as well as a failure to another {@code CommandFuture}. 501 * This method just calls both {@link #forwardSuccess(CommandFuture)} and 502 * {@link #forwardFailure(CommandFuture)}. 503 * <p> 504 * This will set both a {@link SuccessListener} as well as a {@link FailureListener}, 505 * so no other listeners can be registered. 506 * </p> 507 * 508 * @param otherFuture 509 * the future which should be notified about 510 */ 511 public synchronized void forwardResult(final CommandFuture<V> otherFuture) { 512 forwardSuccess(otherFuture).forwardFailure(otherFuture); 513 } 514 515 /** 516 * Combines multiple {@code CommandFuture}s into a single future, which will 517 * succeed if all futures succeed and fail as soon as one future fails. 518 * 519 * @param futures 520 * the futures to combine 521 * @param <F> 522 * the common return type of the futures 523 * 524 * @return a future which succeeds if all supplied futures succeed 525 */ 526 @SafeVarargs 527 public static <F> CommandFuture<List<F>> ofAll(CommandFuture<F>... futures) { 528 return ofAll(Arrays.asList(futures)); 529 } 530 531 /** 532 * Combines a collection of {@code CommandFuture}s into a single future, which will 533 * succeed if all futures succeed and fail as soon as one future fails. 534 * 535 * @param futures 536 * the futures to combine 537 * @param <F> 538 * the common return type of the futures 539 * 540 * @return a future which succeeds if all supplied futures succeed 541 */ 542 public static <F> CommandFuture<List<F>> ofAll(final Collection<CommandFuture<F>> futures) { 543 if (futures.isEmpty()) throw new IllegalArgumentException("Requires at least 1 future"); 544 545 @SuppressWarnings("unchecked") final F[] results = (F[]) new Object[futures.size()]; 546 final AtomicInteger successCounter = new AtomicInteger(futures.size()); 547 final CommandFuture<List<F>> combined = new CommandFuture<>(); 548 549 final Iterator<CommandFuture<F>> iterator = futures.iterator(); 550 for (int i = 0; iterator.hasNext(); ++i) { 551 final int index = i; 552 final CommandFuture<F> future = iterator.next(); 553 554 future.forwardFailure(combined).onSuccess(new SuccessListener<F>() { 555 @Override 556 public void handleSuccess(F result) { 557 results[index] = result; 558 559 if (successCounter.decrementAndGet() == 0) { 560 combined.set(Arrays.asList(results)); 561 } 562 } 563 }); 564 } 565 566 return combined; 567 } 568 569 /** 570 * Combines multiple {@code CommandFuture}s into a single future, which will 571 * succeed if any of the futures succeeds and fail if all of the futures fail. 572 * 573 * @param futures 574 * the futures to combine 575 * @param <F> 576 * the common return type of the futures 577 * 578 * @return a future which succeeds if one of the supplied futures succeeds 579 */ 580 @SafeVarargs 581 public static <F> CommandFuture<F> ofAny(CommandFuture<F>... futures) { 582 return ofAny(Arrays.asList(futures)); 583 } 584 585 /** 586 * Combines a collection of {@code CommandFuture}s into a single future, which will 587 * succeed as soon as one of the futures succeeds and fail if all futures fail. 588 * 589 * @param futures 590 * the futures to combine 591 * @param <F> 592 * the common return type of the futures 593 * 594 * @return a future which succeeds if one of the supplied futures succeeds 595 */ 596 public static <F> CommandFuture<F> ofAny(final Collection<CommandFuture<F>> futures) { 597 if (futures.isEmpty()) throw new IllegalArgumentException("Requires at least 1 future"); 598 599 final CommandFuture<F> any = new CommandFuture<>(); 600 final AtomicInteger failureCounter = new AtomicInteger(futures.size()); 601 602 for (CommandFuture<F> future : futures) { 603 future.forwardSuccess(any).onFailure(new FailureListener() { 604 @Override 605 public void handleFailure(QueryError error) { 606 if (failureCounter.decrementAndGet() == 0) { 607 any.fail(error); 608 } 609 } 610 }); 611 } 612 613 return any; 614 } 615 616 /** 617 * A listener which will be notified if the {@link CommandFuture} succeeded. 618 * In that case, {@link #handleSuccess(Object)} will be called with the value 619 * the future has been set to. 620 * <p> 621 * A {@code CommandFuture}'s {@code SuccessListener} can be set by calling 622 * {@link #onSuccess(SuccessListener)}. 623 * </p> 624 * 625 * @param <V> 626 * the type of the value 627 */ 628 public interface SuccessListener<V> { 629 630 /** 631 * The method to be executed when the command succeeds. 632 * 633 * @param result 634 * the result of the command 635 */ 636 void handleSuccess(V result); 637 } 638 639 /** 640 * A listener which will be notified if the {@link CommandFuture} failed. 641 * In that case, {@link #handleFailure(QueryError)} will be called with 642 * the error sent by the TeamSpeak server. 643 * <p> 644 * A {@code CommandFuture}'s {@code FailureListener} can be set by calling 645 * {@link #onFailure(FailureListener)}. 646 * </p> 647 */ 648 public interface FailureListener { 649 650 /** 651 * The method to be executed when the command failed. 652 * 653 * @param error 654 * the error that was sent back from the TeamSpeak server. 655 */ 656 void handleFailure(QueryError error); 657 } 658}