001/**
002 *
003 * Copyright 2017-2018 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack;
018
019import java.util.concurrent.CancellationException;
020import java.util.concurrent.ExecutionException;
021import java.util.concurrent.Future;
022import java.util.concurrent.TimeUnit;
023import java.util.concurrent.TimeoutException;
024
025import org.jivesoftware.smack.packet.Stanza;
026import org.jivesoftware.smack.util.CallbackRecipient;
027import org.jivesoftware.smack.util.ExceptionCallback;
028import org.jivesoftware.smack.util.SuccessCallback;
029
030public abstract class SmackFuture<V, E extends Exception> implements Future<V>, CallbackRecipient<V, E> {
031
032    private boolean cancelled;
033
034    protected V result;
035
036    protected E exception;
037
038    private SuccessCallback<V> successCallback;
039
040    private ExceptionCallback<E> exceptionCallback;
041
042    @Override
043    public final synchronized boolean cancel(boolean mayInterruptIfRunning) {
044        if (isDone()) {
045            return false;
046        }
047
048        cancelled = true;
049
050        if (mayInterruptIfRunning) {
051            notifyAll();
052        }
053
054        return true;
055    }
056
057    @Override
058    public final synchronized boolean isCancelled() {
059        return cancelled;
060    }
061
062    @Override
063    public final synchronized boolean isDone() {
064        return result != null;
065    }
066
067    @Override
068    public CallbackRecipient<V, E> onSuccess(SuccessCallback<V> successCallback) {
069        this.successCallback = successCallback;
070        maybeInvokeCallbacks();
071        return this;
072    }
073
074    @Override
075    public CallbackRecipient<V, E> onError(ExceptionCallback<E> exceptionCallback) {
076        this.exceptionCallback = exceptionCallback;
077        maybeInvokeCallbacks();
078        return this;
079    }
080
081    private V getOrThrowExecutionException() throws ExecutionException {
082        assert (result != null || exception != null || cancelled);
083        if (result != null) {
084            return result;
085        }
086        if (exception != null) {
087            throw new ExecutionException(exception);
088        }
089
090        assert (cancelled);
091        throw new CancellationException();
092    }
093
094    @Override
095    public final synchronized V get() throws InterruptedException, ExecutionException {
096        while (result == null && exception == null && !cancelled) {
097            wait();
098        }
099
100        return getOrThrowExecutionException();
101    }
102
103    public final synchronized V getOrThrow() throws E, InterruptedException {
104        while (result == null && exception == null && !cancelled) {
105            wait();
106        }
107
108        if (exception != null) {
109            throw exception;
110        }
111
112        if (cancelled) {
113            throw new CancellationException();
114        }
115
116        assert result != null;
117        return result;
118    }
119
120    @Override
121    public final synchronized V get(long timeout, TimeUnit unit)
122                    throws InterruptedException, ExecutionException, TimeoutException {
123        final long deadline = System.currentTimeMillis() + unit.toMillis(timeout);
124        while (result != null && exception != null) {
125            final long waitTimeRemaining = deadline - System.currentTimeMillis();
126            if (waitTimeRemaining > 0) {
127                wait(waitTimeRemaining);
128            }
129        }
130
131        if (cancelled) {
132            throw new CancellationException();
133        }
134
135        if (result == null || exception == null) {
136            throw new TimeoutException();
137        }
138
139        return getOrThrowExecutionException();
140    }
141
142    protected final synchronized void maybeInvokeCallbacks() {
143        if (cancelled) {
144            return;
145        }
146
147        if (result != null && successCallback != null) {
148            AbstractXMPPConnection.asyncGo(new Runnable() {
149                @Override
150                public void run() {
151                    successCallback.onSuccess(result);
152                }
153            });
154        }
155        else if (exception != null && exceptionCallback != null) {
156            AbstractXMPPConnection.asyncGo(new Runnable() {
157                @Override
158                public void run() {
159                    exceptionCallback.processException(exception);
160                }
161            });
162        }
163    }
164
165    public static class InternalSmackFuture<V, E extends Exception> extends SmackFuture<V, E> {
166        public final synchronized void setResult(V result) {
167            this.result = result;
168            this.notifyAll();
169
170            maybeInvokeCallbacks();
171        }
172
173        public final synchronized void setException(E exception) {
174            this.exception = exception;
175            this.notifyAll();
176
177            maybeInvokeCallbacks();
178        }
179    }
180
181    public abstract static class InternalProcessStanzaSmackFuture<V, E extends Exception> extends InternalSmackFuture<V, E>
182                    implements StanzaListener, ExceptionCallback<E> {
183
184        /**
185         * This method checks if the given exception is <b>not</b> fatal. If this method returns <code>false</code>,
186         * then the future will automatically set the given exception as failure reason and notify potential waiting
187         * threads.
188         *
189         * @param exception the exception to check.
190         * @return <code>true</code> if the exception is not fatal, <code>false</code> otherwise.
191         */
192        protected abstract boolean isNonFatalException(E exception);
193
194        protected abstract void handleStanza(Stanza stanza);
195
196        @Override
197        public final synchronized void processException(E exception) {
198            if (!isNonFatalException(exception)) {
199                this.exception = exception;
200                this.notifyAll();
201
202                maybeInvokeCallbacks();
203            }
204        }
205
206        /**
207         * Wrapper method for {@link #handleStanza(Stanza)}. Note that this method is <code>synchronized</code>.
208         */
209        @Override
210        public final synchronized void processStanza(Stanza stanza) {
211            handleStanza(stanza);
212        }
213    }
214
215    /**
216     * A simple version of InternalSmackFuture which implements isNonFatalException(E) as always returning
217     * <code>false</code> method.
218     *
219     * @param <V>
220     */
221    public abstract static class SimpleInternalProcessStanzaSmackFuture<V, E extends Exception>
222                    extends InternalProcessStanzaSmackFuture<V, E> {
223        @Override
224        protected boolean isNonFatalException(E exception) {
225            return false;
226        }
227    }
228
229    public static <V, E extends Exception> SmackFuture<V, E> from(V result) {
230        InternalSmackFuture<V, E> future = new InternalSmackFuture<>();
231        future.setResult(result);
232        return future;
233    }
234
235}