/jEdit/branches/4.3.x-merge-request-2980833/org/gjt/sp/jedit/PluginJAR.java

# · Java · 1745 lines · 1246 code · 169 blank · 330 comment · 228 complexity · db9a74f5796817a810ccfba4f8235332 MD5 · raw file

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