package dimitrovskif.smartcache; import android.os.Handler; import com.google.common.reflect.TypeToken; import com.squareup.okhttp.Request; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.concurrent.Executor; import retrofit.Call; import retrofit.CallAdapter; import retrofit.Callback; import retrofit.Response; import retrofit.Retrofit; public class SmartCallFactory implements CallAdapter.Factory { private final CachingSystem cachingSystem; private final Executor asyncExecutor; public SmartCallFactory(CachingSystem cachingSystem){ this.cachingSystem = cachingSystem; this.asyncExecutor = new AndroidExecutor(); } public SmartCallFactory(CachingSystem cachingSystem, Executor executor){ this.cachingSystem = cachingSystem; this.asyncExecutor = executor; } @Override public CallAdapter<SmartCall<?>> get(final Type returnType, final Annotation[] annotations, final Retrofit retrofit) { TypeToken<?> token = TypeToken.of(returnType); if (token.getRawType() != SmartCall.class) { return null; } if (!(returnType instanceof ParameterizedType)) { throw new IllegalStateException( "SmartCall must have generic type (e.g., SmartCall<ResponseBody>)"); } final Type responseType = ((ParameterizedType) returnType).getActualTypeArguments()[0]; final Executor callbackExecutor = asyncExecutor; return new CallAdapter<SmartCall<?>>() { @Override public Type responseType() { return responseType; } @Override public <R> SmartCall<R> adapt(Call<R> call) { return new SmartCallImpl<>(callbackExecutor, call, responseType(), annotations, retrofit, cachingSystem); } }; } static class SmartCallImpl<T> implements SmartCall<T>{ private final Executor callbackExecutor; private final Call<T> baseCall; private final Type responseType; private final Annotation[] annotations; private final Retrofit retrofit; private final CachingSystem cachingSystem; private final Request request; public SmartCallImpl(Executor callbackExecutor, Call<T> baseCall, Type responseType, Annotation[] annotations, Retrofit retrofit, CachingSystem cachingSystem){ this.callbackExecutor = callbackExecutor; this.baseCall = baseCall; this.responseType = responseType; this.annotations = annotations; this.retrofit = retrofit; this.cachingSystem = cachingSystem; // This one is a hack but should create a valid Response (which can later be cloned) this.request = buildRequestFromCall(); } /*** * Inspects an OkHttp-powered Call<T> and builds a Request * * @return A valid Request (that contains query parameters, right method and endpoint) */ private Request buildRequestFromCall(){ try { Field argsField = baseCall.getClass().getDeclaredField("args"); argsField.setAccessible(true); Object[] args = (Object[]) argsField.get(baseCall); Field requestFactoryField = baseCall.getClass().getDeclaredField("requestFactory"); requestFactoryField.setAccessible(true); Object requestFactory = requestFactoryField.get(baseCall); Method createMethod = requestFactory.getClass().getDeclaredMethod("create", Object[].class); createMethod.setAccessible(true); return (Request) createMethod.invoke(requestFactory, new Object[]{args}); }catch(Exception exc){ return null; } } public void enqueueWithCache(final Callback<T> callback) { Runnable enqueueRunnable = new Runnable() { @Override public void run() { /* Read cache */ byte[] data = cachingSystem.getFromCache(buildRequest()); if(data != null) { final T convertedData = SmartUtils.bytesToResponse(retrofit, responseType, annotations, data); Runnable cacheCallbackRunnable = new Runnable() { @Override public void run() { callback.onResponse(Response.success(convertedData), retrofit); } }; callbackExecutor.execute(cacheCallbackRunnable); } /* Enqueue actual network call */ baseCall.enqueue(new Callback<T>() { @Override public void onResponse(final Response<T> response, final Retrofit retrofit) { // Make a main thread runnable Runnable responseRunnable = new Runnable() { @Override public void run() { if (response.isSuccess()) { byte[] rawData = SmartUtils.responseToBytes(retrofit, response.body(), responseType(), annotations); cachingSystem.addInCache(response, rawData); } callback.onResponse(response, retrofit); } }; // Run it on the proper thread callbackExecutor.execute(responseRunnable); } @Override public void onFailure(final Throwable t) { Runnable failureRunnable = new Runnable() { @Override public void run() { callback.onFailure(t); } }; callbackExecutor.execute(failureRunnable); } }); } }; Thread enqueueThread = new Thread(enqueueRunnable); enqueueThread.start(); } @Override public void enqueue(final Callback<T> callback) { if(buildRequest().method().equals("GET")){ enqueueWithCache(callback); }else{ baseCall.enqueue(new Callback<T>() { @Override public void onResponse(final Response<T> response, final Retrofit retrofit) { callbackExecutor.execute(new Runnable() { @Override public void run() { callback.onResponse(response, retrofit); } }); } @Override public void onFailure(final Throwable t) { callbackExecutor.execute(new Runnable() { @Override public void run() { callback.onFailure(t); } }); } }); } } @Override public Type responseType() { return responseType; } @Override public Request buildRequest() { return request.newBuilder().build(); } @Override public SmartCall<T> clone() { return new SmartCallImpl<>(callbackExecutor, baseCall.clone(), responseType(), annotations, retrofit, cachingSystem); } @Override public Response<T> execute() throws IOException { return baseCall.execute(); } @Override public void cancel() { baseCall.cancel(); } } }