Java tutorial
/* * 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 retroweibo.processor; import retroweibo.RetroWeibo; 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; /** * Javac annotation processor (compiler plugin) for value types; user code never references this * class. * * @author amonn McManus * @see retroweibo.RetroWeibo */ @AutoService(Processor.class) public class RetroWeiboProcessor extends AbstractProcessor { public RetroWeiboProcessor() { } @Override public Set<String> getSupportedAnnotationTypes() { return ImmutableSet.of(RetroWeibo.class.getName()); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } private ErrorReporter errorReporter; /** * Qualified names of {@code @RetroWeibo} 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<String> deferredTypeNames = new ArrayList<String>(); @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); errorReporter = new ErrorReporter(processingEnv); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { List<TypeElement> deferredTypes = new ArrayList<TypeElement>(); 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 @RetroWeibo; 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 @RetroWeibo class for " + type.getQualifiedName() + " because it references undefined types", type); } return false; } Collection<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(RetroWeibo.class); List<TypeElement> types = new ImmutableList.Builder<TypeElement>().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 @RetroWeibo 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("@RetroWeibo 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, "RetroWeibo_"); } public interface Action1<T> { void call(T t); } private void onAnnotationForProperty(AnnotationMirror annotation) { onAnnotationForProperty.call(annotation); } private Action1<? super AnnotationMirror> onAnnotationForProperty; private void annotationForProperty(Action1<? super AnnotationMirror> onAnnotationForProperty) { this.onAnnotationForProperty = onAnnotationForProperty; } /** * A property of an {@code @RetroWeibo} 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 final ImmutableList<String> annotations; private final String args; private final String path; private final Map<String, String> queries; private final List<String> queryMaps; private final List<String> queryBundles; private final boolean isGet; private final boolean isPost; private final boolean isDelete; private final String body; private final String callbackType; private final String callbackArg; private final ProcessingEnvironment processingEnv; private final TypeSimplifier typeSimplifier; private final List<String> permissions; 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.typeArgs = buildTypeArguments(type); this.queries = buildQueries(method); this.queryMaps = buildQueryMaps(method); this.queryBundles = buildQueryBundles(method); this.isGet = buildIsGet(method); this.isPost = buildIsPost(method); this.isDelete = buildIsDelete(method); this.body = buildBody(method); this.callbackType = buildCallbackType(method); this.callbackArg = buildCallbackArg(method); if ("".equals(typeArgs)) typeArgs = callbackType; this.permissions = buildPermissions(method); } private String buildTypeArguments(String type) { Pattern pattern = Pattern.compile("<(.*?)>"); Matcher m = pattern.matcher(type); if (m.find()) return m.group(1); return ""; } public String buildCallbackArg(ExecutableElement method) { return "callback"; // TODO } public String buildCallbackType(ExecutableElement method) { Types typeUtils = processingEnv.getTypeUtils(); TypeMirror callback = getTypeMirror(processingEnv, RetroWeibo.Callback.class); List<? extends VariableElement> parameters = method.getParameters(); for (VariableElement parameter : parameters) { TypeMirror type = parameter.asType(); if (type instanceof DeclaredType) { List<? extends TypeMirror> 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 ""; } public boolean buildIsGet(ExecutableElement method) { // TODO duplicated routine return method.getAnnotation(retroweibo.RetroWeibo.GET.class) != null; } public boolean buildIsPost(ExecutableElement method) { // TODO duplicated routine return method.getAnnotation(retroweibo.RetroWeibo.POST.class) != null; } public boolean buildIsDelete(ExecutableElement method) { // TODO duplicated routine return method.getAnnotation(retroweibo.RetroWeibo.DELETE.class) != null; } public String buildBody(ExecutableElement method) { String body = ""; // TODO duplicated routine retroweibo.RetroWeibo.POST post = method.getAnnotation(retroweibo.RetroWeibo.POST.class); if (post == null) return body; // TODO duplicated code List<? extends VariableElement> parameters = method.getParameters(); for (VariableElement parameter : parameters) { if (parameter.getAnnotation(retroweibo.RetroWeibo.Body.class) != null) { body = parameter.getSimpleName().toString(); } } return body; } public List<String> buildPermissions(ExecutableElement method) { retroweibo.RetroWeibo.GET get = method.getAnnotation(retroweibo.RetroWeibo.GET.class); retroweibo.RetroWeibo.POST post = method.getAnnotation(retroweibo.RetroWeibo.POST.class); retroweibo.RetroWeibo.DELETE delete = method.getAnnotation(retroweibo.RetroWeibo.DELETE.class); if (get != null) return Arrays.asList(get.permissions()); if (post != null) return Arrays.asList(post.permissions()); if (delete != null) return Arrays.asList(delete.permissions()); return Collections.emptyList(); } // /{postId} // /{userIdA}/friends/{userIdB} // "/" + userIdA + "/friends/" + userIdB // "/" + userIdA + "/friends/" + userIdB + "" public String buildPath(ExecutableElement method) { // TODO duplicated routine retroweibo.RetroWeibo.GET get = method.getAnnotation(retroweibo.RetroWeibo.GET.class); retroweibo.RetroWeibo.POST post = method.getAnnotation(retroweibo.RetroWeibo.POST.class); retroweibo.RetroWeibo.DELETE delete = method.getAnnotation(retroweibo.RetroWeibo.DELETE.class); String fullPath = null; if (get != null) fullPath = get.value(); if (post != null) fullPath = post.value(); if (delete != null) fullPath = delete.value(); List<? extends VariableElement> parameters = method.getParameters(); for (VariableElement parameter : parameters) { retroweibo.RetroWeibo.Path path = parameter.getAnnotation(retroweibo.RetroWeibo.Path.class); if ((path != null) && (!path.value().equals("null"))) { fullPath = fullPath.replace("{" + path.value() + "}", "\" + " + parameter.getSimpleName().toString() + " + \""); } else { fullPath = fullPath.replace("{" + parameter.getSimpleName().toString() + "}", "\" + " + parameter.getSimpleName().toString() + " + \""); } } return "\"" + fullPath.replaceAll("\\?.+", "") + "\""; } public Map<String, String> buildQueries(ExecutableElement method) { Map<String, String> map = new HashMap<String, String>(); // TODO duplicated routine retroweibo.RetroWeibo.GET get = method.getAnnotation(retroweibo.RetroWeibo.GET.class); retroweibo.RetroWeibo.POST post = method.getAnnotation(retroweibo.RetroWeibo.POST.class); retroweibo.RetroWeibo.DELETE delete = method.getAnnotation(retroweibo.RetroWeibo.DELETE.class); String fullPath = null; if (get != null) fullPath = get.value(); if (post != null) fullPath = post.value(); if (delete != null) fullPath = delete.value(); 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<? extends VariableElement> parameters = method.getParameters(); for (VariableElement parameter : parameters) { retroweibo.RetroWeibo.Query query = parameter.getAnnotation(retroweibo.RetroWeibo.Query.class); if (query == null) { continue; } if (!query.value().equals("null")) { map.put("\"" + query.value() + "\"", parameter.getSimpleName().toString()); } else { map.put("\"" + parameter.getSimpleName().toString() + "\"", parameter.getSimpleName().toString()); } } return map; } public List<String> buildQueryMaps(ExecutableElement method) { List<String> queryMaps = new ArrayList<String>(); List<? extends VariableElement> parameters = method.getParameters(); for (VariableElement parameter : parameters) { retroweibo.RetroWeibo.QueryMap queryMap = parameter .getAnnotation(retroweibo.RetroWeibo.QueryMap.class); if (queryMap == null) { continue; } queryMaps.add(parameter.getSimpleName().toString()); } return queryMaps; } public List<String> buildQueryBundles(ExecutableElement method) { List<String> queryBundles = new ArrayList<String>(); List<? extends VariableElement> parameters = method.getParameters(); for (VariableElement parameter : parameters) { retroweibo.RetroWeibo.QueryBundle queryBundle = parameter .getAnnotation(retroweibo.RetroWeibo.QueryBundle.class); if (queryBundle == null) { continue; } queryBundles.add(parameter.getSimpleName().toString()); } return queryBundles; } private ImmutableList<String> buildAnnotations(TypeSimplifier typeSimplifier) { ImmutableList.Builder<String> 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 @RetroWeibo} * 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 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 getBody() { return body; } public List<String> getPermissions() { return permissions; } public boolean isGet() { return isGet; } public boolean isPost() { return isPost; } public boolean isDelete() { return isDelete; } public List<String> getAnnotations() { return annotations; } public String getArgs() { return args; } public String getPath() { return path; } public Map<String, String> getQueries() { return queries; } public List<String> getQueryMaps() { return queryMaps; } public List<String> getQueryBundles() { return queryBundles; } 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<Modifier> 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<ExecutableElement> 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<ExecutableElement> theseMethods = ElementFilter.methodsIn(type.getEnclosedElements()); for (ExecutableElement method : theseMethods) { if (!method.getModifiers().contains(Modifier.PRIVATE)) { boolean alreadySeen = false; for (Iterator<ExecutableElement> 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) { /* retroweibo.RetroWeibo.GET action = method.getAnnotation(retroweibo.RetroWeibo.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) { RetroWeibo autoValue = type.getAnnotation(RetroWeibo.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 @RetroWeibo 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("@" + RetroWeibo.class.getName() + " only applies to classes", type); } if (ancestorIsRetroWeibo(type)) { errorReporter.abortWithError("One @RetroWeibo class may not extend another", type); } if (implementsAnnotation(type)) { errorReporter.abortWithError("@RetroWeibo may not be used to implement an annotation" + " interface; try using @AutoAnnotation instead", type); } RetroWeiboTemplateVars vars = new RetroWeiboTemplateVars(); 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, RetroWeiboTemplateVars vars) { Types typeUtils = processingEnv.getTypeUtils(); List<ExecutableElement> methods = new ArrayList<ExecutableElement>(); findLocalAndInheritedMethods(type, methods); determineObjectMethodsToGenerate(methods, vars); ImmutableSet<ExecutableElement> methodsToImplement = methodsToImplement(methods); Set<TypeMirror> 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<BuilderSpec.Builder> builder = builderSpec.getBuilder(); ImmutableSet<ExecutableElement> 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<ExecutableElement> 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<ExecutableElement, String> methodToPropertyName = methodToPropertyNameMap(propertyMethods); Map<ExecutableElement, String> methodToIdentifier = Maps.newLinkedHashMap(methodToPropertyName); fixReservedIdentifiers(methodToIdentifier); List<Property> props = new ArrayList<Property>(); for (ExecutableElement method : propertyMethods) { String propertyType = typeSimplifier.simplify(method.getReturnType()); String propertyName = methodToPropertyName.get(method); String identifier = methodToIdentifier.get(method); List<String> args = new ArrayList<String>(); 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); TypeElement parcelable = processingEnv.getElementUtils().getTypeElement("android.os.Parcelable"); vars.parcelable = parcelable != null && processingEnv.getTypeUtils().isAssignable(type.asType(), parcelable.asType()); // Check for @RetroWeibo.Builder and add appropriate variables if it is present. if (builder.isPresent()) { builder.get().defineVars(vars, typeSimplifier, methodToPropertyName); } } private ImmutableMap<ExecutableElement, String> methodToPropertyNameMap( Iterable<ExecutableElement> propertyMethods) { ImmutableMap.Builder<ExecutableElement, String> 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<ExecutableElement, String> map = builder.build(); if (allGetters) { checkDuplicateGetters(map); } return map; } private static boolean allGetters(Iterable<ExecutableElement> methods) { if (true) return true; 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<ExecutableElement, String> methodToIdentifier) { if (true) return; Set<String> seen = Sets.newHashSet(); for (Map.Entry<ExecutableElement, String> entry : methodToIdentifier.entrySet()) { if (!seen.add(entry.getValue())) { errorReporter.reportError("More than one @RetroWeibo 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<ExecutableElement, String> methodToIdentifier) { for (Map.Entry<ExecutableElement, String> entry : methodToIdentifier.entrySet()) { if (SourceVersion.isKeyword(entry.getValue())) { entry.setValue(disambiguate(entry.getValue(), methodToIdentifier.values())); } } } private String disambiguate(String name, Collection<String> existingNames) { for (int i = 0;; i++) { String candidate = name + i; if (!existingNames.contains(candidate)) { return candidate; } } } private Set<TypeMirror> returnTypesOf(Iterable<ExecutableElement> methods) { Set<TypeMirror> returnTypes = new TypeMirrorSet(); for (ExecutableElement method : methods) { returnTypes.add(method.getReturnType()); } return returnTypes; } private static boolean containsArrayType(Set<TypeMirror> 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<ExecutableElement> methods, RetroWeiboTemplateVars 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<ExecutableElement> methodsToImplement(List<ExecutableElement> methods) { ImmutableSet.Builder<ExecutableElement> 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 @RetroWeibo 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 ancestorIsRetroWeibo(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(RetroWeibo.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<VariableElement> 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 static TypeMirror getTypeMirror(ProcessingEnvironment processingEnv, Class<?> c) { return processingEnv.getElementUtils().getTypeElement(c.getCanonicalName()).asType(); } // The @RetroWeibo type, with a ? for every type. // If we have @RetroWeibo abstract class Foo<T extends Something> then this method will return // just <?>. private static String wildcardTypeParametersString(TypeElement type) { List<? extends TypeParameterElement> 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<? extends VariableElement> parameters = method.getParameters(); if (parameters.isEmpty()) { return ""; } else { return "" + Joiner.on(" + ") .join(FluentIterable.from(parameters).transform(new Function<VariableElement, String>() { @Override public String apply(VariableElement element) { return "" + element.getSimpleName(); } })) + ""; } } private static String formalArgsString(ExecutableElement method) { List<? extends VariableElement> parameters = method.getParameters(); if (parameters.isEmpty()) { return ""; } else { return "" + Joiner.on(", ") .join(FluentIterable.from(parameters).transform(new Function<VariableElement, String>() { @Override public String apply(VariableElement element) { return "" + element.getSimpleName(); } })) + ""; } } private static String formalTypeArgsString(ExecutableElement method) { List<? extends VariableElement> parameters = method.getParameters(); if (parameters.isEmpty()) { return ""; } else { return "" + Joiner.on(", ") .join(FluentIterable.from(parameters).transform(new Function<VariableElement, String>() { @Override public String apply(VariableElement element) { return "final " + element.asType() + " " + element.getSimpleName(); } })) + ""; } } private EclipseHack eclipseHack() { return new EclipseHack(processingEnv); } }