com.strobel.assembler.metadata.MetadataHelper Maven / Gradle / Ivy
/*
* MetadataHelper.java
*
* Copyright (c) 2013 Mike Strobel
*
* This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain;
* and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa.
*
* This source code is subject to terms and conditions of the Apache License, Version 2.0.
* A copy of the license can be found in the License.html file at the root of this distribution.
* By using this source code in any fashion, you are agreeing to be bound by the terms of the
* Apache License, Version 2.0.
*
* You must not remove this notice, or any other, from this software.
*/
package com.strobel.assembler.metadata;
import com.strobel.collections.ListBuffer;
import com.strobel.core.ArrayUtilities;
import com.strobel.core.Pair;
import com.strobel.core.Predicate;
import com.strobel.core.Predicates;
import com.strobel.core.StringUtilities;
import com.strobel.core.VerifyArgument;
import java.util.*;
import static com.strobel.core.CollectionUtilities.*;
public final class MetadataHelper {
public static boolean areGenericsSupported(final TypeDefinition t) {
return t != null && t.getCompilerMajorVersion() >= 49;
}
public static int getArrayRank(final TypeReference t) {
if (t == null) {
return 0;
}
int rank = 0;
TypeReference current = t;
while (current.isArray()) {
++rank;
current = current.getElementType();
}
return rank;
}
public static boolean isEnclosedBy(final TypeReference innerType, final TypeReference outerType) {
if (innerType == null) {
return false;
}
for (TypeReference current = innerType;
current != null;
current = current.getDeclaringType()) {
if (isSameType(current, outerType)) {
return true;
}
}
final TypeDefinition resolvedInnerType = innerType.resolve();
return resolvedInnerType != null &&
isEnclosedBy(resolvedInnerType.getBaseType(), outerType);
}
public static boolean canReferenceTypeVariablesOf(final TypeReference declaringType, final TypeReference referenceSite) {
if (declaringType == null || referenceSite == null) {
return false;
}
if (declaringType == referenceSite) {
return declaringType.isGenericType();
}
for (TypeReference current = referenceSite.getDeclaringType();
current != null; ) {
if (isSameType(current, declaringType)) {
return true;
}
final TypeDefinition resolvedType = current.resolve();
if (resolvedType != null) {
final MethodReference declaringMethod = resolvedType.getDeclaringMethod();
if (declaringMethod != null) {
current = declaringMethod.getDeclaringType();
continue;
}
}
current = current.getDeclaringType();
}
return false;
}
public static TypeReference findCommonSuperType(final TypeReference type1, final TypeReference type2) {
VerifyArgument.notNull(type1, "type1");
VerifyArgument.notNull(type2, "type2");
if (type1 == type2) {
return type1;
}
if (type1.isPrimitive()) {
if (type2.isPrimitive()) {
if (isAssignableFrom(type1, type2)) {
return type1;
}
if (isAssignableFrom(type2, type1)) {
return type2;
}
return doNumericPromotion(type1, type2);
}
return findCommonSuperType(getBoxedTypeOrSelf(type1), type2);
}
else if (type2.isPrimitive()) {
return findCommonSuperType(type1, getBoxedTypeOrSelf(type2));
}
int rank1 = 0;
int rank2 = 0;
TypeReference elementType1 = type1;
TypeReference elementType2 = type2;
while (elementType1.isArray()) {
elementType1 = elementType1.getElementType();
++rank1;
}
while (elementType2.isArray()) {
elementType2 = elementType2.getElementType();
++rank2;
}
if (rank1 != rank2) {
return BuiltinTypes.Object;
}
if (rank1 != 0 && (elementType1.isPrimitive() || elementType2.isPrimitive())) {
if (elementType1.isPrimitive() && elementType2.isPrimitive()) {
TypeReference promotedType = doNumericPromotion(elementType1, elementType2);
while (rank1-- > 0) {
promotedType = promotedType.makeArrayType();
}
return promotedType;
}
return BuiltinTypes.Object;
}
while (!elementType1.isUnbounded()) {
elementType1 = elementType1.hasSuperBound() ? elementType1.getSuperBound()
: elementType1.getExtendsBound();
}
while (!elementType2.isUnbounded()) {
elementType2 = elementType2.hasSuperBound() ? elementType2.getSuperBound()
: elementType2.getExtendsBound();
}
TypeReference result = findCommonSuperTypeCore(elementType1, elementType2);
while (rank1-- > 0) {
result = result.makeArrayType();
}
return result;
}
private static TypeReference doNumericPromotion(final TypeReference leftType, final TypeReference rightType) {
final JvmType left = leftType.getSimpleType();
final JvmType right = rightType.getSimpleType();
if (left == right) {
return leftType;
}
if (left == JvmType.Double || right == JvmType.Double) {
return BuiltinTypes.Double;
}
if (left == JvmType.Float || right == JvmType.Float) {
return BuiltinTypes.Float;
}
if (left == JvmType.Long || right == JvmType.Long) {
return BuiltinTypes.Long;
}
if (left.isNumeric() && left != JvmType.Boolean || right.isNumeric() && right != JvmType.Boolean) {
return BuiltinTypes.Integer;
}
return leftType;
}
private static TypeReference findCommonSuperTypeCore(final TypeReference type1, final TypeReference type2) {
if (isAssignableFrom(type1, type2)) {
if (type2.isGenericType() && !type1.isGenericType()) {
final TypeDefinition resolved1 = type1.resolve();
if (resolved1 != null) {
return substituteGenericArguments(resolved1, type2);
}
}
return substituteGenericArguments(type1, type2);
}
if (isAssignableFrom(type2, type1)) {
if (type1.isGenericType() && !type2.isGenericType()) {
final TypeDefinition resolved2 = type2.resolve();
if (resolved2 != null) {
return substituteGenericArguments(resolved2, type1);
}
}
return substituteGenericArguments(type2, type1);
}
final TypeDefinition c = type1.resolve();
final TypeDefinition d = type2.resolve();
if (c == null || d == null || c.isInterface() || d.isInterface()) {
return BuiltinTypes.Object;
}
TypeReference current = c;
while (current != null) {
for (final TypeReference interfaceType : getInterfaces(current)) {
if (isAssignableFrom(interfaceType, d)) {
return interfaceType;
}
}
current = getBaseType(current);
if (current != null) {
if (isAssignableFrom(current, d)) {
return current;
}
}
}
return BuiltinTypes.Object;
// do {
// final TypeReference baseType = current.getBaseType();
//
// if (baseType == null || (current = baseType.resolve()) == null) {
// return BuiltinTypes.Object;
// }
// }
// while (!isAssignableFrom(current, d));
//
// return current;
}
public static ConversionType getConversionType(final TypeReference target, final TypeReference source) {
VerifyArgument.notNull(source, "source");
VerifyArgument.notNull(target, "target");
final TypeReference underlyingTarget = getUnderlyingPrimitiveTypeOrSelf(target);
final TypeReference underlyingSource = getUnderlyingPrimitiveTypeOrSelf(source);
if (underlyingTarget.getSimpleType().isNumeric() && underlyingSource.getSimpleType().isNumeric()) {
return getNumericConversionType(target, source);
}
if (StringUtilities.equals(target.getInternalName(), "java/lang/Object")) {
return ConversionType.IMPLICIT;
}
if (isSameType(target, source, true)) {
return ConversionType.IDENTITY;
}
if (isAssignableFrom(target, source, false)) {
return ConversionType.IMPLICIT;
}
int targetRank = 0;
int sourceRank = 0;
TypeReference targetElementType = target;
TypeReference sourceElementType = source;
while (targetElementType.isArray()) {
++targetRank;
targetElementType = targetElementType.getElementType();
}
while (sourceElementType.isArray()) {
++sourceRank;
sourceElementType = sourceElementType.getElementType();
}
if (sourceRank != targetRank) {
if (isSameType(sourceElementType, BuiltinTypes.Object)) {
return ConversionType.EXPLICIT;
}
return ConversionType.NONE;
}
return ConversionType.EXPLICIT;
}
public static ConversionType getNumericConversionType(final TypeReference target, final TypeReference source) {
VerifyArgument.notNull(source, "source");
VerifyArgument.notNull(target, "target");
if (isSameType(target, source)) {
return ConversionType.IDENTITY;
}
if (!source.isPrimitive()) {
final TypeReference unboxedSourceType;
switch (source.getInternalName()) {
case "java/lang/Byte":
unboxedSourceType = BuiltinTypes.Byte;
break;
case "java/lang/Character":
unboxedSourceType = BuiltinTypes.Character;
break;
case "java/lang/Short":
unboxedSourceType = BuiltinTypes.Short;
break;
case "java/lang/Integer":
unboxedSourceType = BuiltinTypes.Integer;
break;
case "java/lang/Long":
unboxedSourceType = BuiltinTypes.Long;
break;
case "java/lang/Float":
unboxedSourceType = BuiltinTypes.Float;
break;
case "java/lang/Double":
unboxedSourceType = BuiltinTypes.Double;
break;
case "java/lang/Boolean":
unboxedSourceType = BuiltinTypes.Boolean;
break;
default:
return ConversionType.NONE;
}
final ConversionType unboxedConversion = getNumericConversionType(target, unboxedSourceType);
switch (unboxedConversion) {
case IDENTITY:
case IMPLICIT:
return ConversionType.IMPLICIT;
case EXPLICIT:
return ConversionType.NONE;
default:
return unboxedConversion;
}
}
if (!target.isPrimitive()) {
final TypeReference unboxedTargetType;
switch (target.getInternalName()) {
case "java/lang/Byte":
unboxedTargetType = BuiltinTypes.Byte;
break;
case "java/lang/Character":
unboxedTargetType = BuiltinTypes.Character;
break;
case "java/lang/Short":
unboxedTargetType = BuiltinTypes.Short;
break;
case "java/lang/Integer":
unboxedTargetType = BuiltinTypes.Integer;
break;
case "java/lang/Long":
unboxedTargetType = BuiltinTypes.Long;
break;
case "java/lang/Float":
unboxedTargetType = BuiltinTypes.Float;
break;
case "java/lang/Double":
unboxedTargetType = BuiltinTypes.Double;
break;
case "java/lang/Boolean":
unboxedTargetType = BuiltinTypes.Boolean;
break;
default:
return ConversionType.NONE;
}
switch (getNumericConversionType(unboxedTargetType, source)) {
case IDENTITY:
return ConversionType.IMPLICIT;
case IMPLICIT:
return ConversionType.EXPLICIT_TO_UNBOXED;
case EXPLICIT:
return ConversionType.EXPLICIT;
default:
return ConversionType.NONE;
}
}
final JvmType targetJvmType = target.getSimpleType();
final JvmType sourceJvmType = source.getSimpleType();
if (targetJvmType == sourceJvmType) {
return ConversionType.IDENTITY;
}
if (sourceJvmType == JvmType.Boolean) {
return ConversionType.NONE;
}
switch (targetJvmType) {
case Float:
case Double:
if (sourceJvmType.isIntegral() || sourceJvmType.bitWidth() <= targetJvmType.bitWidth()) {
return ConversionType.IMPLICIT;
}
return ConversionType.EXPLICIT;
case Byte:
case Short:
if (sourceJvmType == JvmType.Character) {
return ConversionType.EXPLICIT;
}
// fall through
case Integer:
case Long:
if (sourceJvmType.isIntegral() &&
sourceJvmType.bitWidth() <= targetJvmType.bitWidth()) {
return ConversionType.IMPLICIT;
}
return ConversionType.EXPLICIT;
case Character:
return sourceJvmType.isNumeric() ? ConversionType.EXPLICIT
: ConversionType.NONE;
}
return ConversionType.NONE;
}
public static boolean hasImplicitNumericConversion(final TypeReference target, final TypeReference source) {
VerifyArgument.notNull(source, "source");
VerifyArgument.notNull(target, "target");
if (target == source) {
return true;
}
if (!target.isPrimitive() || !source.isPrimitive()) {
return false;
}
final JvmType targetJvmType = target.getSimpleType();
final JvmType sourceJvmType = source.getSimpleType();
if (targetJvmType == sourceJvmType) {
return true;
}
if (sourceJvmType == JvmType.Boolean) {
return false;
}
switch (targetJvmType) {
case Float:
case Double:
return sourceJvmType.bitWidth() <= targetJvmType.bitWidth();
case Byte:
case Short:
case Integer:
case Long:
return sourceJvmType.isIntegral() &&
sourceJvmType.bitWidth() <= targetJvmType.bitWidth();
}
return false;
}
public static boolean isConvertible(final TypeReference source, final TypeReference target) {
return isConvertible(source, target, true);
}
public static boolean isConvertible(final TypeReference source, final TypeReference target, final boolean allowUnchecked) {
VerifyArgument.notNull(source, "source");
VerifyArgument.notNull(target, "target");
final boolean tPrimitive = target.isPrimitive();
final boolean sPrimitive = source.isPrimitive();
if (source == BuiltinTypes.Null) {
return !tPrimitive;
}
if (target.isWildcardType() && target.isUnbounded()) {
return !sPrimitive;
}
if (tPrimitive == sPrimitive) {
return allowUnchecked ? isSubTypeUnchecked(source, target)
: isSubType(source, target);
}
if (tPrimitive) {
switch (getNumericConversionType(target, source)) {
case IDENTITY:
case IMPLICIT:
return true;
default:
return false;
}
}
return allowUnchecked ? isSubTypeUnchecked(getBoxedTypeOrSelf(source), target)
: isSubType(getBoxedTypeOrSelf(source), target);
}
private static boolean isSubTypeUnchecked(final TypeReference t, final TypeReference s) {
return isSubtypeUncheckedInternal(t, s);
}
private static boolean isSubtypeUncheckedInternal(final TypeReference t, final TypeReference s) {
if (t == s) {
return true;
}
if (t == null || s == null) {
return false;
}
if (t.isArray() && s.isArray()) {
if (t.getElementType().isPrimitive()) {
return isSameType(getElementType(t), getElementType(s));
}
else {
return isSubTypeUnchecked(getElementType(t), getElementType(s));
}
}
else if (isSubType(t, s)) {
return true;
}
else if (t.isGenericParameter() && t.hasExtendsBound()) {
return isSubTypeUnchecked(getUpperBound(t), s);
}
else if (!isRawType(s)) {
final TypeReference t2 = asSuper(s, t);
if (t2 != null && isRawType(t2)) {
return true;
}
}
return false;
}
public static boolean isAssignableFrom(final TypeReference target, final TypeReference source) {
return isConvertible(source, target);
}
public static boolean isAssignableFrom(final TypeReference target, final TypeReference source, final boolean allowUnchecked) {
return isConvertible(source, target, allowUnchecked);
}
public static boolean isSubType(final TypeReference type, final TypeReference baseType) {
VerifyArgument.notNull(type, "type");
VerifyArgument.notNull(baseType, "baseType");
return isSubType(type, baseType, true);
}
public static boolean isPrimitiveBoxType(final TypeReference type) {
VerifyArgument.notNull(type, "type");
switch (type.getInternalName()) {
case "java/lang/Void":
case "java/lang/Boolean":
case "java/lang/Byte":
case "java/lang/Character":
case "java/lang/Short":
case "java/lang/Integer":
case "java/lang/Long":
case "java/lang/Float":
case "java/lang/Double":
return true;
default:
return false;
}
}
public static TypeReference getBoxedTypeOrSelf(final TypeReference type) {
VerifyArgument.notNull(type, "type");
if (type.isPrimitive()) {
switch (type.getSimpleType()) {
case Boolean:
return CommonTypeReferences.Boolean;
case Byte:
return CommonTypeReferences.Byte;
case Character:
return CommonTypeReferences.Character;
case Short:
return CommonTypeReferences.Short;
case Integer:
return CommonTypeReferences.Integer;
case Long:
return CommonTypeReferences.Long;
case Float:
return CommonTypeReferences.Float;
case Double:
return CommonTypeReferences.Double;
case Void:
return CommonTypeReferences.Void;
}
}
return type;
}
public static TypeReference getUnderlyingPrimitiveTypeOrSelf(final TypeReference type) {
VerifyArgument.notNull(type, "type");
switch (type.getInternalName()) {
case "java/lang/Void":
return BuiltinTypes.Void;
case "java/lang/Boolean":
return BuiltinTypes.Boolean;
case "java/lang/Byte":
return BuiltinTypes.Byte;
case "java/lang/Character":
return BuiltinTypes.Character;
case "java/lang/Short":
return BuiltinTypes.Short;
case "java/lang/Integer":
return BuiltinTypes.Integer;
case "java/lang/Long":
return BuiltinTypes.Long;
case "java/lang/Float":
return BuiltinTypes.Float;
case "java/lang/Double":
return BuiltinTypes.Double;
default:
return type;
}
}
public static TypeReference getDeclaredType(final TypeReference type) {
if (type == null) {
return null;
}
final TypeDefinition resolvedType = type.resolve();
if (resolvedType == null) {
return type;
}
if (resolvedType.isAnonymous()) {
final List interfaces = resolvedType.getExplicitInterfaces();
final TypeReference baseType = interfaces.isEmpty() ? resolvedType.getBaseType() : interfaces.get(0);
if (baseType != null) {
final TypeReference asSuperType = asSuper(baseType, type);
if (asSuperType != null) {
return asSuperType;
}
return baseType.isGenericType() ? new RawType(baseType) : baseType;
}
}
return type;
}
public static TypeReference getBaseType(final TypeReference type) {
if (type == null) {
return null;
}
final TypeDefinition resolvedType = type.resolve();
if (resolvedType == null) {
return null;
}
final TypeReference baseType = resolvedType.getBaseType();
if (baseType == null) {
return null;
}
return substituteGenericArguments(baseType, type);
}
public static List getInterfaces(final TypeReference type) {
final List result = INTERFACES_VISITOR.visit(type);
return result != null ? result : Collections.emptyList();
}
public static TypeReference asSubType(final TypeReference type, final TypeReference baseType) {
VerifyArgument.notNull(type, "type");
VerifyArgument.notNull(baseType, "baseType");
TypeReference effectiveType = type;
if (type instanceof RawType) {
effectiveType = type.getUnderlyingType();
}
else if (isRawType(type)) {
final TypeDefinition resolvedType = type.resolve();
effectiveType = resolvedType != null ? resolvedType : type;
}
return AS_SUBTYPE_VISITOR.visit(baseType, effectiveType);
}
public static TypeReference asSuper(final TypeReference type, final TypeReference subType) {
VerifyArgument.notNull(subType, "t");
VerifyArgument.notNull(type, "s");
return AS_SUPER_VISITOR.visit(subType, type);
}
@SuppressWarnings("ConstantConditions")
public static Map getGenericSubTypeMappings(final TypeReference type, final TypeReference baseType) {
VerifyArgument.notNull(type, "type");
VerifyArgument.notNull(baseType, "baseType");
if (type.isArray() && baseType.isArray()) {
TypeReference elementType = type.getElementType();
TypeReference baseElementType = baseType.getElementType();
while (elementType.isArray() && baseElementType.isArray()) {
elementType = elementType.getElementType();
baseElementType = baseElementType.getElementType();
}
return getGenericSubTypeMappings(elementType, baseElementType);
}
TypeReference current = type;
final List extends TypeReference> baseArguments;
if (baseType.isGenericDefinition()) {
baseArguments = baseType.getGenericParameters();
}
else if (baseType.isGenericType()) {
baseArguments = ((IGenericInstance) baseType).getTypeArguments();
}
else {
baseArguments = Collections.emptyList();
}
final TypeDefinition resolvedBaseType = baseType.resolve();
while (current != null) {
final TypeDefinition resolved = current.resolve();
if (resolvedBaseType != null &&
resolvedBaseType.isGenericDefinition() &&
isSameType(resolved, resolvedBaseType)) {
if (current instanceof IGenericInstance &&
baseType instanceof IGenericInstance) {
final List extends TypeReference> typeArguments = ((IGenericInstance) current).getTypeArguments();
if (baseArguments.size() == typeArguments.size()) {
final Map map = new HashMap<>();
for (int i = 0; i < typeArguments.size(); i++) {
map.put(typeArguments.get(i), baseArguments.get(i));
}
return map;
}
}
else if (baseType instanceof IGenericInstance &&
resolved.isGenericDefinition()) {
final List genericParameters = resolved.getGenericParameters();
final List extends TypeReference> typeArguments = ((IGenericInstance) baseType).getTypeArguments();
if (genericParameters.size() == typeArguments.size()) {
final Map map = new HashMap<>();
for (int i = 0; i < typeArguments.size(); i++) {
map.put(genericParameters.get(i), typeArguments.get(i));
}
return map;
}
}
}
if (resolvedBaseType != null && resolvedBaseType.isInterface()) {
for (final TypeReference interfaceType : getInterfaces(current)) {
final Map interfaceMap = getGenericSubTypeMappings(interfaceType, baseType);
if (!interfaceMap.isEmpty()) {
return interfaceMap;
}
}
}
current = getBaseType(current);
}
return Collections.emptyMap();
}
public static MethodReference asMemberOf(final MethodReference method, final TypeReference baseType) {
VerifyArgument.notNull(method, "method");
VerifyArgument.notNull(baseType, "baseType");
final MethodReference asMember;
TypeReference base = baseType;
if (baseType instanceof RawType) {
asMember = erase(method);
}
else {
while (base.isGenericParameter() || base.isWildcardType()) {
if (base.hasExtendsBound()) {
base = getUpperBound(base);
}
else {
base = BuiltinTypes.Object;
}
}
final TypeReference asSuper = asSuper(method.getDeclaringType(), base);
Map map;
try {
map = adapt(method.getDeclaringType(), asSuper != null ? asSuper : base);
}
catch (AdaptFailure ignored) {
map = getGenericSubTypeMappings(method.getDeclaringType(), asSuper != null ? asSuper : base);
}
asMember = TypeSubstitutionVisitor.instance().visitMethod(method, map);
if (asMember != method && asMember instanceof GenericMethodInstance) {
((GenericMethodInstance) asMember).setDeclaringType(asSuper != null ? asSuper : base);
}
}
final MethodReference result = specializeIfNecessary(method, asMember, base);
return result;
}
private static MethodReference specializeIfNecessary(
final MethodReference originalMethod,
final MethodReference asMember,
final TypeReference baseType) {
if (baseType.isArray() &&
StringUtilities.equals(asMember.getName(), "clone") &&
asMember.getParameters().isEmpty()) {
return ensureReturnType(originalMethod, asMember, baseType, baseType);
}
else if (StringUtilities.equals(asMember.getName(), "getClass") &&
asMember.getParameters().isEmpty()) {
TypeReference classType;
TypeDefinition resolvedClassType;
TypeDefinition resolvedType = baseType.resolve();
//noinspection UnusedAssignment
if (resolvedType == null ||
(classType = resolvedType.getResolver().lookupType("java/lang/Class")) == null ||
(resolvedClassType = classType.resolve()) == null) {
resolvedType = originalMethod.getDeclaringType().resolve();
}
if (resolvedType == null ||
(classType = resolvedType.getResolver().lookupType("java/lang/Class")) == null ||
(resolvedClassType = classType.resolve()) == null) {
return asMember;
}
if (resolvedClassType.isGenericType()) {
final MethodDefinition resolvedMethod = originalMethod.resolve();
return new GenericMethodInstance(
baseType,
resolvedMethod != null ? resolvedMethod : asMember,
resolvedClassType.makeGenericType(WildcardType.makeExtends(erase(baseType))),
Collections.emptyList(),
Collections.emptyList()
);
}
return asMember;
}
return asMember;
}
private static MethodReference ensureReturnType(
final MethodReference originalMethod,
final MethodReference method,
final TypeReference returnType,
final TypeReference declaringType) {
if (isSameType(method.getReturnType(), returnType, true)) {
return method;
}
final MethodDefinition resolvedMethod = originalMethod.resolve();
final List typeArguments;
if (method instanceof IGenericInstance && method.isGenericMethod()) {
typeArguments = ((IGenericInstance) method).getTypeArguments();
}
else {
typeArguments = Collections.emptyList();
}
return new GenericMethodInstance(
declaringType,
resolvedMethod != null ? resolvedMethod : originalMethod,
returnType,
copyParameters(method.getParameters()),
typeArguments
);
}
public static FieldReference asMemberOf(final FieldReference field, final TypeReference baseType) {
VerifyArgument.notNull(field, "field");
VerifyArgument.notNull(baseType, "baseType");
final Map map = adapt(field.getDeclaringType(), baseType);
return TypeSubstitutionVisitor.instance().visitField(field, map);
}
public static TypeReference substituteGenericArguments(
final TypeReference inputType,
final TypeReference substitutionsProvider) {
if (inputType == null || substitutionsProvider == null) {
return inputType;
}
return substituteGenericArguments(inputType, adapt(inputType, substitutionsProvider));
}
public static TypeReference substituteGenericArguments(
final TypeReference inputType,
final MethodReference substitutionsProvider) {
if (inputType == null) {
return null;
}
if (substitutionsProvider == null || !isGenericSubstitutionNeeded(inputType)) {
return inputType;
}
final TypeReference declaringType = substitutionsProvider.getDeclaringType();
assert declaringType != null;
if (!substitutionsProvider.isGenericMethod() && !declaringType.isGenericType()) {
return null;
}
final List extends TypeReference> methodGenericParameters;
final List extends TypeReference> genericParameters;
final List extends TypeReference> methodTypeArguments;
final List extends TypeReference> typeArguments;
if (substitutionsProvider.isGenericMethod()) {
methodGenericParameters = substitutionsProvider.getGenericParameters();
}
else {
methodGenericParameters = Collections.emptyList();
}
if (substitutionsProvider.isGenericDefinition()) {
methodTypeArguments = methodGenericParameters;
}
else {
methodTypeArguments = ((IGenericInstance) substitutionsProvider).getTypeArguments();
}
if (declaringType.isGenericType()) {
genericParameters = declaringType.getGenericParameters();
if (declaringType.isGenericDefinition()) {
typeArguments = genericParameters;
}
else {
typeArguments = ((IGenericInstance) declaringType).getTypeArguments();
}
}
else {
genericParameters = Collections.emptyList();
typeArguments = Collections.emptyList();
}
if (methodTypeArguments.isEmpty() && typeArguments.isEmpty()) {
return inputType;
}
final Map map = new HashMap<>();
if (methodTypeArguments.size() == methodGenericParameters.size()) {
for (int i = 0; i < methodTypeArguments.size(); i++) {
map.put(methodGenericParameters.get(i), methodTypeArguments.get(i));
}
}
if (typeArguments.size() == genericParameters.size()) {
for (int i = 0; i < typeArguments.size(); i++) {
map.put(genericParameters.get(i), typeArguments.get(i));
}
}
return substituteGenericArguments(inputType, map);
}
public static TypeReference substituteGenericArguments(
final TypeReference inputType,
final Map substitutionsProvider) {
if (inputType == null) {
return null;
}
if (substitutionsProvider == null || substitutionsProvider.isEmpty()) {
return inputType;
}
return TypeSubstitutionVisitor.instance().visit(inputType, substitutionsProvider);
}
private static boolean isGenericSubstitutionNeeded(final TypeReference type) {
if (type == null) {
return false;
}
final TypeDefinition resolvedType = type.resolve();
return resolvedType != null &&
resolvedType.containsGenericParameters();
}
public static List findMethods(final TypeReference type) {
return findMethods(type, Predicates.alwaysTrue());
}
public static List findMethods(
final TypeReference type,
final Predicate super MethodReference> filter) {
return findMethods(type, filter, false);
}
public static List findMethods(
final TypeReference type,
final Predicate super MethodReference> filter,
final boolean includeBridgeMethods) {
return findMethods(type, filter, includeBridgeMethods, false);
}
public static List findMethods(
final TypeReference type,
final Predicate super MethodReference> filter,
final boolean includeBridgeMethods,
final boolean includeOverriddenMethods) {
VerifyArgument.notNull(type, "type");
VerifyArgument.notNull(filter, "filter");
final Set descriptors = new HashSet<>();
final ArrayDeque agenda = new ArrayDeque<>();
List results = null;
agenda.addLast(getUpperBound(type));
descriptors.add(type.getInternalName());
while (!agenda.isEmpty()) {
final TypeDefinition resolvedType = agenda.removeFirst().resolve();
if (resolvedType == null) {
break;
}
final TypeReference baseType = resolvedType.getBaseType();
if (baseType != null && descriptors.add(baseType.getInternalName())) {
agenda.addLast(baseType);
}
for (final TypeReference interfaceType : resolvedType.getExplicitInterfaces()) {
if (interfaceType != null && descriptors.add(interfaceType.getInternalName())) {
agenda.addLast(interfaceType);
}
}
for (final MethodDefinition method : resolvedType.getDeclaredMethods()) {
if (!includeBridgeMethods && method.isBridgeMethod()) {
continue;
}
if (filter.test(method)) {
final String key = (includeOverriddenMethods ? method.getFullName() : method.getName()) + ":" + method.getErasedSignature();
if (descriptors.add(key)) {
if (results == null) {
results = new ArrayList<>();
}
final MethodReference asMember = asMemberOf(method, type);
results.add(asMember != null ? asMember : method);
}
}
}
}
return results != null ? results
: Collections.emptyList();
}
public static boolean isOverloadCheckingRequired(final MethodReference method) {
final MethodDefinition resolved = method.resolve();
final boolean isVarArgs = resolved != null && resolved.isVarArgs();
final TypeReference declaringType = (resolved != null ? resolved : method).getDeclaringType();
final int parameterCount = (resolved != null ? resolved.getParameters() : method.getParameters()).size();
final List methods = findMethods(
declaringType,
Predicates.and(
MetadataFilters.matchName(method.getName()),
new Predicate() {
@Override
public boolean test(final MethodReference m) {
final List p = m.getParameters();
final MethodDefinition r = m instanceof MethodDefinition ? (MethodDefinition) m
: m.resolve();
if (r != null && r.isBridgeMethod()) {
return false;
}
if (isVarArgs) {
if (r != null && r.isVarArgs()) {
return true;
}
return p.size() >= parameterCount;
}
if (p.size() < parameterCount) {
return r != null && r.isVarArgs();
}
return p.size() == parameterCount;
}
}
)
);
return methods.size() > 1;
}
public static TypeReference getLowerBound(final TypeReference t) {
return LOWER_BOUND_VISITOR.visit(t);
}
public static TypeReference getUpperBound(final TypeReference t) {
return UPPER_BOUND_VISITOR.visit(t);
}
public static TypeReference getElementType(final TypeReference t) {
if (t.isArray()) {
return t.getElementType();
}
if (t.isWildcardType()) {
return getElementType(getUpperBound(t));
}
return null;
}
public static TypeReference getSuperType(final TypeReference t) {
if (t == null) {
return null;
}
return SUPER_VISITOR.visit(t);
}
public static boolean isSubTypeNoCapture(final TypeReference type, final TypeReference baseType) {
return isSubType(type, baseType, false);
}
public static boolean isSubType(final TypeReference type, final TypeReference baseType, final boolean capture) {
if (type == baseType) {
return true;
}
if (type == null || baseType == null) {
return false;
}
if (baseType instanceof CompoundTypeReference) {
final CompoundTypeReference c = (CompoundTypeReference) baseType;
if (!isSubType(type, getSuperType(c), capture)) {
return false;
}
for (final TypeReference interfaceType : c.getInterfaces()) {
if (!isSubType(type, interfaceType, capture)) {
return false;
}
}
return true;
}
final TypeReference lower = getLowerBound(baseType);
if (lower != baseType) {
return isSubType(capture ? capture(type) : type, lower, false);
}
return IS_SUBTYPE_VISITOR.visit(capture ? capture(type) : type, baseType);
}
private static TypeReference capture(final TypeReference type) {
// TODO: Implement wildcard capture.
return type;
}
public static Map adapt(final TypeReference source, final TypeReference target) {
final Adapter adapter = new Adapter();
adapter.visit(source, target);
return adapter.mapping;
}
private static Map adaptSelf(final TypeReference t) {
final TypeDefinition r = t.resolve();
return r != null ? adapt(r, t)
: Collections.emptyMap();
}
private static TypeReference rewriteSupers(final TypeReference t) {
if (!(t instanceof IGenericInstance)) {
return t;
}
final Map map = adaptSelf(t);
if (map.isEmpty()) {
return t;
}
Map rewrite = null;
for (final TypeReference k : map.keySet()) {
final TypeReference original = map.get(k);
TypeReference s = rewriteSupers(original);
if (s.hasSuperBound() && !s.hasExtendsBound()) {
s = WildcardType.unbounded();
if (rewrite == null) {
rewrite = new HashMap<>(map);
}
}
else if (s != original) {
s = WildcardType.makeExtends(getUpperBound(s));
if (rewrite == null) {
rewrite = new HashMap<>(map);
}
}
if (rewrite != null) {
map.put(k, s);
}
}
if (rewrite != null) {
return substituteGenericArguments(t, rewrite);
}
else {
return t;
}
}
/**
* Check if {@code t} contains {@code s}.
*
* {@code T} contains {@code S} if:
*
*
{@code L(T) <: L(S) && U(S) <: U(T)}
*
*
This relation is only used by isSubType(), that is:
*
*
{@code C <: C if T contains S.}
*
* Because of F-bounds, this relation can lead to infinite recursion. Thus, we must
* somehow break that recursion. Notice that containsType() is only called from isSubType().
* Since the arguments have already been checked against their bounds, we know:
*
*
{@code U(S) <: U(T) if T is "super" bound (U(T) *is* the bound)}
*
*
{@code L(T) <: L(S) if T is "extends" bound (L(T) is bottom)}
*
* @param t
* a type
* @param s
* a type
*/
public static boolean containsType(final TypeReference t, final TypeReference s) {
return CONTAINS_TYPE_VISITOR.visit(t, s);
}
public static boolean isSameType(final TypeReference t, final TypeReference s) {
return isSameType(t, s, false);
}
public static boolean isSameType(final TypeReference t, final TypeReference s, final boolean strict) {
if (t == s) {
return true;
}
if (t == null || s == null) {
return false;
}
return strict ? SAME_TYPE_VISITOR_STRICT.visit(t, s)
: SAME_TYPE_VISITOR_LOOSE.visit(t, s);
}
public static boolean areSameTypes(final List extends TypeReference> t, final List extends TypeReference> s) {
return areSameTypes(t, s, false);
}
public static boolean areSameTypes(
final List extends TypeReference> t,
final List extends TypeReference> s,
final boolean strict) {
if (t.size() != s.size()) {
return false;
}
for (int i = 0, n = t.size(); i < n; i++) {
if (!isSameType(t.get(i), s.get(i), strict)) {
return false;
}
}
return true;
}
private static boolean isCaptureOf(final TypeReference t, final TypeReference s) {
return isSameWildcard(t, s);
}
private static boolean isSameWildcard(final TypeReference t, final TypeReference s) {
VerifyArgument.notNull(t, "t");
VerifyArgument.notNull(s, "s");
if (!t.isWildcardType() || !s.isWildcardType()) {
return false;
}
if (t.isUnbounded()) {
return s.isUnbounded();
}
if (t.hasSuperBound()) {
return s.hasSuperBound() && isSameType(t.getSuperBound(), s.getSuperBound());
}
return s.hasExtendsBound() && isSameType(t.getExtendsBound(), s.getExtendsBound());
}
private static List extends TypeReference> getTypeArguments(final TypeReference t) {
if (t instanceof IGenericInstance) {
return ((IGenericInstance) t).getTypeArguments();
}
if (t.isGenericType()) {
return t.getGenericParameters();
}
return Collections.emptyList();
}
private static boolean containsType(final List extends TypeReference> t, final List extends TypeReference> s) {
if (t.size() != s.size()) {
return false;
}
if (t.isEmpty()) {
return true;
}
for (int i = 0, n = t.size(); i < n; i++) {
if (!containsType(t.get(i), s.get(i))) {
return false;
}
}
return true;
}
private static boolean containsTypeEquivalent(final TypeReference t, final TypeReference s) {
return s == t ||
containsType(t, s) && containsType(s, t);
}
private static boolean containsTypeEquivalent(final List extends TypeReference> t, final List extends TypeReference> s) {
if (t.size() != s.size()) {
return false;
}
for (int i = 0, n = t.size(); i < n; i++) {
if (!containsTypeEquivalent(t.get(i), s.get(i))) {
return false;
}
}
return true;
}
private final static ThreadLocal>> CONTAINS_TYPE_CACHE =
new ThreadLocal>>() {
@Override
protected final HashSet> initialValue() {
return new HashSet<>();
}
};
private final static ThreadLocal>> ADAPT_CACHE =
new ThreadLocal>>() {
@Override
protected final HashSet> initialValue() {
return new HashSet<>();
}
};
private static boolean containsTypeRecursive(final TypeReference t, final TypeReference s) {
final HashSet> cache = CONTAINS_TYPE_CACHE.get();
final Pair pair = new Pair<>(t, s);
if (cache.add(pair)) {
try {
return containsType(getTypeArguments(t), getTypeArguments(s));
}
finally {
cache.remove(pair);
}
}
else {
return containsType(getTypeArguments(t), getTypeArguments(rewriteSupers(s)));
}
}
private static TypeReference arraySuperType(final TypeReference t) {
final TypeDefinition resolved = t.resolve();
if (resolved != null) {
final IMetadataResolver resolver = resolved.getResolver();
final TypeReference cloneable = resolver.lookupType("java/lang/Cloneable");
final TypeReference serializable = resolver.lookupType("java/io/Serializable");
if (cloneable != null) {
if (serializable != null) {
return new CompoundTypeReference(
null,
ArrayUtilities.asUnmodifiableList(cloneable, serializable)
);
}
return cloneable;
}
if (serializable != null) {
return serializable;
}
}
return BuiltinTypes.Object;
}
public static boolean isRawType(final TypeReference t) {
if (t == null) {
return false;
}
if (t instanceof RawType) {
return true;
}
if (t.isGenericType()) {
return false;
}
final TypeReference r = t.resolve();
if (r != null && r.isGenericType()) {
return true;
}
return false;
}
public static int getUnboundGenericParameterCount(final TypeReference t) {
if (t == null || t instanceof RawType || !t.isGenericType()) {
return 0;
}
final List genericParameters = t.getGenericParameters();
if (t.isGenericDefinition()) {
return genericParameters.size();
}
final IGenericParameterProvider genericDefinition = ((IGenericInstance) t).getGenericDefinition();
if (!genericDefinition.isGenericDefinition()) {
return 0;
}
final List typeArguments = ((IGenericInstance) t).getTypeArguments();
assert genericParameters.size() == typeArguments.size();
int count = 0;
for (int i = 0; i < genericParameters.size(); i++) {
final GenericParameter genericParameter = genericParameters.get(i);
final TypeReference typeArgument = typeArguments.get(i);
if (isSameType(genericParameter, typeArgument, true)) {
++count;
}
}
return count;
}
public static List eraseRecursive(final List types) {
ArrayList result = null;
for (int i = 0, n = types.size(); i < n; i++) {
final TypeReference type = types.get(i);
final TypeReference erased = eraseRecursive(type);
if (result != null) {
result.set(i, erased);
}
else if (type != erased) {
result = new ArrayList<>(types);
result.set(i, erased);
}
}
return result != null ? result : types;
}
public static TypeReference eraseRecursive(final TypeReference type) {
return erase(type, true);
}
private static boolean eraseNotNeeded(final TypeReference type) {
return type == null ||
type instanceof RawType ||
type.isPrimitive() ||
StringUtilities.equals(type.getInternalName(), CommonTypeReferences.String.getInternalName());
}
public static TypeReference erase(final TypeReference type) {
return erase(type, false);
}
public static TypeReference erase(final TypeReference type, final boolean recurse) {
if (eraseNotNeeded(type)) {
return type;
}
return type.accept(ERASE_VISITOR, recurse);
}
public static MethodReference erase(final MethodReference method) {
if (method != null) {
MethodReference baseMethod = method;
final MethodDefinition resolvedMethod = baseMethod.resolve();
if (resolvedMethod != null) {
baseMethod = resolvedMethod;
}
else if (baseMethod instanceof IGenericInstance) {
baseMethod = (MethodReference) ((IGenericInstance) baseMethod).getGenericDefinition();
}
if (baseMethod != null) {
return new RawMethod(baseMethod);
}
}
return method;
}
private static TypeReference classBound(final TypeReference t) {
//
// TODO: Implement class bound computation.
//
return t;
}
public static boolean isOverride(final MethodDefinition method, final MethodReference ancestorMethod) {
final MethodDefinition resolvedAncestor = ancestorMethod.resolve();
if (resolvedAncestor == null || resolvedAncestor.isFinal() || resolvedAncestor.isPrivate() || resolvedAncestor.isStatic()) {
return false;
}
final int modifiers = method.getModifiers() & Flags.AccessFlags;
final int ancestorModifiers = resolvedAncestor.getModifiers() & Flags.AccessFlags;
if (modifiers != ancestorModifiers) {
return false;
}
if (!StringUtilities.equals(method.getName(), ancestorMethod.getName())) {
return false;
}
if (method.getDeclaringType().isInterface()) {
return false;
}
final MethodDefinition resolved = method.resolve();
final TypeReference declaringType = erase(
resolved != null ? resolved.getDeclaringType()
: method.getDeclaringType()
);
final TypeReference ancestorDeclaringType = erase(resolvedAncestor.getDeclaringType());
if (isSameType(declaringType, ancestorDeclaringType)) {
return false;
}
if (StringUtilities.equals(method.getErasedSignature(), ancestorMethod.getErasedSignature())) {
return true;
}
if (!isSubType(declaringType, ancestorDeclaringType)) {
return false;
}
final List parameters = method.getParameters();
final List ancestorParameters = ancestorMethod.getParameters();
if (parameters.size() != ancestorParameters.size()) {
return false;
}
final TypeReference ancestorReturnType = erase(ancestorMethod.getReturnType());
final TypeReference baseReturnType = erase(method.getReturnType());
if (!isAssignableFrom(ancestorReturnType, baseReturnType)) {
return false;
}
for (int i = 0, n = ancestorParameters.size(); i < n; i++) {
final TypeReference parameterType = erase(parameters.get(i).getParameterType());
final TypeReference ancestorParameterType = erase(ancestorParameters.get(i).getParameterType());
if (!isSameType(parameterType, ancestorParameterType, false)) {
return false;
}
}
return true;
}
//
private final static TypeMapper UPPER_BOUND_VISITOR = new TypeMapper() {
@Override
public TypeReference visitType(final TypeReference t, final Void ignored) {
if (t.isWildcardType() || t.isGenericParameter() || t instanceof ICapturedType) {
return t.isUnbounded() || t.hasSuperBound() ? BuiltinTypes.Object
: visit(t.getExtendsBound());
}
return t;
}
@Override
public TypeReference visitCapturedType(final CapturedType t, final Void ignored) {
return t.getExtendsBound();
}
};
private final static TypeMapper LOWER_BOUND_VISITOR = new TypeMapper() {
@Override
public TypeReference visitWildcard(final WildcardType t, final Void ignored) {
return t.hasSuperBound() ? visit(t.getSuperBound())
: BuiltinTypes.Bottom;
}
@Override
public TypeReference visitCapturedType(final CapturedType t, final Void ignored) {
return t.getSuperBound();
}
};
private final static TypeRelation IS_SUBTYPE_VISITOR = new TypeRelation() {
@Override
public Boolean visitArrayType(final ArrayType t, final TypeReference s) {
if (s.isArray()) {
final TypeReference et = getElementType(t);
final TypeReference es = getElementType(s);
if (et.isPrimitive()) {
return isSameType(et, es);
}
return isSubTypeNoCapture(et, es);
}
final String sName = s.getInternalName();
return StringUtilities.equals(sName, "java/lang/Object") ||
StringUtilities.equals(sName, "java/lang/Cloneable") ||
StringUtilities.equals(sName, "java/io/Serializable");
}
@Override
public Boolean visitBottomType(final TypeReference t, final TypeReference s) {
switch (t.getSimpleType()) {
case Object:
case Array:
case TypeVariable:
return true;
default:
return false;
}
}
@Override
public Boolean visitClassType(final TypeReference t, final TypeReference s) {
final TypeReference superType = asSuper(s, t);
return superType != null &&
StringUtilities.equals(superType.getInternalName(), s.getInternalName()) &&
// You're not allowed to write
// Vector
}