Created
January 12, 2018 17:33
How to simulate @stable semantics with method handles ?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import static java.lang.invoke.MethodHandles.constant; | |
import static java.lang.invoke.MethodHandles.dropArguments; | |
import static java.lang.invoke.MethodHandles.exactInvoker; | |
import static java.lang.invoke.MethodHandles.foldArguments; | |
import static java.lang.invoke.MethodHandles.lookup; | |
import static java.lang.invoke.MethodType.methodType; | |
import java.lang.invoke.MethodHandle; | |
import java.lang.invoke.MethodHandles; | |
import java.lang.invoke.MethodHandles.Lookup; | |
import java.lang.invoke.MethodType; | |
import java.lang.invoke.MutableCallSite; | |
import java.lang.reflect.UndeclaredThrowableException; | |
import java.util.function.Function; | |
import java.util.function.ToIntFunction; | |
public enum Constantifier { | |
FIRST_WIN(getter -> object -> { | |
Object result = getter.invoke(object); | |
MethodType type = getter.type(); | |
return dropArguments(constant(type.returnType(), result), 0, type.parameterType(0)) | |
.asType(type.erase()); | |
}), | |
STABLE(getter -> object -> { | |
Object result = getter.invoke(object); | |
MethodType type = getter.type(); | |
MethodHandle test = ConstCS.IDENTITY_CHECK.bindTo(result).asType(methodType(boolean.class, type.parameterType(0))); | |
MethodHandle target = dropArguments(constant(type.returnType(), result), 0, type.parameterType(0)); | |
return MethodHandles.guardWithTest(test, target, getter) | |
.asType(type.erase()); | |
}); | |
interface Strategy { | |
MethodHandle create(Object object) throws Throwable; | |
} | |
private final Function<MethodHandle, Strategy> strategyFactory; | |
private Constantifier(Function<MethodHandle, Strategy> strategyFactory) { | |
this.strategyFactory = strategyFactory; | |
} | |
public <T, U> Function<T, U> get(Lookup lookup, Class<T> declaringClass, String name, Class<U> type) { | |
MethodHandle getter = findGetter(lookup, declaringClass, name, type); | |
MethodHandle mh = new ConstCS(getter.type().erase(), strategyFactory.apply(getter)).dynamicInvoker(); | |
return o -> { | |
try { | |
return (U)mh.invokeExact(o); | |
} catch (Throwable e) { | |
throw rethrow(e); | |
} | |
}; | |
} | |
public <T> ToIntFunction<T> getInt(Lookup lookup, Class<T> declaringClass, String name) { | |
MethodHandle getter = findGetter(lookup, declaringClass, name, int.class); | |
MethodHandle mh = new ConstCS(getter.type().erase(), strategyFactory.apply(getter)).dynamicInvoker(); | |
return o -> { | |
try { | |
return (int)mh.invokeExact(o); | |
} catch (Throwable e) { | |
throw rethrow(e); | |
} | |
}; | |
} | |
static class ConstCS extends MutableCallSite { | |
private static final MethodHandle FALLBACK; | |
static final MethodHandle IDENTITY_CHECK; | |
static { | |
Lookup lookup = lookup(); | |
try { | |
FALLBACK = lookup.findVirtual(ConstCS.class, "fallback", methodType(MethodHandle.class, Object.class)); | |
IDENTITY_CHECK = lookup.findStatic(ConstCS.class, "identityCheck", methodType(boolean.class, Object.class, Object.class)); | |
} catch (NoSuchMethodException | IllegalAccessException e) { | |
throw new AssertionError(e); | |
} | |
} | |
private final Strategy strategy; | |
private final MethodHandle fallback; | |
private final Object lock = new Object(); | |
ConstCS(MethodType type, Strategy strategy) { | |
super(type); | |
MethodHandle fallback = foldArguments(exactInvoker(type), FALLBACK.bindTo(this)); | |
this.strategy = strategy; | |
this.fallback = fallback; | |
setTarget(fallback); | |
} | |
@SuppressWarnings("unused") | |
private MethodHandle fallback(Object o) throws Throwable { | |
MethodHandle target = strategy.create(o); | |
synchronized(lock) { // only one value can be seen | |
MethodHandle oldTarget = getTarget(); | |
if (oldTarget == fallback) { | |
setTarget(target); | |
return target; | |
} | |
return oldTarget; | |
} | |
} | |
@SuppressWarnings("unused") | |
private static boolean identityCheck(Object value, Object value2) { | |
return value == value2; | |
} | |
} | |
private static MethodHandle findGetter(Lookup lookup, Class<?> declaringClass, String name, Class<?> type) { | |
try { | |
return lookup.findGetter(declaringClass, name, type); | |
} catch (NoSuchFieldException e) { | |
throw (NoSuchFieldError)new NoSuchFieldError().initCause(e); | |
} catch (IllegalAccessException e) { | |
throw (IllegalAccessError)new IllegalAccessError().initCause(e); | |
} | |
} | |
private static UndeclaredThrowableException rethrow(Throwable e) { | |
if (e instanceof RuntimeException) { | |
throw (RuntimeException)e; | |
} | |
if (e instanceof Error) { | |
throw (Error)e; | |
} | |
return new UndeclaredThrowableException(e); | |
} | |
// ----------------------------------------- | |
static class Hello { | |
final String message; | |
final int age; | |
public Hello(String message, int age) { this.message = message; this.age = age; } | |
} | |
private static final Hello HELLO = new Hello("hello", 23); | |
private static final Function<Hello, String> FIRSTWIN_GET = | |
FIRST_WIN.get(MethodHandles.lookup(), Hello.class, "message", String.class); | |
private static final ToIntFunction<Hello> FIRSTWIN_GETINT = | |
FIRST_WIN.getInt(MethodHandles.lookup(), Hello.class, "age"); | |
private static final Function<Hello, String> STABLE_GET = | |
STABLE.get(MethodHandles.lookup(), Hello.class, "message", String.class); | |
private static final ToIntFunction<Hello> STABLE_GETINT = | |
STABLE.getInt(MethodHandles.lookup(), Hello.class, "age"); | |
public static void main(String[] args) { | |
for(int i = 0; i < 2; i++) { | |
System.out.println(FIRSTWIN_GET.apply(HELLO)); | |
} | |
for(int i = 0; i < 2; i++) { | |
System.out.println(FIRSTWIN_GETINT.applyAsInt(HELLO)); | |
} | |
for(int i = 0; i < 2; i++) { | |
System.out.println(STABLE_GET.apply(HELLO)); | |
} | |
for(int i = 0; i < 2; i++) { | |
System.out.println(STABLE_GETINT.applyAsInt(HELLO)); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment