package com.uploadcare.urls;

import com.uploadcare.api.File;

import java.awt.*;

public class CdnPathBuilder {

    public enum ImageFormat {
        FORMAT_JPEG {
            public String toString() {
                return "jpeg";
            }
        },

        FORMAT_PNG {
            public String toString() {
                return "png";
            }
        }
    }

    public enum ImageQuality {
        QUALITY_NORMAL {
            public String toString() {
                return "normal";
            }
        },

        QUALITY_BETTER {
            public String toString() {
                return "better";
            }
        },
        QUALITY_BEST {
            public String toString() {
                return "best";
            }
        },

        QUALITY_LIGHTER {
            public String toString() {
                return "lighter";
            }
        },
        QUALITY_LIGHTEST {
            public String toString() {
                return "lightest";
            }
        }
    }

    private final StringBuilder sb = new StringBuilder("/");

    /**
     * Creates a new CDN path builder for some image file.
     *
     * @param file File to be used for the path
     *
     * @see com.uploadcare.api.File#cdnPath()
     */
    public CdnPathBuilder(File file) {
        sb.append(file.getFileId());
    }

    private void dimensionGuard(int dim) {
        if (dim < 1 || dim > 2048) {
            throw new IllegalArgumentException("Dimensions must be in the range 1-2048");
        }
    }

    private void dimensionsGuard(int width, int height) {
        dimensionGuard(width);
        dimensionGuard(height);
        if (width > 634 && height > 634) {
            throw new IllegalArgumentException("At least one dimension must be less than 634");
        }
    }

    private String colorToHex(Color color) {
        String rgb = Integer.toHexString(color.getRGB());
        return rgb.substring(2);
    }

    /**
     * Adds top-left-aligned crop.
     *
     * @param width Crop width
     * @param height Crop height
     */
    public CdnPathBuilder crop(int width, int height) {
        dimensionsGuard(width, height);
        sb.append("/-/crop/")
                .append(width)
                .append("x")
                .append(height);
        return this;
    }

    /**
     * Adds center-aligned crop.
     *
     * @param width Crop width
     * @param height Crop height
     */
    public CdnPathBuilder cropCenter(int width, int height) {
        dimensionsGuard(width, height);
        sb.append("/-/crop/")
                .append(width)
                .append("x")
                .append(height)
                .append("/center");
        return this;
    }

    /**
     * Adds top-left-aligned crop with a filled background.
     *
     * @param width Crop width
     * @param height Crop height
     * @param color Background color
     */
    public CdnPathBuilder cropColor(int width, int height, Color color) {
        dimensionsGuard(width, height);
        sb.append("/-/crop/")
                .append(width)
                .append("x")
                .append(height)
                .append("/")
                .append(colorToHex(color));
        return this;
    }

    /**
     * Adds center-aligned crop with a filled background.
     *
     * @param width Crop width
     * @param height Crop height
     * @param color Background color
     */
    public CdnPathBuilder cropCenterColor(int width, int height, Color color) {
        dimensionsGuard(width, height);
        sb.append("/-/crop/")
                .append(width)
                .append("x")
                .append(height)
                .append("/center/")
                .append(colorToHex(color));
        return this;
    }

    /**
     * Resizes width, keeping the aspect ratio.
     *
     * @param width New width
     */
    public CdnPathBuilder resizeWidth(int width) {
        dimensionGuard(width);
        sb.append("/-/resize/")
                .append(width)
                .append("x");
        return this;
    }

    /**
     * Resizes height, keeping the aspect ratio.
     *
     * @param height New height
     */
    public CdnPathBuilder resizeHeight(int height) {
        dimensionGuard(height);
        sb.append("/-/resize/x")
                .append(height);
        return this;
    }

    /**
     * Resizes width and height
     *
     * @param width New width
     * @param height New height
     */
    public CdnPathBuilder resize(int width, int height) {
        dimensionsGuard(width, height);
        sb.append("/-/resize/")
                .append(width)
                .append("x")
                .append(height);
        return this;
    }

    /**
     * Scales the image until one of the dimensions fits,
     * then crops the bottom or right side.
     *
     * @param width New width
     * @param height New height
     */
    public CdnPathBuilder scaleCrop(int width, int height) {
        dimensionsGuard(width, height);
        sb.append("/-/scale_crop/")
                .append(width)
                .append("x")
                .append(height);
        return this;
    }

     /**
     * Scales the image until one of the dimensions fits,
     * centers it, then crops the rest.
     *
     * @param width New width
     * @param height New height
     */
    public CdnPathBuilder scaleCropCenter(int width, int height) {
        dimensionsGuard(width, height);
        sb.append("/-/scale_crop/")
                .append(width)
                .append("x")
                .append(height)
                .append("/center");
        return this;
    }

    /**
     * Flips the image.
     */
    public CdnPathBuilder flip() {
        sb.append("/-/flip");
        return this;
    }

    /**
     * Adds a grayscale effect.
     */
    public CdnPathBuilder grayscale() {
        sb.append("/-/grayscale");
        return this;
    }

    /**
     * Inverts colors.
     */
    public CdnPathBuilder invert() {
        sb.append("/-/invert");
        return this;
    }

    /**
     * Horizontally mirror image.
     */
    public CdnPathBuilder mirror() {
        sb.append("/-/mirror");
        return this;
    }

    /**
     * Performs Gaussian blur on result image.
     */
    public CdnPathBuilder blur() {
        sb.append("/-/blur");
        return this;
    }

    /**
     * Performs Gaussian blur on result image.
     *
     * @param strength Strength is standard deviation (aka blur radius) multiplied by ten. Strength
     *                 can be up to 5000. Default is 10.
     */
    public CdnPathBuilder blur(int strength) {
        if (strength < 0 || strength > 5000) {
            strength = 10;
        }
        sb.append("/-/blur/")
                .append(strength);
        return this;
    }

    /**
     * Performs sharpening on result image. This can be useful after scaling down.
     */
    public CdnPathBuilder sharp() {
        sb.append("/-/sharp");
        return this;
    }

    /**
     * Performs sharpening on result image. This can be useful after scaling down.
     *
     * @param strength Strength can be from 0 to 20. Default is 5.
     */
    public CdnPathBuilder sharp(int strength) {
        if (strength < 0 || strength > 20) {
            strength = 5;
        }
        sb.append("/-/sharp/")
                .append(strength);
        return this;
    }

    /**
     * Reduces an image proportionally in order to fit it into given dimensions.
     *
     * @param width New width
     * @param height New height
     */
    public CdnPathBuilder preview(int width, int height) {
        dimensionsGuard(width, height);
        sb.append("/-/preview/")
                .append(width)
                .append("x")
                .append(height);
        return this;
    }

    /**
     * Turn an image to one of the following formats: FORMAT_JPEG or FORMAT_PNG.
     *
     * @param format @link ImageFormat.
     */
    public CdnPathBuilder format(ImageFormat format) {
        sb.append("/-/format/")
                .append(format.toString());
        return this;
    }

    /**
     * Image quality affects size of image and loading speed. Has no effect on non-JPEG images, but does not force format to JPEG.
     *
     * @param quality @link ImageQuality
     * QUALITY_NORMAL – used by default. Fine in most cases.
     * QUALITY_BETTER – can be used on relatively small previews with lots of details. ≈125% file size compared to normal image.
     * QUALITY_BEST – useful if you're a photography god and you want to get perfect quality without paying attention to size. ≈170% file size.
     * QUALITY_LIGHTER – can be used on relatively large images to save traffic without significant quality loss. ≈80% file size.
     * QUALITY_LIGHTEST — useful for retina resolutions, when you don't wory about quality of each pixel. ≈50% file size.
     */
    public CdnPathBuilder quality(ImageQuality quality) {
        sb.append("/-/quality/")
                .append(quality.toString());
        return this;
    }

    /**
     * Returns the current CDN path as a string.
     *
     * Avoid using directly.
     * Instead, pass the configured builder to a URL factory.
     *
     * @return CDN path
     *
     * @see com.uploadcare.urls.Urls#cdn(CdnPathBuilder)
     */
    public String build() {
        return sb.append("/").toString();
    }

}


package com.uploadcare.api;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.uploadcare.data.CopyFileData;
import com.uploadcare.data.FileData;
import com.uploadcare.data.ProjectData;
import com.uploadcare.exceptions.UploadcareApiException;
import com.uploadcare.urls.Urls;

import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

/**
 * Uploadcare API client.
 *
 * Provides simple access to {@code File} and {@code Project} resources.
 *
 * @see com.uploadcare.api.File
 * @see Project
 */
public class Client {

    private final String publicKey;
    private final String privateKey;
    private final boolean simpleAuth;

    private final CloseableHttpClient httpClient;
    private final ObjectMapper objectMapper;
    private final RequestHelperProvider requestHelperProvider;

    /**
     * Initializes a client with custom access keys and simple authentication.
     *
     * @param publicKey Public key
     * @param privateKey Private key
     */
    public Client(String publicKey, String privateKey) {
        this(publicKey, privateKey, true, null);
    }

