PageRenderTime 70ms CodeModel.GetById 40ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-1-pre5/org/gjt/sp/jedit/JARClassLoader.java

#
Java | 652 lines | 472 code | 81 blank | 99 comment | 87 complexity | 0e551555496993fefd7b3af80ebe7a45 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, 2000, 2001, 2002 Slava Pestov
  7 * Portions copyright (C) 1999 mike dillon
  8 * Portions copyright (C) 2002 Marco Hunsicker
  9 *
 10 * This program is free software; you can redistribute it and/or
 11 * modify it under the terms of the GNU General Public License
 12 * as published by the Free Software Foundation; either version 2
 13 * of the License, or any later version.
 14 *
 15 * This program is distributed in the hope that it will be useful,
 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 18 * GNU General Public License for more details.
 19 *
 20 * You should have received a copy of the GNU General Public License
 21 * along with this program; if not, write to the Free Software
 22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 23 */
 24
 25package org.gjt.sp.jedit;
 26
 27//{{{ Imports
 28import java.io.*;
 29import java.lang.reflect.Modifier;
 30import java.net.*;
 31import java.util.*;
 32import java.util.jar.*;
 33import java.util.zip.*;
 34import org.gjt.sp.jedit.gui.DockableWindowManager;
 35import org.gjt.sp.util.Log;
 36//}}}
 37
 38/**
 39 * A class loader implementation that loads classes from JAR files.
 40 * @author Slava Pestov
 41 * @version $Id: JARClassLoader.java 4329 2002-08-28 20:51:29Z spestov $
 42 */
 43public class JARClassLoader extends ClassLoader
 44{
 45	//{{{ JARClassLoader constructor
 46	/**
 47	 * This constructor creates a class loader for loading classes from all
 48	 * plugins. For example BeanShell uses one of these so that scripts can
 49	 * use plugin classes.
 50	 */
 51	public JARClassLoader()
 52	{
 53	} //}}}
 54
 55	//{{{ JARClassLoader constructor
 56	public JARClassLoader(String path)
 57		throws IOException
 58	{
 59		zipFile = new JarFile(path);
 60		definePackages();
 61
 62		jar = new EditPlugin.JAR(path,this);
 63
 64		Enumeration entires = zipFile.entries();
 65		while(entires.hasMoreElements())
 66		{
 67			ZipEntry entry = (ZipEntry)entires.nextElement();
 68			String name = entry.getName();
 69			String lname = name.toLowerCase();
 70			if(lname.equals("actions.xml"))
 71			{
 72				jEdit.loadActions(
 73					path + "!actions.xml",
 74					new BufferedReader(new InputStreamReader(
 75					zipFile.getInputStream(entry))),
 76					jar.getActions());
 77			}
 78			if(lname.equals("dockables.xml"))
 79			{
 80				DockableWindowManager.loadDockableWindows(
 81					path + "!dockables.xml",
 82					new BufferedReader(new InputStreamReader(
 83					zipFile.getInputStream(entry))),
 84					jar.getActions());
 85			}
 86			else if(lname.endsWith(".props"))
 87				jEdit.loadProps(zipFile.getInputStream(entry),true);
 88			else if(name.endsWith(".class"))
 89			{
 90				classHash.put(MiscUtilities.fileToClass(name),this);
 91
 92				if(name.endsWith("Plugin.class"))
 93					pluginClasses.addElement(name);
 94			}
 95		}
 96
 97		jEdit.addPluginJAR(jar);
 98	} //}}}
 99
100	//{{{ loadClass() method
101	/**
102	 * @exception ClassNotFoundException if the class could not be found
103	 */
104	public Class loadClass(String clazz, boolean resolveIt)
105		throws ClassNotFoundException
106	{
107		// see what JARClassLoader this class is in
108		Object obj = classHash.get(clazz);
109		if(obj == NO_CLASS)
110		{
111			// we remember which classes we don't exist
112			// because BeanShell tries loading all possible
113			// <imported prefix>.<class name> combinations
114			throw new ClassNotFoundException(clazz);
115		}
116		else if(obj instanceof ClassLoader)
117		{
118			JARClassLoader classLoader = (JARClassLoader)obj;
119			return classLoader._loadClass(clazz,resolveIt);
120		}
121
122		// if it's not in the class hash, and not marked as
123		// non-existent, try loading it from the CLASSPATH
124		try
125		{
126			Class cls;
127
128			/* Defer to whoever loaded us (such as JShell,
129			 * Echidna, etc) */
130			ClassLoader parentLoader = getClass().getClassLoader();
131			if (parentLoader != null)
132				cls = parentLoader.loadClass(clazz);
133			else
134				cls = findSystemClass(clazz);
135
136			return cls;
137		}
138		catch(ClassNotFoundException cnf)
139		{
140			// remember that this class doesn't exist for
141			// future reference
142			classHash.put(clazz,NO_CLASS);
143
144			throw cnf;
145		}
146	} //}}}
147
148	//{{{ getResourceAsStream() method
149	public InputStream getResourceAsStream(String name)
150	{
151		if(zipFile == null)
152			return null;
153
154		try
155		{
156			ZipEntry entry = zipFile.getEntry(name);
157			if(entry == null)
158				return getSystemResourceAsStream(name);
159			else
160				return zipFile.getInputStream(entry);
161		}
162		catch(IOException io)
163		{
164			Log.log(Log.ERROR,this,io);
165
166			return null;
167		}
168	} //}}}
169
170	//{{{ getResource() method
171	public URL getResource(String name)
172	{
173		if(zipFile == null)
174			return null;
175
176		ZipEntry entry = zipFile.getEntry(name);
177		if(entry == null)
178			return getSystemResource(name);
179
180		try
181		{
182			return new URL(getResourceAsPath(name));
183		}
184		catch(MalformedURLException mu)
185		{
186			Log.log(Log.ERROR,this,mu);
187			return null;
188		}
189	} //}}}
190
191	//{{{ getResourceAsPath() method
192	public String getResourceAsPath(String name)
193	{
194		if(zipFile == null)
195			return null;
196
197		if(!name.startsWith("/"))
198			name = "/" + name;
199
200		return "jeditresource:/" + MiscUtilities.getFileName(
201			jar.getPath()) + "!" + name;
202	} //}}}
203
204	//{{{ closeZipFile() method
205	/**
206	 * Closes the ZIP file. This plugin will no longer be usable
207	 * after this.
208	 * @since jEdit 2.6pre1
209	 */
210	public void closeZipFile()
211	{
212		if(zipFile == null)
213			return;
214
215		try
216		{
217			zipFile.close();
218		}
219		catch(IOException io)
220		{
221			Log.log(Log.ERROR,this,io);
222		}
223
224		zipFile = null;
225	} //}}}
226
227	//{{{ getZipFile() method
228	/**
229	 * Returns the ZIP file associated with this class loader.
230	 * @since jEdit 3.0final
231	 */
232	public ZipFile getZipFile()
233	{
234		return zipFile;
235	} //}}}
236
237	//{{{ startAllPlugins() method
238	void startAllPlugins()
239	{
240		for(int i = 0; i < pluginClasses.size(); i++)
241		{
242			String name = (String)pluginClasses.elementAt(i);
243			name = MiscUtilities.fileToClass(name);
244
245			try
246			{
247				loadPluginClass(name);
248			}
249			catch(Throwable t)
250			{
251				Log.log(Log.ERROR,this,"Error while starting plugin " + name);
252				Log.log(Log.ERROR,this,t);
253
254				jar.addPlugin(new EditPlugin.Broken(name));
255				String[] args = { t.toString() };
256				jEdit.pluginError(jar.getPath(),
257					"plugin-error.start-error",args);
258			}
259		}
260	} //}}}
261
262	//{{{ Private members
263
264	// used to mark non-existent classes in class hash
265	private static final Object NO_CLASS = new Object();
266
267	private static Hashtable classHash = new Hashtable();
268
269	private EditPlugin.JAR jar;
270	private Vector pluginClasses = new Vector();
271	private JarFile zipFile;
272
273	//{{{ loadPluginClass() method
274	private void loadPluginClass(String name)
275		throws Exception
276	{
277		// Check if a plugin with the same name is already loaded
278		EditPlugin[] plugins = jEdit.getPlugins();
279
280		for(int i = 0; i < plugins.length; i++)
281		{
282			if(plugins[i].getClass().getName().equals(name))
283			{
284				jEdit.pluginError(jar.getPath(),
285					"plugin-error.already-loaded",null);
286				return;
287			}
288		}
289
290		/* This is a bit silly... but WheelMouse seems to be
291		 * unmaintained so the best solution is to add a hack here.
292		 */
293		if(name.equals("WheelMousePlugin")
294			&& OperatingSystem.hasJava14())
295		{
296			jar.addPlugin(new EditPlugin.Broken(name));
297			jEdit.pluginError(jar.getPath(),"plugin-error.obsolete",null);
298			return;
299		}
300
301		// Check dependencies
302		if(!checkDependencies(name))
303		{
304			jar.addPlugin(new EditPlugin.Broken(name));
305			return;
306		}
307
308		// JDK 1.1.8 throws a GPF when we do an isAssignableFrom()
309		// on an unresolved class
310		Class clazz = loadClass(name,true);
311		int modifiers = clazz.getModifiers();
312		if(!Modifier.isInterface(modifiers)
313			&& !Modifier.isAbstract(modifiers)
314			&& EditPlugin.class.isAssignableFrom(clazz))
315		{
316			String label = jEdit.getProperty("plugin."
317				+ name + ".name");
318			String version = jEdit.getProperty("plugin."
319				+ name + ".version");
320
321			if(version == null)
322			{
323				Log.log(Log.ERROR,this,"Plugin " +
324					name + " needs"
325					+ " 'name' and 'version' properties.");
326				jar.addPlugin(new EditPlugin.Broken(name));
327				return;
328			}
329
330			jar.getActions().setLabel(jEdit.getProperty(
331				"action-set.plugin",
332				new String[] { label }));
333			Log.log(Log.NOTICE,this,"Starting plugin " + label
334				+ " (version " + version + ")");
335
336			jar.addPlugin((EditPlugin)clazz.newInstance());
337		}
338	} //}}}
339
340	//{{{ checkDependencies() method
341	private boolean checkDependencies(String name)
342	{
343		int i = 0;
344
345		String dep;
346		while((dep = jEdit.getProperty("plugin." + name + ".depend." + i++)) != null)
347		{
348			int index = dep.indexOf(' ');
349			if(index == -1)
350			{
351				Log.log(Log.ERROR,this,name + " has an invalid"
352					+ " dependency: " + dep);
353				return false;
354			}
355
356			String what = dep.substring(0,index);
357			String arg = dep.substring(index + 1);
358
359			if(what.equals("jdk"))
360			{
361				if(MiscUtilities.compareStrings(
362					System.getProperty("java.version"),
363					arg,false) < 0)
364				{
365					String[] args = { arg,
366						System.getProperty("java.version") };
367					jEdit.pluginError(jar.getPath(),"plugin-error.dep-jdk",args);
368					return false;
369				}
370			}
371			else if(what.equals("jedit"))
372			{
373				if(arg.length() != 11)
374				{
375					Log.log(Log.ERROR,this,"Invalid jEdit version"
376						+ " number: " + arg);
377					return false;
378				}
379
380				if(MiscUtilities.compareStrings(
381					jEdit.getBuild(),arg,false) < 0)
382				{
383					String needs = MiscUtilities.buildToVersion(arg);
384					String[] args = { needs,
385						jEdit.getVersion() };
386					jEdit.pluginError(jar.getPath(),
387						"plugin-error.dep-jedit",args);
388					return false;
389				}
390			}
391			else if(what.equals("plugin"))
392			{
393				int index2 = arg.indexOf(' ');
394				if(index2 == -1)
395				{
396					Log.log(Log.ERROR,this,name 
397						+ " has an invalid dependency: "
398						+ dep + " (version is missing)");
399					return false;
400				}
401				
402				String plugin = arg.substring(0,index2);
403				String needVersion = arg.substring(index2 + 1);
404				String currVersion = jEdit.getProperty("plugin." 
405					+ plugin + ".version");
406
407				if(currVersion == null)
408				{
409					String[] args = { needVersion, plugin };
410					jEdit.pluginError(jar.getPath(),
411						"plugin-error.dep-plugin.no-version",
412						args);
413					return false;
414				}
415
416				if(MiscUtilities.compareStrings(currVersion,
417					needVersion,false) < 0)
418				{
419					String[] args = { needVersion, plugin, currVersion };
420					jEdit.pluginError(jar.getPath(),
421						"plugin-error.dep-plugin",args);
422					return false;
423				}
424
425				if(jEdit.getPlugin(plugin) instanceof EditPlugin.Broken)
426				{
427					String[] args = { plugin };
428					jEdit.pluginError(jar.getPath(),
429						"plugin-error.dep-plugin.broken",args);
430					return false;
431				}
432			}
433			else if(what.equals("class"))
434			{
435				try
436				{
437					loadClass(arg,false);
438				}
439				catch(Exception e)
440				{
441					String[] args = { arg };
442					jEdit.pluginError(jar.getPath(),
443						"plugin-error.dep-class",args);
444					return false;
445				}
446			}
447			else
448			{
449				Log.log(Log.ERROR,this,name + " has unknown"
450					+ " dependency: " + dep);
451				return false;
452			}
453		}
454
455		return true;
456	} //}}}
457
458	//{{{ _loadClass() method
459	/**
460	 * Load class from this JAR only.
461	 */
462	private Class _loadClass(String clazz, boolean resolveIt)
463		throws ClassNotFoundException
464	{
465		Class cls = findLoadedClass(clazz);
466		if(cls != null)
467		{
468			if(resolveIt)
469				resolveClass(cls);
470			return cls;
471		}
472
473		String name = MiscUtilities.classToFile(clazz);
474
475		try
476		{
477			ZipEntry entry = zipFile.getEntry(name);
478
479			if(entry == null)
480				throw new ClassNotFoundException(clazz);
481
482			InputStream in = zipFile.getInputStream(entry);
483
484			int len = (int)entry.getSize();
485			byte[] data = new byte[len];
486			int success = 0;
487			int offset = 0;
488			while(success < len)
489			{
490				len -= success;
491				offset += success;
492				success = in.read(data,offset,len);
493				if(success == -1)
494				{
495					Log.log(Log.ERROR,this,"Failed to load class "
496						+ clazz + " from " + zipFile.getName());
497					throw new ClassNotFoundException(clazz);
498				}
499			}
500
501			cls = defineClass(clazz,data,0,data.length);
502
503			if(resolveIt)
504				resolveClass(cls);
505
506			return cls;
507		}
508		catch(IOException io)
509		{
510			Log.log(Log.ERROR,this,io);
511
512			throw new ClassNotFoundException(clazz);
513		}
514	} //}}}
515
516	//{{{ definePackages() method
517	/**
518	 * Defines all packages found in the given Java archive file. The
519	 * attributes contained in the specified Manifest will be used to obtain
520	 * package version and sealing information.
521	 */
522	private void definePackages()
523	{
524		try
525		{
526			Manifest manifest = zipFile.getManifest();
527
528			if(manifest != null)
529			{
530				Map entries = manifest.getEntries();
531				Iterator i = entries.keySet().iterator();
532
533				while(i.hasNext())
534				{
535					String path = (String)i.next();
536
537					if(!path.endsWith(".class"))
538					{
539						String name = path.replace('/', '.');
540
541						if(name.endsWith("."))
542							name = name.substring(0, name.length() - 1);
543
544						// code url not implemented
545						definePackage(path,name,manifest,null);
546					}
547				}
548			}
549		}
550		catch (Exception ex)
551		{
552			// should never happen, not severe anyway
553			Log.log(Log.ERROR, this,"Error extracting manifest info "
554				+ "for file " + zipFile);
555			Log.log(Log.ERROR, this, ex);
556		}
557	} //}}}
558
559	//{{{ definePackage() method
560	/**
561	 * Defines a new package by name in this ClassLoader. The attributes
562	 * contained in the specified Manifest will be used to obtain package
563	 * version and sealing information. For sealed packages, the additional
564	 * URL specifies the code source URL from which the package was loaded.
565	 */
566	private Package definePackage(String path, String name, Manifest man,
567		URL url) throws IllegalArgumentException
568	{
569		String specTitle = null;
570		String specVersion = null;
571		String specVendor = null;
572		String implTitle = null;
573		String implVersion = null;
574		String implVendor = null;
575		String sealed = null;
576		URL sealBase = null;
577
578		Attributes attr = man.getAttributes(path);
579
580		if(attr != null)
581		{
582			specTitle = attr.getValue(
583				Attributes.Name.SPECIFICATION_TITLE);
584			specVersion = attr.getValue(
585				Attributes.Name.SPECIFICATION_VERSION);
586			specVendor = attr.getValue(
587				Attributes.Name.SPECIFICATION_VENDOR);
588			implTitle = attr.getValue(
589				Attributes.Name.IMPLEMENTATION_TITLE);
590			implVersion = attr.getValue(
591				Attributes.Name.IMPLEMENTATION_VERSION);
592			implVendor = attr.getValue(
593				Attributes.Name.IMPLEMENTATION_VENDOR);
594			sealed = attr.getValue(Attributes.Name.SEALED);
595		}
596
597		attr = man.getMainAttributes();
598
599		if (attr != null)
600		{
601			if (specTitle == null)
602			{
603				specTitle = attr.getValue(
604					Attributes.Name.SPECIFICATION_TITLE);
605			}
606
607			if (specVersion == null)
608			{
609				specVersion = attr.getValue(
610					Attributes.Name.SPECIFICATION_VERSION);
611			}
612
613			if (specVendor == null)
614			{
615				specVendor = attr.getValue(
616					Attributes.Name.SPECIFICATION_VENDOR);
617			}
618
619			if (implTitle == null)
620			{
621				implTitle = attr.getValue(
622					Attributes.Name.IMPLEMENTATION_TITLE);
623			}
624
625			if (implVersion == null)
626			{
627				implVersion = attr.getValue(
628					Attributes.Name.IMPLEMENTATION_VERSION);
629			}
630
631			if (implVendor == null)
632			{
633				implVendor = attr.getValue(
634					Attributes.Name.IMPLEMENTATION_VENDOR);
635			}
636
637			if (sealed == null)
638			{
639				sealed = attr.getValue(Attributes.Name.SEALED);
640			}
641		}
642
643		//if("true".equalsIgnoreCase(sealed))
644		//	sealBase = url;
645
646		return super.definePackage(name, specTitle, specVersion, specVendor,
647			implTitle, implVersion, implVendor,
648			sealBase);
649	} //}}}
650
651	//}}}
652}