/* * Copyright 2016 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 com.google.android.libraries.remixer.annotation.processor; import com.google.android.libraries.remixer.Remixer; import com.google.android.libraries.remixer.annotation.RemixerBinder; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; /** * A class that has members annotated with any of the * {@link com.google.android.libraries.remixer.annotation} annotations. * * <p>Every instance of this object keeps track of all methods annotated with @*Method annotations. * * <p>While adding methods, this also makes sure that their remixer keys are not duplicated. */ class AnnotatedClass { /** * The {@link RemixerBinder} class' name. */ private static final ClassName REMIXER_BINDER_CLASS_NAME = ClassName.get(RemixerBinder.Binder.class); /** * The class being compiled that has Remixer annotations. */ private final TypeElement sourceClass; /** * Class name for the source class. */ private final TypeName sourceClassName; /** * The name of the class that to generate to contain all the code related to Remixer annotations. */ private final String generatedClassName; /** * The package name to use for the generated class. This *must* match the package for * {@code sourceClass}. */ private final String packageName; /** * A set of all the used Variable Keys. Used for guaranteeing that there is no duplication of * keys. */ private final Set<String> remixerKeys; /** * A mapping from method to annotation, this is used later for sorting and to make sure one method * is only annotated once. */ private HashMap<ExecutableElement, MethodAnnotation> methodMap; /** * Constructor * @param sourceClass The TypeElement that represent the class that contains members annotated by * Remixer annotations. */ AnnotatedClass(TypeElement sourceClass) { this.sourceClass = sourceClass; this.sourceClassName = ClassName.get(sourceClass.asType()); generatedClassName = sourceClass.getSimpleName() + "_RemixerBinder"; packageName = ((PackageElement) sourceClass.getEnclosingElement()).getQualifiedName().toString(); methodMap = new HashMap<>(); remixerKeys = new HashSet<>(); } /** * Adds a method annotated by a Remixer annotation * @throws RemixerAnnotationException when a key is repeated among Remixer annotations in the same * class. */ void addMethod(MethodAnnotation method) throws RemixerAnnotationException { if (!method.getSourceClass().equals(sourceClass)) { throw new RemixerAnnotationException(method.getSourceMethod(), "Adding a method annotation to the wrong class, shouldn't happen. File a bug."); } if (remixerKeys.contains(method.getKey())) { throw new RemixerAnnotationException(method.getSourceMethod(), "Repeated Variable key, there can only be one with the same name in the same class"); } if (methodMap.containsKey(method.getSourceMethod())) { throw new RemixerAnnotationException(method.getSourceMethod(), "Method can only be annotated once."); } remixerKeys.add(method.getKey()); // Put in a map, these will be sorted at the time of generating the source code methodMap.put(method.getSourceMethod(), method); } /** * Generates a Java file with the code corresponding to all Remixer annotations in this class. */ JavaFile generateJavaFile() throws RemixerAnnotationException { Collection<MethodAnnotation> annotatedMethods = sortMethods(); TypeSpec.Builder classBuilder = TypeSpec.classBuilder(generatedClassName); ParameterizedTypeName superInterface = ParameterizedTypeName.get(REMIXER_BINDER_CLASS_NAME, sourceClassName); classBuilder .addModifiers(Modifier.PUBLIC) .addSuperinterface(superInterface) .addJavadoc("This class was generated by RemixerAnnotationProcessor"); // Create bind method signature MethodSpec.Builder bindMethodBuilder = MethodSpec .methodBuilder("bindInstance") .addParameter(ClassName.get(sourceClass), "activity") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC); bindMethodBuilder .addStatement("$T remixer = $T.getInstance()", Remixer.class, Remixer.class); for (MethodAnnotation method : annotatedMethods) { // Create all of the internal callback classes classBuilder.addType(method.generateCallbackClass()); // Add them to the bind method. method.addSetupStatements(bindMethodBuilder); } classBuilder.addMethod(bindMethodBuilder.build()); return JavaFile.builder(packageName, classBuilder.build()).build(); } /** * Sorts all the methods by appearance in the source code file. * * <p>Not only this generates predictable output for tests, but this is necessary for the user to * have any control over the order of remixes being added. */ private Collection<MethodAnnotation> sortMethods() throws RemixerAnnotationException { List<MethodAnnotation> annotatedMethods = new ArrayList<>(); List<? extends Element> enclosedElements = sourceClass.getEnclosedElements(); for (Element element : enclosedElements) { if (element instanceof ExecutableElement) { ExecutableElement currentMethod = (ExecutableElement) element; if (methodMap.containsKey(currentMethod)) { annotatedMethods.add(methodMap.get(currentMethod)); } } } return annotatedMethods; } }