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