products/Sources/formale Sprachen/Isabelle/Tools/jEdit/dist/jEdit/org/gjt/sp/jedit image not shown  

Quellcode-Bibliothek

© Kompilation durch diese Firma

[Weder Korrektheit noch Funktionsfähigkeit der Software werden zugesichert.]

Datei: PluginJAR.java   Sprache: JAVA

Original von: Isabelle©

/*
 * PluginJAR.java - Controls JAR loading and unloading
 * :tabSize=4:indentSize=4:noTabs=false:
 * :folding=explicit:collapseFolds=1:
 *
 * Copyright (C) 1999, 2004 Slava Pestov
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */


package org.gjt.sp.jedit;

//{{{ Imports
import java.awt.EventQueue;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.annotation.Nonnull;
import javax.swing.SwingUtilities;

import org.gjt.sp.jedit.browser.VFSBrowser;
import org.gjt.sp.jedit.buffer.DummyFoldHandler;
import org.gjt.sp.jedit.buffer.FoldHandler;
import org.gjt.sp.jedit.gui.DockableWindowFactory;
import org.gjt.sp.jedit.gui.DockableWindowManager;
import org.gjt.sp.jedit.io.CharsetEncoding;
import org.gjt.sp.jedit.manager.BufferManager;
import org.gjt.sp.jedit.msg.PluginUpdate;
import org.gjt.sp.jedit.msg.PropertiesChanged;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.StandardUtilities;
import org.gjt.sp.util.IOUtilities;

import static org.gjt.sp.jedit.EditBus.EBHandler;
//}}}

/**
 * Loads and unloads plugins.<p>
 *
 * <h3>JAR file contents</h3>
 *
 * When loading a plugin, jEdit looks for the following resources:
 *
 * <ul>
 * <li>A file named <code>actions.xml</code> defining plugin actions.
 * Only one such file per plugin is allowed. See {@link ActionSet} for
 * syntax.</li>
 * <li>A file named <code>browser.actions.xml</code> defining file system
 * browser actions.
 * Only one such file per plugin is allowed. See {@link ActionSet} for
 * syntax.</li>
 * <li>A file named <code>dockables.xml</code> defining dockable windows.
 * Only one such file per plugin is allowed. See {@link
 * DockableWindowManager} for
 * syntax.</li>
 * <li>A file named <code>services.xml</code> defining additional services
 * offered by the plugin, such as virtual file systems.
 * Only one such file per plugin is allowed. See {@link
 * ServiceManager} for
 * syntax.</li>
 * <li>File with extension <code>.props</code> containing name/value pairs
 * separated by an equals sign.
 * A plugin can supply any number of property files. Property files are used
 * to define plugin men items, plugin option panes, as well as arbitriary
 * settings and strings used by the plugin. See {@link EditPlugin} for
 * information about properties used by jEdit. See
 * <code>java.util.Properties</code> for property file syntax.</li>
 * <li>Since jEdit 5.0, files named lang_[language_iso_code].properties are
 * localization files. If one of those files match the current language, jEdit
 * will load it. If a label is missing in the localization file, it will be
 * loaded from the other .props files.
 * Those files will be ignored by jEdit's versions older than 5.0 and do not
 * cause any problem
 * See <a href="http://sourceforge.net/apps/mediawiki/jedit/index.php?title=Localization">
 *         jEdit's localization wiki</a>
 * </ul>
 *
 * For a plugin to actually do something once it is resident in memory,
 * it must contain a class whose name ends with <code>Plugin</code>.
 * This class, known as the <i>plugin core class</i> must extend
 * {@link EditPlugin} and define a few required properties, otherwise it is
 * ignored.
 *
 * <h3>Dynamic and deferred loading</h3>
 *
 * Unlike in prior jEdit versions, jEdit 4.2 and later allow
 * plugins to be added and removed to the resident set at any time using
 * the {@link jEdit#addPluginJAR(String)} and
 * {@link jEdit#removePluginJAR(PluginJAR,boolean)} methods. Furthermore, the
 *  plugin core class might not be loaded until the plugin is first used. See
 * {@link EditPlugin#start()} for a full description.
 *
 *
 * @see jEdit#getProperty(String)
 * @see jEdit#getPlugin(String)
 * @see jEdit#getPlugins()
 * @see jEdit#getPluginJAR(String)
 * @see jEdit#getPluginJARs()
 * @see jEdit#addPluginJAR(String)
 * @see jEdit#removePluginJAR(PluginJAR,boolean)
 * @see ActionSet
 * @see DockableWindowManager
 * @see OptionPane
 * @see PluginJAR
 * @see ServiceManager
 *
 * @author Slava Pestov
 * @version $Id: PluginJAR.java 25239 2020-04-14 20:00:17Z kpouer $
 * @since jEdit 4.2pre1
 */

public class PluginJAR
{
 //{{{ Instance variables
 private final String path;
 private String cachePath;
 private final File file;

 private final JARClassLoader classLoader;
 private ZipFile zipFile;
 private Properties properties;
 private Map<String, Properties> localizationProperties;
 /**
 * The class list contained in this jar.
 */

 private String[] classes;
 /**
 * The resource list in this jar.
 */

 private String[] resources;
 private ActionSet actions;
 private ActionSet browserActions;
 private EditPlugin plugin;
 private URL dockablesURI;
 private URL servicesURI;
 private boolean activated;

 // Lists of jarPaths
 /** These plugins require this plugin */
 private final Set<String> theseRequireMe = new LinkedHashSet<>();
 
 /** The plugins that uses me as optional dependency. */
 private final Set<String> theseUseMe = new LinkedHashSet<>();
 
 /** This plugin requires these plugins. */
 private final Set<String> weRequireThese = new LinkedHashSet<>();
 
 /** These plugins are an optional dependency for me, I'll use them if they are available, no worries if they aren't. */
 private final Set<String> weUseThese = new LinkedHashSet<>();
 //}}}

 //{{{ load(String jarPath, boolean activateDependentIfNecessary)
 /**
 * Loads a plugin, and its dependent plugins if necessary.
 *
 * @since jEdit 4.3pre7
 */

