Package

Source Code of Capsule

/*
* Capsule
* Copyright (c) 2014, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are licensed under the terms
* of the Eclipse Public License v1.0, available at
* http://www.eclipse.org/legal/epl-v10.html
*/

import capsule.DependencyManager;
import capsule.PomReader;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

public final class Capsule implements Runnable {
    /*
     * This class contains several strange hacks to avoid creating more classes.
     * We'd like this file to compile to a single .class file.
     *
     * Also, the code here is not meant to be the most efficient, but methods should be as independent and stateless as possible.
     * Other than those few, methods called in the constructor, all others are can be called in any order, and don't rely on any state.
     *
     * We do a lot of data transformations that would have really benefitted from Java 8's lambdas and streams,
     * but we want Capsule to support Java 7.
     */
    private static final String VERSION = "0.4.0";

    private static final String PROP_RESET = "capsule.reset";
    private static final String PROP_VERSION = "capsule.version";
    private static final String PROP_LOG = "capsule.log";
    private static final String PROP_TREE = "capsule.tree";
    private static final String PROP_APP_ID = "capsule.app.id";
    private static final String PROP_PRINT_JRES = "capsule.jvms";
    private static final String PROP_CAPSULE_JAVA_HOME = "capsule.java.home";
    private static final String PROP_MODE = "capsule.mode";
    private static final String PROP_USE_LOCAL_REPO = "capsule.local";
    private static final String PROP_OFFLINE = "capsule.offline";
    private static final String PROP_RESOLVE = "capsule.resolve";

    private static final String PROP_JAVA_VERSION = "java.version";
    private static final String PROP_JAVA_HOME = "java.home";
    private static final String PROP_OS_NAME = "os.name";
    private static final String PROP_USER_HOME = "user.home";
    private static final String PROP_JAVA_LIBRARY_PATH = "java.library.path";
    private static final String PROP_FILE_SEPARATOR = "file.separator";
    private static final String PROP_PATH_SEPARATOR = "path.separator";
    private static final String PROP_JAVA_SECURITY_POLICY = "java.security.policy";
    private static final String PROP_JAVA_SECURITY_MANAGER = "java.security.manager";

    private static final String ENV_CAPSULE_REPOS = "CAPSULE_REPOS";
    private static final String ENV_CACHE_DIR = "CAPSULE_CACHE_DIR";
    private static final String ENV_CACHE_NAME = "CAPSULE_CACHE_NAME";

    private static final String PROP_CAPSULE_JAR = "capsule.jar";
    private static final String PROP_CAPSULE_DIR = "capsule.dir";
    private static final String PROP_CAPSULE_APP = "capsule.app";

    private static final String ATTR_APP_NAME = "Application-Name";
    private static final String ATTR_APP_VERSION = "Application-Version";
    private static final String ATTR_APP_CLASS = "Application-Class";
    private static final String ATTR_APP_ARTIFACT = "Application";
    private static final String ATTR_UNIX_SCRIPT = "Unix-Script";
    private static final String ATTR_WINDOWS_SCRIPT = "Windows-Script";
    private static final String ATTR_EXTRACT = "Extract-Capsule";
    private static final String ATTR_MIN_JAVA_VERSION = "Min-Java-Version";
    private static final String ATTR_JAVA_VERSION = "Java-Version";
    private static final String ATTR_JDK_REQUIRED = "JDK-Required";
    private static final String ATTR_JVM_ARGS = "JVM-Args";
    private static final String ATTR_ARGS = "Args";
    private static final String ATTR_ENV = "Environment-Variables";
    private static final String ATTR_SYSTEM_PROPERTIES = "System-Properties";
    private static final String ATTR_APP_CLASS_PATH = "App-Class-Path";
    private static final String ATTR_CAPSULE_IN_CLASS_PATH = "Capsule-In-Class-Path";
    private static final String ATTR_BOOT_CLASS_PATH = "Boot-Class-Path";
    private static final String ATTR_BOOT_CLASS_PATH_A = "Boot-Class-Path-A";
    private static final String ATTR_BOOT_CLASS_PATH_P = "Boot-Class-Path-P";
    private static final String ATTR_LIBRARY_PATH_A = "Library-Path-A";
    private static final String ATTR_LIBRARY_PATH_P = "Library-Path-P";
    private static final String ATTR_SECURITY_MANAGER = "Security-Manager";
    private static final String ATTR_SECURITY_POLICY = "Security-Policy";
    private static final String ATTR_SECURITY_POLICY_A = "Security-Policy-A";
    private static final String ATTR_JAVA_AGENTS = "Java-Agents";
    private static final String ATTR_REPOSITORIES = "Repositories";
    private static final String ATTR_DEPENDENCIES = "Dependencies";
    private static final String ATTR_NATIVE_DEPENDENCIES_LINUX = "Native-Dependencies-Linux";
    private static final String ATTR_NATIVE_DEPENDENCIES_WIN = "Native-Dependencies-Win";
    private static final String ATTR_NATIVE_DEPENDENCIES_MAC = "Native-Dependencies-Mac";

    private static final String ATTR_IMPLEMENTATION_VERSION = "Implementation-Version";

    private static final String VAR_CAPSULE_DIR = "CAPSULE_DIR";
    private static final String VAR_CAPSULE_JAR = "CAPSULE_JAR";
    private static final String VAR_JAVA_HOME = "JAVA_HOME";

    private static final String CACHE_DEFAULT_NAME = "capsule";
    private static final String DEPS_CACHE_NAME = "deps";
    private static final String APP_CACHE_NAME = "apps";
    private static final String POM_FILE = "pom.xml";
    private static final String FILE_SEPARATOR = System.getProperty(PROP_FILE_SEPARATOR);
    private static final String PATH_SEPARATOR = System.getProperty(PROP_PATH_SEPARATOR);
    private static final Path LOCAL_MAVEN = Paths.get(System.getProperty(PROP_USER_HOME), ".m2", "repository");

    private static final boolean debug = "debug".equals(System.getProperty(PROP_LOG, "quiet"));
    private static final boolean verbose = debug || "verbose".equals(System.getProperty(PROP_LOG, "quiet"));
    private static final Path cacheDir = getCacheDir();

    private final JarFile jar;       // never null
    private final Manifest manifest; // never null
    private final String javaHome;
    private final String appId;      // null iff isEmptyCapsule()
    private final Path appCache;     // non-null iff capsule is extracted
    private final boolean cacheUpToDate;
    private final String mode;
    private final Object pom;               // non-null iff jar has pom AND manifest doesn't have ATTR_DEPENDENCIES
    private final Object dependencyManager; // non-null iff needsDependencyManager is true
    private Process child;

    /**
     * Launches the application
     *
     * @param args the program's command-line arguments
     */
    @SuppressWarnings({"BroadCatchBlock", "CallToPrintStackTrace"})
    public static void main(String[] args) {
        try {
            final Capsule capsule = newCapsule(getJarFile(), args);

            if (anyPropertyDefined(PROP_VERSION, PROP_PRINT_JRES, PROP_TREE, PROP_RESOLVE)) {
                if (anyPropertyDefined(PROP_VERSION))
                    capsule.printVersion();

                if (anyPropertyDefined(PROP_PRINT_JRES))
                    capsule.printJVMs();

                if (anyPropertyDefined(PROP_TREE))
                    capsule.printDependencyTree(args);

                if (anyPropertyDefined(PROP_RESOLVE))
                    capsule.resolve(args);
                return;
            }

            capsule.launch(args);
        } catch (Throwable t) {
            System.err.println("CAPSULE EXCEPTION: " + t.getMessage()
                    + (!verbose ? " (for stack trace, run with -D" + PROP_LOG + "=verbose)" : ""));
            if (verbose)
                t.printStackTrace(System.err);
            System.exit(1);
        }
    }

