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

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