package org.checkerframework.framework.type.visitor;
import java.util.HashSet;
import java.util.Set;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.util.PluginUtil;
import org.checkerframework.javacutil.AnnotationUtils;
/**
* IMPORTANT: DO NOT USE VisitHistory FOR VISITORS THAT UPDATE AN ANNOTATED TYPE MIRROR'S
* ANNOTATIONS OR YOU VIOLATE THE CONTRACT OF equals/Hashcode. THIS CLASS IS DESIGNED FOR USE WITH
* The DefaultTypeHierarchy AND RELATED CLASSES
*
* <p>VisitHistory keeps track of all visits and allows clients of this class to check whether or
* not they have visited a pair of AnnotatedTypeMirrors already. This is necessary in order to halt
* visiting on recursive bounds. Normally, this could be done using a HashSet; this class is
* necessary because of two properties of wildcards:
*
* <ol>
* <li>there are times when we encounter wildcards that have recursive bounds.
* <li>Since getEffectiveSuperBound and getEffectiveExtendsBound copy the bound before returning
* it, two calls that should return the same bound will NOT return objects that are .equals to
* each other. E.g.
* <pre>{@code
* AnnotatedWildcardType wc = ...
* wc.getEffectiveSuperBound().equals(wc.getEffectiveSuperBound());
* // the above line will return false if the super bound is a wildcard
* // because two wildcards are .equals only if they are also referentially (==) equal
* // and each call to getEffectiveSuperBound returns a copy of the original bound
* }</pre>
* </ol>
*
* When we encounter types with property 1, property 2 ensures we cannot stop recursively comparing
* the bounds because the equals method will not return true when we encounter a copy of a bound we
* have already explored. This class defines a "Visit" inner class which consists of a pair of
* AnnotatedTypeMirrors. Its equalityCompare method compares AnnotatedTypeMirrors in a way that
* identifies wildcards that have already been compared.
*/
public class VisitHistory {
private final Set<Visit> visited;
public VisitHistory() {
this.visited = new HashSet<>();
}
public void clear() {
visited.clear();
}
/** Add a visit for type1 and type2. */
public void add(final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
this.visited.add(new Visit(type1, type2));
}
/**
* Returns true if type1 and type2 (or an equivalent pair) have been passed to the add method
* previously.
*
* @return true if an equivalent pair has already been added to the history
*/
public boolean contains(final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
return this.visited.contains(new Visit(type1, type2));
}
@Override
public String toString() {
return "VisitHistory( " + PluginUtil.join(", ", visited) + " )";
}
/**
* Visit represents a pair of types that have been added to the history. See class note for
* VisitHistory (at the top of this file)
*/
private static class Visit {
public final AnnotatedTypeMirror type1;
public final AnnotatedTypeMirror type2;
private Visit(final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
this.type1 = type1;
this.type2 = type2;
}
@Override
public int hashCode() {
return (type1 != null ? 31 * type1.hashCode() : 0)
+ (type2 != null ? 31 * type2.hashCode() : 1);
}
@Override
public boolean equals(final Object oThat) {
if (oThat == null || !oThat.getClass().equals(this.getClass())) {
return false;
}
final Visit that = (Visit) oThat;
return equalityCompare(type1, that.type1) && equalityCompare(type2, that.type2);
}
/**
* This is a replacement for AnnotatedTypeMirror.equals, read the class comment for
* VisitHistory
*/
private boolean equalityCompare(
final AnnotatedTypeMirror thisType, final AnnotatedTypeMirror thatType) {
if (thisType == null) {
return thatType == null;
}
if (thatType == null) {
return false;
}
if (!thisType.getClass().equals(thatType.getClass())) {
return false;
}
if (thisType.getClass().equals(AnnotatedTypeMirror.AnnotatedWildcardType.class)) {
if (thisType.getUnderlyingType().equals(thatType.getUnderlyingType())) {
//TODO: Investigate WHY we get wildcards that are essentially recursive since I
//TODO: don't think we can write these wildcards. Perhaps it is related to our lack of
//TODO: capture conversion or inferring void methods
return true; // Handles the case of recursive wildcard types
}
if (!AnnotationUtils.areSame(
thisType.getAnnotations(), thatType.getAnnotations())) {
return false;
} else {
//TODO: EXPLAIN CASCADING .contains if we don't do it this way
final AnnotatedWildcardType thisWc = (AnnotatedWildcardType) thisType;
final AnnotatedWildcardType thatWc = (AnnotatedWildcardType) thatType;
return equalityCompare(thisWc.getExtendsBound(), thatWc.getExtendsBound())
&& equalityCompare(thisWc.getSuperBound(), thatWc.getSuperBound());
}
}
return thisType.equals(thatType);
}
@Override
public String toString() {
return "( " + type1 + " => " + type2 + " )";
}
}
}