/*
* 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 != null) return 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.56 Sekunden
(vorverarbeitet)
¤
|
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.
|