• 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.common.collect.ImmutableMap;
19 import com.google.common.collect.Iterables;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Optional;
23 import javax.annotation.processing.ProcessingEnvironment;
24 import javax.lang.model.element.AnnotationMirror;
25 import javax.lang.model.element.AnnotationValue;
26 import javax.lang.model.element.Element;
27 import javax.lang.model.element.ExecutableElement;
28 import javax.lang.model.element.VariableElement;
29 import javax.lang.model.type.TypeMirror;
30 import javax.lang.model.util.SimpleAnnotationValueVisitor8;
31 import javax.tools.Diagnostic;
32 
33 /**
34  * Handling of default values for annotation members.
35  *
36  * @author emcmanus@google.com (Éamonn McManus)
37  */
38 final class AnnotationOutput {
AnnotationOutput()39   private AnnotationOutput() {} // There are no instances of this class.
40 
41   /**
42    * Visitor that produces a string representation of an annotation value, suitable for inclusion in
43    * a Java source file as an annotation member or as the initializer of a variable of the
44    * appropriate type. The syntax for the two is the same except for annotation members that are
45    * themselves annotations. Within an annotation, an annotation member can be written as
46    * {@code @NestedAnnotation(...)}, while in an initializer it must be written as an object, for
47    * example the construction of an {@code @AutoAnnotation} class. That's why we have this abstract
48    * class and two concrete subclasses.
49    */
50   private abstract static class SourceFormVisitor
51       extends SimpleAnnotationValueVisitor8<Void, StringBuilder> {
52     @Override
defaultAction(Object value, StringBuilder sb)53     protected Void defaultAction(Object value, StringBuilder sb) {
54       sb.append(value);
55       return null;
56     }
57 
58     @Override
visitArray(List<? extends AnnotationValue> values, StringBuilder sb)59     public Void visitArray(List<? extends AnnotationValue> values, StringBuilder sb) {
60       sb.append('{');
61       String sep = "";
62       for (AnnotationValue value : values) {
63         sb.append(sep);
64         visit(value, sb);
65         sep = ", ";
66       }
67       sb.append('}');
68       return null;
69     }
70 
71     @Override
visitChar(char c, StringBuilder sb)72     public Void visitChar(char c, StringBuilder sb) {
73       appendQuoted(sb, c);
74       return null;
75     }
76 
77     @Override
visitLong(long i, StringBuilder sb)78     public Void visitLong(long i, StringBuilder sb) {
79       sb.append(i).append('L');
80       return null;
81     }
82 
83     @Override
visitDouble(double d, StringBuilder sb)84     public Void visitDouble(double d, StringBuilder sb) {
85       if (Double.isNaN(d)) {
86         sb.append("Double.NaN");
87       } else if (d == Double.POSITIVE_INFINITY) {
88         sb.append("Double.POSITIVE_INFINITY");
89       } else if (d == Double.NEGATIVE_INFINITY) {
90         sb.append("Double.NEGATIVE_INFINITY");
91       } else {
92         sb.append(d);
93       }
94       return null;
95     }
96 
97     @Override
visitFloat(float f, StringBuilder sb)98     public Void visitFloat(float f, StringBuilder sb) {
99       if (Float.isNaN(f)) {
100         sb.append("Float.NaN");
101       } else if (f == Float.POSITIVE_INFINITY) {
102         sb.append("Float.POSITIVE_INFINITY");
103       } else if (f == Float.NEGATIVE_INFINITY) {
104         sb.append("Float.NEGATIVE_INFINITY");
105       } else {
106         sb.append(f).append('F');
107       }
108       return null;
109     }
110 
111     @Override
visitEnumConstant(VariableElement c, StringBuilder sb)112     public Void visitEnumConstant(VariableElement c, StringBuilder sb) {
113       sb.append(TypeEncoder.encode(c.asType())).append('.').append(c.getSimpleName());
114       return null;
115     }
116 
117     @Override
visitString(String s, StringBuilder sb)118     public Void visitString(String s, StringBuilder sb) {
119       appendQuoted(sb, s);
120       return null;
121     }
122 
123     @Override
visitType(TypeMirror classConstant, StringBuilder sb)124     public Void visitType(TypeMirror classConstant, StringBuilder sb) {
125       sb.append(TypeEncoder.encode(classConstant)).append(".class");
126       return null;
127     }
128   }
129 
130   private static class InitializerSourceFormVisitor extends SourceFormVisitor {
131     private final ProcessingEnvironment processingEnv;
132     private final String memberName;
133     private final Element context;
134 
InitializerSourceFormVisitor( ProcessingEnvironment processingEnv, String memberName, Element context)135     InitializerSourceFormVisitor(
136         ProcessingEnvironment processingEnv, String memberName, Element context) {
137       this.processingEnv = processingEnv;
138       this.memberName = memberName;
139       this.context = context;
140     }
141 
142     @Override
visitAnnotation(AnnotationMirror a, StringBuilder sb)143     public Void visitAnnotation(AnnotationMirror a, StringBuilder sb) {
144       processingEnv
145           .getMessager()
146           .printMessage(
147               Diagnostic.Kind.ERROR,
148               "@AutoAnnotation cannot yet supply a default value for annotation-valued member '"
149                   + memberName
150                   + "'",
151               context);
152       sb.append("null");
153       return null;
154     }
155   }
156 
157   private static class AnnotationSourceFormVisitor extends SourceFormVisitor {
158     @Override
visitArray(List<? extends AnnotationValue> values, StringBuilder sb)159     public Void visitArray(List<? extends AnnotationValue> values, StringBuilder sb) {
160       if (values.size() == 1) {
161         // We can shorten @Foo(a = {23}) to @Foo(a = 23). For the specific case where `a` is
162         // actually `value`, we'll already have shortened that in visitAnnotation, so effectively we
163         // go from @Foo(value = {23}) to @Foo({23}) to @Foo(23).
164         visit(values.get(0), sb);
165         return null;
166       }
167       return super.visitArray(values, sb);
168     }
169 
170     @Override
visitAnnotation(AnnotationMirror a, StringBuilder sb)171     public Void visitAnnotation(AnnotationMirror a, StringBuilder sb) {
172       sb.append('@').append(TypeEncoder.encode(a.getAnnotationType()));
173       ImmutableMap<ExecutableElement, AnnotationValue> map =
174           ImmutableMap.copyOf(a.getElementValues());
175       if (!map.isEmpty()) {
176         sb.append('(');
177         Optional<AnnotationValue> shortForm = shortForm(map);
178         if (shortForm.isPresent()) {
179           this.visit(shortForm.get(), sb);
180         } else {
181           String sep = "";
182           for (Map.Entry<ExecutableElement, AnnotationValue> entry : map.entrySet()) {
183             sb.append(sep).append(entry.getKey().getSimpleName()).append(" = ");
184             sep = ", ";
185             this.visit(entry.getValue(), sb);
186           }
187         }
188         sb.append(')');
189       }
190       return null;
191     }
192 
193     // We can shorten @Annot(value = 23) to @Annot(23).
shortForm( Map<ExecutableElement, AnnotationValue> values)194     private static Optional<AnnotationValue> shortForm(
195         Map<ExecutableElement, AnnotationValue> values) {
196       if (values.size() == 1
197           && Iterables.getOnlyElement(values.keySet()).getSimpleName().contentEquals("value")) {
198         return Optional.of(Iterables.getOnlyElement(values.values()));
199       }
200       return Optional.empty();
201     }
202   }
203 
204   /**
205    * Returns a string representation of the given annotation value, suitable for inclusion in a Java
206    * source file as the initializer of a variable of the appropriate type.
207    */
sourceFormForInitializer( AnnotationValue annotationValue, ProcessingEnvironment processingEnv, String memberName, Element context)208   static String sourceFormForInitializer(
209       AnnotationValue annotationValue,
210       ProcessingEnvironment processingEnv,
211       String memberName,
212       Element context) {
213     SourceFormVisitor visitor =
214         new InitializerSourceFormVisitor(processingEnv, memberName, context);
215     StringBuilder sb = new StringBuilder();
216     visitor.visit(annotationValue, sb);
217     return sb.toString();
218   }
219 
220   /**
221    * Returns a string representation of the given annotation mirror, suitable for inclusion in a
222    * Java source file to reproduce the annotation in source form.
223    */
sourceFormForAnnotation(AnnotationMirror annotationMirror)224   static String sourceFormForAnnotation(AnnotationMirror annotationMirror) {
225     StringBuilder sb = new StringBuilder();
226     new AnnotationSourceFormVisitor().visitAnnotation(annotationMirror, sb);
227     return sb.toString();
228   }
229 
appendQuoted(StringBuilder sb, String s)230   private static StringBuilder appendQuoted(StringBuilder sb, String s) {
231     sb.append('"');
232     for (int i = 0; i < s.length(); i++) {
233       appendEscaped(sb, s.charAt(i));
234     }
235     return sb.append('"');
236   }
237 
appendQuoted(StringBuilder sb, char c)238   private static StringBuilder appendQuoted(StringBuilder sb, char c) {
239     sb.append('\'');
240     appendEscaped(sb, c);
241     return sb.append('\'');
242   }
243 
appendEscaped(StringBuilder sb, char c)244   private static void appendEscaped(StringBuilder sb, char c) {
245     switch (c) {
246       case '\\':
247       case '"':
248       case '\'':
249         sb.append('\\').append(c);
250         break;
251       case '\n':
252         sb.append("\\n");
253         break;
254       case '\r':
255         sb.append("\\r");
256         break;
257       case '\t':
258         sb.append("\\t");
259         break;
260       default:
261         if (c < 0x20) {
262           sb.append(String.format("\\%03o", (int) c));
263         } else if (c < 0x7f || Character.isLetter(c)) {
264           sb.append(c);
265         } else {
266           sb.append(String.format("\\u%04x", (int) c));
267         }
268         break;
269     }
270   }
271 }
272