Last active
September 16, 2016 16:27
-
-
Save forax/76124b69631475105f87ddd2205a4d91 to your computer and use it in GitHub Desktop.
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
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