001/**
002 *
003 * Copyright 2017 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.ExecutionException;
020import java.util.concurrent.Future;
021import java.util.concurrent.TimeUnit;
022import java.util.concurrent.TimeoutException;
023
024import org.jivesoftware.smack.SmackException.NotConnectedException;
025import org.jivesoftware.smack.packet.Stanza;
026
027public abstract class SmackFuture<V> implements Future<V> {
028
029    private boolean cancelled;
030
031    private V result;
032
033    protected Exception exception;
034
035    private SuccessCallback<V> successCallback;
036
037    private ExceptionCallback exceptionCallback;
038
039    @Override
040    public synchronized final boolean cancel(boolean mayInterruptIfRunning) {
041        if (isDone()) {
042            return false;
043        }
044
045        cancelled = true;
046        return true;
047    }
048
049    @Override
050    public synchronized final boolean isCancelled() {
051        return cancelled;
052    }
053
054    @Override
055    public synchronized final boolean isDone() {
056        return result != null;
057    }
058
059    public void onSuccessOrError(SuccessCallback<V> successCallback, ExceptionCallback exceptionCallback) {
060        this.successCallback = successCallback;
061        this.exceptionCallback = exceptionCallback;
062
063        maybeInvokeCallbacks();
064    }
065
066    public void onSuccess(SuccessCallback<V> successCallback) {
067        onSuccessOrError(successCallback, null);
068    }
069
070    public void onError(ExceptionCallback exceptionCallback) {
071        onSuccessOrError(null, exceptionCallback);
072    }
073
074    private final V getResultOrThrow() throws ExecutionException {
075        assert (result != null || exception != null);
076        if (result != null) {
077            return result;
078        }
079
080        throw new ExecutionException(exception);
081    }
082
083    @Override
084    public synchronized final V get() throws InterruptedException, ExecutionException {
085        while (result == null && exception == null) {
086            wait();
087        }
088
089        return getResultOrThrow();
090    }
091
092    @Override
093    public synchronized final V get(long timeout, TimeUnit unit)
094                    throws InterruptedException, ExecutionException, TimeoutException {
095        final long deadline = System.currentTimeMillis() + unit.toMillis(timeout);
096        while (result != null && exception != null) {
097            final long waitTimeRemaining = deadline - System.currentTimeMillis();
098            if (waitTimeRemaining > 0) {
099                wait(waitTimeRemaining);
100            }
101        }
102
103        if (result == null || exception == null) {
104            throw new TimeoutException();
105        }
106
107        return getResultOrThrow();
108    }
109
110    protected final synchronized void maybeInvokeCallbacks() {
111        if (result != null && successCallback != null) {
112            successCallback.onSuccess(result);
113        } else if (exception != null && exceptionCallback != null) {
114            exceptionCallback.processException(exception);
115        }
116    }
117
118    /**
119     * This method checks if the given exception is <b>not</b> fatal. If this method returns <code>false</code>, then
120     * the future will automatically set the given exception as failure reason and notify potential waiting threads.
121     *
122     * @param exception the exception to check.
123     * @return <code>true</code> if the exception is not fatal, <code>false</code> otherwise.
124     */
125    protected abstract boolean isNonFatalException(Exception exception);
126
127    protected abstract void handleStanza(Stanza stanza) throws NotConnectedException, InterruptedException;
128
129    protected final void setResult(V result) {
130        assert (Thread.holdsLock(this));
131
132        this.result = result;
133        this.notifyAll();
134
135        maybeInvokeCallbacks();
136    }
137
138    public static abstract class InternalSmackFuture<V> extends SmackFuture<V> implements StanzaListener, ExceptionCallback {
139
140        @Override
141        public synchronized final void processException(Exception exception) {
142            if (!isNonFatalException(exception)) {
143                this.exception = exception;
144                this.notifyAll();
145
146                maybeInvokeCallbacks();
147            }
148        }
149
150        /**
151         * Wrapper method for {@link #handleStanza(Stanza)}. Note that this method is <code>synchronized</code>.
152         */
153        @Override
154        public synchronized final void processStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
155            handleStanza(stanza);
156        }
157    }
158
159    /**
160     * A simple version of InternalSmackFuture which implements {@link #isNonFatalException(Exception)} as always returning <code>false</code> method.
161     *
162     * @param <V>
163     */
164    public static abstract class SimpleInternalSmackFuture<V> extends InternalSmackFuture<V> {
165        @Override
166        protected boolean isNonFatalException(Exception exception) {
167            return false;
168        }
169    }
170}