/*
 * Copyright 2008-2016 Brian Thomas Matthews
 *
 * 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.btmatthews.maven.plugins.ldap.apache;

import com.btmatthews.maven.plugins.ldap.AbstractLDAPServer;
import com.btmatthews.utils.monitor.Logger;
import org.apache.directory.server.core.DefaultDirectoryService;
import org.apache.directory.server.core.DirectoryService;
import org.apache.directory.server.core.authn.AuthenticationInterceptor;
import org.apache.directory.server.core.entry.ServerEntry;
import org.apache.directory.server.core.exception.ExceptionInterceptor;
import org.apache.directory.server.core.interceptor.Interceptor;
import org.apache.directory.server.core.normalization.NormalizationInterceptor;
import org.apache.directory.server.core.operational.OperationalAttributeInterceptor;
import org.apache.directory.server.core.partition.Partition;
import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
import org.apache.directory.server.core.referral.ReferralInterceptor;
import org.apache.directory.server.core.subtree.SubentryInterceptor;
import org.apache.directory.server.ldap.LdapServer;
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
import org.apache.directory.shared.ldap.exception.LdapNameNotFoundException;
import org.apache.directory.shared.ldap.name.LdapDN;

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

/**
 * Implements an embedded ApacheDS LDAP apache.
 *
 * @author Brian Matthews
 * @since 1.1.0
 */
public final class ApacheDSServer extends AbstractLDAPServer {

    /**
     * The LDAP directory service.
     */
    private DirectoryService service;
    /**
     * The server that listens for LDAP requests and forwards them to the directory service.
     */
    private LdapServer server;

    /**
     * Configure and start the embedded ApacheDS server creating the root DN and loading the LDIF seed data.
     *
     * @param logger Used to log informational and error messages.
     */
    @Override
    public void start(final Logger logger) {
        try {
            logger.logInfo("Starting ApacheDS server");
            service = new DefaultDirectoryService();

            final List<Interceptor> list = new ArrayList<Interceptor>();

            list.add(new NormalizationInterceptor());
            list.add(new AuthenticationInterceptor());
            list.add(new ReferralInterceptor());
            // list.add( new AciAuthorizationInterceptor() );
            // list.add( new DefaultAuthorizationInterceptor() );
            list.add(new ExceptionInterceptor());
            // list.add( new ChangeLogInterceptor() );
            list.add(new OperationalAttributeInterceptor());
            // list.add( new SchemaInterceptor() );
            list.add(new SubentryInterceptor());
            // list.add( new CollectiveAttributeInterceptor() );
            // list.add( new EventInterceptor() );
            // list.add( new TriggerInterceptor() );
            // list.add( new JournalInterceptor() );
            service.setInterceptors(list);
            service.setWorkingDirectory(getWorkingDirectory());

            final JdbmPartition partition = new JdbmPartition();
            partition.setId("rootPartition");
            partition.setSuffix(getRoot());
            service.addPartition(partition);
            service.setExitVmOnShutdown(false);
            service.setShutdownHookEnabled(false);
            service.getChangeLog().setEnabled(false);
            service.setDenormalizeOpAttrsEnabled(true);

            server = new LdapServer();
            server.setDirectoryService(service);
            server.setTransports(new TcpTransport(getServerPort()));

            service.startup();
            server.start();

            createRoot(partition);

            loadLdifFile();

            logger.logInfo("Started ApacheDS server");

        } catch (final Exception e) {
            logger.logError("Error starting ApacheDS server e");
        }
    }

    /**
     * Shutdown the the embedded ApacheDS server.
     *
     * @param logger Used to log informational and error messages.
     */
    @Override
    public void stop(final Logger logger) {
        try {
            logger.logInfo("Stopping ApacheDS server");
            server.stop();
            service.shutdown();
            logger.logInfo("Stopped ApacheDS server");
        } catch (final Exception e) {
            logger.logError("Error stopping ApacheDS server", e);
        }
    }

    @Override
    public boolean isStarted(final Logger logger) {
        return server != null && server.isStarted();
    }

    @Override
    public boolean isStopped(final Logger logger) {
        return server == null || !server.isStarted();
    }

    /**
     * Create the root DN.
     *
     * @param partition The partition in which to create the root DN.
     * @throws Exception If there was an error creating the root DN.
     */

    private void createRoot(final Partition partition) throws Exception {
        try {
            service.getAdminSession().lookup(partition.getSuffixDn());
        } catch (final LdapNameNotFoundException e) {
            final LdapDN dn = new LdapDN(getRoot());
            final String dc = getRoot().substring(3, getRoot().indexOf(','));
            final ServerEntry entry = service.newEntry(dn);
            entry.add("objectClass", "top", "domain", "extensibleObject");
            entry.add("dc", dc);
            service.getAdminSession().add(entry);
        }
    }

    /**
     * Load the LDIF file used to seed the directory.
     *
     * @throws Exception If there was an error.
     */
    private void loadLdifFile() throws Exception {
        new LdifFileLoader(service.getAdminSession(), getLdifFile(), null)
                .execute();
    }
}


/*
 * Copyright 2008-2016 Brian Thomas Matthews
 *
 * 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.btmatthews.maven.plugins.ldap.opendj;

import com.btmatthews.maven.plugins.ldap.AbstractLDAPServer;
import com.btmatthews.utils.monitor.Logger;
import org.forgerock.opendj.ldap.*;
import org.forgerock.opendj.ldif.LDIFEntryReader;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Implements an in-memory LDAP directory server using OpenDJ SDK. See
 * here
 * for original code.
 *
 * @author Brian Matthews
 * @since 1.1.1
 */
public final class OpenDJServer extends AbstractLDAPServer {

    /**
     * The listener that accepts and dispatches LDAP requests.
     */
    private LDAPListener listener;

    /**
     * Start the OpenDJ in-memory directory server.
     *
     * @param logger Used for logging.
     */
    @Override
    public void start(final Logger logger) {
        try {
            logger.logInfo("Starting OpenDJ server");
            final MemoryBackend backend;
            if (getLdifFile() == null) {
                backend = new MemoryBackend();
            } else {
                final InputStream inputStream = new FileInputStream(getLdifFile());
                final LDIFEntryReader reader = new LDIFEntryReader(inputStream);
                backend = new MemoryBackend(reader);
            }
            final ServerConnectionFactory<LDAPClientContext, Integer> connectionHandler = Connections.newServerConnectionFactory(backend);
            final LDAPListenerOptions options = new LDAPListenerOptions().setBacklog(4096);
            listener = new LDAPListener("localhost", getServerPort(), connectionHandler, options);
            logger.logInfo("Started OpenDJ server");
        } catch (final IOException e) {
            logger.logError("Error starting OpenDJ server", e);
        }
    }

    /**
     * Stop the OpenDJ in-memory directory server.
     *
     * @param logger User for logging.
     */
    @Override
    public void stop(final Logger logger) {
        logger.logInfo("Stopping OpenDJ server");
        if (listener != null) {
            listener.close();
        }
        logger.logInfo("Stopped OpenDJ server");
    }
}


/*
 * Copyright 2013-2016 Brian Thomas Matthews
 *
 * 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.btmatthews.maven.plugins.ldap.dsml;

import com.btmatthews.maven.plugins.ldap.FormatWriter;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.util.ByteStringBuffer;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;

/**
 * A {@link FormatWriter} that outputs LDAP directory entries to DSML formatted files.
 *
 * @author Brian Matthews
 * @since 1.2.0
 */
public final class DSMLFormatWriter implements FormatWriter {

    /**
     * The underlying output stream.
     */
    private final OutputStream ldifOutputStream;
    /**
     * The end of line marker.
     */
    private final String eol;

    /**
     * Create {@link PrintWriter} that will be used to write the directory entries to the output stream and write the
     * opening <dsml:dsml;> and <dsml:directory-entry;> tags.
     *
     * @param outputStream The file output stream.
     * @throws IOException If there was a problem writing the openng tags.
     */
    public DSMLFormatWriter(final OutputStream outputStream) throws IOException {
        ldifOutputStream = outputStream;
        eol = System.getProperty("line.separator", "\n");
        final ByteStringBuffer buffer = new ByteStringBuffer();
        buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        buffer.append(eol);
        buffer.append("<dsml:dsml xmlns:dsml=\"http://www.dsml.org/DSML\">");
        buffer.append(eol);
        buffer.append("\t<dsml:directory-entries>");
        buffer.append(eol);
        ldifOutputStream.write(buffer.toByteArray());
    }

    /**
     * Writes an individual LDAP directory entry to the DSML file.
     *
     * @param entry The directory entry.
     * @throws IOException If there was a problem writing to the underlying output stream.
     */
    public void printEntry(final Entry entry) throws IOException {
        final ByteStringBuffer buffer = new ByteStringBuffer();
        buffer.append("\t\t<dsml:entry dn=\"");
        buffer.append(entry.getDN());
        buffer.append("\">");
        buffer.append(eol);
        final String[] values = entry.getAttributeValues("objectclass");
        if (values != null) {
            buffer.append("\t\t\t<dsml:objectclass>");
            buffer.append(eol);
            for (final String value : values) {
                buffer.append("\t\t\t\t<dsml:oc-value>");
                buffer.append(value);
                buffer.append("</dsml:oc-value>");
                buffer.append(eol);
            }
            buffer.append("\t\t\t</dsml:objectclass>");
            buffer.append(eol);
        }
        for (final Attribute attribute : entry.getAttributes()) {
            final String name = attribute.getName();
            if (!name.equals("objectclass")) {
                buffer.append("\t\t\t<dsml:attr name=\"");
                buffer.append(name);
                buffer.append("\">");
                buffer.append(eol);
                for (final String value : attribute.getValues()) {
                    buffer.append("\t\t\t\t<dsml:value>");
                    buffer.append(value);
                    buffer.append("</dsml:value>");
                    buffer.append(eol);
                }
                buffer.append("\t\t\t</dsml:attr>");
                buffer.append(eol);
            }
        }
        buffer.append("\t\t</dsml:entry>");
        buffer.append(eol);
        ldifOutputStream.write(buffer.toByteArray());
    }

