package ucar.nc2.ui.table;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import java.awt.*;

/**
 * A listener that sets the alignment of cells in a {@link JTable}. Expected usage:
 *

 * JTable table = new JTable(...);
 * TableAligner aligner = new TableAligner(table, SwingConstants.XXXX);
 * table.getColumnModel().addColumnModelListener(aligner);
 *

 */
public class TableAligner implements TableColumnModelListener {
    private final JTable table;
    private final int alignment;

    /**
     * Creates a listener that installs alignment decorators on new columns added to {@code table}.
     * Also performs the initial alignment of {@code table}.
     *
     * @param table  a table.
     * @param alignment  one of the following constants:
     *

     *
{@link SwingConstants#LEFT}

     *
{@link SwingConstants#CENTER} (the default for image-only labels)

     *
{@link SwingConstants#RIGHT}

     *
{@link SwingConstants#LEADING} (the default for text-only labels)

     *
{@link SwingConstants#TRAILING}

     *

     */
    public TableAligner(JTable table, int alignment) {
        this.table = table;
        this.alignment = alignment;
        installInAllColumns(table, alignment);  // Perform initial installation.
    }

    ////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////

    /**
     * Installs alignment decorators in all of the table's columns.
     *
     * @param table  a table.
     * @param alignment  one of the following constants:
     *

     *
{@link SwingConstants#LEFT}

     *
{@link SwingConstants#CENTER} (the default for image-only labels)

     *
{@link SwingConstants#RIGHT}

     *
{@link SwingConstants#LEADING} (the default for text-only labels)

     *
{@link SwingConstants#TRAILING}

     *

     */
    public static void installInAllColumns(JTable table, int alignment) {
        // We don't want to set up completely new cell renderers: rather, we want to use the existing ones but just
        // change their alignment.
        for (int colViewIndex = 0; colViewIndex < table.getColumnCount(); ++colViewIndex) {
            installInOneColumn(table, colViewIndex, alignment);
        }
    }

    /**
     * Installs alignment decorators in the table column at {@code colViewIndex}.
     *
     * @param table  a table.
     * @param colViewIndex  the index of the column in the table view.
     * @param alignment  one of the following constants:
     *

     *
{@link SwingConstants#LEFT}

     *
{@link SwingConstants#CENTER} (the default for image-only labels)

     *
{@link SwingConstants#RIGHT}

     *
{@link SwingConstants#LEADING} (the default for text-only labels)

     *
{@link SwingConstants#TRAILING}

     *

     */
    public static void installInOneColumn(JTable table, int colViewIndex, int alignment) {
        TableColumn tableColumn = table.getColumnModel().getColumn(colViewIndex);

        TableCellRenderer headerRenderer = tableColumn.getHeaderRenderer();
        if (headerRenderer == null) {
            headerRenderer = table.getTableHeader().getDefaultRenderer();
        }
        if (!(headerRenderer instanceof  RendererAlignmentDecorator)) {  // Don't install a redundant decorator.
            tableColumn.setHeaderRenderer(new RendererAlignmentDecorator(headerRenderer, alignment));
        }

        TableCellRenderer cellRenderer = tableColumn.getCellRenderer();
        if (cellRenderer == null) {
            cellRenderer = table.getDefaultRenderer(table.getColumnClass(colViewIndex));
        }
        if (!(cellRenderer instanceof RendererAlignmentDecorator)) {  // Don't install a redundant decorator.
            tableColumn.setCellRenderer(new RendererAlignmentDecorator(cellRenderer, alignment));
        }
    }

    // We don't want to set up completely new cell renderers.
    // Rather, we just want to decorate the existing ones with alignment behavior.
    private static class RendererAlignmentDecorator implements TableCellRenderer {
        private final TableCellRenderer delegate;
        private final int alignment;

        private RendererAlignmentDecorator(TableCellRenderer delegate, int alignment) {
            this.delegate = delegate;
            this.alignment = alignment;
        }

        @Override public Component getTableCellRendererComponent(
                JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            Component cellRendererComp =
                    delegate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);

            if (cellRendererComp instanceof DefaultTableCellRenderer) {
                ((DefaultTableCellRenderer) cellRendererComp).setHorizontalAlignment(alignment);
            }

            return cellRendererComp;
        }
    }

    ///////////////////////////////////////////// TableColumnModelListener /////////////////////////////////////////////

    @Override public void columnAdded(TableColumnModelEvent e) {
        installInOneColumn(table, e.getToIndex(), alignment);  // Only install in added column.
    }

    @Override public void columnRemoved(TableColumnModelEvent e) { }

    @Override public void columnMoved(TableColumnModelEvent e) { }

    @Override public void columnMarginChanged(ChangeEvent e) { }

    @Override public void columnSelectionChanged(ListSelectionEvent e) { }
}


package thredds.crawlabledataset.s3;

import java.io.File;

import com.google.common.base.Preconditions;

/**
 * An identifier for objects stored in Amazon S3. Ultimately, the identifier is composed solely of a bucket name and
 * a key, and an object of this class can be constructed with those two items. However, this class also supports URIs
 * rendered in a path-like form: {@code s3://<bucket>/<key>}.
 * <p>
 * Instances of this class are immutable.
 *
 * @author cwardgar
 * @since 2015/08/24
 */
public class S3URI {
    public static final String S3_PREFIX = "s3://";
    public static final String S3_DELIMITER = "/";
    public static final File S3ObjectTempDir = new File(System.getProperty("java.io.tmpdir"), "S3Objects");

    private final String bucket, key;

    /**
     * Creates a S3URI by extracting the S3 bucket and key names from {@code uri}. If the key has a trailing
     * {@link #S3_DELIMITER delimiter}, it will be removed.
     *
     * @param uri  an S3 URI in the form {@code s3://<bucket>/<key>}.
     * @throws IllegalArgumentException  if {@code uri} is not in the expected form or if the bucket or key are invalid.
     * @see #S3URI(String, String)
     */
    public S3URI(String uri) {
        if (uri.startsWith(S3_PREFIX)) {
            uri = uri.substring(S3_PREFIX.length(), uri.length());

            int delimPos = uri.indexOf(S3_DELIMITER);
            if (delimPos == -1) {  // Handle case where uri includes bucket but no key, e.g. "s3://bucket".
                this.bucket = checkBucket(uri);
                this.key = checkKey(null);
            } else {
                this.bucket = checkBucket(uri.substring(0, delimPos));
                this.key = checkKey(uri.substring(delimPos + 1, uri.length()));
            }
        } else {
            throw new IllegalArgumentException(String.format(
                    "S3 URI '%s' does not start with the expected prefix '%s'.", uri, S3_PREFIX));
        }
    }

    /**
     * Creates a S3URI from the specified bucket and key. If the key has a trailing
     * {@link #S3_DELIMITER delimiter}, it will be removed.
     *
     * @param bucket  a bucket name. Must be non-{@code null} and at least 3 characters.
     * @param key  a key. May be {@code null} but cannot be the empty string. Also, it may not contain consecutive
     *             delimiters.
     * @throws IllegalArgumentException  if either argument fails the requirements.
     */
    public S3URI(String bucket, String key) throws IllegalArgumentException {
        this.bucket = checkBucket(bucket);
        this.key = checkKey(key);
    }

    private static String checkBucket(String bucket) throws IllegalArgumentException {
        Preconditions.checkNotNull(bucket, "Bucket must be non-null.");
        if (bucket.length() < 3) {
            throw new IllegalArgumentException(String.format(
                    "Bucket name '%s' must be at least 3 characters.", bucket));
        }
        return bucket;
    }

    private static String checkKey(String key) throws IllegalArgumentException {
        if (key == null) {
            return null;
        } else if (key.equals("")) {
            throw new IllegalArgumentException("Key may not be the empty string.");
        } else {
            if (key.contains(S3_DELIMITER + S3_DELIMITER)) {  // Key contains consecutive delimiters.
                throw new IllegalArgumentException(String.format("Key '%s' contains consecutive delimiters.", key));
            }

            if (key.endsWith(S3_DELIMITER)) {
                return key.substring(0, key.length() - 1);  // Remove trailing delimiter.
            } else {
                return key;
            }
        }
    }

    /**
     * Returns the bucket.
     *
     * @return  the bucket.
     */
    public String getBucket() {
        return bucket;
    }

    /**
     * Returns the key. May be {@code null} if the URI did not include a key, e.g. "s3://bucket". If not null,
     * any trailing {@code #S3_DELIMITER delimiter} the key had when passed to the constructor will have been stripped.
     *
     * @return  the key.
     */
    public String getKey() {
        return key;
    }

    /**
     * Returns the key, adding a trailing {@link #S3_DELIMITER delimiter}.
     * Returns {@code null} if the key is {@code null}.
     *
     * @return  the key, with a trailing delimiter.
     */
    public String getKeyWithTrailingDelimiter() {
        if (key == null) {
            return null;
        } else {
            assert !key.endsWith(S3_DELIMITER) : "Didn't we strip this in the ctor?";
            return key + S3_DELIMITER;
        }
    }

    /**
     * Returns the base name of the file or directory denoted by this URI. This is just the last name in the
     * key's name sequence. If the key is {@code null}, {@code null} is returned.
     *
     * @return  the base name.
     */
    public String getBaseName() {
        if (key == null) {
            return null;
        } else {
            return new File(key).getName();
        }
    }

    /**
     * Returns the parent URI of this URI. The determination is completely text-based, using the
     * {@link #S3_DELIMITER delimiter}. If the key is {@code null}, {@code null} is returned. If it is non-{@code null},
     * but doesn't have a logical parent, the returned URI will have a {@code null} key (but the same bucket).
     * For example, the parent of {@code s3://my-bucket/my-key} (bucket="my-bucket", key="my-key") will be
     * {@code s3://my-bucket} (bucket=="my-bucket", key=null).
     *
     * @return the parent URI of this URI.
     */
    public S3URI getParent() {
        if (key == null) {
            return null;
        }

        int lastDelimPos = key.lastIndexOf(S3_DELIMITER);
        if (lastDelimPos == -1) {
            return new S3URI(bucket, null);
        } else {
            return new S3URI(bucket, key.substring(0, lastDelimPos));
        }
    }

