package com.redhat.ceylon.launcher; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import java.util.jar.JarFile; import com.redhat.ceylon.common.Versions; /** * Ceylon-specific class loader that knows how to find and add * all needed dependencies for compiler and runtime. * Implements child-first class loading to prevent mix-ups with * Java's own tool-chain. * * @author Tako Schotanus * */ public class CeylonClassLoader extends URLClassLoader { public static CeylonClassLoader newInstance() throws URISyntaxException, MalformedURLException, FileNotFoundException { return new CeylonClassLoader(getClassPath()); } public static CeylonClassLoader newInstance(List<File> classPath) throws URISyntaxException, MalformedURLException, FileNotFoundException { return new CeylonClassLoader(classPath); } private String signature; private CeylonClassLoader(List<File> classPath) throws URISyntaxException, MalformedURLException, FileNotFoundException { super(toUrls(classPath)); this.signature = toString(classPath); } private CeylonClassLoader(List<File> classPath, ClassLoader parentLoader) throws URISyntaxException, MalformedURLException, FileNotFoundException { super(toUrls(classPath), parentLoader); this.signature = toString(classPath); } public String getSignature(){ return signature; } public boolean hasSignature(String signature){ return signature != null && this.signature.equals(signature); } private static URL[] toUrls(List<File> cp) throws MalformedURLException { URL[] urls = new URL[cp.size()]; int i = 0; for (File f : cp) { urls[i++] = f.toURI().toURL(); } return urls; } private static String toString(List<File> cp) { StringBuilder classPath = new StringBuilder(); for (File f : cp) { if (classPath.length() > 0) { classPath.append(File.pathSeparatorChar); } classPath.append(f.getAbsolutePath()); } return classPath.toString(); } public static String getClassPathAsString() throws URISyntaxException, FileNotFoundException { return toString(getClassPath()); } public static String getClassPathSignature(List<File> cp) { return toString(cp); } public static List<File> getClassPath() throws URISyntaxException, FileNotFoundException { // Determine the necessary folders File ceylonHome = LauncherUtil.determineHome(); File ceylonRepo = LauncherUtil.determineRepo(ceylonHome); // Perform some sanity checks checkFolders(ceylonHome, ceylonRepo); List<File> archives = new LinkedList<File>(); // List all the necessary Ceylon JARs and CARs String version = LauncherUtil.determineSystemVersion(); archives.add(getRepoJar(ceylonRepo, "com.redhat.ceylon.compiler.java", version)); archives.add(getRepoCar(ceylonRepo, "ceylon.language", version)); archives.add(getRepoJar(ceylonRepo, "ceylon.runtime", version)); archives.add(getRepoJar(ceylonRepo, "com.redhat.ceylon.compiler.js", version)); archives.add(getRepoJar(ceylonRepo, "com.redhat.ceylon.typechecker", version)); archives.add(getRepoJar(ceylonRepo, "com.redhat.ceylon.common", version)); archives.add(getRepoJar(ceylonRepo, "com.redhat.ceylon.model", version)); archives.add(getRepoJar(ceylonRepo, "com.redhat.ceylon.module-resolver", version)); archives.add(getRepoJar(ceylonRepo, "org.jboss.jandex", Versions.DEPENDENCY_JANDEX_VERSION)); archives.add(getRepoJar(ceylonRepo, "org.jboss.modules", Versions.DEPENDENCY_JBOSS_MODULES_VERSION)); archives.add(getRepoJar(ceylonRepo, "org.jboss.logmanager", Versions.DEPENDENCY_LOGMANAGER_VERSION)); // Maven support for CMR archives.add(getRepoJar(ceylonRepo, "com.redhat.ceylon.maven-support", "2.0")); // optional // For the typechecker archives.add(getRepoJar(ceylonRepo, "org.antlr.runtime", "3.4")); // For the JS backend archives.add(getRepoJar(ceylonRepo, "net.minidev.json-smart", "1.1.1")); // For the "doc" tool archives.add(getRepoJar(ceylonRepo, "org.tautua.markdownpapers.core", "1.2.7")); archives.add(getRepoJar(ceylonRepo, "com.github.rjeschke.txtmark", "0.13")); // For the --out http:// functionality of the compiler archives.add(getRepoJar(ceylonRepo, "com.github.lookfirst.sardine", "5.1")); archives.add(getRepoJar(ceylonRepo, "org.apache.httpcomponents.httpclient", "4.3.2")); archives.add(getRepoJar(ceylonRepo, "org.apache.httpcomponents.httpcore", "4.3.2")); archives.add(getRepoJar(ceylonRepo, "org.apache.commons.logging", "1.1.1")); archives.add(getRepoJar(ceylonRepo, "org.apache.commons.codec", "1.8")); archives.add(getRepoJar(ceylonRepo, "org.slf4j.api", "1.6.1")); archives.add(getRepoJar(ceylonRepo, "org.slf4j.simple", "1.6.1")); // optional return archives; } private static File getRepoJar(File repo, String moduleName, String version) { return getRepoUrl(repo, moduleName, version, "jar"); } private static File getRepoCar(File repo, String moduleName, String version) { return getRepoUrl(repo, moduleName, version, "car"); } private static File getRepoUrl(File repo, String moduleName, String version, String extension) { return new File(repo, moduleName.replace('.', '/') + "/" + version + "/" + moduleName + "-" + version + "." + extension); } public static File getRepoJar(String moduleName, String version) throws FileNotFoundException, URISyntaxException { return getRepoUrl(moduleName, version, "jar"); } public static File getRepoCar(String moduleName, String version) throws FileNotFoundException, URISyntaxException { return getRepoUrl(moduleName, version, "car"); } public static File getRepoUrl(String moduleName, String version, String extension) throws URISyntaxException, FileNotFoundException { // Determine the necessary folders File ceylonHome = LauncherUtil.determineHome(); File ceylonRepo = LauncherUtil.determineRepo(ceylonHome); // Perform some sanity checks checkFolders(ceylonHome, ceylonRepo); return new File(ceylonRepo, moduleName.replace('.', '/') + "/" + version + "/" + moduleName + "-" + version + "." + extension); } private static void checkFolders(File ceylonHome, File ceylonRepo) throws FileNotFoundException { if (!ceylonHome.isDirectory()) { throw new FileNotFoundException("Could not determine the Ceylon home directory (" + ceylonHome + ")"); } if (!ceylonRepo.isDirectory()) { throw new FileNotFoundException("The Ceylon system repository could not be found (" + ceylonRepo + ")"); } } @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { try { // checking local c = findClass(name); } catch (ClassNotFoundException e) { // checking parent // This call to loadClass may eventually call findClass again, in case the parent doesn't find anything. c = super.loadClass(name, resolve); } } if (resolve) { resolveClass(c); } return c; } @Override public URL getResource(String name) { URL url = findResource(name); if (url == null) { // This call to getResource may eventually call findResource again, in case the parent doesn't find anything. url = super.getResource(name); } return url; } @Override public Enumeration<URL> getResources(String name) throws IOException { /** * Similar to super, but local resources are enumerated before parent resources */ Enumeration<URL> localUrls = findResources(name); Enumeration<URL> parentUrls = null; if (getParent() != null) { parentUrls = getParent().getResources(name); } final List<URL> urls = new ArrayList<URL>(); if (localUrls != null) { while (localUrls.hasMoreElements()) { urls.add(localUrls.nextElement()); } } if (parentUrls != null) { while (parentUrls.hasMoreElements()) { urls.add(parentUrls.nextElement()); } } return Collections.enumeration(urls); } @Override public InputStream getResourceAsStream(String name) { URL url = getResource(name); if (url != null) { try { URLConnection con = url.openConnection(); con.setUseCaches(false); return con.getInputStream(); } catch (IOException e) { } } return null; } /** * Cleans up any resource associated with this class loader. This class loader will not be usable after calling this * method, so any code using it to run better not be running anymore. */ public void clearCache() { try { Class<?> klass = java.net.URLClassLoader.class; Field ucp = klass.getDeclaredField("ucp"); ucp.setAccessible(true); Object sunMiscURLClassPath = ucp.get(this); Field loaders = sunMiscURLClassPath.getClass().getDeclaredField("loaders"); loaders.setAccessible(true); Object collection = loaders.get(sunMiscURLClassPath); for (Object sunMiscURLClassPathJarLoader : ((Collection<?>) collection).toArray()) { try { Field loader = sunMiscURLClassPathJarLoader.getClass().getDeclaredField("jar"); loader.setAccessible(true); Object jarFile = loader.get(sunMiscURLClassPathJarLoader); ((JarFile) jarFile).close(); } catch (Throwable t) { // not a JAR loader? t.printStackTrace(); } } } catch (Throwable t) { // Something's wrong t.printStackTrace(); } return; } }