    /**
     * Close the {@link FormatWriter} writing the closing tags.
     *
     * @throws IOException If there was a problem writing to the underlying output stream.
     */
    public void close() throws IOException {
        final ByteStringBuffer buffer = new ByteStringBuffer();
        buffer.append("\t</dsml:directory-entries>\n");
        buffer.append(eol);
        buffer.append("</dsml:dsml>");
        buffer.append(eol);
        ldifOutputStream.write(buffer.toByteArray());
    }
}


/*
 * Copyright 2008-2016 Brian Thomas Matthews
 *
 * 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.btmatthews.maven.plugins.ldap.mojo;

import com.btmatthews.maven.plugins.ldap.LDAPServer;
import com.btmatthews.utils.monitor.mojo.AbstractRunMojo;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

/**
 * This Mojo implements the run goal which launches an embedded LDAP
 * directory server.
 *
 * @author Brian Matthews
 * @since 1.1.0
 */
@Mojo(
        name = "run",
        defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST,
        configurator = "include-server-dependencies")
public final class RunLDAPMojo extends AbstractRunMojo {

    /**
     * The server can be one of:
     *

     *
apacheds
 - Use ApacheDS 1.5.5
     *
opendj
 - Use OpenDJ 2.6.0
     *
unboundid
 - Use UnboundID 3.1.0
     *
dependency-[serverType]
 - Use custom server included as plugin dependency and identified by serverType
     *
dependency-[serverType]-[groupId]:[artifactId]:[version]
 - Use custom server specified by the GAV coordinates and identified by serverType
     *

     */
    @Parameter(property = "ldap.type", defaultValue = "unboundid")
    private String serverType;
    /**
     * The identity of the admin account for tbe directory server.
     */
    @Parameter(property = "ldap.authDn", defaultValue = "uid=admin,ou=system")
    private String authDn;
    /**
     * The credentials for the admin account of the directory server.
     */
    @Parameter(property = "ldap.passwd", defaultValue = "secret")
    private String passwd;
    /**
     * The root DN for the LDAP server.
     */
    @Parameter(property = "ldap.root", required = true)
    private String rootDn;
    /**
     *
     */
    @Parameter(property = "ldap.objectClasses", required = false)
    private String objectClasses;
    /**
     * An optional LDIF file that can be used to seed the embedded LDAP server.
     */
    @Parameter(property = "ldap.ldif", required = false)
    private File ldifFile;
    /**
     * The port for the LDAP server.
     */
    @Parameter(property = "ldap.port", defaultValue = "389")
    private int ldapPort;
    /**
     * The build target directory.
     */
    @Parameter(defaultValue = "${project.build.directory}", required = true)
    private File outputDirectory;

    /**
     * Get the server type.
     *
     * @return The server type.
     */
    @Override
    public String getServerType() {
        if (serverType.startsWith("dependency-")) {
            return serverType.substring(11);
        }
        if (serverType.startsWith("gav-")) {
            int index = serverType.indexOf("-", 4);
            if (index > 0) {
                return serverType.substring(4, index);
            }
        }
        return serverType;
    }

    /**
     * Get the embedded LDAP directory server configuration.
     *
     * @return A {@link Map} containing the server configuration.
     */
    @Override
    public Map<String, Object> getServerConfig() {
        final Map<String, Object> config = new HashMap<String, Object>();
        config.put(LDAPServer.ROOT, rootDn);
        if (objectClasses != null) {
            config.put(LDAPServer.OBJECT_CLASSES, objectClasses.split(","));
        }
        config.put(LDAPServer.WORK_DIR, new File(outputDirectory, serverType));
        if (ldifFile != null) {
            config.put(LDAPServer.LDIF_FILE, ldifFile);
        }
        config.put(LDAPServer.LDAP_PORT, ldapPort);
        config.put(LDAPServer.AUTH_DN, authDn);
        config.put(LDAPServer.PASSWD, passwd);
        return config;
    }
}


/*
 * Copyright (c) 2014-2015 University of Ulm
 *
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership.  Licensed under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package de.uniulm.omi.cloudiator.visor.reporting.kairos;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.MoreObjects;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import de.uniulm.omi.cloudiator.visor.exceptions.ConfigurationException;
import de.uniulm.omi.cloudiator.visor.monitoring.Metric;
import de.uniulm.omi.cloudiator.visor.reporting.ReportingException;
import de.uniulm.omi.cloudiator.visor.reporting.ReportingInterface;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.Collection;
import org.kairosdb.client.HttpClient;
import org.kairosdb.client.builder.MetricBuilder;
import org.kairosdb.client.response.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A concrete implementation of the reporting interface, reporting metrics to the kairos database.
 */
public class KairosDb implements ReportingInterface<Metric> {

  /**
   * A logger.
   */
  private static final Logger LOGGER = LoggerFactory.getLogger(KairosDb.class);
  /**
   * The server of the kairos db.
   */
  protected final String server;
  /**
   * The port of the kairos db.
   */
  protected final int port;

  @Inject
  public KairosDb(@Named("kairosServer") String server, @Named("kairosPort") int port) {
    checkNotNull(server);
    checkArgument(!server.isEmpty(), "Server must not be empty.");
    checkArgument(port > 0, "Port must be >0");
    this.server = server;
    this.port = port;
  }

  /**
   * Sends a metric to the kairos server.
   *
   * @param metricBuilder the metricbuilder containing the metrics.
   * @throws de.uniulm.omi.cloudiator.visor.reporting.ReportingException If the kairos server could
   * not be reached.
   */
  protected void sendMetric(MetricBuilder metricBuilder) throws ReportingException {
    try {
      HttpClient client = new HttpClient("http://" + this.server + ":" + this.port);
      Response response = client.pushMetrics(metricBuilder);

      //check if response is ok
      if (response.getStatusCode() / 100 != 2) {
        LOGGER.error("Kairos DB reported error. Status code: " + response.getStatusCode());
        LOGGER.error("Error message: " + response.getErrors());
        throw new ReportingException();
      } else {
        LOGGER.debug("Kairos DB returned OK. Status code: " + response.getStatusCode());
      }
      client.shutdown();
    } catch (MalformedURLException | URISyntaxException e) {
      throw new ConfigurationException(e);
    } catch (IOException e) {
      LOGGER.error("Could not request KairosDB.", e);
      throw new ReportingException(e);
    }
  }

  /**
   * Report method. Converts the given metric to a metric kairos understands and sends them.
   *
   * @param metric the metric to report.
   * @throws ReportingException of the metric could not be converted. or sent to kairos.
   */
  @Override
  public void report(Metric metric) throws ReportingException {

    KairosMetricConverter kairosMetricConverter = new KairosMetricConverter();
    try {
      kairosMetricConverter.add(metric);
    } catch (KairosMetricConversionException e) {
      throw new ReportingException(e);
    }
    this.sendMetric(kairosMetricConverter.convert());
  }

  /**
   * Report method for multiple metrics. Converts the metrics to metrics kairos can understand and
   * sends them.
   *
   * @param metrics a collection of metrics.
   * @throws ReportingException if the metric could not be converted or sent to kairos.
   */
  @Override
  public void report(Collection<Metric> metrics) throws ReportingException {
    KairosMetricConverter kairosMetricConverter = new KairosMetricConverter();
    for (Metric metric : metrics) {
      try {
        kairosMetricConverter.add(metric);
      } catch (KairosMetricConversionException e) {
        throw new ReportingException(e);
      }
    }
    this.sendMetric(kairosMetricConverter.convert());
  }

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this).add("server", server).add("port", port).toString();
  }
}


package mil.nga.sf.wkb;

import java.nio.ByteOrder;

import mil.nga.sf.CircularString;
import mil.nga.sf.CompoundCurve;
import mil.nga.sf.Curve;
import mil.nga.sf.CurvePolygon;
import mil.nga.sf.Geometry;
import mil.nga.sf.GeometryCollection;
import mil.nga.sf.GeometryType;
import mil.nga.sf.LineString;
import mil.nga.sf.MultiLineString;
import mil.nga.sf.MultiPoint;
import mil.nga.sf.MultiPolygon;
import mil.nga.sf.Point;
import mil.nga.sf.Polygon;
import mil.nga.sf.PolyhedralSurface;
import mil.nga.sf.TIN;
import mil.nga.sf.Triangle;
import mil.nga.sf.util.ByteReader;
import mil.nga.sf.util.SFException;

/**
 * Well Known Binary reader
 *
 * @author osbornb
 */
public class GeometryReader {

	/**
	 * 2.5D bit
	 */
	private static final long WKB25D = Long.decode("0x80000000");

	/**
	 * Read a geometry from the byte reader
	 *
	 * @param reader
	 * @return geometry
	 */
	public static Geometry readGeometry(ByteReader reader) {
		Geometry geometry = readGeometry(reader, null);
		return geometry;
	}

	/**
	 * Read a geometry from the byte reader
	 *
	 * @param reader
	 * @param expectedType
	 * @return geometry
	 */
	public static <T extends Geometry> T readGeometry(ByteReader reader,
			Class<T> expectedType) {

		ByteOrder originalByteOrder = reader.getByteOrder();

		// Read the byte order and geometry type
		GeometryTypeInfo geometryTypeInfo = readGeometryType(reader);

		GeometryType geometryType = geometryTypeInfo.getGeometryType();
		boolean hasZ = geometryTypeInfo.hasZ();
		boolean hasM = geometryTypeInfo.hasM();

		Geometry geometry = null;

		switch (geometryType) {

		case GEOMETRY:
			throw new SFException("Unexpected Geometry Type of "
					+ geometryType.name() + " which is abstract");
		case POINT:
			geometry = readPoint(reader, hasZ, hasM);
			break;
		case LINESTRING:
			geometry = readLineString(reader, hasZ, hasM);
			break;
		case POLYGON:
			geometry = readPolygon(reader, hasZ, hasM);
			break;
		case MULTIPOINT:
			geometry = readMultiPoint(reader, hasZ, hasM);
			break;
		case MULTILINESTRING:
			geometry = readMultiLineString(reader, hasZ, hasM);
			break;
		case MULTIPOLYGON:
			geometry = readMultiPolygon(reader, hasZ, hasM);
			break;
		case GEOMETRYCOLLECTION:
		case MULTICURVE:
		case MULTISURFACE:
			geometry = readGeometryCollection(reader, hasZ, hasM);
			break;
		case CIRCULARSTRING:
			geometry = readCircularString(reader, hasZ, hasM);
			break;
		case COMPOUNDCURVE:
			geometry = readCompoundCurve(reader, hasZ, hasM);
			break;
		case CURVEPOLYGON:
			geometry = readCurvePolygon(reader, hasZ, hasM);
			break;
		case CURVE:
			throw new SFException("Unexpected Geometry Type of "
					+ geometryType.name() + " which is abstract");
		case SURFACE:
			throw new SFException("Unexpected Geometry Type of "
					+ geometryType.name() + " which is abstract");
		case POLYHEDRALSURFACE:
			geometry = readPolyhedralSurface(reader, hasZ, hasM);
			break;
		case TIN:
			geometry = readTIN(reader, hasZ, hasM);
			break;
		case TRIANGLE:
			geometry = readTriangle(reader, hasZ, hasM);
			break;
		default:
			throw new SFException("Geometry Type not supported: "
					+ geometryType);
		}

		// If there is an expected type, verify the geometry if of that type
		if (expectedType != null && geometry != null
				&& !expectedType.isAssignableFrom(geometry.getClass())) {
			throw new SFException("Unexpected Geometry Type. Expected: "
					+ expectedType.getSimpleName() + ", Actual: "
					+ geometry.getClass().getSimpleName());
		}

		// Restore the byte order
		reader.setByteOrder(originalByteOrder);

		@SuppressWarnings("unchecked")
		T result = (T) geometry;

		return result;
	}

	/**
	 * Read the geometry type info
	 *
	 * @param reader
	 *            byte reader
	 * @return geometry type info
	 */
	public static GeometryTypeInfo readGeometryType(ByteReader reader) {

		// Read the single byte order byte
		byte byteOrderValue = reader.readByte();
		ByteOrder byteOrder = byteOrderValue == 0 ? ByteOrder.BIG_ENDIAN
				: ByteOrder.LITTLE_ENDIAN;
		reader.setByteOrder(byteOrder);

		// Read the geometry type unsigned integer
		long unsignedGeometryTypeCode = reader.readUnsignedInt();

		// Check for 2.5D geometry types
		boolean hasZ = false;
		if (unsignedGeometryTypeCode > WKB25D) {
			hasZ = true;
			unsignedGeometryTypeCode -= WKB25D;
		}

		int geometryTypeCode = (int) unsignedGeometryTypeCode;

		// Determine the geometry type
		GeometryType geometryType = GeometryCodes
				.getGeometryType(geometryTypeCode);

		// Determine if the geometry has a z (3d) or m (linear referencing
		// system) value
		if (!hasZ) {
			hasZ = GeometryCodes.hasZ(geometryTypeCode);
		}
		boolean hasM = GeometryCodes.hasM(geometryTypeCode);

		GeometryTypeInfo geometryInfo = new GeometryTypeInfo(geometryTypeCode,
				geometryType, hasZ, hasM);

		return geometryInfo;
	}

	/**
	 * Read a Point
	 *
	 * @param reader
	 * @param hasZ
	 * @param hasM
	 * @return point
	 */
	public static Point readPoint(ByteReader reader, boolean hasZ, boolean hasM) {

		double x = reader.readDouble();
		double y = reader.readDouble();

		Point point = new Point(hasZ, hasM, x, y);

		if (hasZ) {
			double z = reader.readDouble();
			point.setZ(z);
		}

		if (hasM) {
			double m = reader.readDouble();
			point.setM(m);
		}

		return point;
	}

	/**
	 * Read a Line String
	 *
	 * @param reader
	 * @param hasZ
	 * @param hasM
	 * @return line string
	 */
	public static LineString readLineString(ByteReader reader, boolean hasZ,
			boolean hasM) {

		LineString lineString = new LineString(hasZ, hasM);

		int numPoints = reader.readInt();

		for (int i = 0; i < numPoints; i++) {
			Point point = readPoint(reader, hasZ, hasM);
			lineString.addPoint(point);

		}

		return lineString;
	}

	/**
	 * Read a Polygon
	 *
	 * @param reader
	 * @param hasZ
	 * @param hasM
	 * @return polygon
	 */
	public static Polygon readPolygon(ByteReader reader, boolean hasZ,
			boolean hasM) {

		Polygon polygon = new Polygon(hasZ, hasM);

		int numRings = reader.readInt();

		for (int i = 0; i < numRings; i++) {
			LineString ring = readLineString(reader, hasZ, hasM);
			polygon.addRing(ring);

		}

		return polygon;
	}

	/**
	 * Read a Multi Point
	 *
	 * @param reader
	 * @param hasZ
	 * @param hasM
	 * @return multi point
	 */
	public static MultiPoint readMultiPoint(ByteReader reader, boolean hasZ,
			boolean hasM) {

		MultiPoint multiPoint = new MultiPoint(hasZ, hasM);

		int numPoints = reader.readInt();

		for (int i = 0; i < numPoints; i++) {
			Point point = readGeometry(reader, Point.class);
			multiPoint.addPoint(point);

		}

		return multiPoint;
	}

	/**
	 * Read a Multi Line String
	 *
	 * @param reader
	 * @param hasZ
	 * @param hasM
	 * @return multi line string
	 */
	public static MultiLineString readMultiLineString(ByteReader reader,
			boolean hasZ, boolean hasM) {

		MultiLineString multiLineString = new MultiLineString(hasZ, hasM);

		int numLineStrings = reader.readInt();

		for (int i = 0; i < numLineStrings; i++) {
			LineString lineString = readGeometry(reader, LineString.class);
			multiLineString.addLineString(lineString);
		}

		return multiLineString;
	}

	/**
	 * Read a Multi Polygon
	 *
	 * @param reader
	 * @param hasZ
	 * @param hasM
	 * @return multi polygon
	 */
	public static MultiPolygon readMultiPolygon(ByteReader reader,
			boolean hasZ, boolean hasM) {

		MultiPolygon multiPolygon = new MultiPolygon(hasZ, hasM);

		int numPolygons = reader.readInt();

		for (int i = 0; i < numPolygons; i++) {
			Polygon polygon = readGeometry(reader, Polygon.class);
			multiPolygon.addPolygon(polygon);

		}

		return multiPolygon;
	}

	/**
	 * Read a Geometry Collection
	 *
	 * @param reader
	 * @param hasZ
	 * @param hasM
	 * @return geometry collection
	 */
	public static GeometryCollection<Geometry> readGeometryCollection(
			ByteReader reader, boolean hasZ, boolean hasM) {

		GeometryCollection<Geometry> geometryCollection = new GeometryCollection<Geometry>(
				hasZ, hasM);

		int numGeometries = reader.readInt();

		for (int i = 0; i < numGeometries; i++) {
			Geometry geometry = readGeometry(reader, Geometry.class);
			geometryCollection.addGeometry(geometry);

		}

		return geometryCollection;
	}

	/**
	 * Read a Circular String
	 *
	 * @param reader
	 * @param hasZ
	 * @param hasM
	 * @return circular string
	 */
	public static CircularString readCircularString(ByteReader reader,
			boolean hasZ, boolean hasM) {

		CircularString circularString = new CircularString(hasZ, hasM);

		int numPoints = reader.readInt();

		for (int i = 0; i < numPoints; i++) {
			Point point = readPoint(reader, hasZ, hasM);
			circularString.addPoint(point);

		}

		return circularString;
	}

	/**
	 * Read a Compound Curve
	 *
	 * @param reader
	 * @param hasZ
	 * @param hasM
	 * @return compound curve
	 */
	public static CompoundCurve readCompoundCurve(ByteReader reader,
			boolean hasZ, boolean hasM) {

		CompoundCurve compoundCurve = new CompoundCurve(hasZ, hasM);

		int numLineStrings = reader.readInt();

		for (int i = 0; i < numLineStrings; i++) {
			LineString lineString = readGeometry(reader, LineString.class);
			compoundCurve.addLineString(lineString);

		}

		return compoundCurve;
	}