    /**
     * Creates a new URI by resolving the specified path relative to {@code this}. If {@code key == null}, the key
     * of the returned URI will simply be {@code relativePath}.
     *
     * @param relativePath  a path relative to {@code this}. Must be non-null.
     * @return  the child URI.
     * @throws IllegalArgumentException  if the path starts with a {@link #S3_DELIMITER delimiter}.
     *      The path must be relative.
     */
    public S3URI getChild(String relativePath) throws IllegalArgumentException {
        Preconditions.checkNotNull(relativePath, "relativePath must be non-null.");

        if (relativePath.isEmpty()) {
            return this;
        } else if (relativePath.startsWith(S3_DELIMITER)) {
            throw new IllegalArgumentException(String.format(
                    "Path '%s' should be relative but begins with the delimiter string '%s'.",
                    relativePath, S3_DELIMITER));
        }

        if (key == null) {
            return new S3URI(bucket, relativePath);
        } else {
            return new S3URI(bucket, key + S3_DELIMITER + relativePath);
        }
    }

    /**
     * Gets a temporary file to which the content of the S3Object that this URI points to can be downloaded.
     * The path of the file is {@code ${java.io.tmpdir}/S3Objects/${hashCode()}/${getBaseName()}}.
     * This method does not cause the file to be created; we're just returning a suitable path.
     *
     * @return a temporary file to which the content of the S3Object that this URI points to can be downloaded.
     */
    public File getTempFile() {
        // To avoid collisions of files with the same name, create a parent dir named after the S3URI's hashCode().
        File parentDir = new File(S3ObjectTempDir, String.valueOf(hashCode()));
        return new File(parentDir, getBaseName());
    }

    //////////////////////////////////////// Object ////////////////////////////////////////

    /**
     * Returns a string representation of the URI in the form {@code s3://<bucket>/<key>}.
     *
     * @return  a string representation of the URI.
     */
    @Override
    public String toString() {
        StringBuilder strBuilder = new StringBuilder();
        strBuilder.append(S3_PREFIX);
        strBuilder.append(bucket);

        if (key != null) {
            strBuilder.append(S3_DELIMITER);
            strBuilder.append(key);
        }

        return strBuilder.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        S3URI other = (S3URI) o;
        return this.bucket.equals(other.bucket) &&
               this.key == null ? other.key == null : this.key.equals(other.key);
    }

    @Override
    public int hashCode() {
        int result = bucket.hashCode();
        result = 31 * result + (key != null ? key.hashCode() : 0);
        return result;
    }
}


package com.coinbase.api;

import java.net.URL;

import javax.net.ssl.SSLContext;

public class CoinbaseBuilder {

    String access_token;
    String api_key;
    String api_secret;
    String acct_id;
    SSLContext ssl_context;
    URL base_oauth_url;
    URL base_api_url;
    CallbackVerifier callback_verifier;

    /**
     * Build a new Coinbase client object with the specified options
     *
     * @return a new Coinbase client object
     */
    public Coinbase build() {
        return new CoinbaseImpl(this);
    }

    /**
     * Specify an access token to be used for authenticated requests
     *
     * Coinbase client objects built using an access token are thread-safe
     *
     * @param access_token the OAuth access token
     *
     * @return this CoinbaseBuilder object
     */
    public CoinbaseBuilder withAccessToken(String access_token) {
    	this.access_token = access_token;
    	return this;
    }

    /**
     * Specify the HMAC api key and secret to be used for authenticated requests
     *
     * Having more than one client with the same api/secret globally is unsupported
     * and will result in sporadic auth errors as the nonce is calculated from the system time.
     *
     * @param api_key the HMAC API Key
     * @param api_secret the HMAC API Secret
     *
     * @return this CoinbaseBuilder object
     */
    public CoinbaseBuilder withApiKey(String api_key, String api_secret) {
    	this.api_key = api_key;
    	this.api_secret = api_secret;
    	return this;
    }

    /**
     * Specify the account id to be used for account-specific requests
     *
     * @param acct_id the account id
     *
     * @return this CoinbaseBuilder object
     */
    public CoinbaseBuilder withAccountId(String acct_id) {
    	this.acct_id = acct_id;
    	return this;
    }

    /**
     * Specify the verifier used to verify merchant callbacks
     *
     * @param callback_verifier
     *
     * @return this CoinbaseBuilder object
     */
    public CoinbaseBuilder withCallbackVerifier(CallbackVerifier callback_verifier) {
        this.callback_verifier = callback_verifier;
        return this;
    }

    /**
     * Specify the ssl context to be used when creating SSL sockets
     *
     * @param ssl_context the SSLContext to be used
     *
     * @return this CoinbaseBuilder object
     */
    public CoinbaseBuilder withSSLContext(SSLContext ssl_context) {
        this.ssl_context = ssl_context;
        return this;
    }

    /**
     * Specify the base URL to be used for API requests
     *
     * By default, this is 'https://coinbase.com/api/v1/'
     *
     * @param base_api_url the base URL to use for API requests. Must return an instance of javax.net.ssl.HttpsURLConnection on openConnection.
     *
     * @return this CoinbaseBuilder object
     */
    public CoinbaseBuilder withBaseApiURL(URL base_api_url) {
        this.base_api_url = base_api_url;
        return this;
    }

    /**
     * Specify the base URL to be used for OAuth requests
     *
     * By default, this is 'https://coinbase.com/oauth/'
     *
     * @param base_oauth_url the base URL to use for OAuth requests. Must return an instance of javax.net.ssl.HttpsURLConnection on openConnection.
     *
     * @return this CoinbaseBuilder object
     */
    public CoinbaseBuilder withBaseOAuthURL(URL base_oauth_url) {
        this.base_oauth_url = base_oauth_url;
        return this;
    }

}


/*
 * Copyright © 2015 Stefan Niederhauser (nidin@gmx.ch)
 *
 * 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 guru.nidi.graphviz.service;

import org.apache.commons.exec.CommandLine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CommandRunner {
    private static final Logger LOG = LoggerFactory.getLogger(CommandRunner.class);

    private final Function<CommandLine, CommandLine> wrapperFunc;
    private final DefaultExecutor cmdExec;

    CommandRunner(Function<CommandLine, CommandLine> wrapperFunc, DefaultExecutor cmdExec) {
        this.wrapperFunc = wrapperFunc;
        this.cmdExec = cmdExec;
    }

    void exec(CommandLine cmd, @Nullable File workDir) throws IOException, InterruptedException {
        final CommandLine wrappedCmd = wrapperFunc.apply(cmd);
        cmdExec.execute(wrappedCmd, workDir);
    }

    public void exec(String cmd, @Nullable File workDir, String... args) throws IOException, InterruptedException {
        exec(cmd, workDir, args, true);
    }

    private void exec(String cmd, @Nullable File workDir, String[] args, boolean quote)
            throws IOException, InterruptedException {
        exec(new CommandLine(cmd).addArguments(args, quote), workDir);
    }

    private void exec(String cmd, @Nullable File workDir, List<String> args) throws IOException, InterruptedException {
        exec(cmd, workDir, args.toArray(new String[args.size()]));
    }

    void exec(String cmd, List<String> args) throws IOException, InterruptedException {
        exec(cmd, null, args);
    }

    void exec(String cmd) throws IOException, InterruptedException {
        exec(cmd, null, new String[0]);
    }

    // Cross-platform way of finding an executable in the $PATH.
    static Stream<Path> which(String program) {
        return which(program, Optional.ofNullable(System.getenv("PATH")).orElse(""));
    }

    private static Stream<Path> which(@Nullable String optProgram, @Nullable String pathEnvVar) {
        if (optProgram == null || "".equals(optProgram.trim()) || pathEnvVar == null || "".equals(pathEnvVar)) {
            return Stream.empty();
        }
        final String program = optProgram; //help code analysis
        return Arrays
                .stream(pathEnvVar.split(File.pathSeparator))
                .map(SystemUtils::pathOf)
                .filter(path -> Files.exists(path))
                .map(path -> {
                    try (Stream<Path> entries = Files.list(path)) {
                        return entries
                                // Filter on the filename
                                // Doing a case-sensitive compare here, that's not correct on windows ?
                                .filter(filePath -> program.equals(filePath.getFileName().toString()))

                                // Filter out folders
                                .filter(filePath -> Files.isRegularFile(filePath))

                                // Check if the file is executable
                                // Does this check work on Windows this way ?
                                .filter(Files::isExecutable)

                                // Consume the stream here - we're inside a try-with-resources
                                .collect(Collectors.toList())
                                .stream();
                    } catch (IOException e) {
                        LOG.error("Problem finding path for {}", program, e);
                        return Stream.<Path>empty();
                    }
                })
                .flatMap(stream -> stream);
    }

    static boolean isExecutableFound(String program) {
        return which(program).anyMatch(path -> true);
    }

    public static boolean isExecutableFound(String program, String envPath) {
        return which(program, envPath).anyMatch(path -> true);
    }

}


/*
 * Copyright © 2015 Stefan Niederhauser (nidin@gmx.ch)
 *
 * 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 guru.nidi.graphviz.attribute;

import javax.annotation.Nullable;
import java.util.Objects;
import java.util.stream.Stream;

import static guru.nidi.graphviz.attribute.Label.Justification.*;
import static guru.nidi.graphviz.attribute.Label.Location.BOTTOM;
import static guru.nidi.graphviz.attribute.Label.Location.TOP;
import static java.util.stream.Collectors.joining;

public final class Label extends SimpleLabel implements Attributes<ForAll> {
    public enum Justification {
        LEFT, MIDDLE, RIGHT
    }

    public enum Location {
        TOP, CENTER, BOTTOM
    }

    private final boolean external;
    private final boolean floating;
    private final boolean decorated;
    @Nullable
    private final Justification just;
    @Nullable
    private final Location loc;

    private Label(String value, boolean html, boolean external, boolean floating, boolean decorated,
                  @Nullable Justification just, @Nullable Location loc) {
        super(value, html);
        this.external = external;
        this.floating = floating;
        this.decorated = decorated;
        this.just = just;
        this.loc = loc;
    }

    /**
     * Create a simple label. Create newlines with \n.
     *
     * @param value the label text
     * @return the Label
     */
    public static Label of(String value) {
        return new Label(value, false, false, false, false, null, null);
    }

    /**
     * Create a simple multiline label.
     *
     * @param lines the text lines
     * @return the Label
     */
    public static Label lines(String... lines) {
        return lines(Justification.MIDDLE, lines);
    }

    /**
     * Create a simple multiline label.
     *
     * @param just  the text justification
     * @param lines the text lines
     * @return the Label
     */
    public static Label lines(Justification just, String... lines) {
        final String sep = just == LEFT ? "\\l" : just == RIGHT ? "\\r" : "\n";
        final String value = Stream.of(lines).map(line -> line + sep).collect(joining());
        return new Label(value, false, false, false, false, null, null);
    }

    /**
     * Create a HTML label.
     *
     * @param value the HTML code
     * @return the Label
     * @see www.graphviz.org
     */
    public static Label html(String value) {
        return new Label(value, true, false, false, false, null, null);
    }

    /**
     * Create a multiline HTML label.
     *
     * @param lines the lines in HTML format
     * @return the Label
     * @see www.graphviz.org
     */
    public static Label htmlLines(String... lines) {
        return htmlLines(MIDDLE, lines);
    }

    /**
     * Create a multiline HTML label.
     *
     * @param just  the test justification
     * @param lines the lines in HTML format
     * @return the Label
     * @see www.graphviz.org
     */
    public static Label htmlLines(Justification just, String... lines) {
        final String sep = just == LEFT ? "
" : just == RIGHT ? "
" : "
";
        final String value = Stream.of(lines).map(line -> line + sep).collect(joining());
        return new Label(value, true, false, false, false, null, null);
    }

    /**
     * Create a HTML label from markdown. The following patterns are allowed:

     * \n newline, **bold**, *italics*, ~~strike through~~, _underlined_, ^overlined^, __subscript__, ^^superscript^^.
     *
     * @param value the markdown code
     * @return the Label
     */
    public static Label markdown(String value) {
        return html(replaceMd(replaceMd(replaceMd(replaceMd(replaceMd(replaceMd(replaceMd(value.replace("\n", "
"),
                "\\*\\*", "b"),
                "\\*", "i"),
                "~~", "s"),
                "__", "sub"),
                "_", "u"),
                "\\^\\^", "sup"),
                "\\^", "o")
                .replaceAll("\\\\([*~_^])", "$1"));
    }

    private static String replaceMd(String s, String from, String to) {
        return s.replaceAll("([^\\\\])?" + from + "(.*?[^\\\\])" + from, "$1<" + to + ">$2</" + to + ">");
    }

    /**
     * Create either a simple, HTML or markdown label.
     * If the value is not surrounded by < and >, a simple Label is created.
     * Otherwise if value contains some HTML tags, a HTML label is created.
     * Otherwise a markdown label is created.
     *
     * @param value the raw label
     * @return the Label
     */
    public static Label raw(String value) {
        final boolean isTagged = value.startsWith("<") && value.endsWith(">");
        if (!isTagged) {
            return of(value);
        }
        final String untagged = value.substring(1, value.length() - 1);
        final boolean hasTags = value.contains("/>") || value.contains("</");
        return hasTags ? html(untagged) : markdown(untagged);
    }

    public EndLabel head() {
        return EndLabel.head(this, null, null);
    }

    public EndLabel head(double angle, double distance) {
        return EndLabel.head(this, angle, distance);
    }

    public EndLabel tail() {
        return EndLabel.tail(this, null, null);
    }

    public EndLabel tail(double angle, double distance) {
        return EndLabel.tail(this, angle, distance);
    }

    public Label external() {
        return new Label(value, html, true, floating, decorated, just, loc);
    }

    public Label floating() {
        return new Label(value, html, external, true, decorated, just, loc);
    }

    public Label decorated() {
        return new Label(value, html, external, floating, true, just, loc);
    }

    public Label justify(Justification just) {
        return new Label(value, html, external, floating, decorated, just, loc);
    }

    public Label locate(Location loc) {
        return new Label(value, html, external, floating, decorated, just, loc);
    }

    public boolean isExternal() {
        return external;
    }

    @Override
    public Attributes<? super ForAll> applyTo(MapAttributes<? super ForAll> attributes) {
        attributes.add(external ? "xlabel" : "label", this);
        if (floating) {
            attributes.add("labelfloat", true);
        }
        if (decorated) {
            attributes.add("decorate", true);
        }
        if (just == LEFT) {
            attributes.add("labeljust", "l");
        }
        if (just == RIGHT) {
            attributes.add("labeljust", "r");
        }
        if (loc == TOP) {
            attributes.add("labelloc", "t");
        }
        if (loc == BOTTOM) {
            attributes.add("labelloc", "b");
        }
        return attributes;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        if (!super.equals(o)) {
            return false;
        }
        final Label label = (Label) o;
        return external == label.external
                && floating == label.floating
                && decorated == label.decorated
                && just == label.just
                && loc == label.loc;
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), external, floating, decorated, just, loc);
    }

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


