/**
 * (C) Copyright 2011-2015 FastConnect SAS
 * (http://www.fastconnect.fr/) and others.
 *
 * 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 fr.fastconnect.factory.tibco.bw.maven.builtin;

import static org.twdata.maven.mojoexecutor.MojoExecutor.artifactId;
import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration;
import static org.twdata.maven.mojoexecutor.MojoExecutor.element;
import static org.twdata.maven.mojoexecutor.MojoExecutor.executeMojo;
import static org.twdata.maven.mojoexecutor.MojoExecutor.executionEnvironment;
import static org.twdata.maven.mojoexecutor.MojoExecutor.goal;
import static org.twdata.maven.mojoexecutor.MojoExecutor.groupId;
import static org.twdata.maven.mojoexecutor.MojoExecutor.plugin;
import static org.twdata.maven.mojoexecutor.MojoExecutor.version;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.project.MavenProject;
import org.twdata.maven.mojoexecutor.MojoExecutor;
import org.twdata.maven.mojoexecutor.MojoExecutor.Element;
import org.twdata.maven.mojoexecutor.MojoExecutor.ExecutionEnvironment;

/**
 *

 * This goal is just a wrapper to launch a goal from a builtin Maven plugin
 * (such as org.apache.maven.plugins:maven-dependency-plugin) with a
 * configuration bound to the lifecycle but without triggering a parallel
 * lifecycle.

 * This configuration will be injected from the Plexus 'components.xml' file.
 *

 *
 *

 * NB: The Maven 2 doclet annotations must be used to inject properly the
 * configuration.
 *

 *
 *

 * Duplicate this template code to create a new wrapped goal (all << >> must
 * be replaced).
 *

 *
 *

 * Original goal :
 * <<groupId>>:<<artifactId>>:<<goal-name>>
 *

 *
 *

 * Associated Plexus 'components.xml' sections (first section is shared) :
 *

 * <component>
 *  <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
 *    <role-hint><<packaging-name>></role-hint>
 *    <implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping
 *    </implementation>
 *   <configuration>
 *    <lifecycles>
 *     <lifecycle>
 *      <id>default</id>
 *      <phases>
 *       <generate-sources>
 *        << fr.fastconnect.factory.tibco.bw.maven:bw-maven-plugin:resolve-bw-dependencies >>
 *       </generate-sources>
 *      </phases>
 *     </lifecycle>
 *    </lifecycles>
 *   </configuration>
 *	</component>
 *
 *	<component>
 *		<role>org.apache.maven.plugin.Mojo</role>
 *		<role-hint><<default-new-goal-name>></role-hint>
 *		<implementation><<fr.fastconnect.factory.tibco.bw.maven.builtin.AbstractWrapperForBuiltinMojo>></implementation>
 *		<configuration>
 *			<groupId><<org.apache.maven.plugins>></groupId>
 *			<artifactId><<maven-dependency-plugin>></artifactId>
 *			<version><<2.8>></version>
 *			<goal><<resolve>></goal>
 *			<< <outputFile>${project.build.directory}/resolved</outputFile> >>
 *			<< <includeTypes>projlib,jar</includeTypes>      >>
 *		</configuration>
 *		<requirements>
 *			<requirement>
 *				<role>org.apache.maven.plugin.BuildPluginManager</role>
 *				<role-hint />
 *				<field-name>pluginManager</field-name>
 *			</requirement>
 *		</requirements>
 *	</component>
 *

 *

 *
 * @_goal <<new-goal-name>>
 * @inheritByDefault true
 * @requiresProject true
 * @aggregator true
 * @requiresDependencyResolution test
 *
 * @author Mathieu Debove
 *
 */
public abstract class AbstractWrapperForBuiltinMojo<CustomResource extends Resource> extends AbstractMojo {
	// Mojo configuration
	/**
	 *  @parameter property="groupId"
	 */
	protected String groupId;
	protected abstract String getGroupId();

	/**
	 *  @parameter property="artifactId"
	 */
	protected String artifactId;
	protected abstract String getArtifactId();

	/**
	 *  @parameter property="version"
	 */
	protected String version;
	protected abstract String getVersion();

	/**
	 *  @parameter property="goal}
	 */
	protected String goal;
	protected abstract String getGoal();

	// Environment configuration
	/**
	 * The project currently being build.
	 *
	 * @parameter property="project"
	 * @required
	 * @readonly
	 */
	protected MavenProject project;
	protected abstract MavenProject getProject();

	/**
	 * The current Maven session.
	 *
	 * @parameter property="session"
	 * @required
	 * @readonly
	 */
	protected MavenSession session;
	protected abstract MavenSession getSession();

	/**
	 * The Build Plugin Manager (this one is Java5 annotation style).
	 */
	@Component (role = BuildPluginManager.class)
	protected BuildPluginManager pluginManager;
	protected abstract BuildPluginManager getPluginManager();

	// Configuration
    /**
     * The actual Mojo configuration found in the Plexus 'components.xml' file.
     *
     * @parameter
     */
	protected Properties configuration;
    protected abstract Properties getConfiguration();

    /**
    * Optional resources parameter do define includes/excludes filesets
    *
    * @parameter
    */
	protected Properties resources;
    protected abstract List<CustomResource> getResources();

	// Execution
	/**
	 * @return the Execution Environment for the actual Mojo being called
	 */
	protected ExecutionEnvironment getEnvironment() {
		return executionEnvironment(getProject(), getSession(), getPluginManager());
	}

	/**
	 * Children classes can use a descendant of {@link Resource} (because of the
	 * <? extends Resource> declaration).
	 * If so, the children classes are responsible for coding the additional
	 * mappings from Java objects to {@link Element}.
	 * @param resource
	 *
	 *@return null, children classes need to override this method to parse
	 * their custom elements
	 */
	protected List<Element> generateCustomElements(CustomResource resource) {
		return null;
	}

	private List<Element> generateResources() {
		List<CustomResource> resources = getResources();
		List<Element> resourcesElement = new ArrayList<Element>();

		if (resources != null) {
			getLog().debug(resources.toString());

			for (CustomResource resource : resources) {
				List<Element> children = new ArrayList<Element>();
				children.add(element("directory", resource.getDirectory()));
				children.add(element("filtering", resource.getFiltering()));
				children.add(element("targetPath", resource.getTargetPath()));

				List<Element> excludes = new ArrayList<Element>();
				for (String exclude : resource.getExcludes()) {
					excludes.add(element("exclude", exclude));
				}
				children.add(element("excludes", excludes.toArray(new Element[0])));

				List<Element> includes = new ArrayList<Element>();
				for (String include : resource.getIncludes()) {
					excludes.add(element("include", include));
				}
				children.add(element("includes", includes.toArray(new Element[0])));

				List<Element> customElements = generateCustomElements(resource);
				if (customElements != null) {
					for (Element element : customElements) {
						children.add(element);
					}
				}

				resourcesElement.add(element("resource", children.toArray(new Element[0])));
			}
		}

		return resourcesElement;
	}

	/**
	 *
	 * @param configuration, the configuration found in Plexus 'components.xml'
	 * file
	 * @return the configuration as a List<Element> used by MojoExecutor
	 * @throws IOException
	 */
	private List<Element> generateConfiguration(Properties configuration) {
		ArrayList<Element> configurations = new ArrayList<Element>();

		if (configuration != null) {
			for (Object key : configuration.keySet()) {
				String value = configuration.get(key).toString();
				getLog().debug(value);
				if ("properties".equals(key) && value != null) {
					getLog().debug("key='properties'");
					Pattern p = Pattern.compile("^\\$\\{(.*)\\}$");
					Matcher matcherElement = p.matcher(value);
					if (matcherElement.matches()) {
						getLog().debug("matching properties to expand");
						value = matcherElement.group(1);
						String replacedValue = this.getProject().getProperties().getProperty(value);
						if (replacedValue != null && !replacedValue.isEmpty()) {
							value = replacedValue;
							getLog().debug("properties_value: " + value);
							Properties properties = new Properties();
							try {
								properties.load(new StringReader(value));
								getLog().debug(properties.toString());
							} catch (IOException e) {
								continue;
							}

							List<Element> children = new ArrayList<Element>();
							for (Object o : properties.keySet()) {
								String childKey = (String) o;
								String childValue = properties.getProperty(childKey);
								children.add(element(childKey, childValue));
							}

							configurations.add(element(key.toString(), children.toArray(new Element[0])));
						}
					}

				} else {
					configurations.add(element(key.toString(), value));
				}
			}
		}

		List<Element> resources = generateResources();
		if (resources != null && !resources.isEmpty()) {
			configurations.add(element("resources", resources.toArray(new Element[0])));
		}

		return configurations;
	}

	/**
	 * This method will call the actual builtin goal from the Mojo specified in
	 * Plexus 'components.xml'.

	 *
	 * The {@link MojoExecutor} is used to perform the call natively.
	 */
	public void execute() throws MojoExecutionException {
		executeMojo(
                plugin(
                    groupId(getGroupId()),
                    artifactId(getArtifactId()),
                    version(getVersion())
                ),
                goal(getGoal()),
                configuration(
                	generateConfiguration(getConfiguration()).toArray(new Element[0])
                ),
                getEnvironment()
            );
	}

}


/**
 * (C) Copyright 2011-2015 FastConnect SAS
 * (http://www.fastconnect.fr/) and others.
 *
 * 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.
 */
//
// Ce fichier a été généré par l'implémentation de référence JavaTM Architecture for XML Binding (JAXB), v2.2.7
// Voir http://java.sun.com/xml/jaxb
// Toute modification apportée à ce fichier sera perdue lors de la recompilation du schéma source.
// Généré le : 2014.09.10 à 11:00:34 AM CEST
//

package fr.fastconnect.factory.tibco.bw.maven.compile.repository;

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import org.w3c.dom.Element;

/**
 *
Classe Java pour anonymous complex type.
 *
 *
Le fragment de schéma suivant indique le contenu attendu figurant dans cette classe.
 *
 *

 * <complexType>
 *   <complexContent>
 *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       <sequence maxOccurs="unbounded" minOccurs="0">
 *         <any processContents='lax' maxOccurs="unbounded" minOccurs="0"/>
 *       </sequence>
 *     </restriction>
 *   </complexContent>
 * </complexType>
 *

 *
 *
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "any"
})
@XmlRootElement(name = "repository")
public class Repository {

    @XmlAnyElement(lax = true)
    protected List<java.lang.Object> any;

    /**
     * Gets the value of the any property.
     *
     *

     * This accessor method returns a reference to the live list,
     * not a snapshot. Therefore any modification you make to the
     * returned list will be present inside the JAXB object.
     * This is why there is not a set method for the any property.
     *
     *

     * For example, to add a new item, do as follows:
     *

     *    getAny().add(newItem);
     *

     *
     *
     *

     * Objects of the following type(s) are allowed in the list
     * {@link Element }
     * {@link java.lang.Object }
     *
     *
     */
    public List<java.lang.Object> getAny() {
        if (any == null) {
            any = new ArrayList<java.lang.Object>();
        }
        return this.any;
    }

}


