Java程序  |  283行  |  10.14 KB

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);
        }
    }

}