• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 Square, Inc.
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.squareup.javapoet;
17 
18 import java.io.IOException;
19 import java.lang.annotation.Annotation;
20 import java.lang.reflect.Array;
21 import java.lang.reflect.Method;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Comparator;
25 import java.util.Iterator;
26 import java.util.LinkedHashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Objects;
30 import javax.lang.model.SourceVersion;
31 import javax.lang.model.element.AnnotationMirror;
32 import javax.lang.model.element.AnnotationValue;
33 import javax.lang.model.element.ExecutableElement;
34 import javax.lang.model.element.TypeElement;
35 import javax.lang.model.element.VariableElement;
36 import javax.lang.model.type.TypeMirror;
37 import javax.lang.model.util.SimpleAnnotationValueVisitor8;
38 
39 import static com.squareup.javapoet.Util.characterLiteralWithoutSingleQuotes;
40 import static com.squareup.javapoet.Util.checkArgument;
41 import static com.squareup.javapoet.Util.checkNotNull;
42 
43 /** A generated annotation on a declaration. */
44 public final class AnnotationSpec {
45   public final TypeName type;
46   public final Map<String, List<CodeBlock>> members;
47 
AnnotationSpec(Builder builder)48   private AnnotationSpec(Builder builder) {
49     this.type = builder.type;
50     this.members = Util.immutableMultimap(builder.members);
51   }
52 
emit(CodeWriter codeWriter, boolean inline)53   void emit(CodeWriter codeWriter, boolean inline) throws IOException {
54     String whitespace = inline ? "" : "\n";
55     String memberSeparator = inline ? ", " : ",\n";
56     if (members.isEmpty()) {
57       // @Singleton
58       codeWriter.emit("@$T", type);
59     } else if (members.size() == 1 && members.containsKey("value")) {
60       // @Named("foo")
61       codeWriter.emit("@$T(", type);
62       emitAnnotationValues(codeWriter, whitespace, memberSeparator, members.get("value"));
63       codeWriter.emit(")");
64     } else {
65       // Inline:
66       //   @Column(name = "updated_at", nullable = false)
67       //
68       // Not inline:
69       //   @Column(
70       //       name = "updated_at",
71       //       nullable = false
72       //   )
73       codeWriter.emit("@$T(" + whitespace, type);
74       codeWriter.indent(2);
75       for (Iterator<Map.Entry<String, List<CodeBlock>>> i
76           = members.entrySet().iterator(); i.hasNext(); ) {
77         Map.Entry<String, List<CodeBlock>> entry = i.next();
78         codeWriter.emit("$L = ", entry.getKey());
79         emitAnnotationValues(codeWriter, whitespace, memberSeparator, entry.getValue());
80         if (i.hasNext()) codeWriter.emit(memberSeparator);
81       }
82       codeWriter.unindent(2);
83       codeWriter.emit(whitespace + ")");
84     }
85   }
86 
emitAnnotationValues(CodeWriter codeWriter, String whitespace, String memberSeparator, List<CodeBlock> values)87   private void emitAnnotationValues(CodeWriter codeWriter, String whitespace,
88       String memberSeparator, List<CodeBlock> values) throws IOException {
89     if (values.size() == 1) {
90       codeWriter.indent(2);
91       codeWriter.emit(values.get(0));
92       codeWriter.unindent(2);
93       return;
94     }
95 
96     codeWriter.emit("{" + whitespace);
97     codeWriter.indent(2);
98     boolean first = true;
99     for (CodeBlock codeBlock : values) {
100       if (!first) codeWriter.emit(memberSeparator);
101       codeWriter.emit(codeBlock);
102       first = false;
103     }
104     codeWriter.unindent(2);
105     codeWriter.emit(whitespace + "}");
106   }
107 
get(Annotation annotation)108   public static AnnotationSpec get(Annotation annotation) {
109     return get(annotation, false);
110   }
111 
get(Annotation annotation, boolean includeDefaultValues)112   public static AnnotationSpec get(Annotation annotation, boolean includeDefaultValues) {
113     Builder builder = builder(annotation.annotationType());
114     try {
115       Method[] methods = annotation.annotationType().getDeclaredMethods();
116       Arrays.sort(methods, Comparator.comparing(Method::getName));
117       for (Method method : methods) {
118         Object value = method.invoke(annotation);
119         if (!includeDefaultValues) {
120           if (Objects.deepEquals(value, method.getDefaultValue())) {
121             continue;
122           }
123         }
124         if (value.getClass().isArray()) {
125           for (int i = 0; i < Array.getLength(value); i++) {
126             builder.addMemberForValue(method.getName(), Array.get(value, i));
127           }
128           continue;
129         }
130         if (value instanceof Annotation) {
131           builder.addMember(method.getName(), "$L", get((Annotation) value));
132           continue;
133         }
134         builder.addMemberForValue(method.getName(), value);
135       }
136     } catch (Exception e) {
137       throw new RuntimeException("Reflecting " + annotation + " failed!", e);
138     }
139     return builder.build();
140   }
141 
get(AnnotationMirror annotation)142   public static AnnotationSpec get(AnnotationMirror annotation) {
143     TypeElement element = (TypeElement) annotation.getAnnotationType().asElement();
144     AnnotationSpec.Builder builder = AnnotationSpec.builder(ClassName.get(element));
145     Visitor visitor = new Visitor(builder);
146     for (ExecutableElement executableElement : annotation.getElementValues().keySet()) {
147       String name = executableElement.getSimpleName().toString();
148       AnnotationValue value = annotation.getElementValues().get(executableElement);
149       value.accept(visitor, name);
150     }
151     return builder.build();
152   }
153 
builder(ClassName type)154   public static Builder builder(ClassName type) {
155     checkNotNull(type, "type == null");
156     return new Builder(type);
157   }
158 
builder(Class<?> type)159   public static Builder builder(Class<?> type) {
160     return builder(ClassName.get(type));
161   }
162 
toBuilder()163   public Builder toBuilder() {
164     Builder builder = new Builder(type);
165     for (Map.Entry<String, List<CodeBlock>> entry : members.entrySet()) {
166       builder.members.put(entry.getKey(), new ArrayList<>(entry.getValue()));
167     }
168     return builder;
169   }
170 
equals(Object o)171   @Override public boolean equals(Object o) {
172     if (this == o) return true;
173     if (o == null) return false;
174     if (getClass() != o.getClass()) return false;
175     return toString().equals(o.toString());
176   }
177 
hashCode()178   @Override public int hashCode() {
179     return toString().hashCode();
180   }
181 
toString()182   @Override public String toString() {
183     StringBuilder out = new StringBuilder();
184     try {
185       CodeWriter codeWriter = new CodeWriter(out);
186       codeWriter.emit("$L", this);
187       return out.toString();
188     } catch (IOException e) {
189       throw new AssertionError();
190     }
191   }
192 
193   public static final class Builder {
194     private final TypeName type;
195 
196     public final Map<String, List<CodeBlock>> members = new LinkedHashMap<>();
197 
Builder(TypeName type)198     private Builder(TypeName type) {
199       this.type = type;
200     }
201 
addMember(String name, String format, Object... args)202     public Builder addMember(String name, String format, Object... args) {
203       return addMember(name, CodeBlock.of(format, args));
204     }
205 
addMember(String name, CodeBlock codeBlock)206     public Builder addMember(String name, CodeBlock codeBlock) {
207       List<CodeBlock> values = members.computeIfAbsent(name, k -> new ArrayList<>());
208       values.add(codeBlock);
209       return this;
210     }
211 
212     /**
213      * Delegates to {@link #addMember(String, String, Object...)}, with parameter {@code format}
214      * depending on the given {@code value} object. Falls back to {@code "$L"} literal format if
215      * the class of the given {@code value} object is not supported.
216      */
addMemberForValue(String memberName, Object value)217     Builder addMemberForValue(String memberName, Object value) {
218       checkNotNull(memberName, "memberName == null");
219       checkNotNull(value, "value == null, constant non-null value expected for %s", memberName);
220       checkArgument(SourceVersion.isName(memberName), "not a valid name: %s", memberName);
221       if (value instanceof Class<?>) {
222         return addMember(memberName, "$T.class", value);
223       }
224       if (value instanceof Enum) {
225         return addMember(memberName, "$T.$L", value.getClass(), ((Enum<?>) value).name());
226       }
227       if (value instanceof String) {
228         return addMember(memberName, "$S", value);
229       }
230       if (value instanceof Float) {
231         return addMember(memberName, "$Lf", value);
232       }
233       if (value instanceof Character) {
234         return addMember(memberName, "'$L'", characterLiteralWithoutSingleQuotes((char) value));
235       }
236       return addMember(memberName, "$L", value);
237     }
238 
build()239     public AnnotationSpec build() {
240       for (String name : members.keySet()) {
241         checkNotNull(name, "name == null");
242         checkArgument(SourceVersion.isName(name), "not a valid name: %s", name);
243       }
244       return new AnnotationSpec(this);
245     }
246   }
247 
248   /**
249    * Annotation value visitor adding members to the given builder instance.
250    */
251   private static class Visitor extends SimpleAnnotationValueVisitor8<Builder, String> {
252     final Builder builder;
253 
Visitor(Builder builder)254     Visitor(Builder builder) {
255       super(builder);
256       this.builder = builder;
257     }
258 
defaultAction(Object o, String name)259     @Override protected Builder defaultAction(Object o, String name) {
260       return builder.addMemberForValue(name, o);
261     }
262 
visitAnnotation(AnnotationMirror a, String name)263     @Override public Builder visitAnnotation(AnnotationMirror a, String name) {
264       return builder.addMember(name, "$L", get(a));
265     }
266 
visitEnumConstant(VariableElement c, String name)267     @Override public Builder visitEnumConstant(VariableElement c, String name) {
268       return builder.addMember(name, "$T.$L", c.asType(), c.getSimpleName());
269     }
270 
visitType(TypeMirror t, String name)271     @Override public Builder visitType(TypeMirror t, String name) {
272       return builder.addMember(name, "$T.class", t);
273     }
274 
visitArray(List<? extends AnnotationValue> values, String name)275     @Override public Builder visitArray(List<? extends AnnotationValue> values, String name) {
276       for (AnnotationValue value : values) {
277         value.accept(this, name);
278       }
279       return builder;
280     }
281   }
282 }
283