Skip to content

Instantly share code, notes, and snippets.

@forax
Created January 12, 2018 17:33
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save forax/e49d63d50e5973f602f720ba3b6ea1b8 to your computer and use it in GitHub Desktop.
Save forax/e49d63d50e5973f602f720ba3b6ea1b8 to your computer and use it in GitHub Desktop.
How to simulate @stable semantics with method handles ?
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