package com.github.gquintana.metrics.sql;

/*
 * #%L
 * Metrics SQL
 * %%
 * Copyright (C) 2014 Open-Source
 * %%
 * 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.
 * #L%
 */

import com.codahale.metrics.Timer;
import com.github.gquintana.metrics.proxy.MethodInvocation;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;

/**
 * JDBC Proxy handler for {@link Connection}
 */
public class ConnectionProxyHandler extends JdbcProxyHandler<Connection> {

    /**
     * Main constructor
     *
     * @param delegate Wrapped connection
     * @param proxyFactory Strategy to create proxies
     * @param lifeTimerContext Started timed corresponding to connection life
     */
    public ConnectionProxyHandler(Connection delegate, JdbcProxyFactory proxyFactory, Timer.Context lifeTimerContext) {
        super(delegate, Connection.class, proxyFactory, lifeTimerContext);
    }

    @Override
    protected Object invoke(MethodInvocation<Connection> delegatingMethodInvocation) throws Throwable {
        final String methodName = delegatingMethodInvocation.getMethodName();
        Object result;
        if (methodName.equals("isWrapperFor")) {
            result = isWrapperFor(delegatingMethodInvocation);
        } else if (methodName.equals("unwrap")) {
            result = unwrap(delegatingMethodInvocation);
        } else if (methodName.equals("close")) {
            result = close(delegatingMethodInvocation);
        } else if (methodName.equals("createStatement")) {
            result = createStatement(delegatingMethodInvocation);
        } else if (methodName.equals("prepareStatement")) {
            result = prepareStatement(delegatingMethodInvocation);
        } else if (methodName.equals("prepareCall")) {
            result = prepareCall(delegatingMethodInvocation);
        } else {
            result = delegatingMethodInvocation.proceed();
        }
        return result;
    }

    /**
     * Wrap Statement during {@link Connection#createStatement()}
     * @param methodInvocation Current {@link Connection#createStatement()} invocation
     * @return Proxified {@link Statement}
     */
    private Statement createStatement(MethodInvocation<Connection> methodInvocation) throws Throwable {
        Statement result = (Statement) methodInvocation.proceed();
        result = proxyFactory.wrapStatement(result);
        return result;
    }

    /**
     * Wrap Statement during {@link Connection#prepareStatement(String)} ()}
     * @param methodInvocation Current {@link Connection#prepareStatement(String)} invocation
     * @return Proxified {@link PreparedStatement}
     */
    private PreparedStatement prepareStatement(MethodInvocation<Connection> methodInvocation) throws Throwable {
        String sql = methodInvocation.getArgAt(0, String.class);
        PreparedStatement result = (PreparedStatement) methodInvocation.proceed();
        result = proxyFactory.wrapPreparedStatement(result, sql);
        return result;
    }

    /**
     * Wrap Statement during {@link Connection#prepareCall(String)}
     * @param methodInvocation Current {@link Connection#prepareCall(String)}  invocation
     * @return Proxified {@link CallableStatement}
     */
    private CallableStatement prepareCall(MethodInvocation<Connection> methodInvocation) throws Throwable {
        String sql = methodInvocation.getArgAt(0, String.class);
        CallableStatement result = (CallableStatement) methodInvocation.proceed();
        result = proxyFactory.wrapCallableStatement(result, sql);
        return result;
    }
}


package com.github.gquintana.metrics.sql;

/*
 * #%L
 * Metrics SQL
 * %%
 * Copyright (C) 2014 Open-Source
 * %%
 * 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.
 * #L%
 */

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
import com.codahale.metrics.Timer;
import com.github.gquintana.metrics.proxy.ProxyFactory;

import java.lang.reflect.Constructor;
import java.sql.*;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;

/**
 * Metrics SQL JDBC Driver
 */
public class Driver implements java.sql.Driver {
    private static final Driver INSTANCE = new Driver();
    private static boolean registered = false;
    private final Logger parentLogger = Logger.getLogger("com.github.gquintana.metrics");

    static {
        register();
    }

    private static synchronized void register() {
        try {
            if (!registered) {
                registered = true;
                DriverManager.registerDriver(INSTANCE);
            }
        } catch (SQLException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Instantiate a new object of type T
     *
     * @param clazz  Object class
     * @param params Constructor args
     * @param <T>    Object type
     * @return New object
     */
    private static <T> T newInstance(Class<T> clazz, Object... params) throws SQLException {
        try {
            if (params == null || params.length == 0) {
                return clazz.newInstance();
            } else {
                for (Constructor<?> ctor : clazz.getConstructors()) {
                    if (ctor.getParameterTypes().length != params.length) {
                        continue;
                    }
                    int paramIndex = 0;
                    for (Class<?> paramType : ctor.getParameterTypes()) {
                        if (!paramType.isInstance(params[paramIndex])) {
                            break;
                        }
                        paramIndex++;
                    }
                    if (paramIndex != params.length) {
                        continue;
                    }
                    @SuppressWarnings("unchecked")
                    Constructor<T> theCtor = (Constructor<T>) ctor;
                    return theCtor.newInstance(params);
                }
                throw new SQLException("Constructor not found for " + clazz);
            }
        } catch (ReflectiveOperationException reflectiveOperationException) {
            throw new SQLException(reflectiveOperationException);
        }
    }

    @Override
    public Connection connect(String url, Properties info) throws SQLException {
        if (!acceptsURL(url)) {
            return null;
        }
        DriverUrl driverUrl = DriverUrl.parse(url);
        MetricRegistry registry = getMetricRegistry(driverUrl);
        ProxyFactory factory = newInstance(driverUrl.getProxyFactoryClass());
        MetricNamingStrategy namingStrategy = getMetricNamingStrategy(driverUrl);
        JdbcProxyFactory proxyFactory = new JdbcProxyFactory(registry, namingStrategy, factory);
        // Force Driver loading
        Class<? extends Driver> driverClass = driverUrl.getDriverClass();
        // Open connection
        Timer.Context getTimerContext = proxyFactory.getMetricHelper().startConnectionGetTimer();
        Connection rawConnection = DriverManager.getConnection(driverUrl.getCleanUrl(), info);
        if (getTimerContext != null) {
            getTimerContext.stop();
        }
        // Wrap connection
        return proxyFactory.wrapConnection(rawConnection);
    }

    private MetricNamingStrategy getMetricNamingStrategy(DriverUrl driverUrl) throws SQLException {
        Class<? extends MetricNamingStrategy> namingStrategyClass = driverUrl.getNamingStrategyClass();
        String databaseName = driverUrl.getDatabaseName();
        return databaseName == null ? newInstance(namingStrategyClass) : newInstance(namingStrategyClass, databaseName);
    }

    private MetricRegistry getMetricRegistry(DriverUrl driverUrl) {
        String registryName = driverUrl.getRegistryName();
        MetricRegistry registry;
        if (registryName == null) {
            registry = SharedMetricRegistries.tryGetDefault();
            if (registry == null) {
                registry = SharedMetricRegistries.getOrCreate("default");
            }
        } else {
            registry = SharedMetricRegistries.getOrCreate(registryName);
        }
        return registry;
    }

    @Override
    public boolean acceptsURL(String url) throws SQLException {
        return url != null && url.startsWith(DriverUrl.URL_PREFIX);
    }

    @Override
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
        DriverUrl driverUrl = DriverUrl.parse(url);
        java.sql.Driver driver = DriverManager.getDriver(driverUrl.getCleanUrl());
        return driver.getPropertyInfo(driverUrl.getCleanUrl(), info);
    }

    @Override
    public int getMajorVersion() {
        return 3;
    }

    @Override
    public int getMinorVersion() {
        return 1;
    }

    @Override
    public boolean jdbcCompliant() {
        return true;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return parentLogger;
    }

}


package com.github.gquintana.metrics.proxy;

/*
 * #%L
 * Metrics SQL
 * %%
 * Copyright (C) 2014 Open-Source
 * %%
 * 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.
 * #L%
 */

import java.lang.reflect.Proxy;

/**
 * Factory of proxies producing proxies based on Java reflection
 */
public class ReflectProxyFactory implements ProxyFactory {
	/**
	 * {@inheritDoc}
	 */
        @Override
	public <T> T newProxy(ProxyHandler<T> proxyHandler, ProxyClass proxyClass) {
		return (T) Proxy.newProxyInstance(proxyClass.getClassLoader(), proxyClass.getInterfaces(), proxyHandler);
	}
}


package com.github.gquintana.metrics.proxy;

/*
 * #%L
 * Metrics SQL
 * %%
 * Copyright (C) 2014 Open-Source
 * %%
 * 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.
 * #L%
 */

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Objects;

/**
 * Proxy class key
 */
public final class ProxyClass {
    private final ClassLoader classLoader;
    private final Class<?>[] interfaces;
    private final int hashCode;

    public ProxyClass(ClassLoader classLoader, Class<?>... interfaces) {
        this.classLoader = classLoader;
        this.interfaces = interfaces;
        // Generate hashcode once for all
        final Object[] hashValue = new Object[interfaces.length + 1];
        hashValue[0] = classLoader;
        System.arraycopy(interfaces, 0, hashValue, 1, interfaces.length);
        this.hashCode = Objects.hash(hashValue);
    } // Generate hashcode once for all

    public ClassLoader getClassLoader() {
        return classLoader;
    }

    public Class<?>[] getInterfaces() {
        Class<?>[] copy = new Class<?>[interfaces.length];
        System.arraycopy(interfaces, 0, copy, 0, interfaces.length);
        return copy;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        final ProxyClass that = (ProxyClass) o;
        return (this.hashCode == that.hashCode) && Objects.equals(this.classLoader, that.classLoader) && Arrays.equals(this.interfaces, that.interfaces);
    }

    @Override
    public int hashCode() {
        return hashCode;
    }
    /**
     * Create proxy class
     * @param <T> Expected proxy type
     * @return Class of proxy for given classloader and interfaces
     */
    public <T> Class<T> createClass() {
        return (Class<T>) Proxy.getProxyClass(getClassLoader(), getInterfaces());
    }
    /**
     * Create proxy constructor
     /**
     * Create proxy class
     * @param <T> Expected proxy type
     * @return Constructor of proxy for given classloader and interfaces
     */
    public <T> Constructor<T> createConstructor() {
        try {
            return this.<T>createClass().getConstructor(InvocationHandler.class);
        } catch (NoSuchMethodException noSuchMethodException) {
            throw new ProxyException(noSuchMethodException);
        }
    }
}


package com.github.gquintana.metrics.sql;

/*
 * #%L
 * Metrics SQL
 * %%
 * Copyright (C) 2014 Open-Source
 * %%
 * 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.
 * #L%
 */

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;

/**
 * Start {@link com.codahale.metrics.Timer}s and increments {@link com.codahale.metrics.Meter}s
 * Internal helper class.
 */
class MetricHelper {
    private final MetricRegistry metricRegistry;
    private final MetricNamingStrategy metricNamingStrategy;

