org.novelang.outfit.shell.AgentFileInstaller.java Source code

Java tutorial

Introduction

Here is the source code for org.novelang.outfit.shell.AgentFileInstaller.java

Source

/*
 * Copyright (C) 2011 Laurent Caillette
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.novelang.outfit.shell;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.MissingResourceException;
import java.util.regex.Pattern;

import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import org.apache.commons.io.FileUtils;

import org.novelang.logger.Logger;
import org.novelang.logger.LoggerFactory;

/**
 * Installs the jar of the {@link org.novelang.outfit.shell.insider.InsiderAgent} somewhere
 * on the local filesystem.
 * The JVM installs agents from plain jar files.
 * <p>
 * This class applies the
 * <a href="http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom">Initialization on demand idiom</a>.
 *
 * @author Laurent Caillette
 */
public class AgentFileInstaller {

    private static final Logger LOGGER = LoggerFactory.getLogger(AgentFileInstaller.class);

    /**
     * Name of the system property to set the agent jar file externally.
     * This is useful when running tests from an IDE.
     * The "{@value #VERSION_PLACEHOLDER}" substring evaluates to
     */
    public static final String AGENTJARFILE_SYSTEMPROPERTYNAME = "org.novelang.outfit.shell.agentjarfile";

    private static final String VERSION_PLACEHOLDER = "${project.version}";

    private static final String DEFAULT_VERSION = "SNAPSHOT";

    /**
     * Name of the system property to set the version overriding the content of the
     * {@value #VERSION_RESOURCE_NAME} resource.
     */
    public static final String VERSIONOVERRIDE_SYSTEMPROPERTYNAME = "org.novelang.outfit.shell.versionoverride";

    /**
     * Version of the embedded jar. Needed to find its resource name.
     */
    private final String version;

    private final File jarFile;
    public static final String VERSION_PLACEHOLDER_REGEX = Pattern.quote(VERSION_PLACEHOLDER);

    public static boolean mayHaveValidInstance() {
        return System.getProperty(VERSIONOVERRIDE_SYSTEMPROPERTYNAME) != null
                || AgentFileInstaller.class.getResource(VERSION_RESOURCE_NAME) != null;
    }

    private AgentFileInstaller() throws IOException {

        final String versionOverride = System.getProperty(VERSIONOVERRIDE_SYSTEMPROPERTYNAME);
        if (versionOverride == null) {
            final URL versionResource = AgentFileInstaller.class.getResource(VERSION_RESOURCE_NAME);
            if (versionResource == null) {
                throw new IllegalStateException("Couldn't find resource for '" + VERSION_RESOURCE_NAME + "' "
                        + "(when building with Maven, this may be caused by Insider jar not present "
                        + "in the repository for current version)");
            }
            version = Resources.toString(versionResource, Charsets.UTF_8);
            LOGGER.info("Using version '", version, "' as found inside ", "'", VERSION_RESOURCE_NAME,
                    "' resource.");

        } else {
            version = versionOverride;
            LOGGER.info("Using version override '", version, "' from system property ", "'",
                    VERSIONOVERRIDE_SYSTEMPROPERTYNAME, "'.");
        }

        final String jarFileNameFromSystemProperty = System.getProperty(AGENTJARFILE_SYSTEMPROPERTYNAME);

        if (jarFileNameFromSystemProperty == null) {
            try {
                jarFile = File.createTempFile("Novelang-insider-agent", ".jar").getCanonicalFile();
                copyResourceToFile(JAR_RESOURCE_NAME_RADIX + version + ".jar", jarFile);
                LOGGER.info("Using jar file '", jarFile.getAbsolutePath(), "'.");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else {
            jarFile = resolveWithVersion(jarFileNameFromSystemProperty);
            if (!jarFile.isFile()) {
                throw new IllegalArgumentException(
                        "Jar file '" + jarFile.getAbsolutePath() + "' doesn't exist as a file");
            }
            LOGGER.info("Using jar file '", jarFile.getAbsolutePath(), "' ", "set from system property '",
                    AGENTJARFILE_SYSTEMPROPERTYNAME, "'.");
        }

    }

    public File resolveWithVersion(final String filenameWithVersionPlaceholders) {
        final String resolvedJarFileName = filenameWithVersionPlaceholders.replaceAll(VERSION_PLACEHOLDER_REGEX,
                version);
        final File resolvedFile = new File(resolvedJarFileName);
        return resolvedFile;
    }

    /**
     * Returns a {@code File} object referencing the jar containing the
     * {@link org.novelang.outfit.shell.insider.InsiderAgent}.
     *
     * @return a non-null object.
     */
    public File getJarFile() {
        return jarFile;
    }

    public void copyVersionedJarToFile(final String jarResourceRadix, final File file) throws IOException {
        copyResourceToFile(jarResourceRadix + version + ".jar", file);
    }

    public static void copyResourceToFile(final String resourceName, final File file) throws IOException {
        final URL resourceUrl = AgentFileInstaller.class.getResource(resourceName);
        if (resourceUrl == null) {
            throw new MissingResourceException(
                    "The resource '" + resourceName + "' does not appear in the classpath "
                            + "(Maven should handle this, IDEs probably won't)",
                    AgentFileInstaller.class.getName(), resourceName);
        }

        LOGGER.info("Copying resource '", resourceName, "' to ", "'", file.getAbsolutePath(), "'");
        FileUtils.copyURLToFile(resourceUrl, file);
    }

    /**
     * Defined by the assembly of the Insider project.
     */
    @SuppressWarnings({ "HardcodedFileSeparator" })
    private static final String JAR_RESOURCE_NAME_RADIX = "/org/novelang/outfit/shell/Novelang-insider-";

    /**
     * Defined by the assembly of the Insider project.
     */
    @SuppressWarnings({ "HardcodedFileSeparator" })
    private static final String VERSION_RESOURCE_NAME = "/org/novelang/outfit/shell/version.txt";

    // ========================
    // Initialization on demand
    // ========================

    @SuppressWarnings({ "UtilityClassWithoutPrivateConstructor" })
    private static class LazyHolder {
        private static final AgentFileInstaller INSTANCE;
        static {
            try {
                INSTANCE = new AgentFileInstaller();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static AgentFileInstaller getInstance() {
        return LazyHolder.INSTANCE;
    }

}