    /**
     * Initializes a client with custom access keys and simple authentication.
     *
     * @param publicKey Public key
     * @param privateKey Private key
     * @param privateKey CloseableHttpClient
     */
    public Client(String publicKey, String privateKey, CloseableHttpClient httpClient) {
        this(publicKey, privateKey, true, null, httpClient);
    }

    /**
     * Initializes a client with custom access keys.
     * Can use simple or secure authentication.
     *
     * @param publicKey Public key
     * @param privateKey Private key
     * @param simpleAuth If {@code false}, HMAC-based authentication is used
     * @param requestHelperProvider Should be {@code null} to use {@link DefaultRequestHelperProvider}
     */
    public Client(
            String publicKey,
            String privateKey,
            boolean simpleAuth,
            RequestHelperProvider requestHelperProvider) {
        this(publicKey, privateKey, simpleAuth, requestHelperProvider, null);
    }

    /**
     * Initializes a client with custom access keys.
     * Can use simple or secure authentication.
     *
     * @param publicKey Public key
     * @param privateKey Private key
     * @param simpleAuth If {@code false}, HMAC-based authentication is used
     * @param requestHelperProvider Should be {@code null} to use {@link DefaultRequestHelperProvider}
     * @param httpClient Custom HttpClient
     */
    public Client(
            String publicKey,
            String privateKey,
            boolean simpleAuth,
            RequestHelperProvider requestHelperProvider,
            CloseableHttpClient httpClient) {
        this.publicKey = publicKey;
        this.privateKey = privateKey;
        this.simpleAuth = simpleAuth;

        if (requestHelperProvider != null) {
            this.requestHelperProvider = requestHelperProvider;
            this.httpClient = null;
            this.objectMapper = null;
        } else {
            this.requestHelperProvider = new DefaultRequestHelperProvider();

            if (httpClient != null) {
                this.httpClient = httpClient;
            } else {
                PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
                cm.setMaxTotal(200);
                cm.setDefaultMaxPerRoute(20);
                this.httpClient = HttpClients.custom()
                        .setConnectionManager(cm)
                        .build();
            }
            this.objectMapper = new ObjectMapper();
            this.objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
            this.objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        }
    }

    /**
     * Creates a client with demo credentials.
     * Useful for tests and anonymous access.
     *
     * <b>Warning!</b> Do not use in production.
     * All demo account files are eventually purged.
     *
     * @return A demo client
     */
    public static Client demoClient() {
        return new Client("demopublickey", "demoprivatekey");
    }

    /**
     * Returns the public key.
     *
     * @return Public key
     */
    public String getPublicKey() {
        return publicKey;
    }

    /**
     * Returns the private key.
     *
     * @return Private key
     */
    public String getPrivateKey() {
        return privateKey;
    }

    /**
     * Returns {@code true}, if simple authentication is used.
     *
     * @return {@code true}, if simple authentication is used, {@code false} otherwise
     */
    public boolean isSimpleAuth() {
        return simpleAuth;
    }

    CloseableHttpClient getHttpClient() {
        return httpClient;
    }

    ObjectMapper getObjectMapper() {
        return objectMapper;
    }

    public RequestHelper getRequestHelper() {
        return requestHelperProvider.get(this);
    }

    /**
     * Requests project info from the API.
     *
     * @return Project resource
     */
    public Project getProject() {
        URI url = Urls.apiProject();
        RequestHelper requestHelper = getRequestHelper();
        ProjectData projectData = requestHelper.executeQuery(new HttpGet(url), true, ProjectData.class);
        return new Project(this, projectData);
    }

    /**
     * Requests file data.
     *
     * @param fileId Resource UUID
     * @return File resource
     */
    public File getFile(String fileId) {
        URI url = Urls.apiFile(fileId);
        RequestHelper requestHelper = getRequestHelper();
        FileData fileData = requestHelper.executeQuery(new HttpGet(url), true, FileData.class);
        return new File(this, fileData);
    }

    /**
     * Begins to build a request for uploaded files for the current account.
     *
     * @return File resource request builder
     */
    public FilesQueryBuilder getFiles() {
        return new FilesQueryBuilder(this);
    }

    /**
     * Marks a file as deleted.
     *
     * @param fileId Resource UUID
     */
    public void deleteFile(String fileId) {
        URI url = Urls.apiFile(fileId);
        RequestHelper requestHelper = getRequestHelper();
        requestHelper.executeCommand(new HttpDelete(url), true);
    }

    /**
     * Marks a file as saved.
     *
     * This has to be done for all files you want to keep.
     * Unsaved files are eventually purged.
     *
     * @param fileId Resource UUID
     */
    public void saveFile(String fileId) {
        URI url = Urls.apiFileStorage(fileId);
        RequestHelper requestHelper = getRequestHelper();
        requestHelper.executeCommand(new HttpPost(url), true);
    }

    /**
     * Closes client.
     *
     * Ensures that all connections kept alive by the manager get closed and system resources
     * allocated by those connections are released.
     */
    public void close() {
        if (httpClient != null) {
            try {
                httpClient.close();
            } catch (IOException e) {
                throw new UploadcareApiException("Error during closing CloseableHttpClient", e);
            }
        }
    }

    /**
     * @param fileId  Resource UUID
     * @param storage Target storage name
     * @return An object containing the results of the copy request
     */
    public CopyFileData copyFile(String fileId, String storage) {
        RequestHelper requestHelper = getRequestHelper();
        HttpPost request = new HttpPost(Urls.apiFiles());

        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
        nameValuePairs.add(new BasicNameValuePair("source", fileId));
        if (storage != null && !storage.isEmpty()) {
            nameValuePairs.add(new BasicNameValuePair("target", storage));
        }

        try {
            request.setEntity(new UrlEncodedFormEntity(nameValuePairs));
        } catch (UnsupportedEncodingException e) {
            throw new UploadcareApiException("Error during copyFile request creation", e);
        }

        return requestHelper.executeQuery(request, true, CopyFileData.class);
    }
}


package com.uploadcare.upload;

import com.uploadcare.api.Client;
import com.uploadcare.api.File;
import com.uploadcare.data.UploadBaseData;
import com.uploadcare.urls.Urls;

import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.FileBody;

import java.io.InputStream;
import java.net.URI;

/**
 * Uploadcare uploader for files and binary data.
 */
public class FileUploader implements Uploader {

    private final Client client;

    private final java.io.File file;

    private final InputStream stream;

    private final byte[] bytes;

    private final String filename;

    private String store = "auto";

    /**
     * Creates a new uploader from a file on disk
     * (not to be confused with a file resource from Uploadcare API).
     *
     * @param client Uploadcare client
     * @param file   File on disk
     */
    public FileUploader(Client client, java.io.File file) {
        this.client = client;
        this.file = file;
        this.stream = null;
        this.bytes = null;
        this.filename = null;
    }

    /**
     * Creates a new uploader from binary data.
     *
     * @param client   Uploadcare client
     * @param bytes    File contents as binary data
     * @param filename Original filename
     */
    public FileUploader(Client client, byte[] bytes, String filename) {
        this.client = client;
        this.file = null;
        this.stream = null;
        this.bytes = bytes;
        this.filename = filename;
    }

    public FileUploader(Client client, InputStream stream, String filename) {
        this.client = client;
        this.file = null;
        this.stream = stream;
        this.bytes = null;
        this.filename = filename;
    }

    /**
     * Synchronously uploads the file to Uploadcare.
     *
     * The calling thread will be busy until the upload is finished.
     *
     * @return An Uploadcare file
     */
    public File upload() throws UploadFailureException {
        URI uploadUrl = Urls.uploadBase();
        HttpPost request = new HttpPost(uploadUrl);

        MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
        entityBuilder.addTextBody("UPLOADCARE_PUB_KEY", client.getPublicKey());
        entityBuilder.addTextBody("UPLOADCARE_STORE", store);
        if (file != null) {
            entityBuilder.addPart("file", new FileBody(file));
        } else if (stream != null) {
            entityBuilder.addBinaryBody("file", stream);
        } else {
            entityBuilder.addPart("file", new ByteArrayBody(bytes, filename));
        }
        request.setEntity(entityBuilder.build());

        String fileId = client.getRequestHelper()
                .executeQuery(request, false, UploadBaseData.class).file;
        return client.getFile(fileId);
    }

    /**
     * Store the file upon uploading.
     *
     * @param store is set true - store the file upon uploading. Requires “automatic file storing”
     *              setting to be enabled.
     *              is set false - do not store file upon uploading.
     */
    public FileUploader store(boolean store) {
        this.store = store ? String.valueOf(1) : String.valueOf(0);
        return this;
    }
}


package com.uploadcare.api;

import com.uploadcare.data.DataWrapper;
import com.uploadcare.data.PageData;
import com.uploadcare.exceptions.UploadcareApiException;
import com.uploadcare.exceptions.UploadcareAuthenticationException;
import com.uploadcare.exceptions.UploadcareInvalidRequestException;
import com.uploadcare.exceptions.UploadcareNetworkException;
import com.uploadcare.urls.UrlParameter;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import static com.uploadcare.urls.UrlUtils.trustedBuild;

/**
 * A helper class for doing API calls to the Uploadcare API. Supports API version 0.4.
 *
 * TODO Support of throttled requests needs to be added
 */
public class RequestHelper {

