PageRenderTime 102ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

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

#
Java | 1372 lines | 962 code | 150 blank | 260 comment | 179 complexity | b6615d84aa89d0eeaa1c54972e79d7a4 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, 2003 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 4838 2003-07-25 20:13:09Z 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 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. String name = plugin.getClassName();
  201. String dep;
  202. while((dep = jEdit.getProperty("plugin." + name + ".depend." + i++)) != null)
  203. {
  204. int index = dep.indexOf(' ');
  205. if(index == -1)
  206. {
  207. Log.log(Log.ERROR,this,name + " has an invalid"
  208. + " dependency: " + dep);
  209. ok = false;
  210. continue;
  211. }
  212. String what = dep.substring(0,index);
  213. String arg = dep.substring(index + 1);
  214. if(what.equals("jdk"))
  215. {
  216. if(MiscUtilities.compareStrings(
  217. System.getProperty("java.version"),
  218. arg,false) < 0)
  219. {
  220. String[] args = { arg,
  221. System.getProperty("java.version") };
  222. jEdit.pluginError(path,"plugin-error.dep-jdk",args);
  223. ok = false;
  224. }
  225. }
  226. else if(what.equals("jedit"))
  227. {
  228. if(arg.length() != 11)
  229. {
  230. Log.log(Log.ERROR,this,"Invalid jEdit version"
  231. + " number: " + arg);
  232. ok = false;
  233. }
  234. if(MiscUtilities.compareStrings(
  235. jEdit.getBuild(),arg,false) < 0)
  236. {
  237. String needs = MiscUtilities.buildToVersion(arg);
  238. String[] args = { needs,
  239. jEdit.getVersion() };
  240. jEdit.pluginError(path,
  241. "plugin-error.dep-jedit",args);
  242. ok = false;
  243. }
  244. }
  245. else if(what.equals("plugin"))
  246. {
  247. int index2 = arg.indexOf(' ');
  248. if(index2 == -1)
  249. {
  250. Log.log(Log.ERROR,this,name
  251. + " has an invalid dependency: "
  252. + dep + " (version is missing)");
  253. ok = false;
  254. continue;
  255. }
  256. String pluginName = arg.substring(0,index2);
  257. String needVersion = arg.substring(index2 + 1);
  258. String currVersion = jEdit.getProperty("plugin."
  259. + pluginName + ".version");
  260. EditPlugin plugin = jEdit.getPlugin(pluginName);
  261. if(plugin == null)
  262. {
  263. String[] args = { needVersion,
  264. pluginName };
  265. jEdit.pluginError(path,
  266. "plugin-error.dep-plugin.no-version",
  267. args);
  268. ok = false;
  269. }
  270. else if(MiscUtilities.compareStrings(
  271. currVersion,needVersion,false) < 0)
  272. {
  273. String[] args = { needVersion,
  274. pluginName, currVersion };
  275. jEdit.pluginError(path,
  276. "plugin-error.dep-plugin",args);
  277. ok = false;
  278. }
  279. else if(plugin instanceof EditPlugin.Broken)
  280. {
  281. String[] args = { pluginName };
  282. jEdit.pluginError(path,
  283. "plugin-error.dep-plugin.broken",args);
  284. ok = false;
  285. }
  286. else
  287. {
  288. plugin.getPluginJAR().theseRequireMe.add(path);
  289. }
  290. }
  291. else if(what.equals("class"))
  292. {
  293. try
  294. {
  295. classLoader.loadClass(arg,false);
  296. }
  297. catch(Exception e)
  298. {
  299. String[] args = { arg };
  300. jEdit.pluginError(path,
  301. "plugin-error.dep-class",args);
  302. ok = false;
  303. }
  304. }
  305. else
  306. {
  307. Log.log(Log.ERROR,this,name + " has unknown"
  308. + " dependency: " + dep);
  309. ok = false;
  310. }
  311. }
  312. if(!ok)
  313. breakPlugin();
  314. return ok;
  315. } //}}}
  316. //{{{ getDependentPlugins() method
  317. /**
  318. * Returns an array of all plugins that depend on this one.
  319. * @since jEdit 4.2pre2
  320. */
  321. public String[] getDependentPlugins()
  322. {
  323. return (String[])theseRequireMe.toArray(
  324. new String[theseRequireMe.size()]);
  325. } //}}}
  326. //{{{ getPlugin() method
  327. /**
  328. * Returns the plugin core class for this JAR file. Note that if the
  329. * plugin has not been activated, this will return an instance of
  330. * {@link EditPlugin.Deferred}. If you need the actual plugin core
  331. * class instance, call {@link #activatePlugin()} first.
  332. *
  333. * @since jEdit 4.2pre1
  334. */
  335. public EditPlugin getPlugin()
  336. {
  337. return plugin;
  338. } //}}}
  339. //{{{ activatePlugin() method
  340. /**
  341. * Loads the plugin core class. Does nothing if the plugin core class
  342. * has already been loaded. This method might be called on startup,
  343. * depending on what properties are set. See {@link EditPlugin#start()}.
  344. * This method is thread-safe.
  345. *
  346. * @since jEdit 4.2pre1
  347. */
  348. public void activatePlugin()
  349. {
  350. synchronized(this)
  351. {
  352. if(activated)
  353. {
  354. // recursive call
  355. return;
  356. }
  357. activated = true;
  358. if(!(plugin instanceof EditPlugin.Deferred))
  359. return;
  360. String className = plugin.getClassName();
  361. try
  362. {
  363. Class clazz = classLoader.loadClass(className,false);
  364. int modifiers = clazz.getModifiers();
  365. if(Modifier.isInterface(modifiers)
  366. || Modifier.isAbstract(modifiers)
  367. || !EditPlugin.class.isAssignableFrom(clazz))
  368. {
  369. Log.log(Log.ERROR,this,"Plugin has properties but does not extend EditPlugin: "
  370. + className);
  371. breakPlugin();
  372. return;
  373. }
  374. plugin = (EditPlugin)clazz.newInstance();
  375. plugin.jar = (EditPlugin.JAR)this;
  376. }
  377. catch(Throwable t)
  378. {
  379. breakPlugin();
  380. Log.log(Log.ERROR,this,"Error while starting plugin " + className);
  381. Log.log(Log.ERROR,this,t);
  382. String[] args = { t.toString() };
  383. jEdit.pluginError(path,"plugin-error.start-error",args);
  384. return;
  385. }
  386. }
  387. if(jEdit.isMainThread()
  388. || SwingUtilities.isEventDispatchThread())
  389. {
  390. startPlugin();
  391. }
  392. else
  393. {
  394. // for thread safety
  395. startPluginLater();
  396. }
  397. EditBus.send(new PluginUpdate(this,PluginUpdate.ACTIVATED,false));
  398. } //}}}
  399. //{{{ activateIfNecessary() method
  400. /**
  401. * Should be called after a new plugin is installed.
  402. * @since jEdit 4.2pre2
  403. */
  404. public void activatePluginIfNecessary()
  405. {
  406. if(!(plugin instanceof EditPlugin.Deferred && plugin != null))
  407. return;
  408. String className = plugin.getClassName();
  409. // default for plugins that don't specify this property (ie,
  410. // 4.1-style plugins) is to load them on startup
  411. String activate = jEdit.getProperty("plugin."
  412. + className + ".activate");
  413. if(activate == null)
  414. {
  415. // 4.1 plugin
  416. if(!jEdit.isMainThread())
  417. {
  418. breakPlugin();
  419. jEdit.pluginError(path,"plugin-error.not-42",null);
  420. }
  421. else
  422. activatePlugin();
  423. }
  424. else
  425. {
  426. // 4.2 plugin
  427. // if at least one property listed here is true,
  428. // load the plugin
  429. boolean load = false;
  430. StringTokenizer st = new StringTokenizer(activate);
  431. while(st.hasMoreTokens())
  432. {
  433. String prop = st.nextToken();
  434. boolean value = jEdit.getBooleanProperty(prop);
  435. if(value)
  436. {
  437. Log.log(Log.DEBUG,this,"Activating "
  438. + className + " because of " + prop);
  439. load = true;
  440. break;
  441. }
  442. }
  443. if(load)
  444. activatePlugin();
  445. }
  446. } //}}}
  447. //{{{ deactivatePlugin() method
  448. /**
  449. * Unloads the plugin core class. Does nothing if the plugin core class
  450. * has not been loaded.
  451. * This method can only be called from the AWT event dispatch thread!
  452. * @see EditPlugin#stop()
  453. *
  454. * @since jEdit 4.2pre3
  455. */
  456. public void deactivatePlugin(boolean exit)
  457. {
  458. synchronized(this)
  459. {
  460. if(!activated)
  461. return;
  462. activated = false;
  463. if(!exit)
  464. {
  465. // buffers retain a reference to the fold handler in
  466. // question... and the easiest way to handle fold
  467. // handler unloading is this...
  468. Buffer buffer = jEdit.getFirstBuffer();
  469. while(buffer != null)
  470. {
  471. if(buffer.getFoldHandler() != null
  472. && buffer.getFoldHandler().getClass()
  473. .getClassLoader() == classLoader)
  474. {
  475. buffer.setFoldHandler(
  476. new DummyFoldHandler());
  477. }
  478. buffer = buffer.getNext();
  479. }
  480. }
  481. if(plugin != null && !(plugin instanceof EditPlugin.Broken))
  482. {
  483. if(plugin instanceof EBPlugin)
  484. EditBus.removeFromBus((EBPlugin)plugin);
  485. try
  486. {
  487. plugin.stop();
  488. }
  489. catch(Throwable t)
  490. {
  491. Log.log(Log.ERROR,this,"Error while "
  492. + "stopping plugin:");
  493. Log.log(Log.ERROR,this,t);
  494. }
  495. plugin = new EditPlugin.Deferred(
  496. plugin.getClassName());
  497. plugin.jar = (EditPlugin.JAR)this;
  498. EditBus.send(new PluginUpdate(this,
  499. PluginUpdate.DEACTIVATED,exit));
  500. if(!exit)
  501. {
  502. // see if this is a 4.1-style plugin
  503. String activate = jEdit.getProperty("plugin."
  504. + plugin.getClassName() + ".activate");
  505. if(activate == null)
  506. {
  507. breakPlugin();
  508. jEdit.pluginError(path,"plugin-error.not-42",null);
  509. }
  510. }
  511. }
  512. }
  513. } //}}}
  514. //{{{ getDockablesURI() method
  515. /**
  516. * Returns the location of the plugin's
  517. * <code>dockables.xml</code> file.
  518. * @since jEdit 4.2pre1
  519. */
  520. public URL getDockablesURI()
  521. {
  522. return dockablesURI;
  523. } //}}}
  524. //{{{ getServicesURI() method
  525. /**
  526. * Returns the location of the plugin's
  527. * <code>services.xml</code> file.
  528. * @since jEdit 4.2pre1
  529. */
  530. public URL getServicesURI()
  531. {
  532. return servicesURI;
  533. } //}}}
  534. //{{{ toString() method
  535. public String toString()
  536. {
  537. if(plugin == null)
  538. return path;
  539. else
  540. return path + ",class=" + plugin.getClassName();
  541. } //}}}
  542. //{{{ Package-private members
  543. //{{{ Static methods
  544. //{{{ getPluginCache() method
  545. static PluginCacheEntry getPluginCache(PluginJAR plugin)
  546. {
  547. String jarCachePath = plugin.getCachePath();
  548. if(jarCachePath == null)
  549. return null;
  550. DataInputStream din = null;
  551. try
  552. {
  553. PluginCacheEntry cache = new PluginCacheEntry();
  554. cache.plugin = plugin;
  555. cache.modTime = plugin.getFile().lastModified();
  556. din = new DataInputStream(
  557. new BufferedInputStream(
  558. new FileInputStream(jarCachePath)));
  559. if(cache.read(din))
  560. return cache;
  561. else
  562. {
  563. // returns false with outdated cache
  564. return null;
  565. }
  566. }
  567. catch(FileNotFoundException fnf)
  568. {
  569. return null;
  570. }
  571. catch(IOException io)
  572. {
  573. Log.log(Log.ERROR,PluginJAR.class,io);
  574. return null;
  575. }
  576. finally
  577. {
  578. try
  579. {
  580. if(din != null)
  581. din.close();
  582. }
  583. catch(IOException io)
  584. {
  585. Log.log(Log.ERROR,PluginJAR.class,io);
  586. }
  587. }
  588. } //}}}
  589. //{{{ setPluginCache() method
  590. static void setPluginCache(PluginJAR plugin, PluginCacheEntry cache)
  591. {
  592. String jarCachePath = plugin.getCachePath();
  593. if(jarCachePath == null)
  594. return;
  595. Log.log(Log.DEBUG,PluginJAR.class,"Writing " + jarCachePath);
  596. DataOutputStream dout = null;
  597. try
  598. {
  599. dout = new DataOutputStream(
  600. new BufferedOutputStream(
  601. new FileOutputStream(jarCachePath)));
  602. cache.write(dout);
  603. dout.close();
  604. }
  605. catch(IOException io)
  606. {
  607. Log.log(Log.ERROR,PluginJAR.class,io);
  608. try
  609. {
  610. dout.close();
  611. }
  612. catch(IOException io2)
  613. {
  614. Log.log(Log.ERROR,PluginJAR.class,io2);
  615. }
  616. new File(jarCachePath).delete();
  617. }
  618. } //}}}
  619. //}}}
  620. //{{{ PluginJAR constructor
  621. PluginJAR(File file)
  622. {
  623. this.path = file.getPath();
  624. String jarCacheDir = jEdit.getJARCacheDirectory();
  625. if(jarCacheDir != null)
  626. {
  627. cachePath = MiscUtilities.constructPath(
  628. jarCacheDir,file.getName() + ".summary");
  629. }
  630. this.file = file;
  631. classLoader = new JARClassLoader(this);
  632. actions = new ActionSet();
  633. } //}}}
  634. //{{{ init() method
  635. void init()
  636. {
  637. PluginCacheEntry cache = getPluginCache(this);
  638. if(cache != null)
  639. loadCache(cache);
  640. else
  641. {
  642. try
  643. {
  644. cache = generateCache();
  645. setPluginCache(this,cache);
  646. }
  647. catch(IOException io)
  648. {
  649. Log.log(Log.ERROR,this,"Cannot load"
  650. + " plugin " + plugin);
  651. Log.log(Log.ERROR,this,io);
  652. String[] args = { io.toString() };
  653. jEdit.pluginError(path,"plugin-error.load-error",args);
  654. }
  655. }
  656. classLoader.activate();
  657. } //}}}
  658. //{{{ uninit() method
  659. void uninit(boolean exit)
  660. {
  661. deactivatePlugin(exit);
  662. if(!exit)
  663. {
  664. classLoader.deactivate();
  665. BeanShell.resetClassManager();
  666. if(actions != null)
  667. jEdit.getActionContext().removeActionSet(actions);
  668. if(browserActions != null)
  669. VFSBrowser.getActionContext().removeActionSet(browserActions);
  670. DockableWindowManager.unloadDockableWindows(this);
  671. ServiceManager.unloadServices(this);
  672. try
  673. {
  674. if(zipFile != null)
  675. {
  676. zipFile.close();
  677. zipFile = null;
  678. }
  679. }
  680. catch(IOException io)
  681. {
  682. Log.log(Log.ERROR,this,io);
  683. }
  684. }
  685. } //}}}
  686. //{{{ getClasses() method
  687. String[] getClasses()
  688. {
  689. return classes;
  690. } //}}}
  691. //}}}
  692. //{{{ Private members
  693. //{{{ Instance variables
  694. private String path;
  695. private String cachePath;
  696. private File file;
  697. private JARClassLoader classLoader;
  698. private ZipFile zipFile;
  699. private String[] classes;
  700. private ActionSet actions;
  701. private ActionSet browserActions;
  702. private EditPlugin plugin;
  703. private URL dockablesURI;
  704. private URL servicesURI;
  705. private boolean activated;
  706. private List theseRequireMe = new LinkedList();
  707. //}}}
  708. //{{{ loadCache() method
  709. private void loadCache(PluginCacheEntry cache)
  710. {
  711. classes = cache.classes;
  712. if(cache.actionsURI != null
  713. && cache.cachedActionNames != null)
  714. {
  715. actions = new ActionSet(this,
  716. cache.cachedActionNames,
  717. cache.cachedActionToggleFlags,
  718. cache.actionsURI);
  719. }
  720. if(cache.browserActionsURI != null
  721. && cache.cachedBrowserActionNames != null)
  722. {
  723. browserActions = new ActionSet(this,
  724. cache.cachedBrowserActionNames,
  725. cache.cachedBrowserActionToggleFlags,
  726. cache.browserActionsURI);
  727. VFSBrowser.getActionContext().addActionSet(browserActions);
  728. }
  729. if(cache.dockablesURI != null
  730. && cache.cachedDockableNames != null
  731. && cache.cachedDockableActionFlags != null)
  732. {
  733. dockablesURI = cache.dockablesURI;
  734. DockableWindowManager.cacheDockableWindows(this,
  735. cache.cachedDockableNames,
  736. cache.cachedDockableActionFlags);
  737. }
  738. if(actions.size() != 0)
  739. jEdit.addActionSet(actions);
  740. if(cache.servicesURI != null
  741. && cache.cachedServices != null)
  742. {
  743. servicesURI = cache.servicesURI;
  744. for(int i = 0; i < cache.cachedServices.length;
  745. i++)
  746. {
  747. ServiceManager.Descriptor d
  748. = cache.cachedServices[i];
  749. ServiceManager.registerService(d);
  750. }
  751. }
  752. if(cache.cachedProperties != null)
  753. jEdit.addProperties(cache.cachedProperties);
  754. if(cache.pluginClass != null)
  755. {
  756. if(actions != null)
  757. {
  758. String label = jEdit.getProperty("plugin."
  759. + cache.pluginClass + ".name");
  760. actions.setLabel(jEdit.getProperty(
  761. "action-set.plugin",
  762. new String[] { label }));
  763. }
  764. plugin = new EditPlugin.Deferred(cache.pluginClass);
  765. plugin.jar = (EditPlugin.JAR)this;
  766. }
  767. } //}}}
  768. //{{{ generateCache() method
  769. private PluginCacheEntry generateCache() throws IOException
  770. {
  771. Properties properties = new Properties();
  772. LinkedList classes = new LinkedList();
  773. ZipFile zipFile = getZipFile();
  774. List plugins = new LinkedList();
  775. PluginCacheEntry cache = new PluginCacheEntry();
  776. cache.modTime = file.lastModified();
  777. cache.cachedProperties = new HashMap();
  778. Enumeration entries = zipFile.entries();
  779. while(entries.hasMoreElements())
  780. {
  781. ZipEntry entry = (ZipEntry)
  782. entries.nextElement();
  783. String name = entry.getName();
  784. String lname = name.toLowerCase();
  785. if(lname.equals("actions.xml"))
  786. {
  787. cache.actionsURI = classLoader.getResource(name);
  788. }
  789. else if(lname.equals("browser.actions.xml"))
  790. {
  791. cache.browserActionsURI = classLoader.getResource(name);
  792. }
  793. else if(lname.equals("dockables.xml"))
  794. {
  795. dockablesURI = classLoader.getResource(name);
  796. cache.dockablesURI = dockablesURI;
  797. }
  798. else if(lname.equals("services.xml"))
  799. {
  800. servicesURI = classLoader.getResource(name);
  801. cache.servicesURI = servicesURI;
  802. }
  803. else if(lname.endsWith(".props"))
  804. {
  805. InputStream in = classLoader.getResourceAsStream(name);
  806. properties.load(in);
  807. in.close();
  808. }
  809. else if(name.endsWith(".class"))
  810. {
  811. String className = MiscUtilities
  812. .fileToClass(name);
  813. if(className.endsWith("Plugin"))
  814. {
  815. // Check if a plugin with the same name
  816. // is already loaded
  817. if(jEdit.getPlugin(className) != null)
  818. {
  819. jEdit.pluginError(path,
  820. "plugin-error.already-loaded",
  821. null);
  822. }
  823. else
  824. {
  825. plugins.add(className);
  826. }
  827. }
  828. classes.add(className);
  829. }
  830. }
  831. cache.cachedProperties = properties;
  832. jEdit.addProperties(properties);
  833. this.classes = cache.classes =
  834. (String[])classes.toArray(
  835. new String[classes.size()]);
  836. String label = null;
  837. Iterator iter = plugins.iterator();
  838. while(iter.hasNext())
  839. {
  840. String className = (String)iter.next();
  841. String _label = jEdit.getProperty("plugin."
  842. + className + ".name");
  843. String version = jEdit.getProperty("plugin."
  844. + className + ".version");
  845. if(_label == null || version == null)
  846. {
  847. Log.log(Log.NOTICE,this,"Ignoring: " + className);
  848. }
  849. else
  850. {
  851. plugin = new EditPlugin.Deferred(className);
  852. plugin.jar = (EditPlugin.JAR)this;
  853. cache.pluginClass = className;
  854. label = _label;
  855. break;
  856. }
  857. }
  858. if(cache.actionsURI != null)
  859. {
  860. actions = new ActionSet(this,null,null,
  861. cache.actionsURI);
  862. actions.load();
  863. cache.cachedActionNames =
  864. actions.getCacheableActionNames();
  865. cache.cachedActionToggleFlags = new boolean[
  866. cache.cachedActionNames.length];
  867. for(int i = 0; i < cache.cachedActionNames.length; i++)
  868. {
  869. cache.cachedActionToggleFlags[i]
  870. = jEdit.getBooleanProperty(
  871. cache.cachedActionNames[i]
  872. + ".toggle");
  873. }
  874. }
  875. if(cache.browserActionsURI != null)
  876. {
  877. browserActions = new ActionSet(this,null,null,
  878. cache.browserActionsURI);
  879. browserActions.load();
  880. VFSBrowser.getActionContext().addActionSet(browserActions);
  881. cache.cachedBrowserActionNames =
  882. browserActions.getCacheableActionNames();
  883. cache.cachedBrowserActionToggleFlags = new boolean[
  884. cache.cachedBrowserActionNames.length];
  885. for(int i = 0;
  886. i < cache.cachedBrowserActionNames.length;
  887. i++)
  888. {
  889. cache.cachedBrowserActionToggleFlags[i]
  890. = jEdit.getBooleanProperty(
  891. cache.cachedBrowserActionNames[i]
  892. + ".toggle");
  893. }
  894. }
  895. if(dockablesURI != null)
  896. {
  897. DockableWindowManager.loadDockableWindows(this,
  898. dockablesURI,cache);
  899. }
  900. if(label != null)
  901. {
  902. actions.setLabel(jEdit.getProperty(
  903. "action-set.plugin",
  904. new String[] { label }));
  905. }
  906. if(actions.size() != 0)
  907. jEdit.addActionSet(actions);
  908. if(servicesURI != null)
  909. {
  910. ServiceManager.loadServices(this,servicesURI,cache);
  911. }
  912. return cache;
  913. } //}}}
  914. //{{{ startPlugin() method
  915. private void startPlugin()
  916. {
  917. try
  918. {
  919. plugin.start();
  920. }
  921. catch(Throwable t)
  922. {
  923. breakPlugin();
  924. Log.log(Log.ERROR,PluginJAR.this,
  925. "Error while starting plugin " + plugin.getClassName());
  926. Log.log(Log.ERROR,PluginJAR.this,t);
  927. String[] args = { t.toString() };
  928. jEdit.pluginError(path,"plugin-error.start-error",args);
  929. }
  930. if(plugin instanceof EBPlugin)
  931. {
  932. if(jEdit.getProperty("plugin."
  933. + plugin.getClassName() + ".activate")
  934. == null)
  935. {
  936. // old plugins expected jEdit 4.1-style
  937. // behavior, where a PropertiesChanged
  938. // was sent after plugins were started
  939. ((EBComponent)plugin).handleMessage(
  940. new org.gjt.sp.jedit.msg.PropertiesChanged(null));
  941. }
  942. EditBus.addToBus((EBPlugin)plugin);
  943. }
  944. // buffers retain a reference to the fold handler in
  945. // question... and the easiest way to handle fold
  946. // handler loading is this...
  947. Buffer buffer = jEdit.getFirstBuffer();
  948. while(buffer != null)
  949. {
  950. FoldHandler handler =
  951. FoldHandler.getFoldHandler(
  952. buffer.getStringProperty("folding"));
  953. // == null before loaded
  954. if(buffer.getFoldHandler() != null
  955. && handler != null
  956. && handler != buffer.getFoldHandler())
  957. {
  958. buffer.setFoldHandler(handler);
  959. }
  960. buffer = buffer.getNext();
  961. }
  962. } //}}}
  963. //{{{ startPluginLater() method
  964. private void startPluginLater()
  965. {
  966. SwingUtilities.invokeLater(new Runnable()
  967. {
  968. public void run()
  969. {
  970. if(!activated)
  971. return;
  972. startPlugin();
  973. }
  974. });
  975. } //}}}
  976. //{{{ breakPlugin() method
  977. private void breakPlugin()
  978. {
  979. plugin = new EditPlugin.Broken(plugin.getClassName());
  980. plugin.jar = (EditPlugin.JAR)this;
  981. // remove action sets, dockables, etc so that user doesn't
  982. // see the broken plugin
  983. uninit(false);
  984. } //}}}
  985. //}}}
  986. //{{{ PluginCacheEntry class
  987. /**
  988. * Used by the <code>DockableWindowManager</code> and
  989. * <code>ServiceManager</code> to handle caching.
  990. * @since jEdit 4.2pre1
  991. */
  992. public static class PluginCacheEntry
  993. {
  994. public static final int MAGIC = 0xB7A2E420;
  995. //{{{ Instance variables
  996. public PluginJAR plugin;
  997. public long modTime;
  998. public String[] classes;
  999. public URL actionsURI;
  1000. public String[] cachedActionNames;
  1001. public boolean[] cachedActionToggleFlags;
  1002. public URL browserActionsURI;
  1003. public String[] cachedBrowserActionNames;
  1004. public boolean[] cachedBrowserActionToggleFlags;
  1005. public URL dockablesURI;
  1006. public String[] cachedDockableNames;
  1007. public boolean[] cachedDockableActionFlags;
  1008. public URL servicesURI;
  1009. public ServiceManager.Descriptor[] cachedServices;
  1010. public Map cachedProperties;
  1011. public String pluginClass;
  1012. //}}}
  1013. /* read() and write() must be kept perfectly in sync...
  1014. * its a very simple file format. doing it this way is
  1015. * faster than serializing since serialization calls
  1016. * reflection, etc. */
  1017. //{{{ read() method
  1018. public boolean read(DataInputStream din) throws IOException
  1019. {
  1020. int cacheMagic = din.readInt();
  1021. if(cacheMagic != MAGIC)
  1022. return false;
  1023. String cacheBuild = readString(din);
  1024. if(!cacheBuild.equals(jEdit.getBuild()))
  1025. return false;
  1026. long cacheModTime = din.readLong();
  1027. if(cacheModTime != modTime)
  1028. return false;
  1029. actionsURI = readURI(din);
  1030. cachedActionNames = readStringArray(din);
  1031. cachedActionToggleFlags = readBooleanArray(din);
  1032. browserActionsURI = readURI(din);
  1033. cachedBrowserActionNames = readStringArray(din);
  1034. cachedBrowserActionToggleFlags = readBooleanArray(din);
  1035. dockablesURI = readURI(din);
  1036. cachedDockableNames = readStringArray(din);
  1037. cachedDockableActionFlags = readBooleanArray(din);
  1038. servicesURI = readURI(din);
  1039. int len = din.readInt();
  1040. if(len == 0)
  1041. cachedServices = null;
  1042. else
  1043. {
  1044. cachedServices = new ServiceManager.Descriptor[len];
  1045. for(int i = 0; i < len; i++)
  1046. {
  1047. ServiceManager.Descriptor d = new
  1048. ServiceManager.Descriptor(
  1049. readString(din),
  1050. readString(din),
  1051. null,
  1052. plugin);
  1053. cachedServices[i] = d;
  1054. }
  1055. }
  1056. classes = readStringArray(din);
  1057. cachedProperties = readMap(din);
  1058. pluginClass = readString(din);
  1059. return true;
  1060. } //}}}
  1061. //{{{ write() method
  1062. public void write(DataOutputStream dout) throws IOException
  1063. {
  1064. dout.writeInt(MAGIC);
  1065. writeString(dout,jEdit.getBuild());
  1066. dout.writeLong(modTime);
  1067. writeString(dout,actionsURI);
  1068. writeStringArray(dout,cachedActionNames);
  1069. writeBooleanArray(dout,cachedActionToggleFlags);
  1070. writeString(dout,browserActionsURI);
  1071. writeStringArray(dout,cachedBrowserActionNames);
  1072. writeBooleanArray(dout,cachedBrowserActionToggleFlags);
  1073. writeString(dout,dockablesURI);
  1074. writeStringArray(dout,cachedDockableNames);
  1075. writeBooleanArray(dout,cachedDockableActionFlags);
  1076. writeString(dout,servicesURI);
  1077. if(cachedServices == null)
  1078. dout.writeInt(0);
  1079. else
  1080. {
  1081. dout.writeInt(cachedServices.length);
  1082. for(int i = 0; i < cachedServices.length; i++)
  1083. {
  1084. writeString(dout,cachedServices[i].clazz);
  1085. writeString(dout,cachedServices[i].name);
  1086. }
  1087. }
  1088. writeStringArray(dout,classes);
  1089. writeMap(dout,cachedProperties);
  1090. writeString(dout,pluginClass);
  1091. } //}}}
  1092. //{{{ Private members
  1093. //{{{ readString() method
  1094. private String readString(DataInputStream din)
  1095. throws IOException
  1096. {
  1097. int len = din.readInt();
  1098. if(len == 0)
  1099. return null;
  1100. char[] str = new char[len];
  1101. for(int i = 0; i < len; i++)
  1102. str[i] = din.readChar();
  1103. return new String(str);
  1104. } //}}}
  1105. //{{{ readURI() method
  1106. private URL readURI(DataInputStream din)
  1107. throws IOException
  1108. {
  1109. String str = readString(din);
  1110. if(str == null)
  1111. return null;
  1112. else
  1113. return new URL(str);
  1114. } //}}}
  1115. //{{{ readStringArray() method
  1116. private String[] readStringArray(DataInputStream din)
  1117. throws IOException
  1118. {
  1119. int len = din.readInt();
  1120. if(len == 0)
  1121. return null;
  1122. String[] str = new String[len];
  1123. for(int i = 0; i < len; i++)
  1124. {
  1125. str[i] = readString(din);
  1126. }
  1127. return str;
  1128. } //}}}
  1129. //{{{ readBooleanArray() method
  1130. private boolean[] readBooleanArray(DataInputStream din)
  1131. throws IOException
  1132. {
  1133. int len = din.readInt();
  1134. if(len == 0)
  1135. return null;
  1136. boolean[] bools = new boolean[len];
  1137. for(int i = 0; i < len; i++)
  1138. {
  1139. bools[i] = din.readBoolean();
  1140. }
  1141. return bools;
  1142. } //}}}
  1143. //{{{ readMap() method
  1144. private Map readMap(DataInputStream din) throws IOException
  1145. {
  1146. HashMap returnValue = new HashMap();
  1147. int count = din.readInt();
  1148. for(int i = 0; i < count; i++)
  1149. {
  1150. String key = readString(din);
  1151. String value = readString(din);
  1152. if(value == null)
  1153. value = "";
  1154. returnValue.put(key,value);
  1155. }
  1156. return returnValue;
  1157. } //}}}
  1158. //{{{ writeString() method
  1159. private void writeString(DataOutputStream dout,
  1160. Object obj) throws IOException
  1161. {
  1162. if(obj == null)
  1163. {
  1164. dout.writeInt(0);
  1165. }
  1166. else
  1167. {
  1168. String str = obj.toString();
  1169. dout.writeInt(str.length());
  1170. dout.writeChars(str);
  1171. }
  1172. } //}}}
  1173. //{{{ writeStringArray() method
  1174. private void writeStringArray(DataOutputStream dout,
  1175. String[] str) throws IOException
  1176. {
  1177. if(str == null)
  1178. {
  1179. dout.writeInt(0);
  1180. }
  1181. else
  1182. {
  1183. dout.writeInt(str.length);
  1184. for(int i = 0; i < str.length; i++)
  1185. {
  1186. writeString(dout,str[i]);
  1187. }
  1188. }
  1189. } //}}}
  1190. //{{{ writeBooleanArray() method
  1191. private void writeBooleanArray(DataOutputStream dout,
  1192. boolean[] bools) throws IOException
  1193. {
  1194. if(bools == null)
  1195. {
  1196. dout.writeInt(0);
  1197. }
  1198. else
  1199. {
  1200. dout.writeInt(bools.length);
  1201. for(int i = 0; i < bools.length; i++)
  1202. {
  1203. dout.writeBoolean(bools[i]);
  1204. }
  1205. }
  1206. } //}}}
  1207. //{{{ writeMap() method
  1208. private void writeMap(DataOutputStream dout, Map map)
  1209. throws IOException
  1210. {
  1211. dout.writeInt(map.size());
  1212. Iterator iter = map.keySet().iterator();
  1213. while(iter.hasNext())
  1214. {
  1215. String key = (String)iter.next();
  1216. writeString(dout,key);
  1217. writeString(dout,map.get(key));
  1218. }
  1219. } //}}}
  1220. //}}}
  1221. } //}}}
  1222. }