public class ApiException extends Exception {

    public ApiException(final String message) {
        super(message);
    }
} public class RestException extends ApiException {

    private final StatusCode statusCode;
    private final int error;
    private final String details;

    public RestException(final StatusCode statusCode,
                         final int error,
                         final String message) {
        super(message);
        this.statusCode = statusCode;
        this.error = error;
        this.details = "";
    }

    public RestException(final StatusCode statusCode,
                         final RestError error) {
        super(error.getMessage());
        this.statusCode = statusCode;
        this.error = error.getError();
        this.details = error.getDetails();
    }

    public StatusCode getStatusCode() {
        return statusCode;
    }

    public int getError() {
        return error;
    }

    public String getDetails() {
        return details;
    }
} public class AuthException extends RestException {

    public AuthException(final int error, final String message) {
        super(StatusCode.UNAUTHORIZED, error, message);
    }

    public AuthException(final RestError error) {
        super(StatusCode.UNAUTHORIZED, error);
    }
} public interface AuthHandler extends Callable<AuthToken> {

    public void setForceRefresh(boolean force);

    public AuthToken refreshToken() throws IOException, ApiException;
} public abstract class AuthToken implements Serializable {

    private static final long serialVersionUID = 2L;
    private final String token;

    public AuthToken(final String token) {
        this.token = token;
    }

    public String getToken() {
        return token;
    }

    public abstract String getType();

    public boolean isValid() {
        return true;
    }

    private void readObject(final ObjectInputStream in)
        throws IOException, ClassNotFoundException {
        in.defaultReadObject();
    }

    @Override
    public String toString() {
        return "AuthToken{" +
               "token='" + token + '\'' +
               "type='" + getType() + '\'' +
               '}';
    }
} public class ProjectBearerAuthToken extends AuthToken {

    private final long projectId;
    private final long expires;

    public ProjectBearerAuthToken(final long projectId,
                                  final String token,
                                  final Date expires) {
        super(token);
        this.projectId = projectId;
        this.expires = expires.getTime();
    }

    @Override
    public String getType() {
        return "Bearer";
    }

    @Override
    public boolean isValid() {
        return !hasExpired();
    }

    public long getProjectId() {
        return projectId;
    }

    public Date getExpires() {
        return new Date(expires);
    }

    public boolean hasExpired() {
        return System.currentTimeMillis() > expires;
    }

    public static ProjectBearerAuthToken fromJson(final JSONObject json) throws ParseException {
        return new ProjectBearerAuthToken(json.getLong("project_id"),
                                          json.getString("token"),
                                          Util.parseToDate(json.getString("expires")));
    }

    @Override
    public String toString() {
        return "ProjectBearerAuthToken{" +
               "projectId=" + projectId +
               ", expires=" + expires +
               '}';
    }
} public class UserBearerAuthToken extends AuthToken {

    private final long userId;
    private final long expires;

    public UserBearerAuthToken(final long userId,
                               final String token,
                               final Date expires) {
        super(token);
        this.userId = userId;
        this.expires = expires.getTime();
    }

    @Override
    public String getType() {
        return "Bearer";
    }

    @Override
    public boolean isValid() {
        return !hasExpired();
    }

    public long getUserId() {
        return userId;
    }

    public Date getExpires() {
        return new Date(expires);
    }

    public boolean hasExpired() {
        return System.currentTimeMillis() > expires;
    }

    public static UserBearerAuthToken fromJson(final JSONObject json) throws ParseException {
        return new UserBearerAuthToken(json.getLong("user_id"),
                                       json.getString("token"),
                                       Util.parseToDate(json.getString("expires")));
    }

    @Override
    public String toString() {
        return "UserBearerAuthToken{" +
               "userId=" + userId +
               ", expires=" + expires +
               '}';
    }
} public enum ContentType {
    NONE(""),
    JSON("application/json"),
    URLENCODED("application/x-www-form-urlencoded");

    private final String value;

    private ContentType(final String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
} public class RequestBuilder implements Serializable {

    private static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 4000;
    private static final int DEFAULT_READ_TIMEOUT_MILLIS = 2000;
    private final String url;
    private RequestMethod method = RequestMethod.GET;
    private Object content = null;
    private long contentLength = 0;
    private ContentType contentType = ContentType.NONE;
    private final Map<String, List<String>> headers = new HashMap<String, List<String>>();
    private final Map<String, Object> parameters = new HashMap<String, Object>();
    private boolean doInput = true;
    private boolean doOutput = false;
    private boolean enableGzip = true;
    private int connectTimeoutMillis = DEFAULT_CONNECT_TIMEOUT_MILLIS;
    private int readTimeoutMillis = DEFAULT_READ_TIMEOUT_MILLIS;

    public RequestBuilder(final String url) {
        this.url = url;
    }

    public RequestBuilder(final RequestBuilder builder) {
        this.url = builder.url;
        this.headers.putAll(builder.headers);
        this.parameters.putAll(builder.parameters);
        this.doInput = builder.doInput;
        this.doOutput = builder.doOutput;
        this.connectTimeoutMillis = builder.connectTimeoutMillis;
        this.readTimeoutMillis = builder.readTimeoutMillis;
        this.method = builder.method;
        this.content = builder.content;
        this.contentLength = builder.contentLength;
        this.contentType = builder.contentType;
    }

    public HttpURLConnection build() throws IOException {
        /*
        In case we want to use OkHttp with SPDY support:

        final OkUrlFactory urlFactory = new OkUrlFactory(new OkHttpClient());
        final HttpURLConnection conn = urlFactory.open(new URL(buildUrl()));
        */
        final HttpURLConnection conn = (HttpURLConnection) new URL(buildUrl()).openConnection();

        conn.setDoInput(doInput);
        conn.setDoOutput(doOutput);
        conn.setReadTimeout(readTimeoutMillis);
        conn.setConnectTimeout(connectTimeoutMillis);
        conn.setRequestMethod(method.name());
        conn.setInstanceFollowRedirects(false);
        conn.setRequestProperty("Accept", "application/json");

        if (enableGzip) {
            conn.setRequestProperty("Accept-Encoding", "gzip");
        }

        if (contentType != ContentType.NONE) {
            conn.addRequestProperty("Content-Type", contentType.getValue());
        }

        if (doOutput && contentLength > 0 && contentLength <= Integer.MAX_VALUE) {
            // Must cast contentLength to int here, since long version of
            // setFixedLengthStreamingMode isn't available early versions of
            // Android
            conn.setFixedLengthStreamingMode((int) contentLength);
        }

        for (final Map.Entry<String, List<String>> entry : headers.entrySet()) {
            for (final String value : entry.getValue()) {
                conn.addRequestProperty(entry.getKey(), value);
            }
        }

        return conn;
    }

    public RequestMethod getMethod() {
        return method;
    }

    private RequestBuilder addHeader(final String name,
                                     final String value,
                                     final boolean clear) {
        List<String> values = headers.get(name);

        if (values == null) {
            values = new LinkedList<String>();
        } else if (clear) {
            values.clear();
        }
        values.add(value);
        headers.put(name, values);

        return this;
    }

    public RequestBuilder addHeader(final String name,
                                    final String value) {
        addHeader(name, value, false);
        return this;
    }

    public RequestBuilder setHeader(final String name,
                                    final String value) {
        addHeader(name, value, true);
        return this;
    }

    public Map<String, List<String>> getHeaders() {
        return headers;
    }

    public RequestBuilder setReadTimeout(final int millis) {
        readTimeoutMillis = millis;
        return this;
    }

    public RequestBuilder setConnectTimeout(final int millis) {
        connectTimeoutMillis = millis;
        return this;
    }

    public RequestBuilder setEnableGzip(final boolean enableGzip) {
        this.enableGzip = enableGzip;
        return this;
    }

    public RequestBuilder setDoInput(final boolean doInput) {
        this.doInput = doInput;
        return this;
    }

    public RequestBuilder setDoOutput(final boolean doOutput) {
        this.doOutput = doOutput;
        return this;
    }

    public RequestBuilder setRequestMethod(final RequestMethod method) {
        this.method = method;
        return this;
    }

    public RequestBuilder setContent(final Object content) {
        this.content = content;

        if (content != null) {
            setDoOutput(true);
        } else {
            setDoOutput(false);
        }
        return this;
    }

    public Object getContent() {
        return content;
    }

    public RequestBuilder setContentType(final ContentType type) {
        contentType = type;
        return this;
    }

    public ContentType getContentType() {
        return contentType;
    }

    public RequestBuilder setContentLength(final long length) {
        this.contentLength = length;

        if (length > 0) {
            setDoOutput(true);
        } else {
            setDoOutput(false);
        }
        return this;
    }

    public String getBaseUrl() {
        return url;
    }

    private String buildUrl() {
        final StringBuilder builder = new StringBuilder(url);
        final Iterator<Map.Entry<String, Object>> it = parameters.entrySet().iterator();

        if (it.hasNext()) {
            builder.append("?");
        }

        while (it.hasNext()) {
            final Map.Entry<String, Object> entry = it.next();

            try {
                builder.append(URLEncoder.encode(entry.getKey(), "UTF-8"))
                    .append("=")
                    .append(URLEncoder.encode(entry.getValue().toString(), "UTF-8"));

                if (it.hasNext()) {
                    builder.append("&");
                }
            } catch (UnsupportedEncodingException e) {
                // Ignore, potentially log.
            }
        }

        return builder.toString();
    }

    public void addParameter(final String key, final String value) {
        if (key != null && value != null) {
            parameters.put(key, value);
        }
    }

    public void addParameter(final String key, final int value) {
        if (key != null) {
            parameters.put(key, value);
        }
    }

    public void addParameter(final String key, final long value) {
        if (key != null) {
            parameters.put(key, value);
        }
    }

    public void addParameter(final String key, final boolean value) {
        if (key != null) {
            parameters.put(key, value);
        }
    }

    @Override
    public String toString() {
        return "RequestBuilder{" +
               "url='" + url + '\'' +
               ", content=" + content +
               ", method=" + method +
               ", contentLength=" + contentLength +
               ", contentType=" + contentType +
               ", doOutput=" + doOutput +
               ", doInput=" + doInput +
               ", connectTimeoutMillis=" + connectTimeoutMillis +
               ", readTimeoutMillis=" + readTimeoutMillis +
               '}';
    }
} public enum StatusCode {

    OK(200),
    CREATED(201),
    ACCEPTED(202),
    NO_CONTENT(204),

    MOVED_PERMANENTLY(301),
    FOUND(302),
    SEE_OTHER(303),
    NOT_MODIFIED(304),

    BAD_REQUEST(400),
    UNAUTHORIZED(401),
    FORBIDDEN(403),
    NOT_FOUND(404),
    METHOD_NOT_ALLOWED(405),
    NOT_ACCEPTABLE(406),
    REQUEST_TIMEOUT(408),
    CONFLICT(409),
    LENGTH_REQUIRED(411),
    PRECONDITION_FAILED(412),
    REQUEST_ENTITY_TOO_LARGE(413),
    TOO_MANY_REQUESTS(429),

    INTERNAL_SERVER_ERROR(500),
    NOT_IMPLEMENTED(501),
    BAD_GATEWAY(502),
    SERVICE_UNAVAILABLE(503),
    HTTP_VERSION_NOT_SUPPORTED(504);

    private static final EnumMap<StatusCode, String> descriptions =
        new EnumMap<StatusCode, String>(StatusCode.class);

    private static final Map<Integer, StatusCode> reverseLookup =
        new HashMap<Integer, StatusCode>();

    static {
        for (final StatusCode status : StatusCode.values()) {
            descriptions.put(status, status.name().toLowerCase().replace('_', ' '));
            reverseLookup.put(status.getCode(), status);
        }
    }

    private final int status;

    private StatusCode(final int status) {
        this.status = status;
    }

    public int getCode() {
        return status;
    }

    public String getDescription() {
        return descriptions.get(this);
    }

    public static StatusCode fromValue(final int statusCode) {
        return reverseLookup.get(statusCode);
    }
} public class ResourceException extends ApiException {

    public ResourceException(final String message) {
        super(message);
    }
} public class ResourceMapper {

    @SuppressWarnings("unchecked")
    public <T> T fromJson(final JSONObject json,
                          final Class<T> resourceClass) throws ResourceException {

        try {
            if (resourceClass.equals(Device.class)) {
                return (T) Device.fromJson(json);
            } else if (resourceClass.equals(Device.Id.class)) {
                return (T) Device.Id.fromJson(json);
            } else if (resourceClass.equals(DeviceList.class)) {
                return (T) DeviceList.fromJson(json);
            } else if (resourceClass.equals(ProjectBearerAuthToken.class)) {
                return (T) ProjectBearerAuthToken.fromJson(json);
            } else if (resourceClass.equals(UserBearerAuthToken.class)) {
                return (T) UserBearerAuthToken.fromJson(json);
            } else if (resourceClass.equals(RestError.class)) {
                // We only support one error so far.
                final JSONArray arr = json.getJSONArray("errors");
                final JSONObject err = arr.getJSONObject(0);
                return (T) RestError.fromJson(err);
            } else if (resourceClass.equals(Void.class)) {
                return null;
            }
        } catch (JSONException e) {
            // Fall through and throw ResourceException
        } catch (ParseException e) {
            // Fall through and throw ResourceException
        }

        throw new ResourceException("JSON resource mapping failure for class "
                                    + resourceClass.getName()
                                    + "\n\nJSON:\n" + json);
    }

    // Recursively serialize bean. Code partly based on wrap() function
    // org.json.JSONObject
    JSONObject beanSerialize(final Object resource,
                             final Map<String, Object> out) {
        final Class clazz = resource.getClass();
        final Method[] methods = clazz.getDeclaredMethods();
        final JSONObject json = new JSONObject();

        if (clazz.equals(Import.class)) {
            return ((Import) resource).serialize(out);
        } else if (clazz.equals(ImportBatch.class)) {
            return ((ImportBatch) resource).toJson();
        }

        for (final Method m : methods) {

            try {
                if (Modifier.isPublic(m.getModifiers())) {
                    final String name = m.getName();
                    if (name.equals("getClass") || name.equals("getDeclaringClass")) {
                        continue;
                    }

                    final JsonProperty jsonProp = m.getAnnotation(JsonProperty.class);
                    if (m.getAnnotation(JsonIgnore.class) != null) {
                        continue;
                    }

                    boolean isGetter = name.length() > 3 && name.startsWith("get")
                                       && Character.isUpperCase(name.charAt(3));
                    boolean isBoolGetter = name.length() > 2 && name.startsWith("is")
                                           && Character.isUpperCase(name.charAt(2));

                    final String key;
                    if (jsonProp != null) {
                        key = jsonProp.value();
                    } else if (isGetter) {
                        key = Character.toLowerCase(name.charAt(3)) + name.substring(4);
                    } else if (isBoolGetter) {
                        key = Character.toLowerCase(name.charAt(2)) + name.substring(3);
                    } else {
                        continue;  // on to the next method
                    }

                    if (key != null) {
                        final Object object = m.invoke(resource, (Object[]) null);
                        Object result;

                        if (object != null) {
                            try {
                                if (object instanceof JSONObject
                                    || object instanceof JSONArray
                                    || object instanceof Byte
                                    || object instanceof Character
                                    || object instanceof Short
                                    || object instanceof Integer
                                    || object instanceof Long
                                    || object instanceof Boolean
                                    || object instanceof Float
                                    || object instanceof Double
                                    || object instanceof String) {
                                    result = object;
                                } else if (object instanceof Collection) {
                                    result = new JSONArray((Collection) object);
                                } else if (object.getClass().isArray()) {
                                    result = new JSONArray(Arrays.asList(object));
                                } else if (object instanceof Map) {
                                    result = new JSONObject((Map) object);
                                } else if (object instanceof Date) {
                                    result = Util.DATE_FORMAT.format((Date) object);
                                } else {
                                    Package objectPackage = object.getClass().getPackage();
                                    String objectPackageName =
                                        objectPackage != null ? objectPackage.getName() : "";
                                    if (objectPackageName.startsWith("java.")
                                        || objectPackageName.startsWith("javax.")
                                        || object.getClass().getClassLoader() == null) {
                                        result = object.toString();
                                    } else {
                                        result = beanSerialize(object, out);
                                    }
                                }
                                if (result != null) {
                                    out.put(key, result);
                                    json.put(key, result);
                                }
                            } catch (Exception exception) {
                                return null;
                            }
                        }
                    }
                }
            } catch (Exception ignore) {
            }
        }
        return json;
    }

    /*
     * The age old json.org library in Android doesn't support
     * mapping bean-style objects directly to JSON, so we have
     * to call this from a more recent version of JSONObject that
     * we include in our jar.
     */
    public byte[] toJsonBytes(final Object resource) throws UnsupportedEncodingException {
        if (resource instanceof JSONObject) {
            final JSONObject json = (JSONObject) resource;
            return json.toString().getBytes("UTF-8");
        }

        final HashMap<String, Object> out = new HashMap<String, Object>();
        final JSONObject res = beanSerialize(resource, out);
        if (res == null || res.length() == 0) {
            return new JSONObject(out).toString().getBytes("UTF-8");
        } else {
            return res.toString().getBytes("UTF-8");
        }
    }
} public class SameThreadExecutorService extends AbstractExecutorService {

    boolean isShutdown = false;

    @Override
    public void shutdown() {
        isShutdown = true;
    }

    @Override
    public List<Runnable> shutdownNow() {
        isShutdown = true;
        return new LinkedList<Runnable>();
    }

    @Override
    public boolean isShutdown() {
        return isShutdown;
    }

    @Override
    public boolean isTerminated() {
        return isShutdown;
    }

    @Override
    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
        return true;
    }

    @Override
    public void execute(Runnable command) {
        command.run();
    }
}
import com.iobeam.api.ApiException;
import com.iobeam.api.RestException;
import com.iobeam.api.auth.AuthException;
import com.iobeam.api.auth.AuthHandler;
import com.iobeam.api.auth.AuthToken;
import com.iobeam.api.auth.ProjectBearerAuthToken;
import com.iobeam.api.auth.UserBearerAuthToken;
import com.iobeam.api.http.ContentType;
import com.iobeam.api.http.RequestBuilder;
import com.iobeam.api.http.StatusCode;
import com.iobeam.api.resource.ResourceException;
import com.iobeam.api.resource.ResourceMapper;
import com.iobeam.util.concurrent.SameThreadExecutorService;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.CookieManager;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
package com.iobeam.api.client;




/**
 *
 */
public class RestClient {

    protected final static Logger logger = Logger.getLogger(RestClient.class.getName());
    public static final String DEFAULT_DEV_API_HOST = "https://api-dev.iobeam.com";
    public static final String DEFAULT_API_HOST = "https://api.iobeam.com";
    private static final int MAX_HTTP_RETRIES = 3;
    private static final int DEFAULT_MAX_AUTH_ATTEMPTS = 3;
    private final URL url;
    private final CookieManager cookieManager;
    private final ExecutorService executor;
    private final ResourceMapper mapper = new ResourceMapper();