	/**
	 * Read a Curve Polygon
	 *
	 * @param reader
	 * @param hasZ
	 * @param hasM
	 * @return curve polygon
	 */
	public static CurvePolygon<Curve> readCurvePolygon(ByteReader reader,
			boolean hasZ, boolean hasM) {

		CurvePolygon<Curve> curvePolygon = new CurvePolygon<Curve>(hasZ, hasM);

		int numRings = reader.readInt();

		for (int i = 0; i < numRings; i++) {
			Curve ring = readGeometry(reader, Curve.class);
			curvePolygon.addRing(ring);

		}

		return curvePolygon;
	}

	/**
	 * Read a Polyhedral Surface
	 *
	 * @param reader
	 * @param hasZ
	 * @param hasM
	 * @return polyhedral surface
	 */
	public static PolyhedralSurface readPolyhedralSurface(ByteReader reader,
			boolean hasZ, boolean hasM) {

		PolyhedralSurface polyhedralSurface = new PolyhedralSurface(hasZ, hasM);

		int numPolygons = reader.readInt();

		for (int i = 0; i < numPolygons; i++) {
			Polygon polygon = readGeometry(reader, Polygon.class);
			polyhedralSurface.addPolygon(polygon);

		}

		return polyhedralSurface;
	}

	/**
	 * Read a TIN
	 *
	 * @param reader
	 * @param hasZ
	 * @param hasM
	 * @return TIN
	 */
	public static TIN readTIN(ByteReader reader, boolean hasZ, boolean hasM) {

		TIN tin = new TIN(hasZ, hasM);

		int numPolygons = reader.readInt();

		for (int i = 0; i < numPolygons; i++) {
			Polygon polygon = readGeometry(reader, Polygon.class);
			tin.addPolygon(polygon);

		}

		return tin;
	}

	/**
	 * Read a Triangle
	 *
	 * @param reader
	 * @param hasZ
	 * @param hasM
	 * @return triangle
	 */
	public static Triangle readTriangle(ByteReader reader, boolean hasZ,
			boolean hasM) {

		Triangle triangle = new Triangle(hasZ, hasM);

		int numRings = reader.readInt();

		for (int i = 0; i < numRings; i++) {
			LineString ring = readLineString(reader, hasZ, hasM);
			triangle.addRing(ring);

		}

		return triangle;
	}

}


package mil.nga.sf.wkb;

import java.io.IOException;
import java.nio.ByteOrder;

import mil.nga.sf.CircularString;
import mil.nga.sf.CompoundCurve;
import mil.nga.sf.Curve;
import mil.nga.sf.CurvePolygon;
import mil.nga.sf.Geometry;
import mil.nga.sf.GeometryCollection;
import mil.nga.sf.GeometryType;
import mil.nga.sf.LineString;
import mil.nga.sf.MultiLineString;
import mil.nga.sf.MultiPoint;
import mil.nga.sf.MultiPolygon;
import mil.nga.sf.Point;
import mil.nga.sf.Polygon;
import mil.nga.sf.PolyhedralSurface;
import mil.nga.sf.TIN;
import mil.nga.sf.Triangle;
import mil.nga.sf.util.ByteWriter;
import mil.nga.sf.util.SFException;

/**
 * Well Known Binary writer
 *
 * @author osbornb
 */
public class GeometryWriter {

	/**
	 * Write a geometry to the byte writer
	 *
	 * @param writer
	 * @param geometry
	 * @throws IOException
	 */
	public static void writeGeometry(ByteWriter writer, Geometry geometry)
			throws IOException {

		// Write the single byte order byte
		byte byteOrder = writer.getByteOrder() == ByteOrder.BIG_ENDIAN ? (byte) 0
				: (byte) 1;
		writer.writeByte(byteOrder);

		// Write the geometry type integer
		writer.writeInt(GeometryCodes.getCode(geometry));

		GeometryType geometryType = geometry.getGeometryType();

		switch (geometryType) {

		case GEOMETRY:
			throw new SFException("Unexpected Geometry Type of "
					+ geometryType.name() + " which is abstract");
		case POINT:
			writePoint(writer, (Point) geometry);
			break;
		case LINESTRING:
			writeLineString(writer, (LineString) geometry);
			break;
		case POLYGON:
			writePolygon(writer, (Polygon) geometry);
			break;
		case MULTIPOINT:
			writeMultiPoint(writer, (MultiPoint) geometry);
			break;
		case MULTILINESTRING:
			writeMultiLineString(writer, (MultiLineString) geometry);
			break;
		case MULTIPOLYGON:
			writeMultiPolygon(writer, (MultiPolygon) geometry);
			break;
		case GEOMETRYCOLLECTION:
		case MULTICURVE:
		case MULTISURFACE:
			writeGeometryCollection(writer, (GeometryCollection<?>) geometry);
			break;
		case CIRCULARSTRING:
			writeCircularString(writer, (CircularString) geometry);
			break;
		case COMPOUNDCURVE:
			writeCompoundCurve(writer, (CompoundCurve) geometry);
			break;
		case CURVEPOLYGON:
			writeCurvePolygon(writer, (CurvePolygon<?>) geometry);
			break;
		case CURVE:
			throw new SFException("Unexpected Geometry Type of "
					+ geometryType.name() + " which is abstract");
		case SURFACE:
			throw new SFException("Unexpected Geometry Type of "
					+ geometryType.name() + " which is abstract");
		case POLYHEDRALSURFACE:
			writePolyhedralSurface(writer, (PolyhedralSurface) geometry);
			break;
		case TIN:
			writeTIN(writer, (TIN) geometry);
			break;
		case TRIANGLE:
			writeTriangle(writer, (Triangle) geometry);
			break;
		default:
			throw new SFException("Geometry Type not supported: "
					+ geometryType);
		}

	}

	/**
	 * Write a Point
	 *
	 * @param writer
	 * @param point
	 * @throws IOException
	 */
	public static void writePoint(ByteWriter writer, Point point)
			throws IOException {

		writer.writeDouble(point.getX());
		writer.writeDouble(point.getY());

		if (point.hasZ()) {
			writer.writeDouble(point.getZ());
		}

		if (point.hasM()) {
			writer.writeDouble(point.getM());
		}
	}

	/**
	 * Write a Line String
	 *
	 * @param writer
	 * @param lineString
	 * @throws IOException
	 */
	public static void writeLineString(ByteWriter writer, LineString lineString)
			throws IOException {

		writer.writeInt(lineString.numPoints());

		for (Point point : lineString.getPoints()) {
			writePoint(writer, point);
		}
	}

	/**
	 * Write a Polygon
	 *
	 * @param writer
	 * @param polygon
	 * @throws IOException
	 */
	public static void writePolygon(ByteWriter writer, Polygon polygon)
			throws IOException {

		writer.writeInt(polygon.numRings());

		for (LineString ring : polygon.getRings()) {
			writeLineString(writer, ring);
		}
	}

	/**
	 * Write a Multi Point
	 *
	 * @param writer
	 * @param multiPoint
	 * @throws IOException
	 */
	public static void writeMultiPoint(ByteWriter writer, MultiPoint multiPoint)
			throws IOException {

		writer.writeInt(multiPoint.numPoints());

		for (Point point : multiPoint.getPoints()) {
			writeGeometry(writer, point);
		}
	}

	/**
	 * Write a Multi Line String
	 *
	 * @param writer
	 * @param multiLineString
	 * @throws IOException
	 */
	public static void writeMultiLineString(ByteWriter writer,
			MultiLineString multiLineString) throws IOException {

		writer.writeInt(multiLineString.numLineStrings());

		for (LineString lineString : multiLineString.getLineStrings()) {
			writeGeometry(writer, lineString);
		}
	}

	/**
	 * Write a Multi Polygon
	 *
	 * @param writer
	 * @param multiPolygon
	 * @throws IOException
	 */
	public static void writeMultiPolygon(ByteWriter writer,
			MultiPolygon multiPolygon) throws IOException {

		writer.writeInt(multiPolygon.numPolygons());

		for (Polygon polygon : multiPolygon.getPolygons()) {
			writeGeometry(writer, polygon);
		}
	}

	/**
	 * Write a Geometry Collection
	 *
	 * @param writer
	 * @param geometryCollection
	 * @throws IOException
	 */
	public static void writeGeometryCollection(ByteWriter writer,
			GeometryCollection<?> geometryCollection) throws IOException {

		writer.writeInt(geometryCollection.numGeometries());

		for (Geometry geometry : geometryCollection.getGeometries()) {
			writeGeometry(writer, geometry);
		}
	}

	/**
	 * Write a Circular String
	 *
	 * @param writer
	 * @param circularString
	 * @throws IOException
	 */
	public static void writeCircularString(ByteWriter writer,
			CircularString circularString) throws IOException {

		writer.writeInt(circularString.numPoints());

		for (Point point : circularString.getPoints()) {
			writePoint(writer, point);
		}
	}

	/**
	 * Write a Compound Curve
	 *
	 * @param writer
	 * @param compoundCurve
	 * @throws IOException
	 */
	public static void writeCompoundCurve(ByteWriter writer,
			CompoundCurve compoundCurve) throws IOException {

		writer.writeInt(compoundCurve.numLineStrings());

		for (LineString lineString : compoundCurve.getLineStrings()) {
			writeGeometry(writer, lineString);
		}
	}

