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