public class ContentTypeHeader extends SimpleHeaderWithParameter {

    public ContentTypeHeader(String value, Param... params) {
        super("Content-Type", value, params);
    }

    public ContentTypeHeader(String value) {
        super("Content-Type", value);
    }
} public class RequestMock {

    private final RequestBuilder.Method method;
    private final String url;
    private final String data;
    private final Headers headers;

    public RequestMock(RequestBuilder.Method method, String url, String data) {
        this(method, url, data, null);
    }

    public RequestMock(RequestBuilder.Method method, String url, String data, Headers headers) {
        this.method = method;
        this.url = url;
        this.data = data;
        this.headers = headers;
    }

    public RequestBuilder.Method getMethod() {
        return method;
    }

    public String getUrl() {
        return url;
    }

    public String getData() {
        return data;
    }

    @Nullable
    public Headers getHeaders() {
        return headers;
    }
} public class ResponseMock extends Response {

    private final String text;
    private final int statusCode;
    private final String statusText;
    private final Header[] headers;

    ResponseMock(String text, int statusCode, String statusText, Header[] headers) {
        this.text = text;
        this.statusCode = statusCode;
        this.statusText = statusText;
        this.headers = headers;
    }

    public static Builder builder() {
        return new Builder();
    }

    public static ResponseMock of(String text, int statusCode, String statusText, Header... headers) {
        return new ResponseMock(text, statusCode, statusText, headers);
    }

    @Override
    public String getHeader(String header) {
        if (header == null) throw new NullPointerException("Header param cannot be null.");
        if (header.isEmpty()) throw new IllegalArgumentException("Header param cannot be empty.");
        for (Header h : headers) {
            if (h.getName().equals(header)) return h.getValue();
        }
        return null;
    }

    @Override
    public Header[] getHeaders() {
        return headers;
    }

    @Override
    public String getHeadersAsString() {
        return Arrays.toString(headers);
    }

    @Override
    public int getStatusCode() {
        return statusCode;
    }

    @Override
    public String getStatusText() {
        return statusText;
    }

    @Override
    public String getText() {
        return text;
    }

    /**
     * A builder of {@link ResponseMock}.
     */
    public static class Builder {
        private String text;
        private int statusCode;
        private String statusText;
        private final List<Header> headers = new ArrayList<>();

        public Builder text(String text) {
            this.text = text;
            return this;
        }

        public Builder statusCode(int statusCode) {
            this.statusCode = statusCode;
            return this;
        }

        public Builder statusText(String statusText) {
            this.statusText = statusText;
            return this;
        }

        public Builder header(final String name, final String value) {
            headers.add(new Header() {
                @Override
                public String getName() {
                    return name;
                }

                @Override
                public String getValue() {
                    return value;
                }

                @Override
                public String toString() {
                    return name + " = " + value;
                }
            });
            return this;
        }

        public Builder clearHeaders() {
            headers.clear();
            return this;
        }

        public ResponseMock build() {
            return new ResponseMock(text, statusCode, statusText, (Header[]) headers.toArray());
        }
    }
} public class ServerStub implements Server {

    private static Map<String, Response> responseData = new HashMap<>();
    private static Map<String, RequestMock> requestData = new HashMap<>();

    private static boolean returnSuccess = true;

    public static boolean isReturnSuccess() {
        return returnSuccess;
    }

    public static void setReturnSuccess(boolean success) {
        returnSuccess = success;
    }

    public static void responseFor(String uri, Response response) {
        responseData.put(uri, response);
    }

    public static RequestMock getRequestData(String uri) {
        return requestData.get(uri);
    }

    public static void triggerPendingRequest() {
        ServerConnectionMock.triggerPendingRequest();
    }

    public static void clearStub() {
        responseData.clear();
        requestData.clear();
        returnSuccess = true;
    }

    static Response getResponseFor(String uri) {
        return responseData.get(uri);
    }

    static RequestMock setRequestData(String uri, RequestMock requestMock) {
        return requestData.put(uri, requestMock);
    }

    /**
     * Retrieve an instance of {@link org.turbogwt.net.http.client.ServerConnection}.
     *
     * @return The ServerConnection instance.
     */
    @Override
    public ServerConnection getConnection() {
        return new ServerConnectionMock();
    }
} public class Person {

    private int id;
    private String name;
    private double weight;
    private Date birthday;

    public Person(int id, String name, double weight, Date birthday) {
        this.id = id;
        this.name = name;
        this.weight = weight;
        this.birthday = birthday;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public double getWeight() {
        return weight;
    }

    public Date getBirthday() {
        return birthday;
    }

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

        final Person person = (Person) o;

        if (id != person.id) {
            return false;
        }
        if (Double.compare(person.weight, weight) != 0) {
            return false;
        }
        if (!birthday.equals(person.birthday)) {
            return false;
        }
        if (!name.equals(person.name)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = id;
        result = 31 * result + name.hashCode();
        temp = Double.doubleToLongBits(weight);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        result = 31 * result + birthday.hashCode();
        return result;
    }
} public final class PersonJso extends JavaScriptObject {

    protected PersonJso() {
    }

    public static PersonJso create(int id, String name, double weight, Date birthday) {
        return create(id, name, weight, birthday.getTime());
    }

    private static native PersonJso create(int id, String name, double weight, double birthday) /*-{
        return {id: id, name: name, weight: weight, birthday: birthday};
    }-*/;

    public native int getId() /*-{
        return this.id;
    }-*/;

    public native String getName() /*-{
        return this.name;
    }-*/;

    public native double getWeight() /*-{
        return this.weight;
    }-*/;

    public native Date getBirthday() /*-{
        return this.birthday;
    }-*/;
} public class PersonSerdes extends JsonObjectSerdes<Person> {

    private String[] acceptPatterns = JsonSerdes.ACCEPT_PATTERNS;
    private String[] contentTypePatterns = JsonSerdes.CONTENT_TYPE_PATTERNS;

    public PersonSerdes() {
        super(Person.class);
    }

    public void setAcceptPatterns(String... acceptPatterns) {
        this.acceptPatterns = acceptPatterns;
    }

    public void setContentTypePatterns(String... contentTypePatterns) {
        this.contentTypePatterns = contentTypePatterns;
    }

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

    /**
     * Informs the content type this serializer serializes.
     *
     * @return The content type serialized.
     */
    @Override
    public String[] contentType() {
        return contentTypePatterns;
    }

    /**
     * Map response deserialized as JavaScriptObject to T.
     * <p/>
     * You may use {@link org.turbogwt.core.Overlays} helper methods to easily perform this mapping.
     *
     * @param reader  The evaluated response
     * @param context Context of the deserialization
     *
     * @return The object deserialized
     */
    @Override
    public Person readJson(JsonRecordReader reader, DeserializationContext context) {
        return new Person(reader.readInteger("id"),
                reader.readString("name"),
                reader.readDouble("weight"),
                new Date(reader.readLong("birthday")));
    }

    /**
     * Map T as JavaScriptObject to serialize using JSON.stringify.
     * <p/>
     * You may use {@link org.turbogwt.core.Overlays} helper methods to easily perform this mapping.
     *
     * @param person  The object to be serialized
     * @param writer  The serializing JSON
     * @param context Context of the serialization
     */
    @Override
    public void writeJson(Person person, JsonRecordWriter writer, SerializationContext context) {
        writer.writeInt("id", person.getId())
                .writeString("name", person.getName())
                .writeDouble("weight", person.getWeight())
                .writeDouble("birthday", person.getBirthday().getTime());
    }
} public abstract class DeserializationContext {

    private final ContainerFactoryManager containerFactoryManager;

    public DeserializationContext(ContainerFactoryManager containerFactoryManager) {
        this.containerFactoryManager = containerFactoryManager;
    }

    public <C extends Collection> C getContainerInstance(Class<C> type) {
        final Factory<C> factory = containerFactoryManager.getFactory(type);
        if (factory == null)
            throw new UnableToDeserializeException("Could not get container instance because there's no factory " +
                    "registered in the requestor.");
        return factory.get();
    }

    protected ContainerFactoryManager getContainerFactoryManager() {
        return containerFactoryManager;
    }
} public abstract class SerializationContext {
} public abstract class JsonObjectSerdes<T> extends JsonSerdes<T> {

    public JsonObjectSerdes(Class<T> handledType) {
        super(handledType);
    }

    /**
     * Verifies if the deserializer should evaluate the response safely.
     * <p/>
     * If this method returns <code>true</code>, then the deserializer will evaluate the response using
     * {@link com.google.gwt.core.client.JsonUtils#safeEval(String)}, otherwise it will use
     * {@link com.google.gwt.core.client.JsonUtils#unsafeEval(String)}.
     * <p/>
     * If you are completely sure you'll will always receive safe contents, then you can override it
     * to return <code>false</code> and you'll benefit a faster deserialization.
     * <p/>
     * The default implementation is <code>true</code>.
     *
     * @return  <code>true</code> if you want to evaluate response safely,
     *          or <code>false</code> to evaluate unsafely
     */
    public boolean useSafeEval() {
        return true;
    }

    /**
     * Recover an instance of T from deserialized JSON.
     *
     * @param reader    The evaluated response
     * @param context   Context of the deserialization
     *
     * @return The object deserialized
     */
    public abstract T readJson(JsonRecordReader reader, DeserializationContext context);

    /**
     * Build a JSON using {@link JsonRecordWriter}.
     * Later this JSON will be serialized using JSON#stringify.
     *
     * @param t         The object to be serialized
     * @param writer    The serializing JSON
     * @param context   Context of the serialization
     */
    public abstract void writeJson(T t, JsonRecordWriter writer, SerializationContext context);

    @Override
    public T deserialize(String response, DeserializationContext context) {
        if (!isObject(response))
            throw new UnableToDeserializeException("Response content is not an object");
        final JavaScriptObject deserialized = eval(response);
        return readJson((JsonRecordReader) deserialized, context);
    }

    @Override
    public <C extends Collection<T>> C deserializeAsCollection(Class<C> collectionType, String response,
                                                               DeserializationContext context) {
        if (!isArray(response)) throw new UnableToDeserializeException("Response content is not an array.");

        C col = getCollectionInstance(context, collectionType);
        @SuppressWarnings("unchecked")
        JsArray<JavaScriptObject> jsArray = (JsArray<JavaScriptObject>) eval(response);
        for (int i = 0; i < jsArray.length(); i++) {
            JavaScriptObject jso = jsArray.get(i);
            col.add(readJson((JsonRecordReader) jso, context));
        }
        return col;
    }

    @Override
    public String serialize(T t, SerializationContext context) {
        final JsonRecordWriter writer = JsonRecordWriter.create();
        writeJson(t, writer, context);
        return Overlays.stringify(writer);
    }

    /**
     * Checks if the serialized content is a JSON Object.
     *
     * @param text Serialized response
     *
     * @return {@code true} if argument is a JSON object, {@code false} otherwise
     */
    protected boolean isObject(String text) {
        final String trim = text.trim();
        return trim.startsWith("{") && trim.endsWith("}");
    }

    /**
     * Performs evaluation of serialized response obeying the #useSafeEval configuration.
     * <p/>
     *
     * If #useSafeEval is {@code true} then the eval is performed using {@link JsonUtils#safeEval},
     * otherwise then content will be loosely evaluated by {@link JsonUtils#unsafeEval}.
     *
     * @param response The serialized content
     *
     * @return The converted JavaScriptObject
     */
    protected JavaScriptObject eval(String response) {
        return useSafeEval() ? JsonUtils.safeEval(response) : JsonUtils.unsafeEval(response);
    }
} public final class JsonRecordReader extends JavaScriptObject {

    protected JsonRecordReader() {
    }

    public native Boolean readBoolean(String property) /*-{
        return this[property] ? @java.lang.Boolean::valueOf(Z)(this[property]) : null;
    }-*/;

    public native boolean readBooleanPrimitive(String property) /*-{
        return this[property];
    }-*/;

    public native Double readDouble(String property) /*-{
        return this[property] ? @java.lang.Double::valueOf(D)(this[property]) : null;
    }-*/;

    public native double readDoublePrimitive(String property) /*-{
        return this[property];
    }-*/;

    public native int readIntPrimitive(String property) /*-{
        return this[property];
    }-*/;

    public native Integer readInteger(String property) /*-{
        return this[property] ? @java.lang.Integer::valueOf(I)(this[property]) : null;
    }-*/;

    public native Long readLong(String property) /*-{
        return this[property] ? @java.lang.Long::valueOf(Ljava/lang/String;)(this[property]+'') : null;
    }-*/;

    public native JavaScriptObject readObject(String property) /*-{
        return this[property];
    }-*/;

    public native String readString(String property) /*-{
        return this[property];
    }-*/;
} public final class JsonRecordWriter extends JavaScriptObject {

    protected JsonRecordWriter() {
    }

    static JsonRecordWriter create() {
        return (JsonRecordWriter) JavaScriptObject.createObject();
    }

    public native JsonRecordWriter writeBoolean(String property, boolean value) /*-{
        this[property] = value;
        return this;
    }-*/;

    public native JsonRecordWriter writeDouble(String property, double value) /*-{
        this[property] = value;
        return this;
    }-*/;

    public native JsonRecordWriter writeInt(String property, int value) /*-{
        this[property] = value;
        return this;
    }-*/;

    public native JsonRecordWriter writeNull(String property) /*-{
        this[property] = null;
        return this;
    }-*/;

    public native JsonRecordWriter writeObject(String property, JavaScriptObject value) /*-{
        this[property] = value;
        return this;
    }-*/;

    public native JsonRecordWriter writeString(String property, String value) /*-{
        this[property] = value;
        return this;
    }-*/;
}
import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.Header;
import com.google.gwt.junit.client.GWTTestCase;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.turbogwt.core.collections.client.JsArray;
import org.turbogwt.core.collections.client.JsArrayList;
import org.turbogwt.core.future.shared.AlwaysCallback;
import org.turbogwt.core.future.shared.DoneCallback;
import org.turbogwt.core.future.shared.Promise;
import org.turbogwt.core.util.client.Overlays;
import org.turbogwt.net.http.client.header.ContentTypeHeader;
import org.turbogwt.net.http.client.mock.RequestMock;
import org.turbogwt.net.http.client.mock.ResponseMock;
import org.turbogwt.net.http.client.mock.ServerStub;
import org.turbogwt.net.http.client.model.Person;
import org.turbogwt.net.http.client.model.PersonJso;
import org.turbogwt.net.http.client.model.PersonSerdes;
import org.turbogwt.net.serialization.client.DeserializationContext;
import org.turbogwt.net.serialization.client.SerializationContext;
import org.turbogwt.net.serialization.client.json.JsonObjectSerdes;
import org.turbogwt.net.serialization.client.json.JsonRecordReader;
import org.turbogwt.net.serialization.client.json.JsonRecordWriter;
        final boolean[] executed = new boolean[1];

        requestor.request("/notValid").get().always(new AlwaysCallback<Void, Throwable>() {
            @Override
            public void onAlways(Promise.State state, Void resolved, Throwable rejected) {
                executed[0] = true;
            }
        });
        ServerStub.triggerPendingRequest();

        assertTrue(executed[0]);
    }

    public void testAwaysCallbackExecutionOnSuccess() {

        final Requestor requestor = getRequestor();

        final String uri = "/success";

        ServerStub.responseFor(uri, ResponseMock.of("success", 200, "OK", new ContentTypeHeader("plain/text")));

        final boolean[] executed = new boolean[1];

        requestor.request(uri).get().always(new AlwaysCallback<Void, Throwable>() {
            @Override
            public void onAlways(Promise.State state, Void resolved, Throwable rejected) {
                executed[0] = true;
            }
        });
        ServerStub.triggerPendingRequest();

        assertTrue(executed[0]);
    }

    public void testOverlayArrayRequest() {
        final Requestor requestor = getRequestor();

        final String uri = "/person-jso-array";

        final PersonJso p1 = PersonJso.create(1, "John Doe", 6.3, new Date(329356800));
        final PersonJso p2 = PersonJso.create(2, "Alice", 5.87, new Date(355343600));

        final JsArray<PersonJso> persons = JsArray.create();
        persons.push(p1);
        persons.push(p2);

        final String serializedResp = "[{\"id\": 1, \"name\": \"John Doe\", \"weight\": 6.3, \"birthday\": 329356800},"
                + "{\"id\": 2, \"name\": \"Alice\", \"weight\": 5.87, \"birthday\": 355343600}]";

        ServerStub.responseFor(uri, ResponseMock.of(serializedResp, 200, "OK",
                new ContentTypeHeader("application/json")));

        final boolean[] callbackSuccessCalled = new boolean[1];

//        requestor.get(uri, PersonJso.class, new ListAsyncCallback<PersonJso>() {
//            @Override
//            public void onFailure(Throwable caught) {
//            }
//
//            @Override
//            public void onSuccess(List<PersonJso> result) {
//                JsArray<PersonJso> resultArray = ((JsArrayList<PersonJso>) result).asJsArray();
//                assertEquals(Overlays.stringify(persons), Overlays.stringify(resultArray));
//                callbackSuccessCalled[0] = true;
//            }
//        });

        requestor.request(uri).get(PersonJso.class, List.class).done(new DoneCallback<Collection<PersonJso>>() {
            @Override
            public void onDone(Collection<PersonJso> result) {
                JsArray<PersonJso> resultArray = ((JsArrayList<PersonJso>) result).asJsArray();
                assertEquals(Overlays.stringify(persons), Overlays.stringify(resultArray));
                callbackSuccessCalled[0] = true;
            }
        });
        ServerStub.triggerPendingRequest();

        assertTrue(callbackSuccessCalled[0]);
    }

    public void testOverlayRequest() {
        final Requestor requestor = getRequestor();

        final String uri = "/person-jso";

        final PersonJso person = PersonJso.create(1, "John Doe", 6.3, new Date(329356800));
        final String serializedResp = "{ \"id\" : 1, \"name\":\"John Doe\",\"weight\" :6.3,  \"birthday\": 329356800}";

        ServerStub.responseFor(uri, ResponseMock.of(serializedResp, 200, "OK",
                new ContentTypeHeader("application/json")));

        final boolean[] callbackSuccessCalled = new boolean[1];

        requestor.request(uri).get(PersonJso.class).done(new DoneCallback<PersonJso>() {
            @Override
            public void onDone(PersonJso result) {
                assertEquals(Overlays.stringify(person), Overlays.stringify(result));
                callbackSuccessCalled[0] = true;
            }
        });
        ServerStub.triggerPendingRequest();

        assertTrue(callbackSuccessCalled[0]);
    }

    public void testRequestHeaders() {
        final Requestor requestor = getRequestor();

        final String uri = "/person-jso";

        final PersonJso person = PersonJso.create(1, "John Doe", 6.3, new Date(329356800));
        final String serializedResp = "{ \"id\" : 1, \"name\":\"John Doe\",\"weight\" :6.3,  \"birthday\": 329356800}";

        ServerStub.responseFor(uri, ResponseMock.of(serializedResp, 200, "OK",
                new ContentTypeHeader("application/json")));

        requestor.request(uri).payload(person).post(PersonJso.class);
        ServerStub.triggerPendingRequest();

        // On #post execution, request mock should be set from requestor
        final RequestMock requestMock = ServerStub.getRequestData(uri);