	/**
	 * Write a Curve Polygon
	 *
	 * @param writer
	 * @param curvePolygon
	 * @throws IOException
	 */
	public static void writeCurvePolygon(ByteWriter writer,
			CurvePolygon<?> curvePolygon) throws IOException {

		writer.writeInt(curvePolygon.numRings());

		for (Curve ring : curvePolygon.getRings()) {
			writeGeometry(writer, ring);
		}
	}

	/**
	 * Write a Polyhedral Surface
	 *
	 * @param writer
	 * @param polyhedralSurface
	 * @throws IOException
	 */
	public static void writePolyhedralSurface(ByteWriter writer,
			PolyhedralSurface polyhedralSurface) throws IOException {

		writer.writeInt(polyhedralSurface.numPolygons());

		for (Polygon polygon : polyhedralSurface.getPolygons()) {
			writeGeometry(writer, polygon);
		}
	}

	/**
	 * Write a TIN
	 *
	 * @param writer
	 * @param tin
	 * @throws IOException
	 */
	public static void writeTIN(ByteWriter writer, TIN tin) throws IOException {

		writer.writeInt(tin.numPolygons());

		for (Polygon polygon : tin.getPolygons()) {
			writeGeometry(writer, polygon);
		}
	}

	/**
	 * Write a Triangle
	 *
	 * @param writer
	 * @param triangle
	 * @throws IOException
	 */
	public static void writeTriangle(ByteWriter writer, Triangle triangle)
			throws IOException {

		writer.writeInt(triangle.numRings());

		for (LineString ring : triangle.getRings()) {
			writeLineString(writer, ring);
		}
	}

}


package mil.nga.sf.wkb;

import mil.nga.sf.Geometry;
import mil.nga.sf.GeometryType;
import mil.nga.sf.util.SFException;

/**
 * Geometry Code utilities to convert between geometry attributes and geometry
 * codes
 *
 * @author osbornb
 */
public class GeometryCodes {

	/**
	 * Get the geometry code from the geometry
	 *
	 * @param geometry
	 *            geometry
	 * @return geometry code
	 */
	public static int getCode(Geometry geometry) {
		int code = getCode(geometry.getGeometryType());
		if (geometry.hasZ()) {
			code += 1000;
		}
		if (geometry.hasM()) {
			code += 2000;
		}
		return code;
	}

	/**
	 * Get the geometry code from the geometry type
	 *
	 * @param geometryType
	 *            geometry type
	 * @return geometry code
	 */
	public static int getCode(GeometryType geometryType) {

		int code;

		switch (geometryType) {
		case GEOMETRY:
			code = 0;
			break;
		case POINT:
			code = 1;
			break;
		case LINESTRING:
			code = 2;
			break;
		case POLYGON:
			code = 3;
			break;
		case MULTIPOINT:
			code = 4;
			break;
		case MULTILINESTRING:
			code = 5;
			break;
		case MULTIPOLYGON:
			code = 6;
			break;
		case GEOMETRYCOLLECTION:
			code = 7;
			break;
		case CIRCULARSTRING:
			code = 8;
			break;
		case COMPOUNDCURVE:
			code = 9;
			break;
		case CURVEPOLYGON:
			code = 10;
			break;
		case MULTICURVE:
			code = 11;
			break;
		case MULTISURFACE:
			code = 12;
			break;
		case CURVE:
			code = 13;
			break;
		case SURFACE:
			code = 14;
			break;
		case POLYHEDRALSURFACE:
			code = 15;
			break;
		case TIN:
			code = 16;
			break;
		case TRIANGLE:
			code = 17;
			break;
		default:
			throw new SFException(
					"Unsupported Geometry Type for code retrieval: "
							+ geometryType);
		}

		return code;
	}

	/**
	 * Get the Geometry Type from the code
	 *
	 * @param code
	 *            geometry type code
	 * @return geometry type
	 */
	public static GeometryType getGeometryType(int code) {

		// Look at the last 2 digits to find the geometry type code
		int geometryTypeCode = code % 1000;

		GeometryType geometryType = null;

		switch (geometryTypeCode) {
		case 0:
			geometryType = GeometryType.GEOMETRY;
			break;
		case 1:
			geometryType = GeometryType.POINT;
			break;
		case 2:
			geometryType = GeometryType.LINESTRING;
			break;
		case 3:
			geometryType = GeometryType.POLYGON;
			break;
		case 4:
			geometryType = GeometryType.MULTIPOINT;
			break;
		case 5:
			geometryType = GeometryType.MULTILINESTRING;
			break;
		case 6:
			geometryType = GeometryType.MULTIPOLYGON;
			break;
		case 7:
			geometryType = GeometryType.GEOMETRYCOLLECTION;
			break;
		case 8:
			geometryType = GeometryType.CIRCULARSTRING;
			break;
		case 9:
			geometryType = GeometryType.COMPOUNDCURVE;
			break;
		case 10:
			geometryType = GeometryType.CURVEPOLYGON;
			break;
		case 11:
			geometryType = GeometryType.MULTICURVE;
			break;
		case 12:
			geometryType = GeometryType.MULTISURFACE;
			break;
		case 13:
			geometryType = GeometryType.CURVE;
			break;
		case 14:
			geometryType = GeometryType.SURFACE;
			break;
		case 15:
			geometryType = GeometryType.POLYHEDRALSURFACE;
			break;
		case 16:
			geometryType = GeometryType.TIN;
			break;
		case 17:
			geometryType = GeometryType.TRIANGLE;
			break;
		default:
			throw new SFException(
					"Unsupported Geometry code for type retrieval: " + code);
		}

		return geometryType;
	}

	/**
	 * Determine if the geometry code has a Z (3D) value
	 *
	 * @param code
	 *            geometry code
	 * @return true is has Z
	 */
	public static boolean hasZ(int code) {

		boolean hasZ = false;

		int mode = getGeometryMode(code);

		switch (mode) {
		case 0:
		case 2:
			break;
		case 1:
		case 3:
			hasZ = true;
			break;
		default:
			throw new SFException(
					"Unexpected Geometry code for Z determination: " + code);
		}

		return hasZ;
	}

	/**
	 * Determine if the geometry code has a M (linear referencing system) value
	 *
	 * @param code
	 *            geometry code
	 * @return true is has M
	 */
	public static boolean hasM(int code) {

		boolean hasM = false;

		int mode = getGeometryMode(code);

		switch (mode) {
		case 0:
		case 1:
			break;
		case 2:
		case 3:
			hasM = true;
			break;
		default:
			throw new SFException(
					"Unexpected Geometry code for M determination: " + code);
		}

		return hasM;
	}

	/**
	 * Get the geometry mode from the geometry code. Returns the digit in the
	 * thousands place. (z is enabled when 1 or 3, m is enabled when 2 or 3)
	 *
	 * @param code
	 *            geometry code
	 * @return geometry mode
	 */
	public static int getGeometryMode(int code) {
		return code / 1000;
	}

}


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

package org.jmxtrans.agent.util.io;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import javax.annotation.Nonnull;

/**
 * Inspired by {@code org.springframework.core.io.AbstractResource}
 * @author Cyrille Le Clerc
 */
public abstract class AbstractResource  implements Resource {

    @Override
    public long lastModified() throws IoRuntimeException {
        long lastModified = getFile().lastModified();
        if (lastModified == 0L) {
            throw new FileNotFoundRuntimeException(getDescription() +
                    " cannot be resolved in the file system for resolving its last-modified timestamp");
        }
        return lastModified;
    }

    @Nonnull
    @Override
    public URL getURL() throws IoRuntimeException {
        throw new FileNotFoundRuntimeException(getDescription() + " cannot be resolved to URL");
    }

    @Nonnull
    @Override
    public File getFile() throws IoRuntimeException {
        throw new FileNotFoundRuntimeException(getDescription() + " cannot be resolved to absolute file path");
    }

    @Nonnull
    @Override
    public URI getURI() throws IoRuntimeException {
        URL url = getURL();
        try {
            return url.toURI();
        } catch (URISyntaxException e) {
            throw new IoRuntimeException("Exception parsing the URI of '" + url + "'", e);
        }
    }

    /**
     * This implementation checks whether a File can be opened,
     * falling back to whether an InputStream can be opened.
     * This will cover both directories and content resources.
     */
    @Override
    public boolean exists() {
        // Try file existence: can we find the file in the file system?
        try {
            return getFile().exists();
        }
        catch (IoRuntimeException ex) {
            // Fall back to stream existence: can we open the stream?
            try {
                try (InputStream is = getInputStream()) {
                	return true;
                }
            }
            catch (Throwable isEx) {
                return false;
            }
        }
    }

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


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

import java.math.BigInteger;
import java.util.List;

/**
 * @author Kristoffer Erlandsson
 */
public class GcdCalculator {

    /**
     * Finds the greatest common divisor of all numbers in the list.
     *
     * @return the GCD or Long.MAX_VALUE if the list is empty.
     */
    public static long gcd(List<Long> l) {
        if (l.isEmpty()) {
            throw new IllegalArgumentException("List must contain at least one element");
        }
        BigInteger gcd = BigInteger.valueOf(l.get(0));
        for (Long num : l.subList(1, l.size())) {
            gcd = gcd.gcd(BigInteger.valueOf(num));
        }
        return gcd.longValue();

    }

}


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

import javax.annotation.Nullable;

import org.jmxtrans.agent.util.json.JsonObject;

/**
 * Generates metric messages to send to graphite instances.
 */
public class ZabbixMetricMessageBuilder {

