package io.gsonfire; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import io.gsonfire.gson.*; import io.gsonfire.postprocessors.MergeMapPostProcessor; import io.gsonfire.postprocessors.methodinvoker.MethodInvokerPostProcessor; import io.gsonfire.util.Mapper; import io.gsonfire.util.reflection.CachedReflectionFactory; import io.gsonfire.util.reflection.Factory; import io.gsonfire.util.reflection.FieldInspector; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * @autor: julio */ public final class GsonFireBuilder { private final Map<Class, ClassConfig> classConfigMap = new HashMap<Class, ClassConfig>(); private final Map<Class, Mapper> wrappedClasses = new HashMap<Class, Mapper>(); private final List<Class> orderedClasses = new ArrayList<Class>(); private final List<FireExclusionStrategy> serializationExclusions = new ArrayList<FireExclusionStrategy>(); private final FieldInspector fieldInspector = new FieldInspector(); private final Factory factory = new CachedReflectionFactory(); private final Map<Class, Enum> enumDefaultValues = new HashMap<Class, Enum>(); private DateSerializationPolicy dateSerializationPolicy; private boolean dateDeserializationStrict = true; private TimeZone serializeTimeZone = TimeZone.getDefault(); private boolean enableExposeMethodResults = false; private boolean enableExclusionByValueStrategies = false; private ClassConfig getClassConfig(Class clazz){ ClassConfig result = classConfigMap.get(clazz); if(result == null){ result = new ClassConfig(clazz); classConfigMap.put(clazz, result); insertOrdered(orderedClasses, clazz); } return result; } private static void insertOrdered(List<Class> classes, Class clazz) { for(int i = classes.size() - 1; i >= 0; i--) { Class current = classes.get(i); if(current.isAssignableFrom(clazz)) { classes.add(i + 1, clazz); return; } } classes.add(0, clazz); } /** * Registers a Type selector for the Class specified. <br /> * A type selector is in charge of deciding which sub class to use when converting a json * into an object.<br /> * See <a href="http://goo.gl/qKo7z"> docs and example</a> * @param clazz * @param factory * @param <T> * @return */ public <T> GsonFireBuilder registerTypeSelector(Class<T> clazz, TypeSelector<T> factory){ ClassConfig config = getClassConfig(clazz); config.setTypeSelector(factory); return this; } /** * Registers a Post processor for the Class specified. <br /> * A post processor is a class that will add new fields to a generated json just after generation, or that * will prepare a class just created from a json.<br /> * See <a href="http://goo.gl/5fLLN"> docs and example</a> * * @param clazz * @param postProcessor * @param <T> * @return */ public <T> GsonFireBuilder registerPostProcessor(Class<T> clazz, PostProcessor<? super T> postProcessor){ ClassConfig config = getClassConfig(clazz); config.getPostProcessors().add(postProcessor); return this; } /** * Registers a pre processor for the Class specified. <br /> * A pre processor is a class that will be given the gson to be deserialized in case it wants to change it before * it actually gets deserialized into a class * See <a href="http://goo.gl/b8V1AA"> docs and example</a> * * @param clazz * @param preProcessor * @param <T> * @return */ public <T> GsonFireBuilder registerPreProcessor(Class<T> clazz, PreProcessor<? super T> preProcessor){ ClassConfig config = getClassConfig(clazz); config.getPreProcessors().add(preProcessor); return this; } /** * Configures the resulting Gson to serialize/unserialize Date instances with a policy * @param policy * @return */ public GsonFireBuilder dateSerializationPolicy(DateSerializationPolicy policy){ dateSerializationPolicy = policy; return this; } /** * A given class will be wrapped/unwrapped with a given string * during serialization/deserialization. * * @param clazz * @param <T> * @return */ public <T> GsonFireBuilder wrap(final Class<T> clazz, final String name) { wrap(clazz, new Mapper<T, String>() { @Override public String map(Object from) { return name; } }); return this; } /** * A given class will be wrapped/unwrapped with a string generated by a mapper * during serialization/deserialization. * * @param clazz * @param mapper * @param <T> * @return */ public <T> GsonFireBuilder wrap(Class<T> clazz, Mapper<T, String> mapper) { wrappedClasses.put(clazz, mapper); return this; } /** * By enabling this, all methods with the annotation {@link io.gsonfire.annotations.ExposeMethodResult} will * be evaluated and it result will be added to the resulting json * @return */ public GsonFireBuilder enableExposeMethodResult(){ this.enableExposeMethodResults = true; return this; } /** * By enabling this, all exclusion by value strategies specified with the annotation * {@link io.gsonfire.annotations.ExcludeByValue} will be run to remove specific fields from the resulting json * @return */ public GsonFireBuilder enableExclusionByValue(){ this.enableExclusionByValueStrategies = true; return this; } /** * By enabling this, all methods with the annotation {@link io.gsonfire.annotations.ExposeMethodResult} will * be evaluated and it result will be added to the resulting json * @return */ public GsonFireBuilder enableHooks(Class clazz){ ClassConfig config = getClassConfig(clazz); config.setHooksEnabled(true); return this; } /** * By enabling this, when a class is being converted to Json and it contains a {@link java.util.Map} class * annotated with {@link io.gsonfire.annotations.MergeMap}, the map will be walked and merged * with the resulting json object. * * This method has been deprecated and a {@link io.gsonfire.PostProcessor} should be used instead * @return */ @Deprecated public GsonFireBuilder enableMergeMaps(Class clazz){ registerPostProcessor(clazz, new MergeMapPostProcessor(fieldInspector)); return this; } /** * Sets the serialization TimeZone. This will affect only values that depend on the TimeZone, for example rfc3339 * dates. * @param timeZone * @return */ public GsonFireBuilder serializeTimeZone(TimeZone timeZone) { this.serializeTimeZone = timeZone; return this; } /** * Defines a default value for an enum when its String representation does not match any of the enum values. * The <code>defaultValue</code> can be null. * @param enumClass * @param defaultValue * @param <T> * @return */ public <T extends Enum> GsonFireBuilder enumDefaultValue(Class<T> enumClass, T defaultValue) { this.enumDefaultValues.put(enumClass, defaultValue); return this; } public GsonFireBuilder addSerializationExclusionStrategy(FireExclusionStrategy exclusionStrategy) { this.serializationExclusions.add(exclusionStrategy); return this; } /** * Returns a new instance of the good old {@link GsonBuilder} * @return */ public GsonBuilder createGsonBuilder(){ Set<TypeToken> alreadyResolvedTypeTokensRegistry = Collections.newSetFromMap(new ConcurrentHashMap<TypeToken, Boolean>()); GsonBuilder builder = new GsonBuilder(); if(enableExposeMethodResults) { FireExclusionStrategy compositeExclusionStrategy = new FireExclusionStrategyComposite(serializationExclusions); registerPostProcessor(Object.class, new MethodInvokerPostProcessor<Object>(compositeExclusionStrategy)); } if(enableExclusionByValueStrategies) { builder.registerTypeAdapterFactory(new ExcludeByValueTypeAdapterFactory(fieldInspector, factory)); } for(Class clazz: orderedClasses){ ClassConfig config = classConfigMap.get(clazz); if(config.getTypeSelector() != null) { builder.registerTypeAdapterFactory(new TypeSelectorTypeAdapterFactory(config, alreadyResolvedTypeTokensRegistry)); } builder.registerTypeAdapterFactory(new FireTypeAdapterFactory(config)); } for(Map.Entry<Class, Enum> enumDefault: enumDefaultValues.entrySet()) { builder.registerTypeAdapterFactory(new EnumDefaultValueTypeAdapterFactory(enumDefault.getKey(), enumDefault.getValue())); } if(dateSerializationPolicy != null){ builder.registerTypeAdapter(Date.class, dateSerializationPolicy.createTypeAdapter(serializeTimeZone)); } builder.registerTypeAdapterFactory(new SimpleIterableTypeAdapterFactory()); builder.registerTypeAdapterFactory(new WrapTypeAdapterFactory(wrappedClasses)); return builder; } /** * Returns a new {@link Gson} instance * @return */ public Gson createGson(){ return createGsonBuilder().create(); } }