    private static Capsule newCapsule(JarFile jar, String[] args) {
        try {
            final Class<?> clazz = Class.forName("CustomCapsule");
            try {
                Constructor<?> ctor = clazz.getConstructor(JarFile.class, String[].class);
                ctor.setAccessible(true);
                return (Capsule) ctor.newInstance(jar, args);
            } catch (Exception e) {
                throw new RuntimeException("Could not launch custom capsule.", e);
            }
        } catch (ClassNotFoundException e) {
            return new Capsule(jar, args);
        }
    }

    private static boolean isNonInteractiveProcess() {
        return System.console() == null || System.console().reader() == null;
    }

    protected Capsule(JarFile jar, String[] args) {
        this.jar = jar;
        try {
            this.manifest = jar.getManifest();
            if (manifest == null)
                throw new RuntimeException("Jar file " + jar.getName() + " does not have a manifest");
        } catch (IOException e) {
            throw new RuntimeException("Could not read Jar file " + jar.getName() + " manifest");
        }

        this.javaHome = getJavaHome();
        this.mode = System.getProperty(PROP_MODE);
        this.pom = (!hasAttribute(ATTR_DEPENDENCIES) && hasPom()) ? createPomReader() : null;
        this.dependencyManager = needsDependencyManager() ? createDependencyManager(getRepositories()) : null;
        this.appId = getAppId(args);
        this.appCache = needsAppCache() ? getAppCacheDir() : null;
        this.cacheUpToDate = appCache != null ? isUpToDate() : false;
    }

    private boolean needsDependencyManager() {
        return hasAttribute(ATTR_APP_ARTIFACT)
                || isEmptyCapsule()
                || pom != null
                || getDependencies() != null
                || getNativeDependencies() != null;
    }

    private void launch(String[] args) throws IOException, InterruptedException {
        if (launchCapsule(args))
            return;

        verbose("Launching app " + appId);

        ensureExtractedIfNecessary();

        final ProcessBuilder pb = buildProcess(args);

        if (appCache != null && !cacheUpToDate)
            markCache();

        if (!isInheritIoBug())
            pb.inheritIO();
        this.child = pb.start();

        if (isNonInteractiveProcess() && !isInheritIoBug()) {
            System.exit(0);
        } else {
            Runtime.getRuntime().addShutdownHook(new Thread(this));
            if (isInheritIoBug())
                pipeIoStreams();
            // registerSignals();
            System.exit(child.waitFor());
        }
    }

    private void printVersion() {
        System.out.println("CAPSULE: Application " + appId);
        System.out.println("CAPSULE: Capsule Version " + VERSION);
    }

    private void printJVMs() {
        final Map<String, Path> jres = getJavaHomes(false);
        if (jres == null)
            println("No detected Java installations");
        else {
            System.out.println("CAPSULE: Detected Java installations:");
            for (Map.Entry<String, Path> j : jres.entrySet())
                System.out.println(j.getKey() + (j.getKey().length() < 8 ? "\t\t" : "\t") + j.getValue());
        }
        System.out.println("CAPSULE: selected " + javaHome);
    }

    private void resolve(String[] args) throws IOException, InterruptedException {
        ensureExtractedIfNecessary();
        getPath(getListAttribute(ATTR_BOOT_CLASS_PATH));
        getPath(getListAttribute(ATTR_BOOT_CLASS_PATH_P));
        getPath(getListAttribute(ATTR_BOOT_CLASS_PATH_A));
        resolveAppArtifact(getAppArtifact(args));
        resolveDependencies(getDependencies(), "jar");
        resolveNativeDependencies();
    }

    private void ensureExtractedIfNecessary() {
        if (appCache != null && !cacheUpToDate) {
            resetAppCache();
            if (shouldExtract())
                extractCapsule();
        }
    }

    private static boolean isInheritIoBug() {
        return isWindows() && compareVersions(System.getProperty(PROP_JAVA_VERSION), "1.8.0") < 0;
    }

    private void pipeIoStreams() {
        new Thread(this, "pipe-out").start();
        new Thread(this, "pipe-err").start();
        new Thread(this, "pipe-in").start();
    }

    @Override
    public void run() {
        if (isInheritIoBug()) {
            switch (Thread.currentThread().getName()) {
                case "pipe-out":
                    pipe(child.getInputStream(), System.out);
                    return;
                case "pipe-err":
                    pipe(child.getErrorStream(), System.err);
                    return;
                case "pipe-in":
                    pipe(System.in, child.getOutputStream());
                    return;
                default: // shutdown hook
            }
        }
        if (child != null)
            child.destroy();
    }

    private static void pipe(InputStream in, OutputStream out) {
        try (BufferedReader r = new BufferedReader(new InputStreamReader(in))) {
            PrintStream p = new PrintStream(out);
            String line;
            while ((line = r.readLine()) != null) {
                p.println(line);
            }
        } catch (IOException e) {
            if (verbose)
                e.printStackTrace(System.err);
        }
    }

    private boolean isEmptyCapsule() {
        return !hasAttribute(ATTR_APP_ARTIFACT) && !hasAttribute(ATTR_APP_CLASS) && getScript() == null;
    }

    private void printDependencyTree(String[] args) {
        System.out.println("Dependencies for " + appId);
        if (dependencyManager == null)
            System.out.println("No dependencies declared.");
        else if (hasAttribute(ATTR_APP_ARTIFACT) || isEmptyCapsule()) {
            final String appArtifact = isEmptyCapsule() ? getCommandLineArtifact(args) : getAttribute(ATTR_APP_ARTIFACT);
            if (appArtifact == null)
                throw new IllegalStateException("capsule " + jar.getName() + " has nothing to run");
            printDependencyTree(appArtifact);
        } else
            printDependencyTree(getDependencies(), "jar");

        final List<String> nativeDeps = getNativeDependencies();
        if (nativeDeps != null) {
            System.out.println("\nNative Dependencies:");
            printDependencyTree(nativeDeps, getNativeLibExtension());
        }
    }

    private boolean launchCapsule(String[] args) {
        if (getScript() == null) {
            String appArtifact = getAppArtifact(args);
            if (appArtifact != null) {
                try {
                    final List<Path> jars = resolveAppArtifact(appArtifact);
                    if (jars == null)
                        return false;
                    if (isCapsule(jars.get(0))) {
                        verbose("Running capsule " + jars.get(0));
                        runCapsule(jars.get(0), isEmptyCapsule() ? Arrays.copyOfRange(args, 1, args.length) : buildArgs(args).toArray(new String[0]));
                        return true;
                    } else if (isEmptyCapsule())
                        throw new IllegalArgumentException("Artifact " + appArtifact + " is not a capsule.");
                } catch (RuntimeException e) {
                    if (isEmptyCapsule())
                        throw new RuntimeException("Usage: java -jar capsule.jar CAPSULE_ARTIFACT_COORDINATES", e);
                    else
                        throw e;
                }
            }
        }
        return false;
    }

