// old version of compare types
  private boolean compareTypesOld(Type parmType, Type argType) {
    // XXX equality not implemented for GenericObjectType
    // if (parmType.equals(argType)) return true;
    // Compare type signatures instead
    if (GenericUtilities.getString(parmType).equals(GenericUtilities.getString(argType)))
      return true;

    if (parmType instanceof GenericObjectType) {
      GenericObjectType o = (GenericObjectType) parmType;
      if (o.getTypeCategory() == GenericUtilities.TypeCategory.WILDCARD_EXTENDS) {
        return compareTypesOld(o.getExtension(), argType);
      }
    }
    // ignore type variables for now
    if (parmType instanceof GenericObjectType && !((GenericObjectType) parmType).hasParameters())
      return true;
    if (argType instanceof GenericObjectType && !((GenericObjectType) argType).hasParameters())
      return true;

    // Case: Both are generic containers
    if (parmType instanceof GenericObjectType && argType instanceof GenericObjectType) {
      return true;
    } else {
      // Don't consider non reference types (should not be possible)
      if (!(parmType instanceof ReferenceType && argType instanceof ReferenceType)) return true;

      // Don't consider non object types (for now)
      if (!(parmType instanceof ObjectType && argType instanceof ObjectType)) return true;

      // Otherwise, compare base types ignoring generic information
      try {
        return Repository.instanceOf(
            ((ObjectType) argType).getClassName(), ((ObjectType) parmType).getClassName());
      } catch (ClassNotFoundException e) {
      }
    }

    return true;
  }
  private boolean isGenericCollection(ClassDescriptor operandClass) {
    String dottedClassName = operandClass.getDottedClassName();

    if (baseGenericTypes.contains(dottedClassName)) return true;

    String found = null;
    for (String c : baseGenericTypes) {
      if (Subtypes2.instanceOf(operandClass, c)) {
        found = c;
        break;
      }
    }
    if (found == null) return false;
    if (dottedClassName.startsWith("java.util.")
        || dottedClassName.startsWith("com.google.common.collect.")) return true;
    try {
      XClass xclass = Global.getAnalysisCache().getClassAnalysis(XClass.class, operandClass);

      String sig = xclass.getSourceSignature();
      if (sig == null) return false;

      String typeParameter = null;
      List<String> split = GenericUtilities.split(sig, true);
      if (sig.charAt(0) == '<') {
        int end = sig.indexOf(':');
        if (end > 0) typeParameter = sig.substring(1, end);
      }
      if (DEBUG) System.out.println(dottedClassName + " " + typeParameter + " " + split);
      for (String s : split) {
        int i = s.indexOf('<');
        if (i < 0) continue;
        if (s.charAt(0) != 'L') throw new IllegalStateException("unexpected non signature: " + s);
        ClassDescriptor c = DescriptorFactory.createClassDescriptor(s.substring(1, i));
        String superTypeParameter = s.substring(i + 1);
        if (isGenericCollection(c)
            && (typeParameter == null || superTypeParameter.startsWith("T" + typeParameter))) {
          if (DEBUG) System.out.println(operandClass + " is a subtype of " + s);
          return true;
        }
      }
      if (DEBUG) System.out.println("Not a subtype");

    } catch (CheckedAnalysisException e1) {
      AnalysisContext.logError(
          "Error checking for weird generic parameterization of " + operandClass, e1);
    }
    return false;
  }
  /**
   * Compare to see if the argument <code>argType</code> passed to the method matches the type of
   * the corresponding parameter. The simplest case is when both are equal.
   *
   * <p>This is a conservative comparison - returns true if it cannot decide. If the parameter type
   * is a type variable (e.g. <code>T</code>) then we don't know enough (yet) to decide if they do
   * not match so return true.
   *
   * @param ignoreBaseType TODO
   */
  private IncompatibleTypes compareTypes(
      Type expectedType, Type actualType, boolean ignoreBaseType) {
    // XXX equality not implemented for GenericObjectType
    // if (parmType.equals(argType)) return true;
    if (expectedType == actualType) return IncompatibleTypes.SEEMS_OK;
    // Compare type signatures instead
    String expectedString = GenericUtilities.getString(expectedType);
    String actualString = GenericUtilities.getString(actualType);
    if (expectedString.equals(actualString)) return IncompatibleTypes.SEEMS_OK;

    if (expectedType.equals(Type.OBJECT)) return IncompatibleTypes.SEEMS_OK;
    // if either type is java.lang.Object, then automatically true!
    // again compare strings...

    String objString = GenericUtilities.getString(Type.OBJECT);

    if (expectedString.equals(objString)) {
      return IncompatibleTypes.SEEMS_OK;
    }

    // get a category for each type
    TypeCategory expectedCat = GenericUtilities.getTypeCategory(expectedType);
    TypeCategory argCat = GenericUtilities.getTypeCategory(actualType);
    if (actualString.equals(objString) && expectedCat == TypeCategory.TYPE_VARIABLE) {
      return IncompatibleTypes.SEEMS_OK;
    }
    if (ignoreBaseType) {
      if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PARAMETERIZED) {
        GenericObjectType parmGeneric = (GenericObjectType) expectedType;
        GenericObjectType argGeneric = (GenericObjectType) actualType;
        return compareTypeParameters(parmGeneric, argGeneric);
      }
      return IncompatibleTypes.SEEMS_OK;
    }

    if (actualType.equals(Type.OBJECT) && expectedCat == TypeCategory.ARRAY_TYPE)
      return IncompatibleTypes.ARRAY_AND_OBJECT;

    // -~- plain objects are easy
    if (expectedCat == TypeCategory.PLAIN_OBJECT_TYPE && argCat == TypeCategory.PLAIN_OBJECT_TYPE)
      return IncompatibleTypes.getPriorityForAssumingCompatible(expectedType, actualType, false);

    if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PLAIN_OBJECT_TYPE)
      return IncompatibleTypes.getPriorityForAssumingCompatible(
          (GenericObjectType) expectedType, actualType);
    if (expectedCat == TypeCategory.PLAIN_OBJECT_TYPE && argCat == TypeCategory.PARAMETERIZED)
      return IncompatibleTypes.getPriorityForAssumingCompatible(
          (GenericObjectType) actualType, expectedType);

    // -~- parmType is: "? extends Another Type" OR "? super Another Type"
    if (expectedCat == TypeCategory.WILDCARD_EXTENDS || expectedCat == TypeCategory.WILDCARD_SUPER)
      return compareTypes(
          ((GenericObjectType) expectedType).getExtension(), actualType, ignoreBaseType);

    // -~- Not handling type variables
    if (expectedCat == TypeCategory.TYPE_VARIABLE || argCat == TypeCategory.TYPE_VARIABLE)
      return IncompatibleTypes.SEEMS_OK;

    // -~- Array Types: compare dimensions, then base type
    if (expectedCat == TypeCategory.ARRAY_TYPE && argCat == TypeCategory.ARRAY_TYPE) {
      ArrayType parmArray = (ArrayType) expectedType;
      ArrayType argArray = (ArrayType) actualType;

      if (parmArray.getDimensions() != argArray.getDimensions())
        return IncompatibleTypes.ARRAY_AND_NON_ARRAY;

      return compareTypes(parmArray.getBasicType(), argArray.getBasicType(), ignoreBaseType);
    }
    // If one is an Array Type and the other is not, then they
    // are incompatible. (We already know neither is java.lang.Object)
    if (expectedCat == TypeCategory.ARRAY_TYPE ^ argCat == TypeCategory.ARRAY_TYPE) {
      return IncompatibleTypes.ARRAY_AND_NON_ARRAY;
    }

    // -~- Parameter Types: compare base type then parameters
    if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PARAMETERIZED) {
      GenericObjectType parmGeneric = (GenericObjectType) expectedType;
      GenericObjectType argGeneric = (GenericObjectType) actualType;

      // base types should be related
      {
        IncompatibleTypes result =
            compareTypes(parmGeneric.getObjectType(), argGeneric.getObjectType(), ignoreBaseType);
        if (!result.equals(IncompatibleTypes.SEEMS_OK)) return result;
      }
      return compareTypeParameters(parmGeneric, argGeneric);

      // XXX More to come
    }
    // If one is a Parameter Type and the other is not, then they
    // are incompatible. (We already know neither is java.lang.Object)
    if (false) {
      // not true. Consider class Foo extends ArrayList<String>
      if (expectedCat == TypeCategory.PARAMETERIZED ^ argCat == TypeCategory.PARAMETERIZED) {
        return IncompatibleTypes.SEEMS_OK; // fix this when we know what
        // we are doing here
      }
    }

    // -~- Wildcard e.g. List<*>.contains(...)
    if (expectedCat == TypeCategory.WILDCARD) // No Way to know
    return IncompatibleTypes.SEEMS_OK;

    // -~- Non Reference types
    // if ( parmCat == TypeCategory.NON_REFERENCE_TYPE ||
    // argCat == TypeCategory.NON_REFERENCE_TYPE )
    if (expectedType instanceof BasicType || actualType instanceof BasicType) {
      // this should not be possible, compiler will complain (pre 1.5)
      // or autobox primitive types (1.5 +)
      throw new IllegalArgumentException(
          "checking for compatibility of " + expectedType + " with " + actualType);
    }

    return IncompatibleTypes.SEEMS_OK;
  }