PageRenderTime 21ms CodeModel.GetById 1ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/JARClassLoader.java

#
Java | 530 lines | 368 code | 57 blank | 105 comment | 71 complexity | 1d104ef90b10efb8aba8ec6f7c699706 MD5 | raw file
  1/*
  2 * JARClassLoader.java - Loads classes from JAR files
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 1999, 2003 Slava Pestov
  7 * Portions copyright (C) 1999 mike dillon
  8 *
  9 * This program is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU General Public License
 11 * as published by the Free Software Foundation; either version 2
 12 * of the License, or any later version.
 13 *
 14 * This program is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17 * GNU General Public License for more details.
 18 *
 19 * You should have received a copy of the GNU General Public License
 20 * along with this program; if not, write to the Free Software
 21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 22 */
 23
 24package org.gjt.sp.jedit;
 25
 26//{{{ Imports
 27import java.io.InputStream;
 28import java.io.IOException;
 29import java.net.URL;
 30import java.util.*;
 31import java.util.zip.ZipEntry;
 32import java.util.zip.ZipFile;
 33import org.gjt.sp.util.Log;
 34
 35import java.util.jar.Manifest;
 36import java.util.jar.JarFile;
 37import java.net.MalformedURLException;
 38import java.util.jar.Attributes;
 39import java.util.jar.Attributes.Name;
 40//}}}
 41
 42/**
 43 * A class loader implementation that loads classes from JAR files. All
 44 * instances share the same set of classes.
 45 * @author Slava Pestov
 46 * @version $Id: JARClassLoader.java 18919 2010-11-04 10:52:55Z kpouer $
 47 */
 48public class JARClassLoader extends ClassLoader
 49{
 50	//{{{ JARClassLoader constructor
 51	/**
 52	 * This constructor creates a class loader for loading classes from all
 53	 * plugins. For example BeanShell uses one of these so that scripts can
 54	 * use plugin classes.
 55	 */
 56	public JARClassLoader()
 57	{
 58		this(true);
 59	}
 60
 61	/**
 62	 * Creates a class loader that will optionally delegate the
 63	 * finding of classes to the parent class loader by default.
 64	 *
 65	 * @since jEdit 4.3pre6
 66	 */
 67	public JARClassLoader(boolean delegateFirst)
 68	{
 69		this.delegateFirst = delegateFirst;
 70		// for debugging
 71		id = INDEX++;
 72		live++;
 73	} //}}}
 74
 75	//{{{ loadClass() method
 76	/**
 77	 * @exception ClassNotFoundException if the class could not be found
 78	 */
 79	public Class loadClass(String clazz, boolean resolveIt)
 80		throws ClassNotFoundException
 81	{
 82		ClassNotFoundException pending = null;
 83		if (delegateFirst)
 84		{
 85			try
 86			{
 87				return loadFromParent(clazz);
 88			}
 89			catch (ClassNotFoundException cnf)
 90			{
 91				// keep going if class was not found.
 92				pending = cnf;
 93			}
 94		}
 95
 96		Object obj = classHash.get(clazz);
 97		if(obj == NO_CLASS)
 98		{
 99			// we remember which classes we don't exist
100			// because BeanShell tries loading all possible
101			// <imported prefix>.<class name> combinations
102			throw new ClassNotFoundException(clazz);
103		}
104		else if(obj instanceof JARClassLoader)
105		{
106			JARClassLoader classLoader = (JARClassLoader)obj;
107			try
108			{
109				return classLoader._loadClass(clazz,resolveIt);
110			} catch (ClassNotFoundException cnf2)
111			{
112				classHash.put(clazz,NO_CLASS);
113				throw cnf2;
114			}
115		}
116		else if (delegateFirst)
117		{
118			// if delegating, reaching this statement means
119			// the class was really not found. Otherwise
120			// we'll try loading from the parent class loader.
121			throw pending;
122		}
123
124		return loadFromParent(clazz);
125	} //}}}
126
127	//{{{ getResourceAsStream() method
128	public InputStream getResourceAsStream(String name)
129	{
130		try
131		{
132			// try in current jar first
133			if(jar != null)
134			{
135				ZipFile zipFile = jar.getZipFile();
136				ZipEntry entry = zipFile.getEntry(name);
137				if(entry != null)
138				{
139					return zipFile.getInputStream(entry);
140				}
141			}
142			// then try from another jar
143			Object obj = resourcesHash.get(name);
144			if(obj instanceof JARClassLoader)
145			{
146				JARClassLoader classLoader = (JARClassLoader)obj;
147				return classLoader.getResourceAsStream(name);
148			}
149			// finally try from the system class loader
150			return getSystemResourceAsStream(name);
151		}
152		catch(IOException io)
153		{
154			Log.log(Log.ERROR,this,io);
155
156			return null;
157		}
158	} //}}}
159
160	//{{{ getResource() method
161	/**
162	 * overriding getResource() because we want to search FIRST in this
163	 * ClassLoader, then the parent, the path, etc.
164	 */
165	public URL getResource(String name)
166	{
167		try
168		{
169			if(jar != null)
170			{
171				ZipFile zipFile = jar.getZipFile();
172				ZipEntry entry = zipFile.getEntry(name);
173				if(entry != null)
174				{
175					return new URL(getResourceAsPath(name));
176				}
177			}
178			
179			Object obj = resourcesHash.get(name);
180			if(obj instanceof JARClassLoader)
181			{
182				JARClassLoader classLoader = (JARClassLoader)obj;
183				return classLoader.getResource(name);
184			} else
185			{
186				URL ret = getSystemResource(name); 
187				if(ret != null)
188				{
189					Log.log(Log.DEBUG,JARClassLoader.class,"Would have returned null for getResource("+name+")");
190					Log.log(Log.DEBUG,JARClassLoader.class,"returning("+ret+")");
191				}
192				return ret;
193			}
194		}
195		catch(IOException io)
196		{
197			Log.log(Log.ERROR,this,io);
198			return null;
199		}
200	} //}}}
201
202	//{{{ getResourceAsPath() method
203	/**
204	 * construct a jeditresource:/etc path from the name
205	 * of a resource in the associated jar.
206	 * The existence of the resource is not actually checked.
207	 *
208	 * @param name name of the resource
209	 * @return jeditresource:/path_to_the_jar!name_of_the_resource
210	 * @throws UnsupportedOperationException if this is an anonymous
211	 * JARClassLoader (no associated jar).
212	 */
213	public String getResourceAsPath(String name)
214	{
215		// this must be fixed during plugin development
216		if(jar == null)
217			throw new UnsupportedOperationException(
218				"don't call getResourceAsPath() on anonymous JARClassLoader");
219
220		if(!name.startsWith("/"))
221			name = '/' + name;
222
223		return "jeditresource:/" + MiscUtilities.getFileName(
224			jar.getPath()) + '!' + name;
225	} //}}}
226
227	//{{{ dump() method
228	/**
229	 * For debugging.
230	 */
231	public static void dump()
232	{
233		Log.log(Log.DEBUG,JARClassLoader.class,
234			"Total instances created: " + INDEX);
235		Log.log(Log.DEBUG,JARClassLoader.class,
236			"Live instances: " + live);
237		synchronized(classHash)
238		{
239			for (Map.Entry<String, Object> entry : classHash.entrySet())
240			{
241				if (entry.getValue() != NO_CLASS)
242				{
243					Log.log(Log.DEBUG, JARClassLoader.class,
244						entry.getKey() + " ==> "
245							+ entry.getValue());
246				}
247			}
248		}
249	} //}}}
250
251	//{{{ toString() method
252	public String toString()
253	{
254		if(jar == null)
255			return "<anonymous>(" + id + ')';
256		else
257			return jar.getPath() + " (" + id + ')';
258	} //}}}
259
260	//{{{ findResources() method
261	/**
262	 * @return zero or one resource, as returned by getResource()
263	 */
264	public Enumeration getResources(String name) throws IOException
265	{
266		class SingleElementEnumeration implements Enumeration
267		{
268			private Object element;
269
270			SingleElementEnumeration(Object element)
271			{
272				this.element = element;
273			}
274
275			public boolean hasMoreElements()
276			{
277				return element != null;
278			}
279
280			public Object nextElement()
281			{
282				if(element != null)
283				{
284					Object retval = element;
285					element = null;
286					return retval;
287				}
288				else
289					throw new NoSuchElementException();
290			}
291		}
292
293		URL resource = getResource(name);
294		return new SingleElementEnumeration(resource);
295	} //}}}
296
297	//{{{ finalize() method
298	protected void finalize()
299	{
300		live--;
301	} //}}}
302
303	//{{{ Package-private members
304
305	//{{{ JARClassLoader constructor
306	/**
307	 * @since jEdit 4.2pre1
308	 */
309	JARClassLoader(PluginJAR jar)
310	{
311		this();
312		this.jar = jar;
313	} //}}}
314
315	//{{{ activate() method
316	void activate()
317	{
318		if (jar.getPlugin() != null)
319		{
320			String _delegate = jEdit.getProperty(
321				"plugin." + jar.getPlugin().getClassName() + ".class_loader_delegate");
322			delegateFirst = _delegate == null || "true".equals(_delegate);
323		}
324
325		String[] classes = jar.getClasses();
326		if(classes != null)
327		{
328			for(int i = 0; i < classes.length; i++)
329			{
330				classHash.put(classes[i],this);
331			}
332		}
333
334		String[] resources = jar.getResources();
335		if(resources != null)
336		{
337			for(int i = 0; i < resources.length; i++)
338			{
339				resourcesHash.put(resources[i],this);
340			}
341		}
342	} //}}}
343
344	//{{{ deactivate() method
345	void deactivate()
346	{
347		String[] classes = jar.getClasses();
348		if(classes != null)
349		{
350			for(int i = 0; i < classes.length; i++)
351			{
352				Object loader = classHash.get(classes[i]);
353				if(loader == this)
354					classHash.remove(classes[i]);
355				else
356					/* two plugins provide same class! */;
357			}
358		}
359
360		String[] resources = jar.getResources();
361		if(resources == null)
362			return;
363
364		for(int i = 0; i < resources.length; i++)
365		{
366			Object loader = resourcesHash.get(resources[i]);
367			if(loader == this)
368				resourcesHash.remove(resources[i]);
369			else
370				/* two plugins provide same resource! */;
371		}
372	} //}}}
373
374	//}}}
375
376	//{{{ Private members
377
378	// used to mark non-existent classes in class hash
379	private static final Object NO_CLASS = new Object();
380
381	private static int INDEX;
382	private static int live;
383	private static Map<String, Object> classHash = new Hashtable<String, Object>();
384	private static Map<String, Object> resourcesHash = new HashMap<String, Object>();
385
386	private int id;
387	private boolean delegateFirst;
388	private PluginJAR jar;
389
390	//{{{ _loadClass() method
391	/**
392	 * Load class from this JAR only.
393	 */
394	private synchronized Class _loadClass(String clazz, boolean resolveIt)
395		throws ClassNotFoundException
396	{
397		jar.activatePlugin();
398
399		synchronized(this)
400		{
401			Class cls = findLoadedClass(clazz);
402			if(cls != null)
403			{
404				if(resolveIt)
405					resolveClass(cls);
406				return cls;
407			}
408
409			String name = MiscUtilities.classToFile(clazz);
410
411			try
412			{
413				definePackage(clazz);
414				ZipFile zipFile = jar.getZipFile();
415				ZipEntry entry = zipFile.getEntry(name);
416
417				if(entry == null)
418					throw new ClassNotFoundException(clazz);
419
420				InputStream in = zipFile.getInputStream(entry);
421
422				int len = (int)entry.getSize();
423				byte[] data = new byte[len];
424				int success = 0;
425				int offset = 0;
426				while(success < len)
427				{
428					len -= success;
429					offset += success;
430					success = in.read(data,offset,len);
431					if(success == -1)
432					{
433						Log.log(Log.ERROR,this,"Failed to load class "
434							+ clazz + " from " + zipFile.getName());
435						throw new ClassNotFoundException(clazz);
436					}
437				}
438
439				cls = defineClass(clazz,data,0,data.length);
440
441				if(resolveIt)
442					resolveClass(cls);
443
444				return cls;
445			}
446			catch(IOException io)
447			{
448				Log.log(Log.ERROR,this,io);
449
450				throw new ClassNotFoundException(clazz);
451			}
452		}
453	} //}}}
454
455	//{{{ definePackage(clazz) method
456	private void definePackage(String clazz) throws IOException
457	{
458		int idx = clazz.lastIndexOf('.');
459		if (idx != -1)
460		{
461			String name = clazz.substring(0, idx);
462			if (getPackage(name) == null) definePackage(name, new JarFile(jar.getFile()).getManifest());
463		}
464	} //}}}
465
466	//{{{ getMfValue() method
467	private static String getMfValue(Attributes sectionAttrs, Attributes mainAttrs, Attributes.Name name)
468	{
469		String value=null;
470		if (sectionAttrs != null)
471			value = sectionAttrs.getValue(name);
472		else if (mainAttrs != null)
473		{
474			value = mainAttrs.getValue(name);
475		}
476		return value;
477	}
478	//}}}
479
480	//{{{ definePackage(packageName, manifest) method
481	private void definePackage(String name, Manifest mf)
482	{
483		if (mf==null)
484		{
485			definePackage(name, null, null, null, null, null,
486			null, null);
487			return;
488		}
489
490		Attributes sa = mf.getAttributes(name.replace('.', '/') + '/');
491		Attributes ma = mf.getMainAttributes();
492
493		URL sealBase = null;
494		if (Boolean.valueOf(getMfValue(sa, ma, Name.SEALED)).booleanValue())
495		{
496			try
497			{
498				sealBase = jar.getFile().toURL();
499			}
500			catch (MalformedURLException e) {}
501		}
502
503		definePackage(
504			name,
505			getMfValue(sa, ma, Name.SPECIFICATION_TITLE),
506			getMfValue(sa, ma, Name.SPECIFICATION_VERSION),
507			getMfValue(sa, ma, Name.SPECIFICATION_VENDOR),
508			getMfValue(sa, ma, Name.IMPLEMENTATION_TITLE),
509			getMfValue(sa, ma, Name.IMPLEMENTATION_VERSION),
510			getMfValue(sa, ma, Name.IMPLEMENTATION_VENDOR),
511			sealBase);
512	} //}}}
513
514	//{{{ loadFromParent() method
515	private Class loadFromParent(String clazz)
516		throws ClassNotFoundException
517	{
518		Class cls;
519
520		ClassLoader parentLoader = getClass().getClassLoader();
521		if (parentLoader != null)
522			cls = parentLoader.loadClass(clazz);
523		else
524			cls = findSystemClass(clazz);
525
526		return cls;
527	} //}}}
528
529	//}}}
530}