/*
 * Copyright 2018 Google 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.google.cloud.sql.mysql;

import com.google.cloud.sql.core.CoreSocketFactory;
import com.mysql.cj.conf.PropertySet;
import com.mysql.cj.protocol.ServerSession;
import com.mysql.cj.protocol.SocketConnection;
import java.io.Closeable;
import java.io.IOException;
import java.util.Properties;

/**
 * A MySQL {@link SocketFactory} that establishes a secure connection to a Cloud SQL instance using
 * ephemeral certificates.
 *
 * <p>The heavy lifting is done by the singleton {@link CoreSocketFactory} class.
 */
public class SocketFactory implements com.mysql.cj.protocol.SocketFactory {

  @Override
  public <T extends Closeable> T connect(
      String host, int portNumber, PropertySet props, int loginTimeout) throws IOException {
    return connect(host, portNumber, props.exposeAsProperties(), loginTimeout);
  }

  /**
   * Implements the interface for com.mysql.cj.protocol.SocketFactory for mysql-connector-java prior
   * to version 8.0.13. This change is required for backwards compatibility.
   */
  public <T extends Closeable> T connect(
      String host, int portNumber, Properties props, int loginTimeout) throws IOException {
    @SuppressWarnings("unchecked")
    T socket =
        (T)
            CoreSocketFactory.getInstance()
                .connect(props, CoreSocketFactory.MYSQL_SOCKET_FILE_FORMAT);
    return socket;
  }

  // Cloud SQL sockets always use TLS and the socket returned by connect above is already TLS-ready.
  // It is fine to implement these as no-ops.
  @Override
  public void beforeHandshake() {}

  @Override
  public <T extends Closeable> T performTlsHandshake(
      SocketConnection socketConnection, ServerSession serverSession) throws IOException {
    @SuppressWarnings("unchecked")
    T socket = (T) socketConnection.getMysqlSocket();
    return socket;
  }

  @Override
  public void afterHandshake() {}

}


//
// A Structured Logger for Fluent
//
// Copyright (C) 2011 - 2013 OZAWA Tsuyoshi
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
//
package org.fluentd.logger;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.WeakHashMap;

import org.fluentd.logger.sender.ExponentialDelayReconnector;
import org.fluentd.logger.sender.RawSocketSender;
import org.fluentd.logger.sender.Reconnector;
import org.fluentd.logger.sender.Sender;

public class FluentLoggerFactory {

    private final Map<FluentLogger, String> loggers;

    public FluentLoggerFactory() {
        loggers = new WeakHashMap<FluentLogger, String>();
    }

    public FluentLogger getLogger(String tagPrefix) {
        return getLogger(tagPrefix, "localhost", 24224);
    }

    public FluentLogger getLogger(String tagPrefix, String host, int port) {
        return getLogger(tagPrefix, host, port, 3 * 1000, 1 * 1024 * 1024, new ExponentialDelayReconnector());
    }

    public FluentLogger getLogger(String tagPrefix, String host, int port, int timeout, int bufferCapacity) {
        return getLogger(tagPrefix, host, port, timeout, bufferCapacity, new ExponentialDelayReconnector());
    }

