public class FormParamSerializer implements Serializer<FormParam> {

    public static String[] CONTENT_TYPE_PATTERNS = new String[]{"application/x-www-form-urlencoded"};

    private static final FormParamSerializer INSTANCE = new FormParamSerializer();

    private MultivaluedParamComposition multivaluedParamComposition = MultivaluedParamComposition.REPEATED_PARAM;

    public static FormParamSerializer getInstance() {
        return INSTANCE;
    }

    @Override
    public Class<FormParam> handledType() {
        return FormParam.class;
    }

    @Override
    public String[] contentType() {
        return CONTENT_TYPE_PATTERNS;
    }

    @Override
    public String serialize(FormParam formParam, SerializationContext context) {
        return multivaluedParamComposition.asUriPart("&", formParam.getName(), formParam.getValues());
    }

    @Override
    public String serializeFromCollection(Collection<FormParam> c, SerializationContext context) {
        MultivaluedParamComposition strategy = multivaluedParamComposition;
        if (c instanceof FormData) {
            FormData data = (FormData) c;
            if (data.getMultivaluedParamComposition() != null) strategy = data.getMultivaluedParamComposition();
        }
        String serialized = "";
        String sep = "";
        for (FormParam formParam : c) {
            serialized += sep + strategy.asUriPart("&", formParam.getName(), formParam.getValues());
            sep = "&";
        }
        return serialized;
    }

    public MultivaluedParamComposition getMultivaluedParamComposition() {
        return multivaluedParamComposition;
    }

