content: /**
   * Copyright 2010-present Facebook.
   *
   * 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 com.facebook.widget;
  
  import android.content.Context;
  import android.os.Handler;
  import android.support.v4.content.Loader;
  import com.facebook.*;
  import com.facebook.model.GraphObject;
  import com.facebook.model.GraphObjectList;
  import com.facebook.internal.CacheableRequestBatch;
  
  class GraphObjectPagingLoader<T extends GraphObject> extends Loader<SimpleGraphObjectCursor<T>> {
      private final Class<T> graphObjectClass;
      private boolean skipRoundtripIfCached;
      private Request originalRequest;
      private Request currentRequest;
      private Request nextRequest;
      private OnErrorListener onErrorListener;
      private SimpleGraphObjectCursor<T> cursor;
      private boolean appendResults = false;
      private boolean loading = false;
  
      public interface OnErrorListener {
          public void onError(FacebookException error, GraphObjectPagingLoader<?> loader);
      }
  
      public GraphObjectPagingLoader(Context context, Class<T> graphObjectClass) {
          super(context);
  
          this.graphObjectClass = graphObjectClass;
      }
  
      public OnErrorListener getOnErrorListener() {
          return onErrorListener;
      }
  
      public void setOnErrorListener(OnErrorListener listener) {
          this.onErrorListener = listener;
      }
  
      public SimpleGraphObjectCursor<T> getCursor() {
          return cursor;
      }
  
      public void clearResults() {
          nextRequest = null;
          originalRequest = null;
          currentRequest = null;
  
          deliverResult(null);
      }
  
      public boolean isLoading() {
          return loading;
      }
  
      public void startLoading(Request request, boolean skipRoundtripIfCached) {
          originalRequest = request;
          startLoading(request, skipRoundtripIfCached, 0);
      }
  
      public void refreshOriginalRequest(long afterDelay) {
          if (originalRequest == null) {
              throw new FacebookException(
                      "refreshOriginalRequest may not be called until after startLoading has been called.");
          }
          startLoading(originalRequest, false, afterDelay);
      }
  
      public void followNextLink() {
          if (nextRequest != null) {
              appendResults = true;
              currentRequest = nextRequest;
  
              currentRequest.setCallback(new Request.Callback() {
                  @Override
                  public void onCompleted(Response response) {
                      requestCompleted(response);
                  }
              });
  
              loading = true;
              CacheableRequestBatch batch = putRequestIntoBatch(currentRequest, skipRoundtripIfCached);
              Request.executeBatchAsync(batch);
          }
      }
  
      @Override
      public void deliverResult(SimpleGraphObjectCursor<T> cursor) {
          SimpleGraphObjectCursor<T> oldCursor = this.cursor;
          this.cursor = cursor;
  
          if (isStarted()) {
              super.deliverResult(cursor);
  
              if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
                  oldCursor.close();
              }
          }
      }
  
      @Override
      protected void onStartLoading() {
          super.onStartLoading();
  
          if (cursor != null) {
              deliverResult(cursor);
          }
      }
  
      private void startLoading(Request request, boolean skipRoundtripIfCached, long afterDelay) {
          this.skipRoundtripIfCached = skipRoundtripIfCached;
          appendResults = false;
          nextRequest = null;
          currentRequest = request;
          currentRequest.setCallback(new Request.Callback() {
              @Override
              public void onCompleted(Response response) {
                  requestCompleted(response);
              }
          });
  
          // We are considered loading even if we have a delay.
          loading = true;
  
          final RequestBatch batch = putRequestIntoBatch(request, skipRoundtripIfCached);
          Runnable r = new Runnable() {
              @Override
              public void run() {
                  Request.executeBatchAsync(batch);
              }
          };
          if (afterDelay == 0) {
              r.run();
          } else {
              Handler handler = new Handler();
              handler.postDelayed(r, afterDelay);
          }
      }
  
      private CacheableRequestBatch putRequestIntoBatch(Request request, boolean skipRoundtripIfCached) {
          // We just use the request URL as the cache key.
          CacheableRequestBatch batch = new CacheableRequestBatch(request);
          // We use the default cache key (request URL).
          batch.setForceRoundTrip(!skipRoundtripIfCached);
          return batch;
      }
  
      private void requestCompleted(Response response) {
          Request request = response.getRequest();
          if (request != currentRequest) {
              return;
          }
  
          loading = false;
          currentRequest = null;
  
          FacebookRequestError requestError = response.getError();
          FacebookException exception = (requestError == null) ? null : requestError.getException();
          if (response.getGraphObject() == null && exception == null) {
              exception = new FacebookException("GraphObjectPagingLoader received neither a result nor an error.");
          }
  
          if (exception != null) {
              nextRequest = null;
  
              if (onErrorListener != null) {
                  onErrorListener.onError(exception, this);
              }
          } else {
              addResults(response);
          }
      }
  
      private void addResults(Response response) {
          SimpleGraphObjectCursor<T> cursorToModify = (cursor == null || !appendResults) ? new SimpleGraphObjectCursor<T>() :
                  new SimpleGraphObjectCursor<T>(cursor);
  
          PagedResults result = response.getGraphObjectAs(PagedResults.class);
          boolean fromCache = response.getIsFromCache();
  
          GraphObjectList<T> data = result.getData().castToListOf(graphObjectClass);
          boolean haveData = data.size() > 0;
  
          if (haveData) {
              nextRequest = response.getRequestForPagedResults(Response.PagingDirection.NEXT);
  
              cursorToModify.addGraphObjects(data, fromCache);
              cursorToModify.setMoreObjectsAvailable(true);
          }
  
          if (!haveData) {
              cursorToModify.setMoreObjectsAvailable(false);
              cursorToModify.setFromCache(fromCache);
  
              nextRequest = null;
          }
  
          // Once we get any set of results NOT from the cache, stop trying to get any future ones
          // from it.
          if (!fromCache) {
              skipRoundtripIfCached = false;
          }
  
          deliverResult(cursorToModify);
      }
  
      interface PagedResults extends GraphObject {
          GraphObjectList<GraphObject> getData();
      }
  }
  