/* This file is part of leafdigital leafChat. leafChat is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. leafChat 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 leafChat. If not, see <http://www.gnu.org/licenses/>. Copyright 2012 Samuel Marshall. */ package leafchat.startup; import java.io.File; import java.net.*; import java.util.LinkedList; import util.PlatformUtils; import leafchat.core.api.BugException; /** * Classloader that will be able to find *.api as well. * <p> * IMPORTANT: This file must not reference ANY other leafChat classes. If * it does, those are loaded with the system classloader and not this one. * The only classes that are supposed to be loaded by the system classloader * are {@link Main}, {@link APIClassLocator}, and this class. */ public class StartupClassLoader extends URLClassLoader { private static boolean ideStartup; private static File mainJar; /** PluginManager is stored here */ private APIClassLocator acl=null; /** * Construct as standard URLClassLoader with main jar and any libraries * @param url URL of main .jar file or main classpath */ public StartupClassLoader(URL url) { super(getJarList(url)); } /** * Sets the IDE startup value. Called from {@link StartupHandler}. * <p> * NOTE: It looks like this function doesn't need to be public (same package) * but it does because the other class is from a different classloader. * @param ideStartup True if started up from IDE, else false */ public static void setIdeStartup(boolean ideStartup) { StartupClassLoader.ideStartup = ideStartup; } /** * @return True if this was startup up from IDE and not using classloader */ public static boolean isIdeStartup() { return ideStartup; } /** * When starting up from IDE or command-line, you must provide an existing * installation of leafChat client which it uses for a few resources that it * has to access in compiled jar form. * <p> * (This is mainly used when scripting, and means if you're changing * scripting, you need to do a full build & install the app if you change * certain things.) * @return Location of template app */ public static File getIdeStartupTemplateApp() { String installed = System.getProperty("leafchat.installation"); if(installed == null) { throw new BugException( "When running from IDE or command-line, you must provide the " + "location (root folder) of a leafChat client installation.\n" + "Example: -Dleafchat.installation=/Applications/leafChat.app"); } File file = new File(installed); if(!file.exists() || !file.isDirectory()) { throw new BugException( "leafchat.installation value points to an invalid location (" + installed + ").\n" + "Example: -Dleafchat.installation=/Applications/leafChat.app"); } return file; } /** * @return Main jar file */ public static File getMainJar() { // Requires the jar file, so if using IDE startup (which doesn't run from // jar files), this must point to a normal installation. if(isIdeStartup()) { return new File(getIdeStartupTemplateApp(), (PlatformUtils.isMac() ? "Contents/Resources/Java/" : "") + "leafChat.jar"); } if(mainJar==null) { throw new Error("Main jar file not set"); } return mainJar; } /** * @return Util jar file */ public static File getUtilJar() { return new File(getLibFolder(), "leafdigital/util.jar"); } /** * Main (core) jar files; currently the main jar plus util.jar * @return Array of jar files */ public static File[] getMainJars() { return new File[] { getMainJar(), getUtilJar() }; } static void hackMainJar(File f) { mainJar=f; } private static URL[] getJarList(URL main) { LinkedList<URL> list = new LinkedList<URL>(); list.add(main); try { mainJar=(new File(new URI(main.toString()))).getAbsoluteFile(); File lib = getLibFolder(); addJars(lib,list); } catch(URISyntaxException e) { // This really shouldn't happen e.printStackTrace(); System.exit(1); } return list.toArray(new URL[list.size()]); } /** * @return Lib folder based on main jar folder */ private static File getLibFolder() { File mainFolder = getMainJar().getParentFile(); File lib; if(mainFolder.toString().endsWith("/Contents/Resources/Java")) lib=new File(mainFolder,"../../../lib"); else lib=new File(mainFolder,"lib"); return lib; } private static void addJars(File folder,LinkedList<URL> list) { File[] files=folder.listFiles(); if(files==null) return; for(int i=0;i<files.length;i++) { File f=files[i]; if(f.isDirectory()) addJars(f,list); else if(f.getName().endsWith(".jar")) { try { list.add(f.toURI().toURL()); } catch(MalformedURLException e) { // This really shouldn't happen e.printStackTrace(); System.exit(1); } } } } /** * Call to setup API class locator * @param acl Class locator */ public void setAPIClassLocator(APIClassLocator acl) { // Note: This must be public, even though it's only called from the same // package. That's because it's called from StartupHandler which was loaded // by a different classloader. this.acl=acl; } /** * Runs startup process. */ public void startup() { try { Class<?> c=loadClass("leafchat.startup.StartupHandler",true); c.newInstance(); } catch(ClassNotFoundException cnfe) { cnfe.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } /** Find the given class; if it's a .api class, looks elsewhere as well */ @Override protected Class<?> findClass(String sName) throws ClassNotFoundException { try { return super.findClass(sName); } catch(ClassNotFoundException cnfe) { if(acl!=null) return acl.findAPIClass(sName); else throw cnfe; } } /** * Modify the ordering of classloading so we don't load anything from the * system classloader unless we really have to */ @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // Special-case the files that already have been loaded by system // classloader, otherwise it gets real ugly if(name.equals("leafchat.startup.Main") || name.equals("leafchat.startup.StartupClassLoader") || name.equals("leafchat.startup.APIClassLocator")) return super.loadClass(name,resolve); // Modify the ordering Class<?> c=findLoadedClass(name); if(c==null) { try { c=findClass(name); } catch(ClassNotFoundException cnfe) { c=getParent().loadClass(name); } } if(resolve) resolveClass(c); return c; } }