    public synchronized FluentLogger getLogger(String tagPrefix, String host, int port, int timeout, int bufferCapacity,
            Reconnector reconnector) {
        String key = String.format("%s_%s_%d_%d_%d", new Object[] { tagPrefix, host, port, timeout, bufferCapacity });

        for (Map.Entry<FluentLogger, String> entry : loggers.entrySet()) {
            if (entry.getValue().equals(key)) {
                FluentLogger found = entry.getKey();
                if(found != null) {
                    return found;
                }
                break;
            }
        }

        Sender sender = null;
        Properties props = System.getProperties();
        if (!props.containsKey(Config.FLUENT_SENDER_CLASS)) {
            // create default sender object
            sender = new RawSocketSender(host, port, timeout, bufferCapacity, reconnector);
        } else {
            String senderClassName = props.getProperty(Config.FLUENT_SENDER_CLASS);
            try {
                sender = createSenderInstance(senderClassName, new Object[] { host, port, timeout, bufferCapacity });
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        FluentLogger logger = new FluentLogger(tagPrefix, sender);
        loggers.put(logger, key);
        return logger;
    }

    /** Purges an invalid logger from the cache.
     */
	protected synchronized void purgeLogger(FluentLogger logger) {
		Iterator<Entry<FluentLogger, String>> it = loggers.entrySet().iterator();
		while (it.hasNext()) {
			if (it.next().getKey() == logger) {
				it.remove();
				return;
			}
		}
	}

    @SuppressWarnings("unchecked")
    private Sender createSenderInstance(final String className, final Object[] params) throws ClassNotFoundException,
            SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException,
            IllegalAccessException, InvocationTargetException {
        Class<Sender> cl = (Class<Sender>) FluentLogger.class.getClassLoader().loadClass(className);
        Constructor<Sender> cons = cl.getDeclaredConstructor(new Class[] { String.class, int.class, int.class,
                int.class });
        return (Sender) cons.newInstance(params);
    }

    /**
     * the method is for testing
     */
    Map<FluentLogger, String> getLoggers() {
        return loggers;
    }

    public synchronized void closeAll() {
        for (FluentLogger logger : new ArrayList<FluentLogger>(loggers.keySet())) {
            logger.close();
        }
        loggers.clear();
    }

    public synchronized void flushAll() {
        for (FluentLogger logger : loggers.keySet()) {
            logger.flush();
        }
    }

}


package org.mdkt.compiler;

import java.util.*;

import javax.tools.*;

/**
 * Compile Java sources in-memory
 */
public class InMemoryJavaCompiler {
	private JavaCompiler javac;
	private DynamicClassLoader classLoader;
	private Iterable<String> options;
	boolean ignoreWarnings = false;

	private Map<String, SourceCode> sourceCodes = new HashMap<String, SourceCode>();

	public static InMemoryJavaCompiler newInstance() {
		return new InMemoryJavaCompiler();
	}

	private InMemoryJavaCompiler() {
		this.javac = ToolProvider.getSystemJavaCompiler();
		this.classLoader = new DynamicClassLoader(ClassLoader.getSystemClassLoader());
	}

	public InMemoryJavaCompiler useParentClassLoader(ClassLoader parent) {
		this.classLoader = new DynamicClassLoader(parent);
		return this;
	}

	/**
	 * @return the class loader used internally by the compiler
	 */
	public ClassLoader getClassloader() {
		return classLoader;
	}

	/**
	 * Options used by the compiler, e.g. '-Xlint:unchecked'.
	 *
	 * @param options
	 * @return
	 */
	public InMemoryJavaCompiler useOptions(String... options) {
		this.options = Arrays.asList(options);
		return this;
	}

	/**
	 * Ignore non-critical compiler output, like unchecked/unsafe operation
	 * warnings.
	 *
	 * @return
	 */
	public InMemoryJavaCompiler ignoreWarnings() {
		ignoreWarnings = true;
		return this;
	}

	/**
	 * Compile all sources
	 *
	 * @return Map containing instances of all compiled classes
	 * @throws Exception
	 */
	public Map<String, Class<?>> compileAll() throws Exception {
		if (sourceCodes.size() == 0) {
			throw new CompilationException("No source code to compile");
		}
		Collection<SourceCode> compilationUnits = sourceCodes.values();
		CompiledCode[] code;

		code = new CompiledCode[compilationUnits.size()];
		Iterator<SourceCode> iter = compilationUnits.iterator();
		for (int i = 0; i < code.length; i++) {
			code[i] = new CompiledCode(iter.next().getClassName());
		}
		DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
		ExtendedStandardJavaFileManager fileManager = new ExtendedStandardJavaFileManager(javac.getStandardFileManager(null, null, null), classLoader);
		JavaCompiler.CompilationTask task = javac.getTask(null, fileManager, collector, options, null, compilationUnits);
		boolean result = task.call();
		if (!result || collector.getDiagnostics().size() > 0) {
			StringBuffer exceptionMsg = new StringBuffer();
			exceptionMsg.append("Unable to compile the source");
			boolean hasWarnings = false;
			boolean hasErrors = false;
			for (Diagnostic<? extends JavaFileObject> d : collector.getDiagnostics()) {
				switch (d.getKind()) {
				case NOTE:
				case MANDATORY_WARNING:
				case WARNING:
					hasWarnings = true;
					break;
				case OTHER:
				case ERROR:
				default:
					hasErrors = true;
					break;
				}
				exceptionMsg.append("\n").append("[kind=").append(d.getKind());
				exceptionMsg.append(", ").append("line=").append(d.getLineNumber());
				exceptionMsg.append(", ").append("message=").append(d.getMessage(Locale.US)).append("]");
			}
			if (hasWarnings && !ignoreWarnings || hasErrors) {
				throw new CompilationException(exceptionMsg.toString());
			}
		}

		Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
		for (String className : sourceCodes.keySet()) {
			classes.put(className, classLoader.loadClass(className));
		}
		return classes;
	}

	/**
	 * Compile single source
	 *
	 * @param className
	 * @param sourceCode
	 * @return
	 * @throws Exception
	 */
	public Class<?> compile(String className, String sourceCode) throws Exception {
		return addSource(className, sourceCode).compileAll().get(className);
	}

	/**
	 * Add source code to the compiler
	 *
	 * @param className
	 * @param sourceCode
	 * @return
	 * @throws Exception
	 * @see {@link #compileAll()}
	 */
	public InMemoryJavaCompiler addSource(String className, String sourceCode) throws Exception {
		sourceCodes.put(className, new SourceCode(className, sourceCode));
		return this;
	}
}


/**
 * Copyright 2014 Google, Inc. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.felipecsl.gifimageview.library;

import android.util.Log;

import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

/**
 * A class responsible for creating {@link GifHeader}s from data
 * representing animated gifs.
 */
public class GifHeaderParser {
  public static final String TAG = "GifHeaderParser";

  // The minimum frame delay in hundredths of a second.
  static final int MIN_FRAME_DELAY = 2;
  // The default frame delay in hundredths of a second for GIFs with frame delays less than the
  // minimum.
  static final int DEFAULT_FRAME_DELAY = 10;

  private static final int MAX_BLOCK_SIZE = 256;
  // Raw data read working array.
  private final byte[] block = new byte[MAX_BLOCK_SIZE];

  private ByteBuffer rawData;
  private GifHeader header;
  private int blockSize = 0;

  public GifHeaderParser setData(ByteBuffer data) {
    reset();
    rawData = data.asReadOnlyBuffer();
    rawData.position(0);
    rawData.order(ByteOrder.LITTLE_ENDIAN);
    return this;
  }

  public GifHeaderParser setData(byte[] data) {
    if (data != null) {
      setData(ByteBuffer.wrap(data));
    } else {
      rawData = null;
      header.status = GifDecoder.STATUS_OPEN_ERROR;
    }
    return this;
  }

  public void clear() {
    rawData = null;
    header = null;
  }

  private void reset() {
    rawData = null;
    Arrays.fill(block, (byte) 0);
    header = new GifHeader();
    blockSize = 0;
  }

  public GifHeader parseHeader() {
    if (rawData == null) {
      throw new IllegalStateException("You must call setData() before parseHeader()");
    }
    if (err()) {
      return header;
    }

    readHeader();
    if (!err()) {
      readContents();
      if (header.frameCount < 0) {
        header.status = GifDecoder.STATUS_FORMAT_ERROR;
      }
    }

    return header;
  }

  /**
   * Determines if the GIF is animated by trying to read in the first 2 frames
   * This method reparses the data even if the header has already been read.
   */
  public boolean isAnimated() {
    readHeader();
    if (!err()) {
      readContents(2 /* maxFrames */);
    }
    return header.frameCount > 1;
  }

  /**
   * Main file parser. Reads GIF content blocks.
   */
  private void readContents() {
    readContents(Integer.MAX_VALUE /* maxFrames */);
  }

  /**
   * Main file parser. Reads GIF content blocks. Stops after reading maxFrames
   */
  private void readContents(int maxFrames) {
    // Read GIF file content blocks.
    boolean done = false;
    while (!(done || err() || header.frameCount > maxFrames)) {
      int code = read();
      switch (code) {
        // Image separator.
        case 0x2C:
          // The graphics control extension is optional, but will always come first if it exists.
          // If one did
          // exist, there will be a non-null current frame which we should use. However if one
          // did not exist,
          // the current frame will be null and we must create it here. See issue #134.
          if (header.currentFrame == null) {
            header.currentFrame = new GifFrame();
          }
          readBitmap();
          break;
        // Extension.
        case 0x21:
          code = read();
          switch (code) {
            // Graphics control extension.
            case 0xf9:
              // Start a new frame.
              header.currentFrame = new GifFrame();
              readGraphicControlExt();
              break;
            // Application extension.
            case 0xff:
              readBlock();
              String app = "";
              for (int i = 0; i < 11; i++) {
                app += (char) block[i];
              }
              if (app.equals("NETSCAPE2.0")) {
                readNetscapeExt();
              } else {
                // Don't care.
                skip();
              }
              break;
            // Comment extension.
            case 0xfe:
              skip();
              break;
            // Plain text extension.
            case 0x01:
              skip();
              break;
            // Uninteresting extension.
            default:
              skip();
          }
          break;
        // Terminator.
        case 0x3b:
          done = true;
          break;
        // Bad byte, but keep going and see what happens break;
        case 0x00:
        default:
          header.status = GifDecoder.STATUS_FORMAT_ERROR;
      }
    }
  }

  /**
   * Reads Graphics Control Extension values.
   */
  private void readGraphicControlExt() {
    // Block size.
    read();
    // Packed fields.
    int packed = read();
    // Disposal method.
    header.currentFrame.dispose = (packed & 0x1c) >> 2;
    if (header.currentFrame.dispose == 0) {
      // Elect to keep old image if discretionary.
      header.currentFrame.dispose = 1;
    }
    header.currentFrame.transparency = (packed & 1) != 0;
    // Delay in milliseconds.
    int delayInHundredthsOfASecond = readShort();
    // TODO: consider allowing -1 to indicate show forever.
    if (delayInHundredthsOfASecond < MIN_FRAME_DELAY) {
      delayInHundredthsOfASecond = DEFAULT_FRAME_DELAY;
    }
    header.currentFrame.delay = delayInHundredthsOfASecond * 10;
    // Transparent color index
    header.currentFrame.transIndex = read();
    // Block terminator
    read();
  }

  /**
   * Reads next frame image.
   */
  private void readBitmap() {
    // (sub)image position & size.
    header.currentFrame.ix = readShort();
    header.currentFrame.iy = readShort();
    header.currentFrame.iw = readShort();
    header.currentFrame.ih = readShort();

    int packed = read();
    // 1 - local color table flag interlace
    boolean lctFlag = (packed & 0x80) != 0;
    int lctSize = (int) Math.pow(2, (packed & 0x07) + 1);
    // 3 - sort flag
    // 4-5 - reserved lctSize = 2 << (packed & 7); // 6-8 - local color
    // table size
    header.currentFrame.interlace = (packed & 0x40) != 0;
    if (lctFlag) {
      // Read table.
      header.currentFrame.lct = readColorTable(lctSize);
    } else {
      // No local color table.
      header.currentFrame.lct = null;
    }

    // Save this as the decoding position pointer.
    header.currentFrame.bufferFrameStart = rawData.position();

    // False decode pixel data to advance buffer.
    skipImageData();

    if (err()) {
      return;
    }

    header.frameCount++;
    // Add image to frame.
    header.frames.add(header.currentFrame);
  }

  /**
   * Reads Netscape extension to obtain iteration count.
   */
  private void readNetscapeExt() {
    do {
      readBlock();
      if (block[0] == 1) {
        // Loop count sub-block.
        int b1 = ((int) block[1]) & 0xff;
        int b2 = ((int) block[2]) & 0xff;
        header.loopCount = (b2 << 8) | b1;
        if(header.loopCount == 0) {
          header.loopCount = GifDecoder.LOOP_FOREVER;
        }
      }
    } while ((blockSize > 0) && !err());
  }

  /**
   * Reads GIF file header information.
   */
  private void readHeader() {
    String id = "";
    for (int i = 0; i < 6; i++) {
      id += (char) read();
    }
    if (!id.startsWith("GIF")) {
      header.status = GifDecoder.STATUS_FORMAT_ERROR;
      return;
    }
    readLSD();
    if (header.gctFlag && !err()) {
      header.gct = readColorTable(header.gctSize);
      header.bgColor = header.gct[header.bgIndex];
    }
  }

  /**
   * Reads Logical Screen Descriptor.
   */
  private void readLSD() {
    // Logical screen size.
    header.width = readShort();
    header.height = readShort();
    // Packed fields
    int packed = read();
    // 1 : global color table flag.
    header.gctFlag = (packed & 0x80) != 0;
    // 2-4 : color resolution.
    // 5 : gct sort flag.
    // 6-8 : gct size.
    header.gctSize = 2 << (packed & 7);
    // Background color index.
    header.bgIndex = read();
    // Pixel aspect ratio
    header.pixelAspect = read();
  }

  /**
   * Reads color table as 256 RGB integer values.
   *
   * @param ncolors int number of colors to read.
   * @return int array containing 256 colors (packed ARGB with full alpha).
   */
  private int[] readColorTable(int ncolors) {
    int nbytes = 3 * ncolors;
    int[] tab = null;
    byte[] c = new byte[nbytes];

    try {
      rawData.get(c);

      // TODO: what bounds checks are we avoiding if we know the number of colors?
      // Max size to avoid bounds checks.
      tab = new int[MAX_BLOCK_SIZE];
      int i = 0;
      int j = 0;
      while (i < ncolors) {
        int r = ((int) c[j++]) & 0xff;
        int g = ((int) c[j++]) & 0xff;
        int b = ((int) c[j++]) & 0xff;
        tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
      }
    } catch (BufferUnderflowException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Format Error Reading Color Table", e);
      }
      header.status = GifDecoder.STATUS_FORMAT_ERROR;
    }

    return tab;
  }

  /**
   * Skips LZW image data for a single frame to advance buffer.
   */
  private void skipImageData() {
    // lzwMinCodeSize
    read();
    // data sub-blocks
    skip();
  }

  /**
   * Skips variable length blocks up to and including next zero length block.
   */
  private void skip() {
    try {
      int blockSize;
      do {
        blockSize = read();
        rawData.position(rawData.position() + blockSize);
      } while (blockSize > 0);
    } catch (IllegalArgumentException ex) {
    }
  }

  /**
   * Reads next variable length block from input.
   *
   * @return number of bytes stored in "buffer"
   */
  private int readBlock() {
    blockSize = read();
    int n = 0;
    if (blockSize > 0) {
      int count = 0;
      try {
        while (n < blockSize) {
          count = blockSize - n;
          rawData.get(block, n, count);

          n += count;
        }
      } catch (Exception e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG,
              "Error Reading Block n: " + n + " count: " + count + " blockSize: " + blockSize, e);
        }
        header.status = GifDecoder.STATUS_FORMAT_ERROR;
      }
    }
    return n;
  }

  /**
   * Reads a single byte from the input stream.
   */
  private int read() {
    int curByte = 0;
    try {
      curByte = rawData.get() & 0xFF;
    } catch (Exception e) {
      header.status = GifDecoder.STATUS_FORMAT_ERROR;
    }
    return curByte;
  }

  /**
   * Reads next 16-bit value, LSB first.
   */
  private int readShort() {
    // Read 16-bit value.
    return rawData.getShort();
  }

  private boolean err() {
    return header.status != GifDecoder.STATUS_OK;
  }
}


