retrofit2.mock.BehaviorCall Maven / Gradle / Ivy
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package retrofit2.mock;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import okhttp3.Request;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
final class BehaviorCall implements Call {
final NetworkBehavior behavior;
final ExecutorService backgroundExecutor;
final Call delegate;
private volatile @Nullable Future> task;
volatile boolean canceled;
@GuardedBy("this")
private boolean executed;
BehaviorCall(NetworkBehavior behavior, ExecutorService backgroundExecutor, Call delegate) {
this.behavior = behavior;
this.backgroundExecutor = backgroundExecutor;
this.delegate = delegate;
}
@SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state.
@Override public Call clone() {
return new BehaviorCall<>(behavior, backgroundExecutor, delegate.clone());
}
@Override public Request request() {
return delegate.request();
}
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
@Override public void enqueue(final Callback callback) {
if (callback == null) throw new NullPointerException("callback == null");
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed");
executed = true;
}
task = backgroundExecutor.submit(new Runnable() {
boolean delaySleep() {
long sleepMs = behavior.calculateDelay(MILLISECONDS);
if (sleepMs > 0) {
try {
Thread.sleep(sleepMs);
} catch (InterruptedException e) {
callback.onFailure(BehaviorCall.this, new IOException("canceled"));
return false;
}
}
return true;
}
@Override public void run() {
if (canceled) {
callback.onFailure(BehaviorCall.this, new IOException("canceled"));
} else if (behavior.calculateIsFailure()) {
if (delaySleep()) {
callback.onFailure(BehaviorCall.this, behavior.failureException());
}
} else if (behavior.calculateIsError()) {
if (delaySleep()) {
//noinspection unchecked An error response has no body.
callback.onResponse(BehaviorCall.this, (Response) behavior.createErrorResponse());
}
} else {
delegate.enqueue(new Callback() {
@Override public void onResponse(Call call, Response response) {
if (delaySleep()) {
callback.onResponse(call, response);
}
}
@Override public void onFailure(Call call, Throwable t) {
if (delaySleep()) {
callback.onFailure(call, t);
}
}
});
}
}
});
}
@Override public synchronized boolean isExecuted() {
return executed;
}
@Override public Response execute() throws IOException {
final AtomicReference> responseRef = new AtomicReference<>();
final AtomicReference failureRef = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
enqueue(new Callback() {
@Override public void onResponse(Call call, Response response) {
responseRef.set(response);
latch.countDown();
}
@Override public void onFailure(Call call, Throwable t) {
failureRef.set(t);
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
throw new IOException("canceled");
}
Response response = responseRef.get();
if (response != null) return response;
Throwable failure = failureRef.get();
if (failure instanceof RuntimeException) throw (RuntimeException) failure;
if (failure instanceof IOException) throw (IOException) failure;
throw new RuntimeException(failure);
}
@Override public void cancel() {
canceled = true;
Future> task = this.task;
if (task != null) {
task.cancel(true);
}
}
@Override public boolean isCanceled() {
return canceled;
}
}