package st.gravel.support.jvm.runtime;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import st.gravel.support.jvm.ArrayExtensions;
public abstract class PolymorphicCallSite extends BaseCallSite {
private static final MethodHandle TYPE_TEST_METHOD_HANDLE = getTypeTestMethodHandle();
private static final MethodHandle NIL_TEST_METHOD_HANDLE = getNilTestMethodHandle();
public static boolean typeTest(Object receiver,
Class testClass) {
return receiver != null && receiver.getClass() == testClass;
}
public static boolean nilTest(Object receiver) {
return receiver == null;
}
protected static MethodHandle getTypeTestMethodHandle() {
try {
return MethodHandles.lookup().findStatic(
PolymorphicCallSite.class,
"typeTest",
MethodType.methodType(boolean.class, Object.class,
Class.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
protected static MethodHandle getNilTestMethodHandle() {
try {
return MethodHandles.lookup()
.findStatic(
PolymorphicCallSite.class,
"nilTest",
MethodType.methodType(boolean.class, Object.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
protected CacheEntry[] cache;
public static class CacheEntry {
public CacheEntry(Class<? extends Object> receiverClass,
MethodHandle methodHandle) {
super();
this.receiverClass = receiverClass;
this.methodHandle = methodHandle;
}
public final Class<? extends Object> receiverClass;
public final MethodHandle methodHandle;
}
public PolymorphicCallSite(Lookup lookup, MethodType type, String selector) {
super(lookup, type, selector);
}
@Override
protected synchronized void resetCache() {
this.cache = new CacheEntry[0];
}
private MethodHandle lookupMethodForNil() {
MethodHandle castHandle = findMethodForNil();
addCacheEntry(null, castHandle);
return castHandle;
}
private MethodHandle lookupMethod(final Class receiverClass) {
MethodHandle castHandle = findMethod(receiverClass);
addCacheEntry(receiverClass, castHandle);
return castHandle;
}
protected abstract MethodHandle findMethodForNil();
protected abstract MethodHandle findMethod(Class receiverClass);
@Override
protected synchronized void addTargetToCache(Object receiver) {
if (receiver == null) {
if (cacheIncludesReceiverClass(null))
return;
lookupMethodForNil();
} else {
Class<? extends Object> receiverClass = receiver.getClass();
if (cacheIncludesReceiverClass(receiverClass))
return;
lookupMethod(receiverClass);
}
setTargetFromCache();
}
private boolean cacheIncludesReceiverClass(
Class<? extends Object> receiverClass) {
for (CacheEntry entry : cache) {
if (entry.receiverClass == receiverClass)
return true;
}
return false;
}
private MethodHandle getGuardedMethod(CacheEntry entry,
MethodHandle fallback) {
MethodHandle test = entry.receiverClass == null ? NIL_TEST_METHOD_HANDLE
: MethodHandles.insertArguments(
TYPE_TEST_METHOD_HANDLE, 1, entry.receiverClass);
Class[] tail = ArrayExtensions.tail(type.parameterArray());
test = MethodHandles.dropArguments(test, 1, tail);
test = test.asType(MethodType.methodType(Boolean.TYPE,
type.parameterArray()));
MethodHandle guard1 = MethodHandles.guardWithTest(test,
entry.methodHandle, fallback);
return guard1;
}
private void addCacheEntry(final Class receiverClass,
MethodHandle castHandle) {
// if (cache.length == 0 && receiverClass != null) {
// cache = ArrayExtensions.copyWith_(cache, new CacheEntry(null,
// findMethodForNil()));
// }
cache = ArrayExtensions.copyWith_(cache, new CacheEntry(receiverClass,
castHandle));
}
private void setTargetFromCache() {
MethodHandle sum = fallback;
for (int i = cache.length - 1; i >= 0; i--) {
CacheEntry entry = cache[i];
sum = getGuardedMethod(entry, sum);
}
setTarget(sum);
}
}