package tc.oc.commons.core.reflect;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import com.google.common.collect.ImmutableList;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.common.reflect.TypeToken;
import tc.oc.commons.core.stream.Collectors;
import tc.oc.commons.core.util.Utils;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Equivalent of {@link java.lang.invoke.MethodType} using {@link TypeToken}s
* in order to support generics.
*/
class GenericMethodType<T> {
private final TypeToken<T> returnType;
private final ImmutableList<TypeToken<?>> parameterTypes;
public GenericMethodType(TypeToken<T> returnType, List<TypeToken<?>> parameterTypes) {
this.returnType = checkNotNull(returnType);
this.parameterTypes = ImmutableList.copyOf(parameterTypes);
}
public static GenericMethodType<?> of(Method method) {
return new GenericMethodType<>(TypeToken.of(method.getGenericReturnType()),
Stream.of(method.getGenericParameterTypes())
.map(TypeToken::of)
.collect(Collectors.toImmutableList()));
}
public static GenericMethodType<?> of(Invokable<?, ?> invokable) {
return new GenericMethodType<>(invokable.getReturnType(),
invokable.getParameters()
.stream()
.map(Parameter::getType)
.collect(Collectors.toImmutableList()));
}
public TypeToken<T> returnType() {
return returnType;
}
public List<TypeToken<?>> parameterTypes() {
return parameterTypes;
}
@Override
public int hashCode() {
return Objects.hash(returnType, parameterTypes);
}
@Override
public boolean equals(Object obj) {
return Utils.equals(GenericMethodType.class, this, obj, that ->
this.returnType.equals(that.returnType) && this.parameterTypes.equals(that.parameterTypes)
);
}
@Override
public String toString() {
return returnType.toString() +
"(" +
parameterTypes.stream()
.map(Object::toString)
.collect(java.util.stream.Collectors.joining(", ")) +
")";
}
public GenericMethodType<? extends T> resolveIn(TypeToken<?> context) {
final TypeToken<? extends T> returnType = (TypeToken<? extends T>) context.resolveType(this.returnType.getType());
final ImmutableList<TypeToken<?>> parameterTypes = this.parameterTypes.stream()
.map(t -> context.resolveType(t.getType()))
.collect(Collectors.toImmutableList());
return returnType.equals(this.returnType) && parameterTypes.equals(this.parameterTypes)
? this
: new GenericMethodType<>(returnType, parameterTypes);
}
public boolean canInvoke(GenericMethodType<?> method) {
if(!returnType.isAssignableFrom(method.returnType)) return false;
if(parameterTypes.size() != method.parameterTypes.size()) return false;
for(int i = 0; i < parameterTypes.size(); i++) {
if(!method.parameterTypes.get(i).isAssignableFrom(parameterTypes.get(i))) return false;
}
return true;
}
public boolean canInvoke(Invokable<?, ?> invokable) {
return canInvoke(of(invokable));
}
public boolean canInvoke(TypeToken<?> target, Method method) {
return canInvoke(target.method(method));
}
}