Skip to content

Commit

Permalink
Make Calls more stateful and add deferring factory.
Browse files Browse the repository at this point in the history
The deferring factory allows you to provide functionality that can change when Call.clone() is being used.
  • Loading branch information
JakeWharton committed Jan 30, 2017
1 parent 77a65b7 commit 22004b2
Show file tree
Hide file tree
Showing 2 changed files with 381 additions and 45 deletions.
163 changes: 118 additions & 45 deletions retrofit-mock/src/main/java/retrofit2/mock/Calls.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,82 +16,155 @@
package retrofit2.mock;

import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import okhttp3.Request;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

/** Factory methods for creating {@link Call} instances which immediately respond or fail. */
public final class Calls {
/**
* Invokes {@code callable} once for the returned {@link Call} and once for each instance that is
* obtained from {@linkplain Call#clone() cloning} the returned {@link Call}.
*/
public static <T> Call<T> defer(Callable<Call<T>> callable) {
return new DeferredCall<>(callable);
}

public static <T> Call<T> response(T successValue) {
return response(Response.success(successValue));
return new FakeCall<>(Response.success(successValue), null);
}

public static <T> Call<T> response(Response<T> response) {
return new FakeCall<>(response, null);
}

public static <T> Call<T> response(final Response<T> response) {
return new Call<T>() {
@Override public Response<T> execute() throws IOException {
public static <T> Call<T> failure(IOException failure) {
return new FakeCall<>(null, failure);
}

private Calls() {
throw new AssertionError("No instances.");
}

static final class FakeCall<T> implements Call<T> {
private final Response<T> response;
private final IOException error;
private final AtomicBoolean canceled = new AtomicBoolean();
private final AtomicBoolean executed = new AtomicBoolean();

FakeCall(Response<T> response, IOException error) {
if ((response == null) == (error == null)) {
throw new AssertionError("Only one of response or error can be set.");
}
this.response = response;
this.error = error;
}

@Override public Response<T> execute() throws IOException {
if (!executed.compareAndSet(false, true)) {
throw new IllegalStateException("Already executed");
}
if (canceled.get()) {
throw new IOException("canceled");
}
if (response != null) {
return response;
}
throw error;
}

@Override public void enqueue(Callback<T> callback) {
@Override public void enqueue(Callback<T> callback) {
if (callback == null) {
throw new NullPointerException("callback == null");
}
if (!executed.compareAndSet(false, true)) {
throw new IllegalStateException("Already executed");
}
if (canceled.get()) {
callback.onFailure(this, new IOException("canceled"));
} else if (response != null) {
callback.onResponse(this, response);
} else {
callback.onFailure(this, error);
}
}

@Override public boolean isExecuted() {
return false;
}
@Override public boolean isExecuted() {
return executed.get();
}

@Override public void cancel() {
}
@Override public void cancel() {
canceled.set(true);
}

@Override public boolean isCanceled() {
return false;
}
@Override public boolean isCanceled() {
return canceled.get();
}

@SuppressWarnings("CloneDoesntCallSuperClone") // Immutable object.
@Override public Call<T> clone() {
return this;
}
@Override public Call<T> clone() {
return new FakeCall<>(response, error);
}

@Override public Request request() {
@Override public Request request() {
if (response != null) {
return response.raw().request();
}
};
return new Request.Builder().url("http://localhost").build();
}
}

public static <T> Call<T> failure(final IOException failure) {
return new Call<T>() {
@Override public Response<T> execute() throws IOException {
throw failure;
static final class DeferredCall<T> implements Call<T> {
private final Callable<Call<T>> callable;
private Call<T> delegate;

DeferredCall(Callable<Call<T>> callable) {
this.callable = callable;
}

private synchronized Call<T> getDelegate() {
Call<T> delegate = this.delegate;
if (delegate == null) {
try {
delegate = callable.call();
} catch (IOException e) {
delegate = failure(e);
} catch (Exception e) {
throw new IllegalStateException("Callable threw unrecoverable exception", e);
}
this.delegate = delegate;
}
return delegate;
}

@Override public void enqueue(Callback<T> callback) {
callback.onFailure(this, failure);
}
@Override public Response<T> execute() throws IOException {
return getDelegate().execute();
}

@Override public boolean isExecuted() {
return false;
}
@Override public void enqueue(Callback<T> callback) {
getDelegate().enqueue(callback);
}

@Override public void cancel() {
}
@Override public boolean isExecuted() {
return getDelegate().isExecuted();
}

@Override public boolean isCanceled() {
return false;
}
@Override public void cancel() {
getDelegate().cancel();
}

@SuppressWarnings("CloneDoesntCallSuperClone") // Immutable object.
@Override public Call<T> clone() {
return this;
}
@Override public boolean isCanceled() {
return getDelegate().isCanceled();
}

@Override public Request request() {
return new Request.Builder().url("http://localhost").build();
}
};
}
@Override public Call<T> clone() {
return new DeferredCall<>(callable);
}

private Calls() {
throw new AssertionError("No instances.");
@Override public Request request() {
return getDelegate().request();
}
}
}
Loading

0 comments on commit 22004b2

Please sign in to comment.