    private final Client client;

    public static final String LIBRARY_VERSION = "3.1";

    public static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss Z";

    public static final String DATE_FORMAT_ISO_8601 = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";

    public static final TimeZone UTC = TimeZone.getTimeZone("UTC");

    private static final String EMPTY_MD5 = DigestUtils.md5Hex("");

    private static final String JSON_CONTENT_TYPE = "application/json";

    RequestHelper(Client client) {
        this.client = client;
    }

    public static String rfc2822(Date date) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(RequestHelper.DATE_FORMAT);
        dateFormat.setTimeZone(UTC);
        return dateFormat.format(date);
    }

    public static String iso8601(Date date) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(RequestHelper.DATE_FORMAT_ISO_8601);
        dateFormat.setTimeZone(UTC);
        return dateFormat.format(date);
    }

    public String makeSignature(HttpUriRequest request, String date)
            throws NoSuchAlgorithmException, InvalidKeyException {
        StringBuilder sb = new StringBuilder();
        sb.append(request.getMethod())
                .append("\n").append(EMPTY_MD5)
                .append("\n").append(JSON_CONTENT_TYPE)
                .append("\n").append(date)
                .append("\n").append(request.getURI().getPath());

        byte[] privateKeyBytes = client.getPrivateKey().getBytes();
        SecretKeySpec signingKey = new SecretKeySpec(privateKeyBytes, "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(signingKey);
        byte[] hmacBytes = mac.doFinal(sb.toString().getBytes());
        return Hex.encodeHexString(hmacBytes);
    }

    public void setApiHeaders(HttpUriRequest request) {
        Calendar calendar = new GregorianCalendar(UTC);
        String formattedDate = rfc2822(calendar.getTime());

        request.setHeader("Accept", "application/vnd.uploadcare-v0.4+json");
        request.setHeader("Date", formattedDate);
        request.setHeader("User-Agent",
                String.format("javauploadcare/%s/%s", LIBRARY_VERSION, client.getPublicKey()));

        String authorization;
        if (client.isSimpleAuth()) {
            authorization = "Uploadcare.Simple " + client.getPublicKey() + ":" + client
                    .getPrivateKey();
        } else {
            try {
                String signature = makeSignature(request, formattedDate);
                authorization = "Uploadcare " + client.getPublicKey() + ":" + signature;
            } catch (GeneralSecurityException e) {
                throw new UploadcareApiException("Error when signing the request", e);
            }
        }
        request.setHeader("Authorization", authorization);
    }

    public <T> T executeQuery(HttpUriRequest request, boolean apiHeaders, Class<T> dataClass) {
        if (apiHeaders) {
            setApiHeaders(request);
        }
        try {
            CloseableHttpResponse response = client.getHttpClient().execute(request);
            checkResponseStatus(response);
            try {
                HttpEntity entity = response.getEntity();
                String data = EntityUtils.toString(entity);
                return client.getObjectMapper().readValue(data, dataClass);
            } finally {
                response.close();
            }
        } catch (IOException e) {
            throw new UploadcareNetworkException(e);
        }
    }

    public static void setQueryParameters(URIBuilder builder, List<UrlParameter> parameters) {
        for (UrlParameter parameter : parameters) {
            builder.setParameter(parameter.getParam(), parameter.getValue());
        }
    }

    public <T, U> Iterable<T> executePaginatedQuery(
            final URI url,
            final List<UrlParameter> urlParameters,
            final boolean apiHeaders,
            final Class<? extends PageData<U>> dataClass,
            final DataWrapper<T, U> dataWrapper) {
        return new Iterable<T>() {
            public Iterator<T> iterator() {
                return new Iterator<T>() {
                    private URI next = null;

                    private boolean more;

                    private Iterator<U> pageIterator;

                    {
                        getNext();
                    }

                    private void getNext() {
                        URI pageUrl;
                        if (next == null) {
                            URIBuilder builder = new URIBuilder(url);
                            setQueryParameters(builder, urlParameters);

                            pageUrl = trustedBuild(builder);
                        } else {
                            pageUrl = next;
                        }
                        PageData<U> pageData = executeQuery(new HttpGet(pageUrl), apiHeaders,
                                dataClass);
                        more = pageData.hasMore();
                        next = pageData.getNext();
                        pageIterator = pageData.getResults().iterator();
                    }

                    public boolean hasNext() {
                        if (pageIterator.hasNext()) {
                            return true;
                        } else if (more) {
                            getNext();
                            return true;
                        } else {
                            return false;
                        }
                    }

                    public T next() {
                        return dataWrapper.wrap(pageIterator.next());
                    }

                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }
        };
    }

    /**
     * Executes the request et the Uploadcare API and return the HTTP Response object.
     *
     * The existence of this method(and it's return type) enables the end user to extend the
     * functionality of the
     * Uploadcare API client by creating a subclass of {@link com.uploadcare.api.Client}.
     *
     * @param request    request to be sent to the API
     * @param apiHeaders TRUE if the default API headers should be set
     * @return HTTP Response object
     */
    public HttpResponse executeCommand(HttpUriRequest request, boolean apiHeaders) {
        if (apiHeaders) {
            setApiHeaders(request);
        }

        try {
            CloseableHttpResponse response = client.getHttpClient().execute(request);
            try {
                checkResponseStatus(response);
                return response;
            } finally {
                response.close();
            }
        } catch (IOException e) {
            throw new UploadcareNetworkException(e);
        }
    }

    /**
     * Verifies that the response status codes are within acceptable boundaries and throws
     * corresponding exceptions
     * otherwise.
     *
     * @param response The response object to be checked
     */
    private void checkResponseStatus(HttpResponse response) throws IOException {

        int statusCode = response.getStatusLine().getStatusCode();

        if (statusCode >= 200 && statusCode < 300) {
            return;
        } else if (statusCode == 401 || statusCode == 403) {
            throw new UploadcareAuthenticationException(
                    streamToString(response.getEntity().getContent()));
        } else if (statusCode == 400 || statusCode == 404) {
            throw new UploadcareInvalidRequestException(
                    streamToString(response.getEntity().getContent()));
        } else {
            throw new UploadcareApiException(
                    "Unknown exception during an API call, response:" + streamToString(
                            response.getEntity().getContent()));
        }
    }

    /**
     * Convert an InputStream into a String object. Method taken from http://stackoverflow.com/a/5445161/521535
     *
     * @param is The stream to be converted
     * @return The resulting String
     */
    private static String streamToString(InputStream is) {
        java.util.Scanner s = new java.util.Scanner(is, "UTF-8").useDelimiter("\\A");
        return s.hasNext() ? s.next() : "";
    }
}


package com.uploadcare.urls;

import org.apache.http.client.utils.URIBuilder;

import java.net.URI;

import static com.uploadcare.urls.UrlUtils.trustedBuild;

/**
 * Uploadcare API URL factory methods.
 */
public class Urls {

    private static final String API_BASE = "https://api.uploadcare.com";
    private static final String CDN_BASE = "https://ucarecdn.com";
    private static final String UPLOAD_BASE = "https://upload.uploadcare.com";

    /**
     * Creates a URL to a project resource.
     *
     * @see com.uploadcare.api.Client
     */
    public static URI apiProject() {
        return URI.create(API_BASE + "/project/");
    }

    /**
     * Creates a URL to a file resource.
     *
     * @param fileId File UUID
     *
     * @see com.uploadcare.api.Client
     */
    public static URI apiFile(String fileId) {
        return URI.create(API_BASE + "/files/" + fileId + "/");
    }

    /**
     * Creates a URL to the storage action for a file (saving the file).
     *
     * @param fileId File UUID
     *
     * @see com.uploadcare.api.Client
     */
    public static URI apiFileStorage(String fileId) {
        return URI.create(API_BASE + "/files/" + fileId + "/storage/");
    }

    /**
     * Creates a URL to the file collection resource.
     *
     * @see com.uploadcare.api.Client
     */
    public static URI apiFiles() {
        return URI.create(API_BASE + "/files/");
    }

    /**
     * Creates a full CDN URL with a CDN path builder.
     *
     * @param builder Configured CDN path builder
     */
    public static URI cdn(CdnPathBuilder builder) {
        return URI.create(CDN_BASE + builder.build());
    }

    /**
     * Creates a URL to the file upload endpoint.
     *
     * @see com.uploadcare.upload.FileUploader
     */
    public static URI uploadBase() {
        return URI.create(UPLOAD_BASE + "/base/");
    }

    /**
     * Creates a URL for URL upload.
     *
     * @param sourceUrl URL to upload from
     * @param pubKey Public key
     * @param store Store the file upon uploading.
     *
     * @see com.uploadcare.upload.UrlUploader
     */
    public static URI uploadFromUrl(String sourceUrl, String pubKey, String store) {
        URIBuilder builder = new URIBuilder(URI.create(UPLOAD_BASE));
        builder.setPath("/from_url/")
                .setParameter("source_url", sourceUrl)
                .setParameter("pub_key", pubKey)
                .setParameter("store", store);
        return trustedBuild(builder);
    }

    /**
     * Creates a URL for URL upload status (e.g. progress).
     *
     * @param token Token, received after a URL upload request
     *
     * @see com.uploadcare.upload.UrlUploader
     */
    public static URI uploadFromUrlStatus(String token) {
        URIBuilder builder = new URIBuilder(URI.create(UPLOAD_BASE));
        builder.setPath("/from_url/status/")
                .setParameter("token", token);
        return trustedBuild(builder);
    }

}


/*
 * Copyright (c) 2014 Fizz Buzz LLC
 *
 * 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.
 */

/*
 * Copyright (c) 2013 Fizz Buzz LLC
 *
 * 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 com.fizzbuzz.android.dagger;

import android.support.v7.app.ActionBarActivity;
import dagger.ObjectGraph;

import java.util.ArrayList;
import java.util.List;

import static com.fizzbuzz.android.dagger.Preconditions.checkState;

/**
 * Manages an ObjectGraph on behalf of a ActionBarActivity.  This graph is created by extending the application-scope
 * graph with ActionBarActivity-specific module(s).
 */
public class InjectingActionBarActivity
        extends ActionBarActivity
        implements Injector {
    private ObjectGraph mObjectGraph;

    // implement Injector interface

    /**
     * Gets this ActionBarActivity's object graph.
     *
     * @return the object graph
     */
    @Override
    public final ObjectGraph getObjectGraph() {
        return mObjectGraph;
    }

    /**
     * Injects a target object using this ActionBarActivity's object graph.
     *
     * @param target the target object
     */
    @Override
    public void inject(Object target) {
        checkState(mObjectGraph != null, "object graph must be assigned prior to calling inject");
        mObjectGraph.inject(target);
    }

    /**
     * Creates an object graph for this ActionBarActivity by extending the application-scope object graph with the
     * modules returned by {@link #getModules()}.
     * <p/>
     * Injects this ActionBarActivity using the created graph.
     */
    @Override
    protected void onCreate(android.os.Bundle savedInstanceState) {
        // expand the application graph with the activity-specific module(s)
        ObjectGraph appGraph = ((Injector) getApplication()).getObjectGraph();
        List<Object> activityModules = getModules();
        mObjectGraph = appGraph.plus(activityModules.toArray());

        // now we can inject ourselves
        inject(this);

        // note: we do the graph setup and injection before calling super.onCreate so that InjectingFragments
        // associated with this InjectingActivity can do their graph setup and injection in their
        // onAttach override.
        super.onCreate(savedInstanceState);

    }

    @Override
    protected void onDestroy() {
        // Eagerly clear the reference to the activity graph to allow it to be garbage collected as
        // soon as possible.
        mObjectGraph = null;

        super.onDestroy();
    }

    /**
     * Returns the list of dagger modules to be included in this ActionBarActivity's object graph.  Subclasses that
     * override this method should add to the list returned by super.getModules().
     *
     * @return the list of modules
     */
    protected List<Object> getModules() {
        List<Object> result = new ArrayList<Object>();
        result.add(new InjectingActivityModule(this, this));
        return result;
    }
}


/*
 * Copyright (c) 2013 Fizz Buzz LLC
 *
 * 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 com.fizzbuzz.android.dagger;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import dagger.Module;
import dagger.ObjectGraph;
import dagger.Provides;

import javax.inject.Qualifier;
import javax.inject.Singleton;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;

import static com.fizzbuzz.android.dagger.Preconditions.checkState;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Manages an ObjectGraph on behalf of a BroadcastReceiver.  This graph is created by extending the application-scope
 * graph with BroadcastReceiver-specific module(s).
 */
public class InjectingBroadcastReceiver
        extends BroadcastReceiver
        implements Injector {

    private Context mContext;
    private ObjectGraph mObjectGraph;

    /**
     * Creates an object graph for this BroadcastReceiver by extending the application-scope object graph with the
     * modules returned by {@link #getModules()}.
     *

     * Injects this BroadcastReceiver using the created graph.
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        mContext = context;

        // extend the application-scope object graph with the modules for this broadcast receiver
        mObjectGraph = ((Injector) context.getApplicationContext()).getObjectGraph().plus(getModules().toArray());

        // then inject ourselves
        mObjectGraph.inject(this);
    }

    /**
     * Gets this BroadcastReceiver's object graph.
     *
     * @return the object graph
     */
    @Override
    public ObjectGraph getObjectGraph() {
        return mObjectGraph;
    }

    /**
     * Injects a target object using this BroadcastReceiver's object graph.
     *
     * @param target the target object
     */
    public void inject(Object target) {
        checkState(mObjectGraph != null, "object graph must be initialized prior to calling inject");
        mObjectGraph.inject(target);
    }

    /**
     * Returns the list of dagger modules to be included in this BroadcastReceiver's object graph.  Subclasses that
     * override this method should add to the list returned by super.getModules().
     *
     * @return the list of modules
     */
    protected List<Object> getModules() {
        List<Object> result = new ArrayList<Object>();
        result.add(new InjectingBroadcastReceiverModule(mContext, this, this));
        return result;
    }

    /**
     * The dagger module associated with {@link InjectingBroadcastReceiver}
     */
    @Module(library = true)
    public static class InjectingBroadcastReceiverModule {
        Context mContext;
        android.content.BroadcastReceiver mReceiver;
        Injector mInjector;

        /**
         * Class constructor.
         *
         * @param receiver the InjectingBroadcastReceiver with which this module is associated.
         */
        public InjectingBroadcastReceiverModule(Context context, android.content.BroadcastReceiver receiver, Injector injector) {
            mContext = context;
            mReceiver = receiver;
            mInjector = injector;
        }

        /**
         * Provides the Context for the BroadcastReceiver associated with this graph.
         *
         * @return the BroadcastReceiver Context
         */
        @Provides
        @Singleton
        @BroadcastReceiver
        public Context provideBroadcastReceiverContext() {
            return mContext;
        }
        /**
         * Provides the BroadcastReceiver
         *
         * @return the BroadcastReceiver
         */
        @Provides
        @Singleton
        public android.content.BroadcastReceiver provideBroadcastReceiver() {
            return mReceiver;
        }

        /**
         * Provides the Injector for the BroadcastReceiver-scope graph
         *
         * @return the Injector
         */
        @Provides
        @Singleton
        @BroadcastReceiver
        public Injector provideBroadcastReceiverInjector() {
            return mInjector;
        }
        /**
         * Defines a qualifier annotation which can be used in conjunction with a type to identify dependencies within
         * the object graph.
         *
         * @see the dagger documentation
         */
        @Qualifier
        @Target({FIELD, PARAMETER, METHOD})
        @Documented
        @Retention(RUNTIME)
        public @interface BroadcastReceiver {
        }
    }
}


/*
 * Copyright (c) 2013 Fizz Buzz LLC
 *
 * 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 com.fizzbuzz.android.dagger;

import android.app.Service;
import android.content.Context;
import dagger.Module;
import dagger.ObjectGraph;
import dagger.Provides;

import javax.inject.Qualifier;
import javax.inject.Singleton;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;

import static com.fizzbuzz.android.dagger.Preconditions.checkState;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Manages an ObjectGraph on behalf of an Service.  This graph is created by extending the application-scope graph with
 * Service-specific module(s).
 */
public abstract class InjectingService
        extends Service
        implements Injector {

    private ObjectGraph mObjectGraph;

    /**
     * Creates an object graph for this Service by extending the application-scope object graph with the modules
     * returned by {@link #getModules()}.
     * <p/>
     * Injects this Service using the created graph.
     */
    @Override
    public void onCreate() {
        super.onCreate();

        // extend the application-scope object graph with the modules for this service
        mObjectGraph = ((Injector) getApplication()).getObjectGraph().plus(getModules().toArray());

        // then inject ourselves
        mObjectGraph.inject(this);
    }

    /**
     * Gets this Service's object graph.
     *
     * @return the object graph
     */
    @Override
    public ObjectGraph getObjectGraph() {
        return mObjectGraph;
    }

    /**
     * Injects a target object using this Service's object graph.
     *
     * @param target the target object
     */
    public void inject(Object target) {
        checkState(mObjectGraph != null, "object graph must be initialized prior to calling inject");
        mObjectGraph.inject(target);
    }

    /**
     * Returns the list of dagger modules to be included in this Service's object graph.  Subclasses that override
     * this method should add to the list returned by super.getModules().
     *
     * @return the list of modules
     */
    protected List<Object> getModules() {
        List<Object> result = new ArrayList<Object>();
        return result;
    }

    /**
     * The dagger module associated with {@link InjectingService}.
     */
    @Module(library = true)
    public static class InjectingServiceModule {
        private android.app.Service mService;
        private Injector mInjector;

        /**
         * Class constructor.
         *
         * @param service the Service with which this module is associated.
         */
        public InjectingServiceModule(android.app.Service service, Injector injector) {
            mService = service;
            mInjector = injector;
        }

        /**
         * Provides the Service Context
         *
         * @return the Service Context
         */
        @Provides
        @Singleton
        @Service
        public Context provideServiceContext() {
            return mService;
        }

        @Provides
        @Singleton
        public android.app.Service provideService() {
            return mService;
        }

        @Provides
        @Singleton
        @Service
        public Injector provideServiceInjector() {
            return mInjector;
        }

        @Qualifier
        @Target({FIELD, PARAMETER, METHOD})
        @Documented
        @Retention(RUNTIME)
        public @interface Service {
        }
    }
}


/*
 * Copyright (c) 2014 Fizz Buzz LLC
 *
 * 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.
 */

/*
 * Copyright (c) 2013 Fizz Buzz LLC
 *
 * 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 com.fizzbuzz.android.dagger;

import android.app.Activity;
import dagger.ObjectGraph;

import java.util.ArrayList;
import java.util.List;

import static com.fizzbuzz.android.dagger.Preconditions.checkState;

/**
 * Manages an ObjectGraph on behalf of an Activity.  This graph is created by extending the application-scope graph with
 * Activity-specific module(s).
 */
public class InjectingActivity
        extends Activity
        implements Injector {
    private ObjectGraph mObjectGraph;

    /**
     * Gets this Activity's object graph.
     *
     * @return the object graph
     */
    @Override
    public final ObjectGraph getObjectGraph() {
        return mObjectGraph;
    }

    /**
     * Injects a target object using this Activity's object graph.
     *
     * @param target the target object
     */
    @Override
    public void inject(Object target) {
        checkState(mObjectGraph != null, "object graph must be assigned prior to calling inject");
        mObjectGraph.inject(target);
    }

    // implement Injector interface

    /**
     * Creates an object graph for this Activity by extending the application-scope object graph with the modules
     * returned by {@link #getModules()}.
     * <p/>
     * Injects this Activity using the created graph.
     */
    @Override
    protected void onCreate(android.os.Bundle savedInstanceState) {
        // extend the application-scope object graph with the modules for this activity
        mObjectGraph = ((Injector) getApplication()).getObjectGraph().plus(getModules().toArray());

        // now we can inject ourselves
        inject(this);

        // note: we do the graph setup and injection before calling super.onCreate so that InjectingFragments
        // associated with this InjectingActivity can do their graph setup and injection in their
        // onAttach override.
        super.onCreate(savedInstanceState);

    }

    @Override
    protected void onDestroy() {
        // Eagerly clear the reference to the activity graph to allow it to be garbage collected as
        // soon as possible.
        mObjectGraph = null;

        super.onDestroy();
    }

    /**
     * Returns the list of dagger modules to be included in this Activity's object graph.  Subclasses that override
     * this method should add to the list returned by super.getModules().
     *
     * @return the list of modules
     */
    protected List<Object> getModules() {
        List<Object> result = new ArrayList<Object>();
        result.add(new InjectingActivityModule(this, this));
        return result;
    }
}


/*
 * Copyright (c) 2014 Fizz Buzz LLC
 *
 * 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.
 */

/*
 * Copyright (c) 2013 Fizz Buzz LLC
 *
 * 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 com.fizzbuzz.android.dagger;

import android.app.Activity;
import android.support.v4.app.ListFragment;
import dagger.ObjectGraph;

import java.util.ArrayList;
import java.util.List;

import static com.fizzbuzz.android.dagger.Preconditions.checkState;

/**
 * Manages an ObjectGraph on behalf of an ListFragment.  This graph is created by extending the hosting Activity's graph
 * with Fragment-specific module(s).
 */
public class InjectingListFragment
        extends ListFragment
        implements Injector {
    private ObjectGraph mObjectGraph;
    private boolean mFirstAttach = true;

    /**
     * Creates an object graph for this Fragment by extending the hosting Activity's object graph with the modules
     * returned by {@link #getModules()}.
     * <p/>
     * Injects this Fragment using the created graph.
     */
    @Override
    public void onAttach(final Activity activity) {
        super.onAttach(activity);

        // expand the activity graph with the fragment-specific module(s)
        ObjectGraph appGraph = ((Injector) activity).getObjectGraph();
        List<Object> fragmentModules = getModules();
        mObjectGraph = appGraph.plus(fragmentModules.toArray());

        // make sure it's the first time through; we don't want to re-inject a retained fragment that is going
        // through a detach/attach sequence.
        if (mFirstAttach) {
            inject(this);
            mFirstAttach = false;
        }
    }

    @Override
    public void onDestroy() {
        // Eagerly clear the reference to the object graph to allow it to be garbage collected as
        // soon as possible.
        mObjectGraph = null;

        super.onDestroy();
    }

    // implement Injector interface

    /**
     * Gets this ListFragment's object graph.
     *
     * @return the object graph
     */
    @Override
    public final ObjectGraph getObjectGraph() {
        return mObjectGraph;
    }

    /**
     * Injects a target object using this ListFragment's object graph.
     *
     * @param target the target object
     */
    @Override
    public void inject(Object target) {
        checkState(mObjectGraph != null, "object graph must be assigned prior to calling inject");
        mObjectGraph.inject(target);
    }

    /**
     * Returns the list of dagger modules to be included in this ListFragment's object graph.  Subclasses that override
     * this method should add to the list returned by super.getModules().
     *
     * @return the list of modules
     */
    protected List<Object> getModules() {
        List<Object> result = new ArrayList<Object>();
        result.add(new InjectingFragmentModule(this, this));
        return result;
    }
}


/*
 * Copyright (c) 2013 Fizz Buzz LLC
 *
 * 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 com.fizzbuzz.android.dagger;

import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import dagger.Module;
import dagger.ObjectGraph;
import dagger.Provides;

import javax.inject.Qualifier;
import javax.inject.Singleton;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;

import static com.fizzbuzz.android.dagger.Preconditions.checkState;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Manages an ObjectGraph on behalf of an Application.
 */
public abstract class InjectingApplication
        extends Application
        implements Injector {

    private List<Object> mSeedModules = new ArrayList<Object>();
    private ObjectGraph mObjectGraph;

    public void addSeedModules(List<Object> modules) {
        mSeedModules.addAll(modules);
    }

    /**
     * Creates an object graph for this Application using the modules returned by {@link #getModules()}.
     *

     * Injects this Application using the created graph.
     */
    @Override
    public void onCreate() {
        super.onCreate();

        // initialize object graph and inject this
        mObjectGraph = ObjectGraph.create(getModules().toArray());
        mObjectGraph.inject(this);

        // debug mode stuff
        if ((getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) == 1) {
            mObjectGraph.validate(); // validate dagger's object graph
        }
    }

    /**
     * Gets this Application's object graph.
     *
     * @return the object graph
     */
    @Override
    public ObjectGraph getObjectGraph() {
        return mObjectGraph;
    }

    /**
     * Injects a target object using this Application's object graph.
     *
     * @param target the target object
     */
    public void inject(Object target) {
        checkState(mObjectGraph != null, "object graph must be initialized prior to calling inject");
        mObjectGraph.inject(target);
    }

    /**
     * Returns the list of dagger modules to be included in this Application's object graph.  Subclasses that override
     * this method should add to the list returned by super.getModules().
     *
     * @return the list of modules
     */
    protected List<Object> getModules() {
        List<Object> result = new ArrayList<Object>();
        result.addAll(mSeedModules);
        result.add(new InjectingApplicationModule(this, this));
        return result;
    }

    /**
     * The dagger module associated with {@link InjectingApplication}
     */
    @Module(library = true)
    public static class InjectingApplicationModule {
        android.app.Application mApp;
        Injector mInjector;

        /**
         * Class constructor.
         *
         * @param app the Application with which this module is associated
         * @param injector the dagger injector for the Application-scope graph
         */
        public InjectingApplicationModule(android.app.Application app, Injector injector) {
            mApp = app;
            mInjector = injector;
        }

        /**
         * Provides the Application Context associated with this module's graph.
         *
         * @return the Application Context
         */
        @Provides
        @Singleton
        @Application
        public Context provideApplicationContext() {
            return mApp.getApplicationContext();
        }

        /**
         * Provides the Application
         *
         * @return the Application
         */
        @Provides
        @Singleton
        public android.app.Application provideApplication() {
            return mApp;
        }

        /**
         * Provides the Injector for the Application-scope graph
         *
         * @return the Injector
         */
        @Provides
        @Singleton
        @Application
        public Injector provideApplicationInjector() {
            return mInjector;
        }

        /**
         * Defines an qualifier annotation which can be used in conjunction with a type to identify dependencies within
         * this module's object graph.
         *
         * @see the dagger documentation
         */
        @Qualifier
        @Target({FIELD, PARAMETER, METHOD})
        @Documented
        @Retention(RUNTIME)
        public @interface Application {
        }
    }
}


/*
 * Copyright (c) 2014 Fizz Buzz LLC
 *
 * 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 com.fizzbuzz.android.dagger;

import android.app.Activity;
import android.support.v4.app.Fragment;
import dagger.ObjectGraph;

import java.util.ArrayList;
import java.util.List;

import static com.fizzbuzz.android.dagger.Preconditions.checkState;

/**
 * Manages an ObjectGraph on behalf of a Fragment.  This graph is created by extending the hosting Activity's graph
 * with Fragment-specific module(s).
 */
public class InjectingFragment
        extends Fragment
        implements Injector {
    private ObjectGraph mObjectGraph;
    private boolean mFirstAttach = true;

    /**
     * Creates an object graph for this Fragment by extending the hosting Activity's object graph with the modules
     * returned by {@link #getModules()}.
     * <p/>
     * Injects this Fragment using the created graph.
     */
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // expand the activity graph with the fragment-specific module(s)
        ObjectGraph appGraph = ((Injector) activity).getObjectGraph();
        List<Object> fragmentModules = getModules();
        mObjectGraph = appGraph.plus(fragmentModules.toArray());

        // make sure it's the first time through; we don't want to re-inject a retained fragment that is going
        // through a detach/attach sequence.
        if (mFirstAttach) {
            inject(this);
            mFirstAttach = false;
        }
    }

    @Override
    public void onDestroy() {
        // Eagerly clear the reference to the object graph to allow it to be garbage collected as
        // soon as possible.
        mObjectGraph = null;

        super.onDestroy();
    }

    // implement Injector interface

    /**
     * Gets this Fragment's object graph.
     *
     * @return the object graph
     */
    @Override
    public final ObjectGraph getObjectGraph() {
        return mObjectGraph;
    }

    /**
     * Injects a target object using this Fragment's object graph.
     *
     * @param target the target object
     */
    @Override
    public void inject(Object target) {
        checkState(mObjectGraph != null, "object graph must be assigned prior to calling inject");
        mObjectGraph.inject(target);
    }

    /**
     * Returns the list of dagger modules to be included in this Fragment's object graph.  Subclasses that override
     * this method should add to the list returned by super.getModules().
     *
     * @return the list of modules
     */
    protected List<Object> getModules() {
        List<Object> result = new ArrayList<Object>();
        result.add(new InjectingFragmentModule(this, this));
        return result;
    }
}