    /**
     * Constructor
     * @param metricRegistry Registry storing metrics
     * @param metricNamingStrategy Strategy to name metrics
     */
    MetricHelper(MetricRegistry metricRegistry, MetricNamingStrategy metricNamingStrategy) {
        this.metricRegistry = metricRegistry;
        this.metricNamingStrategy = metricNamingStrategy;
    }

    private Timer.Context startTimer(String name) {
        if (name == null) {
            return null;
        }
        return metricRegistry.timer(name).time();
    }
    private void markMeter(String name) {
        if (name == null) {
            return;
        }
        metricRegistry.meter(name).mark();
    }

    public Timer.Context startConnectionLifeTimer() {
        return startTimer(metricNamingStrategy.getConnectionLifeTimer());
    }

    public Timer.Context startConnectionGetTimer() {
        return startTimer(metricNamingStrategy.getConnectionGetTimer());
    }

    /**
     * Start Timer when statement is created
     *
     * @return Started timer context or null
     */
    public Timer.Context startStatementLifeTimer() {
        return startTimer(metricNamingStrategy.getStatementLifeTimer());
    }
    /**
     * Start Timer when statement is executed
     *
     * @param query SQL query
     * @return Started timer context or null
     */
    public Timer.Context startStatementExecuteTimer(Query query) {
        ensureSqlId(query);
        String name = metricNamingStrategy.getStatementExecuteTimer(query.getSql(), query.getSqlId());
        return startTimer(name);
    }

    private void ensureSqlId(Query query) {
        query.ensureSqlId(metricNamingStrategy);
    }

    /**
     * Start Timer when prepared statement is created
     *
     * @return Started timer context or null
     */
    public Timer.Context startPreparedStatementLifeTimer(Query query) {
        ensureSqlId(query);
        String name = metricNamingStrategy.getPreparedStatementLifeTimer(query.getSql(), query.getSqlId());
        return startTimer(name);
    }

    /**
     * Start Timer when prepared statement is created
     *
     * @return Started timer context or null
     */
    public Timer.Context startPreparedStatementExecuteTimer(Query query) {
        ensureSqlId(query);
        String name = metricNamingStrategy.getPreparedStatementExecuteTimer(query.getSql(), query.getSqlId());
        return startTimer(name);
    }

    private String getSqlId(String sqlId, String sql) {
        if (sqlId == null) {
            sqlId = metricNamingStrategy.getSqlId(sql);
        }
        return sqlId;
    }

    /**
     * Start Timer when callable statement is created
     *
     * @return Started timer context or null
     */
    public Timer.Context startCallableStatementLifeTimer(Query query) {
        ensureSqlId(query);
        String name = metricNamingStrategy.getCallableStatementLifeTimer(query.getSql(), query.getSqlId());
        return startTimer(name);
    }

    /**
     * Start Timer when prepared statement is created
     *
     * @return Started timer context or null
     */
    public Timer.Context startCallableStatementExecuteTimer(Query query) {
        ensureSqlId(query);
        String name = metricNamingStrategy.getCallableStatementExecuteTimer(query.getSql(), query.getSqlId());
        return startTimer(name);
    }

    /**
     * Start Timer when result set is created
     *
     * @return Started timer context or null
     */
    public Timer.Context startResultSetLifeTimer(Query query) {
        ensureSqlId(query);
        String name = metricNamingStrategy.getResultSetLifeTimer(query.getSql(), query.getSqlId());
        return startTimer(name);
    }
    /**
     * Increment when result set row is read
     */
    public void markResultSetRowMeter(Query query) {
        ensureSqlId(query);
        String name = metricNamingStrategy.getResultSetRowMeter(query.getSql(), query.getSqlId());
        markMeter(name);
    }
}


package com.github.gquintana.metrics.sql;

/*
 * #%L
 * Metrics SQL
 * %%
 * Copyright (C) 2014 Open-Source
 * %%
 * 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.
 * #L%
 */

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.github.gquintana.metrics.proxy.ProxyFactory;
import com.github.gquintana.metrics.proxy.ReflectProxyFactory;

import javax.sql.*;
import javax.sql.rowset.*;
import java.sql.*;

/**
 * Factory of {@code JdbcProxyHandler} sub classes, central class of Metrics SQL.
 * It can be used to wrap any JDBC component (connection, statement,
 * result set...).
 */
public class JdbcProxyFactory {
    /**
     * Timer manager
     */
    private final MetricHelper metricHelper;
    /**
     * Proxy factory
     */
    private final ProxyFactory proxyFactory;

    /**
     * Constructor using default {@link ReflectProxyFactory} and default {@link DefaultMetricNamingStrategy}
     * @param metricRegistry Metric registry to store metrics
     */
    public JdbcProxyFactory(MetricRegistry metricRegistry) {
        this(metricRegistry, new DefaultMetricNamingStrategy());
    }

    /**
     * Constructor
     *
     * @param registry Registry storing metrics
     * @param namingStrategy Naming strategy used to get metrics from SQL
     */
    public JdbcProxyFactory(MetricRegistry registry, MetricNamingStrategy namingStrategy) {
        this(registry, namingStrategy, new ReflectProxyFactory());
    }

    /**
     * Constructor
     *
     * @param registry Registry storing metrics
     * @param namingStrategy Naming strategy used to get metrics from SQL
     * @param proxyFactory AbstractProxyFactory to use for proxy creation
     */
    public JdbcProxyFactory(MetricRegistry registry, MetricNamingStrategy namingStrategy, ProxyFactory proxyFactory) {
        this.metricHelper = new MetricHelper(registry, namingStrategy);
        this.proxyFactory = proxyFactory;
    }
    /**
     * Create a proxy for given JDBC proxy handler
     * @param <T> Proxy type
     * @param proxyHandler Proxy handler
     * @return Proxy
     */
    private <T> T newProxy(JdbcProxyHandler<T> proxyHandler) {
        return proxyFactory.newProxy(proxyHandler, proxyHandler.getProxyClass());
    }

    /**
     * Wrap a data source to monitor it.
     *
     * @param wrappedDataSource Data source to wrap
     * @return Wrapped data source
     */
    public DataSource wrapDataSource(DataSource wrappedDataSource) {
        return newProxy(new DataSourceProxyHandler(wrappedDataSource, this));
    }

    /**
     * Wrap a connection to monitor it.
     *
     * @param wrappedConnection Connection to wrap
     * @return Wrapped connection
     */
    public Connection wrapConnection(Connection wrappedConnection) {
        Timer.Context lifeTimerContext = metricHelper.startConnectionLifeTimer();
        return newProxy(new ConnectionProxyHandler(wrappedConnection, this, lifeTimerContext));
    }

    /**
     * Wrap a simple statement to monitor it.
     *
     * @param statement Statement to wrap
     * @return Wrapped statement
     */
    public Statement wrapStatement(Statement statement) {
        Timer.Context lifeTimerContext = getMetricHelper().startStatementLifeTimer();
        return newProxy(new StatementProxyHandler(statement, this, lifeTimerContext));
    }

    /**
     * Wrap a prepared statement to monitor it.
     *
     * @param preparedStatement Prepared statement to wrap
     * @param sql SQL
     * @return Wrapped prepared statement
     */
    public PreparedStatement wrapPreparedStatement(PreparedStatement preparedStatement, String sql) {
        Query query = new Query(sql);
        Timer.Context lifeTimerContext = getMetricHelper().startPreparedStatementLifeTimer(query);
        return newProxy(new PreparedStatementProxyHandler(preparedStatement, this, query, lifeTimerContext));
    }

    /**
     * Wrap a callable statement to monitor it.
     *
     * @param callableStatement Prepared statement to wrap
     * @param sql SQL
     * @return Wrapped prepared statement
     */
    public CallableStatement wrapCallableStatement(CallableStatement callableStatement, String sql) {
        Query query = new Query(sql);
        Timer.Context lifeTimerContext = getMetricHelper().startCallableStatementLifeTimer(query);
        return newProxy(new CallableStatementProxyHandler(callableStatement, this, query, lifeTimerContext));
    }

    /**
     * Wrap a result set to monitor it.
     *
     * @param resultSet set to wrap
     * @param sql SQL related to Result set
     * @return Wrapped prepared statement
     */
    public ResultSet wrapResultSet(ResultSet resultSet, String sql) {
        Query query = new Query(sql);
        Timer.Context lifeTimerContext = metricHelper.startResultSetLifeTimer(query);
        return (ResultSet) newProxy(new ResultSetProxyHandler(resultSet, getResultSetType(resultSet), this, query, lifeTimerContext));
    }

    /**
     * Wrap a result set to monitor it.
     *
     * @param resultSet set to wrap
     * @param query SQL query of result set
     * @param lifeTimerContext Started timer
     * @return Wrapped prepared statement
     */
    public ResultSet wrapResultSet(ResultSet resultSet, Query query, Timer.Context lifeTimerContext) {
        return (ResultSet) newProxy(new ResultSetProxyHandler(resultSet, getResultSetType(resultSet), this, query, lifeTimerContext));
    }
    /**
     * Determine the interface implemented by this result set
     *
     * @param resultSet Result set
     */
    private Class<? extends ResultSet> getResultSetType(ResultSet resultSet) {
        Class<? extends ResultSet> resultSetType;
        if (resultSet instanceof RowSet) {
            if (resultSet instanceof CachedRowSet) {
                if (resultSet instanceof WebRowSet) {
                    if (resultSet instanceof FilteredRowSet) {
                        resultSetType = FilteredRowSet.class;
                    } else if (resultSet instanceof JoinRowSet) {
                        resultSetType = JoinRowSet.class;
                    } else {
                        resultSetType = WebRowSet.class;
                    }
                } else {
                    resultSetType = CachedRowSet.class;
                }
            } else if (resultSet instanceof JdbcRowSet) {
                resultSetType = JdbcRowSet.class;
            } else {
                resultSetType = RowSet.class;
            }
        } else {
            resultSetType = ResultSet.class;
        }
        return resultSetType;
    }

    public MetricHelper getMetricHelper() {
        return metricHelper;
    }
}


package com.github.gquintana.metrics.util;

/*
 * #%L
 * Metrics SQL
 * %%
 * Copyright (C) 2014 Open-Source
 * %%
 * 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.
 * #L%
 */

import com.codahale.metrics.DefaultObjectNameFactory;
import com.codahale.metrics.ObjectNameFactory;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Generates JMX {@link ObjectName} using JMX
 */
public class SqlObjectNameFactory implements ObjectNameFactory {
    private final ObjectNameFactory defaultObjectFactory;

    public SqlObjectNameFactory(ObjectNameFactory defaultObjectNameFactory) {
        this.defaultObjectFactory = defaultObjectNameFactory;
    }

