package me.august.lumen.compile.resolve.convert;
import me.august.lumen.compile.resolve.convert.types.BoxingConversion;
import me.august.lumen.compile.resolve.convert.types.PrimitiveWidening;
import me.august.lumen.compile.resolve.convert.types.ReferenceWidening;
import me.august.lumen.compile.resolve.convert.types.UnboxingConversion;
import me.august.lumen.compile.resolve.data.ClassData;
import me.august.lumen.compile.resolve.data.MethodData;
import me.august.lumen.compile.resolve.data.exception.AmbiguousMethodException;
import me.august.lumen.compile.resolve.lookup.ClassLookup;
import org.objectweb.asm.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import static me.august.lumen.common.BytecodeUtil.*;
public class Conversions {
private Conversions() {}
/**
* Creates a ConversionStrategy from converting from the original
* type to the target type.
* @param original The original type
* @param target The target Type
* @param lookup Source for class lookups
* @return A ConversionStrategy
*/
public static ConversionStrategy convert(Type original, Type target, ClassLookup lookup) {
ConversionStrategy cs = new ConversionStrategy();
convert(original, target, cs, lookup);
return cs;
}
private static void convert(Type original, Type target, ConversionStrategy cs, ClassLookup lookup) {
if (isNumeric(original) && isNumeric(target)) {
// target is wider than original
if (compareWidth(original, target) < 0) {
cs.addStep(new PrimitiveWidening(original, target));
} else {
cs.addStep(null);
}
} else if (isPrimitive(original) && isObject(target)) {
Type boxed = toBoxedType(original);
cs.addStep(new BoxingConversion(original, boxed));
// boxed object type is not target object type
if (!target.equals(boxed)) {
convert(boxed, target, cs, lookup);
}
} else if (isObject(original) && isPrimitive(target)) {
Type unboxed = toUnboxedType(original);
cs.addStep(new UnboxingConversion(original, unboxed));
// unboxed primitive type is not target primitive type
if (!target.equals(unboxed)) {
convert(unboxed, target, cs, lookup);
}
} else if (isObject(original) && isObject(target)) {
ClassData data = lookup.lookup(original.getClassName());
if (data.isAssignableTo(target.getClassName(), lookup)) {
cs.addStep(new ReferenceWidening(original, target));
} else {
cs.addStep(null);
}
}
}
/**
* Picks the suitable overloaded method based on the
* given types. Throws an AmbiguousMethodException if
* two or more methods' types have equal distance to
* the given types.
* @param types The actual argument type
* @param methods The methods to pick from
* @param lookup The ClassLookup to use
* @return The applicable method
* @throws AmbiguousMethodException
*/
public static MethodData pickMethod(Type[] types, MethodData[] methods, ClassLookup lookup)
throws AmbiguousMethodException {
// distance rank -> [methods]
// (lower rank is closer)
// examples:
// distance of Object to Object = 0
// distance of String to Object = 1
// distance of RuntimeException to Throwable = 2
TreeMap<Integer, List<MethodData>> subTypeRanks = new TreeMap<>();
// outer loop label for continuing to next method
outer:
for (MethodData method : methods) {
// method cannot be applicable if argument lengths differ
if (method.getParamTypes().length != types.length)
continue;
// distance rank
int rank = 0;
for (int i = 0; i < method.getParamTypes().length; i++) {
Type paramType = method.getParamTypes()[i];
if (paramType.getSort() != Type.OBJECT) {
// don't consider method for distance ranking
// if argument isn't an object
// TODO include primitive distance for ranking?
continue outer;
}
ClassData actualTypeClass = lookup.lookup(types[i]);
if (actualTypeClass == null) {
// can't resolve argument type, method not applicable
// (we should probably throw an exception...)
continue outer;
}
// distance from actual argument type to method's argument type
int dist = actualTypeClass.assignableDistance(paramType, lookup);
// nonnegative distance implies types are compatible
// via a reference widening conversion
if (dist > -1) {
rank += dist;
} else {
// actual argument type and method's argument type
// are not compatible, go to next method
continue outer;
}
}
// associate ranking with the method
// value type is a list because multiple methods can have
// the same ranking
List<MethodData> list = subTypeRanks.get(rank);
if (list == null) {
list = new ArrayList<>();
list.add(method);
subTypeRanks.put(rank, list);
} else {
list.add(method);
}
}
// at least one method was ranked
if (!subTypeRanks.isEmpty()) {
// list of methods with lowest ranking
List<MethodData> applicable = subTypeRanks.firstEntry().getValue();
if (applicable.size() > 1) {
// multiple methods had the same lowest ranking
throw new AmbiguousMethodException(applicable);
} else {
// list has one element, get first
return applicable.get(0);
}
}
// TODO consider other types of type conversions
return null;
}
}