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}