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}