    public SqlObjectNameFactory() {
        this.defaultObjectFactory = new DefaultObjectNameFactory();
    }

    private static final Pattern PATTERN = Pattern.compile(
            "(java\\.sql"           // Package
            + "\\.[A-Z]\\w+)"          // Class
            + "\\.(\\w+)"              // Database
            + "(?:\\.\\[([^]]*)\\])?"  // SQL Query
            + "(?:\\.(\\w+))?");       // Event
    @Override
    public ObjectName createName(String type, String domain, String name) {
        Matcher matcher = PATTERN.matcher(name);
        ObjectName objectName = null;
        if (matcher.matches()) {
            String className = matcher.group(1);
            String database  = matcher.group(2);
            String sql       = matcher.group(3);
            String event     = matcher.group(4);
            Hashtable<String, String> props = new Hashtable<>();
            props.put("class", className);
            props.put("database", database);
            if (sql!=null) {
                // , and \ are not allowed
                props.put("sql", sql.replaceAll("[:=*?,\\n\\\\]", " ").replaceAll("[\\s]+", " "));
            }
            if (event!=null) {
                props.put("event", event);
            }
            if (type!=null) {
                props.put("metricType", type);
            }
            try {
                objectName = new ObjectName(domain, props);
            } catch (MalformedObjectNameException malformedObjectNameException) {
            }
        }
        if (objectName == null) {
            objectName = defaultObjectFactory.createName(type, domain, name);
        }
        return objectName;
    }

}


package com.github.gquintana.metrics.sql;

/*
 * #%L
 * Metrics SQL
 * %%
 * Copyright (C) 2014 Open-Source
 * %%
 * 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.
 * #L%
 */

import com.codahale.metrics.MetricRegistry;

import javax.sql.PooledConnection;
import java.sql.*;
import java.util.regex.Pattern;

/**
 * Default implementation of {@link MetricNamingStrategy}
 */
public class DefaultMetricNamingStrategy implements MetricNamingStrategy {
    private final String databaseName;

    public DefaultMetricNamingStrategy() {
        databaseName = "";
    }

    /**
     * @param databaseName Database name
     */
    public DefaultMetricNamingStrategy(String databaseName) {
        this.databaseName = databaseName;
    }

    /**
     * Generate SQL Id from SQL query.
     * This method can be used to normalize SQL queries, remove special characters,
     * truncate long SQL queries...
     *
     * @param sql Input SQL
     * @return [sql]
     */
    public String getSqlId(String sql) {
        return "[" + sql.toLowerCase() + "]";
    }

    protected String getStatementTimer(Class<? extends Statement> clazz, String sql, String sqlId) {
        final String lSqlId = sqlId == null ? getSqlId(sql) : sqlId;
        return MetricRegistry.name(clazz, databaseName, lSqlId);
    }

    protected String getStatementExecuteTimer(Class<? extends Statement> clazz, String sql, String sqlId) {
        final String lSqlId = sqlId == null ? getSqlId(sql) : sqlId;
        return MetricRegistry.name(clazz, databaseName, lSqlId, "exec");
    }

    /**
     * {@inheritDoc}
     * Example: java.sql.Connection.database.get
     */
    public String getConnectionGetTimer() {
        return MetricRegistry.name(Connection.class, databaseName, "get");
    }

    /**
     * {@inheritDoc}
     * Example: java.sql.Connection.database
     */
    public String getConnectionLifeTimer() {
        return MetricRegistry.name(Connection.class, databaseName);
    }

    /**
     * {@inheritDoc}
     * Example: java.sql.Statement.database
     */
    public String getStatementLifeTimer() {
        return MetricRegistry.name(Statement.class, databaseName);
    }

    /**
     * {@inheritDoc}
     * Example: java.sql.Statement.database.[sqlId].exec
     */
    public String getStatementExecuteTimer(String sql, String sqlId) {
        return getStatementExecuteTimer(Statement.class, sql, sqlId);
    }

    /**
     * {@inheritDoc}
     * Example: java.sql.PreparedStatement.database.[sqlId]
     */
    public String getPreparedStatementLifeTimer(String sql, String sqlId) {
        return getStatementTimer(PreparedStatement.class, sql, sqlId);
    }

    /**
     * {@inheritDoc}
     * Example: java.sql.PreparedStatement.database.[sqlId].exec
     */
    public String getPreparedStatementExecuteTimer(String sql, String sqlId) {
        return getStatementExecuteTimer(PreparedStatement.class, sql, sqlId);
    }

    /**
     * {@inheritDoc}
     * Example: java.sql.CallableStatement.database.[sqlId]
     */
    public String getCallableStatementLifeTimer(String sql, String sqlId) {
        return getStatementTimer(CallableStatement.class, sql, sqlId);
    }

    /**
     * {@inheritDoc}
     * Example: java.sql.CallableStatement.database.[sqlId].exec
     */
    public String getCallableStatementExecuteTimer(String sql, String sqlId) {
        return getStatementExecuteTimer(CallableStatement.class, sql, sqlId);
    }

    /**
     * {@inheritDoc}
     */
    public String getResultSetLifeTimer(String sql, String sqlId) {
        return MetricRegistry.name(ResultSet.class, databaseName, sqlId);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getResultSetRowMeter(String sql, String sqlId) {
        return MetricRegistry.name(ResultSet.class, databaseName, sqlId, "rows");
    }

    /**
     * Start a builder
     *
     * @return Builder
     */
    public static Builder builder() {
        return new Builder();
    }

    /**
     * Builder of {@link DefaultMetricNamingStrategy}
     *
     * @param <B> Builder type
     */
    public static class Builder<B extends Builder<B>> {
        protected String databaseName;

        public B withDatabaseName(String databaseName) {
            this.databaseName = databaseName;
            return (B) this;
        }

        public DefaultMetricNamingStrategy build() {
            return new DefaultMetricNamingStrategy(databaseName);
        }
    }

}


package com.github.ricksbrown.cowsay.plugin;

import com.github.ricksbrown.cowsay.CowFace;
import com.github.ricksbrown.cowsay.Cowsay;
import com.github.ricksbrown.cowsay.CowsayCli;
import java.util.ArrayList;
import java.util.List;

/**
 * This class acts as a convenient facade to the cowsay methods that accept arrays of arguments.
 * This is useful for Maven, Ant and possibly other plugin types.
 * @author Rick Brown
 */
public class CowExecutor {

	private boolean think = false;
	private String message = null;
	private String mode = null;
	private String eyes = null;
	private String tongue = null;
	private String wrap = null;
	private String cowfile = null;
	private String lang = null;
	private boolean html = false;
	private String alt = null;

	/**
	 * @param eyes Custom eyes.
	 */
	public void setEyes(final String eyes) {
		this.eyes = eyes;
	}

	/**
	 *
	 * @param cowfile Specify an alternate cowfile.
	 */
	public void setCowfile(final String cowfile) {
		this.cowfile = cowfile;
	}

	/**
	 *
	 * @param think If true the cow will think the message instead of saying it.
	 */
	public void setThink(final boolean think) {
		this.think = think;
	}

	/**
	 * @param tongue Custom tongue.
	 */
	public void setTongue(final String tongue) {
		this.tongue = tongue;
	}

	/**
	 * @param wrap Wrap message at approximately this length.
	 * Pass "0" for no wrap.
	 */
	public void setWrap(final String wrap) {
		this.wrap = wrap;
	}

	/**
	 * @param message The message for the cow to say / think.
	 */
	public void setMessage(final String message) {
		this.message = message;
	}

	/**
	 * @param mode The cow mode, for example "b" for "Borg".
	 */
	public void setMode(final String mode) {
		this.mode = mode;
	}

	/**
	 * Check that the mandatory properties have been set.
	 * @throws IllegalStateException If no message has been provided for the cow to say or think.
	 */
	private void validate() throws IllegalStateException {
		if (this.message == null) {
			throw new IllegalStateException("No message specified");
		}
	}

	/**
	 * Set the i18n language.
	 * @param lang The language to use.
	 */
	public void setLang(final String lang) {
		this.lang = lang;
	}

	/**
	 * Enable / disable HTML output mode.
	 * @param html true to turn on HTML output mode.
	 */
	public void setHtml(final boolean html) {
		this.html = html;
	}

	/**
	 * Set the alt text for HTML mode.
	 * @param alt The alt text.
	 */
	public void setAlt(final String alt) {
		this.alt = alt;
	}

	/**
	 * Build an args array that can be passed to cowsay.
	 * @return commandline args
	 */
	protected String[] buildArgs() {
		String result[] = new String[0];
		List<String> args = new ArrayList<String>();
		if (lang != null && lang.length() > 0) {
			args.add(flagify(CowsayCli.Opt.LANG.toString()));
			args.add(lang);
		}
		if (html) {
			args.add(flagify(CowsayCli.Opt.HTML.toString()));
		}
		if (alt != null && alt.length() > 0) {
			args.add(flagify(CowsayCli.Opt.ALT.toString()));
			args.add(alt);
		}
		if (wrap != null) {
			args.add(flagify(CowsayCli.Opt.WRAP_AT.toString()));
			args.add(wrap);
		}
		buildFaceArgs(args);
		args.add(message);
		return args.toArray(result);
	}

	/**
	 * Face specific flags, either a face mode or face customizations.
	 * @param args The arg line to add to.
	 */
	private void buildFaceArgs(final List<String> args) {
		if (mode != null && CowFace.isKnownMode(mode)) {
			args.add(flagify(mode));
		} else {
			if (eyes != null) {
				args.add(flagify(CowsayCli.Opt.EYES.toString()));
				args.add(eyes);
			}
			if (tongue != null) {
				args.add(flagify(CowsayCli.Opt.TONGUE.toString()));
				args.add(tongue);
			}
			if (cowfile != null) {
				args.add(flagify(CowsayCli.Opt.COWFILE.toString()));
				args.add(cowfile);
			}
		}
	}

	/**
	 * Adds the '-' or '--' to a flag name ready to be passed as a commandline arg.
	 *
	 * @param flag A flag name (e.g. "b" or "lang")
	 * @return The flag with leading dash, (e.g. "-b" or "--lang")
	 */
	private String flagify(final String flag) {
		if (flag.length() > 1) {
			return "--" + flag;
		}
		return '-' + flag;
	}

	/**
	 * Run cowsay with the provided properties.
	 * @return The formatted cow message.
	 * @throws IllegalStateException If mandatory properties have not been set.
	 */
	public String execute() throws IllegalStateException {
		validate();
		String[] args = buildArgs();
		String result;
		if (think) {
			result = Cowsay.think(args);
		} else {
			result = Cowsay.say(args);
		}
		return result;
	}
}


package com.github.ricksbrown.cowsay.ant;

import com.github.ricksbrown.cowsay.plugin.CowExecutor;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;

/**
 * This class implements an Ant task to run cowsay.
 *
 * Usage:
 *
 * <pre>{@code
	<taskdef name="cowsay"
		classname="com.github.ricksbrown.cowsay.ant.CowsayTask"
		classpath="cowsay.jar"/>

	<cowsay message="Moo!"/>
	}</pre>
 *
 * @author Rick Brown
 */
public class CowsayTask  extends Task {
	private final CowExecutor executor = new CowExecutor();
	private String property = null;

