*** empty log message ***
diff --git a/bundles/org.eclipse.core.boot/src/org/eclipse/core/internal/boot/PlatformConfiguration.java b/bundles/org.eclipse.core.boot/src/org/eclipse/core/internal/boot/PlatformConfiguration.java index c945cb2..6423c6c 100644 --- a/bundles/org.eclipse.core.boot/src/org/eclipse/core/internal/boot/PlatformConfiguration.java +++ b/bundles/org.eclipse.core.boot/src/org/eclipse/core/internal/boot/PlatformConfiguration.java
@@ -820,11 +820,15 @@ } } + URL getPluginPath(String pluginId) { + return getPluginPath(pluginId, null); + } + // This method is currently public because it is called by InternalPlatform. // However, it is NOT intended as API // FIXME: restructure the code so that InternalBootLoader passes the // required information to InternalPlatform - public URL getPluginPath(String pluginId) { + public URL getPluginPath(String pluginId, String versionId) { // return the plugin path element for the specified plugin. This method // is used during boot processing to obtain "kernel" plugins whose // class loaders must be created prior to the plugin registry being
diff --git a/bundles/org.eclipse.core.runtime/plugin.xml b/bundles/org.eclipse.core.runtime/plugin.xml index 71fa3f5..c37a3e9 100644 --- a/bundles/org.eclipse.core.runtime/plugin.xml +++ b/bundles/org.eclipse.core.runtime/plugin.xml
@@ -2,7 +2,7 @@ <plugin name="%pluginName" id="org.eclipse.core.runtime" - version="2.0.4" + version="2.0.5" provider-name="Object Technology International, Inc."> <runtime>
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/PluginDescriptor.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/PluginDescriptor.java index 03fb0fd..ac29c94 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/PluginDescriptor.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/PluginDescriptor.java Binary files differ
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/PluginRegistry.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/PluginRegistry.java index f844a43..fcb46bd 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/PluginRegistry.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/PluginRegistry.java
@@ -21,10 +21,10 @@ // lifecycle events private static final int STARTUP = 0; private static final int SHUTDOWN = 1; - public PluginRegistry() - { + +public PluginRegistry() { super(); - } +} /** * Iterate over the plug-ins in this registry. Plug-ins are visited in dependent order. That is, * a plug-in, A, which requires another plug-in, B, is visited before its dependents (i.e., A is
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryCacheReader.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryCacheReader.java index 60342d8..c01b4db 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryCacheReader.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryCacheReader.java
@@ -8,10 +8,17 @@ import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.model.*; import org.eclipse.core.internal.plugins.*; +import org.eclipse.core.internal.runtime.InternalPlatform; import org.eclipse.core.internal.runtime.Policy; import org.eclipse.core.boot.BootLoader; +import org.eclipse.core.internal.boot.LaunchInfo; + import java.io.*; +import java.net.URL; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; public class RegistryCacheReader { @@ -90,8 +97,11 @@ public static final byte VALUE_LABEL = 43; public static final byte VERSION_LABEL = 44; + public static final byte REGISTRY_LAST_MOD_START = 56; + public static final byte REGISTRY_LAST_MOD_END = 57; + // So it's easier to add a new label ... - public static final byte LARGEST_LABEL = 55; + public static final byte LARGEST_LABEL = 57; public RegistryCacheReader(Factory factory) { super(); @@ -107,6 +117,9 @@ return (objectTable.size() - 1); } +private void debug(String msg) { + System.out.println("RegistryCacheReader: " + msg); +} public static String decipherLabel(byte labelValue) { switch (labelValue) { case REGISTRY_LABEL: @@ -201,6 +214,28 @@ return "<configElementIndex>"; case REGISTRY_INDEX_LABEL: return "<registryIndex>"; + case FRAGMENT_END_LABEL: + return "<fragmentEnd>"; + case FRAGMENT_INDEX_LABEL: + return "<fragmentIndex>"; + case FRAGMENT_LABEL: + return "<fragment>"; + case FRAGMENT_PLUGIN_LABEL: + return "<fragmentPlugin>"; + case FRAGMENT_PLUGIN_MATCH_LABEL: + return "<fragmentPluginMatch>"; + case FRAGMENT_PLUGIN_VERSION_LABEL: + return "<fragmentPluginVersion>"; + case REGISTRY_LAST_MOD_END: + return "<registryLastModifiedListEnd>"; + case REGISTRY_LAST_MOD_START: + return "<registryLastModifiedListStart>"; + case REQUIRES_OPTIONAL_LABEL: + return "<requiresOptional>"; + case SOURCE_LABEL: + return "<source>"; + case TYPE_LABEL: + return "<type>"; } return "<unknown label>"; @@ -898,12 +933,14 @@ } return requires; } -public PluginRegistryModel readPluginRegistry(DataInputStream in) { +public PluginRegistryModel readPluginRegistry(DataInputStream in, URL[] pluginPath, boolean debugFlag) { if (cacheReadProblems == null) { cacheReadProblems = new MultiStatus(Platform.PI_RUNTIME, Platform.PARSE_PROBLEM, Policy.bind("meta.registryCacheReadProblems"), null); } if (!interpretHeaderInformation(in)) { + if (debugFlag) + debug ("Cache header information out of date - ignoring cache"); return null; } PluginRegistryModel cachedRegistry = cacheFactory.createPluginRegistry(); @@ -921,6 +958,14 @@ break; } switch (inByte) { + case REGISTRY_LAST_MOD_START : + if (!readAndCheckLastModified((PluginRegistry)cachedRegistry, in, pluginPath, debugFlag)) { + // something has changed and we should discard + // the cache file + done = true; + cachedRegistry = null; + } + break; case READONLY_LABEL : if (in.readBoolean()) { setReadOnlyFlag = true; @@ -959,6 +1004,9 @@ cacheReadProblems.add(new Status(IStatus.WARNING, Platform.PI_RUNTIME, Platform.PARSE_PROBLEM, Policy.bind ("meta.regCacheIOException", decipherLabel(REGISTRY_LABEL)), ioe)); return null; } + if (cachedRegistry == null) + return null; + if (setReadOnlyFlag) { // If we are finished reading this registry, we don't need to worry // about setting the read-only flag on other objects we might wish @@ -974,4 +1022,99 @@ return cachedRegistry; } } +private String[] getPathMembers(URL path) { + String[] list = null; + String protocol = path.getProtocol(); + if (protocol.equals("file") || (InternalPlatform.inVAJ() && protocol.equals("valoader"))) { + list = (new File(path.getFile())).list(); + } else { + // XXX: attempt to read URL and see if we got html dir page + } + return list == null ? new String[0] : list; +} +private boolean readAndCheckLastModified (PluginRegistry cachedRegistry, DataInputStream in, URL[] pluginPath, boolean debugFlag){ + try { + int numEntries = in.readInt(); + for (int i = 0; i < numEntries; i++) { + String fileName = in.readUTF(); + long lastMod = in.readLong(); + InternalPlatform.addLastModifiedTime(fileName, lastMod); + } + } catch (IOException ioe) { + cacheReadProblems.add(new Status(IStatus.WARNING, Platform.PI_RUNTIME, Platform.PARSE_PROBLEM, Policy.bind ("meta.regCacheIOException", decipherLabel(REGISTRY_LABEL)), ioe)); + return false; + } + + Map onDiskModTimes = null; + if (pluginPath != null) { + onDiskModTimes = new HashMap(30); + for (int i = 0; i < pluginPath.length; i++) { + String pluginString = pluginPath[i].getFile(); + if (pluginString.endsWith("/")) { + // directory entry - search for plugins + String[] members = getPathMembers(pluginPath[i]); + for (int j = 0; j < members.length; j++) { + onDiskModTimes.put(new String(pluginString + members[j] + "/plugin.xml"), new Long(0L)); + onDiskModTimes.put(new String(pluginString + members[j] + "/fragment.xml"), new Long(0L)); + } + } else { + onDiskModTimes.put(new String(pluginString), new Long(0L)); + } + } + } + + Map regIndex = InternalPlatform.getRegIndex(); + + // Get the simple (i.e. null) cases out of the way quickly + if ((regIndex == null) && (onDiskModTimes == null)) + return true; + + if (((regIndex == null) && (onDiskModTimes != null)) || + ((regIndex != null) && (onDiskModTimes == null))) + return false; + + for (Iterator keys = regIndex.keySet().iterator(); keys.hasNext();) { + String cacheKey = (String)keys.next(); + File diskFile = new File (cacheKey); + if (!onDiskModTimes.containsKey(cacheKey)) { + // This entry exists in the cache but is no longer in + // the plugin path - discard the cache + if (debugFlag) + debug (cacheKey + " exists in cache but not part of plugin path."); + return false; + } + if (!diskFile.exists()) { + // This entry is in the cache but no longer on disk + if (debugFlag) + debug (cacheKey + " exists in cache but not on disk"); + return false; + } + long cacheLastMod = ((Long)regIndex.get(cacheKey)).longValue(); + long diskLastMod = diskFile.lastModified(); + if (diskLastMod != cacheLastMod) { + // something has changed + if (debugFlag) + debug (cacheKey + " last modified time different than on disk"); + return false; + } + onDiskModTimes.remove(cacheKey); + } + + // We've iterated through all the elements in the cache and + // they all exist with the same last modified times as before. + // Now check to see if there is anything else on disk that needs + // to be added. + for (Iterator keys = onDiskModTimes.keySet().iterator(); keys.hasNext();) { + String diskKey = (String)keys.next(); + File diskFile = new File(diskKey); + if (diskFile.exists()) { + // This one isn't in the cache + if (debugFlag) + debug (diskKey + " exists on disk but not in the cache"); + return false; + } + } + + return true; +} }
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryCacheWriter.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryCacheWriter.java index 0be6d20..2e1a9bf 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryCacheWriter.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryCacheWriter.java
@@ -6,15 +6,18 @@ */ import org.eclipse.core.boot.BootLoader; +import org.eclipse.core.internal.boot.LaunchInfo; +import org.eclipse.core.internal.runtime.InternalPlatform; import org.eclipse.core.internal.runtime.Policy; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.model.*; + import java.io.DataOutputStream; import java.io.IOException; -import java.util.ArrayList; +import java.util.*; public class RegistryCacheWriter { // See RegistryCacheReader for constants commonly used here too. @@ -592,6 +595,20 @@ // add this object to the object table first addToObjectTable(registry); writeHeaderInformation(out); + // Now write out all the last modified times + Map regIndex = InternalPlatform.getRegIndex(); + if (regIndex != null) { + writeLabel(RegistryCacheReader.REGISTRY_LAST_MOD_START, out); + out.writeInt(regIndex.keySet().size()); + for (Iterator list = regIndex.keySet().iterator(); list.hasNext();) { + String fileName = (String)list.next(); + long lastMod = ((Long)regIndex.get(fileName)).longValue(); + out.writeUTF(fileName); + out.writeLong(lastMod); + } + writeLabel(RegistryCacheReader.REGISTRY_LAST_MOD_END, out); + } + String outString = null; writeLabel(RegistryCacheReader.REGISTRY_LABEL, out);
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryLoader.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryLoader.java index 37469b2..b5f9a02 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryLoader.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryLoader.java
@@ -5,13 +5,14 @@ * All Rights Reserved. */ -import org.eclipse.core.runtime.*; -import org.eclipse.core.runtime.model.*; +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; + import org.eclipse.core.internal.runtime.InternalPlatform; import org.eclipse.core.internal.runtime.Policy; -import java.io.*; -import java.net.URL; -import java.net.MalformedURLException; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.model.*; import org.xml.sax.InputSource; import org.xml.sax.SAXParseException; @@ -42,6 +43,14 @@ } return list == null ? new String[0] : list; } +/** + * Reports an error and returns true. + */ +private boolean parseProblem(String message) { + factory.error(new Status( + IStatus.WARNING, Platform.PI_RUNTIME, Platform.PARSE_PROBLEM, message, null)); + return true; +} private PluginRegistryModel parseRegistry(URL[] pluginPath) { long startTick = System.currentTimeMillis(); PluginRegistryModel result = processManifestFiles(pluginPath); @@ -91,39 +100,54 @@ // directory entry - search for plugins String[] members = getPathMembers(location); for (int j = 0; j < members.length; j++) { + boolean found = false; try { - boolean found = processPluginPathFile(registry, new URL(location, members[j] + "/plugin.xml")); + found = processPluginPathFile(registry, new URL(location, members[j] + "/plugin.xml")); if (!found) found = processPluginPathFile(registry, new URL(location, members[j] + "/fragment.xml")); } catch (MalformedURLException e) { } if (debug) - debug("Processed - " + members[j]); + debug(found ? "Processed - " : "Processed (not found) - " + members[j]); } } else { // specific file entry - load the given file boolean found = processPluginPathFile(registry, location); if (debug) - debug("Processed - " + location); + debug(found ? "Processed - " : "Processed (not found) - " + location); } } +/** + * @return true if a file was found at the given location, and false otherwise. + */ private boolean processPluginPathFile(PluginRegistryModel registry, URL location) { PluginModel entry = processManifestFile(location); if (entry == null) return false; - + if (entry instanceof PluginDescriptorModel) { + if (entry.getId() == null || entry.getVersion() == null) { + return parseProblem(Policy.bind("parse.nullPluginIdentifier", location.toString())); + } + //skip duplicate entries + if (registry.getPlugin(entry.getId(), entry.getVersion()) != null) { + return parseProblem(Policy.bind("parse.duplicatePlugin", entry.getId())); + } + registry.addPlugin((PluginDescriptorModel) entry); + } else { + if (entry.getId() == null || entry.getVersion() == null) { + return parseProblem(Policy.bind("parse.nullFragmentIdentifier", location.toString())); + } + if (entry instanceof PluginFragmentModel) { + registry.addFragment((PluginFragmentModel) entry); + } else { + return parseProblem(Policy.bind("parse.unknownEntry", location.toString())); + } + } String url = location.toString(); url = url.substring(0, 1 + url.lastIndexOf('/')); - if (entry instanceof PluginDescriptorModel) - registry.addPlugin((PluginDescriptorModel) entry); - else - if (entry instanceof PluginFragmentModel) - registry.addFragment((PluginFragmentModel) entry); - else - // XXX log some kind of error or throw an exception here - return false; entry.setRegistry(registry); entry.setLocation(url); + InternalPlatform.addLastModifiedTime(location.getFile(), new File(location.getFile()).lastModified()); return true; } }
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java index ece3ea7..62c35f9 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java
@@ -24,6 +24,12 @@ public final class InternalPlatform { private static IAdapterManager adapterManager; private static PluginRegistry registry; + // registry index - used to store last modified times for + // registry caching + // ASSUMPTION: Only the plugin registry in 'registry' above + // will be cached + private static Map regIndex = null; + private static Set logListeners = new HashSet(5); private static Map logs = new HashMap(5); private static PlatformLogListener platformLog = null; @@ -41,7 +47,7 @@ private static String password = ""; private static boolean inDevelopmentMode = false; private static boolean splashDown = false; - private static boolean cacheRegistry = false; + private static boolean cacheRegistry = true; private static File lockFile = null; private static RandomAccessFile lockRAF = null; @@ -55,6 +61,7 @@ // execution options private static final String OPTION_DEBUG = Platform.PI_RUNTIME + "/debug"; + private static final String OPTION_DEBUG_SYSTEM_CONTEXT = Platform.PI_RUNTIME + "/debug/context"; private static final String OPTION_DEBUG_PLUGINS = Platform.PI_RUNTIME + "/registry/debug"; private static final String OPTION_DEBUG_PLUGINS_DUMP = Platform.PI_RUNTIME + "/registry/debug/dump"; @@ -64,10 +71,11 @@ private static final String PASSWORD = "-password"; private static final String DEV = "-dev"; private static final String ENDSPLASH = "-endsplash"; - private static final String REGISTRYCACHE = "-registrycache"; + private static final String NOREGISTRYCACHE = "-noregistrycache"; // debug support: set in loadOptions() public static boolean DEBUG = false; + public static boolean DEBUG_CONTEXT = false; public static boolean DEBUG_PLUGINS = false; public static String DEBUG_PLUGINS_DUMP = ""; @@ -210,7 +218,7 @@ try { PlatformConfiguration config = InternalBootLoader.getCurrentPlatformConfiguration(); - URL url = config.getPluginPath(PI_XML); + URL url = config.getPluginPath(PI_XML, XML_VERSION); if (url == null) url = new URL(BootLoader.getInstallURL(), XML_LOCATION); descriptor.setLocation(url.toExternalForm()); @@ -561,6 +569,9 @@ // can't register url handlers until after the plugin registry is loaded PlatformURLPluginHandlerFactory.startup(); activateDefaultPlugins(); + if (DEBUG_CONTEXT) + System.out.println("OS: " + BootLoader.getOS() + " WS: " + BootLoader.getWS() + + " NL: " + BootLoader.getNL() + " ARCH: " + BootLoader.getOSArch()); // can't install the log or log problems until after the platform has been initialized. platformLog = new PlatformLogListener(); addLogListener(platformLog); @@ -612,6 +623,7 @@ options.put(key, ((String) options.get(key)).trim()); } DEBUG = getBooleanOption(OPTION_DEBUG, false); + DEBUG_CONTEXT = getBooleanOption(OPTION_DEBUG_SYSTEM_CONTEXT, false); DEBUG_PLUGINS = getBooleanOption(OPTION_DEBUG_PLUGINS, false); DEBUG_PLUGINS_DUMP = getDebugOption(OPTION_DEBUG_PLUGINS_DUMP); InternalBootLoader.setupOptions(); @@ -628,13 +640,16 @@ IPath tempPath = getMetaArea().getBackupFilePathFor(path); DataInputStream input = null; registry = null; + // augment the plugin path with any additional platform entries + // (eg. user scripts) + URL[] augmentedPluginPath = getAugmentedPluginPath(pluginPath); if (path.toFile().exists() && cacheRegistry) { try { input = new DataInputStream(new BufferedInputStream(new SafeFileInputStream(path.toOSString(), tempPath.toOSString()))); try { long start = System.currentTimeMillis(); RegistryCacheReader cacheReader = new RegistryCacheReader(factory); - registry = (PluginRegistry)cacheReader.readPluginRegistry(input); + registry = (PluginRegistry)cacheReader.readPluginRegistry(input, augmentedPluginPath, DEBUG && DEBUG_PLUGINS); if (DEBUG) System.out.println("Read registry cache: " + (System.currentTimeMillis() - start) + "ms"); } finally { @@ -646,7 +661,6 @@ } } if (registry == null) { - URL[] augmentedPluginPath = getAugmentedPluginPath(pluginPath); // augment the plugin path with any additional platform entries (eg. user scripts) long start = System.currentTimeMillis(); registry = (PluginRegistry) parsePlugins(augmentedPluginPath, factory, DEBUG && DEBUG_PLUGINS); IStatus resolveStatus = registry.resolve(true, true); @@ -722,9 +736,9 @@ found = true; } - // look for the registry cache flag - if (args[i].equalsIgnoreCase(REGISTRYCACHE)) { - cacheRegistry = true; + // look for the no registry cache flag + if (args[i].equalsIgnoreCase(NOREGISTRYCACHE)) { + cacheRegistry = false; found = true; } @@ -826,4 +840,12 @@ if (keyringFile == null) keyringFile = metaArea.getLocation().append(PlatformMetaArea.F_KEYRING).toOSString(); } +public static void addLastModifiedTime (String pathKey, long lastModTime) { + if (regIndex == null) + regIndex = new HashMap(30); + regIndex.put(pathKey, new Long(lastModTime)); +} +public static Map getRegIndex() { + return regIndex; +} } \ No newline at end of file
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/messages.properties b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/messages.properties index 09e5b49..1717658 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/messages.properties +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/messages.properties
@@ -41,6 +41,10 @@ parse.unsatisfiedOptPrereq = Optional prerequisite constraint from {0} to {1} ignored. parse.prereqOptLoop = Optional prerequisite from {0} to {1} produced loop. Prerequisite ignored. parse.unknownLibraryType = Unknown library type {0} for library {1}. +parse.duplicatePlugin= Two plugins found with the same id: {0}. Ignoring duplicate. +parse.unknownEntry=Unknown element parsed by plugin registry: {0}. +parse.nullPluginIdentifier=Plugin not loaded due to missing id or version number: {0}. +parse.nullFragmentIdentifier=Fragment not loaded due to missing id or version number: {0}. ### metadata meta.exceptionParsingLog = An exception occurred while parsing the log file: {0}
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/model/PluginRegistryModel.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/model/PluginRegistryModel.java index f8fee5e..db601be 100644 --- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/model/PluginRegistryModel.java +++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/model/PluginRegistryModel.java
@@ -45,6 +45,14 @@ list[0] = fragment; fragments.put(key, list); } else { + //XXX should we check for duplicates? See javadoc comment. +// for (int i = 0; i < list.length; i++) { +// if (list[i].getVersion().equals(fragment.getVersion())) { +// //replace with new plugin +// list[i] = fragment; +// return; +// } +// } PluginFragmentModel[] newList = new PluginFragmentModel[list.length + 1]; System.arraycopy(list, 0, newList, 0, list.length); newList[list.length] = fragment; @@ -67,6 +75,14 @@ pluginList[0] = plugin; plugins.put(key, pluginList); } else { + //check for duplicates + for (int i = 0; i < pluginList.length; i++) { + if (pluginList[i].getVersion().equals(plugin.getVersion())) { + //replace with new plugin + pluginList[i] = plugin; + return; + } + } PluginDescriptorModel[] newPluginList = new PluginDescriptorModel[pluginList.length + 1]; System.arraycopy(pluginList, 0, newPluginList, 0, pluginList.length); newPluginList[pluginList.length] = plugin;