• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.auto.value.processor;
17 
18 import com.google.auto.common.MoreTypes;
19 import com.google.auto.value.processor.MissingTypes.MissingTypeException;
20 import com.google.common.collect.ImmutableMap;
21 import com.google.common.collect.Iterables;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Optional;
25 import javax.annotation.processing.ProcessingEnvironment;
26 import javax.lang.model.element.AnnotationMirror;
27 import javax.lang.model.element.AnnotationValue;
28 import javax.lang.model.element.Element;
29 import javax.lang.model.element.ElementKind;
30 import javax.lang.model.element.ExecutableElement;
31 import javax.lang.model.element.VariableElement;
32 import javax.lang.model.type.TypeKind;
33 import javax.lang.model.type.TypeMirror;
34 import javax.lang.model.util.SimpleAnnotationValueVisitor8;
35 import javax.tools.Diagnostic;
36 
37 /**
38  * Handling of default values for annotation members.
39  *
40  * @author emcmanus@google.com (Éamonn McManus)
41  */
42 final class AnnotationOutput {
AnnotationOutput()43   private AnnotationOutput() {} // There are no instances of this class.
44 
45   /**
46    * Visitor that produces a string representation of an annotation value, suitable for inclusion in
47    * a Java source file as an annotation member or as the initializer of a variable of the
48    * appropriate type. The syntax for the two is the same except for annotation members that are
49    * themselves annotations. Within an annotation, an annotation member can be written as
50    * {@code @NestedAnnotation(...)}, while in an initializer it must be written as an object, for
51    * example the construction of an {@code @AutoAnnotation} class. That's why we have this abstract
52    * class and two concrete subclasses.
53    */
54   private abstract static class SourceFormVisitor
55       extends SimpleAnnotationValueVisitor8<Void, StringBuilder> {
56     @Override
defaultAction(Object value, StringBuilder sb)57     protected Void defaultAction(Object value, StringBuilder sb) {
58       sb.append(value);
59       return null;
60     }
61 
62     @Override
visitArray(List<? extends AnnotationValue> values, StringBuilder sb)63     public Void visitArray(List<? extends AnnotationValue> values, StringBuilder sb) {
64       sb.append('{');
65       String sep = "";
66       for (AnnotationValue value : values) {
67         sb.append(sep);
68         visit(value, sb);
69         sep = ", ";
70       }
71       sb.append('}');
72       return null;
73     }
74 
75     @Override
visitChar(char c, StringBuilder sb)76     public Void visitChar(char c, StringBuilder sb) {
77       appendQuoted(sb, c);
78       return null;
79     }
80 
81     @Override
visitLong(long i, StringBuilder sb)82     public Void visitLong(long i, StringBuilder sb) {
83       sb.append(i).append('L');
84       return null;
85     }
86 
87     @Override
visitDouble(double d, StringBuilder sb)88     public Void visitDouble(double d, StringBuilder sb) {
89       if (Double.isNaN(d)) {
90         sb.append("Double.NaN");
91       } else if (d == Double.POSITIVE_INFINITY) {
92         sb.append("Double.POSITIVE_INFINITY");
93       } else if (d == Double.NEGATIVE_INFINITY) {
94         sb.append("Double.NEGATIVE_INFINITY");
95       } else {
96         sb.append(d);
97       }
98       return null;
99     }
100 
101     @Override
visitFloat(float f, StringBuilder sb)102     public Void visitFloat(float f, StringBuilder sb) {
103       if (Float.isNaN(f)) {
104         sb.append("Float.NaN");
105       } else if (f == Float.POSITIVE_INFINITY) {
106         sb.append("Float.POSITIVE_INFINITY");
107       } else if (f == Float.NEGATIVE_INFINITY) {
108         sb.append("Float.NEGATIVE_INFINITY");
109       } else {
110         sb.append(f).append('F');
111       }
112       return null;
113     }
114 
115     @Override
visitEnumConstant(VariableElement c, StringBuilder sb)116     public Void visitEnumConstant(VariableElement c, StringBuilder sb) {
117       sb.append(TypeEncoder.encode(c.asType())).append('.').append(c.getSimpleName());
118       return null;
119     }
120 
121     @Override
visitString(String s, StringBuilder sb)122     public Void visitString(String s, StringBuilder sb) {
123       appendQuoted(sb, s);
124       return null;
125     }
126 
127     @Override
visitType(TypeMirror classConstant, StringBuilder sb)128     public Void visitType(TypeMirror classConstant, StringBuilder sb) {
129       sb.append(TypeEncoder.encode(classConstant)).append(".class");
130       return null;
131     }
132   }
133 
134   private static class InitializerSourceFormVisitor extends SourceFormVisitor {
135     private final ProcessingEnvironment processingEnv;
136     private final String memberName;
137     private final Element errorContext;
138 
InitializerSourceFormVisitor( ProcessingEnvironment processingEnv, String memberName, Element errorContext)139     InitializerSourceFormVisitor(
140         ProcessingEnvironment processingEnv, String memberName, Element errorContext) {
141       this.processingEnv = processingEnv;
142       this.memberName = memberName;
143       this.errorContext = errorContext;
144     }
145 
146     @Override
visitAnnotation(AnnotationMirror a, StringBuilder sb)147     public Void visitAnnotation(AnnotationMirror a, StringBuilder sb) {
148       processingEnv
149           .getMessager()
150           .printMessage(
151               Diagnostic.Kind.ERROR,
152               "@AutoAnnotation cannot yet supply a default value for annotation-valued member '"
153                   + memberName
154                   + "'",
155               errorContext);
156       sb.append("null");
157       return null;
158     }
159   }
160 
161   private static class AnnotationSourceFormVisitor extends SourceFormVisitor {
162     @Override
visitArray(List<? extends AnnotationValue> values, StringBuilder sb)163     public Void visitArray(List<? extends AnnotationValue> values, StringBuilder sb) {
164       if (values.size() == 1) {
165         // We can shorten @Foo(a = {23}) to @Foo(a = 23). For the specific case where `a` is
166         // actually `value`, we'll already have shortened that in visitAnnotation, so effectively we
167         // go from @Foo(value = {23}) to @Foo({23}) to @Foo(23).
168         visit(values.get(0), sb);
169         return null;
170       }
171       return super.visitArray(values, sb);
172     }
173 
174     @Override
visitAnnotation(AnnotationMirror a, StringBuilder sb)175     public Void visitAnnotation(AnnotationMirror a, StringBuilder sb) {
176       sb.append('@').append(TypeEncoder.encode(a.getAnnotationType()));
177       ImmutableMap<ExecutableElement, AnnotationValue> map =
178           ImmutableMap.copyOf(a.getElementValues());
179       if (!map.isEmpty()) {
180         sb.append('(');
181         Optional<AnnotationValue> shortForm = shortForm(map);
182         if (shortForm.isPresent()) {
183           this.visit(shortForm.get(), sb);
184         } else {
185           String sep = "";
186           for (Map.Entry<ExecutableElement, AnnotationValue> entry : map.entrySet()) {
187             sb.append(sep).append(entry.getKey().getSimpleName()).append(" = ");
188             sep = ", ";
189             this.visit(entry.getValue(), sb);
190           }
191         }
192         sb.append(')');
193       }
194       return null;
195     }
196 
197     // We can shorten @Annot(value = 23) to @Annot(23).
shortForm( Map<ExecutableElement, AnnotationValue> values)198     private static Optional<AnnotationValue> shortForm(
199         Map<ExecutableElement, AnnotationValue> values) {
200       if (values.size() == 1
201           && Iterables.getOnlyElement(values.keySet()).getSimpleName().contentEquals("value")) {
202         return Optional.of(Iterables.getOnlyElement(values.values()));
203       }
204       return Optional.empty();
205     }
206   }
207 
208   /**
209    * Returns a string representation of the given annotation value, suitable for inclusion in a Java
210    * source file as the initializer of a variable of the appropriate type.
211    */
sourceFormForInitializer( AnnotationValue annotationValue, ProcessingEnvironment processingEnv, String memberName, Element errorContext)212   static String sourceFormForInitializer(
213       AnnotationValue annotationValue,
214       ProcessingEnvironment processingEnv,
215       String memberName,
216       Element errorContext) {
217     SourceFormVisitor visitor =
218         new InitializerSourceFormVisitor(processingEnv, memberName, errorContext);
219     StringBuilder sb = new StringBuilder();
220     visitor.visit(annotationValue, sb);
221     return sb.toString();
222   }
223 
224   /**
225    * Returns a string representation of the given annotation mirror, suitable for inclusion in a
226    * Java source file to reproduce the annotation in source form.
227    */
sourceFormForAnnotation(AnnotationMirror annotationMirror)228   static String sourceFormForAnnotation(AnnotationMirror annotationMirror) {
229     // If a value in the annotation is a reference to a class constant and that class constant is
230     // undefined, javac unhelpfully converts it into a string "<error>" and visits that instead. We
231     // want to catch this case and defer processing to allow the class to be defined by another
232     // annotation processor. So we look for annotation elements whose type is Class but whose
233     // reported value is a string. Unfortunately we can't extract the ErrorType corresponding to the
234     // missing class portably. With javac, the AttributeValue is a
235     // com.sun.tools.javac.code.Attribute.UnresolvedClass, which has a public field classType that
236     // is the ErrorType we need, but obviously that's nonportable and fragile.
237     validateClassValues(annotationMirror);
238     StringBuilder sb = new StringBuilder();
239     new AnnotationSourceFormVisitor().visitAnnotation(annotationMirror, sb);
240     return sb.toString();
241   }
242 
243   /**
244    * Throws an exception if this annotation contains a value for a Class element that is not
245    * actually a type. The assumption is that the value is the string {@code "<error>"} which javac
246    * presents when a Class value is an undefined type.
247    */
validateClassValues(AnnotationMirror annotationMirror)248   private static void validateClassValues(AnnotationMirror annotationMirror) {
249     // A class literal can appear in three places:
250     // * for an element of type Class, for example @SomeAnnotation(Foo.class);
251     // * for an element of type Class[], for example @SomeAnnotation({Foo.class, Bar.class});
252     // * inside a nested annotation, for example @SomeAnnotation(@Nested(Foo.class)).
253     // These three possibilities are the three branches of the if/else chain below.
254     annotationMirror
255         .getElementValues()
256         .forEach(
257             (method, value) -> {
258               TypeMirror type = method.getReturnType();
259               if (isJavaLangClass(type) && !(value.getValue() instanceof TypeMirror)) {
260                 throw new MissingTypeException(null);
261               } else if (type.getKind().equals(TypeKind.ARRAY)
262                   && isJavaLangClass(MoreTypes.asArray(type).getComponentType())
263                   && value.getValue() instanceof List<?>) {
264                 @SuppressWarnings("unchecked") // a List can only be a List<AnnotationValue> here
265                 List<AnnotationValue> values = (List<AnnotationValue>) value.getValue();
266                 if (values.stream().anyMatch(av -> !(av.getValue() instanceof TypeMirror))) {
267                   throw new MissingTypeException(null);
268                 }
269               } else if (type.getKind().equals(TypeKind.DECLARED)
270                   && MoreTypes.asElement(type).getKind().equals(ElementKind.ANNOTATION_TYPE)
271                   && value.getValue() instanceof AnnotationMirror) {
272                 validateClassValues((AnnotationMirror) value.getValue());
273               }
274             });
275   }
276 
isJavaLangClass(TypeMirror type)277   private static boolean isJavaLangClass(TypeMirror type) {
278     return type.getKind().equals(TypeKind.DECLARED)
279         && MoreTypes.asTypeElement(type).getQualifiedName().contentEquals("java.lang.Class");
280   }
281 
appendQuoted(StringBuilder sb, String s)282   private static StringBuilder appendQuoted(StringBuilder sb, String s) {
283     sb.append('"');
284     for (int i = 0; i < s.length(); i++) {
285       appendEscaped(sb, s.charAt(i));
286     }
287     return sb.append('"');
288   }
289 
appendQuoted(StringBuilder sb, char c)290   private static StringBuilder appendQuoted(StringBuilder sb, char c) {
291     sb.append('\'');
292     appendEscaped(sb, c);
293     return sb.append('\'');
294   }
295 
appendEscaped(StringBuilder sb, char c)296   private static void appendEscaped(StringBuilder sb, char c) {
297     switch (c) {
298       case '\\':
299       case '"':
300       case '\'':
301         sb.append('\\').append(c);
302         break;
303       case '\n':
304         sb.append("\\n");
305         break;
306       case '\r':
307         sb.append("\\r");
308         break;
309       case '\t':
310         sb.append("\\t");
311         break;
312       default:
313         if (c < 0x20) {
314           sb.append(String.format("\\%03o", (int) c));
315         } else if (c < 0x7f || Character.isLetter(c)) {
316           sb.append(c);
317         } else {
318           sb.append(String.format("\\u%04x", (int) c));
319         }
320         break;
321     }
322   }
323 }
324