/**
 * Copyright (c) 2013 Xcellent Creations, Inc.
 * Copyright 2014 Google, Inc. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.felipecsl.gifimageview.library;

import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

/**
 * Reads frame data from a GIF image source and decodes it into individual frames
 * for animation purposes. Image data can be read from either and InputStream source
 * or a byte[].
 *
 * This class is optimized for running animations with the frames, there
 * are no methods to get individual frame images, only to decode the next frame in the
 * animation sequence. Instead, it lowers its memory footprint by only housing the minimum
 * data necessary to decode the next frame in the animation sequence.
 *
 * The animation must be manually moved forward using {@link #advance()} before requesting the next
 * frame. This method must also be called before you request the first frame or an error will
 * occur.
 *
 * Implementation adapted from sample code published in Lyons. (2004). Java for Programmers,
 * republished under the MIT Open Source License
 */
class GifDecoder {
  private static final String TAG = GifDecoder.class.getSimpleName();

  /**
   * File read status: No errors.
   */
  static final int STATUS_OK = 0;
  /**
   * File read status: Error decoding file (may be partially decoded).
   */
  static final int STATUS_FORMAT_ERROR = 1;
  /**
   * File read status: Unable to open source.
   */
  static final int STATUS_OPEN_ERROR = 2;
  /**
   * Unable to fully decode the current frame.
   */
  static final int STATUS_PARTIAL_DECODE = 3;
  /**
   * max decoder pixel stack size.
   */
  private static final int MAX_STACK_SIZE = 4096;

  /**
   * GIF Disposal Method meaning take no action.
   */
  private static final int DISPOSAL_UNSPECIFIED = 0;
  /**
   * GIF Disposal Method meaning leave canvas from previous frame.
   */
  private static final int DISPOSAL_NONE = 1;
  /**
   * GIF Disposal Method meaning clear canvas to background color.
   */
  private static final int DISPOSAL_BACKGROUND = 2;
  /**
   * GIF Disposal Method meaning clear canvas to frame before last.
   */
  private static final int DISPOSAL_PREVIOUS = 3;

  private static final int NULL_CODE = -1;

  private static final int INITIAL_FRAME_POINTER = -1;

  static final int LOOP_FOREVER = -1;

  private static final int BYTES_PER_INTEGER = 4;

  // Global File Header values and parsing flags.
  // Active color table.
  private int[] act;
  // Private color table that can be modified if needed.
  private final int[] pct = new int[256];

  // Raw GIF data from input source.
  private ByteBuffer rawData;

  // Raw data read working array.
  private byte[] block;

  // Temporary buffer for block reading. Reads 16k chunks from the native buffer for processing,
  // to greatly reduce JNI overhead.
  private static final int WORK_BUFFER_SIZE = 16384;
  @Nullable private byte[] workBuffer;
  private int workBufferSize = 0;
  private int workBufferPosition = 0;

  private GifHeaderParser parser;

  // LZW decoder working arrays.
  private short[] prefix;
  private byte[] suffix;
  private byte[] pixelStack;
  private byte[] mainPixels;
  private int[] mainScratch;

  private int framePointer;
  private int loopIndex;
  private GifHeader header;
  private BitmapProvider bitmapProvider;
  private Bitmap previousImage;
  private boolean savePrevious;
  private int status;
  private int sampleSize;
  private int downsampledHeight;
  private int downsampledWidth;
  private boolean isFirstFrameTransparent;

  /**
   * An interface that can be used to provide reused {@link android.graphics.Bitmap}s to avoid GCs
   * from constantly allocating {@link android.graphics.Bitmap}s for every frame.
   */
  interface BitmapProvider {
    /**
     * Returns an {@link Bitmap} with exactly the given dimensions and config.
     *
     * @param width  The width in pixels of the desired {@link android.graphics.Bitmap}.
     * @param height The height in pixels of the desired {@link android.graphics.Bitmap}.
     * @param config The {@link android.graphics.Bitmap.Config} of the desired {@link
     *               android.graphics.Bitmap}.
     */
    @NonNull
    Bitmap obtain(int width, int height, Bitmap.Config config);

    /**
     * Releases the given Bitmap back to the pool.
     */
    void release(Bitmap bitmap);

    /**
     * Returns a byte array used for decoding and generating the frame bitmap.
     *
     * @param size the size of the byte array to obtain
     */
    byte[] obtainByteArray(int size);

    /**
     * Releases the given byte array back to the pool.
     */
    void release(byte[] bytes);

    /**
     * Returns an int array used for decoding/generating the frame bitmaps.
     * @param size
     */
    int[] obtainIntArray(int size);

    /**
     * Release the given array back to the pool.
     * @param array
     */
    void release(int[] array);
  }

  GifDecoder(BitmapProvider provider, GifHeader gifHeader, ByteBuffer rawData) {
    this(provider, gifHeader, rawData, 1 /*sampleSize*/);
  }

  GifDecoder(BitmapProvider provider, GifHeader gifHeader, ByteBuffer rawData,
      int sampleSize) {
    this(provider);
    setData(gifHeader, rawData, sampleSize);
  }

  GifDecoder(BitmapProvider provider) {
    this.bitmapProvider = provider;
    header = new GifHeader();
  }

  GifDecoder() {
    this(new SimpleBitmapProvider());
  }

  int getWidth() {
    return header.width;
  }

  int getHeight() {
    return header.height;
  }

  ByteBuffer getData() {
    return rawData;
  }

  /**
   * Returns the current status of the decoder.
   *
   *
 Status will update per frame to allow the caller to tell whether or not the current frame
   * was decoded successfully and/or completely. Format and open failures persist across frames.
   *

   */
  int getStatus() {
    return status;
  }

  /**
   * Move the animation frame counter forward.
   *
   * @return boolean specifying if animation should continue or if loopCount has been fulfilled.
   */
  boolean advance() {
    if (header.frameCount <= 0) {
      return false;
    }

    if(framePointer == getFrameCount() - 1) {
      loopIndex++;
    }

    if(header.loopCount != LOOP_FOREVER && loopIndex > header.loopCount) {
      return false;
    }

    framePointer = (framePointer + 1) % header.frameCount;
    return true;
  }

  /**
   * Gets display duration for specified frame.
   *
   * @param n int index of frame.
   * @return delay in milliseconds.
   */
  int getDelay(int n) {
    int delay = -1;
    if ((n >= 0) && (n < header.frameCount)) {
      delay = header.frames.get(n).delay;
    }
    return delay;
  }

  /**
   * Gets display duration for the upcoming frame in ms.
   */
  int getNextDelay() {
    if (header.frameCount <= 0 || framePointer < 0) {
      return 0;
    }

    return getDelay(framePointer);
  }

  /**
   * Gets the number of frames read from file.
   *
   * @return frame count.
   */
  int getFrameCount() {
    return header.frameCount;
  }

  /**
   * Gets the current index of the animation frame, or -1 if animation hasn't not yet started.
   *
   * @return frame index.
   */
  int getCurrentFrameIndex() {
    return framePointer;
  }

  /**
   * Sets the frame pointer to a specific frame
   *
   * @return boolean true if the move was successful
   */
  boolean setFrameIndex(int frame) {
    if(frame < INITIAL_FRAME_POINTER || frame >= getFrameCount()) {
      return false;
    }
    framePointer = frame;
    return true;
  }

