Java tutorial
/* * 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; } }