public ReferenceValue generalize(ReferenceValue other) {
   return other.generalize(this);
 }
 public int equal(ReferenceValue other) {
   return other.equal(this);
 }
  /** 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);
  }