Home | History | Annotate | Download | only in el
      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      */
     43     public AnnotationDef(String name) {
     44         super("annotation: " + name);
     45         assert name != null;
     46         this.name = name;
     47     }
     48 
     49     @Override
     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      */
     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 
     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 
    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      */
    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      */
    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      */
    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
    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      */
    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
    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      */
    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
    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 
    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