  /**
   * Resets the frame pointer to before the 0th frame, as if we'd never used this decoder to
   * decode any frames.
   */
  void resetFrameIndex() {
    framePointer = INITIAL_FRAME_POINTER;
  }

  /**
   * Resets the loop index to the first loop.
   */
  void resetLoopIndex() { loopIndex = 0; }

  /**
   * Gets the "Netscape" iteration count, if any. A count of 0 means repeat indefinitely.
   *
   * @return iteration count if one was specified, else 1.
   */
  int getLoopCount() { return header.loopCount; }

  /**
   * Gets the number of loops that have been shown.
   *
   * @return iteration count.
   */
  int getLoopIndex() {
    return loopIndex;
  }

  /**
   * Returns an estimated byte size for this decoder based on the data provided to {@link
   * #setData(GifHeader, byte[])}, as well as internal buffers.
   */
  int getByteSize() {
    return rawData.limit() + mainPixels.length + (mainScratch.length * BYTES_PER_INTEGER);
  }

  /**
   * Get the next frame in the animation sequence.
   *
   * @return Bitmap representation of frame.
   */
  synchronized Bitmap getNextFrame() {
    if (header.frameCount <= 0 || framePointer < 0) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "unable to decode frame, frameCount=" + header.frameCount + " framePointer="
            + framePointer);
      }
      status = STATUS_FORMAT_ERROR;
    }
    if (status == STATUS_FORMAT_ERROR || status == STATUS_OPEN_ERROR) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Unable to decode frame, status=" + status);
      }
      return null;
    }
    status = STATUS_OK;

    GifFrame currentFrame = header.frames.get(framePointer);
    GifFrame previousFrame = null;
    int previousIndex = framePointer - 1;
    if (previousIndex >= 0) {
      previousFrame = header.frames.get(previousIndex);
    }

    // Set the appropriate color table.
    act = currentFrame.lct != null ? currentFrame.lct : header.gct;
    if (act == null) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "No Valid Color Table for frame #" + framePointer);
      }
      // No color table defined.
      status = STATUS_FORMAT_ERROR;
      return null;
    }

    // Reset the transparent pixel in the color table
    if (currentFrame.transparency) {
      // Prepare local copy of color table ("pct = act"), see #1068
      System.arraycopy(act, 0, pct, 0, act.length);
      // Forget about act reference from shared header object, use copied version
      act = pct;
      // Set transparent color if specified.
      act[currentFrame.transIndex] = 0;
    }

    // Transfer pixel data to image.
    return setPixels(currentFrame, previousFrame);
  }

  /**
   * Reads GIF image from stream.
   *
   * @param is containing GIF file.
   * @return read status code (0 = no errors).
   */
  int read(InputStream is, int contentLength) {
    if (is != null) {
      try {
        int capacity = (contentLength > 0) ? (contentLength + 4096) : 16384;
        ByteArrayOutputStream buffer = new ByteArrayOutputStream(capacity);
        int nRead;
        byte[] data = new byte[16384];
        while ((nRead = is.read(data, 0, data.length)) != -1) {
          buffer.write(data, 0, nRead);
        }
        buffer.flush();

        read(buffer.toByteArray());
      } catch (IOException e) {
        Log.w(TAG, "Error reading data from stream", e);
      }
    } else {
      status = STATUS_OPEN_ERROR;
    }

    try {
      if (is != null) {
        is.close();
      }
    } catch (IOException e) {
      Log.w(TAG, "Error closing stream", e);
    }

    return status;
  }

  void clear() {
    header = null;
    if (mainPixels != null) {
      bitmapProvider.release(mainPixels);
    }
    if (mainScratch != null) {
      bitmapProvider.release(mainScratch);
    }
    if (previousImage != null) {
      bitmapProvider.release(previousImage);
    }
    previousImage = null;
    rawData = null;
    isFirstFrameTransparent = false;
    if (block != null) {
      bitmapProvider.release(block);
    }
    if (workBuffer != null) {
      bitmapProvider.release(workBuffer);
    }
  }

  synchronized void setData(GifHeader header, byte[] data) {
    setData(header, ByteBuffer.wrap(data));
  }

  synchronized void setData(GifHeader header, ByteBuffer buffer) {
    setData(header, buffer, 1);
  }

  synchronized void setData(GifHeader header, ByteBuffer buffer, int sampleSize) {
    if (sampleSize <= 0) {
      throw new IllegalArgumentException("Sample size must be >=0, not: " + sampleSize);
    }
    // Make sure sample size is a power of 2.
    sampleSize = Integer.highestOneBit(sampleSize);
    this.status = STATUS_OK;
    this.header = header;
    isFirstFrameTransparent = false;
    framePointer = INITIAL_FRAME_POINTER;
    resetLoopIndex();
    // Initialize the raw data buffer.
    rawData = buffer.asReadOnlyBuffer();
    rawData.position(0);
    rawData.order(ByteOrder.LITTLE_ENDIAN);

    // No point in specially saving an old frame if we're never going to use it.
    savePrevious = false;
    for (GifFrame frame : header.frames) {
      if (frame.dispose == DISPOSAL_PREVIOUS) {
        savePrevious = true;
        break;
      }
    }

    this.sampleSize = sampleSize;
    downsampledWidth = header.width / sampleSize;
    downsampledHeight = header.height / sampleSize;
    // Now that we know the size, init scratch arrays.
    // TODO Find a way to avoid this entirely or at least downsample it (either should be possible).
    mainPixels = bitmapProvider.obtainByteArray(header.width * header.height);
    mainScratch = bitmapProvider.obtainIntArray(downsampledWidth * downsampledHeight);
  }

  private GifHeaderParser getHeaderParser() {
    if (parser == null) {
      parser = new GifHeaderParser();
    }
    return parser;
  }

  /**
   * Reads GIF image from byte array.
   *
   * @param data containing GIF file.
   * @return read status code (0 = no errors).
   */
  synchronized int read(byte[] data) {
    this.header = getHeaderParser().setData(data).parseHeader();
    if (data != null) {
      setData(header, data);
    }

    return status;
  }

  /**
   * Creates new frame image from current data (and previous frames as specified by their
   * disposition codes).
   */
  private Bitmap setPixels(GifFrame currentFrame, GifFrame previousFrame) {
    // Final location of blended pixels.
    final int[] dest = mainScratch;

    // clear all pixels when meet first frame
    if (previousFrame == null) {
      Arrays.fill(dest, 0);
    }

    // fill in starting image contents based on last image's dispose code
    if (previousFrame != null && previousFrame.dispose > DISPOSAL_UNSPECIFIED) {
      // We don't need to do anything for DISPOSAL_NONE, if it has the correct pixels so will our
      // mainScratch and therefore so will our dest array.
      if (previousFrame.dispose == DISPOSAL_BACKGROUND) {
        // Start with a canvas filled with the background color
        int c = 0;
        if (!currentFrame.transparency) {
          c = header.bgColor;
          if (currentFrame.lct != null && header.bgIndex == currentFrame.transIndex) {
            c = 0;
          }
        } else if (framePointer == 0) {
          // TODO: We should check and see if all individual pixels are replaced. If they are, the
          // first frame isn't actually transparent. For now, it's simpler and safer to assume
          // drawing a transparent background means the GIF contains transparency.
          isFirstFrameTransparent = true;
        }
        fillRect(dest, previousFrame, c);
      } else if (previousFrame.dispose == DISPOSAL_PREVIOUS) {
        if (previousImage == null) {
          fillRect(dest, previousFrame, 0);
        } else {
          // Start with the previous frame
          int downsampledIH = previousFrame.ih / sampleSize;
          int downsampledIY = previousFrame.iy / sampleSize;
          int downsampledIW = previousFrame.iw / sampleSize;
          int downsampledIX = previousFrame.ix / sampleSize;
          int topLeft = downsampledIY * downsampledWidth + downsampledIX;
          previousImage.getPixels(dest, topLeft, downsampledWidth,
              downsampledIX, downsampledIY, downsampledIW, downsampledIH);
        }
      }
    }

    // Decode pixels for this frame into the global pixels[] scratch.
    decodeBitmapData(currentFrame);

    int downsampledIH = currentFrame.ih / sampleSize;
    int downsampledIY = currentFrame.iy / sampleSize;
    int downsampledIW = currentFrame.iw / sampleSize;
    int downsampledIX = currentFrame.ix / sampleSize;
    // Copy each source line to the appropriate place in the destination.
    int pass = 1;
    int inc = 8;
    int iline = 0;
    boolean isFirstFrame = framePointer == 0;
    for (int i = 0; i < downsampledIH; i++) {
      int line = i;
      if (currentFrame.interlace) {
        if (iline >= downsampledIH) {
          pass++;
          switch (pass) {
            case 2:
              iline = 4;
              break;
            case 3:
              iline = 2;
              inc = 4;
              break;
            case 4:
              iline = 1;
              inc = 2;
              break;
            default:
              break;
          }
        }
        line = iline;
        iline += inc;
      }
      line += downsampledIY;
      if (line < downsampledHeight) {
        int k = line * downsampledWidth;
        // Start of line in dest.
        int dx = k + downsampledIX;
        // End of dest line.
        int dlim = dx + downsampledIW;
        if (k + downsampledWidth < dlim) {
          // Past dest edge.
          dlim = k + downsampledWidth;
        }
        // Start of line in source.
        int sx = i * sampleSize * currentFrame.iw;
        int maxPositionInSource = sx + ((dlim - dx) * sampleSize);
        while (dx < dlim) {
          // Map color and insert in destination.
          int averageColor;
          if (sampleSize == 1) {
            int currentColorIndex = ((int) mainPixels[sx]) & 0x000000ff;
            averageColor = act[currentColorIndex];
          } else {
            // TODO: This is substantially slower (up to 50ms per frame) than just grabbing the
            // current color index above, even with a sample size of 1.
            averageColor = averageColorsNear(sx, maxPositionInSource, currentFrame.iw);
          }
          if (averageColor != 0) {
            dest[dx] = averageColor;
          } else if (!isFirstFrameTransparent && isFirstFrame) {
            isFirstFrameTransparent = true;
          }
          sx += sampleSize;
          dx++;
        }
      }
    }

    // Copy pixels into previous image
    if (savePrevious && (currentFrame.dispose == DISPOSAL_UNSPECIFIED
        || currentFrame.dispose == DISPOSAL_NONE)) {
      if (previousImage == null) {
        previousImage = getNextBitmap();
      }
      previousImage.setPixels(dest, 0, downsampledWidth, 0, 0, downsampledWidth,
          downsampledHeight);
    }

    // Set pixels for current image.
    Bitmap result = getNextBitmap();
    result.setPixels(dest, 0, downsampledWidth, 0, 0, downsampledWidth, downsampledHeight);
    return result;
  }

  private void fillRect(int[] dest, GifFrame frame, int bgColor) {
    // The area used by the graphic must be restored to the background color.
    int downsampledIH = frame.ih / sampleSize;
    int downsampledIY = frame.iy / sampleSize;
    int downsampledIW = frame.iw / sampleSize;
    int downsampledIX = frame.ix / sampleSize;
    int topLeft = downsampledIY * downsampledWidth + downsampledIX;
    int bottomLeft = topLeft + downsampledIH * downsampledWidth;
    for (int left = topLeft; left < bottomLeft; left += downsampledWidth) {
      int right = left + downsampledIW;
      for (int pointer = left; pointer < right; pointer++) {
        dest[pointer] = bgColor;
      }
    }
  }

  private int averageColorsNear(int positionInMainPixels, int maxPositionInMainPixels,
      int currentFrameIw) {
    int alphaSum = 0;
    int redSum = 0;
    int greenSum = 0;
    int blueSum = 0;

    int totalAdded = 0;
    // Find the pixels in the current row.
    for (int i = positionInMainPixels;
        i < positionInMainPixels + sampleSize && i < mainPixels.length
            && i < maxPositionInMainPixels; i++) {
      int currentColorIndex = ((int) mainPixels[i]) & 0xff;
      int currentColor = act[currentColorIndex];
      if (currentColor != 0) {
        alphaSum += currentColor >> 24 & 0x000000ff;
        redSum += currentColor >> 16 & 0x000000ff;
        greenSum += currentColor >> 8 & 0x000000ff;
        blueSum += currentColor & 0x000000ff;
        totalAdded++;
      }
    }
    // Find the pixels in the next row.
    for (int i = positionInMainPixels + currentFrameIw;
        i < positionInMainPixels + currentFrameIw + sampleSize && i < mainPixels.length
            && i < maxPositionInMainPixels; i++) {
      int currentColorIndex = ((int) mainPixels[i]) & 0xff;
      int currentColor = act[currentColorIndex];
      if (currentColor != 0) {
        alphaSum += currentColor >> 24 & 0x000000ff;
        redSum += currentColor >> 16 & 0x000000ff;
        greenSum += currentColor >> 8 & 0x000000ff;
        blueSum += currentColor & 0x000000ff;
        totalAdded++;
      }
    }
    if (totalAdded == 0) {
      return 0;
    } else {
      return ((alphaSum / totalAdded) << 24)
          | ((redSum / totalAdded) << 16)
          | ((greenSum / totalAdded) << 8)
          | (blueSum / totalAdded);
    }
  }

  /**
   * Decodes LZW image data into pixel array. Adapted from John Cristy's BitmapMagick.
   */
  private void decodeBitmapData(GifFrame frame) {
    workBufferSize = 0;
    workBufferPosition = 0;
    if (frame != null) {
      // Jump to the frame start position.
      rawData.position(frame.bufferFrameStart);
    }

    int npix = (frame == null) ? header.width * header.height : frame.iw * frame.ih;
    int available, clear, codeMask, codeSize, endOfInformation, inCode, oldCode, bits, code, count,
        i, datum,
        dataSize, first, top, bi, pi;

    if (mainPixels == null || mainPixels.length < npix) {
      // Allocate new pixel array.
      mainPixels = bitmapProvider.obtainByteArray(npix);
    }
    if (prefix == null) {
      prefix = new short[MAX_STACK_SIZE];
    }
    if (suffix == null) {
      suffix = new byte[MAX_STACK_SIZE];
    }
    if (pixelStack == null) {
      pixelStack = new byte[MAX_STACK_SIZE + 1];
    }

    // Initialize GIF data stream decoder.
    dataSize = readByte();
    clear = 1 << dataSize;
    endOfInformation = clear + 1;
    available = clear + 2;
    oldCode = NULL_CODE;
    codeSize = dataSize + 1;
    codeMask = (1 << codeSize) - 1;
    for (code = 0; code < clear; code++) {
      // XXX ArrayIndexOutOfBoundsException.
      prefix[code] = 0;
      suffix[code] = (byte) code;
    }

    // Decode GIF pixel stream.
    datum = bits = count = first = top = pi = bi = 0;
    for (i = 0; i < npix; ) {
      // Load bytes until there are enough bits for a code.
      if (count == 0) {
        // Read a new data block.
        count = readBlock();
        if (count <= 0) {
          status = STATUS_PARTIAL_DECODE;
          break;
        }
        bi = 0;
      }

      datum += (((int) block[bi]) & 0xff) << bits;
      bits += 8;
      bi++;
      count--;

      while (bits >= codeSize) {
        // Get the next code.
        code = datum & codeMask;
        datum >>= codeSize;
        bits -= codeSize;

        // Interpret the code.
        if (code == clear) {
          // Reset decoder.
          codeSize = dataSize + 1;
          codeMask = (1 << codeSize) - 1;
          available = clear + 2;
          oldCode = NULL_CODE;
          continue;
        }

        if (code > available) {
          status = STATUS_PARTIAL_DECODE;
          break;
        }

        if (code == endOfInformation) {
          break;
        }

        if (oldCode == NULL_CODE) {
          pixelStack[top++] = suffix[code];
          oldCode = code;
          first = code;
          continue;
        }
        inCode = code;
        if (code >= available) {
          pixelStack[top++] = (byte) first;
          code = oldCode;
        }
        while (code >= clear) {
          pixelStack[top++] = suffix[code];
          code = prefix[code];
        }
        first = ((int) suffix[code]) & 0xff;
        pixelStack[top++] = (byte) first;

        // Add a new string to the string table.
        if (available < MAX_STACK_SIZE) {
          prefix[available] = (short) oldCode;
          suffix[available] = (byte) first;
          available++;
          if (((available & codeMask) == 0) && (available < MAX_STACK_SIZE)) {
            codeSize++;
            codeMask += available;
          }
        }
        oldCode = inCode;

        while (top > 0) {
          // Pop a pixel off the pixel stack.
          mainPixels[pi++] = pixelStack[--top];
          i++;
        }
      }
    }

    // Clear missing pixels.
    for (i = pi; i < npix; i++) {
      mainPixels[i] = 0;
    }
  }

  /**
   * Reads the next chunk for the intermediate work buffer.
   */
  private void readChunkIfNeeded() {
    if (workBufferSize > workBufferPosition) {
      return;
    }
    if (workBuffer == null) {
      workBuffer = bitmapProvider.obtainByteArray(WORK_BUFFER_SIZE);
    }
    workBufferPosition = 0;
    workBufferSize = Math.min(rawData.remaining(), WORK_BUFFER_SIZE);
    rawData.get(workBuffer, 0, workBufferSize);
  }

  /**
   * Reads a single byte from the input stream.
   */
  private int readByte() {
    try {
      readChunkIfNeeded();
      return workBuffer[workBufferPosition++] & 0xFF;
    } catch (Exception e) {
      status = STATUS_FORMAT_ERROR;
      return 0;
    }
  }

  /**
   * Reads next variable length block from input.
   *
   * @return number of bytes stored in "buffer".
   */
  private int readBlock() {
    int blockSize = readByte();
    if (blockSize > 0) {
      try {
        if (block == null) {
          block = bitmapProvider.obtainByteArray(255);
        }
        final int remaining = workBufferSize - workBufferPosition;
        if (remaining >= blockSize) {
          // Block can be read from the current work buffer.
          System.arraycopy(workBuffer, workBufferPosition, block, 0, blockSize);
          workBufferPosition += blockSize;
        } else if (rawData.remaining() + remaining >= blockSize) {
          // Block can be read in two passes.
          System.arraycopy(workBuffer, workBufferPosition, block, 0, remaining);
          workBufferPosition = workBufferSize;
          readChunkIfNeeded();
          final int secondHalfRemaining = blockSize - remaining;
          System.arraycopy(workBuffer, 0, block, remaining, secondHalfRemaining);
          workBufferPosition += secondHalfRemaining;
        } else {
          status = STATUS_FORMAT_ERROR;
        }
      } catch (Exception e) {
        Log.w(TAG, "Error Reading Block", e);
        status = STATUS_FORMAT_ERROR;
      }
    }
    return blockSize;
  }

  private Bitmap getNextBitmap() {
    Bitmap.Config config = isFirstFrameTransparent
        ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
    Bitmap result = bitmapProvider.obtain(downsampledWidth, downsampledHeight, config);
    setAlpha(result);
    return result;
  }

  @TargetApi(12)
  private static void setAlpha(Bitmap bitmap) {
    if (Build.VERSION.SDK_INT >= 12) {
      bitmap.setHasAlpha(true);
    }
  }
}


