Java tutorial
/* * 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 auto.parse.processor; import com.google.auto.service.AutoService; 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.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedOptions; 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.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 auto.parse.AutoParse; /** * Javac annotation processor (compiler plugin) for value types; user code never references this * class. * * @see auto.parse.AutoParse * @author amonn McManus */ @AutoService(Processor.class) @SupportedOptions(EclipseHack.ENABLING_OPTION) public class AutoParseProcessor extends AbstractProcessor { private static final boolean SILENT = true; public AutoParseProcessor() { } @Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton(AutoParse.class.getName()); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } private void note(String msg) { if (!SILENT) { processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg); } } @SuppressWarnings("serial") // CHECKSTYLE:OFF:WhitespaceAround private static class CompileException extends Exception { } // CHECKSTYLE:ON /** * Issue a compilation error. This method does not throw an exception, since we want to * continue processing and perhaps report other errors. It is a good idea to introduce a * test case in CompilationErrorsTest for any new call to reportError(...) to ensure that we * continue correctly after an error. */ private void reportError(String msg, Element e) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e); } /** * Issue a compilation error and abandon the processing of this class. This does not prevent * the processing of other classes. */ private void abortWithError(String msg, Element e) throws CompileException { reportError(msg, e); throw new CompileException(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { boolean claimed = (annotations.size() == 1 && annotations.iterator().next().getQualifiedName().toString().equals(AutoParse.class.getName())); if (claimed) { process(roundEnv); return true; } else { return false; } } private void process(RoundEnvironment roundEnv) { Collection<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(AutoParse.class); Collection<? extends TypeElement> types = ElementFilter.typesIn(annotatedElements); for (TypeElement type : types) { try { processType(type); } catch (CompileException e) { // We abandoned this type, but continue with the next. } catch (RuntimeException e) { // Don't propagate this exception, which will confusingly crash the compiler. reportError("@AutoParse processor threw an exception: " + e, type); } } } 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, "AutoParse_"); } private static String simpleNameOf(String s) { if (s.contains(".")) { return s.substring(s.lastIndexOf('.') + 1); } else { return s; } } // Return the name of the class, including any enclosing classes but not the package. private static String classNameOf(TypeElement type) { String name = type.getQualifiedName().toString(); String pkgName = TypeSimplifier.packageNameOf(type); if (!pkgName.isEmpty()) { return name.substring(pkgName.length() + 1); } else { return name; } } // This is just because I hate typing "...\n" + all the time. private static String concatLines(String... lines) { StringBuilder sb = new StringBuilder(); for (String line : lines) { sb.append(line).append("\n"); } return sb.toString(); } // The code below uses a small templating language. This is not hugely readable, but is much more // so than sb.append(this).append(that) with ifs and fors scattered around everywhere. // See the Template class for an explanation of the various constructs. private static final String TEMPLATE_STRING = concatLines( // CHECKSTYLE:OFF:OperatorWrap // Package declaration "$[pkg?package $[pkg];\n]", // Imports "$[imports:i||import $[i];\n]", "import com.parse.ParseClassName;", "import java.util.Date;", "import org.json.JSONArray;", "import org.json.JSONObject;", "import java.util.List;", "import java.util.Map;", "import com.parse.ParseFile;", "import com.parse.ParseGeoPoint;", "import com.parse.ParseObject;", "import com.parse.ParseQuery;", "import com.parse.ParseRelation;", "import com.parse.ParseUser;\n", // Class declaration "@ParseClassName(\"$[origclass]\")", "public class $[subclass]$[formaltypes] extends $[origclass]$[actualtypes] {", // Fields //"$[props:p|| public $[p.type] $[p];\n]", " ParseObject parseObject;\n", // Constructor " public $[subclass]() {", " this.parseObject = this;", " }\n", " public $[subclass](ParseObject parseObject) {", " this.parseObject = parseObject;", " }\n", " public $[subclass](\n $[getters:p|,\n |$[p.type] $[p.getField]]) {", " this();", "$[setters:p|\n| $[p]($[p.getField]);]", " }\n", " private Object _get(String key, Object defValue) {", " return parseObject.get(key);", " }\n", " private boolean _get(String key, Boolean defValue) {", " return parseObject.getBoolean(key);", " }\n", " private byte[] _get(String key, byte[] defValue) {", " return parseObject.getBytes(key);", " }\n", " private Date _get(String key, Date defValue) {", " return parseObject.getDate(key);", " }\n", " private double _get(String key, Double defValue) {", " return parseObject.getDouble(key);", " }\n", " private int _get(String key, Integer defValue) {", " return parseObject.getInt(key);", " }\n", " private JSONArray _get(String key, JSONArray defValue) {", " return parseObject.getJSONArray(key);", " }\n", " private JSONObject _get(String key, JSONObject defValue) {", " return parseObject.getJSONObject(key);", " }\n", " @SuppressWarnings(\"unchecked\")", " private <T> List<T> _get(String key, List<T> defValue) {", " return (List<T>) parseObject.getList(key);", " }\n", " @SuppressWarnings(\"unchecked\")", " private <V> Map<String, V> _get(String key, Map<String, V> defValue) {", " return (Map<String, V>) parseObject.getMap(key);", " }\n", " @SuppressWarnings(\"unchecked\")", " private <T extends ParseObject> ParseRelation<T> _get(String key, ParseRelation<T> defValue) {", " return (ParseRelation<T>) parseObject.getRelation(key);", " }\n", " private long _get(String key, Long defValue) {", " return parseObject.getLong(key);", " }\n", " private Number _get(String key, Number defValue) {", " return parseObject.getNumber(key);", " }\n", " private ParseFile _get(String key, ParseFile defValue) {", " return parseObject.getParseFile(key);", " }\n", " private ParseGeoPoint _get(String key, ParseGeoPoint defValue) {", " return parseObject.getParseGeoPoint(key);", " }\n", " private <T extends ParseObject> T _get(String key, T defValue) {", " return (T) parseObject.getParseObject(key);", " }\n", " private ParseUser _get(String key, ParseUser defValue) {", " return parseObject.getParseUser(key);", " }\n", " private String _get(String key, String defValue) {", " return parseObject.getString(key);", " }", // Property getters "$[getters:p|\n|\n @Override", " $[p.access]$[p.type] $[p]($[p.getTypeArgs]) {", //" if (int.class.equals($[p.type].class)) {", //" return parseObject.getInt(key);", //" }", //" if (boolean.class.equals($[p.type].class)) {", //" return parseObject.getBoolean(key);", //" }", //" if (ParseObject.class.equals($[p.type].class)) {", //" return parseObject.getParseObject(key);", //" }", //" if (Boolean.class.equals($[p.type].class)) {", //" return parseObject.getBoolean(key);", //" }", //" if (Bytes.class.equals($[p.type].class)) {", //" return parseObject.getBytes(key);", //" }", //" if (Date.class.equals($[p.type].class)) {", //" return parseObject.getDate(key);", //" }", //" if (Double.class.equals($[p.type].class)) {", //" return parseObject.getDouble(key);", //" }", //" if (Int.class.equals($[p.type].class)) {", //" return parseObject.getInt(key);", //" }", //" if (JSONArray.class.equals($[p.type].class)) {", //" return parseObject.getJSONArray(key);", //" }", //" if (JSONObject.class.equals($[p.type].class)) {", //" return parseObject.getJSONObject(key);", //" }", //" if (List.class.equals($[p.type].class)) {", //" return (List<T>) parseObject.getList(key);", //" }", //" if (Map.class.equals($[p.type].class)) {", //" return (Map<String, V>) parseObject.getMap(key);", //" }", //" if (Relation.class.equals($[p.type].class)) {", //" return (ParseRelation<T>) parseObject.getRelation(key);", //" }", //" if (Long.class.equals($[p.type].class)) {", //" return parseObject.getLong(key);", //" }", //" if (Number.class.equals($[p.type].class)) {", //" return parseObject.getNumber(key);", //" }", //" if (ParseFile.class.equals($[p.type].class)) {", //" return parseObject.getParseFile(key);", //" }", //" if (ParseGeoPoint.class.equals($[p.type].class)) {", //" return parseObject.getParseGeoPoint(key);", //" }", //" if (ParseObject.class.equals($[p.type].class)) {", //" return parseObject.getParseObject(key);", //" }", //" if (ParseUser.class.equals($[p.type].class)) {", //" return parseObject.getParseUser(key);", //" }", //" if (String.class.equals($[p.type].class)) {", //" return parseObject.getString(key);", //" }]]", " return _get(\"$[p.getField]\", $[p.getDefalutValue]);", " }]", // Property setters "$[setters:p|\n|\n @Override", " $[p.access]$[origclass] $[p]($[p.getTypeArgs]) {", " if ($[p.getArgs] == null) return this;", " put(\"$[p.getField]\", $[p.getArgs]);", " return this;", " }]", // toString() "$[toString?\n @Override", " public String toString() {", " return \"$[simpleclassname]{\"$[getters?\n + \"]" + "$[getters:p|\n + \", |" + "$[p.getField]=\" + $[p.array?[$[Arrays].toString($[p]())][$[p]()]]]", " + \"}\";", " }]", // parcelable "$[parcelable?\n\n", " public static final android.os.Parcelable.Creator<$[origclass]> CREATOR = new android.os.Parcelable.Creator<$[origclass]>() {", " @Override public $[origclass] createFromParcel(android.os.Parcel in) {", " return new $[subclass](in);", " }", " @Override public $[origclass][] newArray(int size) {", " return new $[origclass][size];", " }", " };", "", " private final static java.lang.ClassLoader CL = $[subclass].class.getClassLoader();", "", " private $[subclass](android.os.Parcel in) {", " this(\n $[getters:p|,\n |($[p.castType]) in.readValue(CL)]);", " }", "", " @Override public void writeToParcel(android.os.Parcel dest, int flags) {", "$[getters:p|| dest.writeValue($[p]());\n]", " }", "", " @Override public int describeContents() {", " return 0;", " }", "", " public static ParseQuery<$[origclass]> getQuery() {", " return ParseQuery.getQuery($[origclass].class);", " }", "", " public static ParseQuery<$[subclass]> getAutoQuery() {", " return ParseQuery.getQuery($[subclass].class);", " }", "]", "}" // CHECKSTYLE:ON ); private static final Template template = Template.compile(TEMPLATE_STRING); static class Property { private final ExecutableElement method; private final String type; private final Map<String, Object> vars; Property(ExecutableElement method, String type, Map<String, Object> vars) { this.method = method; this.type = type; this.vars = vars; } @Override public String toString() { return method.getSimpleName().toString(); } public String getField() { String field = method.getSimpleName().toString().replaceFirst("^set", "").replaceFirst("^get", ""); return field.substring(0, 1).toLowerCase() + field.substring(1); } public String getTypeArgs() { return formalTypeArgsString(method, (TypeSimplifier) vars.get("typeSimplifier")); } public String getArgs() { return formalArgsString(method); } public String getDefalutValue() { String defaultValue = formalArgsString(method); if (defaultValue.isEmpty()) { defaultValue = "(" + type + ") null"; } return defaultValue; } TypeElement owner() { return (TypeElement) method.getEnclosingElement(); } public String type() { return type; } // That wouldn't be necessary if we supported Java 7+. Oh well. public String castType() { return primitive() ? box(method.getReturnType().getKind()) : type(); } 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("Found a new Primitive type on the Java platform. " + "Go ahead and claim " + kind + " yours"); } } public boolean primitive() { return method.getReturnType().getKind().isPrimitive(); } public boolean array() { return method.getReturnType().getKind() == TypeKind.ARRAY; } public boolean nullable() { for (AnnotationMirror annotationMirror : method.getAnnotationMirrors()) { String name = annotationMirror.getAnnotationType().asElement().getSimpleName().toString(); if (name.equals("Nullable")) { return true; } } return false; } private static final Template PRIMITIVE_EQUALS_TEMPLATE = Template.compile("this.$[p] == that.$[p]()"); private static final Template ARRAY_EQUALS_TEMPLATE = Template.compile("$[Arrays].equals(this.$[p], " + "(that instanceof $[subclass]) ? (($[subclass]) that).$[p] : that.$[p]())"); private static final Template FLOAT_EQUALS_TEMPLATE = Template .compile("Float.floatToIntBits(this.$[p]) == Float.floatToIntBits(that.$[p]())"); private static final Template DOUBLE_EQUALS_TEMPLATE = Template .compile("Double.doubleToLongBits(this.$[p]) == Double.doubleToLongBits(that.$[p]())"); // CHECKSTYLE:OFF:OperatorWrap private static final Template OBJECT_EQUALS_TEMPLATE = Template.compile("$[p.nullable?" + "(this.$[p] == null) ? (that.$[p]() == null) : ]" + "this.$[p].equals(that.$[p]())"); // CHECKSTYLE:ON /** * A string representing an expression that compares this property with the same property * in another variable called "that" whose type is the class marked {@code @AutoParse}. */ public String equalsThatExpression() { // If the templating language had a case statement we wouldn't need this function, but the // language is unreadable enough as it is. Template template; switch (method.getReturnType().getKind()) { case BYTE: case SHORT: case CHAR: case INT: case LONG: case BOOLEAN: template = PRIMITIVE_EQUALS_TEMPLATE; break; case FLOAT: template = FLOAT_EQUALS_TEMPLATE; break; case DOUBLE: template = DOUBLE_EQUALS_TEMPLATE; break; case ARRAY: template = ARRAY_EQUALS_TEMPLATE; break; default: template = OBJECT_EQUALS_TEMPLATE; break; } Map<String, Object> newVars = new TreeMap<String, Object>(vars); newVars.put("p", this); return template.rewrite(newVars); } /** * A string representing an expression that is the hashCode of this property. */ public String hashCodeExpression() { switch (method.getReturnType().getKind()) { case BYTE: case SHORT: case CHAR: case INT: return this.toString(); case LONG: return "(" + this + " >>> 32) ^ " + this; case FLOAT: return "Float.floatToIntBits(" + this + ")"; case DOUBLE: return "(Double.doubleToLongBits(" + this + ") >>> 32) ^ " + "Double.doubleToLongBits(" + this + ")"; case BOOLEAN: return this + " ? 1231 : 1237"; case ARRAY: return vars.get("Arrays") + ".hashCode(" + this + ")"; default: if (nullable()) { return "(" + this + " == null) ? 0 : " + this + ".hashCode()"; } else { return this + ".hashCode()"; } } } public String access() { 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 static boolean isToStringOrEqualsOrHashCode(ExecutableElement method) { String name = method.getSimpleName().toString(); return ((name.equals("toString") || name.equals("hashCode")) && method.getParameters().isEmpty()) || (name.equals("equals") && method.getParameters().size() == 1 && method.getParameters().get(0).asType().toString().equals("java.lang.Object")); } private void findLocalAndInheritedMethods(TypeElement type, List<ExecutableElement> methods) { note("Looking at methods in " + type); 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()); eclipseHack().sortMethodsIfSimulatingEclipse(theseMethods); 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) { methods.add(method); } } } } private void processType(TypeElement type) throws CompileException { AutoParse autoParse = type.getAnnotation(AutoParse.class); if (autoParse == null) { // This shouldn't happen unless the compilation environment is buggy, // but it has happened in the past and can crash the compiler. abortWithError("annotation processor for @AutoParse was invoked with a type that " + "does not have that annotation; this is probably a compiler bug", type); } if (type.getKind() != ElementKind.CLASS) { abortWithError("@" + AutoParse.class.getName() + " only applies to classes", type); } if (ancestorIsAndroidAutoParse(type)) { abortWithError("One @AutoParse class may not extend another", type); } Map<String, Object> vars = new TreeMap<String, Object>(); vars.put("type", type); vars.put("pkg", TypeSimplifier.packageNameOf(type)); vars.put("origclass", classNameOf(type)); vars.put("simpleclassname", simpleNameOf(classNameOf(type))); vars.put("formaltypes", formalTypeString(type)); vars.put("actualtypes", actualTypeString(type)); vars.put("wildcardtypes", wildcardTypeString(type)); vars.put("subclass", simpleNameOf(generatedSubclassName(type))); vars.put("cacheHashCode", autoParse.cacheHashCode()); defineVarsForType(type, vars); String text = template.rewrite(vars); writeSourceFile(generatedSubclassName(type), text, type); } private void defineVarsForType(TypeElement type, Map<String, Object> vars) throws CompileException { List<ExecutableElement> methods = new ArrayList<ExecutableElement>(); findLocalAndInheritedMethods(type, methods); vars.putAll(objectMethodsToGenerate(methods)); dontImplementAnnotationEqualsOrHashCode(type, vars); List<ExecutableElement> toImplement = methodsToImplement(methods, vars); Set<TypeMirror> types = new HashSet<TypeMirror>(); types.addAll(returnTypesOf(toImplement)); 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); } String pkg = TypeSimplifier.packageNameOf(type); TypeSimplifier typeSimplifier = new TypeSimplifier(processingEnv.getTypeUtils(), pkg, types); vars.put("typeSimplifier", typeSimplifier); vars.put("imports", typeSimplifier.typesToImport()); vars.put("Arrays", typeSimplifier.simplify(javaUtilArrays)); List<Property> getters = new ArrayList<Property>(); List<Property> setters = new ArrayList<Property>(); for (ExecutableElement method : toImplement) { String propType = typeSimplifier.simplify(method.getReturnType()); Property prop = new Property(method, propType, vars); if (method.getSimpleName().toString().startsWith("get")) { getters.add(prop); } else if (method.getSimpleName().toString().startsWith("set")) { setters.add(prop); } } // If we are running from Eclipse, undo the work of its compiler which sorts methods. eclipseHack().reorderProperties(getters); eclipseHack().reorderProperties(setters); vars.put("getters", getters); vars.put("setters", setters); vars.put("serialVersionUID", getSerialVersionUID(type)); TypeMirror parcelable = getTypeMirror("android.os.Parcelable"); vars.put("parcelable", processingEnv.getTypeUtils().isAssignable(type.asType(), parcelable)); } private Set<TypeMirror> returnTypesOf(List<ExecutableElement> methods) { HashSet<TypeMirror> returnTypes = new HashSet<TypeMirror>(); 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; } private void dontImplementAnnotationEqualsOrHashCode(TypeElement type, Map<String, ?> vars) { TypeMirror javaLangAnnotationAnnotation = getTypeMirror(Annotation.class); Types typeUtils = processingEnv.getTypeUtils(); if (typeUtils.isAssignable(type.asType(), javaLangAnnotationAnnotation)) { boolean equals = (Boolean) vars.get("equals"); boolean hashCode = (Boolean) vars.get("hashCode"); if (equals || hashCode) { String bad = equals ? (hashCode ? "equals(Object) and hashCode()" : "equals(Object)") : "hashCode()"; reportError("The implementation of " + bad + " that would be generated for this @AutoParse " + "class would not obey the contract of " + bad + " in " + Annotation.class.getName(), type); } } } /** * Given a list of all methods defined in or inherited by a class, returns a map with keys * "toString", "equals", "hashCode" and corresponding value true if that method should be * generated. */ private static Map<String, Boolean> objectMethodsToGenerate(List<ExecutableElement> methods) { Map<String, Boolean> vars = new TreeMap<String, Boolean>(); // 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.put("equals", false); vars.put("hashCode", false); vars.put("toString", false); for (ExecutableElement method : methods) { if (isToStringOrEqualsOrHashCode(method)) { boolean canGenerate = method.getModifiers().contains(Modifier.ABSTRACT) || isJavaLangObject((TypeElement) method.getEnclosingElement()); vars.put(method.getSimpleName().toString(), canGenerate); } } assert vars.size() == 3; return vars; } private boolean isSuperType(TypeMirror type, TypeMirror origClass) { if (origClass.equals(type)) { return true; } List<? extends TypeMirror> superTypes = processingEnv.getTypeUtils().directSupertypes(origClass); for (TypeMirror superType : superTypes) { if (superType.equals(type)) { return true; } } return false; } private List<ExecutableElement> methodsToImplement(List<ExecutableElement> methods, Map<String, Object> vars) throws CompileException { List<ExecutableElement> toImplement = new ArrayList<ExecutableElement>(); boolean errors = false; for (ExecutableElement method : methods) { if (method.getModifiers().contains(Modifier.ABSTRACT) && !isToStringOrEqualsOrHashCode(method) && !isFromParcelable(method)) { if (method.getParameters().isEmpty() && method.getReturnType().getKind() != TypeKind.VOID && (method.getSimpleName().toString().startsWith("get"))) { if (isReferenceArrayType(method.getReturnType())) { reportError("An @AutoParse class cannot define an array-valued property unless it is " + "a byte array", method); errors = true; } toImplement.add(method); } else if ((method.getParameters().size() == 1) && (method.getReturnType().getKind() != TypeKind.VOID) && (method.getSimpleName().toString().startsWith("get"))) { if (!method.getParameters().get(0).asType().equals(method.getReturnType())) { reportError("An @AutoParse class cannot define an default-value that is difference" + "to return-value ", method); errors = true; } toImplement.add(method); } else if ((method.getParameters().size() == 1) && isSuperType(method.getReturnType(), ((TypeElement) vars.get("type")).asType()) && (method.getSimpleName().toString().startsWith("set"))) { if (isReferenceArrayType(method.getParameters().get(0).asType())) { reportError("An @AutoParse class cannot define an array-valued property unless it is " + "a byte array", method); errors = true; } toImplement.add(method); } else { reportError( "@AutoParse classes cannot have abstract methods other than property getters and setters", method); errors = true; } } } if (errors) { throw new CompileException(); } return toImplement; } private boolean isFromParcelable(ExecutableElement method) { String name = method.getSimpleName().toString(); boolean isDescribeContents = name.equals("describeContents") && method.getParameters().isEmpty() && method.getReturnType().toString().equals("int"); boolean isWriteToParcel = name.equals("writeToParcel") && method.getParameters().size() == 2 && method.getReturnType().toString().equals("void") && method.getParameters().get(0).asType().toString().equals("android.os.Parcel") && method.getParameters().get(1).asType().toString().equals("int"); return isDescribeContents || isWriteToParcel; } private static boolean isReferenceArrayType(TypeMirror type) { return type.getKind() == TypeKind.ARRAY && (((ArrayType) type).getComponentType().getKind() != TypeKind.BYTE); } private void writeSourceFile(String className, String text, TypeElement originatingType) { try { note(text); 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 ancestorIsAndroidAutoParse(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(AutoParse.class) != null) { return true; } type = parentElement; } } // 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 { reportError("serialVersionUID must be a static final long compile-time constant", field); break; } } } } return ""; } private TypeMirror getTypeMirror(Class<?> c) { return getTypeMirror(c.getName()); } private TypeMirror getTypeMirror(String className) { return processingEnv.getElementUtils().getTypeElement(className).asType(); } // Why does TypeParameterElement.toString() not return this? Grrr. private static String typeParameterString(TypeParameterElement type) { String s = type.getSimpleName().toString(); List<? extends TypeMirror> bounds = type.getBounds(); if (bounds.isEmpty()) { return s; } else { s += " extends "; String sep = ""; for (TypeMirror bound : bounds) { s += sep + bound; sep = " & "; } return s; } } private static String formalTypeString(TypeElement type) { List<? extends TypeParameterElement> typeParameters = type.getTypeParameters(); if (typeParameters.isEmpty()) { return ""; } else { String s = "<"; String sep = ""; for (TypeParameterElement typeParameter : typeParameters) { s += sep + typeParameterString(typeParameter); sep = ", "; } return s + ">"; } } private static String actualTypeString(TypeElement type) { List<? extends TypeParameterElement> typeParameters = type.getTypeParameters(); if (typeParameters.isEmpty()) { return ""; } else { String s = "<"; String sep = ""; for (TypeParameterElement typeParameter : typeParameters) { s += sep + typeParameter.getSimpleName(); sep = ", "; } return s + ">"; } } // The @AutoParse type, with a ? for every type. private static String wildcardTypeString(TypeElement type) { List<? extends TypeParameterElement> typeParameters = type.getTypeParameters(); if (typeParameters.isEmpty()) { return ""; } else { String s = "<"; String sep = ""; for (int i = 0; i < typeParameters.size(); i++) { s += sep + "?"; sep = ", "; } return s + ">"; } } private static String formalArgsString(ExecutableElement method) { List<? extends VariableElement> typeParameters = method.getParameters(); if (typeParameters.isEmpty()) { return ""; } else { String s = ""; String sep = ""; for (VariableElement typeParameter : typeParameters) { s += sep + typeParameter.getSimpleName(); sep = ", "; } return s; } } private static String formalTypeArgsString(ExecutableElement method, TypeSimplifier typeSimplifier) { List<? extends VariableElement> typeParameters = method.getParameters(); if (typeParameters.isEmpty()) { return ""; } else { String s = ""; String sep = ""; for (VariableElement typeParameter : typeParameters) { s += sep + typeSimplifier.simplify(typeParameter.asType()) + " " + typeParameter.getSimpleName(); sep = ", "; } return s; } } private EclipseHack eclipseHack() { return new EclipseHack(processingEnv); } }