package me.tomassetti.turin.resolvers.jdk;
import me.tomassetti.turin.compiler.AmbiguousCallException;
import me.tomassetti.jvm.JvmConstructorDefinition;
import me.tomassetti.jvm.JvmType;
import me.tomassetti.turin.definitions.TypeDefinition;
import me.tomassetti.turin.resolvers.SymbolResolver;
import me.tomassetti.turin.parser.ast.expressions.ActualParam;
import me.tomassetti.turin.parser.ast.typeusage.*;
import me.tomassetti.turin.symbols.FormalParameterSymbol;
import me.tomassetti.turin.typesystem.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;
class ReflectionBasedMethodResolution {
private static class MethodOrConstructor {
private Constructor constructor;
private Method method;
public MethodOrConstructor(Constructor constructor) {
this.constructor = constructor;
}
public MethodOrConstructor(Method method) {
this.method = method;
}
public int getParameterCount() {
if (method != null) {
return method.getParameterCount();
} else {
return constructor.getParameterCount();
}
}
public Class<?> getParameterType(int i) {
if (method != null) {
return method.getParameterTypes()[i];
} else {
return constructor.getParameterTypes()[i];
}
}
}
public static List<FormalParameterSymbol> formalParameters(Constructor constructor, SymbolResolver resolver) {
List<FormalParameterSymbol> formalParameters = new ArrayList<>();
int i=0;
for (Type type : constructor.getGenericParameterTypes()) {
formalParameters.add(new FormalParameterSymbol(toTypeUsage(type, Collections.emptyMap(), resolver), constructor.getParameters()[i].getName()));
i++;
}
return formalParameters;
}
public static List<FormalParameterSymbol> formalParameters(Method method, Map<String, TypeUsage> typeVariables, SymbolResolver resolver) {
List<FormalParameterSymbol> formalParameters = new ArrayList<>();
int i=0;
for (Type type : method.getGenericParameterTypes()) {
formalParameters.add(new FormalParameterSymbol(toTypeUsage(type, typeVariables, resolver), method.getParameters()[i].getName()));
i++;
}
return formalParameters;
}
public static TypeUsage toTypeUsage(Type type, Map<String, TypeUsage> typeVariables, SymbolResolver resolver) {
if (type instanceof Class) {
Class clazz = (Class)type;
if (clazz.getCanonicalName().equals(void.class.getCanonicalName())) {
return new VoidTypeUsageNode();
}
if (clazz.isPrimitive()) {
return PrimitiveTypeUsage.getByName(clazz.getName());
}
if (clazz.isArray()) {
return new ArrayTypeUsage(toTypeUsage(clazz.getComponentType(), typeVariables, resolver));
}
TypeDefinition typeDefinition = new ReflectionBasedTypeDefinition((Class) type, resolver);
ReferenceTypeUsage referenceTypeUsage = new ReferenceTypeUsage(typeDefinition);
return referenceTypeUsage;
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
TypeDefinition typeDefinition = new ReflectionBasedTypeDefinition((Class) parameterizedType.getRawType(), resolver);
List<TypeUsage> typeParams = Arrays.stream(parameterizedType.getActualTypeArguments()).map((pt) -> toTypeUsage(pt, typeVariables, resolver)).collect(Collectors.toList());
return new ReferenceTypeUsage(typeDefinition, typeParams);
} else if (type instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable)type;
return toTypeUsage(typeVariable, typeVariables, resolver);
} else {
throw new UnsupportedOperationException(type.getClass().getCanonicalName());
}
}
public static TypeUsage toTypeUsage(TypeVariable typeVariable, Map<String, TypeUsage> typeVariables, SymbolResolver resolver) {
TypeVariableUsage.GenericDeclaration genericDeclaration = null;
List<TypeUsage> bounds = Arrays.stream(typeVariable.getBounds()).map((b)->toTypeUsage(b, typeVariables, resolver)).collect(Collectors.toList());
if (typeVariable.getGenericDeclaration() instanceof Class) {
if (typeVariables.containsKey(typeVariable.getName())) {
return typeVariables.get(typeVariable.getName());
} else {
Class c = (Class)typeVariable.getGenericDeclaration();
//throw new UnsolvedSymbolException("Cannot solve type variable " + typeVariable.getName());
return new ConcreteTypeVariableUsage(TypeVariableUsage.GenericDeclaration.onClass(c.getCanonicalName()), typeVariable.getName(), bounds);
}
} else if (typeVariable.getGenericDeclaration() instanceof Method) {
Method method = (Method)typeVariable.getGenericDeclaration();
genericDeclaration = TypeVariableUsage.GenericDeclaration.onMethod(method.getDeclaringClass().getCanonicalName(), ReflectionTypeDefinitionFactory.toMethodDefinition(method).getDescriptor());
} else {
throw new UnsupportedOperationException(typeVariable.getGenericDeclaration().getClass().getCanonicalName());
}
return new ConcreteTypeVariableUsage(genericDeclaration, typeVariable.getName(), bounds);
}
public static JvmConstructorDefinition findConstructorAmong(List<JvmType> argsTypes, SymbolResolver resolver, List<Constructor> constructors) {
List<MethodOrConstructor> methodOrConstructors = constructors.stream().map((m)->new MethodOrConstructor(m)).collect(Collectors.toList());
MethodOrConstructor methodOrConstructor = findMethodAmong(argsTypes, resolver, methodOrConstructors, "constructor");
if (methodOrConstructor == null) {
throw new RuntimeException("unresolved constructor for " + argsTypes);
}
return ReflectionTypeDefinitionFactory.toConstructorDefinition(methodOrConstructor.constructor);
}
public static Constructor findConstructorAmongActualParams(List<ActualParam> argsTypes, SymbolResolver resolver, List<Constructor> constructors) {
List<MethodOrConstructor> methodOrConstructors = constructors.stream().map((m)->new MethodOrConstructor(m)).collect(Collectors.toList());
MethodOrConstructor methodOrConstructor = findMethodAmongActualParams(argsTypes, resolver, methodOrConstructors, "constructor");
if (methodOrConstructor == null) {
throw new RuntimeException("unresolved constructor for " + argsTypes);
}
return methodOrConstructor.constructor;
}
public static Method findMethodAmong(String name, List<JvmType> argsTypes, SymbolResolver resolver, boolean staticContext, List<Method> methods) {
List<MethodOrConstructor> methodOrConstructors = methods.stream()
.filter((m) -> Modifier.isStatic(m.getModifiers()) == staticContext)
.filter((m) -> m.getName().equals(name))
.map((m) -> new MethodOrConstructor(m)).collect(Collectors.toList());
MethodOrConstructor methodOrConstructor = findMethodAmong(argsTypes, resolver, methodOrConstructors, name);
if (methodOrConstructor == null) {
throw new RuntimeException("unresolved method " + name + " for " + argsTypes);
}
return methodOrConstructor.method;
}
public static Optional<Method> findMethodAmongActualParams(String name, List<ActualParam> argsTypes, SymbolResolver resolver, boolean staticContext, List<Method> methods) {
List<MethodOrConstructor> methodOrConstructors = methods.stream()
.filter((m) -> Modifier.isStatic(m.getModifiers()) == staticContext)
.filter((m) -> m.getName().equals(name))
.map((m) -> new MethodOrConstructor(m)).collect(Collectors.toList());
MethodOrConstructor methodOrConstructor = findMethodAmongActualParams(argsTypes, resolver, methodOrConstructors, name);
if (methodOrConstructor == null) {
return Optional.empty();
}
return Optional.of(methodOrConstructor.method);
}
private static MethodOrConstructor findMethodAmong(List<JvmType> argsTypes, SymbolResolver resolver, List<MethodOrConstructor> methods, String desc) {
List<MethodOrConstructor> suitableMethods = new ArrayList<>();
for (MethodOrConstructor method : methods) {
if (method.getParameterCount() == argsTypes.size()) {
boolean match = true;
for (int i = 0; i < argsTypes.size(); i++) {
TypeUsage actualType = TypeUsageNode.fromJvmType(argsTypes.get(i), resolver.getRoot(), Collections.emptyMap());
TypeUsage formalType = ReflectionTypeDefinitionFactory.toTypeUsage(method.getParameterType(i), resolver);
if (!actualType.canBeAssignedTo(formalType)) {
match = false;
}
}
if (match) {
suitableMethods.add(method);
}
}
}
if (suitableMethods.size() == 0) {
return null;
} else if (suitableMethods.size() == 1) {
return suitableMethods.get(0);
} else {
return findMostSpecific(suitableMethods, new AmbiguousCallException(null, desc, argsTypes), argsTypes, resolver);
}
}
private static MethodOrConstructor findMethodAmongActualParams(List<ActualParam> argsTypes, SymbolResolver resolver, List<MethodOrConstructor> methods, String desc) {
List<MethodOrConstructor> suitableMethods = new ArrayList<>();
for (MethodOrConstructor method : methods) {
if (method.getParameterCount() == argsTypes.size()) {
boolean match = true;
for (int i = 0; i < argsTypes.size(); i++) {
TypeUsage actualType = argsTypes.get(i).getValue().calcType();
TypeUsage formalType = ReflectionTypeDefinitionFactory.toTypeUsage(method.getParameterType(i), resolver);
if (!actualType.canBeAssignedTo(formalType)) {
match = false;
}
}
if (match) {
suitableMethods.add(method);
}
}
}
if (suitableMethods.size() == 0) {
return null;
} else if (suitableMethods.size() == 1) {
return suitableMethods.get(0);
} else {
return findMostSpecific(suitableMethods,
new AmbiguousCallException(null, argsTypes, desc),
argsTypes.stream().map((ap)->ap.getValue().calcType().jvmType()).collect(Collectors.toList()),
resolver);
}
}
private static MethodOrConstructor findMostSpecific(List<MethodOrConstructor> methods, AmbiguousCallException exceptionToThrow,
List<JvmType> argsTypes, SymbolResolver resolver) {
MethodOrConstructor winningMethod = methods.get(0);
for (MethodOrConstructor other : methods.subList(1, methods.size())) {
if (isTheFirstMoreSpecific(winningMethod, other, argsTypes, resolver)) {
} else if (isTheFirstMoreSpecific(other, winningMethod, argsTypes, resolver)) {
winningMethod = other;
} else if (!isTheFirstMoreSpecific(winningMethod, other, argsTypes, resolver)) {
// neither is more specific
throw exceptionToThrow;
}
}
return winningMethod;
}
private static boolean isTheFirstMoreSpecific(MethodOrConstructor first, MethodOrConstructor second,
List<JvmType> argsTypes, SymbolResolver resolver) {
boolean atLeastOneParamIsMoreSpecific = false;
if (first.getParameterCount() != second.getParameterCount()) {
throw new IllegalArgumentException();
}
for (int i=0;i<first.getParameterCount();i++){
Class<?> paramFirst = first.getParameterType(i);
Class<?> paramSecond = second.getParameterType(i);
if (isTheFirstMoreSpecific(paramFirst, paramSecond, argsTypes.get(i), resolver)) {
atLeastOneParamIsMoreSpecific = true;
} else if (isTheFirstMoreSpecific(paramSecond, paramFirst, argsTypes.get(i), resolver)) {
return false;
}
}
return atLeastOneParamIsMoreSpecific;
}
private static boolean isTheFirstMoreSpecific(Class<?> firstType, Class<?> secondType, JvmType targetType, SymbolResolver resolver) {
boolean firstIsPrimitive = firstType.isPrimitive();
boolean secondIsPrimitive = secondType.isPrimitive();
boolean targetTypeIsPrimitive = targetType.isPrimitive();
// it is a match or a primitive promotion
if (targetTypeIsPrimitive && firstIsPrimitive && !secondIsPrimitive) {
return true;
}
if (targetTypeIsPrimitive && !firstIsPrimitive && secondIsPrimitive) {
return false;
}
if (firstType.isPrimitive() || firstType.isArray()) {
return false;
}
if (secondType.isPrimitive() || secondType.isArray()) {
return false;
}
// TODO consider generic parameters?
ReflectionBasedTypeDefinition firstDef = new ReflectionBasedTypeDefinition(firstType, resolver);
ReflectionBasedTypeDefinition secondDef = new ReflectionBasedTypeDefinition(secondType, resolver);
TypeUsage firstTypeUsage = new ReferenceTypeUsage(firstDef);
TypeUsage secondTypeUsage = new ReferenceTypeUsage(secondDef);
return firstTypeUsage.canBeAssignedTo(secondTypeUsage) && !secondTypeUsage.canBeAssignedTo(firstTypeUsage);
}
}