Skip to content

Instantly share code, notes, and snippets.

@forax
Last active September 16, 2016 16:27
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/76124b69631475105f87ddd2205a4d91 to your computer and use it in GitHub Desktop.
Save forax/76124b69631475105f87ddd2205a4d91 to your computer and use it in GitHub Desktop.
package fr.umlv.json;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.invoke.CallSite;
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.invoke.StringConcatException;
import java.lang.invoke.StringConcatFactory;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
public class FastJSONSerializer {
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface JSONProperty {
String value() default "";
}
static String propertyName(Method method) {
JSONProperty property = method.getAnnotation(JSONProperty.class);
String name = property.value();
if (!name.isEmpty()) {
return name;
}
name = method.getName();
return Character.toLowerCase(name.charAt(3)) + name.substring(4);
}
static boolean isUserDefined(Class<?> type) {
return !type.isPrimitive() && !type.getName().startsWith("java.lang.");
}
static final ThreadLocal<HashSet<Class<?>>> REENTRANT = ThreadLocal.withInitial(HashSet::new);
private static final ClassValue<MethodHandle> JSON_ENCODER_MH =
new ClassValue<MethodHandle>() {
@Override
protected MethodHandle computeValue(Class<?> type) {
Lookup lookup = MethodHandles.publicLookup();
ArrayList<MethodHandle> mhs = new ArrayList<>();
ArrayList<Class<?>> types = new ArrayList<>();
StringBuilder builder = new StringBuilder();
String separator = "";
builder.append("{ ");
for(Method method: type.getMethods()) {
if (!method.isAnnotationPresent(JSONProperty.class)) {
continue;
}
builder.append(separator);
separator = ", ";
MethodHandle mh;
try {
mh = lookup.unreflect(method);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
Class<?> returnType = method.getReturnType();
String quote = returnType == String.class? "\"": "";
if (isUserDefined(returnType)) {
HashSet<Class<?>> visited = REENTRANT.get();
if (visited.contains(returnType)) {
throw new IllegalStateException("cycle in the definitions !");
}
visited.add(returnType);
try {
mh = MethodHandles.filterReturnValue(mh, getJSONSerializer(returnType));
} finally {
visited.remove(returnType);
if (visited.isEmpty()) {
REENTRANT.remove();
}
}
returnType = String.class;
quote = "";
}
types.add(returnType);
mhs.add(mh);
builder.append('"').append(propertyName(method)).append("\": ");
builder.append(quote).append("\u0001").append(quote);
}
builder.append(" }");
MethodType concatType = MethodType.methodType(String.class, types);
String recipe = builder.toString();
MethodHandle target;
try {
CallSite cs = StringConcatFactory.makeConcatWithConstants(MethodHandles.lookup(), "", concatType, recipe);
target = cs.dynamicInvoker();
} catch (StringConcatException e) {
throw new IllegalStateException(e);
}
target = MethodHandles.filterArguments(target, 0, mhs.toArray(new MethodHandle[0]));
target = MethodHandles.permuteArguments(target, MethodType.methodType(String.class, type), new int[mhs.size()]);
return target;
}
};
public static MethodHandle getJSONSerializer(Class<?> type) {
return JSON_ENCODER_MH.get(type);
}
static class GenericSerlizerMCS extends MutableCallSite {
private static final MethodHandle FALLBACK, TYPE_CHECK;
static {
Lookup lookup = MethodHandles.lookup();
try {
FALLBACK = lookup.findVirtual(GenericSerlizerMCS.class, "fallback", MethodType.methodType(String.class, Object.class));
TYPE_CHECK = lookup.findStatic(GenericSerlizerMCS.class, "typeCheck", MethodType.methodType(boolean.class, Object.class, Class.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
private static boolean typeCheck(Object o, Class<?> type) {
return o.getClass() == type;
}
public GenericSerlizerMCS() {
super(MethodType.methodType(String.class, Object.class));
this.setTarget(FALLBACK.bindTo(this));
}
private String fallback(Object o) throws Throwable {
Class<? extends Object> receiverClass = o.getClass();
MethodHandle target = getJSONSerializer(receiverClass);
target = target.asType(type());
MethodHandle guard = MethodHandles.guardWithTest(MethodHandles.insertArguments(TYPE_CHECK, 1, receiverClass),
target,
new GenericSerlizerMCS().dynamicInvoker());
setTarget(guard);
return (String) target.invokeExact(o);
}
}
public static MethodHandle getGenericJSONSerializer() {
return new GenericSerlizerMCS().dynamicInvoker();
}
public static class Person {
private final int age;
public Person(int age) {
this.age = age;
}
@JSONProperty
public int getAge() {
return age;
}
}
public static class Car {
private final String name;
private final Person driver;
public Car(String name, Person driver) {
this.name = name;
this.driver = driver;
}
@JSONProperty
public String getName() {
return name;
}
@JSONProperty
public Person getDriver() {
return driver;
}
}
public static void main(String[] args) throws Throwable {
Car car = new Car("mustang", new Person(21));
MethodHandle jsonSerializer = getJSONSerializer(Car.class);
String text = (String)jsonSerializer.invokeExact(car);
System.out.println(text);
MethodHandle jsonSerializer2 = getGenericJSONSerializer();
String text2 = (String)jsonSerializer2.invokeExact((Object)car);
System.out.println(text2);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment