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}