	/**
	 *
	 * @param property Specify a property to set instead of just echoing the message.
	 */
	public void setProperty(final String property) {
		this.property = property;
	}

	/**
	 *
	 * @param cowfile Specify an alternate cowfile.
	 */
	public void setCowfile(final String cowfile) {
		executor.setCowfile(cowfile);
	}

	/**
	 *
	 * @param think If true the cow will think the message instead of saying it.
	 */
	public void setThink(final boolean think) {
		executor.setThink(think);
	}

	/**
	 *
	 * @param lang Set the i18n language.
	 */
	public void setLang(final String lang) {
		executor.setLang(lang);
	}

	/**
	 *
	 * @param alt Set the HTML alt text.
	 */
	public void setAlt(final String alt) {
		executor.setAlt(alt);
	}

	/**
	 *
	 * @param html true to turn on HTML mode.
	 */
	public void setHtml(final boolean html) {
		executor.setHtml(html);
	}

	/**
	 * @param tongue Custom tongue.
	 */
	public void setTongue(final String tongue) {
		executor.setTongue(tongue);
	}

	/**
	 * @param wrap Wrap message at approximately this length.
	 * Pass "0" for no wrap.
	 */
	public void setWrap(final String wrap) {
		executor.setWrap(wrap);
	}

	/**
	 * @param message The message for the cow to say / think.
	 */
	public void setMessage(final String message) {
		executor.setMessage(message);
	}

	/**
	 * @param mode The cow mode, for example "b" for "Borg".
	 */
	public void setMode(final String mode) {
		executor.setMode(mode);
	}

	/**
	 * Run cowsay with the provided attributes.
	 * @throws BuildException If something goes wrong during execution.
	 */
	@Override
	public void execute() throws BuildException {
		try {
			String moo = executor.execute();
			if (this.property != null && this.property.length() > 0) {
				getProject().setProperty(this.property, moo);
			} else {
				System.out.println(moo);
			}
		} catch (IllegalStateException ex) {
			throw new BuildException(ex.getMessage(), ex);
		}
	}
}


package com.github.ricksbrown.cowsay;

import java.util.Locale;
import java.util.ResourceBundle;

/**
 * Allows cowsay to be internationalized.
 * Moo! Muh! Meuh!
 * @author Rick Brown
 */
public final class I18n {
	private static final String DEFAULT_LANG = "en";
	private static Locale currentLocale = null;
	private static ResourceBundle messages = null;

	/**
	 * Utility classes do not need constructors.
	 */
	private I18n() {

	}

	/**
	 * Set the language.
	 * @param language A BCP47 language, e.g. "en", "fr".
	 */
	public static void setLanguage(final String language) {
		currentLocale = new Locale(language);
		messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
	}

	/**
	 * Get a message in the correct language.
	 * @param key The lookup key for the message.
	 * @return The message in the correct language, if found, otherwise in the default language.
	 */
	protected static String getMessage(final String key) {
		if (messages == null) {
			setLanguage(DEFAULT_LANG);
		}
		return messages.getString(key);
	}

}


package com.github.ricksbrown.cowsay;

import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;

/**
 * Java port of the original cowsay commandline utility.
 * There are three entry points to this class:
 *

 *
{@code Cowsay.main(args);} will call either cowsay or cowthink depending on the --cowthink flag

 *
{@code Cowsay.say(args);} is equivalent to cowsay

 *
{@code Cowsay.think(args);} is equivalent to cowthink

 *

 *
 * @author Rick Brown
 */
public class Cowsay {

	/**
	 * Equivalent to cowsay.
	 * @param args the command line arguments
	 * @return What the cow said.
	 */
	public static String say(final String[] args) {
		return sayOrThink(args, false);
	}

	/**
	 * Equivalent to cowthink.
	 * @param args the command line arguments
	 * @return What the cow thought.
	 */
	public static String think(final String[] args) {
		return sayOrThink(args, true);
	}

	/**
	 * Do some cowsaying or cowthinking.
	 * @param args the command line arguments
	 * @param think if true will think instead of say (the --cowthink flag can also invoke thinking)
	 */
	private static String sayOrThink(final String[] args, final boolean think) {
		try {
			boolean isThought = think;

			String wordwrap = null;
			CommandLine commandLine = CowsayCli.parseCmdArgs(args);
			if (commandLine != null) {
				if (commandLine.hasOption(CowsayCli.Opt.HELP.toString())) {
					CowsayCli.showCmdLineHelp();
				}
				else if (commandLine.hasOption(CowsayCli.Opt.LIST_COWS.toString())) {
					String[] files = Cowloader.listAllCowfiles();
					if (files != null) {
						return StringUtils.join(files, System.getProperty("line.separator"));
					}
				}
				else {
					String cowfileSpec = null;
					CowFace cowFace = null;

					if (commandLine.hasOption(CowsayCli.Opt.WRAP_AT.toString())) {
						wordwrap = commandLine.getOptionValue(CowsayCli.Opt.WRAP_AT.toString());
					}
					else if (commandLine.hasOption(CowsayCli.Opt.NOWRAP.toString())) {
						wordwrap = "0";
					}
					cowFace = getCowFaceByMode(commandLine);
					if (cowFace == null) {
						// if we are in here no modes were set
						if (commandLine.hasOption(CowsayCli.Opt.COWFILE.toString())) {
							cowfileSpec = commandLine.getOptionValue(CowsayCli.Opt.COWFILE.toString());
						}
						cowFace = getCowFace(commandLine);
					}

					if (commandLine.hasOption(CowsayCli.Opt.THINK.toString())) {
						isThought = true;
					}

					if (cowfileSpec == null) {
						cowfileSpec = Cowloader.DEFAULT_COW;
					}

					String cowTemplate = Cowloader.load(cowfileSpec);
					if (cowTemplate != null) {
						String moosages[] = commandLine.getArgs();
						String moosage = StringUtils.join(moosages, " ");
						if (moosage != null && moosage.length() > 0) {
							Message message = new Message(moosage, isThought);
							if (wordwrap != null) {
								message.setWordwrap(wordwrap);
							}
							String cow = CowFormatter.formatCow(cowTemplate, cowFace, message);
							cow = formatHtml(commandLine, cow, moosage, isThought);
							return cow;
						}
					}
				}
			}
		}
		catch (CowParseException ex) {
			Logger.getLogger(Cowsay.class.getName()).log(Level.SEVERE, null, ex);
		}
		return "";
	}

	/**
	 * May apply HTML markup to the cow, if requested in the command line.
	 * @param commandLine The command line with user options.
	 * @param plainCow The cow formatted in plain text.
	 * @param moosage The message the cow is saying.
	 * @param isThought true if this is cowthink instead of cowsay.
	 * @return Either the plaintext cow or an HTML marked up version, depending on command line.
	 */
	private static String formatHtml(final CommandLine commandLine, final String plainCow, final String moosage,
									 final boolean isThought) {
		String cow = plainCow;
		if (commandLine.hasOption(CowsayCli.Opt.HTML.toString())) {
			cow = StringEscapeUtils.escapeHtml4(cow);
			cow = "

" + cow + "

";
			String alt;
			if (commandLine.hasOption(CowsayCli.Opt.ALT.toString())) {
				alt = commandLine.getOptionValue(CowsayCli.Opt.ALT.toString());
			}
			else {
				alt = isThought ? I18n.getMessage("altthink") : I18n.getMessage("altsay");

			}
			String escaped = StringEscapeUtils.escapeHtml4(moosage);
			cow += String.format(alt, escaped);
			cow += "

";
		}
		return cow;
	}

	/**
	 * If a pre-defined cow mode has been set on the command line then use that face.
	 * @param commandLine The command line with user options.
	 * @return The cowface for the mode selected on the command line or null if no mode set.
	 */
	private static CowFace getCowFaceByMode(final CommandLine commandLine) {
		CowFace cowFace = null;
		Set<String> modes = CowFace.COW_MODES.keySet();
		for (String mode : modes) {
			if (commandLine.hasOption(mode)) {
				cowFace = CowFace.getByMode(mode);
				break;
			}
		}
		return cowFace;
	}

	/**
	 * Get a regular cow face optionally formatted with custom eyes and tongue from the command line.
	 * @param commandLine The command line with user options.
	 * @return A regular cowface, possibly formatted with custom tongue and/or eyes.
	 */
	private static CowFace getCowFace(final CommandLine commandLine) {
		CowFace cowFace;
		cowFace = new CowFace();
		if (commandLine.hasOption(CowsayCli.Opt.EYES.toString())) {
			cowFace.setEyes(commandLine.getOptionValue(CowsayCli.Opt.EYES.toString()));
		}
		if (commandLine.hasOption(CowsayCli.Opt.TONGUE.toString())) {
			cowFace.setTongue(commandLine.getOptionValue(CowsayCli.Opt.TONGUE.toString()));
		}
		return cowFace;
	}

	/**
	 * Will cowsay (or cowthink if the --cowthink flag is set)
	 * @param args the command line arguments
	 */
	public static void main(final String[] args) {
		CowsayCli.addCowthinkOption();
		String cowsay = say(args);
		if (cowsay != null && cowsay.length() > 0) {
			System.out.println(cowsay);
		}
	}
}


package com.github.ricksbrown.cowsay;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * For a given cow template cleans comments, inserts message, cow face, removes header footer etc.
 *
 * @author Rick Brown
 */
public final class CowFormatter {

	private static final Pattern COWSTART_RE = Pattern.compile(".*\\$the_cow\\s*=\\s*<<\"?EOC\"?;?", Pattern.DOTALL);

	/**
	 * Utility class does not need constructor.
	 */
	private CowFormatter() {

	}

	/**
	 * Extracts the ascii art part of the cowfile, removing any before or after PERL comments, variable assignments, weird EOC markers etc.
	 * @param cow The raw cowfile content.
	 * @return The ascii art portion of the cowfile.
	 * @throws CowParseException if the cowfile cannot be parsed.
	 */
	private static String extractCowTemplate(final String cow) throws CowParseException {

		Matcher matcher = COWSTART_RE.matcher(cow);
		if (matcher.find(0)) {
			String result = matcher.replaceFirst("");
			return result;
		} else {
			throw new CowParseException("Could not parse cow " + cow);
		}
	}

