Skip to content

Commit

Permalink
Merge pull request square#2176 from square/jw/2017-01-30/stateful-calls
Browse files Browse the repository at this point in the history
Make Calls more stateful and add deferring factory.
  • Loading branch information
swankjesse authored Jan 30, 2017
2 parents 77a65b7 + 22004b2 commit 29a4435
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 29a4435

Please sign in to comment.