| package annotations.el; |
| |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.Set; |
| |
| import annotations.Annotation; |
| import annotations.util.coll.VivifyingMap; |
| |
| /*>>> |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| */ |
| |
| /** |
| * An <code>AElement</code> represents a Java element and the annotations it |
| * carries. Some <code>AElements</code> may contain others; for example, an |
| * {@link AClass} may contain {@link AMethod}s. Every <code>AElement</code> |
| * usually belongs directly or indirectly to an {@link AScene}. Each subclass |
| * of <code>AElement</code> represents one kind of annotatable element; its |
| * name should make this clear. |
| */ |
| public class AElement implements Cloneable { |
| static <K extends Object> VivifyingMap<K, AElement> newVivifyingLHMap_AE() { |
| return new VivifyingMap<K, AElement>( |
| new LinkedHashMap<K, AElement>()) { |
| @Override |
| public AElement createValueFor(K k) { |
| return new AElement(k); |
| } |
| |
| @Override |
| public boolean subPrune(AElement v) { |
| return v.prune(); |
| } |
| }; |
| } |
| |
| // Different from the above in that the elements are guaranteed to |
| // contain a non-null "type" field. |
| static <K extends Object> VivifyingMap<K, AElement> newVivifyingLHMap_AET() { |
| return new VivifyingMap<K, AElement>( |
| new LinkedHashMap<K, AElement>()) { |
| @Override |
| public AElement createValueFor(K k) { |
| return new AElement(k, true); |
| } |
| |
| @Override |
| public boolean subPrune(AElement v) { |
| return v.prune(); |
| } |
| }; |
| } |
| |
| @SuppressWarnings("unchecked") |
| <K, V extends AElement> void |
| copyMapContents(VivifyingMap<K, V> orig, VivifyingMap<K, V> copy) { |
| for (K key : orig.keySet()) { |
| V val = orig.get(key); |
| copy.put(key, (V) val.clone()); |
| } |
| } |
| |
| /** |
| * The top-level annotations directly on this element. Annotations on |
| * subelements are in those subelements' <code>tlAnnotationsHere</code> |
| * sets, not here. |
| */ |
| public final Set<Annotation> tlAnnotationsHere; |
| |
| /** The type of a field or a method parameter */ |
| public final ATypeElement type; // initialized in constructor |
| |
| public Annotation lookup(String name) { |
| for (Annotation anno : tlAnnotationsHere) { |
| if (anno.def.name.equals(name)) { |
| return anno; |
| } |
| } |
| return null; |
| } |
| |
| // general description of the element |
| final Object description; |
| |
| AElement(Object description) { |
| this(description, false); |
| } |
| |
| AElement(Object description, boolean hasType) { |
| this(description, |
| hasType ? new ATypeElement("type of " + description) : null); |
| } |
| |
| AElement(Object description, ATypeElement type) { |
| tlAnnotationsHere = new LinkedHashSet<Annotation>(); |
| this.description = description; |
| this.type = type; |
| } |
| |
| AElement(AElement elem) { |
| this(elem, elem.type); |
| } |
| |
| AElement(AElement elem, ATypeElement type) { |
| this(elem.description, |
| type == null ? null : type.clone()); |
| tlAnnotationsHere.addAll(elem.tlAnnotationsHere); |
| } |
| |
| AElement(AElement elem, Object description) { |
| this(description, elem.type == null ? null : elem.type.clone()); |
| tlAnnotationsHere.addAll(elem.tlAnnotationsHere); |
| } |
| |
| // Q: Are there any fields other than elements and maps that can't be shared? |
| |
| @Override |
| public AElement clone() { |
| return new AElement(this); |
| } |
| |
| /** |
| * Returns whether this {@link AElement} equals <code>o</code> (see |
| * warnings below). Generally speaking, two {@link AElement}s are equal if |
| * they are of the same type, have the same {@link #tlAnnotationsHere}, and |
| * have recursively equal, corresponding subelements. Two warnings: |
| * |
| * <ul> |
| * <li>While subelement collections usually remember the order in which |
| * subelements were added and regurgitate them in this order, two |
| * {@link AElement}s are equal even if order of subelements differs. |
| * <li>Two {@link AElement}s are unequal if one has a subelement that the |
| * other does not, even if the tree of elements rooted at the subelement |
| * contains no annotations. Thus, if you want to compare the |
| * <em>annotations</em>, you should {@link #prune} both {@link AElement}s |
| * first. |
| * </ul> |
| */ |
| @Override |
| // Was final. Removed that so that AnnotationDef can redefine. |
| public boolean equals(Object o) { |
| return o instanceof AElement |
| && ((AElement) o).equals(this); |
| } |
| |
| /** |
| * Returns whether this {@link AElement} equals <code>o</code>; a |
| * slightly faster variant of {@link #equals(Object)} for when the argument |
| * is statically known to be another nonnull {@link AElement}. |
| */ |
| /* |
| * We need equals to be symmetric and operate correctly over the class |
| * hierarchy. Let x and y be objects of subclasses S and T, respectively, |
| * of AElement. x.equals((AElement) y) shall check that y is an S. If so, |
| * it shall call ((S) y).equalsS(x), which checks that x is a T and then |
| * compares fields. |
| */ |
| public boolean equals(AElement o) { |
| return o.equalsElement(this); |
| } |
| |
| final boolean equalsElement(AElement o) { |
| return o.tlAnnotationsHere.equals(tlAnnotationsHere) |
| && (o.type == null ? type == null : o.type.equals(type)); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public int hashCode() { |
| return getClass().getName().hashCode() + tlAnnotationsHere.hashCode() |
| + (type == null ? 0 : type.hashCode()); |
| } |
| |
| /** |
| * Removes empty subelements of this {@link AElement} depth-first; returns |
| * whether this {@link AElement} is itself empty after pruning. |
| */ |
| // In subclasses, we use & (not &&) because we want complete evaluation: |
| // we should prune everything even if the first subelement is nonempty. |
| public boolean prune() { |
| return tlAnnotationsHere.isEmpty() |
| & (type == null || type.prune()); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("AElement: "); |
| sb.append(description); |
| sb.append(" : "); |
| tlAnnotationsHereFormatted(sb); |
| if (type!=null) { |
| sb.append(' '); |
| sb.append(type.toString()); |
| } |
| // typeargs? |
| return sb.toString(); |
| } |
| |
| public void tlAnnotationsHereFormatted(StringBuilder sb) { |
| boolean first = true; |
| for (Annotation aElement : tlAnnotationsHere) { |
| if (!first) { |
| sb.append(", "); |
| } |
| first = false; |
| sb.append(aElement.toString()); |
| } |
| } |
| |
| public <R, T> R accept(ElementVisitor<R, T> v, T t) { |
| return v.visitElement(this, t); |
| } |
| } |