	/**
	 * Processes the cow template (the raw content of a cowfile) inserting face and message as appropriate.
	 * @param cow The content of a cowfile.
	 * @param face The face to apply to this cow.
	 * @param message The message the cow is saying or thinking.
	 * @return The formatted cow.
	 * @throws CowParseException If the cow could not be parsed.
	 */
	public static String formatCow(final String cow, final CowFace face, final Message message) throws CowParseException {
		String result = extractCowTemplate(cow);
		String tongue = face.getTongue();
		String eyes = face.getEyes();
		result = result.replaceAll("\\\\\\\\", "\\\\");  // do this first
		result = result.replace("\\@", "@");
		result = result.replace("\\$", "$");
		result = result.replace("${tongue}", tongue);
		result = result.replace("$tongue", tongue);
		result = result.replace("$thoughts", message.getThoughts());
		result = result.replace("${eyes}", eyes);  // sheep - doesn't help that i know zero perl
		result = result.replace("$eyes", eyes);
		result = result.replaceAll("EOC\\s*$", "");
		result = message.getMessage() + result;
		return result;
	}
}


package com.github.ricksbrown.cowsay;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Loads a cow either from:
 *	a provided relative path,
 *  a provided absolute path,
 *	or a named cowfile found on the COWPATH environment variable,
 *	or a named cowfile found in the bundled cowfiles
 *	or the default cowfile.
 *
 * The cow will not be formatted or parsed, it will be the raw content loaded from the filesystem
 *    (though newlines will be replaced to platform default).
 * @author Rick Brown
 */
public final class Cowloader {
	/**
	 * The file extension of a cowfile.
	 */
	public static final String COWFILE_EXT = ".cow";

	/**
	 * The name of the default cowfile to use.
	 */
	public static final String DEFAULT_COW = "default";

	/**
	 * Utility classes do not need constructors.
	 */
	private Cowloader() {

	}

	/**
	 * Call this with the provided cowfileSpec - that is the value passed to `-f` on the commandline.
	 *
	 * @param cowfileSpec If cowfileSpec contains a filepath separator it is interpreted as relative to CWD
	 *    otherwise the COWPATH will be searched. If not found on the COWPATH we will search for a bundled cowfile.
	 * @return The content of the specified cowfile (or default cowfile if cowfileSpec is null or empty).
	 */
	public static String load(final String cowfileSpec) {
		String effectiveCowfileSpec = (cowfileSpec != null) ? cowfileSpec.trim() : DEFAULT_COW;
		if (effectiveCowfileSpec.length() > 0) {
			if (!effectiveCowfileSpec.endsWith(COWFILE_EXT)) {
				effectiveCowfileSpec += COWFILE_EXT;
			}
			InputStream cowInputStream;
			if (effectiveCowfileSpec.indexOf(File.separatorChar) >= 0) {
				cowInputStream = getCowFromPath(effectiveCowfileSpec);
			} else {
				cowInputStream = getCowFromCowPath(effectiveCowfileSpec);
			}
			if (cowInputStream == null) {
				// Maybe there should be a verbose mode where we log this sort of error instead of silently failing?
				cowInputStream = getCowFromResources(DEFAULT_COW + COWFILE_EXT);
			}
			if (cowInputStream != null) {
				String cow = cowInputStreamToString(cowInputStream);
				return cow;
			}
		}
		return null;  // should never happen
	}

	/**
	 * Reads a cowfile from an InputStream and returns a string.
	 * @param cowInputStream The InputStream instance to read into a String.
	 * @return A String representing the result of reading the entire InputStream.
	 */
	private static String cowInputStreamToString(final InputStream cowInputStream) {
		BufferedReader reader = new BufferedReader(new InputStreamReader(cowInputStream));
		StringBuilder sb = new StringBuilder();
		String line;
		try {
			String newLine = System.getProperty("line.separator");
			while ((line = reader.readLine()) != null) {
				sb.append(line);
				sb.append(newLine);
			}
			reader.close();
		} catch (IOException ex) {
			Logger.getLogger(Cowloader.class.getName()).log(Level.SEVERE, null, ex);
		} finally {
			if (cowInputStream != null) {
				try {
					cowInputStream.close();
				} catch (IOException ex) {
					Logger.getLogger(Cowloader.class.getName()).log(Level.SEVERE, null, ex);
				}
			}
		}
		return sb.toString();
	}

	/**
	 * In the case that the cowfileSpec is a filesystem path call this method to attempt to load the cowfile.
	 * It will attempt to load the cowfile relative to CWD and if that fails it will try as an absolute path.
	 * @param path A path to a cowfile either relative to CWD or an absolute path.
	 * @return An InputStream to the cowfile if it exists.
	 */
	private static InputStream getCowFromPath(final String path) {
		String cwd = System.getProperty("user.dir");  // TODO is this really CWD?
		if (cwd != null) {
			File cowfile = new File(cwd, path);
			if (isCowfile(cowfile)) {
				return cowfileToCowInputStream(cowfile);
			}
		}
		// maybe it's an absolute path?
		File cowfile = new File(path);
		if (isCowfile(cowfile)) {
			return cowfileToCowInputStream(cowfile);
		}
		return null;
	}

	/**
	 * This will attempt to load a cowfile, by name, from the COWPATH environment variable or bundled cowfiles.
	 * Note that bundled cowfiles are considered part of the COWPATH since this is how to original `cowsay` worked.
	 * COWPATH takes precedence and bundled cowfiles are only considered after searching the COWPATH.
	 *
	 * @param cowName The name of a cowfile, e.g. "sheep" or "sheep.cow".
	 * @return An InputStream to the first matching cowfile found.
	 */
	private static InputStream getCowFromCowPath(final String cowName) {
		String cowPath = System.getenv("COWPATH");
		if (cowPath != null) {
			String[] paths = cowPath.split(File.pathSeparator);
			if (paths != null) {
				for (String path : paths) {
					File cowfile = getCowfile(path, cowName);
					if (cowfile != null) {
						return cowfileToCowInputStream(cowfile);
					}
				}
			}
		}
		return getCowFromResources(cowName);
	}

	/**
	 * List the names of all cowfiles found when searching COWPATH (including bundled cowfiles).
	 * Primarily useful for the "-l" commandline flag and also handy for unit testing.
	 * @return The names of available cowfiles.
	 */
	public static String[] listAllCowfiles() {
		String[] resultAsArray;
		String[] bundled = null;
		String cowfileExtRe = "\\" + COWFILE_EXT + "$";
		InputStream bundleStream = Cowloader.class.getResourceAsStream("/cowfile-list.csv");
		if (bundleStream != null) {
			BufferedReader reader = new BufferedReader(new InputStreamReader(bundleStream));
			StringBuilder sb = new StringBuilder();
			try {
				String line;
				while ((line = reader.readLine()) != null) {
					sb.append(line);
				}
				reader.close();
				String bundleList = sb.toString();
				bundled = bundleList.split(",");
			} catch (IOException ex) {
				Logger.getLogger(Cowloader.class.getName()).log(Level.WARNING, null, ex);
			} finally {
				try {
					bundleStream.close();
				} catch (IOException ex) {
					Logger.getLogger(Cowloader.class.getName()).log(Level.SEVERE, null, ex);
				}
			}
		}

		Set<String> result = new HashSet<String>();
		if (bundled != null) {
			for (String cowfile : bundled) {
				if (cowfile.endsWith(COWFILE_EXT)) {  // mech-and-cow for example is not a cowfile and should be excluded
					result.add(cowfile.replaceAll(cowfileExtRe, ""));
				}
			}
		}
		String cowPath = System.getenv("COWPATH");
		if (cowPath != null) {
			String[] paths = cowPath.split(File.pathSeparator);
			if (paths != null) {
				for (String path : paths) {
					File[] cowfiles = getCowFiles(path);
					if (cowfiles != null) {
						for (File cowfile : cowfiles) {
							result.add(cowfile.getName().replaceAll(cowfileExtRe, ""));
						}
					}
				}
			}
		}
		resultAsArray = result.toArray(new String[result.size()]);
		Arrays.sort(resultAsArray);
		return resultAsArray;

	}

	/**
	 * Determine if this File appears to be a genuine cowfile.
	 * This is not a deep check, more rigor will be applied later.
	 * @param cowfile A potential cowfile.
	 * @return true if this File seems to be a cowfile.
	 */
	private static boolean isCowfile(final File cowfile) {
		if (cowfile != null && cowfile.exists()) {
			return cowfile.getName().endsWith(COWFILE_EXT);
		}
		return false;
	}

	/**
	 * Reads a File to an InputStream.
	 * Not sure why I thought this should be a separate method, I guess it made sense at the time.
	 * @param cowfile The cowfile to read.
	 * @return An InputStream which can be used to read the File.
	 */
	private static InputStream cowfileToCowInputStream(final File cowfile) {
		InputStream cowInputStream = null;
		try {
			cowInputStream = new FileInputStream(cowfile);
		} catch (FileNotFoundException ex) {
			Logger.getLogger(Cowloader.class.getName()).log(Level.SEVERE, null, ex);
		}
		return cowInputStream;
	}

	/**
	 * Get a cowfile, by name, from the bundled cowfiles.
	 * @param cowName The name of the cowfile to load.
	 * @return An InputStream which can be used to read the cowfile or null if not found.
	 */
	private static InputStream getCowFromResources(final String cowName) {
		return Cowloader.class.getResourceAsStream("/cows/" + cowName);
	}

	/**
	 * Get a cowfile, by name, from the given directory.
	 * @param folder The absolute path to the filesystem folder.
	 * @param cowName The name of the cowfile to load (without the ".cow" extension).
	 * @return A File if the cowfile is found in this directory otherwise null.
	 */
	private static File getCowfile(final String folder, final String cowName) {
		File[] cowfiles = getCowFiles(folder);
		for (File cowfile : cowfiles) {
			if (cowfile.getName().equals(cowName)) {
				return cowfile;
			}
		}
		return null;
	}

	/**
	 * Gets all cowfiles found in the given directory.
	 * @param folder The absolute path to the filesystem folder.
	 * @return A list of all cowfiles found in this directory.
	 */
	private static File[] getCowFiles(final String folder) {
		File dir = new File(folder);
		File[] files;
		files = dir.listFiles(new FilenameFilter() {
			@Override
			public boolean accept(final File dir, final String name) {
				return name.endsWith(".cow");
			}
		});
		return files;
	}
}


package com.github.ricksbrown.cowsay;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Contains the variables used to build a cow face.
 * @author Rick Brown
 */
public class CowFace {
	/**
	 * The various predefined modes built into cowsay, for example "borg" mode.
	 */
	public static final Map<String, CowFace> COW_MODES = new HashMap<String, CowFace>();

	private static final String DEFAULT_EYES = "oo";
	private static final String DEFAULT_TONGUE = "  ";

	private String eyes;  // they're watching you, they see your every move
	private String tongue;

	static {
		COW_MODES.put("b", new CowFace("=="));
		COW_MODES.put("d", new CowFace("xx", "U "));
		COW_MODES.put("g", new CowFace("$$"));
		COW_MODES.put("p", new CowFace("@@"));
		COW_MODES.put("s", new CowFace("**", "U "));
		COW_MODES.put("t", new CowFace("--"));
		COW_MODES.put("w", new CowFace("OO"));
		COW_MODES.put("y", new CowFace(".."));
	}

	/**
	 * Get a default cow face.
	 */
	public CowFace() {
		this(DEFAULT_EYES, DEFAULT_TONGUE);
	}

	/**
	 * Use custom cow eyes.
	 * Default tongue will be used.
	 * @param eyes The two characters to use as eyes.
	 */
	private CowFace(final String eyes) {
		this(eyes, DEFAULT_TONGUE);
	}

	/**
	 * Get a custom cow face!
	 * @param eyes The two characters to use as eyes.
	 * @param tongue The two characters to use as tongue.
	 */
	private CowFace(final String eyes, final String tongue) {
		setEyes(eyes);
		setTongue(tongue);
	}

	/**
	 * Get the character/s to use as eyes.
	 * @return The eyes.
	 */
	public String getEyes() {
		return this.eyes;
	}

	/**
	 * Get the character/s to use as tongue.
	 * @return The tongue.
	 */
	public String getTongue() {
		return this.tongue;
	}

	/**
	 * Get cow args by mode.
	 * @param mode A mode key, "b" for Borg, "d" for dead, "g" for greedy etc
	 * @return The CowArgs for the given mode or null if not found
	 */
	protected static CowFace getByMode(final String mode) {
		if (mode != null) {
			return COW_MODES.get(mode);
		}
		return null;
	}

	/**
	 * Set custom cow eyes.
	 * @param eyes The eyes to use - if more than two characters long the first two will be used.
	 */
	public final void setEyes(final String eyes) {
		if (eyes != null && eyes.length() > 0) {
			if (eyes.length() > 2) {
				this.eyes = eyes.substring(0, 2);
			} else {
				this.eyes = eyes;
			}
		}
	}

	/**
	 * Set custom cow tongue.
	 * @param tongue The tongue to use - if more than two characters long the first two will be used.
	 */
	public final void setTongue(final String tongue) {
		if (tongue != null && tongue.length() > 0) {
			if (tongue.length() > 2) {
				this.tongue = tongue.substring(0, 2);
			} else {
				this.tongue = tongue;
			}
		}
	}

	/**
	 * Determine if the given mode flag is known and mapped to a particular cow face.
	 * @param mode The key to the mode, generally a single character, e.g. "b" for "Borg" mode.
	 * @return true if this is a known mode.
	 */
	public static final boolean isKnownMode(final String mode) {
		Set<String> modes = COW_MODES.keySet();
		return modes.contains(mode);
	}
}


package com.github.ricksbrown.cowsay;

import java.io.File;
import java.util.Collection;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.MissingArgumentException;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

/**
 * Handles parsing of command line arguments.
 *
 * @author Rick Brown
 */
public class CowsayCli {

	private static final Options options;

	static {
		// Option descriptions are added later, if needed, in case the locale / language changes.
		options = new Options();
		options.addOption(Opt.NOWRAP.text, false, "");
		options.addOption(Opt.WRAP_AT.text, true, "");
		options.addOption(Opt.HELP.text, false, "");
		options.addOption(Opt.LIST_COWS.text, false, "");
		Set<String> modes = CowFace.COW_MODES.keySet();
		for (String mode : modes) {
			options.addOption(mode, false, "");
		}
		options.addOption(Opt.EYES.text, true, "");
		options.addOption(Opt.TONGUE.text, true, "");
		options.addOption(Opt.COWFILE.text, true, "");
		options.addOption(null, Opt.LANG.text, true, "");
		options.addOption(null, Opt.HTML.text, false, "");
		options.addOption(null, Opt.ALT.text, true, "");

		for (Option option : options.getOptions()) {
			option.setRequired(false);
		}
	}

	/**
	 * Command line argument constants.
	 */
	public enum Opt {
		NOWRAP("n"),
		WRAP_AT("W"),
		HELP("h"),
		LIST_COWS("l"),
		EYES("e"),
		TONGUE("T"),
		COWFILE("f"),
		LANG("lang"),
		THINK("cowthink"),
		ALT("alt"),
		HTML("html");

		final String text;

		Opt(final String text) {
			this.text = text;
		}

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

	/**
	 * Calling this method results in adding the non-standard "--cowthink" option to the accepted commandline flags.
	 * This is useful if calling cowsay as an executable JAR where there is no other way to determine a
	 *    `cowsay` or a `cowthink` invocation.
	 */
	protected static void addCowthinkOption() {
		if (!options.hasOption(null)) {
			options.addOption(null, Opt.THINK.text, false, "");
		}
	}

	/**
	 * Parses an array of arguments.
	 * @param argv Arguments array.
	 * @return The parsed arguments.
	 */
	public static CommandLine parseCmdArgs(final String[] argv) {
		final CommandLineParser cmdLineParser = new DefaultParser();

		try {
			CommandLine parsed = cmdLineParser.parse(options, argv, true);
			if (parsed.hasOption(Opt.LANG.text)) {
				String language = parsed.getOptionValue(Opt.LANG.text);
				if (language != null) {
					I18n.setLanguage(language);
				}
			}
			return parsed;
		}
		catch (MissingArgumentException ex) {
			Option option = ex.getOption();
			String flag = option.getOpt();
			if (flag == null) {
				flag = option.getLongOpt();
			}
			Logger.getLogger(CowsayCli.class.getName()).log(Level.INFO, I18n.getMessage("missingarg"), flag);
		}
		catch (ParseException ex) {
			Logger.getLogger(CowsayCli.class.getName()).log(Level.FINEST, null, ex);
		}
		return null;
	}

	/**
	 * Call to add options descriptions to the command line options.
	 * This is deferred so that language / locale changes will be reflected.
	 */
	private static void updateOptionDescriptions() {
		Collection<Option> allOptions = options.getOptions();
		for (Option option : allOptions) {
			String key = option.getOpt();
			if (key == null) {
				key = option.getLongOpt();
			}
			if (key != null) {
				String description = I18n.getMessage(key);
				if (description != null) {
					if (key.equals(Opt.COWFILE.text)) {
						description = String.format(description, File.separatorChar);
					}
					option.setDescription(description);
				}
			}
		}
	}

	/**
	 * Displays help message to user.
	 */
	public static void showCmdLineHelp() {
		HelpFormatter formatter = new HelpFormatter();
		updateOptionDescriptions();
		formatter.printHelp(I18n.getMessage("usage"), options);
	}
}


package com.github.ricksbrown.cowsay;
import org.apache.commons.lang3.StringUtils;

/**
 * Knows how to draw a speech or thought bubble around a message.
 *
 * @author Rick Brown
 */
public final class Bubble {
	private static final BubbleWrap SPEECH_BUBBLE;
	private static final BubbleWrap THOUGHT_BUBBLE;

	/**
	 * Utility class does not need a constructor.
	 */
	private Bubble() {

	}

	static {
		SPEECH_BUBBLE = new BubbleWrap();
		SPEECH_BUBBLE.setSingle('<', '>');
		SPEECH_BUBBLE.setMulti('/', '\\', '|', '|', '\\', '/');
		THOUGHT_BUBBLE = new BubbleWrap();
		THOUGHT_BUBBLE.setSingle('(', ')');
		THOUGHT_BUBBLE.setMulti('(', ')', '(', ')', '(', ')');
	}

	/**
	 * Draws a complete bubble around a line-wrapped message.
	 * @param bubble The BubbleWrap to use (e.g. SPEECH_BUBBLE or THOUGHT_BUBBLE)
	 * @param message The message to wrap, it should already be line-wrapped if appropriate.
	 * @param longestLine The length of the longest line in the message.
	 * @return The message, wrapped in a bubble.
	 */
	private static String formatBubble(final BubbleWrap bubble, final String message, final int longestLine) {
		String newLine = System.getProperty("line.separator");
		String[] lines = message.split(newLine);
		StringBuilder sb = new StringBuilder();
		sb.append(bubble.buildTop(longestLine));
		if (lines.length > 1) {
			sb.append(bubble.formatMultiOpen(lines[0], longestLine));
			for (int i = 1; i < (lines.length - 1); i++) {
				sb.append(bubble.formatMultiMid(lines[i], longestLine));
			}
			sb.append(bubble.formatMultiEnd(lines[(lines.length - 1)], longestLine));
		} else {
			sb.append(bubble.formatSingle(lines[0]));
		}
		sb.append(bubble.buildBottom(longestLine));
		return sb.toString();
	}

	/**
	 * Wraps the message in a speech bubble.
	 * @param message A message that has already been line wrapped (if necessary).
	 * @param longestLine The number of characters in the longest line of the message.
	 * @return The message, wrapped in a speech bubble.
	 */
	public static String formatSpeech(final String message, final int longestLine) {
		return formatBubble(SPEECH_BUBBLE, message, longestLine);
	}

	/**
	 * Wraps the message in a thought bubble.
	 * @param message A message that has already been line wrapped (if necessary).
	 * @param longestLine The number of characters in the longest line of the message.
	 * @return The message, wrapped in a thought bubble.
	 */
	public static String formatThought(final String message, final int longestLine) {
		return formatBubble(THOUGHT_BUBBLE, message, longestLine);
	}

	/**
	 * Instances of this class knows about bubble wrapping characters for a specific bubble type.
	 */
	private static class BubbleWrap {
		private String singleOpen;  // characters to use at start of a single line bubble
		private String singleClose;  // characters to use at end of a single line bubble
		private String multiStartOpen;  // characters to use at start of first line of a multi line bubble
		private String multiStartClose;  // characters to use at end of first line of a multi line bubble
		private String multiMidOpen;  // characters to use at start of ongoing lines of a multi line bubble
		private String multiMidClose;  // characters to use at end of ongoing lines of a multi line bubble
		private String multiEndOpen;  // characters to use at start of final line of a multi line bubble
		private String multiEndClose;  // characters to use at end of final line of a multi line bubble
		private final String newLine = System.getProperty("line.separator");

		/**
		 * Set the characters to use for a single line bubble.
		 * @param open The bubble character to use at the start of the line.
		 * @param close The bubble character to use at the end of the line.
		 */
		public void setSingle(final char open, final char close) {
			this.singleOpen = open + " ";
			this.singleClose = " " + close;
		}

		/**
		 * Set the characters to use for multi line bubbles.
		 * @param open The bubble character to use at the start of the first line.
		 * @param close The bubble character to use at the end of the first line.
		 * @param midOpen The character to use at the start of subsequent lines.
		 * @param midClose The character to use at the end of subsequent lines.
		 * @param endOpen The character to use at the start of the final line.
		 * @param endClose The character to use at the end of the final line.
		 */
		public void setMulti(final char open, final char close, final char midOpen, final char midClose, final char endOpen, final char endClose) {
			this.multiStartOpen = open + " ";
			this.multiStartClose = " " + close;
			this.multiMidOpen = midOpen + " ";
			this.multiMidClose = " " + midClose;
			this.multiEndOpen = endOpen + " ";
			this.multiEndClose = " " + endClose;
		}

