package com.jsoniter.spi; import java.lang.reflect.*; import java.util.*; public class JsoniterSpi { static List<Extension> extensions = new ArrayList<Extension>(); static Map<Class, Class> typeImpls = new HashMap<Class, Class>(); static volatile Map<String, Encoder> encoders = new HashMap<String, Encoder>(); static volatile Map<String, Decoder> decoders = new HashMap<String, Decoder>(); static volatile Map<Class, Extension> objectFactories = new HashMap<Class, Extension>(); public static void registerExtension(Extension extension) { extensions.add(extension); } public static List<Extension> getExtensions() { return Collections.unmodifiableList(extensions); } public static void registerTypeImplementation(Class superClazz, Class implClazz) { typeImpls.put(superClazz, implClazz); } public static Class getTypeImplementation(Class superClazz) { return typeImpls.get(superClazz); } public static void registerTypeDecoder(Class clazz, Decoder decoder) { addNewDecoder(TypeLiteral.create(clazz).getDecoderCacheKey(), decoder); } public static void registerTypeDecoder(TypeLiteral typeLiteral, Decoder decoder) { addNewDecoder(typeLiteral.getDecoderCacheKey(), decoder); } public static void registerPropertyDecoder(Class clazz, String field, Decoder decoder) { addNewDecoder(field + "@" + TypeLiteral.create(clazz).getDecoderCacheKey(), decoder); } public static void registerPropertyDecoder(TypeLiteral typeLiteral, String field, Decoder decoder) { addNewDecoder(field + "@" + typeLiteral.getDecoderCacheKey(), decoder); } public static void registerTypeEncoder(Class clazz, Encoder encoder) { addNewEncoder(TypeLiteral.create(clazz).getEncoderCacheKey(), encoder); } public static void registerTypeEncoder(TypeLiteral typeLiteral, Encoder encoder) { addNewEncoder(typeLiteral.getDecoderCacheKey(), encoder); } public static void registerPropertyEncoder(Class clazz, String field, Encoder encoder) { addNewEncoder(field + "@" + TypeLiteral.create(clazz).getEncoderCacheKey(), encoder); } public static void registerPropertyEncoder(TypeLiteral typeLiteral, String field, Encoder encoder) { addNewEncoder(field + "@" + typeLiteral.getDecoderCacheKey(), encoder); } public static Decoder getDecoder(String cacheKey) { return decoders.get(cacheKey); } public synchronized static void addNewDecoder(String cacheKey, Decoder decoder) { HashMap<String, Decoder> newCache = new HashMap<String, Decoder>(decoders); newCache.put(cacheKey, decoder); decoders = newCache; } public static Encoder getEncoder(String cacheKey) { return encoders.get(cacheKey); } public synchronized static void addNewEncoder(String cacheKey, Encoder encoder) { HashMap<String, Encoder> newCache = new HashMap<String, Encoder>(encoders); newCache.put(cacheKey, encoder); encoders = newCache; } public static boolean canCreate(Class clazz) { if (objectFactories.containsKey(clazz)) { return true; } for (Extension extension : extensions) { if (extension.canCreate(clazz)) { addObjectFactory(clazz, extension); return true; } } return false; } public static Object create(Class clazz) { return objectFactories.get(clazz).create(clazz); } private synchronized static void addObjectFactory(Class clazz, Extension extension) { HashMap<Class, Extension> copy = new HashMap<Class, Extension>(objectFactories); copy.put(clazz, extension); objectFactories = copy; } public static ClassDescriptor getDecodingClassDescriptor(Class clazz, boolean includingPrivate) { Map<String, Type> lookup = collectTypeVariableLookup(clazz); ClassDescriptor desc = new ClassDescriptor(); desc.clazz = clazz; desc.lookup = lookup; desc.ctor = getCtor(clazz); desc.fields = getFields(lookup, clazz, includingPrivate); desc.setters = getSetters(lookup, clazz, includingPrivate); desc.wrappers = new ArrayList<WrapperDescriptor>(); desc.unWrappers = new ArrayList<Method>(); for (Extension extension : extensions) { extension.updateClassDescriptor(desc); } for (Binding field : desc.fields) { if (field.valueType instanceof Class) { Class valueClazz = (Class) field.valueType; if (valueClazz.isArray()) { field.valueCanReuse = false; continue; } } field.valueCanReuse = field.valueTypeLiteral.nativeType == null; } decodingDeduplicate(desc); if (includingPrivate) { if (desc.ctor.ctor != null) { desc.ctor.ctor.setAccessible(true); } if (desc.ctor.staticFactory != null) { desc.ctor.staticFactory.setAccessible(true); } for (WrapperDescriptor setter : desc.wrappers) { setter.method.setAccessible(true); } } for (Binding binding : desc.allDecoderBindings()) { if (binding.fromNames == null) { binding.fromNames = new String[]{binding.name}; } if (binding.field != null && includingPrivate) { binding.field.setAccessible(true); } if (binding.method != null && includingPrivate) { binding.method.setAccessible(true); } if (binding.decoder != null) { JsoniterSpi.addNewDecoder(binding.decoderCacheKey(), binding.decoder); } } return desc; } public static ClassDescriptor getEncodingClassDescriptor(Class clazz, boolean includingPrivate) { Map<String, Type> lookup = collectTypeVariableLookup(clazz); ClassDescriptor desc = new ClassDescriptor(); desc.clazz = clazz; desc.lookup = lookup; desc.fields = getFields(lookup, clazz, includingPrivate); desc.getters = getGetters(lookup, clazz, includingPrivate); desc.wrappers = new ArrayList<WrapperDescriptor>(); desc.unWrappers = new ArrayList<Method>(); for (Extension extension : extensions) { extension.updateClassDescriptor(desc); } encodingDeduplicate(desc); for (Binding binding : desc.allEncoderBindings()) { if (binding.toNames == null) { binding.toNames = new String[]{binding.name}; } if (binding.field != null && includingPrivate) { binding.field.setAccessible(true); } if (binding.method != null && includingPrivate) { binding.method.setAccessible(true); } if (binding.encoder != null) { JsoniterSpi.addNewEncoder(binding.encoderCacheKey(), binding.encoder); } } return desc; } private static void decodingDeduplicate(ClassDescriptor desc) { HashMap<String, Binding> byName = new HashMap<String, Binding>(); for (Binding field : desc.fields) { if (byName.containsKey(field.name)) { throw new JsonException("field name conflict: " + field.name); } byName.put(field.name, field); } for (Binding setter : desc.setters) { Binding existing = byName.get(setter.name); if (existing == null) { byName.put(setter.name, setter); continue; } if (desc.fields.remove(existing)) { continue; } throw new JsonException("setter name conflict: " + setter.name); } for (WrapperDescriptor wrapper : desc.wrappers) { for (Binding param : wrapper.parameters) { Binding existing = byName.get(param.name); if (existing == null) { byName.put(param.name, param); continue; } if (desc.fields.remove(existing)) { continue; } if (desc.setters.remove(existing)) { continue; } throw new JsonException("wrapper parameter name conflict: " + param.name); } } for (Binding param : desc.ctor.parameters) { Binding existing = byName.get(param.name); if (existing == null) { byName.put(param.name, param); continue; } if (desc.fields.remove(existing)) { continue; } if (desc.setters.remove(existing)) { continue; } throw new JsonException("ctor parameter name conflict: " + param.name); } } private static void encodingDeduplicate(ClassDescriptor desc) { HashMap<String, Binding> byName = new HashMap<String, Binding>(); for (Binding field : desc.fields) { if (byName.containsKey(field.name)) { throw new JsonException("field name conflict: " + field.name); } byName.put(field.name, field); } for (Binding getter : desc.getters) { Binding existing = byName.get(getter.name); if (existing == null) { byName.put(getter.name, getter); continue; } if (desc.fields.remove(existing)) { continue; } throw new JsonException("getter name conflict: " + getter.name); } } private static ConstructorDescriptor getCtor(Class clazz) { ConstructorDescriptor cctor = new ConstructorDescriptor(); if (canCreate(clazz)) { cctor.objectFactory = objectFactories.get(clazz); return cctor; } try { cctor.ctor = clazz.getDeclaredConstructor(); } catch (Exception e) { cctor.ctor = null; } return cctor; } private static List<Binding> getFields(Map<String, Type> lookup, Class clazz, boolean includingPrivate) { ArrayList<Binding> bindings = new ArrayList<Binding>(); for (Field field : getAllFields(clazz, includingPrivate)) { if (Modifier.isStatic(field.getModifiers())) { continue; } if (Modifier.isTransient(field.getModifiers())) { continue; } if (!includingPrivate && !Modifier.isPublic(field.getType().getModifiers())) { continue; } if (includingPrivate) { field.setAccessible(true); } Binding binding = createBindingFromField(lookup, clazz, field); bindings.add(binding); } return bindings; } private static Binding createBindingFromField(Map<String, Type> lookup, Class clazz, Field field) { try { Binding binding = new Binding(clazz, lookup, field.getGenericType()); binding.fromNames = new String[]{field.getName()}; binding.name = field.getName(); binding.annotations = field.getAnnotations(); binding.field = field; return binding; } catch (Exception e) { throw new JsonException("failed to create binding for field: " + field, e); } } private static List<Field> getAllFields(Class clazz, boolean includingPrivate) { List<Field> allFields = Arrays.asList(clazz.getFields()); if (includingPrivate) { allFields = new ArrayList<Field>(); Class current = clazz; while (current != null) { allFields.addAll(Arrays.asList(current.getDeclaredFields())); current = current.getSuperclass(); } } return allFields; } private static List<Binding> getSetters(Map<String, Type> lookup, Class clazz, boolean includingPrivate) { ArrayList<Binding> setters = new ArrayList<Binding>(); for (Method method : getAllMethods(clazz, includingPrivate)) { if (Modifier.isStatic(method.getModifiers())) { continue; } String methodName = method.getName(); if (methodName.length() < 4) { continue; } if (!methodName.startsWith("set")) { continue; } Type[] paramTypes = method.getGenericParameterTypes(); if (paramTypes.length != 1) { continue; } if (!includingPrivate && !Modifier.isPublic(method.getParameterTypes()[0].getModifiers())) { continue; } if (includingPrivate) { method.setAccessible(true); } try { String fromName = translateSetterName(methodName); Binding binding = new Binding(clazz, lookup, paramTypes[0]); binding.fromNames = new String[]{fromName}; binding.name = fromName; binding.method = method; binding.annotations = method.getAnnotations(); setters.add(binding); } catch (Exception e) { throw new JsonException("failed to create binding from setter: " + method, e); } } return setters; } private static List<Method> getAllMethods(Class clazz, boolean includingPrivate) { List<Method> allMethods = Arrays.asList(clazz.getMethods()); if (includingPrivate) { allMethods = new ArrayList<Method>(); Class current = clazz; while (current != null) { allMethods.addAll(Arrays.asList(current.getDeclaredMethods())); current = current.getSuperclass(); } } return allMethods; } private static String translateSetterName(String methodName) { if (!methodName.startsWith("set")) { return null; } String fromName = methodName.substring("set".length()); char[] fromNameChars = fromName.toCharArray(); fromNameChars[0] = Character.toLowerCase(fromNameChars[0]); fromName = new String(fromNameChars); return fromName; } private static List<Binding> getGetters(Map<String, Type> lookup, Class clazz, boolean includingPrivate) { ArrayList<Binding> getters = new ArrayList<Binding>(); for (Method method : getAllMethods(clazz, includingPrivate)) { if (Modifier.isStatic(method.getModifiers())) { continue; } String methodName = method.getName(); if ("getClass".equals(methodName)) { continue; } if (methodName.length() < 4) { continue; } if (!methodName.startsWith("get")) { continue; } if (method.getGenericParameterTypes().length != 0) { continue; } String toName = methodName.substring("get".length()); char[] fromNameChars = toName.toCharArray(); fromNameChars[0] = Character.toLowerCase(fromNameChars[0]); toName = new String(fromNameChars); Binding getter = new Binding(clazz, lookup, method.getGenericReturnType()); getter.toNames = new String[]{toName}; getter.name = toName; getter.method = method; getter.annotations = method.getAnnotations(); getters.add(getter); } return getters; } public static void dump() { for (String cacheKey : decoders.keySet()) { System.err.println(cacheKey); } for (String cacheKey : encoders.keySet()) { System.err.println(cacheKey); } } private static Map<String, Type> collectTypeVariableLookup(Type type) { HashMap<String, Type> vars = new HashMap<String, Type>(); if (null == type) { return vars; } if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) type; Type[] actualTypeArguments = pType.getActualTypeArguments(); Class clazz = (Class) pType.getRawType(); for (int i = 0; i < clazz.getTypeParameters().length; i++) { TypeVariable variable = clazz.getTypeParameters()[i]; vars.put(variable.getName() + "@" + clazz.getCanonicalName(), actualTypeArguments[i]); } vars.putAll(collectTypeVariableLookup(clazz.getGenericSuperclass())); return vars; } if (type instanceof Class) { Class clazz = (Class) type; vars.putAll(collectTypeVariableLookup(clazz.getGenericSuperclass())); return vars; } throw new JsonException("unexpected type: " + type); } }