    private final String hostName;

    /**
     * @param hostName
     *            hostname of the zabbix server
     */
    public ZabbixMetricMessageBuilder(@Nullable String hostName) {
        this.hostName = hostName;
    }

    public String getHostName()
    {
        return hostName;
    }

    /**
     * Builds a metric string to send to a Graphite instance.
     *
     * @return The metric json string without trailing newline
     */
    public String buildMessage(String metricName, Object value, long timestamp) {
        String valueToString = "null";
        if( value != null ) {
            valueToString = value.toString();
        }

        JsonObject jsonValue = new JsonObject();
        jsonValue.add("host", getHostName());
        jsonValue.add("key", metricName);
        jsonValue.add("value", valueToString);
        jsonValue.add("clock", timestamp);

        return jsonValue.toString();
    }

}


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

import org.jmxtrans.agent.util.logging.Logger;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Timestamp;
import java.util.logging.Level;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

/**
 * @author Cyrille Le Clerc
 */
public class IoUtils {
    protected final static Logger logger = Logger.getLogger(IoUtils.class.getName());

    private IoUtils(){}

    /**
     * @param filePath can be prefixed by "{@code http://}", "{@code https://}", "{@code file://}". If given value is not prefixed, "{@code file://}" is assumed.
     * @return @return A long value representing the time the file was
     * last modified, measured in milliseconds since the epoch
     * (00:00:00 GMT, January 1, 1970), or {@code 0L} if the
     * file does not exist, if an I/O error occurs or if the given filePath is {@code null}
     * @throws IoRuntimeException
     */
    public static long getFileLastModificationDate(@Nullable String filePath) throws IoRuntimeException {
        if (filePath == null)
            return 0;

        if (filePath.toLowerCase().startsWith("classpath:")) {
            String classpathResourcePath = filePath.substring("classpath:".length());
            URL configurationFileUrl = Thread.currentThread().getContextClassLoader().getResource(classpathResourcePath);
            if (configurationFileUrl == null) {
                if (logger.isLoggable(Level.FINER))
                    logger.fine("File '" + filePath + "' not found in classpath");
                return 0L;
            } else {
                File configurationFile;
                try {
                    configurationFile = new File(configurationFileUrl.toURI());
                } catch (URISyntaxException e) {
                    throw new IoRuntimeException("Exception parsing '" + filePath + "'", e);
                }
                if (!configurationFile.exists())
                    throw new IllegalStateException("File path=" + filePath + ", url=" + configurationFileUrl + " not found");

                if (logger.isLoggable(Level.FINER))
                    logger.fine("Classpath file '" + filePath + "' last modified at " + new Timestamp(configurationFile.lastModified()));
                return configurationFile.lastModified();
            }
        } else if (
                filePath.toLowerCase().startsWith("http://") ||
                        filePath.toLowerCase().startsWith("https://")
                ) {
            if (logger.isLoggable(Level.FINER))
                logger.fine("Http files not supported: '" + filePath + "' is seen as never modified");
            return 0L;
        } else if (filePath.toLowerCase().startsWith("file://")){
            File file;
            try {
                file = new File(new URI(filePath));
            } catch (URISyntaxException|RuntimeException e) {
                throw new IoRuntimeException("Exception parsing '" + filePath + "'", e);
            }

            if (file.exists()) {
                if (logger.isLoggable(Level.FINER))
                    logger.fine("File '" + filePath + "' last modified at " + new Timestamp(file.lastModified()));
                return file.lastModified();
            } else {
                if (logger.isLoggable(Level.FINER))
                    logger.fine("File '" + filePath + "' not found ");
                return 0L;
            }
        } else {
            File file;
            try {
                file = new File(filePath);
            } catch (RuntimeException e) {
                throw new IoRuntimeException("Exception parsing '" + filePath + "'", e);
            }

            if (file.exists()) {
                if (logger.isLoggable(Level.FINER))
                    logger.fine("File '" + filePath + "' last modified at " + new Timestamp(file.lastModified()));
                return file.lastModified();
            } else {
                if (logger.isLoggable(Level.FINER))
                    logger.fine("File '" + filePath + "' not found ");
                return 0L;
            }
        }
    }

    @Nonnull
    public static Document getFileAsDocument(@Nonnull Resource resource) throws IoRuntimeException {
        if (resource == null)
            throw new IoRuntimeException(new NullPointerException("resource cannot be null"));

        DocumentBuilder dBuilder;
        try {
            dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();

            try {
                File configurationFile = resource.getFile();
                return dBuilder.parse(configurationFile);
            } catch(IoRuntimeException e) {
                try (InputStream in = resource.getInputStream()) {
                	return dBuilder.parse(in);
                }
            }
        } catch (ParserConfigurationException | SAXException e) {
            throw new IoRuntimeException(e);
        } catch (IOException e) {
            throw IoRuntimeException.propagate(e);
        }
    }

    /**
     * Simple implementation without chunking if the source file is big.
     *
     * @param source
     * @param destination
     * @throws java.io.IOException
     */
    private static void doCopySmallFile(File source, File destination) throws IOException {
        if (destination.exists() && destination.isDirectory()) {
            throw new IOException("Can not copy file, destination is a directory: " + destination.getAbsolutePath());
        }

        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel input = null;
        FileChannel output = null;
        try {
            fis = new FileInputStream(source);
            fos = new FileOutputStream(destination, false);
            input = fis.getChannel();
            output = fos.getChannel();
            output.transferFrom(input, 0, input.size());
        } finally {
            closeQuietly(output);
            closeQuietly(input);
            closeQuietly(fis);
            closeQuietly(fos);
        }
        if (destination.length() != source.length()) {
            throw new IOException("Failed to copy content from '" +
                    source + "' (" + source.length() + "bytes) to '" + destination + "' (" + destination.length() + ")");
        }

    }

    public static boolean isFileUrl(URL url){
        if (url.getProtocol().equals("file")) {
            return true;
        } else {
            return false;
        }

    }
    public static void closeQuietly (URLConnection cnn) {
        if (cnn == null) {
            return;
        } else if (cnn instanceof HttpURLConnection) {
            ((HttpURLConnection) cnn).disconnect();
        } else {
            // do nothing
        }
    }
    public static void closeQuietly(Closeable closeable) {
        if (closeable == null)
            return;
        try {
            closeable.close();
        } catch (Exception e) {
            // ignore silently
        }
    }

    public static void closeQuietly(Writer writer) {
        if (writer == null)
            return;
        try {
            writer.close();
        } catch (Exception e) {
            // ignore silently
        }
    }

    /**
     * Needed for old JVMs where {@link java.io.InputStream} does not implement {@link java.io.Closeable}.
     */
    public static void closeQuietly(InputStream inputStream) {
        if (inputStream == null)
            return;
        try {
            inputStream.close();
        } catch (Exception e) {
            // ignore silently
        }
    }

    public static void replaceFile(File source, File destination) throws IOException {
        boolean destinationExists;
        if (destination.exists()) {
            boolean deleted = destination.delete();
            if (deleted) {
                destinationExists = false;
            } else {
                destinationExists = true;
            }
        } else {
            destinationExists = false;
        }
        if (destinationExists) {
            doCopySmallFile(source, destination);
        } else {
            boolean renamed = source.renameTo(destination);
            if (!renamed) {
                doCopySmallFile(source, destination);
            }
        }
    }

    /**
     * Simple implementation without chunking if the source file is big.
     *
     * @param source
     * @param destination
     * @throws java.io.IOException
     */
    private static void doCopySmallFile(File source, File destination, boolean append, boolean prependNewLine) throws IOException {
        if (destination.exists() && destination.isDirectory()) {
            throw new IOException("Can not copy file, destination is a directory: " + destination.getAbsolutePath());
        } else if (!destination.exists()) {
            boolean renamed = source.renameTo(destination);
            if (renamed) return;
        }

        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel input = null;
        FileChannel output = null;
        long initialSize = destination.length();
        try {
            fos = new FileOutputStream(destination, append);
            if (prependNewLine) {
                fos.write(("\n").getBytes(StandardCharsets.UTF_8));
            }
            fos.write(Files.readAllBytes(Paths.get(source.getAbsolutePath())));
        } finally {
            closeQuietly(output);
            closeQuietly(input);
            closeQuietly(fis);
            closeQuietly(fos);
        }
        if (!append && destination.length() != source.length()) {
            throw new IOException("Failed to copy content from '" +
                    source + "' (" + source.length() + "bytes) to '" + destination + "' (" + destination.length() + "). isAppend? " + append );
        }
        else if (append && destination.length() <= initialSize ) {
            throw new IOException("Failed to append content from '" +
                    source + "' (" + source.length() + "bytes) to '" + destination + "' (" + destination.length() + "). isAppend? " + append );
        }

    }

    public static void appendToFile(File source, File destination, long maxFileSize, int maxBackupIndex, boolean prependNewLine) throws IOException {
        boolean destinationExists = validateDestinationFile(source, destination, maxFileSize, maxBackupIndex);
        if (destinationExists) {
            doCopySmallFile(source, destination, true, prependNewLine);
        } else {
            boolean renamed = source.renameTo(destination);
            if (!renamed) {
                doCopySmallFile(source, destination, false, false);
            }
        }
    }

