All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.ceylon.tools.assemble.CeylonAssemblyRunner Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
/*
 * Copyright 2012 Red Hat inc. and third party contributors as noted
 * by the author tags.
 * 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.
 * 
 * Broadly based on http://qdolan.blogspot.com.es/2008/10/embedded-jar-classloader-in-under-100.html
 */
package com.redhat.ceylon.tools.assemble;

import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Attributes;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import com.redhat.ceylon.common.Constants;

/**
 * @author Tako Schotanus ([email protected])
 */
public class CeylonAssemblyRunner {
    
    public static void main(String[] args) throws Exception {
        try (CeylonAssemblyClassLoader loader = new CeylonAssemblyClassLoader(
                Thread.currentThread().getContextClassLoader())) {
            if (runtimeExists(loader)) {
                invokeRuntime(loader, args);
            } else {
                invokeMain(loader, args);
            }
        }
    }

    private static Attributes getAssemblyManifestAttributes() throws IOException {
        ProtectionDomain protectionDomain = CeylonAssemblyRunner.class.getProtectionDomain();
        CodeSource codeSource = protectionDomain.getCodeSource();
        if (codeSource != null) {
            URL srcUrl = codeSource.getLocation();
            URL assemblyUrl = new URL("jar", "", srcUrl + "!/");
            JarURLConnection uc = (JarURLConnection)assemblyUrl.openConnection();
            return uc.getMainAttributes();
        }
        return null;
    }

    private static String getMainClassName() {
        Attributes attrs;
        try {
            attrs = getAssemblyManifestAttributes();
            if (attrs != null) {
                return attrs.getValue(Constants.ATTR_ASSEMBLY_MAIN_CLASS);
            }
        } catch (IOException e) {
        }
        return null;
    }
    
    private static void invokeMain(ClassLoader loader, String[] args) throws Exception {
        String className = getMainClassName();
        if (className == null) {
            throw new IllegalArgumentException("Missing " + Constants.ATTR_ASSEMBLY_MAIN_CLASS + " attribute in assembly manifest");
        }
        Class clazz = loader.loadClass(className);
        Method method = clazz.getMethod("main", new Class[] { args.getClass() });
        method.setAccessible(true);
        int mods = method.getModifiers();
        if (method.getReturnType() != void.class || !Modifier.isStatic(mods)
                || !Modifier.isPublic(mods)) {
            throw new NoSuchMethodException("'main' in class '" + className + "'");
        }
        method.invoke(null, new Object[] { args });
    }

    private static boolean runtimeExists(ClassLoader loader) {
        try {
            Class clazz = loader.loadClass("ceylon.modules.bootstrap.CeylonRunTool");
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    private static void invokeRuntime(CeylonAssemblyClassLoader loader, String[] args) throws Exception {
        Class clazz = loader.loadClass("ceylon.modules.bootstrap.CeylonRunTool");
        Object runtool = clazz.newInstance();

        Attributes attrs = getAssemblyManifestAttributes();
        
        String mainModule = attrs.getValue(Constants.ATTR_ASSEMBLY_MAIN_MODULE);
        if (mainModule == null) {
            throw new IllegalArgumentException("Missing " + Constants.ATTR_ASSEMBLY_MAIN_MODULE + " attribute in assembly manifest");
        }
        Method moduleSetter = clazz.getMethod("setModule", String.class);
        moduleSetter.invoke(runtool, mainModule);
        
        File repoFolder = loader.getAssemblyFolder();
        String repo = attrs.getValue(Constants.ATTR_ASSEMBLY_REPOSITORY);
        if (repo != null) {
            repoFolder = new File(repoFolder, repo);
        }
        Method sysrepSetter = clazz.getMethod("setSystemRepository", String.class);
        sysrepSetter.invoke(runtool, repoFolder.getPath());
//        List repos = new ArrayList(1);
//        repos.add(repoFolder.toURI());
//        Method repoSetter = clazz.getMethod("setRepository", List.class);
//        repoSetter.invoke(runtool, repos);
        
        List argList = Arrays.asList(args);
        Method argsSetter = clazz.getMethod("setArgs", List.class);
        argsSetter.invoke(runtool, argList);
        
        String runDecl = attrs.getValue(Constants.ATTR_ASSEMBLY_RUN);
        if (runDecl != null) {
            Method runSetter = clazz.getMethod("setRun", String.class);
            runSetter.invoke(runtool, runDecl);
        }
        
        String overrides = attrs.getValue(Constants.ATTR_ASSEMBLY_OVERRIDES);
        if (overrides != null) {
            Method overridesSetter = clazz.getMethod("setOverrides", String.class);
            overridesSetter.invoke(runtool, overrides);
        }
        
        Method run = clazz.getMethod("run");
        run.invoke(runtool);
    }

    public static class CeylonAssemblyClassLoader extends URLClassLoader {
        private File tmpAssemblyFolder = null;
        
        public File getAssemblyFolder() {
            return tmpAssemblyFolder;
        }
        
        public CeylonAssemblyClassLoader(ClassLoader parent) {
            super(new URL[] {}, parent);
            ProtectionDomain protectionDomain = getClass().getProtectionDomain();
            CodeSource codeSource = protectionDomain.getCodeSource();
            if (codeSource != null) {
                File assembly = new File(codeSource.getLocation().getPath());
                // Create a temp folder to extract our assembly into
                try {
                    tmpAssemblyFolder = Files.createTempDirectory("ceylon-assembly-").toFile();
                    extractArchive(assembly, tmpAssemblyFolder);
                    deleteOnExit(tmpAssemblyFolder);
                } catch (IOException ex) {
                    delete(tmpAssemblyFolder);
                    throw new RuntimeException(ex);
                }
            }
        }

        private void extractArchive(File zip, File dir) throws IOException {
            try (ZipFile zf = new ZipFile(zip)) {
                Enumeration entries = zf.entries();
                while (entries.hasMoreElements()) {
                    ZipEntry entry = entries.nextElement();
                    String entryName = entry.getName();
                    File out = new File(dir, entryName);
                    if (entry.isDirectory()) {
                        continue;
                    }
                    if (!shouldInclude(entryName)) {
                        continue;
                    }
                    addURL(out.toURI().toURL());
                    mkdirs(out.getParentFile());
                    try (InputStream zipIn = zf.getInputStream(entry)) {
                        try (BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream(out))) {
                            copyStream(zipIn, fileOut, false, false);
                        }
                    }
                }
            }
        }
        
        private static boolean shouldInclude(String entryName) {
            entryName = entryName.toLowerCase();
            return !entryName.isEmpty() &&
                    (entryName.endsWith(".jar")
                            || entryName.endsWith(".car")
                            || entryName.endsWith("/module.xml")
                            || entryName.endsWith("/module.properties"));
        }

        private static File mkdirs(File dir) {
            if (!dir.exists() && !dir.mkdirs()) {
                throw new RuntimeException("Unable to create destination directory: " + dir);
            }
            return dir;
        }

        private static void copyStream(InputStream in, OutputStream out, boolean closeIn, boolean closeOut) throws IOException {
            try {
                copyStreamNoClose(in, out);
            } finally {
                if (closeIn) {
                    safeClose(in);
                }
                if (closeOut) {
                    safeClose(out);
                }
            }
        }

        private static void copyStreamNoClose(InputStream in, OutputStream out) throws IOException {
            final byte[] bytes = new byte[8192];
            int cnt;
            while ((cnt = in.read(bytes)) != -1) {
                out.write(bytes, 0, cnt);
            }
            out.flush();
        }

        public static void safeClose(Closeable c) {
            try {
                if (c != null) {
                    c.close();
                }
            } catch (IOException ignored) {
            }
        }

        // The normal File.deleteOnExit() doesn't work for directories that might
        // not be empty, so we need to add a shutdown hook to do it ourselves
        private static void deleteOnExit(final File repo) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    delete(repo);
                }
            });
        }
        
        // Recursively delete files and folders
        private static boolean delete(File f) {
            boolean ok = true;
            if (f != null && f.exists()) {
                if (f.isDirectory()) {
                    for (File c : f.listFiles()) {
                        ok = ok && delete(c);
                    }
                }
                try {
                    boolean deleted = f.delete();
                    ok = ok && deleted;
                } catch (Exception ex) {
                    ok = false;
                }
            }
            return ok;
        }
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy