/* * Copyright (c) 2006-2013 Rogério Liesenfeld * This file is subject to the terms of the MIT license (see LICENSE.txt). */ package mockit.internal.util; import java.lang.reflect.*; import java.util.*; import java.util.Map.*; public final class GenericTypeReflection { private final Map<String, String> typeParametersToTypeArguments; public GenericTypeReflection(Class<?> realClass, Type mockedType) { typeParametersToTypeArguments = new HashMap<String, String>(4); if (mockedType instanceof ParameterizedType) { addMappingsFromTypeParametersToTypeArguments(realClass, (ParameterizedType) mockedType); } addGenericTypeMappingsForSuperTypes(realClass); } private void addGenericTypeMappingsForSuperTypes(Class<?> realClass) { Type superType = realClass; while (superType instanceof Class<?> && superType != Object.class) { Class<?> superClass = (Class<?>) superType; superType = superClass.getGenericSuperclass(); superType = addGenericTypeMappingsIfParameterized(superType); for (Type implementedInterface : superClass.getGenericInterfaces()) { addGenericTypeMappingsIfParameterized(implementedInterface); } } } private Type addGenericTypeMappingsIfParameterized(Type superType) { if (superType instanceof ParameterizedType) { ParameterizedType mockedSuperType = (ParameterizedType) superType; Type rawType = mockedSuperType.getRawType(); addMappingsFromTypeParametersToTypeArguments((Class<?>) rawType, mockedSuperType); return rawType; } return superType; } private void addMappingsFromTypeParametersToTypeArguments(Class<?> mockedClass, ParameterizedType mockedType) { TypeVariable<?>[] typeParameters = mockedClass.getTypeParameters(); Type[] typeArguments = mockedType.getActualTypeArguments(); int n = typeParameters.length; for (int i = 0; i < n; i++) { Type typeArg = typeArguments[i]; String typeArgName = null; String typeVarName = typeParameters[i].getName(); if (typeArg instanceof Class<?>) { typeArgName = 'L' + ((Class<?>) typeArg).getName().replace('.', '/'); } else if (typeArg instanceof TypeVariable<?>) { String intermediateTypeArg = 'T' + ((TypeVariable<?>) typeArg).getName(); typeArgName = typeParametersToTypeArguments.get(intermediateTypeArg); } String mappedTypeArgName = typeArgName == null ? "Ljava/lang/Object" : typeArgName; typeParametersToTypeArguments.put('T' + typeVarName, mappedTypeArgName); } } public final class GenericSignature { private final List<String> parameters; private final String parameterTypeDescs; private final int lengthOfParameterTypeDescs; private int currentPos; GenericSignature(String signature) { int p = signature.indexOf('('); int q = signature.lastIndexOf(')'); parameterTypeDescs = signature.substring(p + 1, q); lengthOfParameterTypeDescs = parameterTypeDescs.length(); parameters = new ArrayList<String>(); addTypeDescsToList(); } private void addTypeDescsToList() { while (currentPos < lengthOfParameterTypeDescs) { addNextParameter(); } } private void addNextParameter() { int startPos = currentPos; int endPos; char c = parameterTypeDescs.charAt(startPos); if (c == 'T') { endPos = parameterTypeDescs.indexOf(';', startPos); currentPos = endPos; } else if (c == 'L') { endPos = advanceToEndOfTypeDesc(); } else if (c == '[') { char elemTypeStart = firstCharacterOfArrayElementType(); if (elemTypeStart == 'T') { endPos = parameterTypeDescs.indexOf(';', startPos); currentPos = endPos; } else if (elemTypeStart == 'L') { endPos = advanceToEndOfTypeDesc(); } else { endPos = currentPos + 1; } } else { endPos = currentPos + 1; } currentPos++; String parameter = parameterTypeDescs.substring(startPos, endPos); parameters.add(parameter); } private int advanceToEndOfTypeDesc() { char c = '\0'; do { currentPos++; if (currentPos == lengthOfParameterTypeDescs) break; c = parameterTypeDescs.charAt(currentPos); } while (c != ';' && c != '<'); int endPos = currentPos; if (c == '<') { advancePastTypeArguments(); } return endPos; } private char firstCharacterOfArrayElementType() { char c; do { currentPos++; c = parameterTypeDescs.charAt(currentPos); } while (c == '['); return c; } private void advancePastTypeArguments() { int angleBracketDepth = 1; do { currentPos++; char c = parameterTypeDescs.charAt(currentPos); if (c == '>') angleBracketDepth--; else if (c == '<') angleBracketDepth++; } while (angleBracketDepth > 0); } public boolean satisfiesGenericSignature(String otherSignature) { GenericSignature other = new GenericSignature(otherSignature); int n = parameters.size(); if (n != other.parameters.size()) { return false; } for (int i = 0; i < n; i++) { String p1 = other.parameters.get(i); String p2 = parameters.get(i); if (!areParametersOfSameType(p1, p2)) { return false; } } return true; } private boolean areParametersOfSameType(String param1, String param2) { if (param1.equals(param2)) return true; int i = -1; char c; do { i++; c = param1.charAt(i); } while (c == '['); if (c != 'T') return false; String typeArg1 = typeParametersToTypeArguments.get(param1.substring(i)); return param2.substring(i).equals(typeArg1); } } public GenericSignature parseSignature(String signature) { return new GenericSignature(signature); } public String resolveReturnType(String signature) { int p = signature.lastIndexOf(')') + 1; int q = signature.length(); String returnType = signature.substring(p, q); String resolvedReturnType = replaceTypeParametersWithActualTypes(returnType); StringBuilder finalSignature = new StringBuilder(signature); finalSignature.replace(p, q, resolvedReturnType); return finalSignature.toString(); } private String replaceTypeParametersWithActualTypes(String typeDesc) { if (typeDesc.charAt(0) == 'T') { String typeParameter = typeDesc.substring(0, typeDesc.length() - 1); String typeArg = typeParametersToTypeArguments.get(typeParameter); return typeArg == null ? typeDesc : typeArg + ';'; } int p = typeDesc.indexOf('<'); if (p < 0) { return typeDesc; } String resolvedTypeDesc = typeDesc; for (Entry<String, String> paramAndArg : typeParametersToTypeArguments.entrySet()) { String typeParam = paramAndArg.getKey() + ';'; String typeArg = paramAndArg.getValue() + ';'; resolvedTypeDesc = resolvedTypeDesc.replace(typeParam, typeArg); } return resolvedTypeDesc; } }