    private String getAppArtifact(String[] args) {
        String appArtifact = null;
        if (isEmptyCapsule()) {
            appArtifact = getCommandLineArtifact(args);
            if (appArtifact == null)
                throw new IllegalStateException("capsule " + jar.getName() + " has nothing to run");
        }
        if (appArtifact == null)
            appArtifact = getAttribute(ATTR_APP_ARTIFACT);
        return appArtifact;
    }

    private String getCommandLineArtifact(String[] args) {
        if (args.length > 0)
            return args[0];
        return null;
    }

    private ProcessBuilder buildProcess(String[] args) {
        final ProcessBuilder pb = new ProcessBuilder();
        if (!buildScriptProcess(pb))
            buildJavaProcess(pb);

        final List<String> command = pb.command();
        command.addAll(buildArgs(args));

        buildEnvironmentVariables(pb.environment());

        verbose(join(command, " "));

        return pb;
    }

    /**
     * Returns a list of command line arguments to pass to the application.
     *
     * @param args The command line arguments passed to the capsule at launch
     */
    protected List<String> buildArgs(String[] args) {
        final List<String> args0 = new ArrayList<String>();
        args0.addAll(nullToEmpty(expand(getListAttribute(ATTR_ARGS))));
        args0.addAll(Arrays.asList(args));
        return args0;
    }

    private boolean buildJavaProcess(ProcessBuilder pb) {
        final RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
        final List<String> cmdLine = runtimeBean.getInputArguments();

//        final String classPath = runtimeBean.getClassPath();
//        final String bootClassPath = runtimeBean.getBootClassPath();
//        final String libraryPath = runtimeBean.getLibraryPath();
        if (javaHome != null)
            pb.environment().put("JAVA_HOME", javaHome);

        final List<String> command = pb.command();

        command.add(getJavaProcessName(javaHome));

        command.addAll(buildJVMArgs(cmdLine));
        command.addAll(compileSystemProperties(buildSystemProperties(cmdLine)));

        addOption(command, "-Xbootclasspath:", compileClassPath(buildBootClassPath(cmdLine)));
        addOption(command, "-Xbootclasspath/p:", compileClassPath(buildClassPath(ATTR_BOOT_CLASS_PATH_P)));
        addOption(command, "-Xbootclasspath/a:", compileClassPath(buildClassPath(ATTR_BOOT_CLASS_PATH_A)));

        final List<Path> classPath = buildClassPath();
        command.add("-classpath");
        command.add(compileClassPath(classPath));

        for (String jagent : nullToEmpty(buildJavaAgents()))
            command.add("-javaagent:" + jagent);

        command.add(getMainClass(classPath));
        return true;
    }

    private String getScript() {
        return getAttribute(isWindows() ? ATTR_WINDOWS_SCRIPT : ATTR_UNIX_SCRIPT);
    }

    private boolean buildScriptProcess(ProcessBuilder pb) {
        final String script = getScript();
        if (script == null)
            return false;

        if (appCache == null)
            throw new IllegalStateException("Cannot run the startup script " + script + " when the "
                    + ATTR_EXTRACT + " attribute is set to false");

        final Path scriptPath = appCache.resolve(sanitize(script)).toAbsolutePath();
        ensureExecutable(scriptPath);
        pb.command().add(scriptPath.toString());
        return true;
    }

