aboutsummaryrefslogtreecommitdiff
path: root/scene-lib/src/annotations/el/AnnotationDef.java
diff options
context:
space:
mode:
Diffstat (limited to 'scene-lib/src/annotations/el/AnnotationDef.java')
-rw-r--r--scene-lib/src/annotations/el/AnnotationDef.java282
1 files changed, 282 insertions, 0 deletions
diff --git a/scene-lib/src/annotations/el/AnnotationDef.java b/scene-lib/src/annotations/el/AnnotationDef.java
new file mode 100644
index 0000000..323f862
--- /dev/null
+++ b/scene-lib/src/annotations/el/AnnotationDef.java
@@ -0,0 +1,282 @@
+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);
+ }
+ }
+
+}