package de.saxsys.mvvmfx.examples.contacts.util;

import java.util.function.Supplier;
import javafx.beans.property.BooleanProperty;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

/**
 * This class is a helper to create a dialog that will open and close itself
 * according to a boolean property.
 *
 * This way you can define in a ViewModel when and under which conditions a
 * dialog should be shown.
 */
public class DialogHelper {

	/**
	 * Use this method to initialize the show/hide listeners for the dialog.
	 *
	 * @param openProperty the boolean property that defines whether the dialog
	 * should be shown or hidden. Set this property to true to open
	 * the dialog. Set it to false to close the dialog. When the
	 * dialog is closed by the user by clicking on the close-button of the
	 * window, this property will also be set to false by the
	 * dialog.
	 * @param parentStage the Stage that is used as parent to initialize the
	 * ownership of the dialog. This way modal dialogs can be created.
	 * @param rootSupplier a supplier function that is called when the dialog is
	 * made visible for the first time. This function has to return a
	 * {@link Parent} instance that is used as the root node of the dialog
	 * scene.
	 */
	public static void initDialog(BooleanProperty openProperty, final Stage parentStage, Supplier<Parent> rootSupplier) {
		final Stage dialogStage = new Stage(StageStyle.UTILITY);
		dialogStage.initOwner(parentStage);
		dialogStage.initModality(Modality.APPLICATION_MODAL);

		openProperty.addListener((obs, oldValue, newValue) -> {
			if (newValue) {
				// when it is the first time the dialog is made visible (and therefore no scene exists) ...
				if (dialogStage.getScene() == null) {
					// ... we create a new scene and register it in the stage.
					Scene dialogScene = new Scene(rootSupplier.get());
					dialogScene.getStylesheets().add("/contacts.css");
					dialogStage.setScene(dialogScene);
				} else {
					// ... otherwise we simple bring the dialog to front.
					dialogStage.toFront();
				}

				dialogStage.sizeToScene();
				dialogStage.show();
			} else {
				dialogStage.close();
			}
		});

		// when the user clicks on the close button of the dialog window
		// we want to set the property to false
		dialogStage.setOnCloseRequest(event -> openProperty.set(false));
	}

