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