 public static PluginJAR load(String path, boolean loadDependents)
 {
  PluginJAR jar = jEdit.getPluginJAR(path);
  if (jar != null && jar.getPlugin() != null)
  {
   return jar;
  }
  jEdit.addPluginJAR(path);
  jar = jEdit.getPluginJAR(path);
  if (jar == null)
   return null;
  EditPlugin plugin = jar.getPlugin();
  if (plugin == null)
  {
   // No plugin, maybe it is a library
   return jar;
  }
  String className = plugin.getClassName();
  if (loadDependents)
  {
   Set<String> pluginLoadList = getDependencySet(className);
   for (String jarName: pluginLoadList)
   {
    String jarPath = findPlugin(jarName);
    if (jarPath == null)
    {
     Log.log(Log.WARNING, PluginJAR.class"Unable to load dependency " + jarName+
           " the plugin is not installed");
     continue;
    }
    load(jarPath, true);
   }
  }
  
  // Load extra jars that are part of this plugin
  Collection<String> jarsPaths = jar.getJars();
  for(String _jarPath: jarsPaths)
  {
   PluginJAR _jar = jEdit.getPluginJAR(_jarPath);
   if(_jar == null)
   {
    jEdit.addPluginJAR(_jarPath);
   }
  }
  jar.checkDependencies();
  jar.activatePluginIfNecessary();
  jEdit.propertiesChanged();
  
  // check all other installed plugins to see if any of them  
  // use me. Reload those that do so the classloaders work together.
  PluginJAR[] installedPlugins = jEdit.getPluginJARs();
  for (PluginJAR installed : installedPlugins)
  {
   if (installed == null || installed.equals(jar) )
   {
    continue
   }
   EditPlugin ep = installed.getPlugin();
   if (ep == null
   {
    continue;
   }
   String installedClassname = ep.getClassName();
   PluginDepends[] deps = getPluginDepends(installedClassname);
   for (PluginDepends dep : deps)
   {
    if ("plugin".equals(dep.what) && className.equals(dep.name))
    {
     String reloadPath = ep.getPluginJAR().getPath();
     jEdit.removePluginJAR(ep.getPluginJAR(), false);
     load(reloadPath, true);
    }
   }
  }
  return jar;
 } // }}}
 
 //{{{ parseJarsFilesString(String path, String jarsString) method
 /**
 * parse the files listed in plugin.CLASSNAME.jars or plugin.CLASSNAME.files
 * and return full paths to each file of the list.
 * @since jEdit 5.3pre1
 */

 public static Collection<String> parseJarsFilesString(String path, String jarsString)
 {
  String dir = MiscUtilities.getParentOfPath(path);
  StringTokenizer st = new StringTokenizer(jarsString);
  Collection<String> jarPaths = new LinkedList<>();
  while(st.hasMoreTokens())
  {
   String _jarPath = MiscUtilities.constructPath(dir,st.nextToken());
   jarPaths.add(_jarPath);
  }
  return jarPaths;
 }
 // }}}
 //{{{ parseJarsFilesStringNames(String path, String jarsString) method
 /**
 * parse the files listed in plugin.CLASSNAME.jars or plugin.CLASSNAME.files
 * and return them as a collection
 * @since jEdit 5.3pre1
 */

 public static Collection<String> parseJarsFilesStringNames(String jarsString)
 {
  StringTokenizer st = new StringTokenizer(jarsString);
  Collection<String> jarPaths = new LinkedList<>();
  while(st.hasMoreTokens())
  {
   jarPaths.add(st.nextToken());
  }
  return jarPaths;
 }
 // }}}

 //{{{ getJars() method
 /**
 * Get the jars listed in this plugin and return full paths to them
 *
 * @return jars full paths or empty collection if plugin is null
 * @since jEdit 5.3pre1
 */

 public Collection<String> getJars()
 {
  if(plugin != null)
  {
   String jars = jEdit.getProperty("plugin." + plugin.getClassName() + ".jars");
   if(jars != null)
   {
    return parseJarsFilesString(path, jars);
   }
  }
  return Collections.emptyList();
 }
 // }}}

 // {{{ getFiles() method
 /**
 * Get the files listed in this plugin and return full paths to them
 *
 * @return files full paths or empty collection if plugin is null
 * @since jEdit 5.3pre1
 */

 public Collection<String> getFiles()
 {
  if(plugin != null)
  {
   String files = jEdit.getProperty("plugin." + plugin.getClassName() + ".files");
   if(files != null)
   {
    return parseJarsFilesString(path, files);
   }
  }
  return Collections.emptyList();
 }
 // }}}

 //{{{ getPath() method
 /**
 * Returns the full path name of this plugin's JAR file.
 */

 public String getPath()
 {
  return path;
 } //}}}

 //{{{ findPlugin() method
 /**
 * Unlike getPlugin(), will return a PluginJAR that is not yet loaded,
 * given its classname.
 *
 * @param className a class name
 * @return the JARpath of the first PluginJAR it can find which contains this className,
 *      or null if not found.
 * @since 4.3pre7
 */

 public static String findPlugin(String className)
 {
  EditPlugin ep = jEdit.getPlugin(className);
  if (ep != nullreturn ep.getPluginJAR().getPath();

  for (String JARpath: jEdit.getNotLoadedPluginJARs())
  {
   PluginJAR pjar = new PluginJAR(new File(JARpath));
   if (pjar.containsClass(className))
   {
    return JARpath;
   }
  }
  return null;
 } // }}}

 //{{{ containsClass() function
 /**
 * @param className a class name
 * @return true if this jar contains a class with that classname.
 * @since jedit 4.3pre7
 */

 boolean containsClass(String className)
 {
  try
  {
   getZipFile();
  }
  catch (IOException ioe)
  {
   throw new RuntimeException(ioe);
  }
  Enumeration<? extends ZipEntry> itr = zipFile.entries();
  while (itr.hasMoreElements())
  {
   String entry = itr.nextElement().toString();
   if (entry.endsWith(".class"))
   {
    String name = entry.substring(0, entry.length() - 6).replace('/''.');
    if (name.equals(className))
     return true;
   }
  }
  return false;

 } // }}}

 //{{{ getCachePath() method
 /**
 * Returns the full path name of this plugin's summary file.
 * The summary file is used to store certain information which allows
 * loading of the plugin's resources and core class to be deferred
 * until the plugin is first used. As long as a plugin is using the
 * jEdit 4.2 plugin API, no extra effort is required to take advantage
 * of the summary cache.
 */

 public String getCachePath()
 {
  return cachePath;
 } //}}}

 //{{{ getDependencySet() method
 /**
 *
 * @param className of a plugin that we wish to load
 * @return an ordered set of JARpaths that contains the
 *      plugins that need to be (re)loaded, in the correct order.
 */

 public static Set<String> getDependencySet(String className)
 {
  Set<String> retval = new LinkedHashSet<String>();
  PluginDepends[] deps = getPluginDepends(className);
  for (PluginDepends pluginDepends : deps)
  {
   if (pluginDepends.optional)
   {
    continue
   }

   if("plugin".equals(pluginDepends.what))
   {
    int index2 = pluginDepends.arg.indexOf(' ');
    if ( index2 == -1)
    {
     Log.log(Log.ERROR, PluginJAR.class, className
      + " has an invalid dependency: "
      + pluginDepends.dep + " (version is missing)");
     continue;
    }

    String pluginName = pluginDepends.arg.substring(0,index2);
    String needVersion = pluginDepends.arg.substring(index2 + 1);
    //todo : check version ?
    Set<String> loadTheseFirst = getDependencySet(pluginName);
    loadTheseFirst.add(pluginName);
    loadTheseFirst.addAll(retval);
    retval = loadTheseFirst;
   }
  }
  return retval;
 } // }}}

 //{{{ getFile() method
 /**
 * Returns a file pointing to the plugin JAR.
 */

 public File getFile()
 {
  return file;
 } //}}}

 //{{{ getClassLoader() method
 /**
 * Returns the plugin's class loader.
 */

 public JARClassLoader getClassLoader()
 {
  return classLoader;
 } //}}}

 //{{{ getZipFile() method
 /**
 * Returns the plugin's JAR file, opening it if necessary.
 * @since jEdit 4.2pre1
 */

 public synchronized ZipFile getZipFile() throws IOException
 {
  if(zipFile == null)
  {
   Log.log(Log.DEBUG,this,"Opening " + path);
   zipFile = new ZipFile(path);
  }
  return zipFile;
 } //}}}

 //{{{ getActionSet() method
 /**
 * Returns the plugin's action set for the jEdit action context
 * {@link jEdit#getActionContext()}. These actions are loaded from
 * the <code>actions.xml</code> file; see {@link ActionSet}.
 *.
 * @since jEdit 4.2pre1
 */

 public ActionSet getActionSet()
 {
  return actions;
 } //}}}

 //{{{ getBrowserActionSet() method
 /**
 * Returns the plugin's action set for the file system browser action
 * context {@link
 * VFSBrowser#getActionContext()}.
 * These actions are loaded from
 * the <code>browser.actions.xml</code> file; see {@link ActionSet}.
 *.
 * @since jEdit 4.2pre1
 */

 public ActionSet getBrowserActionSet()
 {
  return browserActions;
 } //}}}

 //{{{ checkDependencies() method
 /**
 * Returns true if all dependencies are satisified, false otherwise.
 * Also if dependencies are not satisfied, the plugin is marked as
 * "broken".
 *
 */

 public boolean checkDependencies()
 {
  if(plugin == null)
   return true;
  boolean ok = true;

  String name = plugin.getClassName();
  PluginDepends[] deps = getPluginDepends(name);
  for (PluginDepends pluginDepends : deps)
  {
   if("jdk".equals(pluginDepends.what))
   {
    if(!pluginDepends.optional && StandardUtilities.compareStrings(
     System.getProperty("java.version"),
     pluginDepends.arg,false) < 0)
    {
     String[] args = { pluginDepends.arg,
      System.getProperty("java.version") };
     jEdit.pluginError(path,"plugin-error.dep-jdk",args);
     ok = false;
    }
   }
   else if("jedit".equals(pluginDepends.what))
   {
    if(pluginDepends.arg.length() != 11)
    {
     Log.log(Log.ERROR,this,"Invalid jEdit version"
      + " number: " + pluginDepends.arg);
     ok = false;
    }

    if(!pluginDepends.optional && StandardUtilities.compareStrings(
     jEdit.getBuild(),pluginDepends.arg,false) < 0)
    {
     String needs = MiscUtilities.buildToVersion(pluginDepends.arg);
     String[] args = { needs,
      jEdit.getVersion() };
     jEdit.pluginError(path,
      "plugin-error.dep-jedit",args);
     ok = false;
    }
   }
   else if("plugin".equals(pluginDepends.what))
   {
    int index2 = pluginDepends.arg.indexOf(' ');
    if(index2 == -1)
    {
     Log.log(Log.ERROR,this,name
      + " has an invalid dependency: "
      + pluginDepends.dep + " (version is missing)");
     ok = false;
     continue;
    }

    String pluginName = pluginDepends.arg.substring(0,index2);
    String needVersion = pluginDepends.arg.substring(index2 + 1);
    String currVersion = jEdit.getProperty("plugin."
     + pluginName + ".version");

    EditPlugin editPlugin = jEdit.getPlugin(pluginName, false);
    if(editPlugin == null)
    {
     if(!pluginDepends.optional)
     {
      String[] args = { needVersion,
       pluginName };
      jEdit.pluginError(path,
       "plugin-error.dep-plugin.no-version",
       args);
      ok = false;
     }
    }
    else if(StandardUtilities.compareStrings(
     currVersion,needVersion,false) < 0)
    {
     if(!pluginDepends.optional)
     {
      String[] args = { needVersion,
       pluginName, currVersion };
      jEdit.pluginError(path, "plugin-error.dep-plugin",args);
      ok = false;
     }
    }
    else if(editPlugin instanceof EditPlugin.Broken)
    {
     if(!pluginDepends.optional)
     {
      String[] args = { pluginName };
      jEdit.pluginError(path, "plugin-error.dep-plugin.broken",args);
      ok = false;
     }
    }
    else
    {
     PluginJAR jar = editPlugin.getPluginJAR();
     if (pluginDepends.optional)
     {
      jar.theseUseMe.add(path);
      weUseThese.add(jar.getPath());
     }
     else
     {
      jar.theseRequireMe.add(path);
      weRequireThese.add(jar.getPath());
     }
    }
   }
   else if("class".equals(pluginDepends.what))
   {
    if(!pluginDepends.optional)
    {
     try
     {
      classLoader.loadClass(pluginDepends.arg,false);
     }
     catch(Exception e)
     {
      String[] args = { pluginDepends.arg };
      jEdit.pluginError(path, "plugin-error.dep-class",args);
      ok = false;
     }
    }
   }
   else
   {
    Log.log(Log.ERROR,this,name + " has unknown"
     + " dependency: " + pluginDepends.dep);
    ok = false;
   }
  }

  // each JAR file listed in the plugin's jars property
  // needs to know that we need them
  Collection<String> jarsPaths = getJars();

  for(String jarPath: jarsPaths)
  {
   PluginJAR jar = jEdit.getPluginJAR(jarPath);
   if(jar == null)
   {
    String[] args = { jarPath };
    jEdit.pluginError(path, "plugin-error.missing-jar",args);
    ok = false;
   }
   else
   {
    weRequireThese.add(jarPath);
    jar.theseRequireMe.add(path);
   }
  }
  
  if(!ok)
   breakPlugin();

  return ok;
 } //}}}

 //{{{ getRequiredJars() method
 /**
 * Returns the required jars of this plugin.
 *
 * @return the required jars of this plugin
 * @since jEdit 4.3pre12
 */

 public Set<String> getRequiredJars()
 {
  return weRequireThese;
 } //}}}

 //{{{ getPluginDepends() method
 private static PluginDepends[] getPluginDepends(String classname) throws IllegalArgumentException
 {
  List<PluginDepends> ret = new ArrayList<PluginDepends>();
  int i = 0;
  String dep;
  while((dep = jEdit.getProperty("plugin." + classname + ".depend." + i++)) != null)
  {
   boolean optional;
   if(dep.startsWith("optional "))
   {
    optional = true;
    dep = dep.substring("optional ".length());
   }
   else
   {
    optional = false;
   }
 
   int index = dep.indexOf(' ');
   if(index == -1)
    throw new IllegalArgumentException("wrong dependency");
 
   String what = dep.substring(0,index);
   String arg = dep.substring(index + 1);
   PluginDepends depends = new PluginDepends();
   depends.what = what;
   depends.arg = arg;
   depends.optional = optional;
   depends.dep = dep;
   if ("plugin".equals(what))
    depends.name = arg.indexOf(' ') > 0 ? arg.substring(0, arg.indexOf(' ')) : arg;
   ret.add(depends);
  }
  return ret.toArray(new PluginDepends[ret.size()]);
 } //}}}

 //{{{ getDependencies() method
 /**
 * Returns a list of dependencies by searching the plugin properties.
 * @param classname The classname of a plugin
 * @return A list of classnames of plugins the plugin depends on.
 */

 @Nonnull
 public static Set<String> getDependencies(String classname) throws IllegalArgumentException
 {
  Set<String> ret = new HashSet<>();
  int i = 0;
  String dep;
  while((dep = jEdit.getProperty("plugin." + classname + ".depend." + i++)) != null)
  {
   int index = dep.indexOf(' ');
   String what = dep.substring(0,index);
   String arg = dep.substring(index + 1);
   if ("plugin".equals(what))
   {
    ret.add(arg.indexOf(' ') > 0 ? arg.substring(0, arg.indexOf(' ')) : arg);
   }
  }
  return ret;
 } //}}}

 //{{{ getOptionalDependencies() method
 /**
 * Returns a list of optional dependencies by searching the plugin properties.
 * @param classname The classname of a plugin
 * @return A list of classnames of plugins the plugin optionally depends on.
 */

 public static Set<String> getOptionalDependencies(String classname) throws IllegalArgumentException
 {
  Set<String> ret = new HashSet<String>();
  int i = 0;
  String dep;
  while((dep = jEdit.getProperty("plugin." + classname + ".depend." + i++)) != null)
  {
   int index = dep.indexOf(' ');
   String what = dep.substring(0,index);
   String arg = dep.substring(index + 1);
   if ("optional".equals(what))
   {
    index = arg.indexOf(' ');
    what = arg.substring(0, index);
    arg = arg.substring(index + 1);
    if ("plugin".equals(what))
    {
     ret.add(arg.indexOf(' ') > 0 ? arg.substring(0, arg.indexOf(' ')) : arg);
    }
   }
  }
  return ret;
 } //}}}

 //{{{ PluginDepends class
 private static class PluginDepends
 {
  String dep;  // full string, e.g. plugin errorlist.ErrorList 1.3
  String what; // depends type, e.g. jedit, jdk, plugin
  String arg;  // classname + version, e.g errorlist.ErrorList 1.3
  String name; // just the class name, e.g. errorlist.ErrorList, only filled in if what is plugin
  boolean optional;
  
 } //}}}

 //{{{ transitiveClosure()
 /**
 * If plugin A is needed by B, and B is needed by C, we want to
 * tell the user that A is needed by B and C when they try to
 * unload A.
 *
 * @param dependents a set of plugins which we wish to disable
 * @param listModel a set of plugins which will be affected, and will need
 *  to be disabled also.
 */

 public static void transitiveClosure(String[] dependents, List<String> listModel)
 {
  for (String jarPath : dependents)
  {
   if (!listModel.contains(jarPath))
   {
    listModel.add(jarPath);
    PluginJAR jar = jEdit.getPluginJAR(jarPath);
    if (jar == null)
    {
     Log.log(Log.WARNING, PluginJAR.class"The jar file " + jarPath +
               " doesn't exist, the plugin may have been partially removed");
    }
    else
    {
     transitiveClosure(jar.getDependentPlugins(), listModel);
    }
   }
  }
   } //}}}

 //{{{ getDependentPlugins() method
 /**
* @return an array of plugin names that have a hard dependency on this plugin
*/

 public String[] getDependentPlugins()
 {
  return theseRequireMe.toArray(StandardUtilities.EMPTY_STRING_ARRAY);
 } //}}}

 //{{{ getOptionallyDependentPlugins() method
 /**
* @return an array of plugin names that have an optional dependency on this plugin
*/

 public String[] getOptionallyDependentPlugins()
 {
  return theseUseMe.toArray(StandardUtilities.EMPTY_STRING_ARRAY);
 } //}}}
 
 //{{{ getAllDependentPlugins() method
 /**
* @return an array of plugin names that have a dependency or an optional dependency on this plugin,
* this returns a combination of <code>getDependentPlugins</code> and <code>getOptionallyDependentPlugins</code>.
*/

 public String[] getAllDependentPlugins()
 {
  String[] dependents = new String[theseRequireMe.size() + theseUseMe.size()];
  System.arraycopy( theseRequireMe.toArray(), 0, dependents, 0, theseRequireMe.size() );
  System.arraycopy( theseUseMe.toArray(), 0, dependents, theseRequireMe.size(), theseUseMe.size());
  return dependents;
 } //}}}
 

 //{{{ getPlugin() method
 /**
 * Returns the plugin core class for this JAR file. Note that if the
 * plugin has not been activated, this will return an instance of
 * {@link EditPlugin.Deferred}. If you need the actual plugin core
 * class instance, call {@link #activatePlugin()} first.
 * If the plugin is not yet loaded, returns null
 *
 * @since jEdit 4.2pre1
 */

 public EditPlugin getPlugin()
 {
  return plugin;
 } //}}}

 //{{{ activatePlugin() method
 /**
 * Loads the plugin core class. Does nothing if the plugin core class
 * has already been loaded. This method might be called on startup,
 * depending on what properties are set. See {@link EditPlugin#start()}.
 * This method is thread-safe.
 *
 * @since jEdit 4.2pre1
 */

 @SuppressWarnings("unchecked")
 public void activatePlugin()
 {
  synchronized (this)
  {
   if (activated)
   {
    // recursive call
    return;
   }

   activated = true;
  }

  if (!(plugin instanceof EditPlugin.Deferred))
  {
   return;
  }

  String className = plugin.getClassName();

  try
  {
   Class clazz = classLoader.loadClass(className,false);
   int modifiers = clazz.getModifiers();
   if(Modifier.isInterface(modifiers)
    || Modifier.isAbstract(modifiers)
    || !EditPlugin.class.isAssignableFrom(clazz))
   {
    Log.log(Log.ERROR,this,"Plugin has properties but does not extend EditPlugin: "
     + className);
    breakPlugin();
    return;
   }

   plugin = (EditPlugin)clazz.getDeclaredConstructor().newInstance();
   plugin.jar = this;
  }
  catch (Throwable t)
  {
   breakPlugin();

   Log.log(Log.ERROR,this,"Error while starting plugin " + className);
   Log.log(Log.ERROR,this,t);
   String[] args = { t.toString() };
   jEdit.pluginError(path,"plugin-error.start-error",args);

   return;
  }

  if (jEdit.isMainThread()
   || SwingUtilities.isEventDispatchThread())
  {
   startPlugin();
  }
  else
  {
   // for thread safety
   startPluginLater();
  }

  EditBus.sendAsync(new PluginUpdate(this,PluginUpdate.ACTIVATED,false));
 } //}}}

 //{{{ activateIfNecessary() method
 /**
 * Should be called after a new plugin is installed.
 * @since jEdit 4.2pre2
 */

 public void activatePluginIfNecessary()
 {
  String filename = MiscUtilities.getFileName(getPath());
  jEdit.unsetProperty("plugin-blacklist." + filename);
  if(!(plugin instanceof EditPlugin.Deferred))
  {
   return;
  }

  String className = plugin.getClassName();

  // default for plugins that don't specify this property (ie,
  // 4.1-style plugins) is to load them on startup
  String activate = jEdit.getProperty("plugin."
   + className + ".activate");

  if(activate == null)
  {
   // 4.1 plugin
   if(!jEdit.isMainThread())
   {
    breakPlugin();

    jEdit.pluginError(path,"plugin-error.not-42",null);
   }
   else
   {
    activatePlugin();
   }
  }
  else
  {
   // 4.2 plugin

   // if at least one property listed here is true,
   // load the plugin
   boolean load = false;

   StringTokenizer st = new StringTokenizer(activate);
   while(st.hasMoreTokens())
   {
    String prop = st.nextToken();
    boolean value = jEdit.getBooleanProperty(prop);
    if(value)
    {
     Log.log(Log.DEBUG,this,"Activating "
      + className + " because of " + prop);
     load = true;
     break;
    }
   }

   if(load)
   {
    activatePlugin();
   }
  }
 } //}}}

 //{{{ deactivatePlugin() method
 /**
 * Unloads the plugin core class. Does nothing if the plugin core class
 * has not been loaded.
 * This method can only be called from the AWT event dispatch thread!
 * @see EditPlugin#stop()
 *
 * @since jEdit 4.2pre3
 */

 public void deactivatePlugin(boolean exit)
 {
  if(!activated)
   return;

  if(!exit)
  {
   // buffers retain a reference to the fold handler in
   // question... and the easiest way to handle fold
   // handler unloading is this...
   BufferManager bufferManager = jEdit.getBufferManager();
   bufferManager
    .getBuffers()
    .stream()
    .filter(buffer -> buffer.getFoldHandler().getClass().getClassLoader() == classLoader)
    .forEach(buffer -> buffer.setFoldHandler(new DummyFoldHandler()));
  }

  if(plugin != null && !(plugin instanceof EditPlugin.Broken))
  {
   if(plugin instanceof EBPlugin ||
      plugin.getClass().getAnnotation(EBHandler.class) != null)
   {
    EditBus.removeFromBus(plugin);
   }

   try
   {
    plugin.stop();
   }
   catch(Throwable t)
   {
    Log.log(Log.ERROR,this,"Error while "
     + "stopping plugin:");
    Log.log(Log.ERROR,this,t);
   }

   plugin = new EditPlugin.Deferred(this,
    plugin.getClassName());

   EditBus.send(new PluginUpdate(this,
    PluginUpdate.DEACTIVATED,exit));

   if(!exit)
   {
    // see if this is a 4.1-style plugin
    String activate = jEdit.getProperty("plugin."
     + plugin.getClassName() + ".activate");

    if(activate == null)
    {
     breakPlugin();
     jEdit.pluginError(path,"plugin-error.not-42",null);
    }
   }
  }

  activated = false;
 } //}}}

 //{{{ getDockablesURI() method
 /**
 * Returns the location of the plugin's
 * <code>dockables.xml</code> file.
 * @since jEdit 4.2pre1
 */

 public URL getDockablesURI()
 {
  return dockablesURI;
 } //}}}

 //{{{ getServicesURI() method
 /**
 * Returns the location of the plugin's
 * <code>services.xml</code> file.
 * @since jEdit 4.2pre1
 */

 public URL getServicesURI()
 {
  return servicesURI;
 } //}}}

 //{{{ toString() method
 @Override
 public String toString()
 {
  if(plugin == null)
   return path;
  else
   return path + ",class=" + plugin.getClassName();
 } //}}}

 //{{{ Package-private members

 //{{{ Static methods
 
 //{{{ getPluginCache() method
 public static PluginCacheEntry getPluginCache(PluginJAR plugin)
 {
  String jarCachePath = plugin.getCachePath();
  if(jarCachePath == null)
   return null;

  DataInputStream din = null;
  try
  {
   PluginCacheEntry cache = new PluginCacheEntry();
   cache.plugin = plugin;
   cache.modTime = plugin.getFile().lastModified();
   din = new DataInputStream(
    new BufferedInputStream(
    new FileInputStream(jarCachePath)));
   if(cache.read(din))
    return cache;
   else
   {
    // returns false with outdated cache
    return null;
   }
  }
  catch(FileNotFoundException fnf)
  {
   return null;
  }
  catch(IOException io)
  {
   Log.log(Log.ERROR,PluginJAR.class,io);
   return null;
  }
  finally
  {
   IOUtilities.closeQuietly((Closeable)din);
  }
 } //}}}

 //{{{ setPluginCache() method
 static void setPluginCache(PluginJAR plugin, PluginCacheEntry cache)
 {
  String jarCachePath = plugin.getCachePath();
  if(jarCachePath == null)
   return;

  Log.log(Log.DEBUG,PluginJAR.class,"Writing " + jarCachePath);

  DataOutputStream dout = null;
  try
  {
   dout = new DataOutputStream(
    new BufferedOutputStream(
    new FileOutputStream(jarCachePath)));
   cache.write(dout);
   dout.close();
  }
  catch(IOException io)
  {
   Log.log(Log.ERROR,PluginJAR.class,io);
   IOUtilities.closeQuietly((Closeable)dout);
   new File(jarCachePath).delete();
  }
 } //}}}

 //{{{ getPluginCacheEntry() method
 /**
 * Returns the cache entry for an installed but not loaded plugin.
 * There is no need to use this method if the plugin is loaded.
 *
 * @param path path to the the plugin jar
 * @return cache entry or null
 * @throws IOException if jEdit cannot generate cache
 * @since jEdit 5.3pre1
 */

 public static PluginJAR.PluginCacheEntry getPluginCacheEntry(String path) throws IOException
 {
  PluginJAR pluginJAR = new PluginJAR(new File(path));
  PluginJAR.PluginCacheEntry pluginCacheEntry = PluginJAR.getPluginCache(pluginJAR);
  if (pluginCacheEntry == null)
  {
   try
   {
    pluginCacheEntry = pluginJAR.generateCache();
   }
   finally
   {
    IOUtilities.closeQuietly(pluginJAR.getZipFile());
   }
  }
  if(pluginCacheEntry == null)
  {
   // this happens when, for some reason, two versions
   // of a plugin are installed, e.g when XSLT.jar and
   // xslt.jar are both in $JEDIT_HOME/jars on Linux.
   Log.log(Log.WARNING, PluginJAR.class,
     "couldn't load plugin "+pluginJAR.getPath()
     +" (most likely other version exists)");
  }
  return pluginCacheEntry;
 }//}}}

 //}}}

 //{{{ PluginJAR constructor
 /**
 * Creates a PluginJAR object which is not necessarily loaded, but can be later.
 * @see #load(String, boolean)
 */

 public PluginJAR(File file)
 {
  path = file.getPath();
  String jarCacheDir = jEdit.getJARCacheDirectory();
  if(jarCacheDir != null)
  {
   cachePath = MiscUtilities.constructPath(
    jarCacheDir,file.getName() + ".summary");
  }
  this.file = file;
  classLoader = new JARClassLoader(this);
  actions = new ActionSet();
 } //}}}

 //{{{ init() method
 public boolean init()
 {
  PluginCacheEntry cache = getPluginCache(this);
  if(cache != null)
  {
   if (!loadCache(cache))
    return false;
   classLoader.activate();
  }
  else
  {
   try
   {
    cache = generateCache();
    if(cache != null)
    {
     setPluginCache(this,cache);
     classLoader.activate();
    }
    else
    {
     return false;
    }
   }
   catch(IOException io)
   {
    Log.log(Log.ERROR,this,"Cannot load"
     + " plugin " + path);
    Log.log(Log.ERROR,this,io);

    String[] args = { io.toString() };
    jEdit.pluginError(path,"plugin-error.load-error",args);

    uninit(false);
   }
  }
  return true;
 } //}}}

 //{{{ uninit() method
 public void uninit(boolean exit)
 {
  deactivatePlugin(exit);

  if(!exit)
  {
   for (String path : weRequireThese)
   {
    PluginJAR jar = jEdit.getPluginJAR(path);
    if(jar != null)
     jar.theseRequireMe.remove(this.path);
   }

   for (String path : weUseThese)
   {
    PluginJAR jar = jEdit.getPluginJAR(path);
    if(jar != null)
     jar.theseUseMe.remove(this.path);
   }

   classLoader.deactivate();
   BeanShell.resetClassManager();

   if(actions != null)
    jEdit.removeActionSet(actions);
   if(browserActions != null)
    VFSBrowser.getActionContext().removeActionSet(browserActions);

   DockableWindowFactory.getInstance()
    .unloadDockableWindows(this);
   ServiceManager.unloadServices(this);

   jEdit.removePluginProps(properties);
   if (localizationProperties != null)
   {
    Collection<Properties> values = localizationProperties.values();
    for (Properties value : values)
    {
     jEdit.removePluginLocalizationProps(value);
    }
   }
   try
   {
    if(zipFile != null)
    {
     zipFile.close();
     zipFile = null;
    }
   }
   catch(IOException io)
   {
    Log.log(Log.ERROR,this,io);
   }
   removePluginCache();
  }
 } //}}}

 //{{{ getClasses() method
 String[] getClasses()
 {
  return classes;
 } //}}}

 //{{{ getResources() method
 public String[] getResources()
 {
  return resources;
 } //}}}

 //}}}

 //{{{ Private members

 //{{{ actionsPresentButNotCoreClass() method
 private void actionsPresentButNotCoreClass()
 {
  Log.log(Log.WARNING,this,getPath() + " has an actions.xml but no plugin core class");
  actions.setLabel("MISSING PLUGIN CORE CLASS");
 } //}}}

 //{{{ loadCache() method
 private boolean loadCache(PluginCacheEntry cache)
 {
  // Check if a plugin with the same name
  // is already loaded
  if(cache.pluginClass != null)
  {
   // Check if a plugin with the same name
   // is already loaded
   if (!continueLoading(cache.pluginClass, cache.cachedProperties))
   {
    return false;
   }
   else
   {
    EditPlugin otherPlugin = jEdit.getPlugin(cache.pluginClass);
    if (otherPlugin != null)
     jEdit.removePluginJAR(otherPlugin.getPluginJAR(), false);
   }
  }

  classes = cache.classes;
  resources = cache.resources;

  // this must be done before loading cachedProperties
  if (cache.localizationProperties != null)
  {
   localizationProperties = cache.localizationProperties;
   String currentLanguage = jEdit.getCurrentLanguage();
   Properties langProperties = localizationProperties.get(currentLanguage);
   if (langProperties != null)
   {
    jEdit.addPluginLocalizationProps(langProperties);
   }
  }

  /* this should be before dockables are initialized */
  if(cache.cachedProperties != null)
  {
   properties = cache.cachedProperties;
   jEdit.addPluginProps(cache.cachedProperties);
  }

  if(cache.actionsURI != null
   && cache.cachedActionNames != null)
  {
   actions = new ActionSet(this,
    cache.cachedActionNames,
    cache.cachedActionToggleFlags,
    cache.actionsURI);
  }

  if(cache.browserActionsURI != null
   && cache.cachedBrowserActionNames != null)
  {
   browserActions = new ActionSet(this,
    cache.cachedBrowserActionNames,
    cache.cachedBrowserActionToggleFlags,
    cache.browserActionsURI);
   String label = jEdit.getProperty(
     "plugin." + cache.pluginClass
     + ".name");
   browserActions.setLabel(jEdit.getProperty(
     "action-set.plugin",
     new String[] { label }));
   VFSBrowser.getActionContext().addActionSet(browserActions);
  }

  if(cache.dockablesURI != null
   && cache.cachedDockableNames != null
   && cache.cachedDockableActionFlags != null
   && cache.cachedDockableMovableFlags != null)
  {
   dockablesURI = cache.dockablesURI;
   DockableWindowFactory.getInstance()
    .cacheDockableWindows(this,
    cache.cachedDockableNames,
    cache.cachedDockableActionFlags,
    cache.cachedDockableMovableFlags);
  }

  if(actions.size() != 0)
   jEdit.addActionSet(actions);

  if(cache.servicesURI != null
   && cache.cachedServices != null)
  {
   servicesURI = cache.servicesURI;
   for(int i = 0; i < cache.cachedServices.length;
    i++)
   {
    ServiceManager.Descriptor d
     = cache.cachedServices[i];
    ServiceManager.registerService(d);
   }
  }

  if(cache.pluginClass != null)
  {
   String label = jEdit.getProperty(
    "plugin." + cache.pluginClass
    + ".name");
   actions.setLabel(jEdit.getProperty(
    "action-set.plugin",
    new String[] { label }));
   plugin = new EditPlugin.Deferred(this,
    cache.pluginClass);
  }
  else
  {
   if(actions.size() != 0)
    actionsPresentButNotCoreClass();
  }
  return true;
 } //}}}

 //{{{ generateCache() method
 public PluginCacheEntry generateCache() throws IOException
 {
  properties = new Properties();
  localizationProperties = new HashMap<>();

  List<String> classes = new LinkedList<>();
  List<String> resources = new LinkedList<>();

  ZipFile zipFile = getZipFile();

  List<String> plugins = new LinkedList<>();

  PluginCacheEntry cache = new PluginCacheEntry();
  cache.modTime = file.lastModified();

  Enumeration<? extends ZipEntry> entries = zipFile.entries();
  Pattern languageFilePattern = Pattern.compile("lang_(\\w+).properties");

  while(entries.hasMoreElements())
  {
   ZipEntry entry = entries.nextElement();
   String name = entry.getName();
   String lname = name.toLowerCase();
   if("actions.xml".equals(lname))
   {
    cache.actionsURI = classLoader.getResource(name);
   }
   else if("browser.actions.xml".equals(lname))
   {
    cache.browserActionsURI = classLoader.getResource(name);
   }
   else if("dockables.xml".equals(lname))
   {
    dockablesURI = classLoader.getResource(name);
    cache.dockablesURI = dockablesURI;
   }
   else if("services.xml".equals(lname))
   {
    servicesURI = classLoader.getResource(name);
    cache.servicesURI = servicesURI;
   }
   else if(lname.endsWith(".props"))
   {
    InputStream in = null;
    try
    {
     in = classLoader.getResourceAsStream(name);
     properties.load(in);
    }
    finally
    {
     IOUtilities.closeQuietly((Closeable)in);
    }
   }
   else if(name.endsWith(".class"))
   {
    String className = MiscUtilities
     .fileToClass(name);
    if(className.endsWith("Plugin"))
    {
     plugins.add(className);
    }
    classes.add(className);
   }
   else
   {
    Matcher matcher = languageFilePattern.matcher(lname);
    if (matcher.matches())
    {
     String languageName = matcher.group(1);
     Properties props = new Properties();
     InputStream in = null;
     try
     {
      in = classLoader.getResourceAsStream(name);
      CharsetEncoding utf8 = new CharsetEncoding(StandardCharsets.UTF_8);
      Reader utf8in = utf8.getTextReader(in);
      props.load(utf8in);
      localizationProperties.put(languageName, props);
     }
     finally
     {
      IOUtilities.closeQuietly((Closeable)in);
     }
    }
    else
     resources.add(name);
   }
  }

  cache.cachedProperties = properties;
  cache.localizationProperties = localizationProperties;

  // this must be done before loading cachedProperties
  if (cache.localizationProperties != null)
  {
   localizationProperties = cache.localizationProperties;
   String currentLanguage = jEdit.getCurrentLanguage();
   Properties langProperties = localizationProperties.get(currentLanguage);
   if (langProperties != null)
   {
    jEdit.addPluginLocalizationProps(langProperties);
   }
  }

  jEdit.addPluginProps(properties);

  this.classes = cache.classes = classes.toArray(StandardUtilities.EMPTY_STRING_ARRAY);
  this.resources = cache.resources = resources.toArray(StandardUtilities.EMPTY_STRING_ARRAY);

  String label = null;

  for (String className : plugins)
  {
   String _label = jEdit.getProperty("plugin."
    + className + ".name");
   String version = jEdit.getProperty("plugin."
    + className + ".version");
   if(_label == null || version == null)
   {
    Log.log(Log.WARNING,this,"Ignoring: "
     + className);
   }
   else
   {
    cache.pluginClass = className;

    // Check if a plugin with the same name
    // is already loaded
    if (!continueLoading(className, cache.cachedProperties))
    {
     return null;
    }
    else
    {
     EditPlugin otherPlugin = jEdit.getPlugin(className);
     if (otherPlugin != null)
     {
      jEdit.removePluginJAR(otherPlugin.getPluginJAR(), false);
// otherPlugin.getPluginJAR().uninit(false);
     }
    }

    plugin = new EditPlugin.Deferred(this,
         className);
    label = _label;

    break;
   }
  }

  boolean isBeingLoaded = jEdit.getPluginJAR(getPath()) != null;
  if(!isBeingLoaded)
  {
   Log.log(Log.DEBUG, PluginJAR.class,
     "not loading actions, dockables, services "
     +"because the plugin is not really being loaded");
   return cache;
  }
  if(cache.actionsURI != null)
  {
   actions = new ActionSet(this,null,null,
    cache.actionsURI);
   actions.load();
   cache.cachedActionNames =
    actions.getCacheableActionNames();
   cache.cachedActionToggleFlags =
    new boolean[cache.cachedActionNames.length];
   for(int i = 0; i < cache.cachedActionNames.length; i++)
   {
     cache.cachedActionToggleFlags[i] =
      jEdit.getBooleanProperty(
       cache.cachedActionNames[i] + ".toggle");
   }
  }

  if(cache.browserActionsURI != null)
  {
   browserActions =
    new ActionSet(this,null,null, cache.browserActionsURI);
   browserActions.load();
   VFSBrowser.getActionContext().addActionSet(browserActions);
   cache.cachedBrowserActionNames =
    browserActions.getCacheableActionNames();
   cache.cachedBrowserActionToggleFlags = new boolean[
    cache.cachedBrowserActionNames.length];
   for(int i = 0;
    i < cache.cachedBrowserActionNames.length; i++)
   {
     cache.cachedBrowserActionToggleFlags[i]
      = jEdit.getBooleanProperty(
       cache.cachedBrowserActionNames[i] + ".toggle");
   }
  }

  if(dockablesURI != null)
  {
   DockableWindowFactory.getInstance()
    .loadDockableWindows(this, dockablesURI,cache);
  }

  if(actions.size() != 0)
  {
   if(label != null)
   {
    actions.setLabel(jEdit.getProperty(
     "action-set.plugin"new String[] { label }));
   }
   else
    actionsPresentButNotCoreClass();

   jEdit.addActionSet(actions);
  }

  if(servicesURI != null)
  {
   ServiceManager.loadServices(this,servicesURI,cache);
  }

  return cache;
 } //}}}

 private static boolean continueLoading(String clazz, Properties cachedProperties)
 {
  if(jEdit.getPlugin(clazz) != null)
  {
   String otherVersion = jEdit.getProperty("plugin."+clazz+".version");
   String thisVersion = cachedProperties.getProperty("plugin."+clazz+".version");
   if (otherVersion.compareTo(thisVersion) > 0)
    return false;
  }
  return true;
 }

 //{{{ startPlugin() method
 private void startPlugin()
 {
  try
  {
   plugin.start();
  }
  catch(Throwable t)
  {
   breakPlugin();

   Log.log(Log.ERROR,this"Error while starting plugin " + plugin.getClassName());
   Log.log(Log.ERROR,this,t);
   String[] args = { t.toString() };
   jEdit.pluginError(path, "plugin-error.start-error",args);
  }

  if(plugin instanceof EBPlugin ||
     plugin.getClass().getAnnotation(EBHandler.class) != null)
  {
   if(jEdit.getProperty("plugin." + plugin.getClassName()
    + ".activate") == null)
   {
    // old plugins expected jEdit 4.1-style
    // behavior, where a PropertiesChanged
    // was sent after plugins were started
    ((EBComponent)plugin).handleMessage(
     new PropertiesChanged(null));
   }
   EditBus.addToBus(plugin);
  }

  // buffers retain a reference to the fold handler in
  // question... and the easiest way to handle fold
  // handler loading is this...
  BufferManager bufferManager = jEdit.getBufferManager();
  bufferManager.getBuffers()
   .forEach(buffer ->
   {
    FoldHandler handler = FoldHandler.getFoldHandler(buffer.getStringProperty("folding"));
    // == null before loaded
    if(handler != null && handler != buffer.getFoldHandler())
     buffer.setFoldHandler(handler);
   });
 } //}}}

 //{{{ startPluginLater() method
 private void startPluginLater()
 {
  EventQueue.invokeLater(() ->
  {
   if (!activated)
    return;

   startPlugin();
  });
 } //}}}

 //{{{ breakPlugin() method
 private void breakPlugin()
 {
  plugin = new EditPlugin.Broken(this,plugin.getClassName());

  // remove action sets, dockables, etc so that user doesn't
  // see the broken plugin
  uninit(false);
  // but we want properties to hang around
  jEdit.addPluginProps(properties);
 } //}}}

 //{{{ removePluginCache() method
 private void removePluginCache()
 {
  if(cachePath != null)
   new File(cachePath).delete();
 } //}}}

 //}}}

 //{{{ PluginCacheEntry class
 /**
 * Used by the <code>DockableWindowManager</code> and
 * <code>ServiceManager</code> to handle caching.
 * @since jEdit 4.2pre1
 */

 public static class PluginCacheEntry
 {
  public static final int MAGIC = 0xB7A2E424;

  //{{{ Instance variables
  public PluginJAR plugin;
  public long modTime;

  public String[] classes;
  public String[] resources;
  public URL actionsURI;
  public String[] cachedActionNames;
  public boolean[] cachedActionToggleFlags;
  public URL browserActionsURI;
  public String[] cachedBrowserActionNames;
  public boolean[] cachedBrowserActionToggleFlags;
  public URL dockablesURI;
  public String[] cachedDockableNames;
  public boolean[] cachedDockableActionFlags;
  public boolean[] cachedDockableMovableFlags;
  public URL servicesURI;
  ServiceManager.Descriptor[] cachedServices;

  public Properties cachedProperties;
  public Map<String, Properties> localizationProperties;
  public String pluginClass;
  //}}}

  /* read() and write() must be kept perfectly in sync...
 * its a very simple file format. doing it this way is
 * faster than serializing since serialization calls
 * reflection, etc. */


  //{{{ read() method
  public boolean read(DataInputStream din) throws IOException
  {
   int cacheMagic = din.readInt();
   if(cacheMagic != MAGIC)
    return false;

   String cacheBuild = readString(din);
   if(!cacheBuild.equals(jEdit.getBuild()))
    return false;

   long cacheModTime = din.readLong();
   if(cacheModTime != modTime)
    return false;

   actionsURI = readURI(din);
   cachedActionNames = readStringArray(din);
   cachedActionToggleFlags = readBooleanArray(din);

   browserActionsURI = readURI(din);
   cachedBrowserActionNames = readStringArray(din);
   cachedBrowserActionToggleFlags = readBooleanArray(din);

   dockablesURI = readURI(din);
   cachedDockableNames = readStringArray(din);
   cachedDockableActionFlags = readBooleanArray(din);
   cachedDockableMovableFlags = readBooleanArray(din);

   servicesURI = readURI(din);
   int len = din.readInt();
   if(len == 0)
    cachedServices = null;
   else
   {
    cachedServices = new ServiceManager.Descriptor[len];
    for(int i = 0; i < len; i++)
    {
     ServiceManager.Descriptor d = new
      ServiceManager.Descriptor(
      readString(din),
      readString(din),
      null,
      plugin);
     cachedServices[i] = d;
    }
   }

   classes = readStringArray(din);
   resources = readStringArray(din);

   cachedProperties = readMap(din);
   localizationProperties = readLanguagesMap(din);

   pluginClass = readString(din);

   return true;
  } //}}}

  //{{{ write() method
  public void write(DataOutputStream dout) throws IOException
  {
   dout.writeInt(MAGIC);
   writeString(dout,jEdit.getBuild());

   dout.writeLong(modTime);

   writeString(dout,actionsURI);
   writeStringArray(dout,cachedActionNames);
   writeBooleanArray(dout,cachedActionToggleFlags);

   writeString(dout,browserActionsURI);
   writeStringArray(dout,cachedBrowserActionNames);
   writeBooleanArray(dout,cachedBrowserActionToggleFlags);

   writeString(dout,dockablesURI);
   writeStringArray(dout,cachedDockableNames);
   writeBooleanArray(dout,cachedDockableActionFlags);
   writeBooleanArray(dout,cachedDockableMovableFlags);

   writeString(dout,servicesURI);
   if(cachedServices == null)
    dout.writeInt(0);
   else
   {
    dout.writeInt(cachedServices.length);
    for (ServiceManager.Descriptor cachedService : cachedServices)
    {
     writeString(dout, cachedService.clazz);
     writeString(dout, cachedService.name);
    }
   }

   writeStringArray(dout,classes);
   writeStringArray(dout,resources);

   writeMap(dout,cachedProperties);
   writeLanguages(dout, localizationProperties);

   writeString(dout,pluginClass);
  } //}}}

  //{{{ Private members

  //{{{ readString() method
  private static String readString(DataInputStream din)
   throws IOException
  {
   int len = din.readInt();
   if(len == 0)
    return null;
   char[] str = new char[len];
   for(int i = 0; i < len; i++)
    str[i] = din.readChar();
   return new String(str);
  } //}}}

  //{{{ readURI() method
  private static URL readURI(DataInputStream din)
   throws IOException
  {
   String str = readString(din);
   if(str == null)
    return null;
   else
    return new URL(str);
  } //}}}

  //{{{ readStringArray() method
  private static String[] readStringArray(DataInputStream din)
   throws IOException
  {
   int len = din.readInt();
   if(len == 0)
    return null;
   String[] str = new String[len];
   for(int i = 0; i < len; i++)
   {
    str[i] = readString(din);
   }
   return str;
  } //}}}

  //{{{ readBooleanArray() method
  private static boolean[] readBooleanArray(DataInputStream din)
   throws IOException
  {
   int len = din.readInt();
   if(len == 0)
    return null;
   boolean[] bools = new boolean[len];
   for(int i = 0; i < len; i++)
   {
    bools[i] = din.readBoolean();
   }
   return bools;
  } //}}}

  //{{{ readMap() method
  private static Properties readMap(DataInputStream din)
   throws IOException
  {
   Properties returnValue = new Properties();
   int count = din.readInt();
   for(int i = 0; i < count; i++)
   {
    String key = readString(din);
    String value = readString(din);
    if(value == null)
     value = "";
    returnValue.setProperty(key, value);
   }
   return returnValue;
  } //}}}

  //{{{ readLanguagesMap() method
  private static Map<String, Properties> readLanguagesMap(DataInputStream din)
   throws IOException
  {
   int languagesCount = din.readInt();
   if (languagesCount == 0)
    return Collections.emptyMap();


   Map<String, Properties> languages = new HashMap<>(languagesCount);
   for (int i = 0;i<languagesCount;i++)
   {
    String lang = readString(din);
    Properties props = readMap(din);
    languages.put(lang, props);
   }

   return languages;
  } //}}}

  //{{{ writeString() method
  private static void writeString(DataOutputStream dout,
   Object obj) throws IOException
  {
   if(obj == null)
   {
    dout.writeInt(0);
   }
   else
   {
    String str = obj.toString();
    dout.writeInt(str.length());
    dout.writeChars(str);
   }
  } //}}}

  //{{{ writeStringArray() method
  private static void writeStringArray(DataOutputStream dout,
   String[] str) throws IOException
  {
   if(str == null)
   {
    dout.writeInt(0);
   }
   else
   {
    dout.writeInt(str.length);
    for (String s : str)
     writeString(dout, s);
   }
  } //}}}

  //{{{ writeBooleanArray() method
  private static void writeBooleanArray(DataOutputStream dout,
   boolean[] bools) throws IOException
  {
   if(bools == null)
   {
    dout.writeInt(0);
   }
   else
   {
    dout.writeInt(bools.length);
    for (boolean bool : bools)
     dout.writeBoolean(bool);
   }
  } //}}}

  //{{{ writeMap() method
  private static void writeMap(DataOutputStream dout, Properties properties)
   throws IOException
  {
   dout.writeInt(properties.size());
   Set<Map.Entry<Object, Object>> set = properties.entrySet();
   for (Map.Entry<Object, Object> entry : set)
   {
    writeString(dout,entry.getKey());
    writeString(dout,entry.getValue());
   }
  } //}}}

  //{{{ writeLanguages() method
  private static void writeLanguages(DataOutputStream dout, Map<String, Properties> languages)
   throws IOException
  {
   dout.writeInt(languages.size());
   for (Map.Entry<String, Properties> entry : languages.entrySet())
   {
    writeString(dout, entry.getKey());
    writeMap(dout, entry.getValue());
   }
  } //}}}

  //}}}
 } //}}}
}

¤ Dauer der Verarbeitung: 0.31 Sekunden  (vorverarbeitet)  ¤





Download des
Quellennavigators
Download des
sprechenden Kalenders

in der Quellcodebibliothek suchen




Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.


Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.


Bot Zugriff