/*
 * Copyright (c) 2014 Fizz Buzz LLC
 */

package com.fizzbuzz.android.dagger;

import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import dagger.Module;
import dagger.ObjectGraph;
import dagger.Provides;

import javax.inject.Qualifier;
import javax.inject.Singleton;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;

import static com.fizzbuzz.android.dagger.Preconditions.checkState;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Manages an ObjectGraph on behalf of an AppWidgetProvider.  This graph is created by extending the application-scope
 * graph with AppWidgetProvider-specific module(s).
 */
public class InjectingAppWidgetProvider
        extends AppWidgetProvider
        implements Injector {

    private Context mContext;
    private ObjectGraph mObjectGraph;

    /**
     * Creates an object graph for this AppWidgetProvider by extending the application-scope object graph with the
     * modules returned by {@link #getModules()}.
     *

     * Injects this AppWidgetProvider using the created graph.
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        mContext = context;

        // extend the application-scope object graph with the modules for this AppWidgetProvider
        mObjectGraph = ((Injector) context.getApplicationContext()).getObjectGraph().plus(getModules().toArray());

        // then inject ourselves
        mObjectGraph.inject(this);

        super.onReceive(context, intent);
    }

    /**
     * Gets this AppWidgetProvider's object graph.
     *
     * @return the object graph
     */
    @Override
    public ObjectGraph getObjectGraph() {
        return mObjectGraph;
    }

    /**
     * Injects a target object using this AppWidgetProvider's object graph.
     *
     * @param target the target object
     */
    public void inject(Object target) {
        checkState(mObjectGraph != null, "object graph must be initialized prior to calling inject");
        mObjectGraph.inject(target);
    }

    /**
     * Returns the list of dagger modules to be included in this AppWidgetProvider's object graph.  Subclasses that
     * override this method should add to the list returned by super.getModules().
     *
     * @return the list of modules
     */
    protected List<Object> getModules() {
        List<Object> result = new ArrayList<Object>();
        result.add(new InjectingAppWidgetProviderModule(mContext, this, this));
        return result;
    }

    /**
     * The dagger module associated with {@link com.fizzbuzz.android.dagger.InjectingAppWidgetProvider}
     */
    @Module(library = true)
    public static class InjectingAppWidgetProviderModule {
        Context mContext;
        android.appwidget.AppWidgetProvider mAppWidgetProvider;
        Injector mInjector;

        /**
         * Class constructor.
         *
         * @param appWidgetProvider the InjectingAppWidgetProvider with which this module is associated.
         */
        public InjectingAppWidgetProviderModule(Context context, android.appwidget.AppWidgetProvider appWidgetProvider, Injector injector) {
            mContext = context;
            mAppWidgetProvider = appWidgetProvider;
            mInjector = injector;
        }

        /**
         * Provides the Context for the AppWidgetProvider associated with this graph.
         *
         * @return the AppWidgetProvider Context
         */
        @Provides
        @Singleton
        @AppWidgetProvider
        public Context provideAppWidgetProviderContext() {
            return mContext;
        }
        /**
         * Provides the AppWidgetProvider
         *
         * @return the AppWidgetProvider
         */
        @Provides
        @Singleton
        public android.appwidget.AppWidgetProvider provideAppWidgetProvider() {
            return mAppWidgetProvider;
        }

        /**
         * Provides the Injector for the AppWidgetProvider-scope graph
         *
         * @return the Injector
         */
        @Provides
        @Singleton
        @AppWidgetProvider
        public Injector provideAppWidgetProviderInjector() {
            return mInjector;
        }
        /**
         * Defines a qualifier annotation which can be used in conjunction with a type to identify dependencies within
         * the object graph.
         *
         * @see the dagger documentation
         */
        @Qualifier
        @Target({FIELD, PARAMETER, METHOD})
        @Documented
        @Retention(RUNTIME)
        public @interface AppWidgetProvider {
        }
    }
}