		/**
		 * Build the line that forms the top of the bubble.
		 * @param longestLine The length of the longest line in the message.
		 * @return The top line.
		 */
		public String buildTop(final int longestLine) {
			String result = StringUtils.repeat('_', longestLine + 2);
			return " " + result + newLine;
		}

		/**
		 * Build the line that forms the bottom of the bubble.
		 * @param longestLine The length of the longest line in the message.
		 * @return The bottom line.
		 */
		public String buildBottom(final int longestLine) {
			String result = StringUtils.repeat('-', longestLine + 2);
			return " " + result;
		}

		/**
		 * Apply bubble formatting to a single line message.
		 * @param line The message line.
		 * @return The line with start and end characters.
		 */
		public String formatSingle(final String line) {
			String result = line;
			return singleOpen + result + singleClose + newLine;
		}

		/**
		 * Apply bubble formatting to a first line of a mutli line message.
		 * @param line The message line.
		 * @param padTo The minimum length the string should be after padding is applied.
		 * @return The line with start and end characters.
		 */
		public String formatMultiOpen(final String line, final int padTo) {
			String result = StringUtils.rightPad(line, padTo);
			return multiStartOpen + result + multiStartClose + newLine;
		}

		/**
		 * Apply bubble formatting to a subsequent line of a mutli line message.
		 * @param line The message line.
		 * @param padTo The minimum length the string should be after padding is applied.
		 * @return The line with start and end characters.
		 */
		public String formatMultiMid(final String line, final int padTo) {
			String result = StringUtils.rightPad(line, padTo);
			return multiMidOpen + result + multiMidClose + newLine;
		}

		/**
		 * Apply bubble formatting to a last line of a mutli line message.
		 * @param line The message line.
		 * @param padTo The minimum length the string should be after padding is applied.
		 * @return The line with start and end characters.
		 */
		public String formatMultiEnd(final String line, final int padTo) {
			String result = StringUtils.rightPad(line, padTo);
			return multiEndOpen + result + multiEndClose + newLine;
		}
	}
}


package com.github.ricksbrown.cowsay;

import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.WordUtils;

/**
 * Represents a "bubble-wrapped" and "line-wrapped" message portion of the cowsay / cowthink output.
 * This is the user provided message line wrapped (if appropriate) and formatted with a thought or speech bubble.
 *
 * @author Rick Brown
 */
public class Message {
	/**
	 * The default wrap point for long lines.
	 */
	public static final byte DEFAULT_WRAP = 40;
	private static final String SAY_TOKEN = "\\";
	private static final String THINK_TOKEN = "o";
	private int wordwrap = -1;

	private final String message;
	private final boolean isThought;

	/**
	 * Create the formatted message.
	 * @param message The user provided message to format.
	 * @param isThought true if this should be formatted as thought instead of speech.
	 */
	public Message(final String message, final boolean isThought) {
		this.isThought = isThought;
		this.message = message;
	}

	/**
	 * Get the formatted message.
	 * @return The message formatted with speech or thought bubble.
	 */
	public String getMessage() {
		return formatMessage(this.message);
	}

	/**
	 * Get the character/s to use for the lines going from the cow to the bubble.
	 * @return The correct character/s to use for `$thoughts`.
	 */
	public String getThoughts() {
		return this.isThought ? THINK_TOKEN : SAY_TOKEN;
	}

	/**
	 * Applies word wrapping to the message to handle long lines.
	 * @param message The raw input message.
	 * @return The message with long lines wrapped.
	 */
	private String wrapMessage(final String message) {
		// Note that the original cowsay wraps lines mid-word.
		// This version differs in that it wraps between words if possible.
		int wrap = getWordwrap();
		if (wrap <= 0) {
			return message;
		}
		final List<String> result = new ArrayList<String>();
		String newLine = System.getProperty("line.separator");
		String[] lines = message.split(newLine);
		for (String line : lines) {
			result.add(WordUtils.wrap(line, wrap, null, true));
		}
		return StringUtils.join(result, newLine);
	}

	/**
	 * Builds the bubble around the message.
	 * @param message The plain message as provided by the user.
	 * @return The message, line-wrapped and bubble-wrapped.
	 */
	private String formatMessage(final String message) {
		String result;
		if (message != null) {
			result = wrapMessage(message);
			int longestLine = getLongestLineLen(result);
			if (!isThought) {
				result = Bubble.formatSpeech(result, longestLine);
			} else {
				result = Bubble.formatThought(result, longestLine);
			}
			return result;
		}
		return "";
	}

	/**
	 * Set the length of the wordwrap, default is "40", zero disables line-wrap.
	 * @param wordwrap A number indicating where (approximately) to line-wrap the message.
	 */
	public void setWordwrap(final String wordwrap) {
		try {
			int ww = Integer.parseInt(wordwrap);
			if (ww >= 0) {
				this.wordwrap = ww;
			}
		} catch(Throwable ignore) {
			// ignore
		}
	}

	/**
	 * Get the current value used for line wrap length.
	 * @return The current line-wrap value.
	 */
	public int getWordwrap() {
		if (this.wordwrap >= 0) {
			return this.wordwrap;
		}
		return DEFAULT_WRAP;
	}

	/**
	 * For a given multiline message determines the character count of the longest line.
	 * @param message The message after line-wrap has been applied but before any bubble wrapping.
	 * @return The count of characters in the longest line.
	 */
	private static int getLongestLineLen(final String message) {
		String newLine = System.getProperty("line.separator");
		String[] lines = message.split(newLine);
		int maxLen = 0;
		for (String line : lines) {
			maxLen = Math.max(maxLen, line.length());
		}
		return maxLen;
	}
}


/**
 * Copyright (C) 2015 Anthony K. Trinh
 *
 * 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.github.tony19.loggly;

import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

import java.util.Arrays;
import java.util.Collection;

/**
 * Loggly client
 *
 * @author tony19@gmail.com
 */
public class LogglyClient implements ILogglyClient {
    private static final String API_URL = "https://logs-01.loggly.com/";
    private final ILogglyRestService loggly;
    private final String token;
    private String tags;

    /**
     * Creates a Loggly client
     * @param token Loggly customer token
     *              http://loggly.com/docs/customer-token-authentication-token/
     */
    public LogglyClient(String token) {
        if (token == null || token.isEmpty()) {
            throw new IllegalArgumentException("token cannot be empty");
        }

        Retrofit restAdapter = new Retrofit.Builder()
                .baseUrl(API_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        this.token = token;
        this.loggly = restAdapter.create(ILogglyRestService.class);
    }

    /**
     * Creates a Loggly client with the specified REST API.
     * This is package private for internal testing only.
     * @param token Loggly customer token
     * @param restApi implementation of {@link ILogglyRestService}
     */
    LogglyClient(String token, ILogglyRestService restApi) {
        this.token = token;
        this.loggly = restApi;
    }

    /**
     * Sets the tags to use for Loggly messages. The list of
     * strings are converted into a single CSV (trailing/leading
     * spaces stripped from each entry).
     * @param tags CSV or list of tags
     */
    public void setTags(String... tags) {
        StringBuilder builder = new StringBuilder();
        boolean first = true;
        for (String s : tags) {
            for (String t : s.split(",")) {
                 t = t.trim();
                 if (!t.isEmpty()) {
                     if (!first) {
                         builder.append(",");
                     }
                     builder.append(t);
                 }
                 first = false;
            }
        }
        // "tags" field must be null for Retrofit to exclude Loggly tags header.
        // Empty header string is not acceptable.
        this.tags = builder.length() > 0 ? builder.toString() : null;
    }

    /**
     * Posts a log message to Loggly
     * @param message message to be logged
     * @return {@code true} if successful; {@code false} otherwise
     */
    public boolean log(String message) {
        if (message == null) return false;

        boolean ok;
        try {
            ok = loggly.log(token, tags, message).isExecuted();
        } catch (Exception e) {
            e.printStackTrace();
            ok = false;
        }
        return ok;
    }

    /**
     * Posts a log message asynchronously to Loggly
     * @param message message to be logged
     * @param callback callback to be invoked on completion of the post
     */
    public void log(String message, final Callback callback) {
        if (message == null) return;

        loggly.log(token,
                tags,
                message,
                new retrofit2.Callback<LogglyResponse>() {
                    @Override
                    public void onResponse(Call<LogglyResponse> call, Response<LogglyResponse> response) {
                        callback.success();
                    }

                    @Override
                    public void onFailure(Call<LogglyResponse> call, Throwable throwable) {
                        callback.failure(throwable.getMessage());
                    }
                });
    }

    /**
     * Posts several log messages in bulk to Loggly
     * @param messages messages to be logged
     * @return {@code true} if successful; {@code false} otherwise
     */
    public boolean logBulk(String... messages) {
        if (messages == null) return false;
        return logBulk(Arrays.asList(messages));
    }

    /**
     * Posts several log messages in bulk to Loggly
     * @param messages messages to be logged
     * @return {@code true} if successful; {@code false} otherwise
     */
    public boolean logBulk(Collection<String> messages) {
        if (messages == null) return false;

        String parcel = joinStrings(messages);
        if (parcel.isEmpty()) return false;

        boolean ok;
        try {
            ok = loggly.logBulk(token, tags, parcel).isExecuted();
        } catch (Exception e) {
            e.printStackTrace();
            ok = false;
        }
        return ok;
    }

    /**
     * Posts several log messages in bulk to Loggly asynchronously
     * @param messages messages to be logged
     * @param callback callback to be invoked on completion of the post
     */
    public void logBulk(Collection<String> messages, final Callback callback) {
        if (messages == null) return;

        String parcel = joinStrings(messages);
        if (parcel.isEmpty()) return;

        loggly.logBulk(token,
                tags,
                parcel,
                new retrofit2.Callback<LogglyResponse>() {
                    @Override
                    public void onResponse(Call<LogglyResponse> call, Response<LogglyResponse> response) {
                        callback.success();
                    }

                    @Override
                    public void onFailure(Call<LogglyResponse> call, Throwable throwable) {
                        callback.failure(throwable.getMessage());
                    }
                });
    }

    /**
     * Combines a collection of messages to be sent to Loggly.
     * In order to preserve event boundaries, the new lines in
     * each message are replaced with '\r', which get stripped
     * by Loggly.
     * @param messages messages to be combined
     * @return a single string containing all the messages
     */
    private String joinStrings(Collection<String> messages) {
        StringBuilder b = new StringBuilder();
        for (String s : messages) {
            if (s == null || s.isEmpty()) {
                continue;
            }

            // Preserve new-lines in this event by replacing them
            // with "\r". Otherwise, they're processed as event
            // delimiters, resulting in unintentional multiple events.
            b.append(s.replaceAll("[\r\n]", "\r")).append('\n');
        }
        return b.toString();
    }
}