• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package annotations.el;
2 
3 /*>>>
4 import org.checkerframework.checker.nullness.qual.Nullable;
5 */
6 
7 import java.io.File;
8 import java.util.*;
9 import java.lang.annotation.RetentionPolicy;
10 import java.lang.reflect.Method;
11 
12 import annotations.el.AElement;
13 import annotations.AnnotationBuilder;
14 import annotations.field.*;
15 import annotations.Annotation;
16 import annotations.Annotations;
17 
18 /**
19  * An annotation type definition, consisting of the annotation name,
20  * its meta-annotations, and its field names and
21  * types. <code>AnnotationDef</code>s are immutable.  An AnnotationDef with
22  * a non-null retention policy is called a "top-level annotation definition".
23  */
24 public final class AnnotationDef extends AElement {
25 
26     /**
27      * The binary name of the annotation type, such as
28      * "foo.Bar$Baz" for inner class Baz in class Bar in package foo.
29      */
30     public final String name;
31 
32     /**
33      * A map of the names of this annotation type's fields to their types. Since
34      * {@link AnnotationDef}s are immutable, attempting to modify this
35      * map will result in an exception.
36      */
37     public Map<String, AnnotationFieldType> fieldTypes;
38 
39     /**
40      * Constructs an annotation definition with the given name.
41      * You MUST call setFieldTypes afterward, even if with an empty map.  (Yuck.)
42      */
AnnotationDef(String name)43     public AnnotationDef(String name) {
44         super("annotation: " + name);
45         assert name != null;
46         this.name = name;
47     }
48 
49     @Override
clone()50     public AnnotationDef clone() {
51         throw new UnsupportedOperationException("can't duplicate AnnotationDefs");
52     }
53 
54     // Problem:  I am not sure how to handle circularities (annotations meta-annotated with themselves)
55     /**
56      * Look up an AnnotationDefs in adefs.
57      * If not found, read from a class and insert in adefs.
58      */
fromClass(Class<? extends java.lang.annotation.Annotation> annoType, Map<String,AnnotationDef> adefs)59     public static AnnotationDef fromClass(Class<? extends java.lang.annotation.Annotation> annoType, Map<String,AnnotationDef> adefs) {
60         String name = annoType.getName();
61         assert name != null;
62 
63         if (adefs.containsKey(name)) {
64             return adefs.get(name);
65         }
66 
67         Map<String,AnnotationFieldType> fieldTypes = new LinkedHashMap<String,AnnotationFieldType>();
68         for (Method m : annoType.getDeclaredMethods()) {
69             AnnotationFieldType aft = AnnotationFieldType.fromClass(m.getReturnType(), adefs);
70             fieldTypes.put(m.getName(), aft);
71         }
72 
73         AnnotationDef result = new AnnotationDef(name, Annotations.noAnnotations, fieldTypes);
74         adefs.put(name, result);
75 
76         // An annotation can be meta-annotated with itself, so add
77         // meta-annotations after putting the annotation in the map.
78         java.lang.annotation.Annotation[] jannos;
79         try {
80             jannos = annoType.getDeclaredAnnotations();
81         } catch (Exception e) {
82             printClasspath();
83             throw new Error("Exception in anno.getDeclaredAnnotations() for anno = " + annoType, e);
84         }
85         for (java.lang.annotation.Annotation ja : jannos) {
86             result.tlAnnotationsHere.add(new Annotation(ja, adefs));
87         }
88 
89         return result;
90     }
91 
AnnotationDef(String name, Set<Annotation> tlAnnotationsHere)92     public AnnotationDef(String name, Set<Annotation> tlAnnotationsHere) {
93         super("annotation: " + name);
94         assert name != null;
95         this.name = name;
96         if (tlAnnotationsHere != null) {
97             this.tlAnnotationsHere.addAll(tlAnnotationsHere);
98         }
99     }
100 
AnnotationDef(String name, Set<Annotation> tlAnnotationsHere, Map<String, ? extends AnnotationFieldType> fieldTypes)101     public AnnotationDef(String name, Set<Annotation> tlAnnotationsHere, Map<String, ? extends AnnotationFieldType> fieldTypes) {
102         this(name, tlAnnotationsHere);
103         setFieldTypes(fieldTypes);
104     }
105 
106     /**
107      * Constructs an annotation definition with the given name and field types.
108      * The field type map is copied and then wrapped in an
109      * {@linkplain Collections#unmodifiableMap unmodifiable map} to protect the
110      * immutability of the annotation definition.
111      * You MUST call setFieldTypes afterward, even if with an empty map.  (Yuck.)
112      */
setFieldTypes(Map<String, ? extends AnnotationFieldType> fieldTypes)113     public void setFieldTypes(Map<String, ? extends AnnotationFieldType> fieldTypes) {
114         this.fieldTypes = Collections.unmodifiableMap(
115                 new LinkedHashMap<String, AnnotationFieldType>(fieldTypes)
116                 );
117     }
118 
119 
120     /**
121      * The retention policy for annotations of this type.
122      * If non-null, this is called a "top-level" annotation definition.
123      * It may be null for annotations that are used only as a field of other
124      * annotations.
125      */
retention()126     public /*@Nullable*/ RetentionPolicy retention() {
127         if (tlAnnotationsHere.contains(Annotations.aRetentionClass)) {
128             return RetentionPolicy.CLASS;
129         } else if (tlAnnotationsHere.contains(Annotations.aRetentionRuntime)) {
130             return RetentionPolicy.RUNTIME;
131         } else if (tlAnnotationsHere.contains(Annotations.aRetentionSource)) {
132             return RetentionPolicy.SOURCE;
133         } else {
134             return null;
135         }
136     }
137 
138     /**
139      * True if this is a type annotation (was meta-annotated
140      * with @Target(ElementType.TYPE_USE) or @TypeQualifier).
141      */
isTypeAnnotation()142     public boolean isTypeAnnotation() {
143         return (tlAnnotationsHere.contains(Annotations.aTargetTypeUse)
144                 || tlAnnotationsHere.contains(Annotations.aTypeQualifier));
145     }
146 
147 
148     /**
149      * This {@link AnnotationDef} equals <code>o</code> if and only if
150      * <code>o</code> is another nonnull {@link AnnotationDef} and
151      * <code>this</code> and <code>o</code> define annotation types of the same
152      * name with the same field names and types.
153      */
154     @Override
equals(Object o)155     public boolean equals(Object o) {
156         return o instanceof AnnotationDef
157             && ((AnnotationDef) o).equals(this);
158     }
159 
160     /**
161      * Returns whether this {@link AnnotationDef} equals <code>o</code>; a
162      * slightly faster variant of {@link #equals(Object)} for when the argument
163      * is statically known to be another nonnull {@link AnnotationDef}.
164      */
equals(AnnotationDef o)165     public boolean equals(AnnotationDef o) {
166         boolean sameName = name.equals(o.name);
167         boolean sameMetaAnnotations = equalsElement(o);
168         boolean sameFieldTypes = fieldTypes.equals(o.fieldTypes);
169         // Can be useful for debugging
170         if (false && sameName && (! (sameMetaAnnotations
171                             && sameFieldTypes))) {
172             String message = String.format("Warning: incompatible definitions of annotation %s%n  %s%n  %s%n",
173                                            name, this, o);
174             new Exception(message).printStackTrace(System.out);
175         }
176         return sameName
177             && sameMetaAnnotations
178             && sameFieldTypes;
179     }
180 
181     /**
182      * {@inheritDoc}
183      */
184     @Override
hashCode()185     public int hashCode() {
186         return name.hashCode()
187             // Omit tlAnnotationsHere, becase it should be unique and, more
188             // importantly, including it causes an infinite loop.
189             // + tlAnnotationsHere.hashCode()
190             + fieldTypes.hashCode();
191     }
192 
193     /**
194      * Returns an <code>AnnotationDef</code> containing all the information
195      * from both arguments, or <code>null</code> if the two arguments
196      * contradict each other.  Currently this just
197      * {@linkplain AnnotationFieldType#unify unifies the field types}
198      * to handle arrays of unknown element type, which can arise via
199      * {@link AnnotationBuilder#addEmptyArrayField}.
200      */
unify(AnnotationDef def1, AnnotationDef def2)201     public static AnnotationDef unify(AnnotationDef def1,
202             AnnotationDef def2) {
203         // if (def1.name.equals(def2.name)
204         //     && def1.fieldTypes.keySet().equals(def2.fieldTypes.keySet())
205         //     && ! def1.equalsElement(def2)) {
206         //     throw new Error(String.format("Unifiable except for meta-annotations:%n  %s%n  %s%n",
207         //                                   def1, def2));
208         // }
209         if (def1.equals(def2)) {
210             return def1;
211         } else if (def1.name.equals(def2.name)
212                  && def1.equalsElement(def2)
213                  && def1.fieldTypes.keySet().equals(def2.fieldTypes.keySet())) {
214             Map<String, AnnotationFieldType> newFieldTypes
215                 = new LinkedHashMap<String, AnnotationFieldType>();
216             for (String fieldName : def1.fieldTypes.keySet()) {
217                 AnnotationFieldType aft1 = def1.fieldTypes.get(fieldName);
218                 AnnotationFieldType aft2 = def2.fieldTypes.get(fieldName);
219                 AnnotationFieldType uaft = AnnotationFieldType.unify(aft1, aft2);
220                 if (uaft == null) {
221                     return null;
222                 } else {
223                     newFieldTypes.put(fieldName, uaft);
224                 }
225             }
226             return new AnnotationDef(def1.name, def1.tlAnnotationsHere, newFieldTypes);
227         } else {
228             return null;
229         }
230     }
231 
232     @Override
toString()233     public String toString() {
234         StringBuilder sb = new StringBuilder();
235         sb.append("[");
236         // Not: sb.append(((AElement) this).toString());
237         // because it causes an infinite loop.
238         boolean first;
239         first = true;
240         for (Annotation a : tlAnnotationsHere) {
241             if (!first) {
242                 sb.append(" ");
243             } else {
244                 first=false;
245             }
246             sb.append(a);
247         }
248         sb.append("] ");
249         sb.append("@");
250         sb.append(name);
251         sb.append("(");
252         first = true;
253         for (Map.Entry<String, AnnotationFieldType> entry : fieldTypes.entrySet()) {
254             if (!first) {
255                 sb.append(",");
256             } else {
257                 first = false;
258             }
259             sb.append(entry.getValue().toString());
260             sb.append(" ");
261             sb.append(entry.getKey());
262         }
263         sb.append(")");
264         return sb.toString();
265     }
266 
printClasspath()267     public static void printClasspath() {
268         System.out.println();
269         System.out.println("Classpath:");
270         StringTokenizer tokenizer =
271             new StringTokenizer(System.getProperty("java.class.path"), File.pathSeparator);
272         while (tokenizer.hasMoreTokens()) {
273             String cpelt = tokenizer.nextToken();
274             boolean exists = new File(cpelt).exists();
275             if (! exists) {
276                 System.out.print(" non-existent:");
277             }
278             System.out.println("  " + cpelt);
279         }
280     }
281 
282 }
283