/*
 * Copyright 2016 Yan Zhenjie
 *
 * 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 com.yanzhenjie.alertdialog;

import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ArrayRes;
import android.support.annotation.AttrRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.annotation.StyleRes;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListAdapter;
import android.widget.ListView;

/**
 * Created by Yan Zhenjie on 2016/12/12.
 */
public abstract class AlertDialog {

    /**
     * Create new Builder.
     *
     * @param context context.
     * @return {@link Builder}.
     */
    public static Builder newBuilder(Context context) {
        if (Build.VERSION.SDK_INT >= 21) {
            return new APi21Builder(context);
        }
        return new Api20Builder(context);
    }

    /**
     * Create new Builder.
     *
     * @param context    context.
     * @param themeResId theme res id.
     * @return {@link Builder}.
     */
    public static Builder newBuilder(Context context, int themeResId) {
        if (Build.VERSION.SDK_INT >= 21) {
            return new APi21Builder(context, themeResId);
        }
        return new Api20Builder(context, themeResId);
    }

    /**
     * Create new Builder.
     *
     * @param context context.
     * @return {@link Builder}.
     * @deprecated use {@link #newBuilder(Context)} instead.
     */
    @Deprecated
    public static Builder build(Context context) {
        return newBuilder(context);
    }

    /**
     * Create new Builder.
     *
     * @param context    context.
     * @param themeResId theme res id.
     * @return {@link Builder}.
     * @deprecated use {@link #newBuilder(Context, int)} instead.
     */
    public static Builder build(Context context, int themeResId) {
        return newBuilder(context, themeResId);
    }

