001: /*
002: * JARClassLoader.java - Loads classes from JAR files
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 1999, 2003 Slava Pestov
007: * Portions copyright (C) 1999 mike dillon
008: *
009: * This program is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; either version 2
012: * of the License, or any later version.
013: *
014: * This program is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017: * GNU General Public License for more details.
018: *
019: * You should have received a copy of the GNU General Public License
020: * along with this program; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
022: */
023:
024: package org.gjt.sp.jedit;
025:
026: //{{{ Imports
027: import java.io.InputStream;
028: import java.io.IOException;
029: import java.net.URL;
030: import java.util.*;
031: import java.util.zip.ZipEntry;
032: import java.util.zip.ZipFile;
033: import org.gjt.sp.util.Log;
034:
035: import java.util.jar.Manifest;
036: import java.util.jar.JarFile;
037: import java.net.MalformedURLException;
038: import java.util.jar.Attributes;
039: import java.util.jar.Attributes.Name;
040:
041: //}}}
042:
043: /**
044: * A class loader implementation that loads classes from JAR files. All
045: * instances share the same set of classes.
046: * @author Slava Pestov
047: * @version $Id: JARClassLoader.java 6459 2006-07-30 00:54:17Z vanza $
048: */
049: public class JARClassLoader extends ClassLoader {
050: //{{{ JARClassLoader constructor
051: /**
052: * This constructor creates a class loader for loading classes from all
053: * plugins. For example BeanShell uses one of these so that scripts can
054: * use plugin classes.
055: */
056: public JARClassLoader() {
057: this (true);
058: }
059:
060: /**
061: * Creates a class loader that will optionally delegate the
062: * finding of classes to the parent class loader by default.
063: *
064: * @since jEdit 4.3pre6
065: */
066: public JARClassLoader(boolean delegateFirst) {
067: this .delegateFirst = delegateFirst;
068: // for debugging
069: id = INDEX++;
070: live++;
071: } //}}}
072:
073: //{{{ loadClass() method
074: /**
075: * @exception ClassNotFoundException if the class could not be found
076: */
077: public Class loadClass(String clazz, boolean resolveIt)
078: throws ClassNotFoundException {
079: ClassNotFoundException pending = null;
080: if (delegateFirst) {
081: try {
082: return loadFromParent(clazz);
083: } catch (ClassNotFoundException cnf) {
084: // keep going if class was not found.
085: pending = cnf;
086: }
087: }
088:
089: Object obj = classHash.get(clazz);
090: if (obj == NO_CLASS) {
091: // we remember which classes we don't exist
092: // because BeanShell tries loading all possible
093: // <imported prefix>.<class name> combinations
094: throw new ClassNotFoundException(clazz);
095: } else if (obj instanceof JARClassLoader) {
096: JARClassLoader classLoader = (JARClassLoader) obj;
097: try {
098: return classLoader._loadClass(clazz, resolveIt);
099: } catch (ClassNotFoundException cnf2) {
100: classHash.put(clazz, NO_CLASS);
101: throw cnf2;
102: }
103: } else if (delegateFirst) {
104: // if delegating, reaching this statement means
105: // the class was really not found. Otherwise
106: // we'll try loading from the parent class loader.
107: throw pending;
108: }
109:
110: return loadFromParent(clazz);
111: } //}}}
112:
113: //{{{ getResourceAsStream() method
114: public InputStream getResourceAsStream(String name) {
115: if (jar == null)
116: return null;
117:
118: try {
119: ZipFile zipFile = jar.getZipFile();
120: ZipEntry entry = zipFile.getEntry(name);
121: if (entry == null)
122: return getSystemResourceAsStream(name);
123: else
124: return zipFile.getInputStream(entry);
125: } catch (IOException io) {
126: Log.log(Log.ERROR, this , io);
127:
128: return null;
129: }
130: } //}}}
131:
132: //{{{ getResource() method
133: public URL getResource(String name) {
134: if (jar == null)
135: return null;
136:
137: try {
138: ZipFile zipFile = jar.getZipFile();
139: ZipEntry entry = zipFile.getEntry(name);
140: if (entry == null)
141: return getSystemResource(name);
142: else
143: return new URL(getResourceAsPath(name));
144: } catch (IOException io) {
145: Log.log(Log.ERROR, this , io);
146: return null;
147: }
148: } //}}}
149:
150: //{{{ getResourceAsPath() method
151: public String getResourceAsPath(String name) {
152: if (jar == null)
153: return null;
154:
155: if (!name.startsWith("/"))
156: name = "/" + name;
157:
158: return "jeditresource:/"
159: + MiscUtilities.getFileName(jar.getPath()) + "!" + name;
160: } //}}}
161:
162: //{{{ getZipFile() method
163: /**
164: * @deprecated Call <code>PluginJAR.getZipFile()</code> instead.
165: */
166: public ZipFile getZipFile() {
167: try {
168: return jar.getZipFile();
169: } catch (IOException io) {
170: Log.log(Log.ERROR, this , io);
171: return null;
172: }
173: } //}}}
174:
175: //{{{ dump() method
176: /**
177: * For debugging.
178: */
179: public static void dump() {
180: Log.log(Log.DEBUG, JARClassLoader.class,
181: "Total instances created: " + INDEX);
182: Log.log(Log.DEBUG, JARClassLoader.class, "Live instances: "
183: + live);
184: synchronized (classHash) {
185: Iterator entries = classHash.entrySet().iterator();
186: while (entries.hasNext()) {
187: Map.Entry entry = (Map.Entry) entries.next();
188: if (entry.getValue() != NO_CLASS) {
189: Log.log(Log.DEBUG, JARClassLoader.class, entry
190: .getKey()
191: + " ==> " + entry.getValue());
192: }
193: }
194: }
195: } //}}}
196:
197: //{{{ toString() method
198: public String toString() {
199: if (jar == null)
200: return "<anonymous>(" + id + ")";
201: else
202: return jar.getPath() + " (" + id + ")";
203: } //}}}
204:
205: //{{{ findResources() method
206: protected Enumeration findResources(String name) throws IOException {
207: class SingleElementEnumeration implements Enumeration {
208: private Object element;
209:
210: public SingleElementEnumeration(Object element) {
211: this .element = element;
212: }
213:
214: public boolean hasMoreElements() {
215: return (element != null);
216: }
217:
218: public Object nextElement() {
219: if (element != null) {
220: Object retval = element;
221: element = null;
222: return retval;
223: } else
224: throw new NoSuchElementException();
225: }
226: }
227:
228: URL resource = getResource(name);
229: return new SingleElementEnumeration(resource);
230: } //}}}
231:
232: //{{{ finalize() method
233: protected void finalize() {
234: live--;
235: } //}}}
236:
237: //{{{ Package-private members
238:
239: //{{{ JARClassLoader constructor
240: /**
241: * @since jEdit 4.2pre1
242: */
243: JARClassLoader(PluginJAR jar) {
244: this ();
245: this .jar = jar;
246: } //}}}
247:
248: //{{{ activate() method
249: void activate() {
250: if (jar.getPlugin() != null) {
251: String _delegate = jEdit.getProperty("plugin."
252: + jar.getPlugin().getClassName()
253: + ".class_loader_delegate");
254: delegateFirst = (_delegate == null || "true"
255: .equals(_delegate));
256: }
257:
258: String[] classes = jar.getClasses();
259: if (classes != null) {
260: for (int i = 0; i < classes.length; i++) {
261: classHash.put(classes[i], this );
262: }
263: }
264: } //}}}
265:
266: //{{{ deactivate() method
267: void deactivate() {
268: String[] classes = jar.getClasses();
269: if (classes == null)
270: return;
271:
272: for (int i = 0; i < classes.length; i++) {
273: Object loader = classHash.get(classes[i]);
274: if (loader == this )
275: classHash.remove(classes[i]);
276: else
277: /* two plugins provide same class! */;
278: }
279: } //}}}
280:
281: //}}}
282:
283: //{{{ Private members
284:
285: // used to mark non-existent classes in class hash
286: private static final Object NO_CLASS = new Object();
287:
288: private static int INDEX;
289: private static int live;
290: private static Hashtable classHash = new Hashtable();
291:
292: private int id;
293: private boolean delegateFirst;
294: private PluginJAR jar;
295:
296: //{{{ _loadClass() method
297: /**
298: * Load class from this JAR only.
299: */
300: private synchronized Class _loadClass(String clazz,
301: boolean resolveIt) throws ClassNotFoundException {
302: jar.activatePlugin();
303:
304: synchronized (this ) {
305: Class cls = findLoadedClass(clazz);
306: if (cls != null) {
307: if (resolveIt)
308: resolveClass(cls);
309: return cls;
310: }
311:
312: String name = MiscUtilities.classToFile(clazz);
313:
314: try {
315: definePackage(clazz);
316: ZipFile zipFile = jar.getZipFile();
317: ZipEntry entry = zipFile.getEntry(name);
318:
319: if (entry == null)
320: throw new ClassNotFoundException(clazz);
321:
322: InputStream in = zipFile.getInputStream(entry);
323:
324: int len = (int) entry.getSize();
325: byte[] data = new byte[len];
326: int success = 0;
327: int offset = 0;
328: while (success < len) {
329: len -= success;
330: offset += success;
331: success = in.read(data, offset, len);
332: if (success == -1) {
333: Log.log(Log.ERROR, this ,
334: "Failed to load class " + clazz
335: + " from " + zipFile.getName());
336: throw new ClassNotFoundException(clazz);
337: }
338: }
339:
340: cls = defineClass(clazz, data, 0, data.length);
341:
342: if (resolveIt)
343: resolveClass(cls);
344:
345: return cls;
346: } catch (IOException io) {
347: Log.log(Log.ERROR, this , io);
348:
349: throw new ClassNotFoundException(clazz);
350: }
351: }
352: } //}}}
353:
354: //{{{ definePackage(clazz) method
355: private void definePackage(String clazz) throws IOException {
356: int idx = clazz.lastIndexOf('.');
357: if (idx != -1) {
358: String name = clazz.substring(0, idx);
359: if (getPackage(name) == null)
360: definePackage(name, new JarFile(jar.getFile())
361: .getManifest());
362: }
363: } //}}}
364:
365: //{{{ getMfValue() method
366: private String getMfValue(Attributes sectionAttrs,
367: Attributes mainAttrs, Attributes.Name name) {
368: String value = null;
369: if (sectionAttrs != null)
370: value = sectionAttrs.getValue(name);
371: else if (mainAttrs != null) {
372: value = mainAttrs.getValue(name);
373: }
374: return value;
375: }
376:
377: //}}}
378:
379: //{{{ definePackage(packageName, manifest) method
380: private void definePackage(String name, Manifest mf) {
381: if (mf == null) {
382: definePackage(name, null, null, null, null, null, null,
383: null);
384: return;
385: }
386:
387: Attributes sa = mf.getAttributes(name.replace('.', '/') + "/");
388: Attributes ma = mf.getMainAttributes();
389:
390: URL sealBase = null;
391: if (Boolean.valueOf(getMfValue(sa, ma, Name.SEALED))
392: .booleanValue()) {
393: try {
394: sealBase = jar.getFile().toURL();
395: } catch (MalformedURLException e) {
396: }
397: }
398:
399: Package pkg = definePackage(name, getMfValue(sa, ma,
400: Name.SPECIFICATION_TITLE), getMfValue(sa, ma,
401: Name.SPECIFICATION_VERSION), getMfValue(sa, ma,
402: Name.SPECIFICATION_VENDOR), getMfValue(sa, ma,
403: Name.IMPLEMENTATION_TITLE), getMfValue(sa, ma,
404: Name.IMPLEMENTATION_VERSION), getMfValue(sa, ma,
405: Name.IMPLEMENTATION_VENDOR), sealBase);
406: } //}}}
407:
408: //{{{ loadFromParent() method
409: private Class loadFromParent(String clazz)
410: throws ClassNotFoundException {
411: Class cls;
412:
413: ClassLoader parentLoader = getClass().getClassLoader();
414: if (parentLoader != null)
415: cls = parentLoader.loadClass(clazz);
416: else
417: cls = findSystemClass(clazz);
418:
419: return cls;
420: } //}}}
421:
422: //}}}
423: }
|