	public static Stage showDialog(Parent view, Stage parentStage, String... sceneStyleSheets) {
		final Stage dialogStage = new Stage(StageStyle.UTILITY);
		dialogStage.initOwner(parentStage);
		dialogStage.initModality(Modality.APPLICATION_MODAL);
		if (dialogStage.getScene() == null) {
			// ... we create a new scene and register it in the stage.
			Scene dialogScene = new Scene(view);
			dialogScene.getStylesheets().addAll(sceneStyleSheets);
			dialogStage.setScene(dialogScene);

			dialogStage.sizeToScene();
			dialogStage.show();
			return dialogStage;
		}
		return null;
	}
}


package de.saxsys.mvvmfx.examples.welcome.viewmodel.personlogin;

import javafx.application.Platform;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.collections.FXCollections;

import javax.inject.Inject;

import de.saxsys.mvvmfx.examples.welcome.model.Person;
import de.saxsys.mvvmfx.examples.welcome.model.Repository;
import de.saxsys.mvvmfx.ViewModel;
import de.saxsys.mvvmfx.utils.commands.Action;
import de.saxsys.mvvmfx.utils.commands.Command;
import de.saxsys.mvvmfx.utils.commands.DelegateCommand;
import de.saxsys.mvvmfx.utils.itemlist.SelectableItemList;
import de.saxsys.mvvmfx.utils.itemlist.SelectableStringList;

/**
 * ViewModel for a login view for the persons. It provides the data which should
 * be visualized in the frontend e.g. the list of persons in string
 * representations. The tests for it can be written first. Have a look on
 * PersonLoginViewModelTest.
 *
 * @author alexander.casall
 *
 */
public class PersonLoginViewModel implements ViewModel {

	// Properties which are used by the view.
	private SelectableItemList<Person> selectablePersons;
	private ReadOnlyIntegerWrapper loggedInPersonId;
	private Command loginCommand;

	// Repo
	private final Repository repository;

	@Inject
	public PersonLoginViewModel(Repository repository) {
		this.repository = repository;
	}

	private BooleanBinding createLoginPossibleBinding() {
		return selectablePersonsProperty().selectedIndexProperty().isNotEqualTo(-1);
	}

	/**
	 * Persons in string representation.
	 *
	 * @return persons
	 */
	public SelectableStringList selectablePersonsProperty() {
		if (selectablePersons == null) {
			selectablePersons = new SelectableItemList<>(
					FXCollections.observableArrayList(repository.getPersons()),
					person -> person.getFirstName() + " " + person.getLastName());
		}
		return selectablePersons;
	}

	/**
	 * Person which is logged in.
	 *
	 * @return person
	 */
	public ReadOnlyIntegerProperty loggedInPersonIdProperty() {
		if (loggedInPersonId == null) {
			loggedInPersonId = new ReadOnlyIntegerWrapper();
		}
		return loggedInPersonId.getReadOnlyProperty();
	}

	public Command getLoginCommand() {
		if (loginCommand == null) {
			loginCommand = new DelegateCommand(() -> new Action() {
				@Override
				protected void action() throws Exception {
					performLogin();
				}
			}, createLoginPossibleBinding(), true);
		}
		return loginCommand;
	}

	private void performLogin() {
		try {
			// fakesleep, simulating latency
			Thread.sleep(2000);
		} catch (Exception e) {
		}

		Platform.runLater(() -> {
			loggedInPersonId.set(selectablePersons.getSelectedItem().getId());
		});
		publish(PersonLoginViewModelNotifications.OK.getId(), PersonLoginViewModelNotifications.OK.getMessage());
	}

}


/*******************************************************************************
 * Copyright 2013 Alexander Casall, Manuel Mauky
 *
 * 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 de.saxsys.mvvmfx.guice;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import de.saxsys.mvvmfx.MvvmFX;
import de.saxsys.mvvmfx.guice.internal.MvvmfxModule;
import de.saxsys.mvvmfx.internal.MvvmfxApplication;
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.stage.Stage;

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

/**
 * This class has to be extended by the user to build a javafx application powered by Guice.
 *
 * @author manuel.mauky
 */
public abstract class MvvmfxGuiceApplication extends Application implements MvvmfxApplication {

	private Stage primaryStage;

	/**
	 * This method is called by the javafx runtime when the application is initialized.
	 * See {@link Application#init()} for more details.
	 *

	 * In this method the initialization of the guice container is done.
	 * For this reason, this method is marked as final and cannot be overwritten by users.
	 *

	 * Please use {@link #initMvvmfx()} for your own initialization logic.
	 *
	 * @throws Exception
	 */
	public final void init() throws Exception {
		List<Module> modules = new ArrayList<>();

		modules.add(new MvvmfxModule());

		modules.add(new AbstractModule() {
			@Override
			protected void configure() {
				bind(HostServices.class).toProvider(MvvmfxGuiceApplication.this::getHostServices);

				bind(Stage.class).toProvider(() -> primaryStage);

				bind(Parameters.class).toProvider(MvvmfxGuiceApplication.this::getParameters);
			}
		});

		this.initGuiceModules(modules);

		final Injector injector = Guice.createInjector(modules);
		MvvmFX.setCustomDependencyInjector(injector::getInstance);

		injector.injectMembers(this);

		this.initMvvmfx();
	}

	/**
	 * This method is overridden to initialize the mvvmFX framework. Override the
	 * {@link #startMvvmfx(javafx.stage.Stage)} method for your application entry point and startup code instead of this
	 * method.
	 */
	public final void start(Stage stage) throws Exception {
		this.primaryStage = stage;

		this.startMvvmfx(stage);
	}

	@Override
	public final void stop() throws Exception {
		stopMvvmfx();
	}

	/**
	 * Configure your guice modules. Use the given list and
	 * add your modules to it.
	 *
	 * @param modules
	 *            module list
	 * @throws Exception
	 *             exc
	 */
	public void initGuiceModules(List<Module> modules) throws Exception {
	}
}


/*******************************************************************************
 * Copyright 2015 Alexander Casall, Manuel Mauky
 *
 * 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 de.saxsys.mvvmfx.internal.viewloader;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;

import de.saxsys.mvvmfx.InjectResourceBundle;

import static de.saxsys.mvvmfx.internal.viewloader.ResourceBundleManager.EMPTY_RESOURCE_BUNDLE;

/**
 * This helper class is used to encapsulate the logic for injection of {@link ResourceBundle} into fields annotated with
 * {@link InjectResourceBundle} in Views/ViewModels.
 *
 */
class ResourceBundleInjector {

	/**
	 * Try to inject the given {@link ResourceBundle} into the given target instance.
	 *
	 * @param target
	 *            the View/ViewModel instance that the ResourceBundle will be injected to. May not be null.
	 * @param resourceBundle
	 *            the ResourceBundle instance that is used.
	 */
	static void injectResourceBundle(Object target, ResourceBundle resourceBundle) {
		final List<Field> fieldsWithAnnotation = ReflectionUtils
				.getFieldsWithAnnotation(target, InjectResourceBundle.class);

		final boolean notAssignableFieldPresent = fieldsWithAnnotation.stream()
				.anyMatch(field -> !field.getType().isAssignableFrom(ResourceBundle.class));

		if (notAssignableFieldPresent) {
			throw new IllegalStateException(
					"The class ["
							+ target
							+ "] has at least one field with the annotation @InjectResourceBundle but the field is not of type ResourceBundle.");
		}

		// check whether the user has provided any resourceBundle or not
		if (resourceBundle == null || resourceBundle.equals(EMPTY_RESOURCE_BUNDLE)) {

			if (!fieldsWithAnnotation.isEmpty()) {

				final boolean nonOptionalFieldsPresent = fieldsWithAnnotation.stream()
						.flatMap(field -> Arrays.stream(field.getAnnotationsByType(InjectResourceBundle.class)))
						.anyMatch(annotation -> !annotation.optional());

				// if all annotated fields are marked as "optional", no exception has to be thrown.
				if (nonOptionalFieldsPresent) {
					throw new IllegalStateException(
							"The class ["
									+ target
									+ "] expects a ResourceBundle to be injected but no ResourceBundle was defined while loading.");
				}
			}
		} else {
			fieldsWithAnnotation
					.forEach(field -> {
						if (field.getType().isAssignableFrom(ResourceBundle.class)) {
							ReflectionUtils.setField(field, target, resourceBundle);
						} else {
							throw new IllegalStateException(
									"The class ["
											+ target
											+ "] has a field with the @InjectResourceBundle annotation but the type of the field doesn't match ResourceBundle");
						}
					});
		}
	}

}


package de.saxsys.mvvmfx.examples.contacts.util;

import java.time.Clock;
import java.time.ZoneId;
import java.time.ZonedDateTime;

/**
 * This class is used to have a single clock that is used to get time instances.
 * When somewhere an instance of {@link java.time.LocalDate} or
 * {@link java.time.LocalDateTime} with the current time is needed you should
 * get it by using the 'now' method with the Clock param:
 *
 * <pre>
 * LocalDate now = LocalDate.now(CentralClock.getClock());
 *
 * </pre>
 *
 * This can help to improve the testablility of the code because in the test you
 * can define a fixed clock (see {#setFixedClock}).
 *
 */
public class CentralClock {

	private static Clock clock = Clock.systemUTC();

	public static Clock getClock() {
		return clock;
	}

	public static void setClock(Clock clock) {
		CentralClock.clock = clock;
	}

	/**
	 * This method is used to set the clock to a fixed time. This is useful for
	 * tests. This way it's possible to create date/time instances with a
	 * predictable value for your tests.
	 */
	public static void setFixedClock(ZonedDateTime zonedDateTime) {
		CentralClock.clock = Clock.fixed(zonedDateTime.toInstant(), ZoneId.systemDefault());
	}

}