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