    public void setMultivaluedParamComposition(MultivaluedParamComposition multivaluedParamComposition) {
        this.multivaluedParamComposition = multivaluedParamComposition;
    }
} public final class ContainerFactoryManager {

    private final JsMap<Factory<? extends Collection>> factories;

    public ContainerFactoryManager() {
        factories = JsMap.create();
        final Factory<ArrayList> arrayListFactory = new Factory<ArrayList>() {
            @Override
            public ArrayList get() {
                return new ArrayList<>();
            }
        };
        final Factory<JsArrayList> jsArrayListFactory = new Factory<JsArrayList>() {
            @Override
            public JsArrayList get() {
                return new JsArrayList();
            }
        };
        factories.put(JsArrayList.class.getName(), jsArrayListFactory);
        factories.put(Collection.class.getName(), jsArrayListFactory);
        factories.put(List.class.getName(), jsArrayListFactory);
        factories.put(ArrayList.class.getName(), arrayListFactory);
        factories.put(LinkedList.class.getName(), new Factory<LinkedList>() {
            @Override
            public LinkedList get() {
                return new LinkedList<>();
            }
        });

        final Factory<HashSet> hashSetFactory = new Factory<HashSet>() {
            @Override
            public HashSet get() {
                return new HashSet();
            }
        };
        factories.put(Set.class.getName(), hashSetFactory);
        factories.put(HashSet.class.getName(), hashSetFactory);
        factories.put(TreeSet.class.getName(), new Factory<TreeSet>() {
            @Override
            public TreeSet get() {
                return new TreeSet();
            }
        });
    }

    /**
     * Map a {@link Factory} to some collection class.
     *
     * @param type      The type of the collection
     * @param factory   The factory of the collection
     * @param <C>       The type of the collection
     *
     * @return  The {@link org.turbogwt.core.util.shared.Registration} object, capable of cancelling this registration
     *          to the {@link ContainerFactoryManager}
     */
    public <C extends Collection> Registration registerFactory(Class<C> type, Factory<C> factory) {
        final String typeName = type.getName();
        factories.put(typeName, factory);

        return new Registration() {
            @Override
            public void removeHandler() {
                factories.remove(typeName);
            }
        };
    }

    /**
     * Given collection some class, return its {@link Factory}.
     *
     * @param <C> Collection type
     */
    @SuppressWarnings("unchecked")
    @Nullable
    public <C extends Collection> Factory<C> getFactory(Class<C> type) {
        return (Factory<C>) factories.get(type.getName());
    }
} public interface Deserializer<T> {

    /**
     * Method for accessing type of the Object this deserializer can handle.
     *
     * @return The class which this deserializer can deserialize
     */
    Class<T> handledType();

    /**
     * Tells the content-type patterns which this deserializer handles.
     * <p/>
     * 
     * E.g., a deserializer for JSON can return {"application/json", "application/javascript"}.<br>
     * If you want to create a deserializer for any content-type just return "*&#47;*".
     * 
     * @return The content-type patterns handled by this deserializer.
     */
    String[] accept();

    /**
     * Deserialize the plain text into an object of type T.
     *
     * @param response  Http response body content
     * @param context   Context of deserialization
     *
     * @return The object deserialized
     */
    T deserialize(String response, DeserializationContext context);

    /**
     * Deserialize the plain text into a collection T.
     * <p/>
     * 
     * The collection instance can be retrieved from its {@link org.turbogwt.core.util.shared.Factory},
     * obtained in {@link ContainerFactoryManager#getFactory}.<br>
     * The ContainerFactoryManager can be retrieved from {@link DeserializationContext#getContainerInstance(Class)}.
     *
     * @param collectionType    The class of the collection
     * @param response          Http response body content
     * @param context           Context of deserialization
     *
     * @return The object deserialized
     */
    <C extends Collection<T>> C deserializeAsCollection(Class<C> collectionType, String response,
                                                        DeserializationContext context);
} public interface Serdes<T> extends Deserializer<T>, Serializer<T> {

    /**
     * Method for accessing type of the Object this de/serializer can handle.
     *
     * @return The class which this de/serializer can de/serialize
     */
    @Override
    Class<T> handledType();
} public class SerdesManager {

    private final JsMap<JsArrayList<DeserializerHolder>> deserializers = JsMap.create();
    private final JsMap<JsArrayList<SerializerHolder>> serializers = JsMap.create();

    /**
     * Register a deserializer of the given type.
     *
     * @param type          The class of the deserializer's type.
     * @param deserializer  The deserializer of T.
     * @param <T>           The type of the object to be deserialized.
     *
     * @return  The {@link Registration} object, capable of cancelling this registration
     *          to the {@link SerdesManager}.
     */
    public <T> Registration registerDeserializer(Class<T> type, Deserializer<T> deserializer) {
        final String typeName = type.getName();
        JsArrayList<DeserializerHolder> tDesList = deserializers.get(typeName);
        if (tDesList == null) {
            tDesList = new JsArrayList<>();
            deserializers.put(typeName, tDesList);
        }

        final String[] accept = deserializer.accept();
        final DeserializerHolder[] holders = new DeserializerHolder[accept.length];
        for (int i = 0; i < accept.length; i++) {
            String pattern = accept[i];
            final Key key = new Key(type, pattern);
            final DeserializerHolder holder = new DeserializerHolder(key, deserializer);
            tDesList.add(holder);
            holders[i] = holder;
        }

        Collections.sort(tDesList);

        return new Registration() {
            @Override
            public void removeHandler() {
                for (DeserializerHolder holder : holders) {
                    deserializers.get(typeName).remove(holder);
                }
            }
        };
    }

    /**
     * Register a serializer of the given type.
     *
     * @param type          The class of the serializer's type.
     * @param serializer  The serializer of T.
     * @param <T>           The type of the object to be serialized.
     *
     * @return  The {@link Registration} object, capable of cancelling this registration
     *          to the {@link SerdesManager}.
     */
    public <T> Registration registerSerializer(Class<T> type, Serializer<T> serializer) {
        final String typeName = type.getName();
        JsArrayList<SerializerHolder> tSerList = serializers.get(typeName);
        if (tSerList == null) {
            tSerList = new JsArrayList<>();
            serializers.put(typeName, tSerList);
        }

        final String[] contentType = serializer.contentType();
        final SerializerHolder[] holders = new SerializerHolder[contentType.length];
        for (int i = 0; i < contentType.length; i++) {
            String pattern = contentType[i];
            final Key key = new Key(type, pattern);
            final SerializerHolder holder = new SerializerHolder(key, serializer);
            tSerList.add(holder);
            holders[i] = holder;
        }

        Collections.sort(tSerList);

        return new Registration() {
            @Override
            public void removeHandler() {
                for (SerializerHolder holder : holders) {
                    serializers.get(typeName).remove(holder);
                }
            }
        };
    }

    /**
     * Register a serializer/deserializer of the given type.
     *
     * @param type      The class of the serializer/deserializer's type.
     * @param serdes    The serializer/deserializer of T.
     * @param <T>       The type of the object to be serialized/deserialized.
     *
     * @return  The {@link Registration} object, capable of cancelling this registration
     *          to the {@link SerdesManager}.
     */
    public <T> Registration registerSerdes(Class<T> type, Serdes<T> serdes) {
        final Registration desReg = registerDeserializer(type, serdes);
        final Registration serReg = registerSerializer(type, serdes);

        return new Registration() {
            @Override
            public void removeHandler() {
                desReg.removeHandler();
                serReg.removeHandler();
            }
        };
    }

    /**
     * Retrieve Deserializer from manager.
     *
     * @param type The type class of the deserializer.
     * @param <T> The type of the deserializer.
     * @return The deserializer of the specified type.
     *
     * @throws SerializationException if no deserializer was registered for the class.
     */
    @SuppressWarnings("unchecked")
    public <T> Deserializer<T> getDeserializer(Class<T> type, String contentType) throws SerializationException {
        checkNotNull(type, "Type (Class<T>) cannot be null.");
        checkNotNull(contentType, "Content-Type cannot be null.");

        final Key key = new Key(type, contentType);

        JsArrayList<DeserializerHolder> holders = deserializers.get(type.getName());
        if (holders != null) {
            for (DeserializerHolder holder : holders) {
                if (holder.key.matches(key)) return (Deserializer<T>) holder.deserializer;
            }
        }

        throw new SerializationException("There is no Deserializer registered for " + type.getName() +
                " and content-type " + contentType + ".");
    }

    /**
     * Retrieve Serializer from manager.
     *
     * @param type The type class of the serializer.
     * @param <T> The type of the serializer.
     * @return The serializer of the specified type.
     * @throws SerializationException if no serializer was registered for the class.
     */
    @SuppressWarnings("unchecked")
    public <T> Serializer<T> getSerializer(Class<T> type, String contentType) throws SerializationException {
        checkNotNull(type, "Type (Class<T>) cannot be null.");
        checkNotNull(contentType, "Content-Type cannot be null.");

        final Key key = new Key(type, contentType);

        JsArrayList<SerializerHolder> holders = serializers.get(type.getName());
        if (holders != null) {
            for (SerializerHolder holder : holders) {
                if (holder.key.matches(key)) return (Serializer<T>) holder.serializer;
            }
        }

        throw new SerializationException("There is no Serializer registered for type " + type.getName() +
                " and content-type " + contentType + ".");
    }

    private void checkNotNull(Object o, String message) {
        if (o == null) throw new NullPointerException(message);
    }

    private static class DeserializerHolder implements Comparable<DeserializerHolder> {

        final Key key;
        final Deserializer<?> deserializer;

        private DeserializerHolder(Key key, Deserializer<?> deserializer) {
            this.key = key;
            this.deserializer = deserializer;
        }

        @Override
        public int compareTo(DeserializerHolder deserializerHolder) {
            return key.compareTo(deserializerHolder.key);
        }

        @Override
        public boolean equals(Object o) {
            final DeserializerHolder that = (DeserializerHolder) o;
            return key.equals(that.key);
        }

        @Override
        public int hashCode() {
            return key.hashCode();
        }
    }

    private static class SerializerHolder implements Comparable<SerializerHolder> {

        final Key key;
        final Serializer<?> serializer;

        private SerializerHolder(Key key, Serializer<?> serializer) {
            this.key = key;
            this.serializer = serializer;
        }

        @Override
        public int compareTo(SerializerHolder serializerHolder) {
            return key.compareTo(serializerHolder.key);
        }

        @Override
        public boolean equals(Object o) {
            final SerializerHolder that = (SerializerHolder) o;
            return key.equals(that.key);
        }

        @Override
        public int hashCode() {
            return key.hashCode();
        }
    }

    private static class Key implements Comparable<Key> {

        final Class<?> type;
        final String contentType;
        final double factor;

        private Key(Class<?> type, String contentType) {
            checkSeparatorPresence(contentType);

            this.type = type;
            this.contentType = contentType;
            this.factor = 1.0;
        }

        private Key(Class<?> type, String contentType, double factor) {
            this.type = type;
            this.contentType = contentType;
            this.factor = factor;
        }

        // TODO: test exhaustively
        public boolean matches(Key key) {
            if (!key.type.equals(this.type)) {
                return false;
            }

            boolean matches;

            final int thisSep = this.contentType.indexOf("/");
            final int otherSep = key.contentType.indexOf("/");

            String thisInitialPart = this.contentType.substring(0, thisSep);
            String otherInitialPart = key.contentType.substring(0, otherSep);

            if (thisInitialPart.contains("*")) {
                matches = matchPartsSafely(thisInitialPart, otherInitialPart);
            } else if (otherInitialPart.contains("*")) {
                matches = matchPartsUnsafely(otherInitialPart, thisInitialPart);
            } else {
                matches = thisInitialPart.equalsIgnoreCase(otherInitialPart);
            }

            if (!matches) return false;

            final String thisFinalPart = this.contentType.substring(thisSep + 1);
            final String otherFinalPart = key.contentType.substring(otherSep + 1);

            if (thisFinalPart.contains("*")) {
                matches = matchPartsSafely(thisFinalPart, otherFinalPart);
            } else if (otherFinalPart.contains("*")) {
                matches = matchPartsUnsafely(otherFinalPart, thisFinalPart);
            } else {
                matches = thisFinalPart.equalsIgnoreCase(otherFinalPart);
            }

            return matches;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof Key)) {
                return false;
            }

            final Key key = (Key) o;

            if (!type.equals(key.type)) {
                return false;
            }
            if (!contentType.equals(key.contentType)) {
                return false;
            }
            if (Double.compare(key.factor, factor) != 0) {
                return false;
            }

            return true;
        }

        @Override
        public int hashCode() {
            int result;
            long temp;
            result = type.hashCode();
            result = 31 * result + contentType.hashCode();
            temp = Double.doubleToLongBits(factor);
            result = 31 * result + (int) (temp ^ (temp >>> 32));
            return result;
        }

        @Override
        public int compareTo(Key key) {
            int result = this.type.getSimpleName().compareTo(key.type.getSimpleName());

            // TODO: Improve pattern matching to handle patterns without separators.
            if (result == 0) {
                final int thisSep = this.contentType.indexOf("/");
                final int otherSep = key.contentType.indexOf("/");

                // !!! CAUTION !!!
                // When contentType does not have a '/' separator, than StringArrayIndexOutOfBounds is thrown.
                String thisInitialPart = this.contentType.substring(0, thisSep);
                String otherInitialPart = key.contentType.substring(0, otherSep);
                result = thisInitialPart.compareTo(otherInitialPart);

                // Invert the result if the winner contains wildcard
                if ((result < 0 && thisInitialPart.contains("*")) || (result > 0 && otherInitialPart.contains("*")))
                    result = -result;

                if (result == 0) {
                    String thisFinalPart = this.contentType.substring(thisSep + 1);
                    String otherFinalPart = key.contentType.substring(otherSep + 1);
                    result = thisFinalPart.compareTo(otherFinalPart);

                    // Invert the result if the winner contains wildcard
                    if ((result < 0 && thisFinalPart.contains("*")) || (result > 0 && otherFinalPart.contains("*")))
                        result = -result;

                    if (result == 0) {
                        // Invert comparison because the greater the factor the greater the precedence.
                        result = Double.compare(key.factor, this.factor);
                    }
                }
            }

            return result;
        }

        private void checkSeparatorPresence(String contentType) {
            if (contentType.indexOf("/") < 1)
                throw new RuntimeException("Cannot perform matching. Content-Type *" +
                        this.contentType + "* does not have a '/' separator.");
        }

        private boolean matchPartsSafely(String left, String right) {
            boolean matches = true;
            final String rightCleaned = right.replace("*", "").toLowerCase();
            String[] parts = left.toLowerCase().split("\\*");
            final boolean otherEndsWithWildcard = right.endsWith("*");
            final int otherCleanedLength = rightCleaned.length();
            int i = 0;
            for (String part : parts) {
                if (i == otherCleanedLength && otherEndsWithWildcard) {
                    break;
                }
                if (!part.isEmpty()) {
                    int newIdx = rightCleaned.indexOf(part, i);
                    if (newIdx == -1) {
                        matches = false;
                        break;
                    }
                    i = newIdx + part.length();
                }
            }
            return matches;
        }

        private boolean matchPartsUnsafely(String left, String right) {
            boolean matches = true;
            String rightLower = right.toLowerCase();
            String[] parts = left.toLowerCase().split("\\*");
            int i = 0;
            for (String part : parts) {
                if (!part.isEmpty()) {
                    int newIdx = rightLower.indexOf(part, i);
                    if (newIdx == -1) {
                        matches = false;
                        break;
                    }
                    i = newIdx + part.length();
                }
            }
            return matches;
        }

        @Override
        public String toString() {
            return "{" +
                    "type: '" + type.getName() + '\'' +
                    ", contentType: '" + contentType + '\'' +
                    ", factor: " + factor +
                    '}';
        }
    }
} public interface Serializer<T> {

    /**
     * Method for accessing type of the Object this serializer can handle.
     *
     * @return The class which this serializer can serialize
     */
    Class<T> handledType();

    /**
     * Tells the content-type patterns which this serializer handles.
     * <p/>
     * 
     * E.g., a serializer for JSON can return {"application/json", "application/javascript"}.<br>
     * If you want to create a serializer for any content-type just return "*&#47;*".
     *
     * @return The content-type patterns handled by this serializer.
     */
    String[] contentType();

    /**
     * Serialize T to plain text.
     *
     * @param t         The object to be serialized
     * @param context   Context of the serialization
     *
     * @return The object serialized.
     */
    String serialize(T t, SerializationContext context);

    /**
     * Serialize a collection of T to plain text.
     *
     * @param c        The collection of the object to be serialized
     * @param context   Context of the serialization
     *
     * @return The object serialized.
     */
    String serializeFromCollection(Collection<T> c, SerializationContext context);
} public class JsonBooleanSerdes extends JsonValueSerdes<Boolean> {

    private static JsonBooleanSerdes INSTANCE = new JsonBooleanSerdes();

    public JsonBooleanSerdes() {
        super(Boolean.class);
    }

    public static JsonBooleanSerdes getInstance() {
        return INSTANCE;
    }

    @Override
    public Boolean deserialize(String response, DeserializationContext context) {
        return Boolean.valueOf(response);
    }

    @Override
    public String serialize(Boolean b, SerializationContext context) {
        return String.valueOf(b);
    }
} public class JsonNumberSerdes extends JsonValueSerdes<Number> {

    private static JsonNumberSerdes INSTANCE = new JsonNumberSerdes();

    public JsonNumberSerdes() {
        super(Number.class);
    }

    public static JsonNumberSerdes getInstance() {
        return INSTANCE;
    }

    @Override
    public Number deserialize(String response, DeserializationContext context) {
        try {
            if (response.contains(".")) {
                return Double.valueOf(response);
            }
            try {
                return Integer.valueOf(response);
            } catch (NumberFormatException e) {
                return Long.valueOf(response);
            }
        } catch (NumberFormatException e) {
            throw new UnableToDeserializeException("Could not deserialize response as number.");
        }
    }

    @Override
    public String serialize(Number n, SerializationContext context) {
        return String.valueOf(n);
    }
} public class JsonStringSerdes extends JsonValueSerdes<String> {

    private static JsonStringSerdes INSTANCE = new JsonStringSerdes();

    public JsonStringSerdes() {
        super(String.class);
    }

    public static JsonStringSerdes getInstance() {
        return INSTANCE;
    }

    @Override
    public String deserialize(String response, DeserializationContext context) {
        return response.substring(1, response.length() - 1);
    }

    @Override
    public String serialize(String s, SerializationContext context) {
        return "\"" + s + "\"";
    }
} public class OverlaySerdes<T extends JavaScriptObject> implements Serdes<T> {

    private static OverlaySerdes<JavaScriptObject> INSTANCE = new OverlaySerdes<>();

    @SuppressWarnings("unchecked")
    public static <O extends JavaScriptObject> OverlaySerdes<O> getInstance() {
        return (OverlaySerdes<O>) INSTANCE;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Class<T> handledType() {
        return (Class<T>) JavaScriptObject.class;
    }

    @Override
    public String[] contentType() {
        return JsonSerdes.CONTENT_TYPE_PATTERNS;
    }

    @Override
    public String[] accept() {
        return JsonSerdes.ACCEPT_PATTERNS;
    }

    @Override
    public T deserialize(String response, DeserializationContext context) {
        return JsonUtils.safeEval(response);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <C extends Collection<T>> C deserializeAsCollection(Class<C> collectionType, String response,
                                                               DeserializationContext context) {
        JsArray<T> jsArray = JsonUtils.safeEval(response);
        if (collectionType.equals(List.class) || collectionType.equals(Collection.class)) {
            return (C) new JsArrayList(jsArray);
        } else {
            C col = context.getContainerInstance(collectionType);
            for (int i = 0; i < jsArray.length(); i++) {
                T t = jsArray.get(i);
                col.add(t);
            }
            return col;
        }
    }

    @Override
    public String serialize(T t, SerializationContext context) {
        return Overlays.stringify(t);
    }

    @Override
    public String serializeFromCollection(Collection<T> c, SerializationContext context) {
        if (c instanceof JsArrayList) {
            return Overlays.stringify(((JsArrayList<T>) c).asJsArray());
        }

        if (c instanceof JavaScriptObject) {
            return Overlays.stringify((JavaScriptObject) c);
        }

        @SuppressWarnings("unchecked")
        JsArray<T> jsArray = (JsArray<T>) JsArray.createArray();
        for (T t : c) {
            jsArray.push(t);
        }
        return Overlays.stringify(jsArray);
    }
} public class TextDeserializer implements Deserializer<String> {

    public static String[] ACCEPT_PATTERNS = new String[]{"text/plain", "*/*"};

    private static final TextDeserializer INSTANCE = new TextDeserializer();

    public static TextDeserializer getInstance() {
        return INSTANCE;
    }

    /**
     * Method for accessing type of Objects this deserializer can handle.
     *
     * @return The class which this deserializer can deserialize
     */
    @Override
    public Class<String> handledType() {
        return String.class;
    }

    /**
     * Informs the content type this serializer handle.
     *
     * @return The content type handled by this serializer.
     */
    @Override
    public String[] accept() {
        return ACCEPT_PATTERNS;
    }

    /**
     * Deserialize the plain text into an object of type T.
     *
     * @param response Http response body content
     * @param context  Context of deserialization
     *
     * @return The object deserialized
     */
    @Override
    public String deserialize(String response, DeserializationContext context) {
        return response;
    }

    /**
     * Deserialize the plain text into an object of type T.
     *
     * @param collectionType The class of the collection
     * @param response       Http response body content
     * @param context        Context of deserialization
     *
     * @return The object deserialized
     */
    @Override
    public <C extends Collection<String>> C deserializeAsCollection(Class<C> collectionType, String response,
                                                                    DeserializationContext context) {
        final C col = context.getContainerInstance(collectionType);
        col.add(response);
        return col;
    }
} public class VoidSerdes implements Serdes<Void> {

    public static final String[] CONTENT_TYPE_PATTERNS = new String[] {"*/*"};

    private static VoidSerdes INSTANCE = new VoidSerdes();

    public static VoidSerdes getInstance() {
        return INSTANCE;
    }

    @Override
    public Class<Void> handledType() {
        return Void.class;
    }

    @Override
    public String[] contentType() {
        return CONTENT_TYPE_PATTERNS;
    }

    @Override
    public String[] accept() {
        return CONTENT_TYPE_PATTERNS;
    }

    @Override
    public Void deserialize(String response, DeserializationContext context) {
        return null;
    }

    @Override
    public <C extends Collection<Void>> C deserializeAsCollection(Class<C> collectionType, String response,
                                                                  DeserializationContext context) {
        return null;
    }

    @Override
    public String serialize(Void v, SerializationContext context) {
        return null;
    }

    @Override
    public String serializeFromCollection(Collection<Void> c, SerializationContext context) {
        return null;
    }
}
import org.turbogwt.net.serialization.client.json.JsonNumberSerdes;
import org.turbogwt.net.serialization.client.json.JsonStringSerdes;
import org.turbogwt.net.serialization.client.json.OverlaySerdes;
import org.turbogwt.net.serialization.client.misc.TextDeserializer;
import org.turbogwt.net.serialization.client.misc.VoidSerdes;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import java.util.Collection;
import org.turbogwt.core.util.shared.Factory;
import org.turbogwt.core.util.shared.Registration;
import org.turbogwt.net.http.client.serialization.FormParamSerializer;
import org.turbogwt.net.serialization.client.ContainerFactoryManager;
import org.turbogwt.net.serialization.client.Deserializer;
import org.turbogwt.net.serialization.client.Serdes;
import org.turbogwt.net.serialization.client.SerdesManager;
import org.turbogwt.net.serialization.client.Serializer;
import org.turbogwt.net.serialization.client.json.JsonBooleanSerdes;
/*
 * Copyright 2014 Grow Bit
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.turbogwt.net.http.client;




/**
 * Default implementation for {@link Requestor}.
 *
 * @author Danilo Reinert
 */
public class RequestorImpl implements Requestor {

    private final SerdesManager serdesManager = new SerdesManager();
    private final FilterManager filterManager = new FilterManager();
    private final ContainerFactoryManager collectionFactoryManager = new ContainerFactoryManager();
    private String defaultContentType = "application/json";

    public RequestorImpl() {
        initSerdesManager();
    }

    //===================================================================
    // Request factory methods
    //===================================================================

    @Override
    public RequestDispatcher request(String uri) {
        return createRequest(uri);
    }

    //===================================================================
    // Requestor configuration
    //===================================================================

    @Override
    public void setDefaultContentType(String contentType) {
        this.defaultContentType = contentType;
    }

    @Override
    public String getDefaultContentType() {
        return defaultContentType;
    }

    @Override
    public <T> Deserializer<T> getDeserializer(Class<T> type, String contentType) {
        return serdesManager.getDeserializer(type, contentType);
    }

    @Override
    public <T> Serializer<T> getSerializer(Class<T> type, String contentType) {
        return serdesManager.getSerializer(type, contentType);
    }

    @Override
    public <T> Registration registerDeserializer(Class<T> type, Deserializer<T> deserializer) {
        return serdesManager.registerDeserializer(type, deserializer);
    }

    @Override
    public <T> Registration registerSerializer(Class<T> type, Serializer<T> serializer) {
        return serdesManager.registerSerializer(type, serializer);
    }

    @Override
    public <T> Registration registerSerdes(Class<T> type, Serdes<T> serdes) {
        return serdesManager.registerSerdes(type, serdes);
    }

    @Override
    public Registration registerRequestFilter(RequestFilter requestFilter) {
        return filterManager.registerRequestFilter(requestFilter);
    }

    @Override
    public Registration registerResponseFilter(ResponseFilter responseFilter) {
        return filterManager.registerResponseFilter(responseFilter);
    }

    @Override
    public <C extends Collection> Registration registerContainerFactoy(Class<C> collectionType, Factory<C> factory) {
        return collectionFactoryManager.registerFactory(collectionType, factory);
    }

    private RequestDispatcher createRequest(String uri) {
        final RequestImpl request = new RequestImpl(uri, serdesManager, collectionFactoryManager, filterManager);
        request.contentType(defaultContentType);
        request.accept(defaultContentType);
        return request;
    }

    private void initSerdesManager() {
        serdesManager.registerSerdes(String.class, JsonStringSerdes.getInstance());
        serdesManager.registerSerdes(Number.class, JsonNumberSerdes.getInstance());
        serdesManager.registerSerdes(Boolean.class, JsonBooleanSerdes.getInstance());