PageRenderTime 58ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre14/org/gjt/sp/jedit/PluginJAR.java

#
Java | 1484 lines | 1057 code | 160 blank | 267 comment | 198 complexity | 7a8aded660449fc4fd9109f490ee8d19 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. /*
  2. * PluginJAR.java - Controls JAR loading and unloading
  3. * :tabSize=8:indentSize=8:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 1999, 2004 Slava Pestov
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  21. */
  22. package org.gjt.sp.jedit;
  23. //{{{ Imports
  24. import javax.swing.SwingUtilities;
  25. import java.io.*;
  26. import java.lang.reflect.Modifier;
  27. import java.net.URL;
  28. import java.util.*;
  29. import java.util.zip.*;
  30. import org.gjt.sp.jedit.browser.VFSBrowser;
  31. import org.gjt.sp.jedit.buffer.*;
  32. import org.gjt.sp.jedit.gui.DockableWindowManager;
  33. import org.gjt.sp.jedit.msg.*;
  34. import org.gjt.sp.util.Log;
  35. //}}}
  36. /**
  37. * Loads and unloads plugins.<p>
  38. *
  39. * <h3>JAR file contents</h3>
  40. *
  41. * When loading a plugin, jEdit looks for the following resources:
  42. *
  43. * <ul>
  44. * <li>A file named <code>actions.xml</code> defining plugin actions.
  45. * Only one such file per plugin is allowed. See {@link ActionSet} for
  46. * syntax.</li>
  47. * <li>A file named <code>browser.actions.xml</code> defining file system
  48. * browser actions.
  49. * Only one such file per plugin is allowed. See {@link ActionSet} for
  50. * syntax.</li>
  51. * <li>A file named <code>dockables.xml</code> defining dockable windows.
  52. * Only one such file per plugin is allowed. See {@link
  53. * org.gjt.sp.jedit.gui.DockableWindowManager} for
  54. * syntax.</li>
  55. * <li>A file named <code>services.xml</code> defining additional services
  56. * offered by the plugin, such as virtual file systems.
  57. * Only one such file per plugin is allowed. See {@link
  58. * org.gjt.sp.jedit.ServiceManager} for
  59. * syntax.</li>
  60. * <li>File with extension <code>.props</code> containing name/value pairs
  61. * separated by an equals sign.
  62. * A plugin can supply any number of property files. Property files are used
  63. * to define plugin men items, plugin option panes, as well as arbitriary
  64. * settings and strings used by the plugin. See {@link EditPlugin} for
  65. * information about properties used by jEdit. See
  66. * <code>java.util.Properties</code> for property file syntax.</li>
  67. * </ul>
  68. *
  69. * For a plugin to actually do something once it is resident in memory,
  70. * it must contain a class whose name ends with <code>Plugin</code>.
  71. * This class, known as the <i>plugin core class</i> must extend
  72. * {@link EditPlugin} and define a few required properties, otherwise it is
  73. * ignored.
  74. *
  75. * <h3>Dynamic and deferred loading</h3>
  76. *
  77. * Unlike in prior jEdit versions, jEdit 4.2 and later allow
  78. * plugins to be added and removed to the resident set at any time using
  79. * the {@link jEdit#addPluginJAR(String)} and
  80. * {@link jEdit#removePluginJAR(PluginJAR,boolean)} methods. Furthermore, the
  81. * plugin core class might not be loaded until the plugin is first used. See
  82. * {@link EditPlugin#start()} for a full description.
  83. *
  84. * @see org.gjt.sp.jedit.jEdit#getProperty(String)
  85. * @see org.gjt.sp.jedit.jEdit#getPlugin(String)
  86. * @see org.gjt.sp.jedit.jEdit#getPlugins()
  87. * @see org.gjt.sp.jedit.jEdit#getPluginJAR(String)
  88. * @see org.gjt.sp.jedit.jEdit#getPluginJARs()
  89. * @see org.gjt.sp.jedit.jEdit#addPluginJAR(String)
  90. * @see org.gjt.sp.jedit.jEdit#removePluginJAR(PluginJAR,boolean)
  91. * @see org.gjt.sp.jedit.ActionSet
  92. * @see org.gjt.sp.jedit.gui.DockableWindowManager
  93. * @see org.gjt.sp.jedit.OptionPane
  94. * @see org.gjt.sp.jedit.PluginJAR
  95. * @see org.gjt.sp.jedit.ServiceManager
  96. *
  97. * @author Slava Pestov
  98. * @version $Id: PluginJAR.java 5023 2004-04-20 19:58:39Z spestov $
  99. * @since jEdit 4.2pre1
  100. */
  101. public class PluginJAR
  102. {
  103. //{{{ getPath() method
  104. /**
  105. * Returns the full path name of this plugin's JAR file.
  106. */
  107. public String getPath()
  108. {
  109. return path;
  110. } //}}}
  111. //{{{ getCachePath() method
  112. /**
  113. * Returns the full path name of this plugin's summary file.
  114. * The summary file is used to store certain information which allows
  115. * loading of the plugin's resources and core class to be deferred
  116. * until the plugin is first used. As long as a plugin is using the
  117. * jEdit 4.2 plugin API, no extra effort is required to take advantage
  118. * of the summary cache.
  119. */
  120. public String getCachePath()
  121. {
  122. return cachePath;
  123. } //}}}
  124. //{{{ getFile() method
  125. /**
  126. * Returns a file pointing to the plugin JAR.
  127. */
  128. public File getFile()
  129. {
  130. return file;
  131. } //}}}
  132. //{{{ getClassLoader() method
  133. /**
  134. * Returns the plugin's class loader.
  135. */
  136. public JARClassLoader getClassLoader()
  137. {
  138. return classLoader;
  139. } //}}}
  140. //{{{ getZipFile() method
  141. /**
  142. * Returns the plugin's JAR file, opening it if necessary.
  143. * @since jEdit 4.2pre1
  144. */
  145. public synchronized ZipFile getZipFile() throws IOException
  146. {
  147. if(zipFile == null)
  148. {
  149. Log.log(Log.DEBUG,this,"Opening " + path);
  150. zipFile = new ZipFile(path);
  151. }
  152. return zipFile;
  153. } //}}}
  154. //{{{ getActions() method
  155. /**
  156. * @deprecated Call getActionSet() instead
  157. */
  158. public ActionSet getActions()
  159. {
  160. return getActionSet();
  161. } //}}}
  162. //{{{ getActionSet() method
  163. /**
  164. * Returns the plugin's action set for the jEdit action context
  165. * {@link jEdit#getActionContext()}. These actions are loaded from
  166. * the <code>actions.xml</code> file; see {@link ActionSet}.
  167. *.
  168. * @since jEdit 4.2pre1
  169. */
  170. public ActionSet getActionSet()
  171. {
  172. return actions;
  173. } //}}}
  174. //{{{ getBrowserActionSet() method
  175. /**
  176. * Returns the plugin's action set for the file system browser action
  177. * context {@link
  178. * org.gjt.sp.jedit.browser.VFSBrowser#getActionContext()}.
  179. * These actions are loaded from
  180. * the <code>browser.actions.xml</code> file; see {@link ActionSet}.
  181. *.
  182. * @since jEdit 4.2pre1
  183. */
  184. public ActionSet getBrowserActionSet()
  185. {
  186. return browserActions;
  187. } //}}}
  188. //{{{ checkDependencies() method
  189. /**
  190. * Returns true if all dependencies are satisified, false otherwise.
  191. * Also if dependencies are not satisfied, the plugin is marked as
  192. * "broken".
  193. */
  194. public boolean checkDependencies()
  195. {
  196. if(plugin == null)
  197. return true;
  198. int i = 0;
  199. boolean ok = true;
  200. boolean optional = false;
  201. String name = plugin.getClassName();
  202. String dep;
  203. while((dep = jEdit.getProperty("plugin." + name + ".depend." + i++)) != null)
  204. {
  205. if(dep.startsWith("optional "))
  206. {
  207. optional = true;
  208. dep = dep.substring("optional ".length());
  209. }
  210. int index = dep.indexOf(' ');
  211. if(index == -1)
  212. {
  213. Log.log(Log.ERROR,this,name + " has an invalid"
  214. + " dependency: " + dep);
  215. ok = false;
  216. continue;
  217. }
  218. String what = dep.substring(0,index);
  219. String arg = dep.substring(index + 1);
  220. if(what.equals("jdk"))
  221. {
  222. if(!optional && MiscUtilities.compareStrings(
  223. System.getProperty("java.version"),
  224. arg,false) < 0)
  225. {
  226. String[] args = { arg,
  227. System.getProperty("java.version") };
  228. jEdit.pluginError(path,"plugin-error.dep-jdk",args);
  229. ok = false;
  230. }
  231. }
  232. else if(what.equals("jedit"))
  233. {
  234. if(arg.length() != 11)
  235. {
  236. Log.log(Log.ERROR,this,"Invalid jEdit version"
  237. + " number: " + arg);
  238. ok = false;
  239. }
  240. if(!optional && MiscUtilities.compareStrings(
  241. jEdit.getBuild(),arg,false) < 0)
  242. {
  243. String needs = MiscUtilities.buildToVersion(arg);
  244. String[] args = { needs,
  245. jEdit.getVersion() };
  246. jEdit.pluginError(path,
  247. "plugin-error.dep-jedit",args);
  248. ok = false;
  249. }
  250. }
  251. else if(what.equals("plugin"))
  252. {
  253. int index2 = arg.indexOf(' ');
  254. if(index2 == -1)
  255. {
  256. Log.log(Log.ERROR,this,name
  257. + " has an invalid dependency: "
  258. + dep + " (version is missing)");
  259. ok = false;
  260. continue;
  261. }
  262. String pluginName = arg.substring(0,index2);
  263. String needVersion = arg.substring(index2 + 1);
  264. String currVersion = jEdit.getProperty("plugin."
  265. + pluginName + ".version");
  266. EditPlugin plugin = jEdit.getPlugin(pluginName);
  267. if(plugin == null)
  268. {
  269. if(!optional)
  270. {
  271. String[] args = { needVersion,
  272. pluginName };
  273. jEdit.pluginError(path,
  274. "plugin-error.dep-plugin.no-version",
  275. args);
  276. ok = false;
  277. }
  278. }
  279. else if(MiscUtilities.compareStrings(
  280. currVersion,needVersion,false) < 0)
  281. {
  282. if(!optional)
  283. {
  284. String[] args = { needVersion,
  285. pluginName, currVersion };
  286. jEdit.pluginError(path,
  287. "plugin-error.dep-plugin",args);
  288. ok = false;
  289. }
  290. }
  291. else if(plugin instanceof EditPlugin.Broken)
  292. {
  293. if(!optional)
  294. {
  295. String[] args = { pluginName };
  296. jEdit.pluginError(path,
  297. "plugin-error.dep-plugin.broken",args);
  298. ok = false;
  299. }
  300. }
  301. else
  302. {
  303. PluginJAR jar = plugin.getPluginJAR();
  304. jar.theseRequireMe.add(path);
  305. weRequireThese.add(jar.getPath());
  306. }
  307. }
  308. else if(what.equals("class"))
  309. {
  310. if(!optional)
  311. {
  312. try
  313. {
  314. classLoader.loadClass(arg,false);
  315. }
  316. catch(Exception e)
  317. {
  318. String[] args = { arg };
  319. jEdit.pluginError(path,
  320. "plugin-error.dep-class",args);
  321. ok = false;
  322. }
  323. }
  324. }
  325. else
  326. {
  327. Log.log(Log.ERROR,this,name + " has unknown"
  328. + " dependency: " + dep);
  329. ok = false;
  330. }
  331. }
  332. // each JAR file listed in the plugin's jars property
  333. // needs to know that we need them
  334. String jars = jEdit.getProperty("plugin."
  335. + plugin.getClassName() + ".jars");
  336. if(jars != null)
  337. {
  338. String dir = MiscUtilities.getParentOfPath(path);
  339. StringTokenizer st = new StringTokenizer(jars);
  340. while(st.hasMoreTokens())
  341. {
  342. String jarPath = MiscUtilities.constructPath(
  343. dir,st.nextToken());
  344. PluginJAR jar = jEdit.getPluginJAR(jarPath);
  345. if(jar == null)
  346. {
  347. String[] args = { jarPath };
  348. jEdit.pluginError(path,
  349. "plugin-error.missing-jar",args);
  350. ok = false;
  351. }
  352. else
  353. {
  354. weRequireThese.add(jarPath);
  355. jar.theseRequireMe.add(path);
  356. }
  357. }
  358. }
  359. if(!ok)
  360. breakPlugin();
  361. return ok;
  362. } //}}}
  363. //{{{ getDependentPlugins() method
  364. /**
  365. * Returns an array of all plugins that depend on this one.
  366. * @since jEdit 4.2pre2
  367. */
  368. public String[] getDependentPlugins()
  369. {
  370. return (String[])theseRequireMe.toArray(
  371. new String[theseRequireMe.size()]);
  372. } //}}}
  373. //{{{ getPlugin() method
  374. /**
  375. * Returns the plugin core class for this JAR file. Note that if the
  376. * plugin has not been activated, this will return an instance of
  377. * {@link EditPlugin.Deferred}. If you need the actual plugin core
  378. * class instance, call {@link #activatePlugin()} first.
  379. *
  380. * @since jEdit 4.2pre1
  381. */
  382. public EditPlugin getPlugin()
  383. {
  384. return plugin;
  385. } //}}}
  386. //{{{ activatePlugin() method
  387. /**
  388. * Loads the plugin core class. Does nothing if the plugin core class
  389. * has already been loaded. This method might be called on startup,
  390. * depending on what properties are set. See {@link EditPlugin#start()}.
  391. * This method is thread-safe.
  392. *
  393. * @since jEdit 4.2pre1
  394. */
  395. public void activatePlugin()
  396. {
  397. synchronized(this)
  398. {
  399. if(activated)
  400. {
  401. // recursive call
  402. return;
  403. }
  404. activated = true;
  405. }
  406. if(!(plugin instanceof EditPlugin.Deferred))
  407. return;
  408. String className = plugin.getClassName();
  409. try
  410. {
  411. Class clazz = classLoader.loadClass(className,false);
  412. int modifiers = clazz.getModifiers();
  413. if(Modifier.isInterface(modifiers)
  414. || Modifier.isAbstract(modifiers)
  415. || !EditPlugin.class.isAssignableFrom(clazz))
  416. {
  417. Log.log(Log.ERROR,this,"Plugin has properties but does not extend EditPlugin: "
  418. + className);
  419. breakPlugin();
  420. return;
  421. }
  422. plugin = (EditPlugin)clazz.newInstance();
  423. plugin.jar = (EditPlugin.JAR)this;
  424. }
  425. catch(Throwable t)
  426. {
  427. breakPlugin();
  428. Log.log(Log.ERROR,this,"Error while starting plugin " + className);
  429. Log.log(Log.ERROR,this,t);
  430. String[] args = { t.toString() };
  431. jEdit.pluginError(path,"plugin-error.start-error",args);
  432. return;
  433. }
  434. if(jEdit.isMainThread()
  435. || SwingUtilities.isEventDispatchThread())
  436. {
  437. startPlugin();
  438. }
  439. else
  440. {
  441. // for thread safety
  442. startPluginLater();
  443. }
  444. EditBus.send(new PluginUpdate(this,PluginUpdate.ACTIVATED,false));
  445. } //}}}
  446. //{{{ activateIfNecessary() method
  447. /**
  448. * Should be called after a new plugin is installed.
  449. * @since jEdit 4.2pre2
  450. */
  451. public void activatePluginIfNecessary()
  452. {
  453. if(!(plugin instanceof EditPlugin.Deferred && plugin != null))
  454. return;
  455. String className = plugin.getClassName();
  456. // default for plugins that don't specify this property (ie,
  457. // 4.1-style plugins) is to load them on startup
  458. String activate = jEdit.getProperty("plugin."
  459. + className + ".activate");
  460. if(activate == null)
  461. {
  462. // 4.1 plugin
  463. if(!jEdit.isMainThread())
  464. {
  465. breakPlugin();
  466. jEdit.pluginError(path,"plugin-error.not-42",null);
  467. }
  468. else
  469. activatePlugin();
  470. }
  471. else
  472. {
  473. // 4.2 plugin
  474. // if at least one property listed here is true,
  475. // load the plugin
  476. boolean load = false;
  477. StringTokenizer st = new StringTokenizer(activate);
  478. while(st.hasMoreTokens())
  479. {
  480. String prop = st.nextToken();
  481. boolean value = jEdit.getBooleanProperty(prop);
  482. if(value)
  483. {
  484. Log.log(Log.DEBUG,this,"Activating "
  485. + className + " because of " + prop);
  486. load = true;
  487. break;
  488. }
  489. }
  490. if(load)
  491. activatePlugin();
  492. }
  493. } //}}}
  494. //{{{ deactivatePlugin() method
  495. /**
  496. * Unloads the plugin core class. Does nothing if the plugin core class
  497. * has not been loaded.
  498. * This method can only be called from the AWT event dispatch thread!
  499. * @see EditPlugin#stop()
  500. *
  501. * @since jEdit 4.2pre3
  502. */
  503. public void deactivatePlugin(boolean exit)
  504. {
  505. if(!activated)
  506. return;
  507. if(!exit)
  508. {
  509. // buffers retain a reference to the fold handler in
  510. // question... and the easiest way to handle fold
  511. // handler unloading is this...
  512. Buffer buffer = jEdit.getFirstBuffer();
  513. while(buffer != null)
  514. {
  515. if(buffer.getFoldHandler() != null
  516. && buffer.getFoldHandler().getClass()
  517. .getClassLoader() == classLoader)
  518. {
  519. buffer.setFoldHandler(
  520. new DummyFoldHandler());
  521. }
  522. buffer = buffer.getNext();
  523. }
  524. }
  525. if(plugin != null && !(plugin instanceof EditPlugin.Broken))
  526. {
  527. if(plugin instanceof EBPlugin)
  528. EditBus.removeFromBus((EBPlugin)plugin);
  529. try
  530. {
  531. plugin.stop();
  532. }
  533. catch(Throwable t)
  534. {
  535. Log.log(Log.ERROR,this,"Error while "
  536. + "stopping plugin:");
  537. Log.log(Log.ERROR,this,t);
  538. }
  539. plugin = new EditPlugin.Deferred(
  540. plugin.getClassName());
  541. plugin.jar = (EditPlugin.JAR)this;
  542. EditBus.send(new PluginUpdate(this,
  543. PluginUpdate.DEACTIVATED,exit));
  544. if(!exit)
  545. {
  546. // see if this is a 4.1-style plugin
  547. String activate = jEdit.getProperty("plugin."
  548. + plugin.getClassName() + ".activate");
  549. if(activate == null)
  550. {
  551. breakPlugin();
  552. jEdit.pluginError(path,"plugin-error.not-42",null);
  553. }
  554. }
  555. }
  556. activated = false;
  557. } //}}}
  558. //{{{ getDockablesURI() method
  559. /**
  560. * Returns the location of the plugin's
  561. * <code>dockables.xml</code> file.
  562. * @since jEdit 4.2pre1
  563. */
  564. public URL getDockablesURI()
  565. {
  566. return dockablesURI;
  567. } //}}}
  568. //{{{ getServicesURI() method
  569. /**
  570. * Returns the location of the plugin's
  571. * <code>services.xml</code> file.
  572. * @since jEdit 4.2pre1
  573. */
  574. public URL getServicesURI()
  575. {
  576. return servicesURI;
  577. } //}}}
  578. //{{{ toString() method
  579. public String toString()
  580. {
  581. if(plugin == null)
  582. return path;
  583. else
  584. return path + ",class=" + plugin.getClassName();
  585. } //}}}
  586. //{{{ Package-private members
  587. //{{{ Static methods
  588. //{{{ getPluginCache() method
  589. static PluginCacheEntry getPluginCache(PluginJAR plugin)
  590. {
  591. String jarCachePath = plugin.getCachePath();
  592. if(jarCachePath == null)
  593. return null;
  594. DataInputStream din = null;
  595. try
  596. {
  597. PluginCacheEntry cache = new PluginCacheEntry();
  598. cache.plugin = plugin;
  599. cache.modTime = plugin.getFile().lastModified();
  600. din = new DataInputStream(
  601. new BufferedInputStream(
  602. new FileInputStream(jarCachePath)));
  603. if(cache.read(din))
  604. return cache;
  605. else
  606. {
  607. // returns false with outdated cache
  608. return null;
  609. }
  610. }
  611. catch(FileNotFoundException fnf)
  612. {
  613. return null;
  614. }
  615. catch(IOException io)
  616. {
  617. Log.log(Log.ERROR,PluginJAR.class,io);
  618. return null;
  619. }
  620. finally
  621. {
  622. try
  623. {
  624. if(din != null)
  625. din.close();
  626. }
  627. catch(IOException io)
  628. {
  629. Log.log(Log.ERROR,PluginJAR.class,io);
  630. }
  631. }
  632. } //}}}
  633. //{{{ setPluginCache() method
  634. static void setPluginCache(PluginJAR plugin, PluginCacheEntry cache)
  635. {
  636. String jarCachePath = plugin.getCachePath();
  637. if(jarCachePath == null)
  638. return;
  639. Log.log(Log.DEBUG,PluginJAR.class,"Writing " + jarCachePath);
  640. DataOutputStream dout = null;
  641. try
  642. {
  643. dout = new DataOutputStream(
  644. new BufferedOutputStream(
  645. new FileOutputStream(jarCachePath)));
  646. cache.write(dout);
  647. dout.close();
  648. }
  649. catch(IOException io)
  650. {
  651. Log.log(Log.ERROR,PluginJAR.class,io);
  652. try
  653. {
  654. if(dout != null)
  655. dout.close();
  656. }
  657. catch(IOException io2)
  658. {
  659. Log.log(Log.ERROR,PluginJAR.class,io2);
  660. }
  661. new File(jarCachePath).delete();
  662. }
  663. } //}}}
  664. //}}}
  665. //{{{ PluginJAR constructor
  666. PluginJAR(File file)
  667. {
  668. this.path = file.getPath();
  669. String jarCacheDir = jEdit.getJARCacheDirectory();
  670. if(jarCacheDir != null)
  671. {
  672. cachePath = MiscUtilities.constructPath(
  673. jarCacheDir,file.getName() + ".summary");
  674. }
  675. this.file = file;
  676. classLoader = new JARClassLoader(this);
  677. actions = new ActionSet();
  678. } //}}}
  679. //{{{ init() method
  680. void init()
  681. {
  682. boolean initialized = false;
  683. PluginCacheEntry cache = getPluginCache(this);
  684. if(cache != null)
  685. {
  686. loadCache(cache);
  687. classLoader.activate();
  688. initialized = true;
  689. }
  690. else
  691. {
  692. try
  693. {
  694. cache = generateCache();
  695. if(cache != null)
  696. {
  697. setPluginCache(this,cache);
  698. classLoader.activate();
  699. initialized = true;
  700. }
  701. }
  702. catch(IOException io)
  703. {
  704. Log.log(Log.ERROR,this,"Cannot load"
  705. + " plugin " + path);
  706. Log.log(Log.ERROR,this,io);
  707. String[] args = { io.toString() };
  708. jEdit.pluginError(path,"plugin-error.load-error",args);
  709. uninit(false);
  710. }
  711. }
  712. } //}}}
  713. //{{{ uninit() method
  714. void uninit(boolean exit)
  715. {
  716. deactivatePlugin(exit);
  717. if(!exit)
  718. {
  719. Iterator iter = weRequireThese.iterator();
  720. while(iter.hasNext())
  721. {
  722. String path = (String)iter.next();
  723. PluginJAR jar = jEdit.getPluginJAR(path);
  724. if(jar != null)
  725. jar.theseRequireMe.remove(this.path);
  726. }
  727. classLoader.deactivate();
  728. BeanShell.resetClassManager();
  729. if(actions != null)
  730. jEdit.getActionContext().removeActionSet(actions);
  731. if(browserActions != null)
  732. VFSBrowser.getActionContext().removeActionSet(browserActions);
  733. DockableWindowManager.unloadDockableWindows(this);
  734. ServiceManager.unloadServices(this);
  735. jEdit.removePluginProps(properties);
  736. try
  737. {
  738. if(zipFile != null)
  739. {
  740. zipFile.close();
  741. zipFile = null;
  742. }
  743. }
  744. catch(IOException io)
  745. {
  746. Log.log(Log.ERROR,this,io);
  747. }
  748. }
  749. } //}}}
  750. //{{{ getClasses() method
  751. String[] getClasses()
  752. {
  753. return classes;
  754. } //}}}
  755. //}}}
  756. //{{{ Private members
  757. //{{{ Instance variables
  758. private String path;
  759. private String cachePath;
  760. private File file;
  761. private JARClassLoader classLoader;
  762. private ZipFile zipFile;
  763. private Properties properties;
  764. private String[] classes;
  765. private ActionSet actions;
  766. private ActionSet browserActions;
  767. private EditPlugin plugin;
  768. private URL dockablesURI;
  769. private URL servicesURI;
  770. private boolean activated;
  771. private List theseRequireMe = new LinkedList();
  772. private List weRequireThese = new LinkedList();
  773. //}}}
  774. //{{{ actionsPresentButNotCoreClass() method
  775. private void actionsPresentButNotCoreClass()
  776. {
  777. Log.log(Log.WARNING,this,getPath() + " has an actions.xml but no plugin core class");
  778. actions.setLabel("MISSING PLUGIN CORE CLASS");
  779. } //}}}
  780. //{{{ loadCache() method
  781. private void loadCache(PluginCacheEntry cache)
  782. {
  783. classes = cache.classes;
  784. /* this should be before dockables are initialized */
  785. if(cache.cachedProperties != null)
  786. {
  787. properties = cache.cachedProperties;
  788. jEdit.addPluginProps(cache.cachedProperties);
  789. }
  790. if(cache.actionsURI != null
  791. && cache.cachedActionNames != null)
  792. {
  793. actions = new ActionSet(this,
  794. cache.cachedActionNames,
  795. cache.cachedActionToggleFlags,
  796. cache.actionsURI);
  797. }
  798. if(cache.browserActionsURI != null
  799. && cache.cachedBrowserActionNames != null)
  800. {
  801. browserActions = new ActionSet(this,
  802. cache.cachedBrowserActionNames,
  803. cache.cachedBrowserActionToggleFlags,
  804. cache.browserActionsURI);
  805. VFSBrowser.getActionContext().addActionSet(browserActions);
  806. }
  807. if(cache.dockablesURI != null
  808. && cache.cachedDockableNames != null
  809. && cache.cachedDockableActionFlags != null)
  810. {
  811. dockablesURI = cache.dockablesURI;
  812. DockableWindowManager.cacheDockableWindows(this,
  813. cache.cachedDockableNames,
  814. cache.cachedDockableActionFlags);
  815. }
  816. if(actions.size() != 0)
  817. jEdit.addActionSet(actions);
  818. if(cache.servicesURI != null
  819. && cache.cachedServices != null)
  820. {
  821. servicesURI = cache.servicesURI;
  822. for(int i = 0; i < cache.cachedServices.length;
  823. i++)
  824. {
  825. ServiceManager.Descriptor d
  826. = cache.cachedServices[i];
  827. ServiceManager.registerService(d);
  828. }
  829. }
  830. if(cache.pluginClass != null)
  831. {
  832. // Check if a plugin with the same name
  833. // is already loaded
  834. if(jEdit.getPlugin(cache.pluginClass) != null)
  835. {
  836. jEdit.pluginError(path,
  837. "plugin-error.already-loaded",
  838. null);
  839. uninit(false);
  840. }
  841. else
  842. {
  843. String label = jEdit.getProperty(
  844. "plugin." + cache.pluginClass
  845. + ".name");
  846. actions.setLabel(jEdit.getProperty(
  847. "action-set.plugin",
  848. new String[] { label }));
  849. plugin = new EditPlugin.Deferred(
  850. cache.pluginClass);
  851. plugin.jar = (EditPlugin.JAR)this;
  852. }
  853. }
  854. else
  855. {
  856. if(actions.size() != 0)
  857. actionsPresentButNotCoreClass();
  858. }
  859. } //}}}
  860. //{{{ generateCache() method
  861. private PluginCacheEntry generateCache() throws IOException
  862. {
  863. properties = new Properties();
  864. LinkedList classes = new LinkedList();
  865. ZipFile zipFile = getZipFile();
  866. List plugins = new LinkedList();
  867. PluginCacheEntry cache = new PluginCacheEntry();
  868. cache.modTime = file.lastModified();
  869. cache.cachedProperties = new Properties();
  870. Enumeration entries = zipFile.entries();
  871. while(entries.hasMoreElements())
  872. {
  873. ZipEntry entry = (ZipEntry)
  874. entries.nextElement();
  875. String name = entry.getName();
  876. String lname = name.toLowerCase();
  877. if(lname.equals("actions.xml"))
  878. {
  879. cache.actionsURI = classLoader.getResource(name);
  880. }
  881. else if(lname.equals("browser.actions.xml"))
  882. {
  883. cache.browserActionsURI = classLoader.getResource(name);
  884. }
  885. else if(lname.equals("dockables.xml"))
  886. {
  887. dockablesURI = classLoader.getResource(name);
  888. cache.dockablesURI = dockablesURI;
  889. }
  890. else if(lname.equals("services.xml"))
  891. {
  892. servicesURI = classLoader.getResource(name);
  893. cache.servicesURI = servicesURI;
  894. }
  895. else if(lname.endsWith(".props"))
  896. {
  897. InputStream in = classLoader.getResourceAsStream(name);
  898. properties.load(in);
  899. in.close();
  900. }
  901. else if(name.endsWith(".class"))
  902. {
  903. String className = MiscUtilities
  904. .fileToClass(name);
  905. if(className.endsWith("Plugin"))
  906. {
  907. plugins.add(className);
  908. }
  909. classes.add(className);
  910. }
  911. }
  912. cache.cachedProperties = properties;
  913. jEdit.addPluginProps(properties);
  914. this.classes = cache.classes =
  915. (String[])classes.toArray(
  916. new String[classes.size()]);
  917. String label = null;
  918. Iterator iter = plugins.iterator();
  919. while(iter.hasNext())
  920. {
  921. String className = (String)iter.next();
  922. String _label = jEdit.getProperty("plugin."
  923. + className + ".name");
  924. String version = jEdit.getProperty("plugin."
  925. + className + ".version");
  926. if(_label == null || version == null)
  927. {
  928. Log.log(Log.WARNING,this,"Ignoring: "
  929. + className);
  930. }
  931. else
  932. {
  933. cache.pluginClass = className;
  934. // Check if a plugin with the same name
  935. // is already loaded
  936. if(jEdit.getPlugin(className) != null)
  937. {
  938. jEdit.pluginError(path,
  939. "plugin-error.already-loaded",
  940. null);
  941. return null;
  942. }
  943. else
  944. {
  945. plugin = new EditPlugin.Deferred(
  946. className);
  947. plugin.jar = (EditPlugin.JAR)this;
  948. label = _label;
  949. }
  950. break;
  951. }
  952. }
  953. if(cache.actionsURI != null)
  954. {
  955. actions = new ActionSet(this,null,null,
  956. cache.actionsURI);
  957. actions.load();
  958. cache.cachedActionNames =
  959. actions.getCacheableActionNames();
  960. cache.cachedActionToggleFlags = new boolean[
  961. cache.cachedActionNames.length];
  962. for(int i = 0; i < cache.cachedActionNames.length; i++)
  963. {
  964. cache.cachedActionToggleFlags[i]
  965. = jEdit.getBooleanProperty(
  966. cache.cachedActionNames[i]
  967. + ".toggle");
  968. }
  969. }
  970. if(cache.browserActionsURI != null)
  971. {
  972. browserActions = new ActionSet(this,null,null,
  973. cache.browserActionsURI);
  974. browserActions.load();
  975. VFSBrowser.getActionContext().addActionSet(browserActions);
  976. cache.cachedBrowserActionNames =
  977. browserActions.getCacheableActionNames();
  978. cache.cachedBrowserActionToggleFlags = new boolean[
  979. cache.cachedBrowserActionNames.length];
  980. for(int i = 0;
  981. i < cache.cachedBrowserActionNames.length;
  982. i++)
  983. {
  984. cache.cachedBrowserActionToggleFlags[i]
  985. = jEdit.getBooleanProperty(
  986. cache.cachedBrowserActionNames[i]
  987. + ".toggle");
  988. }
  989. }
  990. if(dockablesURI != null)
  991. {
  992. DockableWindowManager.loadDockableWindows(this,
  993. dockablesURI,cache);
  994. }
  995. if(actions.size() != 0)
  996. {
  997. if(label != null)
  998. {
  999. actions.setLabel(jEdit.getProperty(
  1000. "action-set.plugin",
  1001. new String[] { label }));
  1002. }
  1003. else
  1004. actionsPresentButNotCoreClass();
  1005. jEdit.addActionSet(actions);
  1006. }
  1007. if(servicesURI != null)
  1008. {
  1009. ServiceManager.loadServices(this,servicesURI,cache);
  1010. }
  1011. return cache;
  1012. } //}}}
  1013. //{{{ startPlugin() method
  1014. private void startPlugin()
  1015. {
  1016. try
  1017. {
  1018. plugin.start();
  1019. }
  1020. catch(Throwable t)
  1021. {
  1022. breakPlugin();
  1023. Log.log(Log.ERROR,PluginJAR.this,
  1024. "Error while starting plugin " + plugin.getClassName());
  1025. Log.log(Log.ERROR,PluginJAR.this,t);
  1026. String[] args = { t.toString() };
  1027. jEdit.pluginError(path,"plugin-error.start-error",args);
  1028. }
  1029. if(plugin instanceof EBPlugin)
  1030. {
  1031. if(jEdit.getProperty("plugin."
  1032. + plugin.getClassName() + ".activate")
  1033. == null)
  1034. {
  1035. // old plugins expected jEdit 4.1-style
  1036. // behavior, where a PropertiesChanged
  1037. // was sent after plugins were started
  1038. ((EBComponent)plugin).handleMessage(
  1039. new org.gjt.sp.jedit.msg.PropertiesChanged(null));
  1040. }
  1041. EditBus.addToBus((EBPlugin)plugin);
  1042. }
  1043. // buffers retain a reference to the fold handler in
  1044. // question... and the easiest way to handle fold
  1045. // handler loading is this...
  1046. Buffer buffer = jEdit.getFirstBuffer();
  1047. while(buffer != null)
  1048. {
  1049. FoldHandler handler =
  1050. FoldHandler.getFoldHandler(
  1051. buffer.getStringProperty("folding"));
  1052. // == null before loaded
  1053. if(buffer.getFoldHandler() != null
  1054. && handler != null
  1055. && handler != buffer.getFoldHandler())
  1056. {
  1057. buffer.setFoldHandler(handler);
  1058. }
  1059. buffer = buffer.getNext();
  1060. }
  1061. } //}}}
  1062. //{{{ startPluginLater() method
  1063. private void startPluginLater()
  1064. {
  1065. SwingUtilities.invokeLater(new Runnable()
  1066. {
  1067. public void run()
  1068. {
  1069. if(!activated)
  1070. return;
  1071. startPlugin();
  1072. }
  1073. });
  1074. } //}}}
  1075. //{{{ breakPlugin() method
  1076. private void breakPlugin()
  1077. {
  1078. plugin = new EditPlugin.Broken(plugin.getClassName());
  1079. plugin.jar = (EditPlugin.JAR)this;
  1080. // remove action sets, dockables, etc so that user doesn't
  1081. // see the broken plugin
  1082. uninit(false);
  1083. // but we want properties to hang around
  1084. jEdit.addPluginProps(properties);
  1085. } //}}}
  1086. //}}}
  1087. //{{{ PluginCacheEntry class
  1088. /**
  1089. * Used by the <code>DockableWindowManager</code> and
  1090. * <code>ServiceManager</code> to handle caching.
  1091. * @since jEdit 4.2pre1
  1092. */
  1093. public static class PluginCacheEntry
  1094. {
  1095. public static final int MAGIC = 0xB7A2E420;
  1096. //{{{ Instance variables
  1097. public PluginJAR plugin;
  1098. public long modTime;
  1099. public String[] classes;
  1100. public URL actionsURI;
  1101. public String[] cachedActionNames;
  1102. public boolean[] cachedActionToggleFlags;
  1103. public URL browserActionsURI;
  1104. public String[] cachedBrowserActionNames;
  1105. public boolean[] cachedBrowserActionToggleFlags;
  1106. public URL dockablesURI;
  1107. public String[] cachedDockableNames;
  1108. public boolean[] cachedDockableActionFlags;
  1109. public URL servicesURI;
  1110. public ServiceManager.Descriptor[] cachedServices;
  1111. public Properties cachedProperties;
  1112. public String pluginClass;
  1113. //}}}
  1114. /* read() and write() must be kept perfectly in sync...
  1115. * its a very simple file format. doing it this way is
  1116. * faster than serializing since serialization calls
  1117. * reflection, etc. */
  1118. //{{{ read() method
  1119. public boolean read(DataInputStream din) throws IOException
  1120. {
  1121. int cacheMagic = din.readInt();
  1122. if(cacheMagic != MAGIC)
  1123. return false;
  1124. String cacheBuild = readString(din);
  1125. if(!cacheBuild.equals(jEdit.getBuild()))
  1126. return false;
  1127. long cacheModTime = din.readLong();
  1128. if(cacheModTime != modTime)
  1129. return false;
  1130. actionsURI = readURI(din);
  1131. cachedActionNames = readStringArray(din);
  1132. cachedActionToggleFlags = readBooleanArray(din);
  1133. browserActionsURI = readURI(din);
  1134. cachedBrowserActionNames = readStringArray(din);
  1135. cachedBrowserActionToggleFlags = readBooleanArray(din);
  1136. dockablesURI = readURI(din);
  1137. cachedDockableNames = readStringArray(din);
  1138. cachedDockableActionFlags = readBooleanArray(din);
  1139. servicesURI = readURI(din);
  1140. int len = din.readInt();
  1141. if(len == 0)
  1142. cachedServices = null;
  1143. else
  1144. {
  1145. cachedServices = new ServiceManager.Descriptor[len];
  1146. for(int i = 0; i < len; i++)
  1147. {
  1148. ServiceManager.Descriptor d = new
  1149. ServiceManager.Descriptor(
  1150. readString(din),
  1151. readString(din),
  1152. null,
  1153. plugin);
  1154. cachedServices[i] = d;
  1155. }
  1156. }
  1157. classes = readStringArray(din);
  1158. cachedProperties = readMap(din);
  1159. pluginClass = readString(din);
  1160. return true;
  1161. } //}}}
  1162. //{{{ write() method
  1163. public void write(DataOutputStream dout) throws IOException
  1164. {
  1165. dout.writeInt(MAGIC);
  1166. writeString(dout,jEdit.getBuild());
  1167. dout.writeLong(modTime);
  1168. writeString(dout,actionsURI);
  1169. writeStringArray(dout,cachedActionNames);
  1170. writeBooleanArray(dout,cachedActionToggleFlags);
  1171. writeString(dout,browserActionsURI);
  1172. writeStringArray(dout,cachedBrowserActionNames);
  1173. writeBooleanArray(dout,cachedBrowserActionToggleFlags);
  1174. writeString(dout,dockablesURI);
  1175. writeStringArray(dout,cachedDockableNames);
  1176. writeBooleanArray(dout,cachedDockableActionFlags);
  1177. writeString(dout,servicesURI);
  1178. if(cachedServices == null)
  1179. dout.writeInt(0);
  1180. else
  1181. {
  1182. dout.writeInt(cachedServices.length);
  1183. for(int i = 0; i < cachedServices.length; i++)
  1184. {
  1185. writeString(dout,cachedServices[i].clazz);
  1186. writeString(dout,cachedServices[i].name);
  1187. }
  1188. }
  1189. writeStringArray(dout,classes);
  1190. writeMap(dout,cachedProperties);
  1191. writeString(dout,pluginClass);
  1192. } //}}}
  1193. //{{{ Private members
  1194. //{{{ readString() method
  1195. private String readString(DataInputStream din)
  1196. throws IOException
  1197. {
  1198. int len = din.readInt();
  1199. if(len == 0)
  1200. return null;
  1201. char[] str = new char[len];
  1202. for(int i = 0; i < len; i++)
  1203. str[i] = din.readChar();
  1204. return new String(str);
  1205. } //}}}
  1206. //{{{ readURI() method
  1207. private URL readURI(DataInputStream din)
  1208. throws IOException
  1209. {
  1210. String str = readString(din);
  1211. if(str == null)
  1212. return null;
  1213. else
  1214. return new URL(str);
  1215. } //}}}
  1216. //{{{ readStringArray() method
  1217. private String[] readStringArray(DataInputStream din)
  1218. throws IOException
  1219. {
  1220. int len = din.readInt();
  1221. if(len == 0)
  1222. return null;
  1223. String[] str = new String[len];
  1224. for(int i = 0; i < len; i++)
  1225. {
  1226. str[i] = readString(din);
  1227. }
  1228. return str;
  1229. } //}}}
  1230. //{{{ readBooleanArray() method
  1231. private boolean[] readBooleanArray(DataInputStream din)
  1232. throws IOException
  1233. {
  1234. int len = din.readInt();
  1235. if(len == 0)
  1236. return null;
  1237. boolean[] bools = new boolean[len];
  1238. for(int i = 0; i < len; i++)
  1239. {
  1240. bools[i] = din.readBoolean();
  1241. }
  1242. return bools;
  1243. } //}}}
  1244. //{{{ readMap() method
  1245. private Properties readMap(DataInputStream din)
  1246. throws IOException
  1247. {
  1248. Properties returnValue = new Properties();
  1249. int count = din.readInt();
  1250. for(int i = 0; i < count; i++)
  1251. {
  1252. String key = readString(din);
  1253. String value = readString(din);
  1254. if(value == null)
  1255. value = "";
  1256. returnValue.put(key,value);
  1257. }
  1258. return returnValue;
  1259. } //}}}
  1260. //{{{ writeString() method
  1261. private void writeString(DataOutputStream dout,
  1262. Object obj) throws IOException
  1263. {
  1264. if(obj == null)
  1265. {
  1266. dout.writeInt(0);
  1267. }
  1268. else
  1269. {
  1270. String str = obj.toString();
  1271. dout.writeInt(str.length());
  1272. dout.writeChars(str);
  1273. }
  1274. } //}}}
  1275. //{{{ writeStringArray() method
  1276. private void writeStringArray(DataOutputStream dout,
  1277. String[] str) throws IOException
  1278. {
  1279. if(str == null)
  1280. {
  1281. dout.writeInt(0);
  1282. }
  1283. else
  1284. {
  1285. dout.writeInt(str.length);
  1286. for(int i = 0; i < str.length; i++)
  1287. {
  1288. writeString(dout,str[i]);
  1289. }
  1290. }
  1291. } //}}}
  1292. //{{{ writeBooleanArray() method
  1293. private void writeBooleanArray(DataOutputStream dout,
  1294. boolean[] bools) throws IOException
  1295. {
  1296. if(bools == null)
  1297. {
  1298. dout.writeInt(0);
  1299. }
  1300. else
  1301. {
  1302. dout.writeInt(bools.length);
  1303. for(int i = 0; i < bools.length; i++)
  1304. {
  1305. dout.writeBoolean(bools[i]);
  1306. }
  1307. }
  1308. } //}}}
  1309. //{{{ writeMap() method
  1310. private void writeMap(DataOutputStream dout, Map map)
  1311. throws IOException
  1312. {
  1313. dout.writeInt(map.size());
  1314. Iterator iter = map.keySet().iterator();
  1315. while(iter.hasNext())
  1316. {
  1317. String key = (String)iter.next();
  1318. writeString(dout,key);
  1319. writeString(dout,map.get(key));
  1320. }
  1321. } //}}}
  1322. //}}}
  1323. } //}}}
  1324. }