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