    public static boolean validateDestinationFile(File source, File destination, long maxFileSize, int maxBackupIndex) throws IOException {
        if (!destination.exists() || destination.isDirectory()) return false;
        long totalLengthAfterAppending = destination.length() + source.length();
        if (totalLengthAfterAppending > maxFileSize) {
            rollFiles(destination, maxBackupIndex);
            return false; // File no longer exists because it was move to filename.1
        }

        return true;
    }

    public static void rollFiles(File destination, int maxBackupIndex) throws IOException {

        // if maxBackup index == 10 then we will have file
        // outputFile, outpuFile.1 outputFile.2 ... outputFile.10
        // we only care if 9 and lower exists to move them up a number
        for (int i = maxBackupIndex - 1; i >= 0; i--) {
            String path = destination.getAbsolutePath();
            path=(i==0)?path:path + "." + i;
            File f = new File(path);
            if (!f.exists()) continue;

            File fNext = new File(destination + "." + (i + 1));
            doCopySmallFile(f, fNext, false, false);
        }

        boolean deleted = destination.delete();
        if (!deleted) {
            logger.warning("Failure to delete file " + destination);
        }
    }

    public static void copy(InputStream in, OutputStream out) throws IOException{
        byte[] buffer = new byte[1024];
        int length;
        while ((length = in.read(buffer)) >= 0) {
            out.write(buffer, 0,length);
        }
    }
}


package org.jmxtrans.agent;

import org.jmxtrans.agent.util.StringUtils2;
import org.jmxtrans.agent.util.logging.Logger;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.management.ObjectName;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Builds names with general rules like JConsole / VisualVM do.
 * i.e. {@code <domain-name>.<property-name><property-name><attribute-name><composite-data-key-name>}
 *
 * E.g. For objectName = "type:name=metric,value=bar" and attribute "count",
 * it will general resultName = "type.metric.bar.count"
 *
 * @author Mahesh V Kelkar
 * @author Cyrille Le Clerc
 */
public class JConsoleResultNameStrategyImpl implements ResultNameStrategy {
    protected final Logger logger = Logger.getLogger(getClass().getName());
    private ExpressionLanguageEngine expressionLanguageEngine = new ExpressionLanguageEngineImpl();

    @Nonnull
    @Override
    public String getResultName(@Nonnull Query query, @Nonnull ObjectName objectName, @Nullable String attribute, @Nullable String compositeDataKey, @Nullable Integer position) {
        String result;
        if (query.getResultAlias() == null) {
            result = escapeObjectName(objectName);
            if (!StringUtils2.isNullOrEmpty(attribute)) {
                result += "." + attribute;
            }
            if (!StringUtils2.isNullOrEmpty(compositeDataKey)) {
                result += "." + compositeDataKey;
            }
            if (position != null) {
                result += "_" + position;
            }
        } else {
            result = expressionLanguageEngine.resolveExpression(query.getResultAlias(), objectName, attribute, compositeDataKey, position);
        }
        return result;
    }

    /**
     * Transforms an {@linkplain javax.management.ObjectName} into a plain {@linkplain String}
     * only composed of ('a' to 'Z', 'A' to 'Z', '.', '_') similar to JConsole naming.
     *
     * '_' is the escape char for not compliant chars.
     */
    protected String escapeObjectName(@Nonnull ObjectName objectName) {

        /** Add objectName's domain */
        StringBuilder result = new StringBuilder();
        StringUtils2.appendEscapedNonAlphaNumericChars(objectName.getDomain(), false, result);

        /** Walk through (sorted) properties of the ObjectName and add values to the result */
        List<String> keys = Collections.list(objectName.getKeyPropertyList().keys());
        Collections.sort(keys);
        for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
            String propertyKey = it.next();
            result.append('.');
            StringUtils2.appendEscapedNonAlphaNumericChars(objectName.getKeyProperty(propertyKey), false, result);
        }

        return result.toString();
    }

    public ExpressionLanguageEngine getExpressionLanguageEngine() {
        return expressionLanguageEngine;
    }

    public void setExpressionLanguageEngine(ExpressionLanguageEngine expressionLanguageEngine) {
        this.expressionLanguageEngine = expressionLanguageEngine;
    }

    public void postConstruct(@Nonnull Map<String, String> settings) {

    }
}


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

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;

/**
 * Yet another {@code StringUtils} class.
 *
 * @author Cyrille Le Clerc
 */
public class StringUtils2 {

    private StringUtils2() {

    }

    /**
     * Split given String. Delimiters are {@code ","}, {@code ";"} and {@code "\n"}.
     *
     * @param delimitedString
     * @return splitted string or {@code null} if given {@code delimitedString} is {@code null}
     */
    public static List<String> delimitedStringToList(@Nullable String delimitedString) {
        if (delimitedString == null) {
            return null;
        }
        String[] splits = delimitedString.split("[,;\\n]");

        List<String> result = new ArrayList<>();

        for (String split : splits) {
            split = split.trim();
            if (!split.isEmpty()) {
                result.add(split);
            }
        }
        return result;
    }

    /**
     * Join given {@code tokens} with given {@code delimiter}.
     *
     * Sample: tokens {@code "com", "mycompany, "ecommerce", "server1"} with delimiter {@code "."}
     * returns {@code "com.mycompany.ecommerce.server1"}.
     *
     * @param tokens
     * @param delimiter
     * @return the joined tokens ({@code null} if given {@code tokens} is {@code null}
     */
    public static String join(@Nullable List<String> tokens, @Nonnull String delimiter) {
        if (tokens == null) {
            return null;
        }
        Preconditions2.checkNotNull(delimiter, "given delimiter can not be null");

        Iterator<String> it = tokens.iterator();
        StringBuilder sb = new StringBuilder();
        while (it.hasNext()) {
            String token = it.next();
            sb.append(token);
            if (it.hasNext()) {
                sb.append(delimiter);
            }
        }
        return sb.toString();
    }

    /**
     *
 Reverse tokens of given tokenized {@code str}.

     *
Sample: "server1.ecommerce.mycompany.com" returns {@code "com.mycompany.ecommerce.server1"}.

     *
     * @param str
     * @param delimiter
     * @return reversed string or {@code null} if given string is {@code null}
     */
    public static String reverseTokens(@Nullable String str, @Nonnull String delimiter) {
        if (str == null) {
            return null;
        }
        Preconditions2.checkNotNull(delimiter, "given delimiter can not be null");

        StringTokenizer st = new StringTokenizer(str, delimiter);
        List<String> tokens = new ArrayList<>();

        while (st.hasMoreTokens()) {
            tokens.add(st.nextToken());
        }
        Collections.reverse(tokens);

        return join(tokens, delimiter);
    }

    /**
     * Escape all non 'a' to 'z','A' to 'Z', '0' to '9' and '-' with a '_'.
     *
     * '.' is escaped with a '_' if {@code escapeDot} is {@code true}.
     *
     * @param str       the string to escape
     * @param escapeDot indicates whether '.' should be escaped into '_' or not.
     * @param result    the {@linkplain StringBuilder} in which the escaped string is appended
     */
    public static void appendEscapedNonAlphaNumericChars(@Nullable String str, boolean escapeDot, @Nonnull StringBuilder result) {
        if (str == null) {
            return;
        }
        char[] chars = str.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            char ch = chars[i];
            if (Character.isLetter(ch) || Character.isDigit(ch) || ch == '-') {
                result.append(ch);
            } else if (ch == '.') {
                result.append(escapeDot ? '_' : ch);
            } else if (ch == '"' && ((i == 0) || (i == chars.length - 1))) {
                // ignore starting and ending '"' that are used to quote() objectname's values (see ObjectName.value())
            } else {
                result.append('_');
            }
        }
    }

    /**
     * Escape all non 'a' to 'z','A' to 'Z', '0' to '9' and '-' with a '_'.
     *
     * @param str    the string to escape
     * @param result the {@linkplain StringBuilder} in which the escaped string is appended
     */
    public static void appendEscapedNonAlphaNumericChars(@Nonnull String str, @Nonnull StringBuilder result) {
        appendEscapedNonAlphaNumericChars(str, true, result);
    }

    public static boolean isNullOrEmpty(@Nullable String value) {
        return value == null || value.trim().isEmpty();
    }

    /**
     * Abbreviates a String using ellipses. This will turn "Now is the time for all good men" into "Now is the time for..."
     *
     * @param str the String to abbreviate
     * @param max the max number of chars of the abbreviated String
     * @return the abbreviated string
     */
    @Nonnull
    public static String abbreviate(String str, int max) {
        if (str == null) {
            return "";
        } else if (str.length() <= max) {
            return str;
        } else {
            return str.substring(0, max - 3) + "...";
        }
    }

    /**
     * if the given string is {@code null}, return {@code ""}, else return {@link String#trim()}.
     *
     * @param str the string to trim
     * @return the trimmed string
     */
    @Nonnull
    public static String trimToEmpty(@Nullable String str) {
        if (str == null) {
            return "";
        } else {
            return str.trim();
        }
    }

}


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

import org.jmxtrans.agent.util.StringUtils2;
import org.jmxtrans.agent.util.logging.Logger;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.management.ObjectName;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

/**
 * @author Cyrille Le Clerc
 */
public class ExpressionLanguageEngineImpl implements ExpressionLanguageEngine {
    protected final Logger logger = Logger.getLogger(getClass().getName());

