| package annotations.el; |
| |
| /*>>> |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| */ |
| |
| import java.io.File; |
| import java.util.*; |
| import java.lang.annotation.RetentionPolicy; |
| import java.lang.reflect.Method; |
| |
| import annotations.el.AElement; |
| import annotations.AnnotationBuilder; |
| import annotations.field.*; |
| import annotations.Annotation; |
| import annotations.Annotations; |
| |
| /** |
| * An annotation type definition, consisting of the annotation name, |
| * its meta-annotations, and its field names and |
| * types. <code>AnnotationDef</code>s are immutable. An AnnotationDef with |
| * a non-null retention policy is called a "top-level annotation definition". |
| */ |
| public final class AnnotationDef extends AElement { |
| |
| /** |
| * The binary name of the annotation type, such as |
| * "foo.Bar$Baz" for inner class Baz in class Bar in package foo. |
| */ |
| public final String name; |
| |
| /** |
| * A map of the names of this annotation type's fields to their types. Since |
| * {@link AnnotationDef}s are immutable, attempting to modify this |
| * map will result in an exception. |
| */ |
| public Map<String, AnnotationFieldType> fieldTypes; |
| |
| /** |
| * Constructs an annotation definition with the given name. |
| * You MUST call setFieldTypes afterward, even if with an empty map. (Yuck.) |
| */ |
| public AnnotationDef(String name) { |
| super("annotation: " + name); |
| assert name != null; |
| this.name = name; |
| } |
| |
| @Override |
| public AnnotationDef clone() { |
| throw new UnsupportedOperationException("can't duplicate AnnotationDefs"); |
| } |
| |
| // Problem: I am not sure how to handle circularities (annotations meta-annotated with themselves) |
| /** |
| * Look up an AnnotationDefs in adefs. |
| * If not found, read from a class and insert in adefs. |
| */ |
| public static AnnotationDef fromClass(Class<? extends java.lang.annotation.Annotation> annoType, Map<String,AnnotationDef> adefs) { |
| String name = annoType.getName(); |
| assert name != null; |
| |
| if (adefs.containsKey(name)) { |
| return adefs.get(name); |
| } |
| |
| Map<String,AnnotationFieldType> fieldTypes = new LinkedHashMap<String,AnnotationFieldType>(); |
| for (Method m : annoType.getDeclaredMethods()) { |
| AnnotationFieldType aft = AnnotationFieldType.fromClass(m.getReturnType(), adefs); |
| fieldTypes.put(m.getName(), aft); |
| } |
| |
| AnnotationDef result = new AnnotationDef(name, Annotations.noAnnotations, fieldTypes); |
| adefs.put(name, result); |
| |
| // An annotation can be meta-annotated with itself, so add |
| // meta-annotations after putting the annotation in the map. |
| java.lang.annotation.Annotation[] jannos; |
| try { |
| jannos = annoType.getDeclaredAnnotations(); |
| } catch (Exception e) { |
| printClasspath(); |
| throw new Error("Exception in anno.getDeclaredAnnotations() for anno = " + annoType, e); |
| } |
| for (java.lang.annotation.Annotation ja : jannos) { |
| result.tlAnnotationsHere.add(new Annotation(ja, adefs)); |
| } |
| |
| return result; |
| } |
| |
| public AnnotationDef(String name, Set<Annotation> tlAnnotationsHere) { |
| super("annotation: " + name); |
| assert name != null; |
| this.name = name; |
| if (tlAnnotationsHere != null) { |
| this.tlAnnotationsHere.addAll(tlAnnotationsHere); |
| } |
| } |
| |
| public AnnotationDef(String name, Set<Annotation> tlAnnotationsHere, Map<String, ? extends AnnotationFieldType> fieldTypes) { |
| this(name, tlAnnotationsHere); |
| setFieldTypes(fieldTypes); |
| } |
| |
| /** |
| * Constructs an annotation definition with the given name and field types. |
| * The field type map is copied and then wrapped in an |
| * {@linkplain Collections#unmodifiableMap unmodifiable map} to protect the |
| * immutability of the annotation definition. |
| * You MUST call setFieldTypes afterward, even if with an empty map. (Yuck.) |
| */ |
| public void setFieldTypes(Map<String, ? extends AnnotationFieldType> fieldTypes) { |
| this.fieldTypes = Collections.unmodifiableMap( |
| new LinkedHashMap<String, AnnotationFieldType>(fieldTypes) |
| ); |
| } |
| |
| |
| /** |
| * The retention policy for annotations of this type. |
| * If non-null, this is called a "top-level" annotation definition. |
| * It may be null for annotations that are used only as a field of other |
| * annotations. |
| */ |
| public /*@Nullable*/ RetentionPolicy retention() { |
| if (tlAnnotationsHere.contains(Annotations.aRetentionClass)) { |
| return RetentionPolicy.CLASS; |
| } else if (tlAnnotationsHere.contains(Annotations.aRetentionRuntime)) { |
| return RetentionPolicy.RUNTIME; |
| } else if (tlAnnotationsHere.contains(Annotations.aRetentionSource)) { |
| return RetentionPolicy.SOURCE; |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * True if this is a type annotation (was meta-annotated |
| * with @Target(ElementType.TYPE_USE) or @TypeQualifier). |
| */ |
| public boolean isTypeAnnotation() { |
| return (tlAnnotationsHere.contains(Annotations.aTargetTypeUse) |
| || tlAnnotationsHere.contains(Annotations.aTypeQualifier)); |
| } |
| |
| |
| /** |
| * This {@link AnnotationDef} equals <code>o</code> if and only if |
| * <code>o</code> is another nonnull {@link AnnotationDef} and |
| * <code>this</code> and <code>o</code> define annotation types of the same |
| * name with the same field names and types. |
| */ |
| @Override |
| public boolean equals(Object o) { |
| return o instanceof AnnotationDef |
| && ((AnnotationDef) o).equals(this); |
| } |
| |
| /** |
| * Returns whether this {@link AnnotationDef} equals <code>o</code>; a |
| * slightly faster variant of {@link #equals(Object)} for when the argument |
| * is statically known to be another nonnull {@link AnnotationDef}. |
| */ |
| public boolean equals(AnnotationDef o) { |
| boolean sameName = name.equals(o.name); |
| boolean sameMetaAnnotations = equalsElement(o); |
| boolean sameFieldTypes = fieldTypes.equals(o.fieldTypes); |
| // Can be useful for debugging |
| if (false && sameName && (! (sameMetaAnnotations |
| && sameFieldTypes))) { |
| String message = String.format("Warning: incompatible definitions of annotation %s%n %s%n %s%n", |
| name, this, o); |
| new Exception(message).printStackTrace(System.out); |
| } |
| return sameName |
| && sameMetaAnnotations |
| && sameFieldTypes; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public int hashCode() { |
| return name.hashCode() |
| // Omit tlAnnotationsHere, becase it should be unique and, more |
| // importantly, including it causes an infinite loop. |
| // + tlAnnotationsHere.hashCode() |
| + fieldTypes.hashCode(); |
| } |
| |
| /** |
| * Returns an <code>AnnotationDef</code> containing all the information |
| * from both arguments, or <code>null</code> if the two arguments |
| * contradict each other. Currently this just |
| * {@linkplain AnnotationFieldType#unify unifies the field types} |
| * to handle arrays of unknown element type, which can arise via |
| * {@link AnnotationBuilder#addEmptyArrayField}. |
| */ |
| public static AnnotationDef unify(AnnotationDef def1, |
| AnnotationDef def2) { |
| // if (def1.name.equals(def2.name) |
| // && def1.fieldTypes.keySet().equals(def2.fieldTypes.keySet()) |
| // && ! def1.equalsElement(def2)) { |
| // throw new Error(String.format("Unifiable except for meta-annotations:%n %s%n %s%n", |
| // def1, def2)); |
| // } |
| if (def1.equals(def2)) { |
| return def1; |
| } else if (def1.name.equals(def2.name) |
| && def1.equalsElement(def2) |
| && def1.fieldTypes.keySet().equals(def2.fieldTypes.keySet())) { |
| Map<String, AnnotationFieldType> newFieldTypes |
| = new LinkedHashMap<String, AnnotationFieldType>(); |
| for (String fieldName : def1.fieldTypes.keySet()) { |
| AnnotationFieldType aft1 = def1.fieldTypes.get(fieldName); |
| AnnotationFieldType aft2 = def2.fieldTypes.get(fieldName); |
| AnnotationFieldType uaft = AnnotationFieldType.unify(aft1, aft2); |
| if (uaft == null) { |
| return null; |
| } else { |
| newFieldTypes.put(fieldName, uaft); |
| } |
| } |
| return new AnnotationDef(def1.name, def1.tlAnnotationsHere, newFieldTypes); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("["); |
| // Not: sb.append(((AElement) this).toString()); |
| // because it causes an infinite loop. |
| boolean first; |
| first = true; |
| for (Annotation a : tlAnnotationsHere) { |
| if (!first) { |
| sb.append(" "); |
| } else { |
| first=false; |
| } |
| sb.append(a); |
| } |
| sb.append("] "); |
| sb.append("@"); |
| sb.append(name); |
| sb.append("("); |
| first = true; |
| for (Map.Entry<String, AnnotationFieldType> entry : fieldTypes.entrySet()) { |
| if (!first) { |
| sb.append(","); |
| } else { |
| first = false; |
| } |
| sb.append(entry.getValue().toString()); |
| sb.append(" "); |
| sb.append(entry.getKey()); |
| } |
| sb.append(")"); |
| return sb.toString(); |
| } |
| |
| public static void printClasspath() { |
| System.out.println(); |
| System.out.println("Classpath:"); |
| StringTokenizer tokenizer = |
| new StringTokenizer(System.getProperty("java.class.path"), File.pathSeparator); |
| while (tokenizer.hasMoreTokens()) { |
| String cpelt = tokenizer.nextToken(); |
| boolean exists = new File(cpelt).exists(); |
| if (! exists) { |
| System.out.print(" non-existent:"); |
| } |
| System.out.println(" " + cpelt); |
| } |
| } |
| |
| } |