    public abstract void show();

    public abstract void dismiss();

    public abstract boolean isShowing();

    public abstract void cancel();

    public abstract Button getButton(int whichButton);

    public abstract
    @Nullable
    ListView getListView();

    public abstract
    @NonNull
    Context getContext();

    public abstract
    @Nullable
    View getCurrentFocus();

    public abstract
    @NonNull
    LayoutInflater getLayoutInflater();

    public abstract
    @Nullable
    Activity getOwnerActivity();

    public abstract int getVolumeControlStream();

    public abstract
    @Nullable
    Window getWindow();

    private static class Api21Dialog extends AlertDialog {

        private android.app.AlertDialog alertDialogApp;

        private Api21Dialog(android.app.AlertDialog alertDialog) {
            this.alertDialogApp = alertDialog;
        }

        @Override
        public void show() {
            alertDialogApp.show();
        }

        @Override
        public void dismiss() {
            if (alertDialogApp.isShowing())
                alertDialogApp.dismiss();
        }

        @Override
        public boolean isShowing() {
            return alertDialogApp.isShowing();
        }

        @Override
        public void cancel() {
            if (alertDialogApp.isShowing())
                alertDialogApp.cancel();
        }

        @Override
        public Button getButton(int whichButton) {
            return alertDialogApp.getButton(whichButton);
        }

        @Nullable
        @Override
        public ListView getListView() {
            return alertDialogApp.getListView();
        }

        @NonNull
        @Override
        public Context getContext() {
            return alertDialogApp.getContext();
        }

        @Nullable
        @Override
        public View getCurrentFocus() {
            return alertDialogApp.getCurrentFocus();
        }

        @NonNull
        @Override
        public LayoutInflater getLayoutInflater() {
            return alertDialogApp.getLayoutInflater();
        }

        @Nullable
        @Override
        public Activity getOwnerActivity() {
            return alertDialogApp.getOwnerActivity();
        }

        @Override
        public int getVolumeControlStream() {
            return alertDialogApp.getVolumeControlStream();
        }

        @Nullable
        @Override
        public Window getWindow() {
            return alertDialogApp.getWindow();
        }
    }

    private static class Api20Dialog extends AlertDialog {

