/** Returns if the number of superclasses of the given class in the given set of classes. */
  private int superClassCount(Clazz subClass, Set classes) {
    int count = 0;

    Iterator iterator = classes.iterator();

    while (iterator.hasNext()) {
      Clazz clazz = (Clazz) iterator.next();
      if (subClass.extendsOrImplements(clazz)) {
        count++;
      }
    }

    return count;
  }
  public int instanceOf(String otherType, Clazz otherReferencedClass) {
    String thisType = this.type;

    // If this type is null, it is never an instance of any class.
    if (thisType == null) {
      return NEVER;
    }

    // Start taking into account the type dimensions.
    int thisDimensionCount = ClassUtil.internalArrayTypeDimensionCount(thisType);
    int otherDimensionCount = ClassUtil.internalArrayTypeDimensionCount(otherType);
    int commonDimensionCount = Math.min(thisDimensionCount, otherDimensionCount);

    // Strip any common array prefixes.
    thisType = thisType.substring(commonDimensionCount);
    otherType = otherType.substring(commonDimensionCount);

    // If either stripped type is a primitive type, we can tell right away.
    if (commonDimensionCount > 0
        && (ClassUtil.isInternalPrimitiveType(thisType.charAt(0))
            || ClassUtil.isInternalPrimitiveType(otherType.charAt(0)))) {
      return !thisType.equals(otherType) ? NEVER : mayBeNull ? MAYBE : ALWAYS;
    }

    // Strip the class type prefix and suffix of this type, if any.
    if (thisDimensionCount == commonDimensionCount) {
      thisType = ClassUtil.internalClassNameFromClassType(thisType);
    }

    // Strip the class type prefix and suffix of the other type, if any.
    if (otherDimensionCount == commonDimensionCount) {
      otherType = ClassUtil.internalClassNameFromClassType(otherType);
    }

    // If this type is an array type, and the other type is not
    // java.lang.Object, java.lang.Cloneable, or java.io.Serializable,
    // this type can never be an instance.
    if (thisDimensionCount > otherDimensionCount
        && !ClassUtil.isInternalArrayInterfaceName(otherType)) {
      return NEVER;
    }

    // If the other type is an array type, and this type is not
    // java.lang.Object, java.lang.Cloneable, or java.io.Serializable,
    // this type can never be an instance.
    if (thisDimensionCount < otherDimensionCount
        && !ClassUtil.isInternalArrayInterfaceName(thisType)) {
      return NEVER;
    }

    // If this type may be null, it might not be an instance of any class.
    if (mayBeNull) {
      return MAYBE;
    }

    // If this type is equal to the other type, or if the other type is
    // java.lang.Object, this type is always an instance.
    if (thisType.equals(otherType) || ClassConstants.NAME_JAVA_LANG_OBJECT.equals(otherType)) {
      return ALWAYS;
    }

    // If this type is an array type, it's ok.
    if (thisDimensionCount > otherDimensionCount) {
      return ALWAYS;
    }

    // If the other type is an array type, it might be ok.
    if (thisDimensionCount < otherDimensionCount) {
      return MAYBE;
    }

    // If the value extends the type, we're sure.
    return referencedClass != null
            && otherReferencedClass != null
            && referencedClass.extendsOrImplements(otherReferencedClass)
        ? ALWAYS
        : MAYBE;
  }
  public ReferenceValue generalize(TypedReferenceValue other) {
    // If both types are identical, the generalization is the same too.
    if (this.equals(other)) {
      return this;
    }

    String thisType = this.type;
    String otherType = other.type;

    // If both types are nul, the generalization is null too.
    if (thisType == null && otherType == null) {
      return ValueFactory.REFERENCE_VALUE_NULL;
    }

    // If this type is null, the generalization is the other type, maybe null.
    if (thisType == null) {
      return other.generalizeMayBeNull(true);
    }

    // If the other type is null, the generalization is this type, maybe null.
    if (otherType == null) {
      return this.generalizeMayBeNull(true);
    }

    boolean mayBeNull = this.mayBeNull || other.mayBeNull;

    // If the two types are equal, the generalization remains the same, maybe null.
    if (thisType.equals(otherType)) {
      return typedReferenceValue(this, mayBeNull);
    }

    // Start taking into account the type dimensions.
    int thisDimensionCount = ClassUtil.internalArrayTypeDimensionCount(thisType);
    int otherDimensionCount = ClassUtil.internalArrayTypeDimensionCount(otherType);
    int commonDimensionCount = Math.min(thisDimensionCount, otherDimensionCount);

    if (thisDimensionCount == otherDimensionCount) {
      // See if we can take into account the referenced classes.
      Clazz thisReferencedClass = this.referencedClass;
      Clazz otherReferencedClass = other.referencedClass;

      if (thisReferencedClass != null && otherReferencedClass != null) {
        // Is one class simply an extension of the other one?
        if (thisReferencedClass.extendsOrImplements(otherReferencedClass)) {
          return typedReferenceValue(other, mayBeNull);
        }

        if (otherReferencedClass.extendsOrImplements(thisReferencedClass)) {
          return typedReferenceValue(this, mayBeNull);
        }

        // Do the classes have a non-trivial common superclass?
        Clazz commonClass = findCommonClass(thisReferencedClass, otherReferencedClass, false);

        if (commonClass.getName().equals(ClassConstants.NAME_JAVA_LANG_OBJECT)) {
          // Otherwise, do the classes have a common interface?
          Clazz commonInterface = findCommonClass(thisReferencedClass, otherReferencedClass, true);
          if (commonInterface != null) {
            commonClass = commonInterface;
          }
        }

        return new TypedReferenceValue(
            commonDimensionCount == 0
                ? commonClass.getName()
                : ClassUtil.internalArrayTypeFromClassName(
                    commonClass.getName(), commonDimensionCount),
            commonClass,
            mayBeNull);
      }
    } else if (thisDimensionCount > otherDimensionCount) {
      // See if the other type is an interface type of arrays.
      if (ClassUtil.isInternalArrayInterfaceName(
          ClassUtil.internalClassNameFromClassType(otherType))) {
        return typedReferenceValue(other, mayBeNull);
      }
    } else if (thisDimensionCount < otherDimensionCount) {
      // See if this type is an interface type of arrays.
      if (ClassUtil.isInternalArrayInterfaceName(
          ClassUtil.internalClassNameFromClassType(thisType))) {
        return typedReferenceValue(this, mayBeNull);
      }
    }

    // Reduce the common dimension count if either type is an array of
    // primitives type of this dimension.
    if (commonDimensionCount > 0
            && (ClassUtil.isInternalPrimitiveType(otherType.charAt(commonDimensionCount)))
        || ClassUtil.isInternalPrimitiveType(thisType.charAt(commonDimensionCount))) {
      commonDimensionCount--;
    }

    // Fall back on a basic Object or array of Objects type.
    return commonDimensionCount != 0
        ? new TypedReferenceValue(
            ClassUtil.internalArrayTypeFromClassName(
                ClassConstants.NAME_JAVA_LANG_OBJECT, commonDimensionCount),
            null,
            mayBeNull)
        : mayBeNull
            ? ValueFactory.REFERENCE_VALUE_JAVA_LANG_OBJECT_MAYBE_NULL
            : ValueFactory.REFERENCE_VALUE_JAVA_LANG_OBJECT_NOT_NULL;
  }
  /** Returns the generalization of this ReferenceValue and the given other ReferenceValue. */
  public ReferenceValue generalize(ReferenceValue other) {
    // If both types are identical, the generalization is the same too.
    if (this.equals(other)) {
      return this;
    }

    String thisType = this.type;
    String otherType = other.type;

    // If both types are nul, the generalization is null too.
    if (thisType == null && otherType == null) {
      return ValueFactory.REFERENCE_VALUE_NULL;
    }

    // If this type is null, the generalization is the other type, maybe null.
    if (thisType == null) {
      return other.generalizeMayBeNull(true);
    }

    // If the other type is null, the generalization is this type, maybe null.
    if (otherType == null) {
      return this.generalizeMayBeNull(true);
    }

    boolean mayBeNull = this.mayBeNull || other.mayBeNull;

    // If the two types are equal, the generalization remains the same, maybe null.
    if (thisType.equals(otherType)) {
      return this.generalizeMayBeNull(mayBeNull);
    }

    // Start taking into account the type dimensions.
    int thisDimensionCount = ClassUtil.internalArrayTypeDimensionCount(thisType);
    int otherDimensionCount = ClassUtil.internalArrayTypeDimensionCount(otherType);
    int commonDimensionCount = Math.min(thisDimensionCount, otherDimensionCount);

    if (thisDimensionCount == otherDimensionCount) {
      // See if we can take into account the referenced classes.
      Clazz thisReferencedClass = this.referencedClass;
      Clazz otherReferencedClass = other.referencedClass;

      if (thisReferencedClass != null && otherReferencedClass != null) {
        if (thisReferencedClass.extendsOrImplements(otherReferencedClass)) {
          return other.generalizeMayBeNull(mayBeNull);
        }

        if (otherReferencedClass.extendsOrImplements(thisReferencedClass)) {
          return this.generalizeMayBeNull(mayBeNull);
        }

        // Collect the superclasses and interfaces of this class.
        Set thisSuperClasses = new HashSet();
        thisReferencedClass.hierarchyAccept(
            false, true, true, false, new ClassCollector(thisSuperClasses));

        int thisSuperClassesCount = thisSuperClasses.size();
        if (thisSuperClassesCount == 0 && thisReferencedClass.getSuperName() != null) {
          throw new IllegalArgumentException(
              "Can't find any super classes of ["
                  + thisType
                  + "] (not even immediate super class ["
                  + thisReferencedClass.getSuperName()
                  + "])");
        }

        // Collect the superclasses and interfaces of the other class.
        Set otherSuperClasses = new HashSet();
        otherReferencedClass.hierarchyAccept(
            false, true, true, false, new ClassCollector(otherSuperClasses));

        int otherSuperClassesCount = otherSuperClasses.size();
        if (otherSuperClassesCount == 0 && otherReferencedClass.getSuperName() != null) {
          throw new IllegalArgumentException(
              "Can't find any super classes of ["
                  + otherType
                  + "] (not even immediate super class ["
                  + otherReferencedClass.getSuperName()
                  + "])");
        }

        if (DEBUG) {
          System.out.println(
              "ReferenceValue.generalize this ["
                  + thisReferencedClass.getName()
                  + "] with other ["
                  + otherReferencedClass.getName()
                  + "]");
          System.out.println("  This super classes:  " + thisSuperClasses);
          System.out.println("  Other super classes: " + otherSuperClasses);
        }

        // Find the common superclasses.
        thisSuperClasses.retainAll(otherSuperClasses);

        if (DEBUG) {
          System.out.println("  Common super classes: " + thisSuperClasses);
        }

        // Find a class that is a subclass of all common superclasses,
        // or that at least has the maximum number of common superclasses.
        Clazz commonClass = null;

        int maximumSuperClassCount = -1;

        // Go over all common superclasses to find it. In case of
        // multiple subclasses, keep the lowest one alphabetically,
        // in order to ensure that the choice is deterministic.
        Iterator commonSuperClasses = thisSuperClasses.iterator();
        while (commonSuperClasses.hasNext()) {
          Clazz commonSuperClass = (Clazz) commonSuperClasses.next();

          int superClassCount = superClassCount(commonSuperClass, thisSuperClasses);
          if (maximumSuperClassCount < superClassCount
              || (maximumSuperClassCount == superClassCount
                  && commonClass != null
                  && commonClass.getName().compareTo(commonSuperClass.getName()) > 0)) {
            commonClass = commonSuperClass;
            maximumSuperClassCount = superClassCount;
          }
        }

        if (commonClass == null) {
          throw new IllegalArgumentException(
              "Can't find common super class of ["
                  + thisType
                  + "] (with "
                  + thisSuperClassesCount
                  + " known super classes) and ["
                  + otherType
                  + "] (with "
                  + otherSuperClassesCount
                  + " known super classes)");
        }

        if (DEBUG) {
          System.out.println("  Best common class: [" + commonClass.getName() + "]");
        }

        // TODO: Handle more difficult cases, with multiple global subclasses.

        return new ReferenceValue(
            commonDimensionCount == 0
                ? commonClass.getName()
                : ClassUtil.internalArrayTypeFromClassName(
                    commonClass.getName(), commonDimensionCount),
            commonClass,
            mayBeNull);
      }
    } else if (thisDimensionCount > otherDimensionCount) {
      // See if the other type is an interface type of arrays.
      if (ClassUtil.isInternalArrayInterfaceName(
          ClassUtil.internalClassNameFromClassType(otherType))) {
        return other.generalizeMayBeNull(mayBeNull);
      }
    } else if (thisDimensionCount < otherDimensionCount) {
      // See if this type is an interface type of arrays.
      if (ClassUtil.isInternalArrayInterfaceName(
          ClassUtil.internalClassNameFromClassType(thisType))) {
        return this.generalizeMayBeNull(mayBeNull);
      }
    }

    // Reduce the common dimension count if either type is an array of
    // primitives type of this dimension.
    if (commonDimensionCount > 0
            && (ClassUtil.isInternalPrimitiveType(otherType.charAt(commonDimensionCount)))
        || ClassUtil.isInternalPrimitiveType(thisType.charAt(commonDimensionCount))) {
      commonDimensionCount--;
    }

    // Fall back on a basic Object or array of Objects type.
    return commonDimensionCount == 0
        ? mayBeNull
            ? ValueFactory.REFERENCE_VALUE_JAVA_LANG_OBJECT_MAYBE_NULL
            : ValueFactory.REFERENCE_VALUE_JAVA_LANG_OBJECT_NOT_NULL
        : new ReferenceValue(
            ClassUtil.internalArrayTypeFromClassName(
                ClassConstants.INTERNAL_NAME_JAVA_LANG_OBJECT, commonDimensionCount),
            null,
            mayBeNull);
  }