/* 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 2011 Samuel Marshall. */ package leafchat.core; import java.io.*; import leafchat.core.api.*; /** * Classloader used for plugins; adds extra features and bizarre delegation * to a URLClassLoader. Also stores general information about plugin. */ public class PluginClassLoader extends SafeJarClassLoader implements Comparable<PluginClassLoader> { /** Owner, which we need for delegation */ private PluginManager owner; /** Metadata from XML file */ private PluginXMLDetails metadata; /** Remember name of jar file */ private File jarFile; /** True if this plugin came from system folder */ private boolean system; @Override public int compareTo(PluginClassLoader other) { if(other==this) return 0; if(system!=other.system) return system ? 1 : -1; int nameCompare=getName().compareTo(other.getName()); if(nameCompare==0) return jarFile.compareTo(other.jarFile); else return nameCompare; } /** * @return Metadata about the plugin */ public PluginXMLDetails getInfo() { return metadata; } /** * Construct and analyse jar. * @param owner Plugin manager * @param jarFile Jar file to open * @param sandbox True if file should be sandboxed (not actually implemented, * but still used to categorise 'system' files) * @throws GeneralException Some other error * @throws IOException If there's an error loading the file */ PluginClassLoader(PluginManager owner, File jarFile, boolean sandbox) throws GeneralException, IOException { // Construct as standard Java classloader based on this file super(jarFile, PluginClassLoader.class.getClassLoader()); // Remember details this.owner=owner; this.jarFile=jarFile; system=!sandbox; // Find plugininfo.xml and classes for export boolean foundInfo = false; String[] entries = getEntryNames(); for(int i=0; i<entries.length; i++) { String name = entries[i]; if(name.endsWith("/plugininfo.xml") || name.equals("plugininfo.xml")) { if(foundInfo) { throw new GeneralException( "Plugin contains multiple plugininfo.xml files: " + jarFile.getName()); } metadata=new PluginXMLDetails( new ByteArrayInputStream(getEntry(name)), jarFile, system); foundInfo=true; } if(name.endsWith(".class")) { // Chop .class and fix into package String sClassName= name.substring(0,name.lastIndexOf('.')).replace('/','.'); int iLastDot=sClassName.lastIndexOf('.'); if(iLastDot!=-1) { String sPackage=sClassName.substring(0,iLastDot); if(sPackage.endsWith(".api")) { owner.addAPIClass(this,sClassName); } } } } } /** * Find the given class; if it's a .api class, looks elsewhere as well * @param name Class name * @return Class * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { return super.findClass(name); } catch(ClassNotFoundException e) { return owner.findAPIClass(name); } } /** * Finds a class which we know is in this classloader and might already have * been loaded. * @param name Name of class * @return Class object * @throws ClassNotFoundException */ Class<?> getAPIClass(String name) throws ClassNotFoundException { Class<?> c=super.findLoadedClass(name); if(c!=null) return c; return super.findClass(name); } /** * @return Nice display name including jar name */ @Override public String toString() { return "PluginClassLoader: "+jarFile.getName(); } /** @return Name of jar file */ public String getJarName() { return jarFile.getName(); } /** * Create actual plugin objects. * @return New plugin objects * @throws GeneralException */ Plugin[] createPlugins() throws GeneralException { String[] pluginClasses=metadata.getPluginClasses(); Plugin[] ap=new Plugin[pluginClasses.length]; for(int i=0;i<ap.length;i++) { try { Class<?> c=loadClass(pluginClasses[i]); ap[i]=(Plugin)c.newInstance(); } catch(ClassCastException cce) { throw new GeneralException( "Plugin instantiation: Class "+pluginClasses[i]+ " must implement Plugin interface",cce); } catch (ClassNotFoundException cnfe) { throw new GeneralException( "Plugin instantiation: Class "+pluginClasses[i]+" not found",cnfe); } catch (InstantiationException ie) { throw new GeneralException( "Plugin instantiation: Class "+pluginClasses[i]+ " could not be instantiated",ie); } catch (IllegalAccessException iae) { throw new GeneralException( "Plugin instantiation: Class "+pluginClasses[i]+ " could not be accessed (check public)",iae); } } return ap; } /** * @return True if this is a system plugin */ public boolean isSystem() { return system; } /** * @return Actual jar file used for this plugin */ public File getJar() { return jarFile; } /** * @return Name of this plugin */ public String getName() { return metadata.getName(); } }