    public ExpressionLanguageEngineImpl() {
        try {
            InetAddress localHost = InetAddress.getLocalHost();
            String hostName = localHost.getHostName();
            String reversedHostName = StringUtils2.reverseTokens(hostName, ".");
            String canonicalHostName = localHost.getCanonicalHostName();
            String reversedCanonicalHostName = StringUtils2.reverseTokens(canonicalHostName, ".");
            String hostAddress = localHost.getHostAddress();

            functionsByName.put("hostname", new StaticFunction(hostName));
            functionsByName.put("reversed_hostname", new StaticFunction(reversedHostName));
            functionsByName.put("escaped_hostname", new StaticFunction(hostName.replaceAll("\\.", "_")));
            functionsByName.put("canonical_hostname", new StaticFunction(canonicalHostName));
            functionsByName.put("reversed_canonical_hostname", new StaticFunction(reversedCanonicalHostName));
            functionsByName.put("escaped_canonical_hostname", new StaticFunction(canonicalHostName.replaceAll("\\.", "_")));
            functionsByName.put("hostaddress", new StaticFunction(hostAddress));
            functionsByName.put("escaped_hostaddress", new StaticFunction(hostAddress.replaceAll("\\.", "_")));
            functionsByName.put("attribute", new ExtractAttributeFunction());
            functionsByName.put("key", new ExtractCompositeDataKeyFunction());
            functionsByName.put("compositeDataKey", new ExtractCompositeDataKeyFunction());
            functionsByName.put("position", new ExtractPositionFunction());
            functionsByName.put("domain", new ExtractDomainFunction());
            functionsByName.put("reversed_domain", new ExtractReversedDomainFunction());
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Exception resolving localhost, expressions like #hostname#, #canonical_hostname# or #hostaddress# will not be available", e);
        }
    }

    /**
     * Function based evaluators for expressions like '#hostname#' or '#hostname_canonical#'
     */
    @Nonnull
    private Map<String, Function> functionsByName = new HashMap<>();

    /**
     * Replace all the '#' based keywords (e.g. #hostname#) by their value.
     *
     * @param expression the expression to resolve (e.g. "servers.#hostname#.")
     * @return the resolved expression (e.g. "servers.tomcat5")
     */
    @Nonnull
    public String resolveExpression(@Nonnull String expression) {
        StringBuilder result = new StringBuilder(expression.length());

        int position = 0;
        while (position < expression.length()) {
            char c = expression.charAt(position);
            if (c == '#') {
                int beginningSeparatorPosition = position;
                int endingSeparatorPosition = expression.indexOf('#', beginningSeparatorPosition + 1);
                if (endingSeparatorPosition == -1) {
                    throw new IllegalStateException("Invalid expression '" + expression + "', no ending '#' after beginning '#' at position " + beginningSeparatorPosition);
                }
                String key = expression.substring(beginningSeparatorPosition + 1, endingSeparatorPosition);
                Function expressionProcessor = functionsByName.get(key);
                String value;
                if (expressionProcessor == null) {
                    value = "#unsupported_expression#";
                    logger.info("Unsupported expression '" + key + "'");
                } else {
                    try {
                        value = expressionProcessor.evaluate();
                    } catch (Exception e) {
                        value = "#expression_error#";
                        logger.log(Level.WARNING, "Error evaluating expression '" + key + "'", e);
                    }
                }
                StringUtils2.appendEscapedNonAlphaNumericChars(value, false, result);
                position = endingSeparatorPosition + 1;

            } else {
                result.append(c);
                position++;
            }
        }
        if (logger.isLoggable(Level.FINEST))
            logger.log(Level.FINEST, "resolveExpression(" + expression + "): " + result);
        return result.toString();

    }

    @Nonnull
    public String resolveExpression(@Nonnull String expression, @Nonnull ObjectName exactObjectName, @Nullable String attribute, @Nullable String compositeDataKey, @Nullable Integer position) {

        StringBuilder result = new StringBuilder(expression.length());

        int pos = 0;
        while (pos < expression.length()) {
            char c = expression.charAt(pos);
            if (c == '%') {
                int beginningSeparatorPosition = pos;
                int endingSeparatorPosition = expression.indexOf('%', beginningSeparatorPosition + 1);
                if (endingSeparatorPosition == -1) {
                    throw new IllegalStateException("Invalid expression '" + expression + "', no ending '%' after beginning '%' at position " + beginningSeparatorPosition);
                }
                String objectNameKey = expression.substring(beginningSeparatorPosition + 1, endingSeparatorPosition);
                String value = exactObjectName.getKeyProperty(objectNameKey);
                if (value == null) {
                    value = "null";
                }
                StringUtils2.appendEscapedNonAlphaNumericChars(value, result);
                pos = endingSeparatorPosition + 1;
            } else if (c == '#') {
                int beginningSeparatorPosition = pos;
                int endingSeparatorPosition = expression.indexOf('#', beginningSeparatorPosition + 1);
                if (endingSeparatorPosition == -1) {
                    throw new IllegalStateException("Invalid expression '" + expression + "', no ending '#' after beginning '#' at position " + beginningSeparatorPosition);
                }
                String functionName = expression.substring(beginningSeparatorPosition + 1, endingSeparatorPosition);
                Function function = functionsByName.get(functionName);
                String value;
                if (function == null) {
                    value = "#unsupported_expression#";
                    logger.info("Unsupported expression '" + functionName + "'");
                } else {
                    try {
                        value = function.evaluate(exactObjectName, attribute, compositeDataKey, position);
                    } catch (Exception e) {
                        value = "#expression_error#";
                        logger.log(Level.WARNING, "Error evaluating expression '" + compositeDataKey + "'", e);
                    }
                }
                StringUtils2.appendEscapedNonAlphaNumericChars(value, false, result);
                pos = endingSeparatorPosition + 1;

            } else {
                result.append(c);
                pos++;
            }
        }
        if (logger.isLoggable(Level.FINEST))
            logger.log(Level.FINEST, "resolveExpression(" + expression + ", " + exactObjectName + "): " + result);

        return result.toString();
    }

    /**
     * Registers an expression evaluator with a static value.
     */
    public void registerExpressionEvaluator(@Nonnull String expression, @Nonnull Function evaluator) {
        functionsByName.put(expression, evaluator);
    }

    public interface Function {
        @Nullable
        String evaluate();
        @Nullable
        String evaluate(@Nullable ObjectName objectName, @Nullable String attribute, @Nullable String compositeDataKey, @Nullable Integer position);
    }

    public static class StaticFunction implements Function {
        final String value;

        public StaticFunction(String value) {
            this.value = value;
        }

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

        @Override
        public String evaluate(@Nullable ObjectName objectName, @Nullable String attribute, @Nullable String compositeDataKey, @Nullable Integer position) {
            return value;
        }

        @Override
        public String toString() {
            return "StaticFunction{" +
                    "value='" + value + '\'' +
                    '}';
        }
    }

    private static class ExtractAttributeFunction implements Function {
        @Override
        public String evaluate() {
            return null;
        }

        @Override
        public String evaluate(@Nullable ObjectName objectName, @Nullable String attribute, @Nullable String compositeDataKey, @Nullable Integer position) {
            return attribute;
        }
    }

    private static class ExtractCompositeDataKeyFunction implements Function {
        @Override
        public String evaluate() {
            return null;
        }

        @Override
        public String evaluate(@Nullable ObjectName objectName, @Nullable String attribute, @Nullable String compositeDataKey, @Nullable Integer position) {
            return compositeDataKey;
        }
    }

    private static class ExtractPositionFunction implements Function {
        @Override
        public String evaluate() {
            return null;
        }

        @Override
        public String evaluate(@Nullable ObjectName objectName, @Nullable String attribute, @Nullable String compositeDataKey, @Nullable Integer position) {
            return position == null ? null : position.toString();
        }
    }

    private static class ExtractDomainFunction implements Function {
        @Override
        public String evaluate() {
            return null;
        }

        @Override
        public String evaluate(@Nullable ObjectName objectName, @Nullable String attribute, @Nullable String compositeDataKey, @Nullable Integer position) {
            return objectName == null ? null : objectName.getDomain();
        }
    }

    private static class ExtractReversedDomainFunction implements Function {
        @Override
        public String evaluate() {
            return null;
        }

        @Override
        public String evaluate(@Nullable ObjectName objectName, @Nullable String attribute, @Nullable String compositeDataKey, @Nullable Integer position) {
            return objectName == null ? null : StringUtils2.reverseTokens(objectName.getDomain(), ".");
        }
    }
}


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

import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * @author Cyrille Le Clerc
 */
public class IoRuntimeException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    /**
     * This method returns an instance {@link IoRuntimeException}.
     *
     * Inspired by {@code com.google.common.base.Throwables#propagate(java.lang.Throwable)}.
     *

     *     try {
     *         ...
     *     } catch (IOException e) {
     *         throw IoRuntimeException.propagate(e);
     *     }
     *

     * @param e
     */
    public static IoRuntimeException propagate(IOException e) {
        if (e instanceof FileNotFoundException) {
            return new FileNotFoundRuntimeException(e);
        } else {
            return new IoRuntimeException(e);
        }
    }

    public IoRuntimeException() {
        super();
    }

    public IoRuntimeException(String message) {
        super(message);
    }

    public IoRuntimeException(String message, Throwable cause) {
        super(message, cause);
    }

    protected IoRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    public IoRuntimeException(Throwable cause) {
        super(cause);
    }
}