All Downloads are FREE. Search and download functionalities are using the official Maven repository.

retrofit.processor.RetrofitProcessor Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015 8tory, Inc.
 * Copyright (C) 2012 Google, 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 retrofit.processor;

import retrofit.http.Retrofit;
import retrofit.RetrofitError;
import retrofit.Callback;
import com.google.auto.service.AutoService;
import com.google.common.base.Functions;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import java.beans.Introspector;
import java.io.IOException;
import java.io.Serializable;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import javax.annotation.Generated;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import javax.lang.model.type.MirroredTypeException;

/**
 * Javac annotation processor (compiler plugin) for value types; user code never references this
 * class.
 *
 * @author Éamonn McManus
 * @see retrofit.http.Retrofit
 */
@AutoService(Processor.class)
public class RetrofitProcessor extends AbstractProcessor {
  public RetrofitProcessor() {
  }

  @Override
  public Set getSupportedAnnotationTypes() {
    return ImmutableSet.of(Retrofit.class.getName());
  }

  @Override
  public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
  }

  private ErrorReporter errorReporter;

  /**
   * Qualified names of {@code @Retrofit} classes that we attempted to process but had to abandon
   * because we needed other types that they referenced and those other types were missing.
   */
  private final List deferredTypeNames = new ArrayList();

  @Override
  public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    errorReporter = new ErrorReporter(processingEnv);
  }

  @Override
  public boolean process(Set annotations, RoundEnvironment roundEnv) {
    List deferredTypes = new ArrayList();
    for (String deferred : deferredTypeNames) {
      deferredTypes.add(processingEnv.getElementUtils().getTypeElement(deferred));
    }
    if (roundEnv.processingOver()) {
      // This means that the previous round didn't generate any new sources, so we can't have found
      // any new instances of @Retrofit; and we can't have any new types that are the reason a type
      // was in deferredTypes.
      for (TypeElement type : deferredTypes) {
        errorReporter.reportError("Did not generate @Retrofit class for " + type.getQualifiedName()
            + " because it references undefined types", type);
      }
      return false;
    }
    Collection annotatedElements =
        roundEnv.getElementsAnnotatedWith(Retrofit.class);
    List types = new ImmutableList.Builder()
        .addAll(deferredTypes)
        .addAll(ElementFilter.typesIn(annotatedElements))
        .build();
    deferredTypeNames.clear();
    for (TypeElement type : types) {
      try {
        processType(type);
      } catch (AbortProcessingException e) {
        // We abandoned this type; continue with the next.
      } catch (MissingTypeException e) {
        // We abandoned this type, but only because we needed another type that it references and
        // that other type was missing. It is possible that the missing type will be generated by
        // further annotation processing, so we will try again on the next round (perhaps failing
        // again and adding it back to the list). We save the name of the @Retrofit type rather
        // than its TypeElement because it is not guaranteed that it will be represented by
        // the same TypeElement on the next round.
        deferredTypeNames.add(type.getQualifiedName().toString());
      } catch (RuntimeException e) {
        // Don't propagate this exception, which will confusingly crash the compiler.
        // Instead, report a compiler error with the stack trace.
        String trace = Throwables.getStackTraceAsString(e);
        errorReporter.reportError("@Retrofit processor threw an exception: " + trace, type);
      }
    }
    return false;  // never claim annotation, because who knows what other processors want?
  }

  private String generatedClassName(TypeElement type, String prefix) {
    String name = type.getSimpleName().toString();
    while (type.getEnclosingElement() instanceof TypeElement) {
      type = (TypeElement) type.getEnclosingElement();
      name = type.getSimpleName() + "_" + name;
    }
    String pkg = TypeSimplifier.packageNameOf(type);
    String dot = pkg.isEmpty() ? "" : ".";
    return pkg + dot + prefix + name;
  }

  private String generatedSubclassName(TypeElement type) {
    return generatedClassName(type, "Retrofit_");
  }

  public interface Action1 {
      void call(T t);
  }

  private void onAnnotationForProperty(AnnotationMirror annotation) {
      onAnnotationForProperty.call(annotation);
  }

  private Action1 onAnnotationForProperty;

  private void annotationForProperty(Action1 onAnnotationForProperty) {
      this.onAnnotationForProperty = onAnnotationForProperty;
  }

  public static class Part {
    private final String name;
    private final String mimeType;
    private final boolean isFile;
    private final boolean isTypedFile;
    private final boolean isTypedString;
    private final boolean isTypedByteArray;

    public Part(String name, String mimeType, boolean isFile, boolean isTypedFile, boolean isTypedString, boolean isTypedByteArray) {
      this.name = name;
      this.mimeType = mimeType;
      this.isFile = isFile;
      this.isTypedFile = isTypedFile;
      this.isTypedString = isTypedString;
      this.isTypedByteArray = isTypedByteArray;
    }

    public String getName() {
      return name;
    }

    public String getMimeType() {
      return mimeType;
    }
    public boolean isFile() {
      return isFile;
    }
    public boolean isTypedFile() {
      return isTypedFile;
    }
    public boolean isTypedString() {
      return isTypedString;
    }
    public boolean isTypedByteArray() {
      return isTypedByteArray;
    }
  }

  /**
   * A property of an {@code @Retrofit} class, defined by one of its abstract methods.
   * An instance of this class is made available to the Velocity template engine for
   * each property. The public methods of this class define JavaBeans-style properties
   * that are accessible from templates. For example {@link #getType()} means we can
   * write {@code $p.type} for a Velocity variable {@code $p} that is a {@code Property}.
   */
  public static class Property {
    private final String name;
    private final String identifier;
    private final ExecutableElement method;
    private final String type;
    private String typeArgs;
    private String typeArgs2;
    private final ImmutableList annotations;
    private final String args;
    private final String path;
    private final Map queries;
    private final List queryMaps;
    private final List queryBundles;
    private final boolean isGet;
    private final boolean isPut;
    private final boolean isPost;
    private final boolean isDelete;
    private final boolean isHead;
    private final boolean isObservable; // returnType Observable
    private final boolean isResponseType; // returnType == Response || returnType
    private final boolean isVoid;
    private final boolean isBlocking;
    private final String body;
    private final String callbackType;
    private final TypeMirror callbackTypeMirror;
    private final String callbackArg;
    private final ProcessingEnvironment processingEnv;
    private final TypeSimplifier typeSimplifier;
    private final List permissions;
    private final boolean isAuthenticated;
    private final boolean isSingletonRequestInterceptor;
    private final Map headers;
    private final Map fields;
    private final Map parts;
    private String callbackName;
    public final String converter;
    public String gsonConverter = "";
    public final String errorHandler;
    public final String logLevel;
    public final String requestInterceptor;

    Property(
        String name,
        String identifier,
        ExecutableElement method,
        String type,
        TypeSimplifier typeSimplifier,
        ProcessingEnvironment processingEnv
        ) {
      this.name = name;
      this.identifier = identifier;
      this.method = method;
      this.type = type;
      this.typeSimplifier = typeSimplifier;
      this.processingEnv = processingEnv;
      this.annotations = buildAnnotations(typeSimplifier);
      this.args = formalTypeArgsString(method);
      this.path = buildPath(method);
      this.queries = buildQueries(method);
      this.queryMaps = buildQueryMaps(method);
      this.queryBundles = buildQueryBundles(method);
      this.isGet = buildIsGet(method);
      this.isPut = buildIsPut(method);
      this.isPost = buildIsPost(method);
      this.isDelete = buildIsDelete(method);
      this.isHead = buildIsHead(method);
      this.isAuthenticated = buildIsAuthenticated(method);
      this.isObservable = buildIsObservable(method);
      this.body = buildBody(method);
      this.callbackTypeMirror = buildCallbackTypeMirror(method);
      //this.callbackType = buildTypeArgument(callbackTypeMirror);
      this.callbackType = buildCallbackTypeArgument(method);
      this.callbackArg = buildTypeArguments(callbackType);
      this.isBlocking = !isCallback() && !isObservable();
      this.isResponseType = buildIsResponseType(method);
      if (isObservable()) {
        this.typeArgs = buildTypeArguments(type); // Observable> -> List
        this.typeArgs2 = buildTypeArguments(typeArgs); // Observable> -> String
      } else if (isCallback()) {
        this.typeArgs = callbackType;  // Callback> -> List
        this.typeArgs2 = buildTypeArguments(typeArgs); // Callback> -> String
      } else { // isBlocking
        this.typeArgs = type;
      }
      if ("".equals(typeArgs)) typeArgs = callbackType;
      this.isVoid = buildIsVoid(method);
      this.permissions = buildPermissions(method);
      this.headers = buildHeaders(method);
      this.fields = buildFields(method);
      this.parts = buildParts(method);
      this.converter = buildConverter(method);
      this.errorHandler = buildErrorHandler(method);
      this.logLevel = buildLogLevel(method);
      this.requestInterceptor = buildRequestInterceptor(method);
      this.isSingletonRequestInterceptor = buildIsSingletonRequestInterceptor(method);
    }

    private String buildRequestInterceptor(ExecutableElement method) {
      String name = "";
      Retrofit.RequestInterceptor requestInterceptorAnnotation = method.getAnnotation(Retrofit.RequestInterceptor.class);
      if (requestInterceptorAnnotation != null) {
        TypeMirror requestInterceptor = null;
        try {
          requestInterceptor = getTypeMirror(processingEnv, requestInterceptorAnnotation.value());
        } catch (MirroredTypeException mte) {
          // http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/
          requestInterceptor = mte.getTypeMirror();
        }
        name = typeSimplifier.simplify(requestInterceptor);
      }
      return name;
    }

    private String buildConverter(ExecutableElement method) {
      String converterName = "";
      Retrofit.Converter converterAnnotation = method.getAnnotation(Retrofit.Converter.class);
      if (converterAnnotation != null) {
        TypeMirror converter = null;
        try {
          converter = getTypeMirror(processingEnv, converterAnnotation.value());
        } catch (MirroredTypeException mte) {
          // http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/
          converter = mte.getTypeMirror();
        }
        converterName = typeSimplifier.simplify(converter);
        TypeMirror gsonConverterType = getTypeMirror(processingEnv, retrofit.converter.GsonConverter.class);
        Types typeUtils = processingEnv.getTypeUtils();
        if (typeUtils.isSubtype(gsonConverterType, converter)) {
          this.gsonConverter = converterName;
        }
      }
      return converterName;
    }

    private String buildErrorHandler(ExecutableElement method) {
      String name = "";
      Retrofit.ErrorHandler errorHandlerAnnotation = method.getAnnotation(Retrofit.ErrorHandler.class);
      if (errorHandlerAnnotation != null) {
        TypeMirror errorHandler = null;
        try {
          errorHandler = getTypeMirror(processingEnv, errorHandlerAnnotation.value());
        } catch (MirroredTypeException mte) {
          // http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/
          errorHandler = mte.getTypeMirror();
        }
        name = typeSimplifier.simplify(errorHandler);
      }
      return name;
    }

    private String buildLogLevel(ExecutableElement method) {
      Retrofit.LogLevel logLevelAnnotation = method.getAnnotation(Retrofit.LogLevel.class);
      if (logLevelAnnotation != null) {
        return ""; // TODO
      }
      return "";
    }

    private boolean buildIsObservable(ExecutableElement method) {
      Types typeUtils = processingEnv.getTypeUtils();
      TypeMirror obsType = getTypeMirror(processingEnv, rx.Observable.class);
      TypeMirror returnType = method.getReturnType();

      if (returnType instanceof DeclaredType) {
        List params = ((DeclaredType) returnType).getTypeArguments();
        if (params.size() == 1) {
          obsType = typeUtils.getDeclaredType((TypeElement) typeUtils.asElement(obsType), new TypeMirror[] {params.get(0)});

          return typeUtils.isSubtype(returnType, obsType);
        }
      }

      return false;
    }

    private boolean buildIsVoid(ExecutableElement method) {
      return method.getReturnType().getKind() == TypeKind.VOID;
    }

    private boolean buildIsResponseType(ExecutableElement method) {
      Types typeUtils = processingEnv.getTypeUtils();
      TypeMirror responseType = getTypeMirror(processingEnv, retrofit.client.Response.class);
      TypeMirror returnType = method.getReturnType();

      if (isObservable()) {
        List params = ((DeclaredType) returnType).getTypeArguments();
        if (params.size() == 1) { // Observable
          returnType = params.get(0); // Response
          return typeUtils.isSubtype(returnType, responseType);
        }
      } else if (isCallback()) {
        List params = ((DeclaredType) callbackTypeMirror).getTypeArguments();
        if (params.size() == 1) { //  Callback
          returnType = params.get(0); // Response
          return typeUtils.isSubtype(returnType, responseType);
        }
      }

      return typeUtils.isSubtype(returnType, responseType); // isBlocking()
    }

    private String buildTypeArguments(String type) {
      Pattern pattern = Pattern.compile( "<(.*)>" );
      Matcher m = pattern.matcher(type);
      if (m.find()) return m.group(1);
      return "";
    }

    private TypeMirror buildCallbackTypeMirror(ExecutableElement method) {
      Types typeUtils = processingEnv.getTypeUtils();
      TypeMirror callback = getTypeMirror(processingEnv, Callback.class);

      List parameters = method.getParameters();
      for (VariableElement parameter : parameters) {
        TypeMirror type = parameter.asType();
        if (type instanceof DeclaredType) {
          List params = ((DeclaredType) type).getTypeArguments();
          if (params.size() == 1) {
            callback = typeUtils.getDeclaredType((TypeElement) typeUtils.asElement(callback), new TypeMirror[] {params.get(0)});

            if (typeUtils.isSubtype(type, callback)) {
              this.callbackName = parameter.getSimpleName().toString();
              return callback;
            }
          }
        }
      }
      return null;
    }

    private String buildCallbackTypeArgument(ExecutableElement method) {
      Types typeUtils = processingEnv.getTypeUtils();
      TypeMirror callback = getTypeMirror(processingEnv, Callback.class);

      List parameters = method.getParameters();
      for (VariableElement parameter : parameters) {
        TypeMirror type = parameter.asType();
        if (type instanceof DeclaredType) {
          List params = ((DeclaredType) type).getTypeArguments();
          if (params.size() == 1) {
            callback = typeUtils.getDeclaredType((TypeElement) typeUtils.asElement(callback), new TypeMirror[] {params.get(0)});

            if (typeUtils.isSubtype(type, callback)) {
              return typeSimplifier.simplify(params.get(0));
            }
          }
        }
      }
      return "";
    }

    private String buildTypeArgument(TypeMirror type) {
      if (type != null) {
        List params = ((DeclaredType) type).getTypeArguments();
        return typeSimplifier.simplify(params.get(0));
      }
      return "";
    }

    public boolean buildIsGet(ExecutableElement method) {
      // TODO duplicated routine
      return method.getAnnotation(Retrofit.GET.class) != null || method.getAnnotation(retrofit.http.HEAD.class) != null;
    }

    public boolean buildIsPost(ExecutableElement method) {
      // TODO duplicated routine
      return method.getAnnotation(Retrofit.POST.class) != null || method.getAnnotation(retrofit.http.HEAD.class) != null;
    }

    public boolean buildIsPut(ExecutableElement method) {
      // TODO duplicated routine
      return method.getAnnotation(Retrofit.PUT.class) != null || method.getAnnotation(retrofit.http.HEAD.class) != null;
    }

    public boolean buildIsDelete(ExecutableElement method) {
      // TODO duplicated routine
      return method.getAnnotation(Retrofit.DELETE.class) != null || method.getAnnotation(retrofit.http.HEAD.class) != null;
    }

    public boolean buildIsHead(ExecutableElement method) {
      // TODO duplicated routine
      return method.getAnnotation(Retrofit.HEAD.class) != null || method.getAnnotation(retrofit.http.HEAD.class) != null;
    }

    public boolean buildIsAuthenticated(ExecutableElement method) {
      return method.getAnnotation(Retrofit.Authenticated.class) != null;
    }

    public boolean buildIsSingletonRequestInterceptor(ExecutableElement method) {
      javax.inject.Singleton singleton = null;
      Retrofit.RequestInterceptor requestInterceptorAnnotation = method.getAnnotation(Retrofit.RequestInterceptor.class);
      if (requestInterceptorAnnotation != null) {
        TypeMirror requestInterceptor = null;
        try {
          requestInterceptor = getTypeMirror(processingEnv, requestInterceptorAnnotation.value());
        } catch (MirroredTypeException mte) {
          // http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/
          requestInterceptor = mte.getTypeMirror();
        }

        Types typeUtils = processingEnv.getTypeUtils();
        TypeElement requestInterceptorType = (TypeElement) typeUtils.asElement(requestInterceptor);
        singleton = requestInterceptorType.getAnnotation(javax.inject.Singleton.class);
      }
      return singleton != null;
    }

    public String buildBody(ExecutableElement method) {
      String body = "";

      if (method.getAnnotation(Retrofit.POST.class) == null && method.getAnnotation(retrofit.http.POST.class) == null) return body;

      List parameters = method.getParameters();
      for (VariableElement parameter : parameters) {
        if (parameter.getAnnotation(Retrofit.Body.class) != null || method.getAnnotation(retrofit.http.Body.class) != null) {
          body = parameter.getSimpleName().toString();
        }
      }
      return body;
    }

    public List buildPermissions(ExecutableElement method) {
      Retrofit.GET get = method.getAnnotation(Retrofit.GET.class);
      Retrofit.PUT put = method.getAnnotation(Retrofit.PUT.class);
      Retrofit.POST post = method.getAnnotation(Retrofit.POST.class);
      Retrofit.DELETE delete = method.getAnnotation(Retrofit.DELETE.class);
      Retrofit.HEAD head = method.getAnnotation(Retrofit.HEAD.class);
      if (get != null) return Arrays.asList(get.permissions());
      if (put != null) return Arrays.asList(put.permissions());
      if (post != null) return Arrays.asList(post.permissions());
      if (delete != null) return Arrays.asList(delete.permissions());
      if (head != null) return Arrays.asList(head.permissions());
      return Collections.emptyList();
    }

    public Map buildHeaders(ExecutableElement method) {
      Map map = new HashMap();
      String[] headers;

      Retrofit.Headers headersAnnotation = method.getAnnotation(Retrofit.Headers.class);
      retrofit.http.Headers headers1Annotation = method.getAnnotation(retrofit.http.Headers.class);
      if (headersAnnotation != null) {
        headers = headersAnnotation.value();
      } else if (headers1Annotation != null) {
        headers = headers1Annotation.value();
      } else {
        return Collections.emptyMap();
      }

      for (String header : headers) {
        String[] tokens = header.split(":");
        map.put(tokens[0].trim(), "\"" + tokens[1].trim() + "\"");
      }

      List parameters = method.getParameters();
      for (VariableElement parameter : parameters) {
        Retrofit.Header header = parameter.getAnnotation(Retrofit.Header.class);
        retrofit.http.Header header1 = parameter.getAnnotation(retrofit.http.Header.class);
        String key = null;
        if (header != null) {
          key = header.value().equals("") ? parameter.getSimpleName().toString() : header.value();
        } else if (header1 != null) {
          key = header1.value().equals("") ? parameter.getSimpleName().toString() : header1.value();
        } else {
          continue;
        }
        map.put(key, parameter.getSimpleName().toString());
      }

      return map;
    }

    public Map buildFields(ExecutableElement method) {
      Map map = new HashMap();
      // TODO FieldMap

      List parameters = method.getParameters();
      for (VariableElement parameter : parameters) {
        Retrofit.Field field = parameter.getAnnotation(Retrofit.Field.class);
        retrofit.http.Field field1 = parameter.getAnnotation(retrofit.http.Field.class);
        String key = null;
        if (field != null) {
          key = field.value().equals("") ? parameter.getSimpleName().toString() : field.value();
        } else if (field1 != null) {
          key = field1.value().equals("") ? parameter.getSimpleName().toString() : field1.value();
        } else {
          continue;
        }
        map.put(key, parameter.getSimpleName().toString());
      }

      return map;
    }

    public Map buildParts(ExecutableElement method) {
      Map map = new HashMap();
      Types typeUtils = processingEnv.getTypeUtils();
      TypeMirror fileType = getTypeMirror(processingEnv, java.io.File.class);
      TypeMirror typedFileType = getTypeMirror(processingEnv, retrofit.mime.TypedFile.class);
      TypeMirror typedStringType = getTypeMirror(processingEnv, retrofit.mime.TypedString.class);
      TypeMirror typedByteArrayType = getTypeMirror(processingEnv, retrofit.mime.TypedByteArray.class);

      List parameters = method.getParameters();
      for (VariableElement parameter : parameters) {
        String mimeType = "";
        String value = "";
        Retrofit.Part partAnnotation = parameter.getAnnotation(Retrofit.Part.class);
        retrofit.http.Part part1Annotation = parameter.getAnnotation(retrofit.http.Part.class);
        if (partAnnotation != null) {
          value = partAnnotation.value();
          mimeType = partAnnotation.mimeType();
        } else if (part1Annotation != null) {
          value = part1Annotation.value();
        } else {
          continue;
        }

        TypeMirror type = parameter.asType();
        boolean isFile = typeUtils.isSubtype(type, fileType);
        boolean isTypedFile = typeUtils.isSubtype(type, typedFileType);
        boolean isTypedString = typeUtils.isSubtype(type, typedStringType);
        boolean isTypedByteArray = typeUtils.isSubtype(type, typedByteArrayType);

        String key = value.equals("") ? parameter.getSimpleName().toString() : value;
        map.put(key, new Part(parameter.getSimpleName().toString(), mimeType, isFile, isTypedFile, isTypedString, isTypedByteArray));
      }
      return map;
    }

    // /{postId}
    // /{userIdA}/friends/{userIdB}
    // "/" + userIdA + "/friends/" + userIdB
    // "/" + userIdA + "/friends/" + userIdB + ""
    public String buildPath(ExecutableElement method) {
      String fullPath = buildRawPath(method);

      List parameters = method.getParameters();
      for (VariableElement parameter : parameters) {
        Retrofit.Path path = parameter.getAnnotation(Retrofit.Path.class);
        retrofit.http.Path path1 = parameter.getAnnotation(retrofit.http.Path.class);
        if ((path != null) && (!path.value().equals(""))) {
          fullPath = fullPath.replace("{" + path.value() + "}", "\" + " +
              parameter.getSimpleName().toString() + " + \"");
        } else if ((path1 != null) && (!path1.value().equals(""))) {
          fullPath = fullPath.replace("{" + path1.value() + "}", "\" + " +
              parameter.getSimpleName().toString() + " + \"");
        } else {
          fullPath = fullPath.replace("{" + parameter.getSimpleName().toString() + "}", "\" + " +
              parameter.getSimpleName().toString() + " + \"");
        }
      }

      return fullPath.replaceAll("\\?.+", "");
    }

    public String buildRawPath(ExecutableElement method) {
      // TODO duplicated routine
      String rawPath = null;
      Retrofit.GET get = method.getAnnotation(Retrofit.GET.class);
      Retrofit.PUT put = method.getAnnotation(Retrofit.PUT.class);
      Retrofit.POST post = method.getAnnotation(Retrofit.POST.class);
      Retrofit.DELETE delete = method.getAnnotation(Retrofit.DELETE.class);
      Retrofit.HEAD head = method.getAnnotation(Retrofit.HEAD.class);
      if (get != null) rawPath = get.value();
      if (put != null) rawPath = put.value();
      if (post != null) rawPath = post.value();
      if (delete != null) rawPath = delete.value();
      if (head != null) rawPath = head.value();
      retrofit.http.GET get1 = method.getAnnotation(retrofit.http.GET.class);
      retrofit.http.PUT put1 = method.getAnnotation(retrofit.http.PUT.class);
      retrofit.http.POST post1 = method.getAnnotation(retrofit.http.POST.class);
      retrofit.http.DELETE delete1 = method.getAnnotation(retrofit.http.DELETE.class);
      retrofit.http.HEAD head1 = method.getAnnotation(retrofit.http.HEAD.class);
      if (get1 != null) rawPath = get1.value();
      if (put1 != null) rawPath = put1.value();
      if (post1 != null) rawPath = post1.value();
      if (delete1 != null) rawPath = delete1.value();
      if (head1 != null) rawPath = head1.value();
      return rawPath;
    }

    public Map buildQueries(ExecutableElement method) {
      Map map = new HashMap();

      String fullPath = buildRawPath(method);
      if (fullPath.indexOf("?") != -1) {
        fullPath = fullPath.replaceAll("^.*\\?", "");
        String[] queries = fullPath.split("&");
        for (String query : queries) {
          String[] keyValue = query.split("=");
          map.put(keyValue[0], "\"" + keyValue[1] + "\"");
        }
      }

      List parameters = method.getParameters();
      for (VariableElement parameter : parameters) {
        String value = "";
        Retrofit.Query query = parameter.getAnnotation(Retrofit.Query.class);
        retrofit.http.Query query1 = parameter.getAnnotation(retrofit.http.Query.class);
        if (query != null) {
          value = query.value();
        } else if (query1 != null) {
          value = query1.value();
        } else {
          continue;
        }

        String key = value.equals("") ? parameter.getSimpleName().toString() : value;
        map.put(key, parameter.getSimpleName().toString());
      }

      return map;
    }

    public List buildQueryMaps(ExecutableElement method) {
      List queryMaps = new ArrayList();
      List parameters = method.getParameters();
      for (VariableElement parameter : parameters) {
        Retrofit.QueryMap queryMap = parameter.getAnnotation(Retrofit.QueryMap.class);
        retrofit.http.QueryMap queryMap1 = parameter.getAnnotation(retrofit.http.QueryMap.class);
        if (queryMap != null) {
        } else if (queryMap1 != null) {
        } else {
          continue;
        }

        queryMaps.add(parameter.getSimpleName().toString());
      }
      return queryMaps;
    }

    public List buildQueryBundles(ExecutableElement method) {
      List queryBundles = new ArrayList();
      List parameters = method.getParameters();
      for (VariableElement parameter : parameters) {
        Retrofit.QueryBundle queryBundle = parameter.getAnnotation(Retrofit.QueryBundle.class);
        if (queryBundle == null) {
          continue;
        }

        queryBundles.add(parameter.getSimpleName().toString());
      }
      return queryBundles;
    }

    private ImmutableList buildAnnotations(TypeSimplifier typeSimplifier) {
      ImmutableList.Builder builder = ImmutableList.builder();

      for (AnnotationMirror annotationMirror : method.getAnnotationMirrors()) {
        TypeElement annotationElement =
            (TypeElement) annotationMirror.getAnnotationType().asElement();
        if (annotationElement.getQualifiedName().toString().equals(Override.class.getName())) {
          // Don't copy @Override if present, since we will be adding our own @Override in the
          // implementation.
          continue;
        }
        // TODO(user): we should import this type if it is not already imported
        AnnotationOutput annotationOutput = new AnnotationOutput(typeSimplifier);
        builder.add(annotationOutput.sourceFormForAnnotation(annotationMirror));
      }

      return builder.build();
    }

    /**
     * Returns the name of the property as it should be used when declaring identifiers (fields and
     * parameters). If the original getter method was {@code foo()} then this will be {@code foo}.
     * If it was {@code getFoo()} then it will be {@code foo}. If it was {@code getPackage()} then
     * it will be something like {@code package0}, since {@code package} is a reserved word.
     */
    @Override
    public String toString() {
      return identifier;
    }

    /**
     * Returns the name of the property as it should be used in strings visible to users. This is
     * usually the same as {@code toString()}, except that if we had to use an identifier like
     * "package0" because "package" is a reserved word, the name here will be the original
     * "package".
     */
    public String getName() {
      return name;
    }

    /**
     * Returns the name of the getter method for this property as defined by the {@code @Retrofit}
     * class. For property {@code foo}, this will be {@code foo} or {@code getFoo} or {@code isFoo}.
     */
    public String getGetter() {
      return method.getSimpleName().toString();
    }

    TypeElement getOwner() {
      return (TypeElement) method.getEnclosingElement();
    }

    TypeMirror getReturnType() {
      return method.getReturnType();
    }

    public String getType() {
      return type;
    }

    public String getTypeArgs() {
      return typeArgs;
    }

    public String getTypeArgs2() {
      return typeArgs2;
    }

    public TypeKind getKind() {
      return method.getReturnType().getKind();
    }

    public String getCastType() {
      return primitive() ? box(method.getReturnType().getKind()) : getType();
    }

    private String box(TypeKind kind) {
      switch (kind) {
        case BOOLEAN:
          return "Boolean";
        case BYTE:
          return "Byte";
        case SHORT:
          return "Short";
        case INT:
          return "Integer";
        case LONG:
          return "Long";
        case CHAR:
          return "Character";
        case FLOAT:
          return "Float";
        case DOUBLE:
          return "Double";
        default:
          throw new RuntimeException("Unknown primitive of kind " + kind);
        }
    }

    public boolean primitive() {
      return method.getReturnType().getKind().isPrimitive();
    }

    public boolean isCallback() {
      return (callbackType != null && !"".equals(callbackType));
    }

    public String getCallbackType() {
      return callbackType;
    }

    public String getCallbackArg() {
      return callbackArg;
    }

    public String getCallbackName() {
      return callbackName;
    }

    public boolean isObservable() {
      return isObservable;
    }

    public boolean isVoid() {
      return isVoid;
    }

    public boolean isBlocking() {
      return isBlocking;
    }

    public boolean isResponseType() {
      return isResponseType;
    }

    public String getBody() {
      return body;
    }

    public String getConverter() {
      return converter;
    }

    public String getGsonConverter() {
      return gsonConverter;
    }

    public String getErrorHandler() {
      return errorHandler;
    }

    public String getRequestInterceptor() {
      return requestInterceptor;
    }

    public String getLogLevel() {
      return logLevel;
    }

    public List getPermissions() {
      return permissions;
    }

    public boolean isGet() {
      return isGet;
    }

    public boolean isPut() {
      return isPut;
    }

    public boolean isPost() {
      return isPost;
    }

    public boolean isDelete() {
      return isDelete;
    }

    public boolean isHead() {
      return isHead;
    }

    public boolean isAuthenticated() {
      return isAuthenticated;
    }

    public boolean isSingletonRequestInterceptor() {
      return isSingletonRequestInterceptor;
    }

    public List getAnnotations() {
      return annotations;
    }

    public String getArgs() {
      return args;
    }

    public String getPath() {
      return path;
    }

    public Map getQueries() {
      return queries;
    }

    public List getQueryMaps() {
      return queryMaps;
    }

    public List getQueryBundles() {
      return queryBundles;
    }

    public Map getHeaders() {
      return headers;
    }

    public Map getFields() {
      return fields;
    }

    public Map getParts() {
      return parts;
    }

    public boolean isNullable() {
      for (AnnotationMirror annotationMirror : method.getAnnotationMirrors()) {
        String name = annotationMirror.getAnnotationType().asElement().getSimpleName().toString();
        if (name.equals("Nullable")) {
          return true;
        }
      }
      return false;
    }

    public String getAccess() {
      Set mods = method.getModifiers();
      if (mods.contains(Modifier.PUBLIC)) {
        return "public ";
      } else if (mods.contains(Modifier.PROTECTED)) {
        return "protected ";
      } else {
        return "";
      }
    }
  }

  private static boolean isJavaLangObject(TypeElement type) {
    return type.getSuperclass().getKind() == TypeKind.NONE && type.getKind() == ElementKind.CLASS;
  }

  private enum ObjectMethodToOverride {
    NONE, TO_STRING, EQUALS, HASH_CODE, DESCRIBE_CONTENTS, WRITE_TO_PARCEL
  }

  private static ObjectMethodToOverride objectMethodToOverride(ExecutableElement method) {
    String name = method.getSimpleName().toString();
    switch (method.getParameters().size()) {
      case 0:
        if (name.equals("toString")) {
          return ObjectMethodToOverride.TO_STRING;
        } else if (name.equals("hashCode")) {
          return ObjectMethodToOverride.HASH_CODE;
        } else if (name.equals("describeContents")) {
          return ObjectMethodToOverride.DESCRIBE_CONTENTS;
        }
        break;
      case 1:
        if (name.equals("equals")
            && method.getParameters().get(0).asType().toString().equals("java.lang.Object")) {
          return ObjectMethodToOverride.EQUALS;
        }
        break;
      case 2:
        if (name.equals("writeToParcel")
            && method.getParameters().get(0).asType().toString().equals("android.os.Parcel")
            && method.getParameters().get(1).asType().toString().equals("int")) {
          return ObjectMethodToOverride.WRITE_TO_PARCEL;
        }
        break;
    }
    return ObjectMethodToOverride.NONE;
  }

  private void findLocalAndInheritedMethods(TypeElement type, List methods) {
    Types typeUtils = processingEnv.getTypeUtils();
    Elements elementUtils = processingEnv.getElementUtils();
    for (TypeMirror superInterface : type.getInterfaces()) {
      findLocalAndInheritedMethods((TypeElement) typeUtils.asElement(superInterface), methods);
    }
    if (type.getSuperclass().getKind() != TypeKind.NONE) {
      // Visit the superclass after superinterfaces so we will always see the implementation of a
      // method after any interfaces that declared it.
      findLocalAndInheritedMethods(
          (TypeElement) typeUtils.asElement(type.getSuperclass()), methods);
    }
    // Add each method of this class, and in so doing remove any inherited method it overrides.
    // This algorithm is quadratic in the number of methods but it's hard to see how to improve
    // that while still using Elements.overrides.
    List theseMethods = ElementFilter.methodsIn(type.getEnclosedElements());
    for (ExecutableElement method : theseMethods) {
      if (!method.getModifiers().contains(Modifier.PRIVATE)) {
        boolean alreadySeen = false;
        for (Iterator methodIter = methods.iterator(); methodIter.hasNext(); ) {
          ExecutableElement otherMethod = methodIter.next();
          if (elementUtils.overrides(method, otherMethod, type)) {
            methodIter.remove();
          } else if (method.getSimpleName().equals(otherMethod.getSimpleName())
              && method.getParameters().equals(otherMethod.getParameters())) {
            // If we inherit this method on more than one path, we don't want to add it twice.
            alreadySeen = true;
          }
        }
        if (!alreadySeen) {
          /*
          Retrofit.GET action = method.getAnnotation(Retrofit.GET.class);
          System.out.printf(
              "%s Action value = %s\n",
              method.getSimpleName(),
              action == null ? null : action.value() );
          */
          methods.add(method);
        }
      }
    }
  }

  private void processType(TypeElement type) {
    Retrofit autoValue = type.getAnnotation(Retrofit.class);
    if (autoValue == null) {
      // This shouldn't happen unless the compilation environment is buggy,
      // but it has happened in the past and can crash the compiler.
      errorReporter.abortWithError("annotation processor for @Retrofit was invoked with a type"
          + " that does not have that annotation; this is probably a compiler bug", type);
    }
    if (type.getKind() != ElementKind.CLASS) {
      errorReporter.abortWithError(
          "@" + Retrofit.class.getName() + " only applies to classes", type);
    }
    if (ancestorIsRetrofit(type)) {
      errorReporter.abortWithError("One @Retrofit class may not extend another", type);
    }
    if (implementsAnnotation(type)) {
      errorReporter.abortWithError("@Retrofit may not be used to implement an annotation"
          + " interface; try using @AutoAnnotation instead", type);
    }
    RetrofitTemplateVars vars = new RetrofitTemplateVars();
    vars.pkg = TypeSimplifier.packageNameOf(type);
    vars.origClass = TypeSimplifier.classNameOf(type);
    vars.simpleClassName = TypeSimplifier.simpleNameOf(vars.origClass);
    vars.subclass = TypeSimplifier.simpleNameOf(generatedSubclassName(type));
    defineVarsForType(type, vars);
    GwtCompatibility gwtCompatibility = new GwtCompatibility(type);
    vars.gwtCompatibleAnnotation = gwtCompatibility.gwtCompatibleAnnotationString();
    String text = vars.toText();
    text = Reformatter.fixup(text);
    writeSourceFile(generatedSubclassName(type), text, type);
    GwtSerialization gwtSerialization = new GwtSerialization(gwtCompatibility, processingEnv, type);
    gwtSerialization.maybeWriteGwtSerializer(vars);
  }

  private void defineVarsForType(TypeElement type, RetrofitTemplateVars vars) {
    Types typeUtils = processingEnv.getTypeUtils();
    List methods = new ArrayList();
    findLocalAndInheritedMethods(type, methods);
    determineObjectMethodsToGenerate(methods, vars);
    ImmutableSet methodsToImplement = methodsToImplement(methods);
    Set types = new TypeMirrorSet();
    types.addAll(returnTypesOf(methodsToImplement));
    //    TypeMirror javaxAnnotationGenerated = getTypeMirror(Generated.class);
    //    types.add(javaxAnnotationGenerated);
    TypeMirror javaUtilArrays = getTypeMirror(Arrays.class);
    if (containsArrayType(types)) {
      // If there are array properties then we will be referencing java.util.Arrays.
      // Arrange to import it unless that would introduce ambiguity.
      types.add(javaUtilArrays);
    }
    BuilderSpec builderSpec = new BuilderSpec(type, processingEnv, errorReporter);
    Optional builder = builderSpec.getBuilder();
    ImmutableSet toBuilderMethods;
    if (builder.isPresent()) {
      types.add(getTypeMirror(BitSet.class));
      toBuilderMethods = builder.get().toBuilderMethods(typeUtils, methodsToImplement);
    } else {
      toBuilderMethods = ImmutableSet.of();
    }
    vars.toBuilderMethods =
        FluentIterable.from(toBuilderMethods).transform(SimpleNameFunction.INSTANCE).toList();
    Set propertyMethods = Sets.difference(methodsToImplement, toBuilderMethods);
    String pkg = TypeSimplifier.packageNameOf(type);
    TypeSimplifier typeSimplifier = new TypeSimplifier(typeUtils, pkg, types, type.asType());
    vars.imports = typeSimplifier.typesToImport();
    //    vars.generated = typeSimplifier.simplify(javaxAnnotationGenerated);
    vars.arrays = typeSimplifier.simplify(javaUtilArrays);
    vars.bitSet = typeSimplifier.simplifyRaw(getTypeMirror(BitSet.class));
    ImmutableMap methodToPropertyName =
        methodToPropertyNameMap(propertyMethods);
    Map methodToIdentifier =
        Maps.newLinkedHashMap(methodToPropertyName);
    fixReservedIdentifiers(methodToIdentifier);
    List props = new ArrayList();
    for (ExecutableElement method : propertyMethods) {
      String propertyType = typeSimplifier.simplify(method.getReturnType());
      String propertyName = methodToPropertyName.get(method);
      String identifier = methodToIdentifier.get(method);
      List args = new ArrayList();
      props.add(new Property(propertyName, identifier, method, propertyType, typeSimplifier, processingEnv));
    }
    // If we are running from Eclipse, undo the work of its compiler which sorts methods.
    eclipseHack().reorderProperties(props);
    vars.props = props;
    vars.serialVersionUID = getSerialVersionUID(type);
    vars.formalTypes = typeSimplifier.formalTypeParametersString(type);
    vars.actualTypes = TypeSimplifier.actualTypeParametersString(type);
    vars.wildcardTypes = wildcardTypeParametersString(type);
    Retrofit typeAnnoation = type.getAnnotation(Retrofit.class);
    vars.baseUrl = typeAnnoation.value();

    Map headerMap = new HashMap();
    Retrofit.Headers headersAnnotation = type.getAnnotation(Retrofit.Headers.class);
    if (headersAnnotation != null) {
      for (String header : headersAnnotation.value()) {
        String[] tokens = header.split(":");
        headerMap.put(tokens[0].trim(), "\"" + tokens[1].trim() + "\"");
      }
      vars.headers = headerMap;
    }

    Map retryHeaderMap = new HashMap();
    Retrofit.RetryHeaders retryHeadersAnnotation = type.getAnnotation(Retrofit.RetryHeaders.class);
    if (retryHeadersAnnotation != null) {
      for (String header : retryHeadersAnnotation.value()) {
        String[] tokens = header.split(":");
        retryHeaderMap.put(tokens[0].trim(), "\"" + tokens[1].trim() + "\"");
      }
      vars.retryHeaders = retryHeaderMap;
    }

    Retrofit.Converter converterAnnotation = type.getAnnotation(Retrofit.Converter.class);
    if (converterAnnotation != null) {
      TypeMirror converter = null;
      try {
        converter = getTypeMirror(converterAnnotation.value());
      } catch (MirroredTypeException mte) {
        // http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/
        converter = mte.getTypeMirror();
      }
      vars.converter = typeSimplifier.simplify(converter);
      TypeMirror gsonConverterType = getTypeMirror(retrofit.converter.GsonConverter.class);
      if (typeUtils.isSubtype(gsonConverterType, converter)) {
        vars.gsonConverter = vars.converter;
      }
    }
    Retrofit.ErrorHandler errorHandlerAnnotation = type.getAnnotation(Retrofit.ErrorHandler.class);
    if (errorHandlerAnnotation != null) {
      TypeMirror errorHandler = null;
      try {
        errorHandler = getTypeMirror(errorHandlerAnnotation.value());
      } catch (MirroredTypeException mte) {
        // http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/
        errorHandler = mte.getTypeMirror();
      }
      vars.errorHandler = typeSimplifier.simplify(errorHandler);
    }
    Retrofit.LogLevel logLevelAnnotation = type.getAnnotation(Retrofit.LogLevel.class);
    if (logLevelAnnotation != null) {
      vars.logLevel = logLevelAnnotation.value();
    }
    Retrofit.RequestInterceptor requestInterceptorAnnotation = type.getAnnotation(Retrofit.RequestInterceptor.class);
    if (requestInterceptorAnnotation != null) {
      TypeMirror requestInterceptor = null;
      try {
        requestInterceptor = getTypeMirror(requestInterceptorAnnotation.value());
      } catch (MirroredTypeException mte) {
        // http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/
        requestInterceptor = mte.getTypeMirror();
      }
      vars.requestInterceptor = typeSimplifier.simplify(requestInterceptor);
    }
    Retrofit.Authenticator authenticatorAnnotation = type.getAnnotation(Retrofit.Authenticator.class);
    if (authenticatorAnnotation != null) {
      TypeMirror authenticator = null;
      try {
        authenticator = getTypeMirror(authenticatorAnnotation.value());
      } catch (MirroredTypeException mte) {
        // http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/
        authenticator = mte.getTypeMirror();
      }
      vars.authenticator = typeSimplifier.simplify(authenticator);
    }
    Retrofit.Authenticated authenticatedAnnotation = type.getAnnotation(Retrofit.Authenticated.class);
    if (authenticatedAnnotation != null) {
      TypeMirror authenticatedType = null;
      try {
        authenticatedType = getTypeMirror(authenticatedAnnotation.value());
      } catch (MirroredTypeException mte) {
        // http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/
        authenticatedType = mte.getTypeMirror();
      }
      String authenticated = typeSimplifier.simplify(authenticatedType);
      vars.authenticated = authenticated != null && !"".equals(authenticated);
    }

    TypeElement parcelable = processingEnv.getElementUtils().getTypeElement("android.os.Parcelable");
    vars.parcelable = parcelable != null
      && processingEnv.getTypeUtils().isAssignable(type.asType(), parcelable.asType());
    // Check for @Retrofit.Builder and add appropriate variables if it is present.
    if (builder.isPresent()) {
      builder.get().defineVars(vars, typeSimplifier, methodToPropertyName);
    }
  }

  private ImmutableMap methodToPropertyNameMap(
      Iterable propertyMethods) {
    ImmutableMap.Builder builder = ImmutableMap.builder();
    boolean allGetters = allGetters(propertyMethods);
    for (ExecutableElement method : propertyMethods) {
      String methodName = method.getSimpleName().toString();
      String name = allGetters ? nameWithoutPrefix(methodName) : methodName;
      builder.put(method, name);
    }
    ImmutableMap map = builder.build();
    if (allGetters) {
      checkDuplicateGetters(map);
    }
    return map;
  }

  private static boolean allGetters(Iterable methods) {
    for (ExecutableElement method : methods) {
      String name = method.getSimpleName().toString();
      // TODO(user): decide whether getfoo() (without a capital) is a getter. Currently it is.
      boolean get = name.startsWith("get") && !name.equals("get");
      boolean is = name.startsWith("is") && !name.equals("is")
          && method.getReturnType().getKind() == TypeKind.BOOLEAN;
      if (!get && !is) {
        return false;
      }
    }
    return true;
  }

  private String nameWithoutPrefix(String name) {
    if (name.startsWith("get")) {
      name = name.substring(3);
    } else {
      assert name.startsWith("is");
      name = name.substring(2);
    }
    return Introspector.decapitalize(name);
  }

  private void checkDuplicateGetters(Map methodToIdentifier) {
    if (true) return;
    Set seen = Sets.newHashSet();
    for (Map.Entry entry : methodToIdentifier.entrySet()) {
      if (!seen.add(entry.getValue())) {
        errorReporter.reportError(
            "More than one @Retrofit property called " + entry.getValue(), entry.getKey());
      }
    }
  }

  // If we have a getter called getPackage() then we can't use the identifier "package" to represent
  // its value since that's a reserved word.
  private void fixReservedIdentifiers(Map methodToIdentifier) {
    for (Map.Entry entry : methodToIdentifier.entrySet()) {
      if (SourceVersion.isKeyword(entry.getValue())) {
        entry.setValue(disambiguate(entry.getValue(), methodToIdentifier.values()));
      }
    }
  }

  private String disambiguate(String name, Collection existingNames) {
    for (int i = 0; ; i++) {
      String candidate = name + i;
      if (!existingNames.contains(candidate)) {
        return candidate;
      }
    }
  }

  private Set returnTypesOf(Iterable methods) {
    Set returnTypes = new TypeMirrorSet();
    for (ExecutableElement method : methods) {
      returnTypes.add(method.getReturnType());
    }
    return returnTypes;
  }

  private static boolean containsArrayType(Set types) {
    for (TypeMirror type : types) {
      if (type.getKind() == TypeKind.ARRAY) {
        return true;
      }
    }
    return false;
  }

  /**
   * Given a list of all methods defined in or inherited by a class, sets the equals, hashCode, and
   * toString fields of vars according as the corresponding methods should be generated.
   */
  private static void determineObjectMethodsToGenerate(
      List methods, RetrofitTemplateVars vars) {
    // The defaults here only come into play when an ancestor class doesn't exist.
    // Compilation will fail in that case, but we don't want it to crash the compiler with
    // an exception before it does. If all ancestors do exist then we will definitely find
    // definitions of these three methods (perhaps the ones in Object) so we will overwrite these:
    vars.equals = false;
    vars.hashCode = false;
    vars.toString = false;
    for (ExecutableElement method : methods) {
      ObjectMethodToOverride override = objectMethodToOverride(method);
      boolean canGenerate = method.getModifiers().contains(Modifier.ABSTRACT)
          || isJavaLangObject((TypeElement) method.getEnclosingElement());
      switch (override) {
        case EQUALS:
          vars.equals = canGenerate;
          break;
        case HASH_CODE:
          vars.hashCode = canGenerate;
          break;
        case TO_STRING:
          vars.toString = canGenerate;
          break;
      }
    }
  }

  private ImmutableSet methodsToImplement(List methods) {
    ImmutableSet.Builder toImplement = ImmutableSet.builder();
    boolean errors = false;
    for (ExecutableElement method : methods) {
      if (method.getModifiers().contains(Modifier.ABSTRACT)
          && objectMethodToOverride(method) == ObjectMethodToOverride.NONE) {
        if (method.getParameters().isEmpty() && method.getReturnType().getKind() != TypeKind.VOID) {
          if (isReferenceArrayType(method.getReturnType())) {
            errorReporter.reportError("An @Retrofit class cannot define an array-valued property"
                + " unless it is a primitive array", method);
            errors = true;
          }
          toImplement.add(method);
        } else {
          toImplement.add(method);
        }
      }
    }
    if (errors) {
      throw new AbortProcessingException();
    }
    return toImplement.build();
  }

  private static boolean isReferenceArrayType(TypeMirror type) {
    return type.getKind() == TypeKind.ARRAY
        && !((ArrayType) type).getComponentType().getKind().isPrimitive();
  }

  private void writeSourceFile(String className, String text, TypeElement originatingType) {
    try {
      JavaFileObject sourceFile =
          processingEnv.getFiler().createSourceFile(className, originatingType);
      Writer writer = sourceFile.openWriter();
      try {
        writer.write(text);
      } finally {
        writer.close();
      }
    } catch (IOException e) {
      processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
          "Could not write generated class " + className + ": " + e);
    }
  }

  private boolean ancestorIsRetrofit(TypeElement type) {
    while (true) {
      TypeMirror parentMirror = type.getSuperclass();
      if (parentMirror.getKind() == TypeKind.NONE) {
        return false;
      }
      Types typeUtils = processingEnv.getTypeUtils();
      TypeElement parentElement = (TypeElement) typeUtils.asElement(parentMirror);
      if (parentElement.getAnnotation(Retrofit.class) != null) {
        return true;
      }
      type = parentElement;
    }
  }

  private boolean implementsAnnotation(TypeElement type) {
    Types typeUtils = processingEnv.getTypeUtils();
    return typeUtils.isAssignable(type.asType(), getTypeMirror(Annotation.class));
  }

  // Return a string like "1234L" if type instanceof Serializable and defines
  // serialVersionUID = 1234L, otherwise "".
  private String getSerialVersionUID(TypeElement type) {
    Types typeUtils = processingEnv.getTypeUtils();
    TypeMirror serializable = getTypeMirror(Serializable.class);
    if (typeUtils.isAssignable(type.asType(), serializable)) {
      List fields = ElementFilter.fieldsIn(type.getEnclosedElements());
      for (VariableElement field : fields) {
        if (field.getSimpleName().toString().equals("serialVersionUID")) {
          Object value = field.getConstantValue();
          if (field.getModifiers().containsAll(Arrays.asList(Modifier.STATIC, Modifier.FINAL))
              && field.asType().getKind() == TypeKind.LONG
              && value != null) {
            return value + "L";
          } else {
            errorReporter.reportError(
                "serialVersionUID must be a static final long compile-time constant", field);
            break;
          }
        }
      }
    }
    return "";
  }

  private TypeMirror getTypeMirror(Class c) {
    return getTypeMirror(processingEnv, c);
  }

  private TypeMirror getTypeMirror(String canonicalName) {
    return getTypeMirror(processingEnv, canonicalName);
  }

  private static TypeMirror getTypeMirror(ProcessingEnvironment processingEnv, Class c) {
    return getTypeMirror(processingEnv, c.getCanonicalName());
  }

  private static TypeMirror getTypeMirror(ProcessingEnvironment processingEnv, String canonicalName) {
    return processingEnv.getElementUtils().getTypeElement(canonicalName).asType();
  }

  // The @Retrofit type, with a ? for every type.
  // If we have @Retrofit abstract class Foo then this method will return
  // just .
  private static String wildcardTypeParametersString(TypeElement type) {
    List typeParameters = type.getTypeParameters();
    if (typeParameters.isEmpty()) {
      return "";
    } else {
      return "<"
          + Joiner.on(", ").join(
          FluentIterable.from(typeParameters).transform(Functions.constant("?")))
          + ">";
    }
  }

  private static String catArgsString(ExecutableElement method) {
    List parameters = method.getParameters();
    if (parameters.isEmpty()) {
      return "";
    } else {
      return ""
        + Joiner.on(" + ").join(
        FluentIterable.from(parameters).transform(new Function() {
          @Override
          public String apply(VariableElement element) {
            return "" + element.getSimpleName();
          }
        }))
        + "";
    }
  }

  private static String formalArgsString(ExecutableElement method) {
    List parameters = method.getParameters();
    if (parameters.isEmpty()) {
      return "";
    } else {
      return ""
        + Joiner.on(", ").join(
        FluentIterable.from(parameters).transform(new Function() {
          @Override
          public String apply(VariableElement element) {
            return "" + element.getSimpleName();
          }
        }))
        + "";
    }
  }

  private static String formalTypeArgsString(ExecutableElement method) {
    List parameters = method.getParameters();
    if (parameters.isEmpty()) {
      return "";
    } else {
      return ""
        + Joiner.on(", ").join(
        FluentIterable.from(parameters).transform(new Function() {
          @Override
          public String apply(VariableElement element) {
            return "final " + element.asType() + " " + element.getSimpleName();
          }
        }))
        + "";
    }
  }

  private EclipseHack eclipseHack() {
    return new EclipseHack(processingEnv);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy