diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/GwtCompilationTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/GwtCompilationTest.java index caf388ffb7..7e6c1f0dfa 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/GwtCompilationTest.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/GwtCompilationTest.java @@ -69,11 +69,11 @@ public void testBasic() { "foo.bar.AutoValue_Baz_CustomFieldSerializer", "package foo.bar;", "", - "import javax.annotation.Generated;", "import com.google.gwt.user.client.rpc.CustomFieldSerializer;", "import com.google.gwt.user.client.rpc.SerializationException;", "import com.google.gwt.user.client.rpc.SerializationStreamReader;", "import com.google.gwt.user.client.rpc.SerializationStreamWriter;", + "import javax.annotation.Generated;", "", "@Generated(\"com.google.auto.value.processor.AutoValueProcessor\")", "public final class AutoValue_Baz_CustomFieldSerializer" @@ -161,12 +161,12 @@ public void testSuppressWarnings() { "foo.bar.AutoValue_Baz_CustomFieldSerializer", "package foo.bar;", "", - "import java.util.List;", - "import javax.annotation.Generated;", "import com.google.gwt.user.client.rpc.CustomFieldSerializer;", "import com.google.gwt.user.client.rpc.SerializationException;", "import com.google.gwt.user.client.rpc.SerializationStreamReader;", "import com.google.gwt.user.client.rpc.SerializationStreamWriter;", + "import java.util.List;", + "import javax.annotation.Generated;", "", "@Generated(\"com.google.auto.value.processor.AutoValueProcessor\")", "public final class AutoValue_Baz_CustomFieldSerializer" @@ -261,12 +261,12 @@ public void testBuildersAndGenerics() { "foo.bar.AutoValue_Baz_CustomFieldSerializer", "package foo.bar;", "", - "import java.util.Map;", - "import javax.annotation.Generated;", "import com.google.gwt.user.client.rpc.CustomFieldSerializer;", "import com.google.gwt.user.client.rpc.SerializationException;", "import com.google.gwt.user.client.rpc.SerializationStreamReader;", "import com.google.gwt.user.client.rpc.SerializationStreamWriter;", + "import java.util.Map;", + "import javax.annotation.Generated;", "", "@Generated(\"com.google.auto.value.processor.AutoValueProcessor\")", "public final class AutoValue_Baz_CustomFieldSerializer" diff --git a/value/src/it/gwtserializer/src/test/java/com/google/auto/value/client/GwtSerializerTest.java b/value/src/it/gwtserializer/src/test/java/com/google/auto/value/client/GwtSerializerTest.java index b19ce5f664..721dcae387 100644 --- a/value/src/it/gwtserializer/src/test/java/com/google/auto/value/client/GwtSerializerTest.java +++ b/value/src/it/gwtserializer/src/test/java/com/google/auto/value/client/GwtSerializerTest.java @@ -24,8 +24,6 @@ import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; import com.google.gwt.user.server.rpc.RemoteServiceServlet; -import java.lang.Override; -import java.lang.SuppressWarnings; public class GwtSerializerTest extends GWTTestCase { diff --git a/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java b/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java index d219ce2983..8cc0998c47 100644 --- a/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java +++ b/value/src/main/java/com/google/auto/value/processor/AnnotationOutput.java @@ -33,12 +33,8 @@ * * @author emcmanus@google.com (Éamonn McManus) */ -class AnnotationOutput { - private final TypeSimplifier typeSimplifier; - - AnnotationOutput(TypeSimplifier typeSimplifier) { - this.typeSimplifier = typeSimplifier; - } +final class AnnotationOutput { + private AnnotationOutput() {} // There are no instances of this class. /** * Visitor that produces a string representation of an annotation value, suitable for inclusion @@ -49,7 +45,7 @@ class AnnotationOutput { * for example the construction of an {@code @AutoAnnotation} class. That's why we have this * abstract class and two concrete subclasses. */ - private abstract class SourceFormVisitor + private abstract static class SourceFormVisitor extends SimpleAnnotationValueVisitor8 { @Override protected Void defaultAction(Object value, StringBuilder sb) { @@ -112,7 +108,7 @@ public Void visitFloat(float f, StringBuilder sb) { @Override public Void visitEnumConstant(VariableElement c, StringBuilder sb) { - sb.append(typeSimplifier.simplify(c.asType())).append('.').append(c.getSimpleName()); + sb.append(TypeEncoder.encode(c.asType())).append('.').append(c.getSimpleName()); return null; } @@ -124,12 +120,12 @@ public Void visitString(String s, StringBuilder sb) { @Override public Void visitType(TypeMirror classConstant, StringBuilder sb) { - sb.append(typeSimplifier.simplify(classConstant)).append(".class"); + sb.append(TypeEncoder.encode(classConstant)).append(".class"); return null; } } - private class InitializerSourceFormVisitor extends SourceFormVisitor { + private static class InitializerSourceFormVisitor extends SourceFormVisitor { private final ProcessingEnvironment processingEnv; private final String memberName; private final Element context; @@ -153,10 +149,10 @@ public Void visitAnnotation(AnnotationMirror a, StringBuilder sb) { } } - private class AnnotationSourceFormVisitor extends SourceFormVisitor { + private static class AnnotationSourceFormVisitor extends SourceFormVisitor { @Override public Void visitAnnotation(AnnotationMirror a, StringBuilder sb) { - sb.append('@').append(typeSimplifier.simplify(a.getAnnotationType())); + sb.append('@').append(TypeEncoder.encode(a.getAnnotationType())); Map map = ImmutableMap.copyOf(a.getElementValues()); if (!map.isEmpty()) { @@ -177,7 +173,7 @@ public Void visitAnnotation(AnnotationMirror a, StringBuilder sb) { * Returns a string representation of the given annotation value, suitable for inclusion in a Java * source file as the initializer of a variable of the appropriate type. */ - String sourceFormForInitializer( + static String sourceFormForInitializer( AnnotationValue annotationValue, ProcessingEnvironment processingEnv, String memberName, @@ -193,7 +189,7 @@ String sourceFormForInitializer( * Returns a string representation of the given annotation mirror, suitable for inclusion in a * Java source file to reproduce the annotation in source form. */ - String sourceFormForAnnotation(AnnotationMirror annotationMirror) { + static String sourceFormForAnnotation(AnnotationMirror annotationMirror) { StringBuilder sb = new StringBuilder(); new AnnotationSourceFormVisitor().visitAnnotation(annotationMirror, sb); return sb.toString(); diff --git a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java index 4a7780db91..b18b3e290f 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java @@ -15,8 +15,6 @@ */ package com.google.auto.value.processor; -import static java.util.stream.Collectors.toCollection; - import com.google.auto.common.MoreElements; import com.google.auto.common.SuperficialValidation; import com.google.auto.service.AutoService; @@ -28,14 +26,12 @@ import com.google.common.primitives.Primitives; import java.io.IOException; import java.io.Writer; -import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import javax.annotation.Generated; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; @@ -143,36 +139,25 @@ private void processMethod(ExecutableElement method) { } TypeElement annotationElement = getAnnotationReturnType(method); - TypeMirror annotationTypeMirror = annotationElement.asType(); Set> wrapperTypesUsedInCollections = wrapperTypesUsedInCollections(method); ImmutableMap memberMethods = getMemberMethods(annotationElement); - Set memberTypes = getMemberTypes(memberMethods.values()); - Set referencedTypes = getReferencedTypes( - annotationTypeMirror, method, memberTypes, wrapperTypesUsedInCollections); TypeElement methodClass = (TypeElement) method.getEnclosingElement(); String pkg = TypeSimplifier.packageNameOf(methodClass); - TypeSimplifier typeSimplifier = new TypeSimplifier( - typeUtils, pkg, referencedTypes, annotationTypeMirror); - AnnotationOutput annotationOutput = new AnnotationOutput(typeSimplifier); ImmutableMap defaultValues = getDefaultValues(annotationElement); - ImmutableMap members = - getMembers(method, memberMethods, typeSimplifier, annotationOutput); - ImmutableMap parameters = - getParameters(annotationElement, method, members, typeSimplifier); + ImmutableMap members = getMembers(method, memberMethods); + ImmutableMap parameters = getParameters(annotationElement, method, members); validateParameters(annotationElement, method, members, parameters, defaultValues); String generatedClassName = generatedClassName(method); AutoAnnotationTemplateVars vars = new AutoAnnotationTemplateVars(); vars.annotationFullName = annotationElement.toString(); - vars.annotationName = typeSimplifier.simplify(annotationElement.asType()); + vars.annotationName = TypeEncoder.encode(annotationElement.asType()); vars.className = generatedClassName; - vars.imports = typeSimplifier.typesToImport(); - vars.generated = getGeneratedTypeName(typeSimplifier); - vars.arrays = typeSimplifier.simplify(getTypeMirror(Arrays.class)); + vars.generated = getGeneratedTypeName(); vars.members = members; vars.params = parameters; vars.pkg = pkg; @@ -185,18 +170,19 @@ private void processMethod(ExecutableElement method) { } vars.invariableHashes = invariableHashes.keySet(); String text = vars.toText(); + text = TypeEncoder.decode(text, processingEnv, pkg, annotationElement.asType()); text = Reformatter.fixup(text); String fullName = fullyQualifiedName(pkg, generatedClassName); writeSourceFile(fullName, text, methodClass); } - private String getGeneratedTypeName(TypeSimplifier typeSimplifier) { + private String getGeneratedTypeName() { TypeElement generatedTypeElement = processingEnv.getElementUtils().getTypeElement("javax.annotation.Generated"); if (generatedTypeElement == null) { return ""; } - return typeSimplifier.simplify(generatedTypeElement.asType()); + return TypeEncoder.encode(generatedTypeElement.asType()); } /** @@ -301,16 +287,14 @@ private ImmutableMap getMemberMethods(TypeElement ann private ImmutableMap getMembers( Element context, - ImmutableMap memberMethods, - TypeSimplifier typeSimplifier, - AnnotationOutput annotationOutput) { + ImmutableMap memberMethods) { ImmutableMap.Builder members = ImmutableMap.builder(); for (Map.Entry entry : memberMethods.entrySet()) { ExecutableElement memberMethod = entry.getValue(); String name = memberMethod.getSimpleName().toString(); members.put( name, - new Member(processingEnv, context, memberMethod, typeSimplifier, annotationOutput)); + new Member(processingEnv, context, memberMethod)); } return members.build(); } @@ -328,17 +312,10 @@ private ImmutableMap getDefaultValues(TypeElement annot return defaultValues.build(); } - private Set getMemberTypes(Collection memberMethods) { - return memberMethods.stream() - .map(m -> m.getReturnType()) - .collect(toCollection(TypeMirrorSet::new)); - } - private ImmutableMap getParameters( TypeElement annotationElement, ExecutableElement method, - Map members, - TypeSimplifier typeSimplifier) { + Map members) { ImmutableMap.Builder parameters = ImmutableMap.builder(); boolean error = false; for (VariableElement parameter : method.getParameters()) { @@ -353,7 +330,7 @@ private ImmutableMap getParameters( TypeMirror parameterType = parameter.asType(); TypeMirror memberType = member.getTypeMirror(); if (compatibleTypes(parameterType, memberType)) { - parameters.put(name, new Parameter(parameterType, typeSimplifier)); + parameters.put(name, new Parameter(parameterType)); } else { reportError(parameter, "@AutoAnnotation method parameter '%s' has type %s but %s.%s has type %s", @@ -440,40 +417,10 @@ private Set> wrapperTypesUsedInCollections(ExecutableElement method) { return usedInCollections.build(); } - private Set getReferencedTypes( - TypeMirror annotation, - ExecutableElement method, - Set memberTypes, - Set> wrapperTypesUsedInCollections) { - Set types = new TypeMirrorSet(); - types.add(annotation); - types.add(getTypeMirror(Generated.class)); - for (VariableElement parameter : method.getParameters()) { - // Method parameter types are usually the same as annotation member types, but in the case of - // List for int[] we are referencing List. - types.add(parameter.asType()); - } - types.addAll(memberTypes); - if (containsArrayType(types)) { - // If there are array properties then we will be referencing java.util.Arrays. - types.add(getTypeMirror(Arrays.class)); - } - if (!wrapperTypesUsedInCollections.isEmpty()) { - // If there is at least one parameter whose type is a collection of a primitive wrapper type - // (for example List) then we will be referencing java util.Collection. - types.add(getTypeMirror(Collection.class)); - } - return types; - } - private TypeMirror getTypeMirror(Class c) { return processingEnv.getElementUtils().getTypeElement(c.getName()).asType(); } - private static boolean containsArrayType(Set types) { - return types.stream().anyMatch(t -> t.getKind().equals(TypeKind.ARRAY)); - } - private static boolean isGwtCompatible(TypeElement annotationElement) { return annotationElement.getAnnotationMirrors().stream() .map(mirror -> mirror.getAnnotationType().asElement()) @@ -507,20 +454,14 @@ public static class Member { private final ProcessingEnvironment processingEnv; private final Element context; private final ExecutableElement method; - private final TypeSimplifier typeSimplifier; - private final AnnotationOutput annotationOutput; Member( ProcessingEnvironment processingEnv, Element context, - ExecutableElement method, - TypeSimplifier typeSimplifier, - AnnotationOutput annotationDefaults) { + ExecutableElement method) { this.processingEnv = processingEnv; this.context = context; this.method = method; - this.typeSimplifier = typeSimplifier; - this.annotationOutput = annotationDefaults; } @Override @@ -529,13 +470,13 @@ public String toString() { } public String getType() { - return typeSimplifier.simplify(getTypeMirror()); + return TypeEncoder.encode(getTypeMirror()); } public String getComponentType() { Preconditions.checkState(getTypeMirror().getKind() == TypeKind.ARRAY); ArrayType arrayType = (ArrayType) getTypeMirror(); - return typeSimplifier.simplify(arrayType.getComponentType()); + return TypeEncoder.encode(arrayType.getComponentType()); } public TypeMirror getTypeMirror() { @@ -579,7 +520,7 @@ public String getDefaultValue() { if (defaultValue == null) { return null; } else { - return annotationOutput.sourceFormForInitializer( + return AnnotationOutput.sourceFormForInitializer( defaultValue, processingEnv, method.getSimpleName().toString(), context); } } @@ -589,8 +530,8 @@ public static class Parameter { private final String typeName; private final TypeKind kind; - Parameter(TypeMirror type, TypeSimplifier typeSimplifier) { - this.typeName = typeSimplifier.simplify(type); + Parameter(TypeMirror type) { + this.typeName = TypeEncoder.encode(type); this.kind = type.getKind(); } diff --git a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java index 629fe166b0..3b82c75746 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java @@ -18,7 +18,6 @@ import com.google.auto.value.processor.escapevelocity.Template; import java.util.Map; import java.util.Set; -import java.util.SortedSet; /** * The variables to substitute into the autoannotation.vm template. @@ -39,19 +38,10 @@ class AutoAnnotationTemplateVars extends TemplateVars { Map params; /** - * The fully-qualified names of the classes to be imported in the generated class. - */ - SortedSet imports; - - /** - * The spelling of the {@code Generated} class: {@code Generated} or {@code - * javax.annotation.Generated}. Empty if the class is not available. + * The encoded form of the {@code Generated} class, or empty if it is not available. */ String generated; - /** The spelling of the java.util.Arrays class: Arrays or java.util.Arrays. */ - String arrays; - /** * The package of the class containing the {@code @AutoAnnotation} annotation, which is also the * package where the annotation implementation will be generated. diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java index de76afd0d0..e11188e24d 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java @@ -22,7 +22,6 @@ import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.collect.Sets.union; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; @@ -56,6 +55,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.OptionalInt; import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import java.util.Set; @@ -73,6 +73,7 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; +import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; @@ -137,7 +138,7 @@ public SourceVersion getSupportedSourceVersion() { * Qualified names of {@code @AutoValue} 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(); + private final List deferredTypeNames = new ArrayList<>(); // Depending on how this AutoValueProcessor was constructed, we might already have a list of // extensions when init() is run, or, if `extensions` is null, we have a ClassLoader that will be @@ -218,7 +219,7 @@ public boolean process(Set annotations, RoundEnvironment return false; // never claim annotation, because who knows what other processors want? } - private String generatedClassName(TypeElement type, String prefix) { + private static String generatedClassName(TypeElement type, String prefix) { String name = type.getSimpleName().toString(); while (type.getEnclosingElement() instanceof TypeElement) { type = (TypeElement) type.getEnclosingElement(); @@ -229,7 +230,7 @@ private String generatedClassName(TypeElement type, String prefix) { return pkg + dot + prefix + name; } - private String generatedSubclassName(TypeElement type, int depth) { + private static String generatedSubclassName(TypeElement type, int depth) { return generatedClassName(type, Strings.repeat("$", depth) + "AutoValue_"); } @@ -246,6 +247,7 @@ public static class Property { private final ExecutableElement method; private final String type; private final ImmutableList annotations; + private final OptionalInt nullableAnnotationIndex; private final Optionalish optional; private final boolean nullable; @@ -254,23 +256,18 @@ public static class Property { String identifier, ExecutableElement method, String type, - TypeSimplifier typeSimplifier, - ImmutableList annotations) { + ImmutableList annotations, + OptionalInt nullableAnnotationIndex) { this.name = name; this.identifier = identifier; this.method = method; this.type = type; this.annotations = annotations; + this.nullableAnnotationIndex = nullableAnnotationIndex; TypeMirror propertyType = method.getReturnType(); - this.optional = - Optionalish.createIfOptional(propertyType, typeSimplifier.simplifyRaw(propertyType)); - this.nullable = - !getNullableAnnotation().isEmpty() || nullableType(propertyType); - } - - private static boolean nullableType(TypeMirror type) { - return type.getAnnotationMirrors().stream().anyMatch( - a -> a.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable")); + this.optional = Optionalish.createIfOptional(propertyType); + this.nullable = nullableAnnotationIndex.isPresent() + || nullableAnnotationIndex(propertyType.getAnnotationMirrors()).isPresent(); } /** @@ -302,10 +299,6 @@ public String getGetter() { return method.getSimpleName().toString(); } - TypeElement getOwner() { - return (TypeElement) method.getEnclosingElement(); - } - public TypeMirror getTypeMirror() { return method.getReturnType(); } @@ -333,18 +326,16 @@ public Optionalish getOptional() { /** * Returns the string to use as a method annotation to indicate the nullability of * this property. It is either the empty string, if the property is not nullable, or - * an annotation string with a trailing space, such as {@code "@Nullable "} or {@code - * "@javax.annotation.Nullable "}. If the property is nullable by virtue of its type - * rather than its method being {@code @Nullable}, this method returns the empty string, because - * the {@code @Nullable} will appear when the type is spelled out. + * an annotation string with a trailing space, such as {@code "@`javax.annotation.Nullable` "}, + * where the {@code ``} is the encoding used by {@link TypeEncoder}. + * If the property is nullable by virtue of its type rather than its method being + * {@code @Nullable}, this method returns the empty string, because the {@code @Nullable} will + * appear when the type is spelled out. */ public final String getNullableAnnotation() { - for (String annotationString : annotations) { - if (annotationString.equals("@Nullable") || annotationString.endsWith(".Nullable")) { - return annotationString + " "; - } - } - return ""; + return nullableAnnotationIndex.isPresent() + ? annotations.get(nullableAnnotationIndex.getAsInt()) + " " + : ""; } public boolean isNullable() { @@ -516,8 +507,7 @@ private void processType(TypeElement type) { vars.identifiers = !processingEnv.getOptions().containsKey("com.google.auto.value.OmitIdentifiers"); determineObjectMethodsToGenerate(methods, vars); - TypeSimplifier typeSimplifier = - defineVarsForType(type, vars, toBuilderMethods, propertyMethods, builder); + defineVarsForType(type, vars, toBuilderMethods, propertyMethods, builder); // Only copy annotations from a class if it has @AutoValue.CopyAnnotations. if (isAnnotationPresent(type, AutoValue.CopyAnnotations.class)) { @@ -530,7 +520,7 @@ private void processType(TypeElement type) { processingEnv.getElementUtils()), getAnnotationsMarkedWithInherited(type)); - vars.annotations = copyAnnotations(type, type, typeSimplifier, excludedAnnotations); + vars.annotations = copyAnnotations(type, type, excludedAnnotations); } else { vars.annotations = ImmutableList.of(); } @@ -544,6 +534,7 @@ private void processType(TypeElement type) { vars.isFinal = (subclassDepth == 0); String text = vars.toText(); + text = TypeEncoder.decode(text, processingEnv, vars.pkg, type.asType()); text = Reformatter.fixup(text); writeSourceFile(subclass, text, type); GwtSerialization gwtSerialization = new GwtSerialization(gwtCompatibility, processingEnv, type); @@ -554,10 +545,27 @@ private void processType(TypeElement type) { private ImmutableList copyAnnotations( Element autoValueType, Element typeOrMethod, - TypeSimplifier typeSimplifier, Set excludedAnnotations) { - ImmutableList.Builder result = ImmutableList.builder(); - AnnotationOutput annotationOutput = new AnnotationOutput(typeSimplifier); + ImmutableList annotationsToCopy = + annotationsToCopy(autoValueType, typeOrMethod, excludedAnnotations); + return annotationStrings(annotationsToCopy); + } + + private static ImmutableList annotationStrings( + ImmutableList annotations) { + // TODO(emcmanus): figure out NoSuchMethodError on Android for ImmutableList.toImmutableList(). + return ImmutableList.copyOf(annotations + .stream() + .map(AnnotationOutput::sourceFormForAnnotation) + .collect(toList())); + } + + /** Implements the semantics of {@link AutoValue.CopyAnnotations}; see its javadoc. */ + private ImmutableList annotationsToCopy( + Element autoValueType, + Element typeOrMethod, + Set excludedAnnotations) { + ImmutableList.Builder result = ImmutableList.builder(); for (AnnotationMirror annotation : typeOrMethod.getAnnotationMirrors()) { String annotationFqName = getAnnotationFqName(annotation); // To be included, the annotation should not be @AutoValue or any of its nested annotations, @@ -565,7 +573,7 @@ private ImmutableList copyAnnotations( if (!AUTO_VALUE_CLASSNAME_PATTERN.matcher(annotationFqName).matches() && !excludedAnnotations.contains(annotationFqName) && annotationVisibleFrom(annotation, autoValueType)) { - result.add(annotationOutput.sourceFormForAnnotation(annotation)); + result.add(annotation); } } @@ -604,7 +612,8 @@ private boolean annotationVisibleFrom(AnnotationMirror annotation, Element from) * "com.google.auto.value.AutoValue". */ private static String getAnnotationFqName(AnnotationMirror annotation) { - return ((TypeElement) annotation.getAnnotationType().asElement()).getQualifiedName().toString(); + return ((QualifiedNameable) annotation.getAnnotationType().asElement()) + .getQualifiedName().toString(); } /** @@ -649,7 +658,7 @@ private ImmutableSet getFieldOfClasses( (List) entry.getValue().getValue(); for (AnnotationValue annotationValue : annotationsToCopy) { String qualifiedName = - ((TypeElement) ((DeclaredType) annotationValue.getValue()).asElement()) + ((QualifiedNameable) ((DeclaredType) annotationValue.getValue()).asElement()) .getQualifiedName() .toString(); result.add(qualifiedName); @@ -661,7 +670,7 @@ private ImmutableSet getFieldOfClasses( return ImmutableSet.of(); } - private Set getAnnotationsMarkedWithInherited(Element element) { + private static Set getAnnotationsMarkedWithInherited(Element element) { return element.getAnnotationMirrors().stream() .filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class)) .map(a -> getAnnotationFqName(a)) @@ -804,88 +813,88 @@ private String extensionName(AutoValueExtension extension) { return extension.getClass().getName(); } - private TypeSimplifier defineVarsForType( + private void defineVarsForType( TypeElement type, AutoValueTemplateVars vars, ImmutableSet toBuilderMethods, ImmutableSet propertyMethods, Optional builder) { - DeclaredType declaredType = MoreTypes.asDeclared(type.asType()); - Set types = new TypeMirrorSet(); - types.addAll(returnTypesOf(propertyMethods)); - if (builder.isPresent()) { - types.addAll(builder.get().referencedTypes()); - } TypeElement generatedTypeElement = processingEnv.getElementUtils().getTypeElement("javax.annotation.Generated"); - if (generatedTypeElement != null) { - types.add(generatedTypeElement.asType()); - } - TypeMirror javaUtilArrays = getTypeMirror(Arrays.class); - if (containsPrimitiveArrayType(types)) { - // If there are primitive array properties then we will be referencing java.util.Arrays. - // Arrange to import it unless that would introduce ambiguity. - types.add(javaUtilArrays); - } // We can't use ImmutableList.toImmutableList() for obscure Google-internal reasons. vars.toBuilderMethods = ImmutableList.copyOf(toBuilderMethods.stream().map(SimpleMethod::new).collect(toList())); - ImmutableSetMultimap excludedAnnotationsMap = - allMethodExcludedAnnotations(propertyMethods); - types.addAll(allMethodAnnotationTypes(propertyMethods, excludedAnnotationsMap)); - String pkg = TypeSimplifier.packageNameOf(type); - TypeSimplifier typeSimplifier = new TypeSimplifier(typeUtils, pkg, types, declaredType); - vars.imports = typeSimplifier.typesToImport(); vars.generated = generatedTypeElement == null ? "" - : typeSimplifier.simplify(generatedTypeElement.asType()); - vars.arrays = typeSimplifier.simplify(javaUtilArrays); + : TypeEncoder.encode(generatedTypeElement.asType()); + ImmutableBiMap methodToPropertyName = + propertyNameToMethodMap(propertyMethods).inverse(); + vars.props = propertySet(type, propertyMethods); + vars.serialVersionUID = getSerialVersionUID(type); + vars.formalTypes = TypeEncoder.formalTypeParametersString(type); + vars.actualTypes = TypeSimplifier.actualTypeParametersString(type); + vars.wildcardTypes = wildcardTypeParametersString(type); + // Check for @AutoValue.Builder and add appropriate variables if it is present. + if (builder.isPresent()) { + builder.get().defineVars(vars, methodToPropertyName); + } + } + + private ImmutableSet propertySet( + TypeElement type, ImmutableSet propertyMethods) { + ImmutableSetMultimap excludedAnnotationsMap = + allMethodExcludedAnnotations(propertyMethods); ImmutableBiMap methodToPropertyName = propertyNameToMethodMap(propertyMethods).inverse(); Map methodToIdentifier = Maps.newLinkedHashMap(methodToPropertyName); fixReservedIdentifiers(methodToIdentifier); - List props = new ArrayList(); EclipseHack eclipseHack = eclipseHack(); + DeclaredType declaredType = MoreTypes.asDeclared(type.asType()); ImmutableMap returnTypes = eclipseHack.methodReturnTypes(propertyMethods, declaredType); + + ImmutableSet.Builder props = ImmutableSet.builder(); for (ExecutableElement method : propertyMethods) { TypeMirror returnType = returnTypes.get(method); - String propertyType = typeSimplifier.simplifyWithAnnotations(returnType); + String propertyType = TypeEncoder.encodeWithAnnotations(returnType); String propertyName = methodToPropertyName.get(method); String identifier = methodToIdentifier.get(method); - ImmutableList annotations = - propertyMethodAnnotations(type, method, excludedAnnotationsMap, typeSimplifier); - Property p = - new Property( - propertyName, identifier, method, propertyType, typeSimplifier, annotations); + ImmutableList annotationMirrors = + propertyMethodAnnotations(type, method, excludedAnnotationsMap); + OptionalInt nullableAnnotationIndex = nullableAnnotationIndex(annotationMirrors); + ImmutableList annotations = annotationStrings(annotationMirrors); + Property p = new Property( + propertyName, identifier, method, propertyType, annotations, nullableAnnotationIndex); props.add(p); if (p.isNullable() && returnType.getKind().isPrimitive()) { errorReporter.reportError("Primitive types cannot be @Nullable", method); } } - vars.props = ImmutableSet.copyOf(props); - vars.serialVersionUID = getSerialVersionUID(type); - vars.formalTypes = typeSimplifier.formalTypeParametersString(type); - vars.actualTypes = TypeSimplifier.actualTypeParametersString(type); - vars.wildcardTypes = wildcardTypeParametersString(type); - // Check for @AutoValue.Builder and add appropriate variables if it is present. - if (builder.isPresent()) { - builder.get().defineVars(vars, typeSimplifier, methodToPropertyName); + return props.build(); + } + + private static OptionalInt nullableAnnotationIndex(List annotations) { + for (int i = 0; i < annotations.size(); i++) { + if (isNullable(annotations.get(i))) { + return OptionalInt.of(i); + } } - return typeSimplifier; + return OptionalInt.empty(); } - private ImmutableList propertyMethodAnnotations( + private static boolean isNullable(AnnotationMirror annotation) { + return annotation.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable"); + } + + private ImmutableList propertyMethodAnnotations( TypeElement type, ExecutableElement method, - ImmutableSetMultimap excludedAnnotationsMap, - TypeSimplifier typeSimplifier) { + ImmutableSetMultimap excludedAnnotationsMap) { ImmutableSet excludedAnnotations = ImmutableSet.builder() .addAll(excludedAnnotationsMap.get(method)) .add(Override.class.getCanonicalName()) .build(); - ImmutableList.Builder builder = ImmutableList.builder(); // We need to exclude type annotations from the ones being output on the method, since // they will be output as part of the method's return type. @@ -895,9 +904,7 @@ private ImmutableList propertyMethodAnnotations( .map(e -> e.getQualifiedName().toString()) .collect(toSet()); Set excluded = Sets.union(excludedAnnotations, typeAnnotations); - builder.addAll(copyAnnotations(type, method, typeSimplifier, excluded)); - - return builder.build(); + return annotationsToCopy(type, method, excluded); } private ImmutableSetMultimap allMethodExcludedAnnotations( @@ -946,38 +953,6 @@ static ImmutableSet prefixedGettersIn(Iterable allMethodAnnotationTypes( - Iterable methods, - ImmutableSetMultimap excludedAnnotationsMap) { - Set annotationTypes = new TypeMirrorSet(); - for (ExecutableElement method : methods) { - ImmutableSet excludedAnnotations = excludedAnnotationsMap.get(method); - for (AnnotationMirror annotationMirror : method.getAnnotationMirrors()) { - String annotationFqName = getAnnotationFqName(annotationMirror); - if (excludedAnnotations.contains(annotationFqName)) { - continue; - } - if (isAnnotationPresent( - annotationMirror.getAnnotationType().asElement(), Inherited.class)) { - continue; - } - if (AUTO_VALUE_CLASSNAME_PATTERN.matcher(annotationFqName).matches()) { - continue; - } - annotationTypes.add(annotationMirror.getAnnotationType()); - } - for (AnnotationMirror annotationMirror : method.getReturnType().getAnnotationMirrors()) { - annotationTypes.add(annotationMirror.getAnnotationType()); - } - } - return annotationTypes; - } - /** * Returns the name of the property defined by the given getter. A getter called {@code getFoo()} * or {@code isFoo()} defines a property called {@code foo}. For consistency with JavaBeans, a @@ -1033,18 +1008,6 @@ private String disambiguate(String name, Collection existingNames) { } } - private Set returnTypesOf(Collection methods) { - return methods.stream() - .map(m -> m.getReturnType()) - .collect(toCollection(TypeMirrorSet::new)); - } - - private static boolean containsPrimitiveArrayType(Set types) { - return types.stream().anyMatch(type -> - type.getKind().equals(TypeKind.ARRAY) - && ((ArrayType) type).getComponentType().getKind().isPrimitive()); - } - /** * 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. diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java index 81ca28eede..436f832555 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java @@ -21,7 +21,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSortedSet; import java.util.Optional; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.util.Types; @@ -56,22 +55,16 @@ class AutoValueTemplateVars extends TemplateVars { /** The type utilities returned by {@link ProcessingEnvironment#getTypeUtils()}. */ Types types; - /** The fully-qualified names of the classes to be imported in the generated class. */ - ImmutableSortedSet imports; - /** - * The spelling of the {@code Generated} class: {@code Generated} or {@code - * javax.annotation.Generated}. Empty if the class is not available. + * The encoding of the {@code Generated} class. Empty if the class is not available. */ String generated; - /** The spelling of the java.util.Arrays class: Arrays or java.util.Arrays. */ - String arrays; - /** - * The full spelling of the {@code @GwtCompatible} annotation to add to this class, or an empty - * string if there is none. A non-empty value might look something like - * {@code "@com.google.common.annotations.GwtCompatible(serializable = true)"}. + * The encoding of the {@code @GwtCompatible} annotation to add to this class, or an empty + * string if there is none. A non-empty value will look something like + * {@code "@`com.google.common.annotations.GwtCompatible`(serializable = true)"}, where the + * {@code ``} represent the encoding used by {@link TypeEncoder}. */ String gwtCompatibleAnnotation; diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java index b8ae2e0f99..0f79beb15c 100644 --- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java +++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java @@ -59,7 +59,6 @@ class BuilderMethodClassifier { private final TypeElement builderType; private final ImmutableBiMap getterToPropertyName; private final ImmutableMap getterNameToGetter; - private final TypeSimplifier typeSimplifier; private final Set buildMethods = Sets.newLinkedHashSet(); private final Map builderGetters = @@ -79,8 +78,7 @@ private BuilderMethodClassifier( ProcessingEnvironment processingEnv, TypeElement autoValueClass, TypeElement builderType, - ImmutableBiMap getterToPropertyName, - TypeSimplifier typeSimplifier) { + ImmutableBiMap getterToPropertyName) { this.errorReporter = errorReporter; this.typeUtils = processingEnv.getTypeUtils(); this.elementUtils = processingEnv.getElementUtils(); @@ -93,7 +91,6 @@ private BuilderMethodClassifier( getterToPropertyNameBuilder.put(getter.getSimpleName().toString(), getter); } this.getterNameToGetter = getterToPropertyNameBuilder.build(); - this.typeSimplifier = typeSimplifier; this.eclipseHack = new EclipseHack(processingEnv); } @@ -106,7 +103,6 @@ private BuilderMethodClassifier( * @param autoValueClass the {@code AutoValue} class containing the builder. * @param builderType the builder class or interface within {@code autoValueClass}. * @param getterToPropertyName a map from getter methods to the properties they get. - * @param typeSimplifier the TypeSimplifier that will be used to control imports. * @param autoValueHasToBuilder true if the containing {@code @AutoValue} class has a * {@code toBuilder()} method. * @@ -120,15 +116,13 @@ static Optional classify( TypeElement autoValueClass, TypeElement builderType, ImmutableBiMap getterToPropertyName, - TypeSimplifier typeSimplifier, boolean autoValueHasToBuilder) { BuilderMethodClassifier classifier = new BuilderMethodClassifier( errorReporter, processingEnv, autoValueClass, builderType, - getterToPropertyName, - typeSimplifier); + getterToPropertyName); if (classifier.classifyMethods(methods, autoValueHasToBuilder)) { return Optional.of(classifier); } else { @@ -197,7 +191,7 @@ private boolean classifyMethods( } for (Map.Entry getterEntry : getterToPropertyName.entrySet()) { String property = getterEntry.getValue(); - String propertyType = typeSimplifier.simplify(getterEntry.getKey().getReturnType()); + TypeMirror propertyType = getterEntry.getKey().getReturnType(); boolean hasSetter = propertyNameToSetter.containsKey(property); PropertyBuilder propertyBuilder = propertyNameToPropertyBuilder.get(property); boolean hasBuilder = propertyBuilder != null; @@ -214,7 +208,7 @@ private boolean classifyMethods( String error = String.format( "Property builder method returns %1$s but there is no way to make that type from " + "%2$s: %2$s does not have a non-static toBuilder() method that returns %1$s", - propertyBuilder.getBuilderType(), + propertyBuilder.getBuilderTypeMirror(), propertyType); errorReporter.reportError(error, propertyBuilder.getPropertyBuilderMethod()); } @@ -275,8 +269,7 @@ private boolean classifyMethodNoArgs(ExecutableElement method) { String property = methodName.substring(0, methodName.length() - "Builder".length()); if (getterToPropertyName.containsValue(property)) { PropertyBuilderClassifier propertyBuilderClassifier = new PropertyBuilderClassifier( - errorReporter, typeUtils, elementUtils, this, getterToPropertyName, typeSimplifier, - eclipseHack); + errorReporter, typeUtils, elementUtils, this, getterToPropertyName, eclipseHack); Optional propertyBuilder = propertyBuilderClassifier.makePropertyBuilder(method, property); if (propertyBuilder.isPresent()) { @@ -305,7 +298,7 @@ private boolean classifyGetter( ExecutableElement builderGetter, ExecutableElement originalGetter) { String propertyName = getterToPropertyName.get(originalGetter); TypeMirror builderGetterType = builderMethodReturnType(builderGetter); - String builderGetterTypeString = typeSimplifier.simplifyWithAnnotations(builderGetterType); + String builderGetterTypeString = TypeEncoder.encodeWithAnnotations(builderGetterType); TypeMirror originalGetterType = originalGetter.getReturnType(); if (TYPE_EQUIVALENCE.equivalent(builderGetterType, originalGetterType)) { builderGetters.put( @@ -313,8 +306,7 @@ private boolean classifyGetter( new BuilderSpec.PropertyGetter(builderGetter, builderGetterTypeString, null)); return true; } - Optionalish optional = Optionalish.createIfOptional( - builderGetterType, typeSimplifier.simplifyRaw(builderGetterType)); + Optionalish optional = Optionalish.createIfOptional(builderGetterType); if (optional != null) { TypeMirror containedType = optional.getContainedType(typeUtils); // If the original method is int getFoo() then we allow Optional here. @@ -392,7 +384,7 @@ private boolean classifyMethodOneArg(ExecutableElement method) { } } - // A frequence source of problems is where the JavaBeans conventions have been followed for + // A frequent source of problems is where the JavaBeans conventions have been followed for // most but not all getters. Then AutoValue considers that they haven't been followed at all, // so you might have a property called getFoo where you thought it was called just foo, and // you might not understand why your setter called setFoo is rejected (it would have to be called diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java index 413c2bbf04..6e308c66d0 100644 --- a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java +++ b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java @@ -39,7 +39,6 @@ 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.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -163,27 +162,8 @@ ImmutableSet toBuilderMethods( return builderMethods; } - /** - * Returns the types that are referenced by abstract methods in the builder, either as - * parameters or as return types. - */ - Set referencedTypes() { - Set types = new TypeMirrorSet(); - for (ExecutableElement method : - ElementFilter.methodsIn(builderTypeElement.getEnclosedElements())) { - if (method.getModifiers().contains(Modifier.ABSTRACT)) { - types.add(method.getReturnType()); - for (VariableElement parameter : method.getParameters()) { - types.add(parameter.asType()); - } - } - } - return types; - } - void defineVars( AutoValueTemplateVars vars, - TypeSimplifier typeSimplifier, ImmutableBiMap getterToPropertyName) { Iterable builderMethods = abstractMethods(builderTypeElement); boolean autoValueHasToBuilder = !toBuilderMethods.isEmpty(); @@ -194,7 +174,6 @@ void defineVars( autoValueClass, builderTypeElement, getterToPropertyName, - typeSimplifier, autoValueHasToBuilder); if (!optionalClassifier.isPresent()) { return; @@ -216,7 +195,7 @@ void defineVars( ExecutableElement buildMethod = Iterables.getOnlyElement(buildMethods); vars.builderIsInterface = builderTypeElement.getKind() == ElementKind.INTERFACE; vars.builderTypeName = TypeSimplifier.classNameOf(builderTypeElement); - vars.builderFormalTypes = typeSimplifier.formalTypeParametersString(builderTypeElement); + vars.builderFormalTypes = TypeEncoder.formalTypeParametersString(builderTypeElement); vars.builderActualTypes = TypeSimplifier.actualTypeParametersString(builderTypeElement); vars.buildMethod = Optional.of(new AutoValueProcessor.SimpleMethod(buildMethod)); vars.builderGetters = classifier.builderGetters(); @@ -227,7 +206,7 @@ void defineVars( String property = entry.getKey(); ExecutableElement setter = entry.getValue(); TypeMirror propertyType = getterToPropertyName.inverse().get(property).getReturnType(); - setterBuilder.put(property, new PropertySetter(setter, propertyType, typeSimplifier)); + setterBuilder.put(property, new PropertySetter(setter, propertyType)); } vars.builderSetters = setterBuilder.build(); @@ -309,29 +288,38 @@ public class PropertySetter { private final boolean primitiveParameter; private final String copyOf; - public PropertySetter( - ExecutableElement setter, TypeMirror propertyType, TypeSimplifier typeSimplifier) { + public PropertySetter(ExecutableElement setter, TypeMirror propertyType) { this.access = AutoValueProcessor.access(setter); this.name = setter.getSimpleName().toString(); TypeMirror parameterType = Iterables.getOnlyElement(setter.getParameters()).asType(); primitiveParameter = parameterType.getKind().isPrimitive(); - String simplifiedParameterType = typeSimplifier.simplifyWithAnnotations(parameterType); - if (setter.isVarArgs()) { - simplifiedParameterType = simplifiedParameterType.replaceAll("\\[\\]$", "..."); - } - this.parameterTypeString = simplifiedParameterType; + this.parameterTypeString = parameterTypeString(setter, parameterType); Types typeUtils = processingEnv.getTypeUtils(); TypeMirror erasedPropertyType = typeUtils.erasure(propertyType); boolean sameType = typeUtils.isSameType(typeUtils.erasure(parameterType), erasedPropertyType); if (sameType) { this.copyOf = null; } else { - String rawTarget = typeSimplifier.simplifyRaw(erasedPropertyType); + String rawTarget = TypeEncoder.encodeRaw(erasedPropertyType); String of = Optionalish.isOptional(propertyType) ? "of" : "copyOf"; this.copyOf = rawTarget + "." + of + "(%s)"; } } + private String parameterTypeString(ExecutableElement setter, TypeMirror parameterType) { + if (setter.isVarArgs()) { + TypeMirror componentType = MoreTypes.asArray(parameterType).getComponentType(); + // This is a bit ugly. It's OK to annotate just the component type, because if it is + // say `@Nullable String` then we will end up with `@Nullable String...`. Unlike the + // normal array case, we can't have the situation where the array itself is annotated; + // you can write `String @Nullable []` to mean that, but you can't write + // `String @Nullable ...`. + return TypeEncoder.encodeWithAnnotations(componentType) + "..."; + } else { + return TypeEncoder.encodeWithAnnotations(parameterType); + } + } + public String getAccess() { return access; } diff --git a/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java index c61f06c1ae..5c3312220e 100644 --- a/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java +++ b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java @@ -15,23 +15,23 @@ */ package com.google.auto.value.processor; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.toList; import com.google.auto.value.processor.escapevelocity.Template; import com.google.common.collect.Multimap; import java.io.IOException; import java.io.Writer; -import java.nio.charset.Charset; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.SortedSet; import java.util.zip.CRC32; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; @@ -82,7 +82,6 @@ private boolean shouldWriteGwtSerializer() { void maybeWriteGwtSerializer(AutoValueTemplateVars autoVars) { if (shouldWriteGwtSerializer()) { GwtTemplateVars vars = new GwtTemplateVars(); - vars.imports = autoVars.imports; vars.pkg = autoVars.pkg; vars.subclass = autoVars.subclass; vars.formalTypes = autoVars.formalTypes; @@ -94,8 +93,9 @@ void maybeWriteGwtSerializer(AutoValueTemplateVars autoVars) { + "_CustomFieldSerializer"; vars.serializerClass = TypeSimplifier.simpleNameOf(className); vars.props = autoVars.props.stream().map(Property::new).collect(toList()); - vars.classHashString = computeClassHash(autoVars.props); + vars.classHashString = computeClassHash(autoVars.props, vars.pkg); String text = vars.toText(); + text = TypeEncoder.decode(text, processingEnv, vars.pkg, type.asType()); writeSourceFile(className, text, type); } } @@ -132,10 +132,11 @@ public String getName() { * every other type uses readObject and writeObject. */ public String getGwtType() { - String type = property.getType(); + TypeMirror typeMirror = property.getTypeMirror(); + String type = typeMirror.toString(); if (property.getKind().isPrimitive()) { return Character.toUpperCase(type.charAt(0)) + type.substring(1); - } else if (type.equals("String")) { + } else if (type.equals("java.lang.String")) { return "String"; } else { return "Object"; @@ -166,9 +167,6 @@ static class GwtTemplateVars extends TemplateVars { /** The properties defined by the parent class's abstract methods. */ List props; - /** The fully-qualified names of the classes to be imported in the generated class. */ - SortedSet imports; - /** * The package of the class with the {@code @AutoValue} annotation and its generated subclass. */ @@ -205,8 +203,8 @@ static class GwtTemplateVars extends TemplateVars { String serializerClass; /** - * The spelling of the {@code Generated} class: {@code Generated} or {@code - * javax.annotation.Generated}. Empty if the class is not available. + * The encoding of the {@code Generated} class. Empty if no {@code Generated} class is + * available. */ String generated; @@ -234,20 +232,25 @@ private void writeSourceFile(String className, String text, TypeElement originat } } - private static final Charset UTF8 = Charset.forName("UTF-8"); - - private String computeClassHash(Iterable props) { - TypeSimplifier typeSimplifier = new TypeSimplifier( - processingEnv.getTypeUtils(), "", new TypeMirrorSet(), null); + // Compute a hash that is guaranteed to change if the names, types, or order of the fields + // change. We use TypeEncoder so that we can get a defined string for types, since + // TypeMirror.toString() isn't guaranteed to remain the same. + private String computeClassHash( + Iterable props, String pkg) { CRC32 crc = new CRC32(); - update(crc, typeSimplifier.simplify(type.asType()) + ":"); + String encodedType = TypeEncoder.encode(type.asType()) + ":"; + String decodedType = TypeEncoder.decode(encodedType, processingEnv, "", null); + if (!decodedType.startsWith(pkg)) { + // This is for compatibility with the way an earlier version did things. Preserving hash + // codes probably isn't vital, since client and server should be in sync. + decodedType = pkg + "." + decodedType; + } + crc.update(decodedType.getBytes(UTF_8)); for (AutoValueProcessor.Property prop : props) { - update(crc, prop + ":" + prop.getType() + ";"); + String encodedProp = prop + ":" + TypeEncoder.encode(prop.getTypeMirror()) + ";"; + String decodedProp = TypeEncoder.decode(encodedProp, processingEnv, pkg, null); + crc.update(decodedProp.getBytes(UTF_8)); } return String.format("%08x", crc.getValue()); } - - private static void update(CRC32 crc, String s) { - crc.update(s.getBytes(UTF8)); - } } diff --git a/value/src/main/java/com/google/auto/value/processor/Optionalish.java b/value/src/main/java/com/google/auto/value/processor/Optionalish.java index e7c27b5fad..c4a68a8d1b 100644 --- a/value/src/main/java/com/google/auto/value/processor/Optionalish.java +++ b/value/src/main/java/com/google/auto/value/processor/Optionalish.java @@ -17,7 +17,6 @@ import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; -import com.google.common.base.Preconditions; import com.google.common.base.Verify; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -43,11 +42,9 @@ public class Optionalish { "java.util.OptionalLong"); private final DeclaredType optionalType; - private final String rawTypeSpelling; - private Optionalish(DeclaredType optionalType, String rawTypeSpelling) { + private Optionalish(DeclaredType optionalType) { this.optionalType = optionalType; - this.rawTypeSpelling = rawTypeSpelling; } /** @@ -55,14 +52,10 @@ private Optionalish(DeclaredType optionalType, String rawTypeSpelling) { * * @param type the TypeMirror for the original optional type, for example * {@code Optional}. - * @param rawTypeSpelling the representation of the base Optional type in source code, given - * the imports that will be present. Usually this will be {@code Optional}, - * {@code OptionalInt}, etc. In cases of ambiguity it might be {@code java.util.Optional} etc. */ - static Optionalish createIfOptional(TypeMirror type, String rawTypeSpelling) { + static Optionalish createIfOptional(TypeMirror type) { if (isOptional(type)) { - return new Optionalish( - MoreTypes.asDeclared(type), Preconditions.checkNotNull(rawTypeSpelling)); + return new Optionalish(MoreTypes.asDeclared(type)); } else { return null; } @@ -84,7 +77,7 @@ static boolean isOptional(TypeMirror type) { * for example. */ public String getRawType() { - return rawTypeSpelling; + return TypeEncoder.encodeRaw(optionalType); } /** @@ -100,7 +93,7 @@ public String getEmpty() { String empty = typeElement.getQualifiedName().toString().startsWith("java.util.") ? ".empty()" : ".absent()"; - return rawTypeSpelling + empty; + return TypeEncoder.encodeRaw(optionalType) + empty; } TypeMirror getContainedType(Types typeUtils) { diff --git a/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java b/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java index 9773415ee1..18c9d7753a 100644 --- a/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java +++ b/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java @@ -52,7 +52,6 @@ class PropertyBuilderClassifier { private final Elements elementUtils; private final BuilderMethodClassifier builderMethodClassifier; private final ImmutableBiMap getterToPropertyName; - private final TypeSimplifier typeSimplifier; private final EclipseHack eclipseHack; PropertyBuilderClassifier( @@ -61,14 +60,12 @@ class PropertyBuilderClassifier { Elements elementUtils, BuilderMethodClassifier builderMethodClassifier, ImmutableBiMap getterToPropertyName, - TypeSimplifier typeSimplifier, EclipseHack eclipseHack) { this.errorReporter = errorReporter; this.typeUtils = typeUtils; this.elementUtils = elementUtils; this.builderMethodClassifier = builderMethodClassifier; this.getterToPropertyName = getterToPropertyName; - this.typeSimplifier = typeSimplifier; this.eclipseHack = eclipseHack; } @@ -82,6 +79,7 @@ public static class PropertyBuilder { private final ExecutableElement propertyBuilderMethod; private final String name; private final String builderType; + private final TypeMirror builderTypeMirror; private final String initializer; private final String beforeInitDefault; private final String initDefault; @@ -91,6 +89,7 @@ public static class PropertyBuilder { PropertyBuilder( ExecutableElement propertyBuilderMethod, String builderType, + TypeMirror builderTypeMirror, String initializer, String beforeInitDefault, String initDefault, @@ -99,6 +98,7 @@ public static class PropertyBuilder { this.propertyBuilderMethod = propertyBuilderMethod; this.name = propertyBuilderMethod.getSimpleName() + "$"; this.builderType = builderType; + this.builderTypeMirror = builderTypeMirror; this.initializer = initializer; this.beforeInitDefault = beforeInitDefault; this.initDefault = initDefault; @@ -125,6 +125,10 @@ public String getBuilderType() { return builderType; } + TypeMirror getBuilderTypeMirror() { + return builderTypeMirror; + } + /** An initializer for the builder field, for example {@code ImmutableSet.builder()}. */ public String getInitializer() { return initializer; @@ -243,8 +247,8 @@ Optional makePropertyBuilder(ExecutableElement method, String p } ExecutableElement builderMaker = maybeBuilderMaker.get(); - String barBuilderType = typeSimplifier.simplifyWithAnnotations(barBuilderTypeMirror); - String rawBarType = typeSimplifier.simplifyRaw(barTypeMirror); + String barBuilderType = TypeEncoder.encodeWithAnnotations(barBuilderTypeMirror); + String rawBarType = TypeEncoder.encodeRaw(barTypeMirror); String initializer = (builderMaker.getKind() == ElementKind.CONSTRUCTOR) ? "new " + barBuilderType + "()" : rawBarType + "." + builderMaker.getSimpleName() + "()"; @@ -259,7 +263,7 @@ Optional makePropertyBuilder(ExecutableElement method, String p } else { boolean isGuavaBuilder = barBuilderTypeMirror.toString().startsWith(COM_GOOGLE_COMMON_COLLECT_IMMUTABLE) - && barBuilderType.contains(".Builder<"); + && barBuilderTypeMirror.toString().contains(".Builder<"); Optional maybeCopyAll = addAllPutAll(barBuilderTypeElement); if (maybeCopyAll.isPresent() && isGuavaBuilder) { copyAll = maybeCopyAll.get().getSimpleName().toString(); @@ -288,6 +292,7 @@ Optional makePropertyBuilder(ExecutableElement method, String p PropertyBuilder propertyBuilder = new PropertyBuilder( method, barBuilderType, + barBuilderTypeMirror, initializer, beforeInitDefault, initDefault, diff --git a/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java b/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java new file mode 100644 index 0000000000..14e8604098 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2017 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.auto.value.processor; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +import com.google.auto.common.MoreElements; +import com.google.auto.common.MoreTypes; +import java.util.List; +import java.util.OptionalInt; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ErrorType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.Elements; +import javax.lang.model.util.SimpleTypeVisitor8; +import javax.lang.model.util.Types; + +/** + * Encodes types so they can later be decoded to incorporate imports. + * + *

The idea is that types that appear in generated source code use {@link #encode}, which will + * spell out a type like {@code java.util.List}, except that wherever a + * class name appears it is replaced by a special token. So the spelling might actually be + * {@code `java.util.List`}. Then once the entire class has been + * generated, {@code #decode} scans for these tokens to determine what classes need to be imported, + * and replaces the tokens with the correct spelling given the imports. So here, + * {@code java.util.List} would be imported, and the final spelling would be + * {@code List} (knowing that {@code Number} is implicitly imported). The special + * token {@code `import`} marks where the imports should be, and {@link #decode} replaces it with + * the correct list of imports. + * + *

The funky syntax for type annotations on qualified type names requires an adjustment to this + * scheme. {@code `«java.util.Map`} stands for {@code java.util.} + * and {@code `»java.util.Map`} stands for {@code Map}. If {@code java.util.Map} is imported, then + * {@code `«java.util.Map`} will eventually be empty, but if {@code java.util.Map} is not imported + * (perhaps because there is another {@code Map} in scope) then {@code `«java.util.Map`} will be + * {@code java.util.}. The end result is that the code can contain + * {@code `«java.util.Map`@`javax.annotation.Nullable` `»java.util.Map`}. That might decode to + * {@code @Nullable Map} or to {@code java.util.@Nullable Map} or even to + * {@code java.util.@javax.annotation.Nullable Map}. + * + * @author emcmanus@google.com (Éamonn McManus) + */ +final class TypeEncoder { + private TypeEncoder() {} // There are no instances of this class. + + private static final EncodingTypeVisitor ENCODING_TYPE_VISITOR = + new EncodingTypeVisitor(); + private static final RawEncodingTypeVisitor RAW_ENCODING_TYPE_VISITOR = + new RawEncodingTypeVisitor(); + private static final AnnotatedEncodingTypeVisitor ANNOTATED_ENCODING_TYPE_VISITOR = + new AnnotatedEncodingTypeVisitor(); + + /** + * Returns the encoding for the given type, where class names are marked by special tokens. + * The encoding for {@code int} will be {@code int}, but the encoding for + * {@code java.util.List} will be + * {@code `java.util.List`<`java.lang.Integer`>}. + */ + static String encode(TypeMirror type) { + StringBuilder sb = new StringBuilder(); + return type.accept(ENCODING_TYPE_VISITOR, sb).toString(); + } + + /** + * Like {@link #encode}, except that only the raw type is encoded. So if the given type is + * {@code java.util.List} the result will be {@code `java.util.List`}. + */ + static String encodeRaw(TypeMirror type) { + StringBuilder sb = new StringBuilder(); + return type.accept(RAW_ENCODING_TYPE_VISITOR, sb).toString(); + } + + /** + * Encodes the given type and its type annotations. The class comment for {@link TypeEncoder} + * covers the details of annotation encoding. + */ + static String encodeWithAnnotations(TypeMirror type) { + StringBuilder sb = new StringBuilder(); + return type.accept(ANNOTATED_ENCODING_TYPE_VISITOR, sb).toString(); + } + + /** + * Decodes the given string, respelling class names appropriately. The text is scanned for + * tokens like {@code `java.util.Locale`} or {@code `«java.util.Locale`} to determine which + * classes are referenced. An appropriate set of imports is computed based on the set of those + * types. If the special token {@code `import`} appears in {@code text} then it will be replaced + * by this set of import statements. Then all of the tokens are replaced by the class names they + * represent, spelled appropriately given the import statements. + * + * @param text the text to be decoded. + * @param packageName the package of the generated class. Other classes in the same package + * do not need to be imported. + * @param baseType a class or interface that the generated class inherits from. Nested classes + * in that type do not need to be imported, and if another class has the same name as one + * of those nested classes then it will need to be qualified. + */ + static String decode( + String text, ProcessingEnvironment processingEnv, String packageName, TypeMirror baseType) { + return decode(text, processingEnv.getElementUtils(), processingEnv.getTypeUtils(), + packageName, baseType); + } + + static String decode( + String text, Elements elementUtils, Types typeUtils, String pkg, TypeMirror baseType) { + TypeRewriter typeRewriter = new TypeRewriter(text, elementUtils, typeUtils, pkg, baseType); + return typeRewriter.rewrite(); + } + + private static String className(DeclaredType declaredType) { + return MoreElements.asType(declaredType.asElement()).getQualifiedName().toString(); + } + + /** + * Returns the formal type parameters of the given type. + * If we have {@code @AutoValue abstract class Foo} then this method will + * return an encoding of {@code } for {@code Foo}. Likewise it will return + * an encoding of the angle-bracket part of:
+ * {@code Foo}
+ * {@code Foo}
+ * {@code Foo>}
+ * {@code Foo>}. + * + *

The encoding is simply that classes in the "extends" part are marked, so the examples + * will actually look something like this:
+ * {@code <`bar.baz.SomeClass`>}
+ * {@code }
+ * {@code >}
+ * {@code >}. + */ + static String formalTypeParametersString(TypeElement type) { + List typeParameters = type.getTypeParameters(); + if (typeParameters.isEmpty()) { + return ""; + } else { + StringBuilder sb = new StringBuilder("<"); + String sep = ""; + for (TypeParameterElement typeParameter : typeParameters) { + sb.append(sep); + sep = ", "; + appendTypeParameterWithBounds(sb, typeParameter); + } + return sb.append(">").toString(); + } + } + + private static void appendTypeParameterWithBounds( + StringBuilder sb, TypeParameterElement typeParameter) { + sb.append(typeParameter.getSimpleName()); + String sep = " extends "; + for (TypeMirror bound : typeParameter.getBounds()) { + if (!bound.toString().equals("java.lang.Object")) { + sb.append(sep); + sep = " & "; + sb.append(encode(bound)); + } + } + } + + /** + * Converts a type into a string, using standard Java syntax, except that every class name + * is wrapped in backquotes, like {@code `java.util.List`}. + */ + private static class EncodingTypeVisitor + extends SimpleTypeVisitor8 { + @Override protected StringBuilder defaultAction(TypeMirror type, StringBuilder sb) { + return sb.append(type); + } + + @Override public StringBuilder visitArray(ArrayType type, StringBuilder sb) { + return visit(type.getComponentType(), sb).append("[]"); + } + + @Override public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) { + sb.append(declaredTypeName(type)); + appendTypeArguments(type, sb); + return sb; + } + + String declaredTypeName(DeclaredType type) { + return "`" + className(type) + "`"; + } + + void appendTypeArguments(DeclaredType type, StringBuilder sb) { + List arguments = type.getTypeArguments(); + if (!arguments.isEmpty()) { + sb.append("<"); + String sep = ""; + for (TypeMirror argument : arguments) { + sb.append(sep); + sep = ", "; + visit(argument, sb); + } + sb.append(">"); + } + } + + @Override public StringBuilder visitWildcard(WildcardType type, StringBuilder sb) { + sb.append("?"); + TypeMirror extendsBound = type.getExtendsBound(); + TypeMirror superBound = type.getSuperBound(); + if (superBound != null) { + sb.append(" super "); + visit(superBound, sb); + } else if (extendsBound != null) { + sb.append(" extends "); + visit(extendsBound, sb); + } + return sb; + } + + @Override public StringBuilder visitError(ErrorType t, StringBuilder p) { + throw new MissingTypeException(); + } + } + + /** + * Like {@link EncodingTypeVisitor} except that type parameters are omitted from the result. + */ + private static class RawEncodingTypeVisitor extends EncodingTypeVisitor { + @Override void appendTypeArguments(DeclaredType type, StringBuilder sb) {} + } + + /** + * Like {@link EncodingTypeVisitor} except that annotations on the visited type are also + * included in the resultant string. Class names in those annotations are also encoded using + * the {@code `java.util.List`} form. + */ + private static class AnnotatedEncodingTypeVisitor extends EncodingTypeVisitor { + @Override public StringBuilder visitPrimitive(PrimitiveType type, StringBuilder sb) { + appendAnnotations(type.getAnnotationMirrors(), sb); + // We can't just append type.toString(), because that will also have the annotation, but + // without encoding. + return sb.append(type.getKind().toString().toLowerCase()); + } + + @Override public StringBuilder visitTypeVariable(TypeVariable type, StringBuilder sb) { + appendAnnotations(type.getAnnotationMirrors(), sb); + return sb.append(type.asElement().getSimpleName()); + } + + /** + * {@inheritDoc} + * The result respects the Java syntax, whereby {@code Foo @Bar []} is an annotation on the + * array type itself, while {@code @Bar Foo[]} would be an annotation on the component type. + */ + @Override public StringBuilder visitArray(ArrayType type, StringBuilder sb) { + visit(type.getComponentType(), sb); + List annotationMirrors = type.getAnnotationMirrors(); + if (!annotationMirrors.isEmpty()) { + sb.append(" "); + appendAnnotations(annotationMirrors, sb); + } + return sb.append("[]"); + } + + @Override public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) { + List annotationMirrors = type.getAnnotationMirrors(); + if (annotationMirrors.isEmpty()) { + sb.append(declaredTypeName(type)); + } else { + String className = className(type); + // See the class doc comment for an explanation of « and » here. + sb.append("`«").append(className).append("`"); + appendAnnotations(annotationMirrors, sb); + sb.append("`»").append(className).append("`"); + } + appendTypeArguments(type, sb); + return sb; + } + + private void appendAnnotations( + List annotationMirrors, StringBuilder sb) { + for (AnnotationMirror annotationMirror : annotationMirrors) { + sb.append(AnnotationOutput.sourceFormForAnnotation(annotationMirror)).append(" "); + } + } + } + + private static class TypeRewriter { + private final String text; + private final int textLength; + private final JavaScanner scanner; + private final Elements elementUtils; + private final Types typeUtils; + private final String packageName; + private final TypeMirror baseType; + + TypeRewriter( + String text, Elements elementUtils, Types typeUtils, String pkg, TypeMirror baseType) { + this.text = text; + this.textLength = text.length(); + this.scanner = new JavaScanner(text); + this.elementUtils = elementUtils; + this.typeUtils = typeUtils; + this.packageName = pkg; + this.baseType = baseType; + } + + String rewrite() { + // Scan the text to determine what classes are referenced. + Set referencedClasses = findReferencedClasses(); + // Make a type simplifier based on these referenced types. + TypeSimplifier typeSimplifier = + new TypeSimplifier(typeUtils, packageName, referencedClasses, baseType); + + StringBuilder output = new StringBuilder(); + int copyStart; + + // Replace the `import` token with the import statements, if it is present. + OptionalInt importMarker = findImportMarker(); + if (importMarker.isPresent()) { + output.append(text, 0, importMarker.getAsInt()); + for (String toImport : typeSimplifier.typesToImport()) { + output.append("import ").append(toImport).append(";\n"); + } + copyStart = scanner.tokenEnd(importMarker.getAsInt()); + } else { + copyStart = 0; + } + + // Replace each of the classname tokens with the appropriate spelling of the classname. + int token; + for (token = copyStart; token < textLength; token = scanner.tokenEnd(token)) { + if (text.charAt(token) == '`') { + output.append(text, copyStart, token); + decode(output, typeSimplifier, token); + copyStart = scanner.tokenEnd(token); + } + } + output.append(text, copyStart, textLength); + return output.toString(); + } + + private Set findReferencedClasses() { + Set classes = new TypeMirrorSet(); + for (int token = 0; token < textLength; token = scanner.tokenEnd(token)) { + if (text.charAt(token) == '`' && !text.startsWith("`import`", token)) { + String className = classNameAt(token); + classes.add(classForName(className)); + } + } + return classes; + } + + private DeclaredType classForName(String className) { + TypeElement typeElement = elementUtils.getTypeElement(className); + checkState(typeElement != null, "Could not find referenced class %s", className); + return MoreTypes.asDeclared(typeElement.asType()); + } + + private void decode(StringBuilder output, TypeSimplifier typeSimplifier, int token) { + String className = classNameAt(token); + DeclaredType type = classForName(className); + String simplified = typeSimplifier.simplifiedClassName(type); + int dot; + switch (text.charAt(token + 1)) { + case '«': + // If this is `«java.util.Map` then we want "java.util." here. + // That's because this is the first part of something like "java.util.@Nullable Map" + // or "java.util.Map.@Nullable Entry". + // If there's no dot, then we want nothing here, for "@Nullable Map". + dot = simplified.lastIndexOf('.'); + output.append(simplified.substring(0, dot + 1)); // correct even if dot == -1 + break; + case '»': + dot = simplified.lastIndexOf('.'); + output.append(simplified.substring(dot + 1)); // correct even if dot == -1 + break; + default: + output.append(simplified); + break; + } + } + + private OptionalInt findImportMarker() { + for (int token = 0; token < textLength; token = scanner.tokenEnd(token)) { + if (text.startsWith("`import`", token)) { + return OptionalInt.of(token); + } + } + return OptionalInt.empty(); + } + + private String classNameAt(int token) { + checkArgument(text.charAt(token) == '`'); + int end = scanner.tokenEnd(token) - 1; // points to the closing ` + int t = token + 1; + char c = text.charAt(t); + if (c == '«' || c == '»') { + t++; + } + return text.substring(t, end); + } + } +} diff --git a/value/src/main/java/com/google/auto/value/processor/TypeMirrorSet.java b/value/src/main/java/com/google/auto/value/processor/TypeMirrorSet.java index dab237f817..d090b0edf6 100644 --- a/value/src/main/java/com/google/auto/value/processor/TypeMirrorSet.java +++ b/value/src/main/java/com/google/auto/value/processor/TypeMirrorSet.java @@ -17,11 +17,12 @@ import com.google.auto.common.MoreTypes; import com.google.common.base.Equivalence; +import com.google.common.base.Equivalence.Wrapper; import com.google.common.collect.ImmutableList; import java.util.AbstractSet; import java.util.Collection; -import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.Set; import javax.lang.model.type.TypeMirror; @@ -32,7 +33,7 @@ */ class TypeMirrorSet extends AbstractSet { private final Set> wrappers = - new HashSet>(); + new LinkedHashSet>(); TypeMirrorSet() { } diff --git a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java index 4d909f98a4..37ae68972e 100644 --- a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java +++ b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java @@ -26,15 +26,13 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Name; import javax.lang.model.element.NestingKind; +import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ErrorType; -import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; @@ -64,7 +62,6 @@ private static class Spelling { } } - private final Types typeUtils; private final Map imports; /** @@ -80,18 +77,16 @@ private static class Spelling { * subclass, so a reference to another class with the same name as one of them is ambiguous. * * @throws MissingTypeException if one of the input types contains an error (typically, - * is undefined). This may be something like {@code UndefinedClass}, or something more subtle - * like {@code Set>}. + * is undefined). */ TypeSimplifier(Types typeUtils, String packageName, Set types, TypeMirror base) { - this.typeUtils = typeUtils; Set typesPlusBase = new TypeMirrorSet(types); if (base != null) { typesPlusBase.add(base); } - Set referenced = referencedClassTypes(typeUtils, typesPlusBase); + Set topLevelTypes = topLevelTypes(typeUtils, typesPlusBase); Set defined = nonPrivateDeclaredTypes(typeUtils, base); - this.imports = findImports(typeUtils, packageName, referenced, defined); + this.imports = findImports(typeUtils, packageName, topLevelTypes, defined); } /** @@ -111,56 +106,17 @@ ImmutableSortedSet typesToImport() { return typesToImport.build(); } - /** - * Returns a string that can be used to refer to the given type given the imports defined by - * {@link #typesToImport}. - */ - String simplify(TypeMirror type) { - return type.accept(toStringTypeVisitor, new StringBuilder()).toString(); - } - - /** - * Returns a string that can be used to refer to the given raw type given the imports defined by - * {@link #typesToImport}. The difference between this and {@link #simplify} is that the string - * returned here will not include type parameters. - */ - String simplifyRaw(TypeMirror type) { - return type.accept(toStringRawTypeVisitor, new StringBuilder()).toString(); - } - - /** - * Returns a string that can be used to refer to the given type given the imports defined by - * {@link #typesToImport}. The difference between this and {@link #simplify} is that the string - * returned here includes any type annotations, with appropriate spelling given current imports. - */ - String simplifyWithAnnotations(TypeMirror type) { - return type.accept(toStringAnnotatedTypeVisitor, new StringBuilder()).toString(); - } - - // The formal type parameters of the given type. - // If we have @AutoValue abstract class Foo then this method will - // return for Foo. Likewise it will return the angle-bracket part of: - // Foo - // Foo - // Foo> - // Foo> - // Type variables need special treatment because we only want to include their bounds when they - // are declared, not when they are referenced. We don't want to include the bounds of the second E - // in > or of the second K in >. That's - // why we put the "extends" handling here and not in ToStringTypeVisitor. - String formalTypeParametersString(TypeElement type) { - List typeParameters = type.getTypeParameters(); - if (typeParameters.isEmpty()) { - return ""; + String simplifiedClassName(DeclaredType type) { + TypeElement typeElement = MoreElements.asType(type.asElement()); + TypeElement top = topLevelType(typeElement); + // We always want to write a class name starting from the outermost class. For example, + // if the type is java.util.Map.Entry then we will import java.util.Map and write Map.Entry. + String topString = top.getQualifiedName().toString(); + if (imports.containsKey(topString)) { + String suffix = typeElement.getQualifiedName().toString().substring(topString.length()); + return imports.get(topString).spelling + suffix; } else { - StringBuilder sb = new StringBuilder("<"); - String sep = ""; - for (TypeParameterElement typeParameter : typeParameters) { - sb.append(sep); - sep = ", "; - appendTypeParameterWithBounds(sb, typeParameter); - } - return sb.append(">").toString(); + return typeElement.getQualifiedName().toString(); } } @@ -180,148 +136,6 @@ static String actualTypeParametersString(TypeElement type) { } } - private void appendTypeParameterWithBounds(StringBuilder sb, TypeParameterElement typeParameter) { - sb.append(typeParameter.getSimpleName()); - String sep = " extends "; - for (TypeMirror bound : typeParameter.getBounds()) { - if (!bound.toString().equals("java.lang.Object")) { - sb.append(sep); - sep = " & "; - bound.accept(toStringTypeVisitor, sb); - } - } - } - - private final ToStringTypeVisitor toStringTypeVisitor = new ToStringTypeVisitor(); - private final ToStringTypeVisitor toStringRawTypeVisitor = new ToStringRawTypeVisitor(); - private final ToStringTypeVisitor toStringAnnotatedTypeVisitor = - new ToStringAnnotatedTypeVisitor(); - - /** - * Visitor that produces a string representation of a type for use in generated code. - * The visitor takes into account the imports defined by {@link #typesToImport} and will use - * the short names of those types. - * - *

A simpler alternative would be just to use TypeMirror.toString() and regular expressions to - * pick apart the type references and replace fully-qualified types where possible. That depends - * on unspecified behaviour of TypeMirror.toString(), though, and is vulnerable to formatting - * quirks such as the way it omits the space after the comma in - * {@code java.util.Map}. - */ - private class ToStringTypeVisitor extends SimpleTypeVisitor8 { - @Override protected StringBuilder defaultAction(TypeMirror type, StringBuilder sb) { - return sb.append(type); - } - - @Override public StringBuilder visitArray(ArrayType type, StringBuilder sb) { - return visit(type.getComponentType(), sb).append("[]"); - } - - @Override public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) { - sb.append(declaredTypeName(type)); - appendTypeArguments(type, sb); - return sb; - } - - String declaredTypeName(DeclaredType type) { - TypeElement typeElement = (TypeElement) typeUtils.asElement(type); - TypeElement top = topLevelType(typeElement); - // We always want to write a class name starting from the outermost class. For example, - // if the type is java.util.Map.Entry then we will import java.util.Map and write Map.Entry. - String topString = top.getQualifiedName().toString(); - if (imports.containsKey(topString)) { - String suffix = typeElement.getQualifiedName().toString().substring(topString.length()); - return imports.get(topString).spelling + suffix; - } else { - return typeElement.getQualifiedName().toString(); - } - } - - void appendTypeArguments(DeclaredType type, StringBuilder sb) { - List arguments = type.getTypeArguments(); - if (!arguments.isEmpty()) { - sb.append("<"); - String sep = ""; - for (TypeMirror argument : arguments) { - sb.append(sep); - sep = ", "; - visit(argument, sb); - } - sb.append(">"); - } - } - - @Override public StringBuilder visitWildcard(WildcardType type, StringBuilder sb) { - sb.append("?"); - TypeMirror extendsBound = type.getExtendsBound(); - TypeMirror superBound = type.getSuperBound(); - if (superBound != null) { - sb.append(" super "); - visit(superBound, sb); - } else if (extendsBound != null) { - sb.append(" extends "); - visit(extendsBound, sb); - } - return sb; - } - } - - private class ToStringRawTypeVisitor extends ToStringTypeVisitor { - @Override void appendTypeArguments(DeclaredType type, StringBuilder sb) {} - } - - private class ToStringAnnotatedTypeVisitor extends ToStringTypeVisitor { - private final AnnotationOutput annotationOutput = new AnnotationOutput(TypeSimplifier.this); - - @Override public StringBuilder visitPrimitive(PrimitiveType type, StringBuilder sb) { - appendAnnotations(type.getAnnotationMirrors(), sb); - // We can't just append type.toString(), because that will also have the annotation, but - // without using our imports. - return sb.append(typeUtils.getPrimitiveType(type.getKind())); - } - - @Override public StringBuilder visitTypeVariable(TypeVariable type, StringBuilder sb) { - appendAnnotations(type.getAnnotationMirrors(), sb); - return sb.append(type.asElement().getSimpleName()); - } - - @Override public StringBuilder visitArray(ArrayType type, StringBuilder sb) { - visit(type.getComponentType(), sb); - List annotationMirrors = type.getAnnotationMirrors(); - if (!annotationMirrors.isEmpty()) { - sb.append(" "); - appendAnnotations(annotationMirrors, sb); - } - return sb.append("[]"); - } - - @Override public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) { - String name = declaredTypeName(type); - List annotationMirrors = type.getAnnotationMirrors(); - if (annotationMirrors.isEmpty()) { - sb.append(name); - } else { - // Find the index of the last part of the name, "Map" in "Map" or "Entry" in "Map.Entry". - // lastIndexOf might return -1 in which case this is still correct. - // The goal here is to produce "@Nullable Foo" for simple type names and - // "com.example.@Nullable Foo" or "Bar.@Nullable Foo" for qualified ones. - int lastPart = name.lastIndexOf('.') + 1; - sb.append(name.substring(0, lastPart)); - appendAnnotations(annotationMirrors, sb); - sb.append(name.substring(lastPart)); - } - appendTypeArguments(type, sb); - return sb; - } - - private void appendAnnotations( - List annotationMirrors, StringBuilder sb) { - for (AnnotationMirror annotationMirror : annotationMirrors) { - sb.append(annotationOutput.sourceFormForAnnotation(annotationMirror)).append(" "); - } - } - } - /** * Returns the name of the given type, including any enclosing types but not the package. */ @@ -379,7 +193,7 @@ static String simpleNameOf(String s) { */ private static Map findImports( Types typeUtils, String packageName, Set referenced, Set defined) { - Map imports = new HashMap(); + Map imports = new HashMap<>(); Set typesInScope = new TypeMirrorSet(); typesInScope.addAll(referenced); typesInScope.addAll(defined); @@ -407,81 +221,20 @@ private static Map findImports( } /** - * Finds all declared types (classes and interfaces) that are referenced in the given - * {@code Set}. This includes classes and interfaces that appear directly in the set, - * but also ones that appear in type parameters and the like. For example, if the set contains - * {@code java.util.List} then both {@code java.util.List} and - * {@code java.lang.Number} will be in the resulting set. + * Finds the top-level types for all the declared types (classes and interfaces) in the given + * {@code Set}. * *

The returned set contains only top-level types. If we reference {@code java.util.Map.Entry} * then the returned set will contain {@code java.util.Map}. This is because we want to write * {@code Map.Entry} everywhere rather than {@code Entry}. */ - private static Set referencedClassTypes(Types typeUtil, Set types) { - Set allReferenced = new TypeMirrorSet(); - ReferencedClassTypeVisitor referencedClassVisitor = - new ReferencedClassTypeVisitor(typeUtil, allReferenced); - for (TypeMirror type : types) { - referencedClassVisitor.visit(type); - } - return allReferenced.stream() + private static Set topLevelTypes(Types typeUtil, Set types) { + return types.stream() .map(typeMirror -> MoreElements.asType(typeUtil.asElement(typeMirror))) .map(typeElement -> topLevelType(typeElement).asType()) .collect(toCollection(TypeMirrorSet::new)); } - private static class ReferencedClassTypeVisitor extends SimpleTypeVisitor8 { - private final Types typeUtils; - private final Set referencedTypes; - private final Set seenTypes; - - ReferencedClassTypeVisitor(Types typeUtils, Set referenced) { - this.typeUtils = typeUtils; - this.referencedTypes = referenced; - this.seenTypes = new TypeMirrorSet(); - } - - @Override public Void visitArray(ArrayType t, Void p) { - return visit(t.getComponentType(), p); - } - - @Override public Void visitDeclared(DeclaredType t, Void p) { - if (seenTypes.add(t)) { - referencedTypes.add(typeUtils.erasure(t)); - for (TypeMirror param : t.getTypeArguments()) { - visit(param, p); - } - } - return null; - } - - @Override public Void visitTypeVariable(TypeVariable t, Void p) { - // Instead of visiting t.getUpperBound(), we explicitly visit the supertypes of t. - // The reason is that for a variable like , t.getUpperBound() will be - // the intersection type Foo & Bar, with no really simple way to extract Foo and Bar. But - // directSupertypes(t) will be exactly [Foo, Bar]. For plain , directSupertypes(t) will - // be java.lang.Object, and it is harmless for us to record a reference to that since we won't - // try to import it or use it in the output string for . - for (TypeMirror upper : typeUtils.directSupertypes(t)) { - visit(upper, p); - } - return visit(t.getLowerBound(), p); - } - - @Override public Void visitWildcard(WildcardType t, Void p) { - for (TypeMirror bound : new TypeMirror[] {t.getSuperBound(), t.getExtendsBound()}) { - if (bound != null) { - visit(bound, p); - } - } - return null; - } - - @Override public Void visitError(ErrorType t, Void p) { - throw new MissingTypeException(); - } - } - /** * Finds all types that are declared with non private visibility by the given {@code TypeMirror}, * any class in its superclass chain, or any interface it implements. @@ -507,8 +260,8 @@ private static Set nonPrivateDeclaredTypes(Types typeUtils, TypeMirr } private static Set ambiguousNames(Types typeUtils, Set types) { - Set ambiguous = new HashSet(); - Map simpleNamesToQualifiedNames = new HashMap(); + Set ambiguous = new HashSet<>(); + Map simpleNamesToQualifiedNames = new HashMap<>(); for (TypeMirror type : types) { if (type.getKind() == TypeKind.ERROR) { throw new MissingTypeException(); @@ -519,7 +272,7 @@ private static Set ambiguousNames(Types typeUtils, Set types * the same (unannotated) type may appear multiple times in the Set. * TODO(emcmanus): investigate further, because this might cause problems elsewhere. */ - Name qualifiedName = ((TypeElement)typeUtils.asElement(type)).getQualifiedName(); + Name qualifiedName = ((QualifiedNameable) typeUtils.asElement(type)).getQualifiedName(); Name previous = simpleNamesToQualifiedNames.put(simpleName, qualifiedName); if (previous != null && !previous.equals(qualifiedName)) { ambiguous.add(simpleName); diff --git a/value/src/main/java/com/google/auto/value/processor/autoannotation.vm b/value/src/main/java/com/google/auto/value/processor/autoannotation.vm index ccfc4a0e4d..a4b9aeb1af 100644 --- a/value/src/main/java/com/google/auto/value/processor/autoannotation.vm +++ b/value/src/main/java/com/google/auto/value/processor/autoannotation.vm @@ -7,11 +7,13 @@ ## Velocity deletes a newline after a directive (#if, #foreach, #end etc) so ## is not needed there. ## That does mean that we sometimes need an extra blank line after such a directive. ## -## A post-processing step will remove unwanted spaces and blank lines, but will not join two lines. +## Post-processing will remove unwanted spaces and blank lines, but will not join two lines. +## It will also replace classes spelled as (e.g.) `java.util.Arrays`, with the backquotes, to +## use just Arrays if that class can be imported unambiguously, or java.util.Arrays if not. #macro (cloneArray $a) #if ($gwtCompatible) - ${arrays}.copyOf($a, ${a}.length) + `java.util.Arrays`.copyOf($a, ${a}.length) #else ${a}.clone() #end @@ -21,9 +23,8 @@ package $pkg; #end -#foreach ($i in $imports) -import $i; -#end +## The following line will be replaced by the required imports during post-processing. +`import` #if (!$generated.empty) @${generated}("com.google.auto.value.processor.AutoAnnotationProcessor") @@ -122,25 +123,25 @@ final class $className implements $annotationName { ## toString #macro (appendMemberString $m) - #if ($m.type == "String" || $m.type == "java.lang.String") + #if ($m.typeMirror.toString() == "java.lang.String") #set ($appendQuotedStringMethod = "true") appendQuoted(sb, $m) ## - #elseif ($m.type == "char") + #elseif ($m.typeMirror.toString() == "char") #set ($appendQuotedCharMethod = "true") appendQuoted(sb, $m) ## - #elseif ($m.type == "String[]" || $m.type == "java.lang.String[]") + #elseif ($m.typeMirror.toString() == "java.lang.String[]") #set ($appendQuotedStringArrayMethod = "true") appendQuoted(sb, $m) ## - #elseif ($m.type == "char[]") + #elseif ($m.typeMirror.toString() == "char[]") #set ($appendQuotedCharArrayMethod = "true") appendQuoted(sb, $m) ## #elseif ($m.kind == "ARRAY") - sb.append(${arrays}.toString($m)) ## + sb.append(`java.util.Arrays`.toString($m)) ## #else sb.append($m) ## @@ -181,12 +182,12 @@ final class $className implements $annotationName { $m == that.${m}() ## #elseif ($m.kind == "ARRAY") #if ($params.containsKey($m.toString())) - ${arrays}.equals($m, + `java.util.Arrays`.equals($m, (that instanceof $className) ? (($className) that).$m : that.${m}()) ## #else ## default value, so if |that| is also a $className then it has the same constant value - that instanceof $className || ${arrays}.equals($m, that.${m}()) + that instanceof $className || `java.util.Arrays`.equals($m, that.${m}()) #end #else ${m}.equals(that.${m}()) ## @@ -236,7 +237,7 @@ final class $className implements $annotationName { #elseif ($m.kind.primitive) $m ## #elseif ($m.kind == "ARRAY") - ${arrays}.hashCode($m) ## + `java.util.Arrays`.hashCode($m) ## #else ${m}.hashCode() ## #end @@ -282,7 +283,7 @@ final class $className implements $annotationName { #foreach ($w in $wrapperTypesUsedInCollections) #set ($prim = $w.getField("TYPE").get("")) - private static ${prim}[] ${prim}ArrayFromCollection(Collection<${w.simpleName}> c) { + private static ${prim}[] ${prim}ArrayFromCollection(`java.util.Collection`<${w.simpleName}> c) { ${prim}[] a = new ${prim}[c.size()]; int i = 0; for (${prim} x : c) { diff --git a/value/src/main/java/com/google/auto/value/processor/autovalue.vm b/value/src/main/java/com/google/auto/value/processor/autovalue.vm index 75466d52a9..20091bd8d5 100644 --- a/value/src/main/java/com/google/auto/value/processor/autovalue.vm +++ b/value/src/main/java/com/google/auto/value/processor/autovalue.vm @@ -7,15 +7,16 @@ ## Velocity deletes a newline after a directive (#if, #foreach, #end etc) so ## is not needed there. ## That does mean that we sometimes need an extra blank line after such a directive. ## -## A post-processing step will remove unwanted spaces and blank lines, but will not join two lines. +## Post-processing will remove unwanted spaces and blank lines, but will not join two lines. +## It will also replace classes spelled as (e.g.) `java.util.Arrays`, with the backquotes, to +## use just Arrays if that class can be imported unambiguously, or java.util.Arrays if not. #if (!$pkg.empty) package $pkg; #end -#foreach ($i in $imports) -import $i; -#end +## The following line will be replaced by the required imports during post-processing. +`import` ${gwtCompatibleAnnotation} #foreach ($a in $annotations) @@ -94,7 +95,7 @@ $a #foreach ($p in $props) #if ($identifiers) + "$p.name=" ## - #end+ #if ($p.kind == "ARRAY") ${arrays}.toString($p) #else $p #end + #end+ #if ($p.kind == "ARRAY") `java.util.Arrays`.toString($p) #else $p #end #if ($foreach.hasNext) + ", " #end #end @@ -113,7 +114,7 @@ $a #elseif ($p.kind.primitive) this.$p == that.${p.getter}() ## #elseif ($p.kind == "ARRAY") - ${arrays}.equals(this.$p, ## + `java.util.Arrays`.equals(this.$p, ## (that instanceof $subclass) ? (($subclass) that).$p : that.${p.getter}()) ## #else #if ($p.nullable) (this.$p == null) ? (that.${p.getter}() == null) : #end ## @@ -165,7 +166,7 @@ $a #elseif ($p.kind == "BOOLEAN") this.$p ? 1231 : 1237 ## #elseif ($p.kind == "ARRAY") - ${arrays}.hashCode(this.$p) ## + `java.util.Arrays`.hashCode(this.$p) ## #else #if ($p.nullable) ($p == null) ? 0 : #end this.${p}.hashCode() ## #end diff --git a/value/src/main/java/com/google/auto/value/processor/gwtserializer.vm b/value/src/main/java/com/google/auto/value/processor/gwtserializer.vm index e24ddbbb21..15b526ef95 100644 --- a/value/src/main/java/com/google/auto/value/processor/gwtserializer.vm +++ b/value/src/main/java/com/google/auto/value/processor/gwtserializer.vm @@ -14,23 +14,20 @@ package $pkg; #end -#foreach ($i in $imports) -import $i; -#end -import com.google.gwt.user.client.rpc.CustomFieldSerializer; -import com.google.gwt.user.client.rpc.SerializationException; -import com.google.gwt.user.client.rpc.SerializationStreamReader; -import com.google.gwt.user.client.rpc.SerializationStreamWriter; +## The following line will be replaced by the required imports during post-processing. +`import` #if (!$generated.empty) @${generated}("com.google.auto.value.processor.AutoValueProcessor") #else // Generated by com.google.auto.value.processor.AutoValueProcessor #end -public final class $serializerClass$formalTypes extends CustomFieldSerializer<$subclass$actualTypes> { +public final class $serializerClass$formalTypes + extends `com.google.gwt.user.client.rpc.CustomFieldSerializer`<$subclass$actualTypes> { public static $formalTypes $subclass$actualTypes instantiate( - SerializationStreamReader streamReader) throws SerializationException { + `com.google.gwt.user.client.rpc.SerializationStreamReader` streamReader) + throws `com.google.gwt.user.client.rpc.SerializationException` { #foreach ($p in $props) #if ($p.castingUnchecked) @SuppressWarnings("unchecked") @@ -50,20 +47,20 @@ public final class $serializerClass$formalTypes extends CustomFieldSerializer<$s } public static $formalTypes void serialize( - SerializationStreamWriter streamWriter, - $subclass$actualTypes instance) throws SerializationException { + `com.google.gwt.user.client.rpc.SerializationStreamWriter` streamWriter, + $subclass$actualTypes instance) throws `com.google.gwt.user.client.rpc.SerializationException` { #foreach ($p in $props) streamWriter.write${p.gwtType}(instance.${p.getter}()); #end } public static $formalTypes void deserialize( - @SuppressWarnings("unused") SerializationStreamReader streamReader, + @SuppressWarnings("unused") `com.google.gwt.user.client.rpc.SerializationStreamReader` streamReader, @SuppressWarnings("unused") $subclass$actualTypes instance) { // instantiate already did all the work. } - // This dummy field is a hash of the fields in $subclass that will change if they do, including + // This dummy field is a hash of the fields in ${subclass}. It will change if they do, including // if their order changes. This is because GWT identity for a class that has a serializer is // based on the fields of the serializer rather than the class itself. @SuppressWarnings("unused") @@ -71,7 +68,7 @@ public final class $serializerClass$formalTypes extends CustomFieldSerializer<$s @Override public void deserializeInstance( - SerializationStreamReader streamReader, + `com.google.gwt.user.client.rpc.SerializationStreamReader` streamReader, $subclass$actualTypes instance) { deserialize(streamReader, instance); } @@ -83,14 +80,16 @@ public final class $serializerClass$formalTypes extends CustomFieldSerializer<$s @Override public $subclass$actualTypes instantiateInstance( - SerializationStreamReader streamReader) throws SerializationException { + `com.google.gwt.user.client.rpc.SerializationStreamReader` streamReader) + throws `com.google.gwt.user.client.rpc.SerializationException` { return instantiate(streamReader); } @Override public void serializeInstance( - SerializationStreamWriter streamWriter, - $subclass$actualTypes instance) throws SerializationException { + `com.google.gwt.user.client.rpc.SerializationStreamWriter` streamWriter, + $subclass$actualTypes instance) + throws `com.google.gwt.user.client.rpc.SerializationException` { serialize(streamWriter, instance); } } diff --git a/value/src/test/java/com/google/auto/value/processor/CompilationTest.java b/value/src/test/java/com/google/auto/value/processor/CompilationTest.java index bc516fe000..aa09fcb62b 100644 --- a/value/src/test/java/com/google/auto/value/processor/CompilationTest.java +++ b/value/src/test/java/com/google/auto/value/processor/CompilationTest.java @@ -1940,9 +1940,9 @@ public void autoValueBuilderPropertyBuilderCantReconstruct() { .compile(javaFileObject); assertThat(compilation) .hadErrorContaining( - "Property builder method returns Baz.StringFactory but there is no way to make " - + "that type from String: String does not have a non-static " - + "toBuilder() method that returns Baz.StringFactory") + "Property builder method returns foo.bar.Baz.StringFactory but there is no way to make " + + "that type from java.lang.String: java.lang.String does not have a non-static " + + "toBuilder() method that returns foo.bar.Baz.StringFactory") .inFile(javaFileObject) .onLine(19); } @@ -1979,9 +1979,9 @@ public void autoValueBuilderPropertyBuilderCantSet() { .compile(javaFileObject); assertThat(compilation) .hadErrorContaining( - "Property builder method returns Baz.StringFactory but there is no way to make " - + "that type from String: String does not have a non-static " - + "toBuilder() method that returns Baz.StringFactory") + "Property builder method returns foo.bar.Baz.StringFactory but there is no way to make " + + "that type from java.lang.String: java.lang.String does not have a non-static " + + "toBuilder() method that returns foo.bar.Baz.StringFactory") .inFile(javaFileObject) .onLine(19); } @@ -2024,9 +2024,9 @@ public void autoValueBuilderPropertyBuilderWrongTypeToBuilder() { .compile(javaFileObject); assertThat(compilation) .hadErrorContaining( - "Property builder method returns Baz.BuhBuilder but there is no way to make " - + "that type from Baz.Buh: Baz.Buh does not have a non-static " - + "toBuilder() method that returns Baz.BuhBuilder") + "Property builder method returns foo.bar.Baz.BuhBuilder but there is no way to make " + + "that type from foo.bar.Baz.Buh: foo.bar.Baz.Buh does not have a non-static " + + "toBuilder() method that returns foo.bar.Baz.BuhBuilder") .inFile(javaFileObject) .onLine(25); } diff --git a/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java b/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java index 3fdc317537..5f277a47bc 100644 --- a/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java +++ b/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java @@ -39,6 +39,8 @@ public class PropertyAnnotationsTest { PropertyAnnotationsTest.class.getName(); private static final String IMPORT_PROPERTY_ANNOTATIONS_TEST = "import " + PROPERTY_ANNOTATIONS_TEST + ";"; + private static final String IMPORT_JAVAX_ANNOTATION_NULLABLE = + "import javax.annotation.Nullable;"; private static final String TEST_ANNOTATION = "@PropertyAnnotationsTest.TestAnnotation"; private static final String TEST_ARRAY_ANNOTATION = @@ -55,7 +57,7 @@ public String toString() { } } - public static @interface TestAnnotation { + public @interface TestAnnotation { byte testByte() default 1; short testShort() default 2; int testInt() default 3; @@ -70,12 +72,12 @@ public String toString() { OtherAnnotation testAnnotation() default @OtherAnnotation(foo = 23, bar = "baz"); } - public static @interface OtherAnnotation { + public @interface OtherAnnotation { int foo() default 123; String bar() default "bar"; } - public static @interface TestArrayAnnotation { + public @interface TestArrayAnnotation { byte[] testBytes() default {1, 2}; short[] testShorts() default {3, 4}; int[] testInts() default {5, 6}; @@ -258,7 +260,7 @@ public void testClassAnnotation() { ImmutableList.of(TEST_ANNOTATION + "(testClass = String.class)"), ImmutableList.of(TEST_ANNOTATION - + "(testClass = java.lang.String.class)")); + + "(testClass = String.class)")); } @Test @@ -334,17 +336,17 @@ public void testClassArrayAnnotation() { ImmutableList.of(IMPORT_PROPERTY_ANNOTATIONS_TEST), ImmutableList.of(TEST_ARRAY_ANNOTATION + "(testClasses = {String.class, Long.class})"), ImmutableList.of(TEST_ARRAY_ANNOTATION - + "(testClasses = {java.lang.String.class, java.lang.Long.class})")); + + "(testClasses = {String.class, Long.class})")); } @Test public void testImportedClassArrayAnnotation() { assertGeneratedMatches( - ImmutableList.of(IMPORT_PROPERTY_ANNOTATIONS_TEST), + ImmutableList.of(IMPORT_PROPERTY_ANNOTATIONS_TEST, IMPORT_JAVAX_ANNOTATION_NULLABLE), ImmutableList.of(TEST_ARRAY_ANNOTATION + "(testClasses = {javax.annotation.Nullable.class, Long.class})"), ImmutableList.of(TEST_ARRAY_ANNOTATION - + "(testClasses = {javax.annotation.Nullable.class, java.lang.Long.class})")); + + "(testClasses = {Nullable.class, Long.class})")); } @Test @@ -391,14 +393,13 @@ public void testCopyingMethodAnnotations() { "@PropertyAnnotationsTest.TestAnnotation", "@PropertyAnnotationsTest.InheritedAnnotation"); - ImmutableList expectedImports = ImmutableList.of("import javax.annotation.Resource;"); ImmutableList expectedAnnotations = ImmutableList.of( "@Resource", - "@" + PROPERTY_ANNOTATIONS_TEST + ".InheritedAnnotation"); + "@PropertyAnnotationsTest.InheritedAnnotation"); JavaFileObject javaFileObject = sourceCode(sourceImports, sourceAnnotations); - JavaFileObject expectedOutput = expectedCode(expectedImports, expectedAnnotations); + JavaFileObject expectedOutput = expectedCode(sourceImports, expectedAnnotations); assertAbout(javaSource()) .that(javaFileObject) diff --git a/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java b/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java index b7203ffa80..1d3fa44a4b 100644 --- a/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java +++ b/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java @@ -35,7 +35,6 @@ import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; -import javax.lang.model.util.Types; import javax.tools.JavaFileObject; import org.junit.Rule; import org.junit.Test; @@ -43,7 +42,7 @@ import org.junit.runners.JUnit4; /** - * This test verifies the method {@link TypeSimplifier#simplifyWithAnnotations(TypeMirror). + * This test verifies the method {@link TypeEncoder#encodeWithAnnotations(TypeMirror). * It takes a list of "type spellings", like {@code @Nullable String}, and compiles a class * with one field for each spelling. So there might be a field {@code @Nullable String x2;}. * Then it examines each compiled field to extract its {@code TypeMirror}, and uses the @@ -64,13 +63,13 @@ public class SimplifyWithAnnotationsTest { /** * The types that we will compile and then recreate. They are referenced in a context where - * {@code List} has been imported but not {@code Set}, which allows us to test the placement of - * annotations in unqualified types like {@code List} and qualified types like - * {@code java.util.Set}. + * {@code Set} is unambiguous but not {@code List}, which allows us to test the placement of + * annotations in unqualified types like {@code Set} and qualified types like + * {@code java.util.List}. */ private static final ImmutableList TYPE_SPELLINGS = ImmutableList.of( "Object", - "List", + "Set", "String", "Nullable", "@Nullable String", @@ -78,21 +77,22 @@ public class SimplifyWithAnnotationsTest { "@Nullable String[]", "String @Nullable []", "String @Nullable [] @Nullable []", - "List", - "List<@Nullable String>", - "@Nullable List", + "java.awt.List", + "java.util.List", + "Set<@Nullable String>", + "@Nullable Set", "int", "@Nullable int", // whatever that might mean "@Nullable int[]", "int @Nullable []", "T", "@Nullable T", - "List<@Nullable T>", - "List", - "List", - "List", - "java.util.@Nullable Set<@Nullable T>", - "java.util.@Nullable Set>"); + "Set<@Nullable T>", + "Set", + "Set", + "Set", + "java.util.@Nullable List<@Nullable T>", + "java.util.@Nullable List>"); private static final JavaFileObject NULLABLE_FILE_OBJECT = JavaFileObjects.forSourceLines( "pkg.Nullable", @@ -116,7 +116,7 @@ private static ImmutableList buildTestClass() { builder.add( "package pkg;", "", - "import java.util.List;", + "import java.util.Set;", "", "public abstract class TestClass {", " abstract @Nullable T witness();"); @@ -139,7 +139,7 @@ public void testSimplifyWithAnnotations() { } @SupportedAnnotationTypes("*") - private class TestProcessor extends AbstractProcessor { + private static class TestProcessor extends AbstractProcessor { @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); @@ -165,20 +165,20 @@ void testTypeSpellings(TypeElement testClass) { } ImmutableMap typeSpellingToType = typesFromTestClass(testClass); assertThat(typeSpellingToType).isNotEmpty(); - TypeMirror nullable = typeSpellingToType.get("Nullable"); - TypeMirror object = typeSpellingToType.get("Object"); - TypeMirror string = typeSpellingToType.get("String"); - TypeMirror list = typeSpellingToType.get("List"); - Types typeUtils = processingEnv.getTypeUtils(); - TypeMirrorSet types = TypeMirrorSet.of(nullable, string, list); - TypeSimplifier typeSimplifier = new TypeSimplifier(typeUtils, "pkg", types, object); + StringBuilder text = new StringBuilder(); + StringBuilder expected = new StringBuilder(); + // Build up a fake source text with the encodings for the types in it, and decode it to + // ensure the type spellings are what we expect. typeSpellingToType.forEach( (typeSpelling, type) -> { - expect.that(typeSimplifier.simplifyWithAnnotations(type)).isEqualTo(typeSpelling); + text.append("{").append(TypeEncoder.encodeWithAnnotations(type)).append("}"); + expected.append("{").append(typeSpelling).append("}"); }); + String decoded = TypeEncoder.decode(text.toString(), processingEnv, "pkg", null); + assertThat(decoded).isEqualTo(expected.toString()); } - private ImmutableMap typesFromTestClass(TypeElement type) { + private static ImmutableMap typesFromTestClass(TypeElement type) { // Reads the types of the fields from the compiled TestClass and uses them to produce // a map from type spellings to types. This method depends on type.getEnclosedElements() // returning the fields in source order, which it is specified to do. diff --git a/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java b/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java new file mode 100644 index 0000000000..1f23b21e9d --- /dev/null +++ b/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2017 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.auto.value.processor; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.testing.compile.Compiler.javac; +import static java.util.stream.Collectors.joining; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.CompilationRule; +import com.google.testing.compile.JavaFileObjects; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ErrorType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for {@link TypeEncoder}. + * + * @author emcmanus@google.com (Éamonn McManus) + */ +@RunWith(JUnit4.class) +public class TypeEncoderTest { + @Rule public final CompilationRule compilationRule = new CompilationRule(); + + private Types typeUtils; + private Elements elementUtils; + + @Before + public void setUp() { + typeUtils = compilationRule.getTypes(); + elementUtils = compilationRule.getElements(); + } + + /** + * Assert that the fake program returned by fakeProgramForTypes has the given list of imports and + * the given list of spellings. Here, "spellings" means the way each type is referenced in the + * decoded program, for example {@code Timer} if {@code java.util.Timer} can be imported, or + * {@code java.util.Timer} if not. + * + *

We construct a fake program that references each of the given types in turn. + * TypeEncoder.decode doesn't have any real notion of Java syntax, so our program just consists of + * START and END markers around the {@code `import`} tag, followed by each type in braces, as + * encoded by TypeEncoder.encode. Once decoded, the program should consist of the appropriate + * imports (inside START...END) and each type in braces, spelled appropriately. + * + * @param fakePackage the package that TypeEncoder should consider the fake program to be in. + * Classes in the same package don't usually need to be imported. + */ + private void assertTypeImportsAndSpellings( + Set types, String fakePackage, List imports, List spellings) { + String fakeProgram = "START\n`import`\nEND\n" + + types.stream().map(TypeEncoder::encode).collect(joining("}\n{", "{", "}")); + String decoded = TypeEncoder.decode( + fakeProgram, elementUtils, typeUtils, fakePackage, baseWithoutContainedTypes()); + String expected = "START\n" + + imports.stream().map(s -> "import " + s + ";\n").collect(joining()) + + "\nEND\n" + + spellings.stream().collect(joining("}\n{", "{", "}")); + assertThat(decoded).isEqualTo(expected); + } + + private static class MultipleBounds & Comparable, V> {} + + @Test + public void testImportsForNoTypes() { + assertTypeImportsAndSpellings( + typeMirrorSet(), + "foo.bar", + ImmutableList.of(), + ImmutableList.of()); + } + + @Test + public void testImportsForImplicitlyImportedTypes() { + Set types = typeMirrorSet( + typeMirrorOf(java.lang.String.class), + typeMirrorOf(javax.management.MBeanServer.class), // Same package, so no import. + typeUtils.getPrimitiveType(TypeKind.INT), + typeUtils.getPrimitiveType(TypeKind.BOOLEAN) + ); + assertTypeImportsAndSpellings( + types, + "javax.management", + ImmutableList.of(), + ImmutableList.of("String", "MBeanServer", "int", "boolean")); + } + + @Test + public void testImportsForPlainTypes() { + Set types = typeMirrorSet( + typeUtils.getPrimitiveType(TypeKind.INT), + typeMirrorOf(java.lang.String.class), + typeMirrorOf(java.net.Proxy.class), + typeMirrorOf(java.net.Proxy.Type.class), + typeMirrorOf(java.util.regex.Pattern.class), + typeMirrorOf(javax.management.MBeanServer.class)); + assertTypeImportsAndSpellings( + types, + "foo.bar", + ImmutableList.of( + "java.net.Proxy", "java.util.regex.Pattern", "javax.management.MBeanServer"), + ImmutableList.of( + "int", "String", "Proxy", "Proxy.Type", "Pattern", "MBeanServer")); + } + + @Test + public void testImportsForComplicatedTypes() { + TypeElement list = typeElementOf(java.util.List.class); + TypeElement map = typeElementOf(java.util.Map.class); + Set types = typeMirrorSet( + typeUtils.getPrimitiveType(TypeKind.INT), + typeMirrorOf(java.util.regex.Pattern.class), + typeUtils.getDeclaredType(list, // List + typeMirrorOf(java.util.Timer.class)), + typeUtils.getDeclaredType(map, // Map + typeUtils.getWildcardType(typeMirrorOf(java.util.Timer.class), null), + typeUtils.getWildcardType(null, typeMirrorOf(java.math.BigInteger.class)))); + // Timer is referenced twice but should obviously only be imported once. + assertTypeImportsAndSpellings( + types, + "foo.bar", + ImmutableList.of( + "java.math.BigInteger", + "java.util.List", + "java.util.Map", + "java.util.Timer", + "java.util.regex.Pattern"), + ImmutableList.of( + "int", + "Pattern", + "List", + "Map")); + } + + @Test + public void testImportsForArrayTypes() { + TypeElement list = typeElementOf(java.util.List.class); + TypeElement set = typeElementOf(java.util.Set.class); + Set types = typeMirrorSet( + typeUtils.getArrayType(typeUtils.getPrimitiveType(TypeKind.INT)), + typeUtils.getArrayType(typeMirrorOf(java.util.regex.Pattern.class)), + typeUtils.getArrayType( // Set[] + typeUtils.getDeclaredType(set, + typeUtils.getArrayType(typeMirrorOf(java.util.regex.Matcher.class)))), + typeUtils.getDeclaredType(list, // List + typeUtils.getArrayType(typeMirrorOf(java.util.Timer.class)))); + // Timer is referenced twice but should obviously only be imported once. + assertTypeImportsAndSpellings( + types, + "foo.bar", + ImmutableList.of( + "java.util.List", + "java.util.Set", + "java.util.Timer", + "java.util.regex.Matcher", + "java.util.regex.Pattern"), + ImmutableList.of( + "int[]", + "Pattern[]", + "Set[]", + "List")); + } + + @Test + public void testImportNestedType() { + Set types = typeMirrorSet(typeMirrorOf(java.net.Proxy.Type.class)); + assertTypeImportsAndSpellings( + types, + "foo.bar", + ImmutableList.of("java.net.Proxy"), + ImmutableList.of("Proxy.Type")); + } + + @Test + public void testImportsForAmbiguousNames() { + TypeMirror wildcard = typeUtils.getWildcardType(null, null); + Set types = typeMirrorSet( + typeUtils.getPrimitiveType(TypeKind.INT), + typeMirrorOf(java.awt.List.class), + typeMirrorOf(java.lang.String.class), + typeUtils.getDeclaredType( // List + typeElementOf(java.util.List.class), wildcard), + typeUtils.getDeclaredType( // Map + typeElementOf(java.util.Map.class), wildcard, wildcard)); + assertTypeImportsAndSpellings( + types, + "foo.bar", + ImmutableList.of( + "java.util.Map" + ), + ImmutableList.of( + "int", + "java.awt.List", + "String", + "java.util.List", + "Map")); + } + + @Test + public void testSimplifyJavaLangString() { + Set types = typeMirrorSet( + typeMirrorOf(java.lang.String.class)); + assertTypeImportsAndSpellings( + types, + "foo.bar", + ImmutableList.of(), + ImmutableList.of("String")); + } + + @Test + public void testSimplifyJavaLangThreadState() { + Set types = typeMirrorSet( + typeMirrorOf(java.lang.Thread.State.class)); + assertTypeImportsAndSpellings( + types, + "foo.bar", + ImmutableList.of(), + ImmutableList.of("Thread.State")); + } + + @Test + public void testSimplifyJavaLangNamesake() { + TypeMirror javaLangType = typeMirrorOf(java.lang.RuntimePermission.class); + TypeMirror notJavaLangType = typeMirrorOf( + com.google.auto.value.processor.testclasses.RuntimePermission.class); + Set types = typeMirrorSet( + javaLangType, + notJavaLangType); + assertTypeImportsAndSpellings( + types, + "foo.bar", + ImmutableList.of(), + ImmutableList.of( + javaLangType.toString(), + notJavaLangType.toString())); + } + + @Test + public void testSimplifyComplicatedTypes() { + // This test constructs a set of types and feeds them to TypeEncoder. Then it verifies that + // the resultant rewrites of those types are what we would expect. + TypeElement list = typeElementOf(java.util.List.class); + TypeElement map = typeElementOf(java.util.Map.class); + TypeMirror string = typeMirrorOf(java.lang.String.class); + TypeMirror integer = typeMirrorOf(java.lang.Integer.class); + TypeMirror pattern = typeMirrorOf(java.util.regex.Pattern.class); + TypeMirror timer = typeMirrorOf(java.util.Timer.class); + TypeMirror bigInteger = typeMirrorOf(java.math.BigInteger.class); + ImmutableMap typeMap = ImmutableMap.builder() + .put(typeUtils.getPrimitiveType(TypeKind.INT), "int") + .put(typeUtils.getArrayType(typeUtils.getPrimitiveType(TypeKind.BYTE)), "byte[]") + .put(pattern, "Pattern") + .put(typeUtils.getArrayType(pattern), "Pattern[]") + .put(typeUtils.getArrayType(typeUtils.getArrayType(pattern)), "Pattern[][]") + .put(typeUtils.getDeclaredType(list, typeUtils.getWildcardType(null, null)), "List") + .put(typeUtils.getDeclaredType(list, timer), "List") + .put(typeUtils.getDeclaredType(map, string, integer), "Map") + .put(typeUtils.getDeclaredType(map, + typeUtils.getWildcardType(timer, null), typeUtils.getWildcardType(null, bigInteger)), + "Map") + .build(); + assertTypeImportsAndSpellings( + typeMap.keySet(), + "foo.bar", + ImmutableList.of( + "java.math.BigInteger", + "java.util.List", + "java.util.Map", + "java.util.Timer", + "java.util.regex.Pattern"), + ImmutableList.copyOf(typeMap.values())); + } + + @Test + public void testSimplifyMultipleBounds() { + TypeElement multipleBoundsElement = typeElementOf(MultipleBounds.class); + TypeMirror multipleBoundsMirror = multipleBoundsElement.asType(); + String text = "`import`\n"; + text += "{" + TypeEncoder.encode(multipleBoundsMirror) + "}"; + text += "{" + TypeEncoder.formalTypeParametersString(multipleBoundsElement) + "}"; + String myPackage = getClass().getPackage().getName(); + String decoded = + TypeEncoder.decode(text, elementUtils, typeUtils, myPackage, baseWithoutContainedTypes()); + String expected = "import java.util.List;\n\n" + + "{TypeEncoderTest.MultipleBounds}" + + "{ & Comparable, V>}"; + assertThat(decoded).isEqualTo(expected); + } + + private static Set typeMirrorSet(TypeMirror... typeMirrors) { + Set set = new TypeMirrorSet(); + for (TypeMirror typeMirror : typeMirrors) { + assertThat(set.add(typeMirror)).isTrue(); + } + return set; + } + + private TypeElement typeElementOf(Class c) { + return elementUtils.getTypeElement(c.getCanonicalName()); + } + + private TypeMirror typeMirrorOf(Class c) { + return typeElementOf(c).asType(); + } + + /** + * Returns a "base type" for TypeSimplifier that does not contain any nested types. The point + * being that every {@code TypeSimplifier} has a base type that the class being generated is + * going to extend, and if that class has nested types they will be in scope, and therefore a + * possible source of ambiguity. + */ + private TypeMirror baseWithoutContainedTypes() { + return typeMirrorOf(Object.class); + } + + // This test checks that we correctly throw MissingTypeException if there is an ErrorType anywhere + // inside a type we are asked to simplify. There's no way to get an ErrorType from typeUtils or + // elementUtils, so we need to fire up the compiler with an erroneous source file and use an + // annotation processor to capture the resulting ErrorType. Then we can run tests within that + // annotation processor, and propagate any failures out of this test. + @Test + public void testErrorTypes() { + JavaFileObject source = JavaFileObjects.forSourceString( + "ExtendsUndefinedType", "class ExtendsUndefinedType extends UndefinedParent {}"); + Compilation compilation = javac() + .withProcessors(new ErrorTestProcessor()) + .compile(source); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorContaining("UndefinedParent"); + assertThat(compilation).hadErrorCount(1); + } + + @SupportedAnnotationTypes("*") + private static class ErrorTestProcessor extends AbstractProcessor { + Types typeUtils; + Elements elementUtils; + + @Override + public boolean process( + Set annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver()) { + typeUtils = processingEnv.getTypeUtils(); + elementUtils = processingEnv.getElementUtils(); + test(); + } + return false; + } + + private void test() { + TypeElement extendsUndefinedType = elementUtils.getTypeElement("ExtendsUndefinedType"); + ErrorType errorType = (ErrorType) extendsUndefinedType.getSuperclass(); + TypeElement list = elementUtils.getTypeElement("java.util.List"); + TypeMirror listOfError = typeUtils.getDeclaredType(list, errorType); + TypeMirror queryExtendsError = typeUtils.getWildcardType(errorType, null); + TypeMirror listOfQueryExtendsError = typeUtils.getDeclaredType(list, queryExtendsError); + TypeMirror querySuperError = typeUtils.getWildcardType(null, errorType); + TypeMirror listOfQuerySuperError = typeUtils.getDeclaredType(list, querySuperError); + TypeMirror arrayOfError = typeUtils.getArrayType(errorType); + testErrorType(errorType); + testErrorType(listOfError); + testErrorType(listOfQueryExtendsError); + testErrorType(listOfQuerySuperError); + testErrorType(arrayOfError); + } + + @SuppressWarnings("MissingFail") // error message gets converted into assertion failure + private void testErrorType(TypeMirror typeWithError) { + try { + TypeEncoder.encode(typeWithError); + processingEnv.getMessager().printMessage( + Diagnostic.Kind.ERROR, "Expected exception for type: " + typeWithError); + } catch (MissingTypeException expected) { + } + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + } +} diff --git a/value/src/test/java/com/google/auto/value/processor/TypeSimplifierTest.java b/value/src/test/java/com/google/auto/value/processor/TypeSimplifierTest.java index a713876491..9bf306b48b 100644 --- a/value/src/test/java/com/google/auto/value/processor/TypeSimplifierTest.java +++ b/value/src/test/java/com/google/auto/value/processor/TypeSimplifierTest.java @@ -16,37 +16,22 @@ package com.google.auto.value.processor; import static com.google.common.truth.Truth.assertThat; -import static com.google.testing.compile.CompilationSubject.assertThat; -import static com.google.testing.compile.Compiler.javac; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.testing.compile.Compilation; import com.google.testing.compile.CompilationRule; -import com.google.testing.compile.JavaFileObjects; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.RoundEnvironment; -import javax.annotation.processing.SupportedAnnotationTypes; -import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ErrorType; -import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; 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 org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -71,8 +56,6 @@ public void setUp() { elementUtils = compilationRule.getElements(); } - private static class MultipleBounds & Comparable, V> {} - private static class Erasure { int intNo; boolean booleanNo; @@ -219,222 +202,6 @@ public void testPackageNameOfMapEntry() { .isEqualTo("java.util"); } - @Test - public void testImportsForNoTypes() { - TypeSimplifier typeSimplifier = - new TypeSimplifier(typeUtils, "foo.bar", typeMirrorSet(), baseWithoutContainedTypes()); - assertThat(typeSimplifier.typesToImport()).isEmpty(); - } - - @Test - public void testImportsForImplicitlyImportedTypes() { - Set types = typeMirrorSet( - typeMirrorOf(java.lang.String.class), - typeMirrorOf(javax.management.MBeanServer.class), // Same package, so no import. - typeUtils.getPrimitiveType(TypeKind.INT), - typeUtils.getPrimitiveType(TypeKind.BOOLEAN) - ); - TypeSimplifier typeSimplifier = - new TypeSimplifier(typeUtils, "javax.management", types, baseWithoutContainedTypes()); - assertThat(typeSimplifier.typesToImport()).isEmpty(); - } - - @Test - public void testImportsForPlainTypes() { - Set types = typeMirrorSet( - typeUtils.getPrimitiveType(TypeKind.INT), - typeMirrorOf(java.lang.String.class), - typeMirrorOf(java.net.Proxy.class), - typeMirrorOf(java.net.Proxy.Type.class), - typeMirrorOf(java.util.regex.Pattern.class), - typeMirrorOf(javax.management.MBeanServer.class)); - List expectedImports = ImmutableList.of( - "java.net.Proxy", - "java.util.regex.Pattern", - "javax.management.MBeanServer" - ); - TypeSimplifier typeSimplifier = - new TypeSimplifier(typeUtils, "foo.bar", types, baseWithoutContainedTypes()); - assertThat(typeSimplifier.typesToImport()).containsExactlyElementsIn(expectedImports).inOrder(); - assertThat(typeSimplifier.simplify(typeMirrorOf(java.net.Proxy.class))) - .isEqualTo("Proxy"); - assertThat(typeSimplifier.simplify(typeMirrorOf(java.net.Proxy.Type.class))) - .isEqualTo("Proxy.Type"); - } - - @Test - public void testImportsForComplicatedTypes() { - TypeElement list = typeElementOf(java.util.List.class); - TypeElement map = typeElementOf(java.util.Map.class); - Set types = typeMirrorSet( - typeUtils.getPrimitiveType(TypeKind.INT), - typeMirrorOf(java.util.regex.Pattern.class), - typeUtils.getDeclaredType(list, // List - typeMirrorOf(java.util.Timer.class)), - typeUtils.getDeclaredType(map, // Map - typeUtils.getWildcardType(typeMirrorOf(java.util.Timer.class), null), - typeUtils.getWildcardType(null, typeMirrorOf(java.math.BigInteger.class)))); - // Timer is referenced twice but should obviously only be imported once. - TypeSimplifier typeSimplifier = - new TypeSimplifier(typeUtils, "foo.bar", types, baseWithoutContainedTypes()); - assertThat(typeSimplifier.typesToImport()) - .containsExactly( - "java.math.BigInteger", - "java.util.List", - "java.util.Map", - "java.util.Timer", - "java.util.regex.Pattern") - .inOrder(); - } - - @Test - public void testImportsForArrayTypes() { - TypeElement list = typeElementOf(java.util.List.class); - TypeElement set = typeElementOf(java.util.Set.class); - Set types = typeMirrorSet( - typeUtils.getArrayType(typeUtils.getPrimitiveType(TypeKind.INT)), - typeUtils.getArrayType(typeMirrorOf(java.util.regex.Pattern.class)), - typeUtils.getArrayType( // Set[] - typeUtils.getDeclaredType(set, - typeUtils.getArrayType(typeMirrorOf(java.util.regex.Matcher.class)))), - typeUtils.getDeclaredType(list, // List - typeUtils.getArrayType(typeMirrorOf(java.util.Timer.class)))); - // Timer is referenced twice but should obviously only be imported once. - TypeSimplifier typeSimplifier = - new TypeSimplifier(typeUtils, "foo.bar", types, baseWithoutContainedTypes()); - assertThat(typeSimplifier.typesToImport()) - .containsExactly( - "java.util.List", - "java.util.Set", - "java.util.Timer", - "java.util.regex.Matcher", - "java.util.regex.Pattern") - .inOrder(); - } - - @Test - public void testImportNestedType() { - Set types = typeMirrorSet(typeMirrorOf(java.net.Proxy.Type.class)); - TypeSimplifier typeSimplifier = - new TypeSimplifier(typeUtils, "foo.bar", types, baseWithoutContainedTypes()); - assertThat(typeSimplifier.typesToImport()) - .containsExactly("java.net.Proxy"); - assertThat(typeSimplifier.simplify(typeMirrorOf(java.net.Proxy.Type.class))) - .isEqualTo("Proxy.Type"); - } - - @Test - public void testImportsForAmbiguousNames() { - Set types = typeMirrorSet( - typeUtils.getPrimitiveType(TypeKind.INT), - typeMirrorOf(java.awt.List.class), - typeMirrorOf(java.lang.String.class), - typeMirrorOf(java.util.List.class), - typeMirrorOf(java.util.Map.class) - ); - TypeSimplifier typeSimplifier = - new TypeSimplifier(typeUtils, "foo.bar", types, baseWithoutContainedTypes()); - assertThat(typeSimplifier.typesToImport()) - .containsExactly("java.util.Map"); - } - - @Test - public void testSimplifyJavaLangString() { - TypeMirror string = typeMirrorOf(java.lang.String.class); - Set types = typeMirrorSet(string); - TypeSimplifier typeSimplifier = - new TypeSimplifier(typeUtils, "foo.bar", types, baseWithoutContainedTypes()); - assertThat(typeSimplifier.simplify(string)).isEqualTo("String"); - } - - @Test - public void testSimplifyJavaLangThreadState() { - TypeMirror threadState = typeMirrorOf(java.lang.Thread.State.class); - Set types = typeMirrorSet(threadState); - TypeSimplifier typeSimplifier = - new TypeSimplifier(typeUtils, "foo.bar", types, baseWithoutContainedTypes()); - assertThat(typeSimplifier.simplify(threadState)).isEqualTo("Thread.State"); - } - - @Test - public void testSimplifyAmbiguousNames() { - TypeMirror javaAwtList = typeMirrorOf(java.awt.List.class); - TypeMirror javaUtilList = typeMirrorOf(java.util.List.class); - Set types = typeMirrorSet(javaAwtList, javaUtilList); - TypeSimplifier typeSimplifier = - new TypeSimplifier(typeUtils, "foo.bar", types, baseWithoutContainedTypes()); - assertThat(typeSimplifier.simplify(javaAwtList)).isEqualTo(javaAwtList.toString()); - assertThat(typeSimplifier.simplify(javaUtilList)).isEqualTo(javaUtilList.toString()); - } - - @Test - public void testSimplifyJavaLangNamesake() { - TypeMirror javaLangType = typeMirrorOf(java.lang.RuntimePermission.class); - TypeMirror notJavaLangType = typeMirrorOf( - com.google.auto.value.processor.testclasses.RuntimePermission.class); - Set types = typeMirrorSet(javaLangType, notJavaLangType); - TypeSimplifier typeSimplifier = - new TypeSimplifier(typeUtils, "foo.bar", types, baseWithoutContainedTypes()); - assertThat(typeSimplifier.simplify(javaLangType)).isEqualTo(javaLangType.toString()); - assertThat(typeSimplifier.simplify(notJavaLangType)).isEqualTo(notJavaLangType.toString()); - } - - @Test - public void testSimplifyComplicatedTypes() { - // This test constructs a set of types and feeds them to TypeSimplifier. Then it verifies that - // the resultant rewrites of those types are what we would expect. - TypeElement list = typeElementOf(java.util.List.class); - TypeElement map = typeElementOf(java.util.Map.class); - TypeMirror string = typeMirrorOf(java.lang.String.class); - TypeMirror integer = typeMirrorOf(java.lang.Integer.class); - TypeMirror pattern = typeMirrorOf(java.util.regex.Pattern.class); - TypeMirror timer = typeMirrorOf(java.util.Timer.class); - TypeMirror bigInteger = typeMirrorOf(java.math.BigInteger.class); - Set types = typeMirrorSet( - typeUtils.getPrimitiveType(TypeKind.INT), - typeUtils.getArrayType(typeUtils.getPrimitiveType(TypeKind.BYTE)), - pattern, - typeUtils.getArrayType(pattern), - typeUtils.getArrayType(typeUtils.getArrayType(pattern)), - typeUtils.getDeclaredType(list, typeUtils.getWildcardType(null, null)), - typeUtils.getDeclaredType(list, timer), - typeUtils.getDeclaredType(map, string, integer), - typeUtils.getDeclaredType(map, - typeUtils.getWildcardType(timer, null), typeUtils.getWildcardType(null, bigInteger))); - Set expectedSimplifications = ImmutableSet.of( - "int", - "byte[]", - "Pattern", - "Pattern[]", - "Pattern[][]", - "List", - "List", - "Map", - "Map" - ); - TypeSimplifier typeSimplifier = - new TypeSimplifier(typeUtils, "foo.bar", types, baseWithoutContainedTypes()); - Set actualSimplifications = new HashSet(); - for (TypeMirror type : types) { - actualSimplifications.add(typeSimplifier.simplify(type)); - } - assertThat(actualSimplifications).isEqualTo(expectedSimplifications); - } - - @Test - public void testSimplifyMultipleBounds() { - TypeElement multipleBoundsElement = typeElementOf(MultipleBounds.class); - TypeMirror multipleBoundsMirror = multipleBoundsElement.asType(); - TypeSimplifier typeSimplifier = new TypeSimplifier(typeUtils, "", - typeMirrorSet(multipleBoundsMirror), baseWithoutContainedTypes()); - assertThat(typeSimplifier.typesToImport()) - .contains("java.util.List"); - assertThat(typeSimplifier.simplify(multipleBoundsMirror)) - .isEqualTo("TypeSimplifierTest.MultipleBounds"); - assertThat(typeSimplifier.formalTypeParametersString(multipleBoundsElement)) - .isEqualTo(" & Comparable, V>"); - } - // Test TypeSimplifier.isCastingUnchecked. We do this by examining the fields of the Erasure // class. A field whose name ends with Yes has a type where // isCastingUnchecked should return true, and one whose name ends with No has a type where @@ -493,81 +260,4 @@ private TypeElement typeElementOf(Class c) { private TypeMirror typeMirrorOf(Class c) { return typeElementOf(c).asType(); } - - /** - * Returns a "base type" for TypeSimplifier that does not contain any nested types. The point - * being that every {@code TypeSimplifier} has a base type that the class being generated is - * going to extend, and if that class has nested types they will be in scope, and therefore a - * possible source of ambiguity. - */ - private TypeMirror baseWithoutContainedTypes() { - return typeMirrorOf(Object.class); - } - - // This test checks that we correctly throw MissingTypeException if there is an ErrorType anywhere - // inside a type we are asked to simplify. There's no way to get an ErrorType from typeUtils or - // elementUtils, so we need to fire up the compiler with an erroneous source file and use an - // annotation processor to capture the resulting ErrorType. Then we can run tests within that - // annotation processor, and propagate any failures out of this test. - @Test - public void testErrorTypes() { - JavaFileObject source = JavaFileObjects.forSourceString( - "ExtendsUndefinedType", "class ExtendsUndefinedType extends UndefinedParent {}"); - Compilation compilation = javac() - .withProcessors(new ErrorTestProcessor()) - .compile(source); - assertThat(compilation).failed(); - assertThat(compilation).hadErrorContaining("UndefinedParent"); - assertThat(compilation).hadErrorCount(1); - } - - @SupportedAnnotationTypes("*") - private static class ErrorTestProcessor extends AbstractProcessor { - Types typeUtils; - Elements elementUtils; - - @Override - public boolean process( - Set annotations, RoundEnvironment roundEnv) { - if (roundEnv.processingOver()) { - typeUtils = processingEnv.getTypeUtils(); - elementUtils = processingEnv.getElementUtils(); - test(); - } - return false; - } - - private void test() { - TypeElement extendsUndefinedType = elementUtils.getTypeElement("ExtendsUndefinedType"); - ErrorType errorType = (ErrorType) extendsUndefinedType.getSuperclass(); - TypeElement list = elementUtils.getTypeElement("java.util.List"); - TypeMirror listOfError = typeUtils.getDeclaredType(list, errorType); - TypeMirror queryExtendsError = typeUtils.getWildcardType(errorType, null); - TypeMirror listOfQueryExtendsError = typeUtils.getDeclaredType(list, queryExtendsError); - TypeMirror querySuperError = typeUtils.getWildcardType(null, errorType); - TypeMirror listOfQuerySuperError = typeUtils.getDeclaredType(list, querySuperError); - TypeMirror arrayOfError = typeUtils.getArrayType(errorType); - testErrorType(errorType); - testErrorType(listOfError); - testErrorType(listOfQueryExtendsError); - testErrorType(listOfQuerySuperError); - testErrorType(arrayOfError); - } - - @SuppressWarnings("MissingFail") // error message gets converted into assertion failure - private void testErrorType(TypeMirror typeWithError) { - TypeMirror javaLangObject = elementUtils.getTypeElement("java.lang.Object").asType(); - try { - new TypeSimplifier(typeUtils, "foo.bar", ImmutableSet.of(typeWithError), javaLangObject); - processingEnv.getMessager().printMessage( - Diagnostic.Kind.ERROR, "Expected exception for type: " + typeWithError); - } catch (MissingTypeException expected) { - } - } - - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } - } }