/* * JARClassLoader.java - Loads classes from JAR files * :tabSize=4:indentSize=4:noTabs=false: * :folding=explicit:collapseFolds=1: * * Copyright (C) 1999, 2003 Slava Pestov * Portions copyright (C) 1999 mike dillon * * This program 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 2 * of the License, or any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.gjt.sp.jedit; //{{{ Imports import java.io.InputStream; import java.io.IOException; import java.net.URL; import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.gjt.sp.util.Log; import java.util.jar.Manifest; import java.util.jar.JarFile; import java.net.MalformedURLException; import java.util.jar.Attributes; import java.util.jar.Attributes.Name; //}}} /** * A class loader implementation that loads classes from JAR files. All * instances share the same set of classes. * @author Slava Pestov * @version $Id: JARClassLoader.java 23224 2013-09-30 20:51:42Z shlomy $ */ public class JARClassLoader extends ClassLoader { //{{{ JARClassLoader constructor /** * This constructor creates a class loader for loading classes from all * plugins. For example BeanShell uses one of these so that scripts can * use plugin classes. */ public JARClassLoader() { this(true); } /** * Creates a class loader that will optionally delegate the * finding of classes to the parent class loader by default. * * @since jEdit 4.3pre6 */ public JARClassLoader(boolean delegateFirst) { this.delegateFirst = delegateFirst; // for debugging id = INDEX++; live++; } //}}} //{{{ loadClass() method /** * @exception ClassNotFoundException if the class could not be found */ public Class loadClass(String clazz, boolean resolveIt) throws ClassNotFoundException { ClassNotFoundException pending = null; if (delegateFirst) { try { return loadFromParent(clazz); } catch (ClassNotFoundException cnf) { // keep going if class was not found. pending = cnf; } } Object obj = classHash.get(clazz); if(obj == NO_CLASS) { // we remember which classes we don't exist // because BeanShell tries loading all possible // <imported prefix>.<class name> combinations throw new ClassNotFoundException(clazz); } else if(obj instanceof JARClassLoader) { JARClassLoader classLoader = (JARClassLoader)obj; try { return classLoader._loadClass(clazz,resolveIt); } catch (ClassNotFoundException cnf2) { classHash.put(clazz,NO_CLASS); throw cnf2; } } else if (delegateFirst) { // if delegating, reaching this statement means // the class was really not found. Otherwise // we'll try loading from the parent class loader. throw pending; } return loadFromParent(clazz); } //}}} //{{{ getResourceAsStream() method public InputStream getResourceAsStream(String name) { try { // try in current jar first if(jar != null) { ZipFile zipFile = jar.getZipFile(); ZipEntry entry = zipFile.getEntry(name); if(entry != null) { return zipFile.getInputStream(entry); } } // then try from another jar Object obj = resourcesHash.get(name); if(obj instanceof JARClassLoader) { JARClassLoader classLoader = (JARClassLoader)obj; return classLoader.getResourceAsStream(name); } // finally try from the system class loader return getSystemResourceAsStream(name); } catch(IOException io) { Log.log(Log.ERROR,this,io); return null; } } //}}} //{{{ getResource() method /** * overriding getResource() because we want to search FIRST in this * ClassLoader, then the parent, the path, etc. */ public URL getResource(String name) { try { if(jar != null) { ZipFile zipFile = jar.getZipFile(); ZipEntry entry = zipFile.getEntry(name); if(entry != null) { return new URL(getResourceAsPath(name)); } } Object obj = resourcesHash.get(name); if(obj instanceof JARClassLoader) { JARClassLoader classLoader = (JARClassLoader)obj; return classLoader.getResource(name); } else { URL ret = getSystemResource(name); if(ret != null) { Log.log(Log.DEBUG,JARClassLoader.class,"Would have returned null for getResource("+name+")"); Log.log(Log.DEBUG,JARClassLoader.class,"returning("+ret+")"); } return ret; } } catch(IOException io) { Log.log(Log.ERROR,this,io); return null; } } //}}} //{{{ getResourceAsPath() method /** * construct a jeditresource:/etc path from the name * of a resource in the associated jar. * The existence of the resource is not actually checked. * * @param name name of the resource * @return jeditresource:/path_to_the_jar!name_of_the_resource * @throws UnsupportedOperationException if this is an anonymous * JARClassLoader (no associated jar). */ public String getResourceAsPath(String name) { // this must be fixed during plugin development if(jar == null) throw new UnsupportedOperationException( "don't call getResourceAsPath() on anonymous JARClassLoader"); if(!name.startsWith("/")) name = '/' + name; return "jeditresource:/" + MiscUtilities.getFileName( jar.getPath()) + '!' + name; } //}}} //{{{ dump() method /** * For debugging. */ public static void dump() { Log.log(Log.DEBUG,JARClassLoader.class, "Total instances created: " + INDEX); Log.log(Log.DEBUG,JARClassLoader.class, "Live instances: " + live); synchronized(classHash) { for (Map.Entry<String, Object> entry : classHash.entrySet()) { if (entry.getValue() != NO_CLASS) { Log.log(Log.DEBUG, JARClassLoader.class, entry.getKey() + " ==> " + entry.getValue()); } } } } //}}} //{{{ toString() method public String toString() { if(jar == null) return "<anonymous>(" + id + ')'; else return jar.getPath() + " (" + id + ')'; } //}}} //{{{ findResources() method /** * @return zero or one resource, as returned by getResource() */ public Enumeration getResources(String name) throws IOException { class SingleElementEnumeration implements Enumeration { private Object element; SingleElementEnumeration(Object element) { this.element = element; } public boolean hasMoreElements() { return element != null; } public Object nextElement() { if(element != null) { Object retval = element; element = null; return retval; } else throw new NoSuchElementException(); } } URL resource = getResource(name); return new SingleElementEnumeration(resource); } //}}} //{{{ finalize() method protected void finalize() { live--; } //}}} //{{{ Package-private members //{{{ JARClassLoader constructor /** * @since jEdit 4.2pre1 */ JARClassLoader(PluginJAR jar) { this(); this.jar = jar; } //}}} //{{{ activate() method void activate() { if (jar.getPlugin() != null) { String _delegate = jEdit.getProperty( "plugin." + jar.getPlugin().getClassName() + ".class_loader_delegate"); delegateFirst = _delegate == null || "true".equals(_delegate); } String[] classes = jar.getClasses(); if(classes != null) { for (String aClass : classes) classHash.put(aClass, this); } String[] resources = jar.getResources(); if(resources != null) { for (String resource : resources) resourcesHash.put(resource, this); } } //}}} //{{{ deactivate() method void deactivate() { String[] classes = jar.getClasses(); if(classes != null) { for (String aClass : classes) { Object loader = classHash.get(aClass); if (loader == this) classHash.remove(aClass); else /* two plugins provide same class! */; } } String[] resources = jar.getResources(); if(resources == null) return; for (String resource : resources) { Object loader = resourcesHash.get(resource); if (loader == this) resourcesHash.remove(resource); else /* two plugins provide same resource! */; } } //}}} //}}} //{{{ Private members // used to mark non-existent classes in class hash private static final Object NO_CLASS = new Object(); private static int INDEX; private static int live; private static Map<String, Object> classHash = new Hashtable<String, Object>(); private static Map<String, Object> resourcesHash = new HashMap<String, Object>(); private int id; private boolean delegateFirst; private PluginJAR jar; //{{{ _loadClass() method /** * Load class from this JAR only. */ private synchronized Class _loadClass(String clazz, boolean resolveIt) throws ClassNotFoundException { jar.activatePlugin(); synchronized(this) { Class cls = findLoadedClass(clazz); if(cls != null) { if(resolveIt) resolveClass(cls); return cls; } String name = MiscUtilities.classToFile(clazz); try { definePackage(clazz); ZipFile zipFile = jar.getZipFile(); ZipEntry entry = zipFile.getEntry(name); if(entry == null) throw new ClassNotFoundException(clazz); InputStream in = zipFile.getInputStream(entry); int len = (int)entry.getSize(); byte[] data = new byte[len]; int success = 0; int offset = 0; while(success < len) { len -= success; offset += success; success = in.read(data,offset,len); if(success == -1) { Log.log(Log.ERROR,this,"Failed to load class " + clazz + " from " + zipFile.getName()); throw new ClassNotFoundException(clazz); } } cls = defineClass(clazz,data,0,data.length); if(resolveIt) resolveClass(cls); return cls; } catch(IOException io) { Log.log(Log.ERROR,this,io); throw new ClassNotFoundException(clazz); } } } //}}} //{{{ definePackage(clazz) method private void definePackage(String clazz) throws IOException { int idx = clazz.lastIndexOf('.'); if (idx != -1) { String name = clazz.substring(0, idx); if (getPackage(name) == null) definePackage(name, new JarFile(jar.getFile()).getManifest()); } } //}}} //{{{ getMfValue() method private static String getMfValue(Attributes sectionAttrs, Attributes mainAttrs, Attributes.Name name) { String value=null; if (sectionAttrs != null) value = sectionAttrs.getValue(name); else if (mainAttrs != null) { value = mainAttrs.getValue(name); } return value; } //}}} //{{{ definePackage(packageName, manifest) method private void definePackage(String name, Manifest mf) { if (mf==null) { definePackage(name, null, null, null, null, null, null, null); return; } Attributes sa = mf.getAttributes(name.replace('.', '/') + '/'); Attributes ma = mf.getMainAttributes(); URL sealBase = null; if (Boolean.valueOf(getMfValue(sa, ma, Name.SEALED)).booleanValue()) { try { sealBase = jar.getFile().toURL(); } catch (MalformedURLException e) {} } definePackage( name, getMfValue(sa, ma, Name.SPECIFICATION_TITLE), getMfValue(sa, ma, Name.SPECIFICATION_VERSION), getMfValue(sa, ma, Name.SPECIFICATION_VENDOR), getMfValue(sa, ma, Name.IMPLEMENTATION_TITLE), getMfValue(sa, ma, Name.IMPLEMENTATION_VERSION), getMfValue(sa, ma, Name.IMPLEMENTATION_VENDOR), sealBase); } //}}} //{{{ loadFromParent() method private Class loadFromParent(String clazz) throws ClassNotFoundException { Class cls; ClassLoader parentLoader = getClass().getClassLoader(); if (parentLoader != null) cls = parentLoader.loadClass(clazz); else cls = findSystemClass(clazz); return cls; } //}}} //}}} }