        private android.support.v7.app.AlertDialog alertDialog;

        private Api20Dialog(android.support.v7.app.AlertDialog alertDialog) {
            this.alertDialog = alertDialog;
        }

        @Override
        public void show() {
            alertDialog.show();
        }

        @Override
        public void dismiss() {
            if (alertDialog.isShowing())
                alertDialog.dismiss();
        }

        @Override
        public boolean isShowing() {
            return alertDialog.isShowing();
        }

        @Override
        public void cancel() {
            if (alertDialog.isShowing())
                alertDialog.cancel();
        }

        @Override
        public Button getButton(int whichButton) {
            return alertDialog.getButton(whichButton);
        }

        @Nullable
        @Override
        public ListView getListView() {
            return alertDialog.getListView();
        }

        @NonNull
        @Override
        public Context getContext() {
            return alertDialog.getContext();
        }

        @Nullable
        @Override
        public View getCurrentFocus() {
            return alertDialog.getCurrentFocus();
        }

        @NonNull
        @Override
        public LayoutInflater getLayoutInflater() {
            return alertDialog.getLayoutInflater();
        }

        @Nullable
        @Override
        public Activity getOwnerActivity() {
            return alertDialog.getOwnerActivity();
        }

        @Override
        public int getVolumeControlStream() {
            return alertDialog.getVolumeControlStream();
        }

        @Nullable
        @Override
        public Window getWindow() {
            return alertDialog.getWindow();
        }
    }

    public interface Builder {

        @NonNull
        Context getContext();

        Builder setTitle(@StringRes int titleId);

        Builder setTitle(CharSequence title);

        Builder setCustomTitle(View customTitleView);

        Builder setMessage(@StringRes int messageId);

        Builder setMessage(CharSequence message);

        Builder setIcon(@DrawableRes int iconId);

        Builder setIcon(Drawable icon);

        Builder setIconAttribute(@AttrRes int attrId);

        Builder setPositiveButton(@StringRes int textId, final DialogInterface.OnClickListener listener);

        Builder setPositiveButton(CharSequence text, final DialogInterface.OnClickListener listener);

        Builder setNegativeButton(@StringRes int textId, final DialogInterface.OnClickListener listener);

        Builder setNegativeButton(CharSequence text, final DialogInterface.OnClickListener listener);

        Builder setNeutralButton(@StringRes int textId, final DialogInterface.OnClickListener listener);

        Builder setNeutralButton(CharSequence text, final DialogInterface.OnClickListener listener);

        Builder setCancelable(boolean cancelable);

        Builder setOnCancelListener(DialogInterface.OnCancelListener onCancelListener);

        Builder setOnDismissListener(DialogInterface.OnDismissListener onDismissListener);

        Builder setOnKeyListener(DialogInterface.OnKeyListener onKeyListener);

        Builder setItems(@ArrayRes int itemsId, final DialogInterface.OnClickListener listener);

        Builder setItems(CharSequence[] items, final DialogInterface.OnClickListener listener);

        Builder setAdapter(final ListAdapter adapter, final DialogInterface.OnClickListener listener);

        Builder setCursor(final Cursor cursor, final DialogInterface.OnClickListener listener, String
                labelColumn);

        Builder setMultiChoiceItems(@ArrayRes int itemsId, boolean[] checkedItems, final DialogInterface
                .OnMultiChoiceClickListener listener);

        Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, final DialogInterface
                .OnMultiChoiceClickListener listener);

        Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn, final
        DialogInterface.OnMultiChoiceClickListener listener);

        Builder setSingleChoiceItems(@ArrayRes int itemsId, int checkedItem, final DialogInterface
                .OnClickListener listener);

        Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn, final
        DialogInterface.OnClickListener listener);

        Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final DialogInterface
                .OnClickListener listener);

        Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, final DialogInterface
                .OnClickListener listener);

        Builder setOnItemSelectedListener(final AdapterView.OnItemSelectedListener listener);

        Builder setView(int layoutResId);

        Builder setView(View view);

        AlertDialog create();

        AlertDialog show();
    }

    private static class APi21Builder implements Builder {

        private android.app.AlertDialog.Builder builder;

        private APi21Builder(@NonNull Context context) {
            this(context, 0);
        }

        private APi21Builder(@NonNull Context context, @StyleRes int themeResId) {
            builder = new android.app.AlertDialog.Builder(context, themeResId);
        }

        @NonNull
        @Override
        public Context getContext() {
            return builder.getContext();
        }

        @Override
        public Builder setTitle(@StringRes int titleId) {
            builder.setTitle(titleId);
            return this;
        }

        @Override
        public Builder setTitle(CharSequence title) {
            builder.setTitle(title);
            return this;
        }

        @Override
        public Builder setCustomTitle(View customTitleView) {
            builder.setCustomTitle(customTitleView);
            return this;
        }

        @Override
        public Builder setMessage(@StringRes int messageId) {
            builder.setMessage(messageId);
            return this;
        }

        @Override
        public Builder setMessage(CharSequence message) {
            builder.setMessage(message);
            return this;
        }

        @Override
        public Builder setIcon(@DrawableRes int iconId) {
            builder.setIcon(iconId);
            return this;
        }

        @Override
        public Builder setIcon(Drawable icon) {
            builder.setIcon(icon);
            return this;
        }

        @Override
        public Builder setIconAttribute(@AttrRes int attrId) {
            builder.setIconAttribute(attrId);
            return this;
        }

        @Override
        public Builder setPositiveButton(@StringRes int textId, final DialogInterface.OnClickListener
                listener) {
            builder.setPositiveButton(textId, listener);
            return this;
        }

        @Override
        public Builder setPositiveButton(CharSequence text, final DialogInterface.OnClickListener listener) {
            builder.setPositiveButton(text, listener);
            return this;
        }

        @Override
        public Builder setNegativeButton(@StringRes int textId, final DialogInterface.OnClickListener
                listener) {
            builder.setNegativeButton(textId, listener);
            return this;
        }

        @Override
        public Builder setNegativeButton(CharSequence text, final DialogInterface.OnClickListener listener) {
            builder.setNegativeButton(text, listener);
            return this;
        }

        @Override
        public Builder setNeutralButton(@StringRes int textId, final DialogInterface.OnClickListener
                listener) {
            builder.setNeutralButton(textId, listener);
            return this;
        }

        @Override
        public Builder setNeutralButton(CharSequence text, final DialogInterface.OnClickListener listener) {
            builder.setNeutralButton(text, listener);
            return this;
        }

        @Override
        public Builder setCancelable(boolean cancelable) {
            builder.setCancelable(cancelable);
            return this;
        }

        @Override
        public Builder setOnCancelListener(DialogInterface.OnCancelListener onCancelListener) {
            builder.setOnCancelListener(onCancelListener);
            return this;
        }

        @Override
        public Builder setOnDismissListener(DialogInterface.OnDismissListener onDismissListener) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                builder.setOnDismissListener(onDismissListener);
            }
            return this;
        }

        @Override
        public Builder setOnKeyListener(DialogInterface.OnKeyListener onKeyListener) {
            builder.setOnKeyListener(onKeyListener);
            return this;
        }

        @Override
        public Builder setItems(@ArrayRes int itemsId, final DialogInterface.OnClickListener listener) {
            builder.setItems(itemsId, listener);
            return this;
        }

        @Override
        public Builder setItems(CharSequence[] items, final DialogInterface.OnClickListener listener) {
            builder.setItems(items, listener);
            return this;
        }

        @Override
        public Builder setAdapter(final ListAdapter adapter, final DialogInterface.OnClickListener listener) {
            builder.setAdapter(adapter, listener);
            return this;
        }

        @Override
        public Builder setCursor(final Cursor cursor, final DialogInterface.OnClickListener listener,
                                 String labelColumn) {
            builder.setCursor(cursor, listener, labelColumn);
            return this;
        }

        @Override
        public Builder setMultiChoiceItems(@ArrayRes int itemsId, boolean[] checkedItems, final
        DialogInterface.OnMultiChoiceClickListener listener) {
            builder.setMultiChoiceItems(itemsId, checkedItems, listener);
            return this;
        }

        @Override
        public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, final
        DialogInterface.OnMultiChoiceClickListener listener) {
            builder.setMultiChoiceItems(items, checkedItems, listener);
            return this;
        }

        @Override
        public Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn, final
        DialogInterface.OnMultiChoiceClickListener listener) {
            builder.setMultiChoiceItems(cursor, isCheckedColumn, labelColumn, listener);
            return this;
        }

        @Override
        public Builder setSingleChoiceItems(@ArrayRes int itemsId, int checkedItem, final DialogInterface
                .OnClickListener listener) {
            builder.setSingleChoiceItems(itemsId, checkedItem, listener);
            return this;
        }

        @Override
        public Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn, final
        DialogInterface.OnClickListener listener) {
            builder.setSingleChoiceItems(cursor, checkedItem, labelColumn, listener);
            return this;
        }

        @Override
        public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final DialogInterface
                .OnClickListener listener) {
            builder.setSingleChoiceItems(items, checkedItem, listener);
            return this;
        }

        @Override
        public Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, final DialogInterface
                .OnClickListener listener) {
            builder.setSingleChoiceItems(adapter, checkedItem, listener);
            return this;
        }

        @Override
        public Builder setOnItemSelectedListener(final AdapterView.OnItemSelectedListener listener) {
            builder.setOnItemSelectedListener(listener);
            return this;
        }

        @Override
        public Builder setView(int layoutResId) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                builder.setView(layoutResId);
            }
            return this;
        }

        @Override
        public Builder setView(View view) {
            builder.setView(view);
            return this;
        }

        @Override
        public AlertDialog create() {
            return new Api21Dialog(builder.create());
        }

        @Override
        public AlertDialog show() {
            final AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }

    }

    private static class Api20Builder implements Builder {

        private android.support.v7.app.AlertDialog.Builder builder;

        private Api20Builder(@NonNull Context context) {
            this(context, 0);
        }

        private Api20Builder(@NonNull Context context, @StyleRes int themeResId) {
            builder = new android.support.v7.app.AlertDialog.Builder(context, themeResId);
        }

        @NonNull
        public Context getContext() {
            return builder.getContext();
        }

        public Builder setTitle(@StringRes int titleId) {
            builder.setTitle(titleId);
            return this;
        }

        public Builder setTitle(CharSequence title) {
            builder.setTitle(title);
            return this;
        }

        public Builder setCustomTitle(View customTitleView) {
            builder.setCustomTitle(customTitleView);
            return this;
        }

        public Builder setMessage(@StringRes int messageId) {
            builder.setMessage(messageId);
            return this;
        }

        public Builder setMessage(CharSequence message) {
            builder.setMessage(message);
            return this;
        }

        public Builder setIcon(@DrawableRes int iconId) {
            builder.setIcon(iconId);
            return this;
        }

        public Builder setIcon(Drawable icon) {
            builder.setIcon(icon);
            return this;
        }

        public Builder setIconAttribute(@AttrRes int attrId) {
            builder.setIconAttribute(attrId);
            return this;
        }

        public Builder setPositiveButton(@StringRes int textId, final DialogInterface.OnClickListener
                listener) {
            builder.setPositiveButton(textId, listener);
            return this;
        }

        public Builder setPositiveButton(CharSequence text, final DialogInterface.OnClickListener listener) {
            builder.setPositiveButton(text, listener);
            return this;
        }

        public Builder setNegativeButton(@StringRes int textId, final DialogInterface.OnClickListener
                listener) {
            builder.setNegativeButton(textId, listener);
            return this;
        }

        public Builder setNegativeButton(CharSequence text, final DialogInterface.OnClickListener listener) {
            builder.setNegativeButton(text, listener);
            return this;
        }

        public Builder setNeutralButton(@StringRes int textId, final DialogInterface.OnClickListener
                listener) {
            builder.setNeutralButton(textId, listener);
            return this;
        }

        public Builder setNeutralButton(CharSequence text, final DialogInterface.OnClickListener listener) {
            builder.setNeutralButton(text, listener);
            return this;
        }

        public Builder setCancelable(boolean cancelable) {
            builder.setCancelable(cancelable);
            return this;
        }

        public Builder setOnCancelListener(DialogInterface.OnCancelListener onCancelListener) {
            builder.setOnCancelListener(onCancelListener);
            return this;
        }

        public Builder setOnDismissListener(DialogInterface.OnDismissListener onDismissListener) {
            builder.setOnDismissListener(onDismissListener);
            return this;
        }

        public Builder setOnKeyListener(DialogInterface.OnKeyListener onKeyListener) {
            builder.setOnKeyListener(onKeyListener);
            return this;
        }

        public Builder setItems(@ArrayRes int itemsId, final DialogInterface.OnClickListener listener) {
            builder.setItems(itemsId, listener);
            return this;
        }

        public Builder setItems(CharSequence[] items, final DialogInterface.OnClickListener listener) {
            builder.setItems(items, listener);
            return this;
        }

        public Builder setAdapter(final ListAdapter adapter, final DialogInterface.OnClickListener listener) {
            builder.setAdapter(adapter, listener);
            return this;
        }

        public Builder setCursor(final Cursor cursor, final DialogInterface.OnClickListener listener,
                                 String labelColumn) {
            builder.setCursor(cursor, listener, labelColumn);
            return this;
        }

        public Builder setMultiChoiceItems(@ArrayRes int itemsId, boolean[] checkedItems, final
        DialogInterface.OnMultiChoiceClickListener listener) {
            builder.setMultiChoiceItems(itemsId, checkedItems, listener);
            return this;
        }

        public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, final
        DialogInterface.OnMultiChoiceClickListener listener) {
            builder.setMultiChoiceItems(items, checkedItems, listener);
            return this;
        }

        public Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn, final
        DialogInterface.OnMultiChoiceClickListener listener) {
            builder.setMultiChoiceItems(cursor, isCheckedColumn, labelColumn, listener);
            return this;
        }

        public Builder setSingleChoiceItems(@ArrayRes int itemsId, int checkedItem, final DialogInterface
                .OnClickListener listener) {
            builder.setSingleChoiceItems(itemsId, checkedItem, listener);
            return this;
        }

        public Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn, final
        DialogInterface.OnClickListener listener) {
            builder.setSingleChoiceItems(cursor, checkedItem, labelColumn, listener);
            return this;
        }

        public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final DialogInterface
                .OnClickListener listener) {
            builder.setSingleChoiceItems(items, checkedItem, listener);
            return this;
        }

        public Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, final DialogInterface
                .OnClickListener listener) {
            builder.setSingleChoiceItems(adapter, checkedItem, listener);
            return this;
        }

        public Builder setOnItemSelectedListener(final AdapterView.OnItemSelectedListener listener) {
            builder.setOnItemSelectedListener(listener);
            return this;
        }

        public Builder setView(int layoutResId) {
            builder.setView(layoutResId);
            return this;
        }

        public Builder setView(View view) {
            builder.setView(view);
            return this;
        }

        public AlertDialog create() {
            return new Api20Dialog(builder.create());
        }

        public AlertDialog show() {
            final AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }
    }
}


/*
 * Copyright 2016 Yan Zhenjie
 *
 * 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 com.yanzhenjie.alertdialog.sample;

import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.yanzhenjie.alertdialog.AlertDialog;

/**
 * Created by Yan Zhenjie on 2016/12/28.
 */
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        findViewById(R.id.btn_show_common).setOnClickListener(mOnClickListener);
        findViewById(R.id.btn_show_special).setOnClickListener(mOnClickListener);
    }

    // 这里注意包别导错了。
    // X 不是：android.support.v7.app.AlertDialog
    // X 不是：android.app.AlertDialog
    // √ 是：com.yanzhenjie.alertdialog.AlertDialog

    /**
     * 显示正常用法的按钮。
     */
    private void onCommonClick() {
        AlertDialog.build(this)
                .setTitle("标题")
                .setMessage("提示信息")
                .setNeutralButton("忽略", (dialog, which) -> {
                    Toast.makeText(MainActivity.this, "点击了忽略", Toast.LENGTH_SHORT).show();
                })
                .setNegativeButton("取消", (dialog, which) -> {
                    Toast.makeText(MainActivity.this, "点击了取消", Toast.LENGTH_SHORT).show();
                })
                .setPositiveButton("好的", (dialog, which) -> {
                    Toast.makeText(MainActivity.this, "点击了确定", Toast.LENGTH_SHORT).show();
                })
                .show();

        //  Button被点击后，不调用dialog.dismiss()，因为AlertDialog会自动调用。
    }

    /**
     * 特殊用法被点击。
     */
    private void onSpecialClick() {
        AlertDialog alertDialog = AlertDialog.build(this)
                .setTitle("标题")
                .setMessage("提示信息")
                // Listener 写null，Button被点击时，Alertdialog就不会自动dismiss了。
                .setNeutralButton("忽略", null)
                .setNegativeButton("取消", null)
                .setPositiveButton("好的", null)
                .show(); // 这里是直接show()，不是create()，否则不能getButton()。

        // 获取中性按钮。
        Button btnNeutral = alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL);
        btnNeutral.setOnClickListener(v -> {
            Toast.makeText(MainActivity.this, "我们拦截了忽略点击关闭dialog操作",
                    Toast.LENGTH_SHORT).show();
        });
        // 获取取消按钮。
        Button btnNegative = alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE);
        btnNegative.setOnClickListener(v -> {
            alertDialog.dismiss(); // 拦截了btn的默认操作，需要调用dialog.dismiss()。
            Toast.makeText(MainActivity.this, "点击了取消",
                    Toast.LENGTH_SHORT).show();
        });
        // 获取确定按钮。
        Button btnPositive = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
        btnPositive.setOnClickListener(v -> {
            alertDialog.dismiss(); // 拦截了btn的默认操作，需要调用dialog.dismiss()。
            Toast.makeText(MainActivity.this, "点击了确定",
                    Toast.LENGTH_SHORT).show();
        });
    }

    /**
     * Show dialog btn click.
     */
    private View.OnClickListener mOnClickListener = v -> {
        int id = v.getId();
        switch (id) {
            case R.id.btn_show_common: {
                onCommonClick();
                break;
            }
            case R.id.btn_show_special: {
                onSpecialClick();
                break;
            }
        }
    };
}