    private static void ensureExecutable(Path file) {
        if (!Files.isExecutable(file)) {
            try {
                Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file);
                if (!perms.contains(PosixFilePermission.OWNER_EXECUTE)) {
                    Set<PosixFilePermission> newPerms = EnumSet.copyOf(perms);
                    newPerms.add(PosixFilePermission.OWNER_EXECUTE);
                    Files.setPosixFilePermissions(file, newPerms);
                }
            } catch (UnsupportedOperationException e) {
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static void addOption(List<String> cmdLine, String prefix, String value) {
        if (value == null)
            return;
        cmdLine.add(prefix + value);
    }

    private static String compileClassPath(List<Path> cp) {
        return join(cp, PATH_SEPARATOR);
    }

    private List<Path> buildClassPath() {
        final List<Path> classPath = new ArrayList<Path>();

        if (!isEmptyCapsule() && !hasAttribute(ATTR_APP_ARTIFACT)) {
            // the capsule jar
            final String isCapsuleInClassPath = getAttribute(ATTR_CAPSULE_IN_CLASS_PATH);
            if (isCapsuleInClassPath == null || Boolean.parseBoolean(isCapsuleInClassPath))
                classPath.add(Paths.get(jar.getName()));
            else if (appCache == null)
                throw new IllegalStateException("Cannot set the " + ATTR_CAPSULE_IN_CLASS_PATH + " attribute to false when the "
                        + ATTR_EXTRACT + " attribute is also set to false");
        }

        if (hasAttribute(ATTR_APP_ARTIFACT)) {
            assert dependencyManager != null;
            classPath.addAll(nullToEmpty(resolveAppArtifact(getAttribute(ATTR_APP_ARTIFACT))));
        }

        if (hasAttribute(ATTR_APP_CLASS_PATH)) {
            for (String sp : getListAttribute(ATTR_APP_CLASS_PATH)) {
                Path p = Paths.get(expand(sanitize(sp)));

                if (appCache == null && (!p.isAbsolute() || p.startsWith(appCache)))
                    throw new IllegalStateException("Cannot resolve " + sp + "  in " + ATTR_APP_CLASS_PATH + " attribute when the "
                            + ATTR_EXTRACT + " attribute is set to false");

                p = appCache.resolve(p);
                classPath.add(p);
            }
        }
        if (appCache != null)
            classPath.addAll(nullToEmpty(getDefaultCacheClassPath()));

        classPath.addAll(nullToEmpty(resolveDependencies(getDependencies(), "jar")));

        return classPath;
    }

    private List<Path> buildBootClassPath(List<String> cmdLine) {
        String option = null;
        for (String o : cmdLine) {
            if (o.startsWith("-Xbootclasspath:"))
                option = o.substring("-Xbootclasspath:".length());
        }
        if (option != null)
            return toPath(Arrays.asList(option.split(PATH_SEPARATOR)));
        return getPath(getListAttribute(ATTR_BOOT_CLASS_PATH));
    }

    private List<Path> buildClassPath(String attr) {
        return getPath(getListAttribute(attr));
    }

    /**
     * Returns a map of environment variables (property-value pairs).
     *
     * @param env the current environment
     */
    private void buildEnvironmentVariables(Map<String, String> env) {
        final List<String> jarEnv = getListAttribute(ATTR_ENV);
        if (jarEnv != null) {
            for (String e : jarEnv) {
                String var = getBefore(e, '=');
                String value = getAfter(e, '=');

                if (var == null)
                    throw new IllegalArgumentException("Malformed env variable definition: " + e);

                boolean overwrite = false;
                if (var.endsWith(":")) {
                    overwrite = true;
                    var = var.substring(0, var.length() - 1);
                }

                if (overwrite || !env.containsKey(var))
                    env.put(var, value != null ? value : "");
            }
        }
    }

    /**
     * Returns a map of system properties (property-value pairs).
     *
     * @param cmdLine the list of JVM arguments passed to the capsule at launch
     */
    protected Map<String, String> buildSystemProperties(List<String> cmdLine) {
        final Map<String, String> systemProerties = new HashMap<String, String>();

        // attribute
        for (String p : nullToEmpty(getListAttribute(ATTR_SYSTEM_PROPERTIES))) {
            if (!p.trim().isEmpty())
                addSystemProperty(p, systemProerties);
        }

        // library path
        if (appCache != null) {
            final List<Path> libraryPath = buildNativeLibraryPath();
            libraryPath.add(appCache);
            systemProerties.put(PROP_JAVA_LIBRARY_PATH, compileClassPath(libraryPath));
        } else if (hasAttribute(ATTR_LIBRARY_PATH_P) || hasAttribute(ATTR_LIBRARY_PATH_A))
            throw new IllegalStateException("Cannot use the " + ATTR_LIBRARY_PATH_P + " or the " + ATTR_LIBRARY_PATH_A
                    + " attributes when the " + ATTR_EXTRACT + " attribute is set to false");

        if (hasAttribute(ATTR_SECURITY_POLICY) || hasAttribute(ATTR_SECURITY_POLICY_A)) {
            systemProerties.put(PROP_JAVA_SECURITY_MANAGER, "");
            if (hasAttribute(ATTR_SECURITY_POLICY_A))
                systemProerties.put(PROP_JAVA_SECURITY_POLICY, toJarUrl(getAttribute(ATTR_SECURITY_POLICY_A)));
            if (hasAttribute(ATTR_SECURITY_POLICY))
                systemProerties.put(PROP_JAVA_SECURITY_POLICY, "=" + toJarUrl(getAttribute(ATTR_SECURITY_POLICY)));
        }
        if (hasAttribute(ATTR_SECURITY_MANAGER))
            systemProerties.put(PROP_JAVA_SECURITY_MANAGER, getAttribute(ATTR_SECURITY_MANAGER));

        // Capsule properties
        if (appCache != null)
            systemProerties.put(PROP_CAPSULE_DIR, appCache.toAbsolutePath().toString());
        systemProerties.put(PROP_CAPSULE_JAR, getJarPath());
        systemProerties.put(PROP_CAPSULE_APP, appId);

        // command line
        for (String option : cmdLine) {
            if (option.startsWith("-D"))
                addSystemProperty0(option.substring(2), systemProerties);
        }

        return systemProerties;
    }

    private List<Path> buildNativeLibraryPath() {
        final List<Path> libraryPath = new ArrayList<Path>();
        resolveNativeDependencies();
        libraryPath.addAll(nullToEmpty(toAbsolutePath(appCache, getListAttribute(ATTR_LIBRARY_PATH_P))));
        libraryPath.addAll(toPath(Arrays.asList(ManagementFactory.getRuntimeMXBean().getLibraryPath().split(PATH_SEPARATOR))));
        libraryPath.addAll(nullToEmpty(toAbsolutePath(appCache, getListAttribute(ATTR_LIBRARY_PATH_A))));
        libraryPath.add(appCache);
        return libraryPath;
    }

    private String getNativeLibExtension() {
        if (isLinux())
            return "so";
        if (isWindows())
            return "dll";
        if (isMac())
            return "dylib";
        throw new RuntimeException("Unsupported operating system: " + System.getProperty(PROP_OS_NAME));
    }

    private List<String> getNativeDependencies() {
        return stripNativeDependencies(getNativeDependenciesAndRename());
    }

    /**
     * Returns a list of dependencies, each in the format {@code groupId:artifactId:version[:classifier][,renameTo]}
     * (classifier and renameTo are optional)
     */
    protected List<String> getNativeDependenciesAndRename() {
        if (isLinux())
            return getListAttribute(ATTR_NATIVE_DEPENDENCIES_LINUX);
        if (isWindows())
            return getListAttribute(ATTR_NATIVE_DEPENDENCIES_WIN);
        if (isMac())
            return getListAttribute(ATTR_NATIVE_DEPENDENCIES_MAC);
        return null;
    }

    protected final List<String> stripNativeDependencies(List<String> nativeDepsAndRename) {
        if (nativeDepsAndRename == null)
            return null;
        final List<String> deps = new ArrayList<String>(nativeDepsAndRename.size());
        for (String depAndRename : nativeDepsAndRename) {
            String[] dna = depAndRename.split(",");
            deps.add(dna[0]);
        }
        return deps;
    }

    private boolean hasRenamedNativeDependencies() {
        final List<String> depsAndRename = getNativeDependenciesAndRename();
        if (depsAndRename == null)
            return false;
        for (String depAndRename : depsAndRename) {
            if (depAndRename.contains(","))
                return true;
        }
        return false;
    }

    private void resolveNativeDependencies() {
        if (appCache == null)
            throw new IllegalStateException("Cannot set " + ATTR_EXTRACT + " to false if there are native dependencies.");

        final List<String> depsAndRename = getNativeDependenciesAndRename();
        if (depsAndRename == null || depsAndRename.isEmpty())
            return;
        final List<String> deps = new ArrayList<String>(depsAndRename.size());
        final List<String> renames = new ArrayList<String>(depsAndRename.size());
        for (String depAndRename : depsAndRename) {
            String[] dna = depAndRename.split(",");
            deps.add(dna[0]);
            renames.add(dna.length > 1 ? dna[1] : null);
        }
        verbose("Resolving native libs " + deps);
        final List<Path> resolved = resolveDependencies(deps, getNativeLibExtension());
        if (resolved.size() != deps.size())
            throw new RuntimeException("One of the native artifacts " + deps + " reolved to more than a single file");

        assert appCache != null;
        if (!cacheUpToDate) {
            if (debug)
                System.err.println("Copying native libs to " + appCache);
            try {
                for (int i = 0; i < deps.size(); i++) {
                    final Path lib = resolved.get(i);
                    final String rename = sanitize(renames.get(i));
                    Files.copy(lib, appCache.resolve(rename != null ? rename : lib.getFileName().toString()));
                }
            } catch (IOException e) {
                throw new RuntimeException("Exception while copying native libs");
            }
        }
    }

    private String getJarPath() {
        return Paths.get(jar.getName()).getParent().toAbsolutePath().toString();
    }

    private static List<String> compileSystemProperties(Map<String, String> ps) {
        final List<String> command = new ArrayList<String>();
        for (Map.Entry<String, String> entry : ps.entrySet())
            command.add("-D" + entry.getKey() + (entry.getValue() != null && !entry.getValue().isEmpty() ? "=" + entry.getValue() : ""));
        return command;
    }

    private void addSystemProperty(String p, Map<String, String> ps) {
        try {
            String name = getBefore(p, '=');
            String value = getAfter(p, '=');
            ps.put(name, value != null ? expand(value) : "");
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("Illegal system property definition: " + p);
        }
    }

    private static void addSystemProperty0(String p, Map<String, String> ps) {
        try {
            String name = getBefore(p, '=');
            String value = getAfter(p, '=');
            ps.put(name, value);
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("Illegal system property definition: " + p);
        }
    }

    /**
     * Returns a list of JVM arguments.
     *
     * @param cmdLine the list of JVM arguments passed to the capsule at launch
     */
    protected List<String> buildJVMArgs(List<String> cmdLine) {
        final Map<String, String> jvmArgs = new LinkedHashMap<String, String>();

        for (String a : nullToEmpty(getListAttribute(ATTR_JVM_ARGS))) {
            a = a.trim();
            if (!a.isEmpty() && !a.startsWith("-Xbootclasspath:") && !a.startsWith("-javaagent:"))
                addJvmArg(expand(a), jvmArgs);
        }

        for (String option : cmdLine) {
            if (!option.startsWith("-D") && !option.startsWith("-Xbootclasspath:"))
                addJvmArg(option, jvmArgs);
        }

        return new ArrayList<String>(jvmArgs.values());
    }

    private static void addJvmArg(String a, Map<String, String> args) {
        args.put(getJvmArgKey(a), a);
    }

    private static String getJvmArgKey(String a) {
        if (a.equals("-client") || a.equals("-server"))
            return "compiler";
        if (a.equals("-enablesystemassertions") || a.equals("-esa")
                || a.equals("-disablesystemassertions") || a.equals("-dsa"))
            return "systemassertions";
        if (a.equals("-jre-restrict-search") || a.equals("-no-jre-restrict-search"))
            return "-jre-restrict-search";
        if (a.startsWith("-Xloggc:"))
            return "-Xloggc";
        if (a.startsWith("-Xloggc:"))
            return "-Xloggc";
        if (a.startsWith("-Xss"))
            return "-Xss";
        if (a.startsWith("-XX:+") || a.startsWith("-XX:-"))
            return "-XX:" + a.substring("-XX:+".length());
        if (a.contains("="))
            return a.substring(0, a.indexOf("="));
        return a;
    }

    private List<String> buildJavaAgents() {
        final List<String> agents0 = getListAttribute(ATTR_JAVA_AGENTS);

        if (agents0 == null)
            return null;
        final List<String> agents = new ArrayList<String>(agents0.size());
        for (String agent : agents0) {
            final String agentJar = getBefore(agent, '=');
            final String agentOptions = getAfter(agent, '=');
            try {
                final Path agentPath = getPath(agentJar);
                agents.add(agentPath + (agentOptions != null ? "=" + agentOptions : ""));
            } catch (IllegalStateException e) {
                if (appCache == null)
                    throw new RuntimeException("Cannot run the embedded Java agent " + agentJar + " when the " + ATTR_EXTRACT + " attribute is set to false");
                throw e;
            }
        }
        return agents;
    }

    private static JarFile getJarFile() {
        final URL url = Capsule.class.getClassLoader().getResource(Capsule.class.getName().replace('.', '/') + ".class");
        if (!"jar".equals(url.getProtocol()))
            throw new IllegalStateException("The Capsule class must be in a JAR file, but was loaded from: " + url);
        final String path = url.getPath();
        if (path == null || !path.startsWith("file:"))
            throw new IllegalStateException("The Capsule class must be in a local JAR file, but was loaded from: " + url);

        try {
            final URI jarUri = new URI(path.substring(0, path.indexOf('!')));
            try {
                final JarFile jar = new JarFile(new File(jarUri));
                return jar;
            } catch (IOException e) {
                throw new RuntimeException("Jar file containing the Capsule could not be opened: " + jarUri, e);
            }
        } catch (URISyntaxException e) {
            throw new AssertionError(e);
        }
    }

    private String getAppId(String[] args) {
        String appName = System.getProperty(PROP_APP_ID);
        if (appName == null)
            appName = getAttribute(ATTR_APP_NAME);
        if (appName == null) {
            appName = getApplicationArtifactId(getAppArtifact(args));
            if (appName != null)
                return getAppArtifactLatestVersion(appName);
        }
        if (appName == null) {
            if (pom != null)
                return getPomAppName();
            appName = getAttribute(ATTR_APP_CLASS);
        }
        if (appName == null) {
            if (isEmptyCapsule())
                return null;
            throw new RuntimeException("Capsule jar " + jar.getName() + " must either have the " + ATTR_APP_NAME + " manifest attribute, "
                    + "the " + ATTR_APP_CLASS + " attribute, or contain a " + POM_FILE + " file.");
        }

        final String version = hasAttribute(ATTR_APP_VERSION) ? getAttribute(ATTR_APP_VERSION) : getAttribute(ATTR_IMPLEMENTATION_VERSION);
        return appName + (version != null ? "_" + version : "");
    }

    private String getMainClass(List<Path> classPath) {
        try {
            String mainClass = getAttribute(ATTR_APP_CLASS);
            if (mainClass == null && hasAttribute(ATTR_APP_ARTIFACT))
                mainClass = getMainClass(new JarFile(classPath.get(0).toAbsolutePath().toString()));
            if (mainClass == null)
                throw new RuntimeException("Jar " + classPath.get(0).toAbsolutePath() + " does not have a main class defined in the manifest.");
            return mainClass;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private List<Path> getDefaultCacheClassPath() {
        final List<Path> cp = new ArrayList<Path>();
        cp.add(appCache);
        // we don't use Files.walkFileTree because we'd like to avoid creating more classes (Capsule$1.class etc.)
        for (File f : appCache.toFile().listFiles()) {
            if (f.isFile() && f.getName().endsWith(".jar"))
                cp.add(f.toPath());
        }

        return cp;
    }

    private Path getPath(String p) {
        if (isDependency(p))
            return getDependencyPath(dependencyManager, p);
        else {
            if (appCache == null)
                throw new IllegalStateException("Capsule not extracted. Cannot obtain path " + p);
            return toAbsolutePath(appCache, p);
        }
    }

    private List<Path> getPath(List<String> ps) {
        if (ps == null)
            return null;
        final List<Path> res = new ArrayList<Path>(ps.size());
        for (String p : ps)
            res.add(getPath(p));
        return res;
    }

    private String toJarUrl(String relPath) {
        return "jar:file:" + getJarPath() + "!/" + relPath;
    }

    private static List<Path> toPath(List<String> ps) {
        if (ps == null)
            return null;
        final List<Path> aps = new ArrayList<Path>(ps.size());
        for (String p : ps)
            aps.add(Paths.get(p));
        return aps;
    }

    private static List<Path> toAbsolutePath(Path root, List<String> ps) {
        if (ps == null)
            return null;
        final List<Path> aps = new ArrayList<Path>(ps.size());
        for (String p : ps)
            aps.add(toAbsolutePath(root, p));
        return aps;
    }

    private static Path toAbsolutePath(Path root, String p) {
        return root.resolve(sanitize(p)).toAbsolutePath();
    }

    private String getAttribute(String attr) {
        String value = null;
        Attributes atts;
        if (mode != null) {
            atts = manifest.getAttributes(mode);
            if (atts == null)
                throw new IllegalArgumentException("Mode " + mode + " not defined in manifest");
            value = atts.getValue(attr);
        }
        if (value == null) {
            atts = manifest.getMainAttributes();
            value = atts.getValue(attr);
        }
        return value;
    }

    private boolean hasAttribute(String attr) {
        final Attributes.Name key = new Attributes.Name(attr);
        Attributes atts;
        if (mode != null) {
            atts = manifest.getAttributes(mode);
            if (atts != null && atts.containsKey(key))
                return true;
        }
        atts = manifest.getMainAttributes();
        return atts.containsKey(new Attributes.Name(attr));
    }

    private List<String> getListAttribute(String attr) {
        return split(getAttribute(attr), "\\s+");
    }

    private List<String> split(String str, String separator) {
        if (str == null)
            return null;
        return Arrays.asList(str.split(separator));
    }

    private Path getAppCacheDir() {
        Path appDir = cacheDir.resolve(APP_CACHE_NAME).resolve(appId);
        try {
            if (!Files.exists(appDir))
                Files.createDirectory(appDir);
            return appDir;
        } catch (IOException e) {
            throw new RuntimeException("Application cache directory " + appDir.toAbsolutePath() + " could not be created.");
        }
    }

    private static Path getCacheDir() {
        final Path cache;
        final String cacheDirEnv = System.getenv(ENV_CACHE_DIR);
        if (cacheDirEnv != null)
            cache = Paths.get(cacheDirEnv);
        else {
            final String userHome = System.getProperty(PROP_USER_HOME);

            final String cacheNameEnv = System.getenv(ENV_CACHE_NAME);
            final String cacheName = cacheNameEnv != null ? cacheNameEnv : CACHE_DEFAULT_NAME;
            if (isWindows()) {
                final String appData = System.getenv("LOCALAPPDATA"); // Files.isDirectory(Paths.get(userHome, "AppData")) ? "AppData" : "Application Data";
                if (appData != null)
                    cache = Paths.get(appData, cacheName);
                else {
                    Path localData = Paths.get(userHome, "AppData", "Local");
                    if (!Files.isDirectory(localData))
                        localData = Paths.get(userHome, "Local Settings", "Application Data");
                    if (!Files.isDirectory(localData))
                        throw new RuntimeException("%LOCALAPPDATA% is undefined, and neither "
                                + Paths.get(userHome, "AppData", "Local") + " nor "
                                + Paths.get(userHome, "Local Settings", "Application Data") + " have been found");
                    cache = localData.resolve(cacheName);
                }
            } else
                cache = Paths.get(userHome, "." + cacheName);
        }
        try {
            if (!Files.exists(cache))
                Files.createDirectory(cache);
            if (!Files.exists(cache.resolve(APP_CACHE_NAME)))
                Files.createDirectory(cache.resolve(APP_CACHE_NAME));
            if (!Files.exists(cache.resolve(DEPS_CACHE_NAME)))
                Files.createDirectory(cache.resolve(DEPS_CACHE_NAME));

            return cache;
        } catch (IOException e) {
            throw new RuntimeException("Error opening cache directory " + cache.toAbsolutePath(), e);
        }
    }

    private boolean needsAppCache() {
        if (isEmptyCapsule())
            return false;
        if (hasRenamedNativeDependencies())
            return true;
        if (hasAttribute(ATTR_APP_ARTIFACT))
            return false;
        return shouldExtract();
    }

    private boolean shouldExtract() {
        final String extract = getAttribute(ATTR_EXTRACT);
        return extract == null || Boolean.parseBoolean(extract);
    }

    private void resetAppCache() {
        try {
            debug("Creating cache for " + jar.getName() + " in " + appCache.toAbsolutePath());
            delete(appCache);
            Files.createDirectory(appCache);
        } catch (IOException e) {
            throw new RuntimeException("Exception while extracting jar " + jar.getName() + " to app cache directory " + appCache.toAbsolutePath(), e);
        }
    }

    private boolean isUpToDate() {
        if (Boolean.parseBoolean(System.getProperty(PROP_RESET, "false")))
            return false;
        try {
            Path extractedFile = appCache.resolve(".extracted");
            if (!Files.exists(extractedFile))
                return false;
            FileTime extractedTime = Files.getLastModifiedTime(extractedFile);

            Path jarFile = Paths.get(jar.getName());
            FileTime jarTime = Files.getLastModifiedTime(jarFile);

            return extractedTime.compareTo(jarTime) >= 0;
        } catch (IOException e) {
            throw new AssertionError(e);
        }
    }

    private void extractCapsule() {
        try {
            verbose("Extracting " + jar.getName() + " to app cache directory " + appCache.toAbsolutePath());
            extractJar(jar, appCache);

        } catch (IOException e) {
            throw new RuntimeException("Exception while extracting jar " + jar.getName() + " to app cache directory " + appCache.toAbsolutePath(), e);
        }
    }

    private void markCache() {
        try {
            Files.createFile(appCache.resolve(".extracted"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static boolean isCapsule(Path path) {
        try {
            if (Files.isRegularFile(path) && path.getFileName().toString().endsWith(".jar")) {
                final JarFile jar = new JarFile(path.toFile());
                return isCapsule(jar);
            }
            return false;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static boolean isCapsule(JarFile jar) {
        return "Capsule".equals(getMainClass(jar));
//        for (Enumeration entries = jar.entries(); entries.hasMoreElements();) {
//            final JarEntry file = (JarEntry) entries.nextElement();
//            if (file.getName().equals("Capsule.class"))
//                return true;
//        }
//        return false;
    }

    private static String getMainClass(JarFile jar) {
        try {
            final Manifest manifest = jar.getManifest();
            if (manifest != null)
                return manifest.getMainAttributes().getValue("Main-Class");
            return null;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static void extractJar(JarFile jar, Path targetDir) throws IOException {
        for (Enumeration entries = jar.entries(); entries.hasMoreElements();) {
            final JarEntry file = (JarEntry) entries.nextElement();

            if (file.isDirectory())
                continue;

            if (file.getName().equals(Capsule.class.getName().replace('.', '/') + ".class")
                    || (file.getName().startsWith(Capsule.class.getName().replace('.', '/') + "$") && file.getName().endsWith(".class")))
                continue;
            if (file.getName().endsWith(".class"))
                continue;
            if (file.getName().startsWith("capsule/"))
                continue;

            final String dir = getDirectory(file.getName());
            if (dir != null && dir.startsWith("META-INF"))
                continue;

            if (dir != null)
                Files.createDirectories(targetDir.resolve(dir));

            final Path targetFile = targetDir.resolve(file.getName());
            try (InputStream is = jar.getInputStream(file)) {
                Files.copy(is, targetFile);
            }
        }
    }

    private static String getDirectory(String filename) {
        final int index = filename.lastIndexOf('/');
        if (index < 0)
            return null;
        return filename.substring(0, index);
    }

    private String getJavaHome() {
        String jhome = System.getProperty(PROP_CAPSULE_JAVA_HOME);
        if (jhome == null && !isMatchingJavaVersion(System.getProperty(PROP_JAVA_VERSION))) {
            final boolean jdk = hasAttribute(ATTR_JDK_REQUIRED) && Boolean.parseBoolean(getAttribute(ATTR_JDK_REQUIRED));
            final Path javaHomePath = findJavaHome(jdk);
            if (javaHomePath == null) {
                throw new RuntimeException("Could not find Java installation for requested version "
                        + getAttribute(ATTR_MIN_JAVA_VERSION) + " / " + getAttribute(ATTR_JAVA_VERSION)
                        + "(JDK required: " + jdk + ")"
                        + ". You can override the used Java version with the -D" + PROP_CAPSULE_JAVA_HOME + " flag.");
            }
            jhome = javaHomePath.toAbsolutePath().toString();
        }
        return jhome;
    }

    private boolean isMatchingJavaVersion(String javaVersion) {
        if (hasAttribute(ATTR_MIN_JAVA_VERSION) && compareVersions(javaVersion, getAttribute(ATTR_MIN_JAVA_VERSION)) < 0)
            return false;
        if (hasAttribute(ATTR_JAVA_VERSION) && compareVersions(majorJavaVersion(javaVersion), getAttribute(ATTR_JAVA_VERSION)) > 0)
            return false;
        return true;
    }

    private String getJavaProcessName(String javaHome) {
        if (javaHome == null)
            javaHome = System.getProperty(PROP_JAVA_HOME);

        final String javaProcessName = javaHome + FILE_SEPARATOR + "bin" + FILE_SEPARATOR + "java" + (isWindows() ? ".exe" : "");
        return javaProcessName;
    }

    protected static boolean isWindows() {
        return System.getProperty(PROP_OS_NAME).toLowerCase().startsWith("windows");
    }

    protected static boolean isMac() {
        return System.getProperty(PROP_OS_NAME).toLowerCase().startsWith("mac");
    }

    protected static boolean isLinux() {
        return System.getProperty(PROP_OS_NAME).toLowerCase().contains("nux");
    }

    private static boolean isDependency(String lib) {
        return lib.contains(":");
    }

    private static String sanitize(String path) {
        if (path.startsWith("/") || path.startsWith("../") || path.contains("/../"))
            throw new IllegalArgumentException("Path " + path + " is not local");
        return path;
    }

    private static String join(Collection<?> coll, String separator) {
        if (coll == null)
            return null;
        StringBuilder sb = new StringBuilder();
        for (Object e : coll) {
            if (e != null)
                sb.append(e).append(separator);
        }
        sb.delete(sb.length() - separator.length(), sb.length());
        return sb.toString();
    }

    private static String getBefore(String s, char separator) {
        final int i = s.indexOf(separator);
        if (i < 0)
            return s;
        return s.substring(0, i);
    }

    private static String getAfter(String s, char separator) {
        final int i = s.indexOf(separator);
        if (i < 0)
            return null;
        return s.substring(i + 1);
    }

    private boolean hasPom() {
        return jar.getEntry(POM_FILE) != null;
    }

    private Object createPomReader() {
        try {
            return new PomReader(jar.getInputStream(jar.getEntry(POM_FILE)));
        } catch (NoClassDefFoundError e) {
            throw new RuntimeException("Jar " + jar.getName()
                    + " contains a pom.xml file, while the necessary dependency management classes are not found in the jar");
        } catch (IOException e) {
            throw new RuntimeException("Failed reading pom", e);
        }
    }

    private List<String> getPomRepositories() {
        return ((PomReader) pom).getRepositories();
    }

    private List<String> getPomDependencies() {
        return ((PomReader) pom).getDependencies();
    }

    private String getPomAppName() {
        final PomReader pr = (PomReader) pom;
        return pr.getGroupId() + "_" + pr.getArtifactId() + "_" + pr.getVersion();
    }

    private Object createDependencyManager(List<String> repositories) {
        try {
            final Path depsCache = cacheDir.resolve(DEPS_CACHE_NAME);

            final boolean reset = Boolean.parseBoolean(System.getProperty(PROP_RESET, "false"));

            final String local = expandCommandLinePath(System.getProperty(PROP_USE_LOCAL_REPO));
            Path localRepo = depsCache;
            if (local != null)
                localRepo = !local.isEmpty() ? Paths.get(local) : LOCAL_MAVEN;
            debug("Local repo: " + localRepo);

            final boolean offline = "".equals(System.getProperty(PROP_OFFLINE)) || Boolean.parseBoolean(System.getProperty(PROP_OFFLINE));
            debug("Offline: " + offline);

            final DependencyManager dm = new DependencyManager(localRepo.toAbsolutePath(), repositories, reset, offline);

            return dm;
        } catch (NoClassDefFoundError e) {
            throw new RuntimeException("Jar " + jar.getName()
                    + " specifies dependencies, while the necessary dependency management classes are not found in the jar");
        }
    }

    private List<String> getRepositories() {
        List<String> repos = new ArrayList<String>();

        List<String> attrRepos = split(System.getenv(ENV_CAPSULE_REPOS), ":");
        if (attrRepos == null)
            getListAttribute(ATTR_REPOSITORIES);
       
        if (attrRepos != null)
            repos.addAll(attrRepos);
        if (pom != null) {
            for (String repo : nullToEmpty(getPomRepositories())) {
                if (!repos.contains(repo))
                    repos.add(repo);
            }
        }

        return !repos.isEmpty() ? Collections.unmodifiableList(repos) : null;
    }

    /**
     * Returns a list of dependencies, each in the format {@code groupId:artifactId:version[:classifier]} (classifier is optional)
     */
    protected List<String> getDependencies() {
        List<String> deps = getListAttribute(ATTR_DEPENDENCIES);
        if (deps == null && pom != null)
            deps = getPomDependencies();

        return deps != null ? Collections.unmodifiableList(deps) : null;
    }

    private void printDependencyTree(String root) {
        final DependencyManager dm = (DependencyManager) dependencyManager;
        dm.printDependencyTree(root);
    }

    private void printDependencyTree(List<String> dependencies, String type) {
        if (dependencies == null)
            return;
        final DependencyManager dm = (DependencyManager) dependencyManager;
        dm.printDependencyTree(dependencies, type);
    }

    private List<Path> resolveDependencies(List<String> dependencies, String type) {
        if (dependencies == null)
            return null;

        return ((DependencyManager) dependencyManager).resolveDependencies(dependencies, type);
    }

    private String getAppArtifactLatestVersion(String coords) {
        if (coords == null)
            return null;
        final DependencyManager dm = (DependencyManager) dependencyManager;
        return dm.getLatestVersion(coords);
    }

    private List<Path> resolveAppArtifact(String coords) {
        if (coords == null)
            return null;
        final DependencyManager dm = (DependencyManager) dependencyManager;
        return dm.resolveRoot(coords);
    }

    private static Path getDependencyPath(Object dependencyManager, String p) {
        if (dependencyManager == null)
            throw new RuntimeException("No dependencies specified in the capsule. Cannot resolve dependency " + p);
        final DependencyManager dm = (DependencyManager) dependencyManager;
        List<Path> depsJars = dm.resolveDependency(p, "jar");

        if (depsJars == null || depsJars.isEmpty())
            throw new RuntimeException("Dependency " + p + " was not found.");
        return depsJars.iterator().next().toAbsolutePath();
    }

    private static void delete(Path dir) throws IOException {
        // we don't use Files.walkFileTree because we'd like to avoid creating more classes (Capsule$1.class etc.)
        delete(dir.toFile());
    }

    private static void delete(File f) throws IOException {
        if (f.isDirectory()) {
            for (File c : f.listFiles())
                delete(c);
        }
        if (!f.delete())
            throw new FileNotFoundException("Failed to delete file: " + f);
    }

    private Path findJavaHome(boolean jdk) {
        final Map<String, Path> homes = getJavaHomes(jdk);
        if (homes == null)
            return null;
        Path best = null;
        String bestVersion = null;
        for (Map.Entry<String, Path> e : homes.entrySet()) {
            final String v = e.getKey();
            if (isMatchingJavaVersion(v)) {
                if (bestVersion == null || compareVersions(v, bestVersion) > 0) {
                    bestVersion = v;
                    best = e.getValue();
                }
            }
        }
        return best;
    }

    private static Map<String, Path> getJavaHomes(boolean jdk) {
        Path dir = Paths.get(System.getProperty(PROP_JAVA_HOME)).getParent();
        while (dir != null) {
            Map<String, Path> homes = getJavaHomes(dir, jdk);
            if (homes != null)
                return homes;
            dir = dir.getParent();
        }
        return null;
    }

    private static Map<String, Path> getJavaHomes(Path dir, boolean jdk) {
        File d = dir.toFile();
        if (!d.isDirectory())
            return null;
        Map<String, Path> dirs = new HashMap<String, Path>();
        for (File f : d.listFiles()) {
            if (f.isDirectory()) {
                String dirName = f.toPath().getFileName().toString();
                String ver = isJavaDir(dirName);
                if (ver != null && (!jdk || isJDK(dirName))) {
                    File home = searchJavaHomeInDir(f);
                    if (home != null)
                        dirs.put(javaVersion(ver), home.toPath());
                }
            }
        }
        return !dirs.isEmpty() ? dirs : null;
    }

    private static String isJavaDir(String fileName) {
        fileName = fileName.toLowerCase();
        if (fileName.startsWith("jdk") || fileName.startsWith("jre") || fileName.endsWith(".jdk") || fileName.endsWith(".jre")) {
            if (fileName.startsWith("jdk") || fileName.startsWith("jre"))
                fileName = fileName.substring(3);
            if (fileName.endsWith(".jdk") || fileName.endsWith(".jre"))
                fileName = fileName.substring(0, fileName.length() - 4);
            return javaVersion(fileName);
        } else
            return null;
    }

    private static boolean isJDK(String filename) {
        return filename.toLowerCase().contains("jdk");
    }

    private static String javaVersion(String ver) {
        try {
            String[] comps = ver.split("\\.");
            if (Integer.parseInt(comps[0]) > 1) {
                if (comps.length == 1)
                    return "1." + ver + ".0";
                else
                    return "1." + ver;
            }
            return ver;
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private static File searchJavaHomeInDir(File dir) {
        if (!dir.isDirectory())
            return null;
        for (File f : dir.listFiles()) {
            if (isJavaHome(f))
                return f;
            File home = searchJavaHomeInDir(f);
            if (home != null)
                return home;
        }
        return null;
    }

    private static boolean isJavaHome(File dir) {
        if (dir.isDirectory()) {
            for (File f : dir.listFiles()) {
                if (f.isDirectory() && f.toPath().getFileName().toString().equals("bin")) {
                    for (File f0 : f.listFiles()) {
                        if (f0.isFile()) {
                            String fname = f0.toPath().getFileName().toString().toLowerCase();
                            if (fname.equals("java") || fname.equals("java.exe"))
                                return true;
                        }
                    }
                    break;
                }
            }
        }
        return false;
    }

    private static String majorJavaVersion(String v) {
        final String[] vs = v.split("\\.");
        if (vs.length == 1)
            return "1." + v;
        return vs[0] + "." + vs[1];
    }

    private static int compareVersions(String a, String b) {
        return compareVersions(parseJavaVersion(a), parseJavaVersion(b));
    }

    private static int compareVersions(int[] a, int[] b) {
        for (int i = 0; i < 4; i++) {
            if (a[i] != b[i])
                return a[i] - b[i];
        }
        return 0;
    }

    private static int[] parseJavaVersion(String v) {
        final int[] ver = new int[4];
        final String[] vs = v.split("\\.");
        if (vs.length < 2)
            throw new IllegalArgumentException("Version " + v + " is illegal. Must be of the form x.y.z[_u]");
        ver[0] = Integer.parseInt(vs[0]);
        ver[1] = Integer.parseInt(vs[1]);
        if (vs.length > 2) {
            String[] vzu = vs[2].split("_");
            ver[2] = Integer.parseInt(vzu[0]);
            ver[3] = vzu.length > 1 ? Integer.parseInt(vzu[1]) : 0;
        }
        return ver;
    }

    private static <T> List<T> nullToEmpty(List<T> list) {
        if (list == null)
            return Collections.emptyList();
        return list;
    }

    private static <T> Collection<T> nullToEmpty(Collection<T> coll) {
        if (coll == null)
            return Collections.emptyList();
        return coll;
    }

    private List<String> expand(List<String> strs) {
        if (strs == null)
            return null;
        final List<String> res = new ArrayList<String>(strs.size());
        for (String s : strs)
            res.add(expand(s));
        return res;
    }

    private String expand(String str) {
        if (appCache != null)
            str = str.replaceAll("\\$" + VAR_CAPSULE_DIR, appCache.toAbsolutePath().toString());
        else if (str.contains("$" + VAR_CAPSULE_DIR))
            throw new IllegalStateException("The $" + VAR_CAPSULE_DIR + " variable cannot be expanded when the "
                    + ATTR_EXTRACT + " attribute is set to false");

        str = expandCommandLinePath(str);
        str = str.replaceAll("\\$" + VAR_CAPSULE_JAR, getJarPath());
        str = str.replaceAll("\\$" + VAR_JAVA_HOME, javaHome != null ? javaHome : System.getProperty(PROP_JAVA_HOME));
        str = str.replace('/', FILE_SEPARATOR.charAt(0));
        return str;
    }

    private static String expandCommandLinePath(String str) {
        if (str == null)
            return null;
//        if (isWindows())
//            return str;
//        else
        return str.startsWith("~/") ? str.replace("~", System.getProperty(PROP_USER_HOME)) : str;
    }

    private static String getApplicationArtifactId(String coords) {
        if (coords == null)
            return null;
        final String[] cs = coords.split(":");
        if (cs.length < 2)
            throw new IllegalArgumentException("Illegal main artifact coordinates: " + coords);
        String id = cs[0] + "_" + cs[1];
        if (cs.length > 2)
            id += "_" + cs[2];
        return id;
    }

    private static void runCapsule(Path path, String[] args) {
        try {
            final ClassLoader cl = new URLClassLoader(new URL[]{path.toUri().toURL()}, null);
            final Class cls = cl.loadClass("Capsule");
            final Method main = cls.getMethod("main", String[].class);
            main.invoke(cls, (Object) args);
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            throw new RuntimeException(path + " does not appear to be a valid capsule.", e);
        } catch (MalformedURLException | IllegalAccessException e) {
            throw new AssertionError();
        } catch (InvocationTargetException e) {
            final Throwable t = e.getTargetException();
            if (t instanceof RuntimeException)
                throw (RuntimeException) t;
            if (t instanceof Error)
                throw (Error) t;
            throw new RuntimeException(t);
        }
    }

    private static boolean anyPropertyDefined(String... props) {
        for (String prop : props) {
            if (System.getProperty(prop) != null)
                return true;
        }
        return false;
    }

    private static void println(String str) {
        System.err.println("CAPSULE: " + str);
    }

    private static void verbose(String str) {
        if (verbose)
            System.err.println("CAPSULE: " + str);
    }

    private static void debug(String str) {
        if (debug)
            System.err.println("CAPSULE: " + str);
    }
}
TOP

Related Classes of Capsule

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.