--- a/autoupdate.services/src/org/netbeans/modules/autoupdate/services/InstallManager.java +++ a/autoupdate.services/src/org/netbeans/modules/autoupdate/services/InstallManager.java @@ -48,7 +48,9 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.logging.Level; @@ -63,12 +65,13 @@ import org.openide.filesystems.FileUtil; import org.openide.modules.InstalledFileLocator; import org.openide.util.Lookup; +import org.openide.util.lookup.ServiceProvider; /** * * @author Jiri Rechtacek */ -@org.openide.util.lookup.ServiceProvider(service=org.openide.modules.InstalledFileLocator.class) +@ServiceProvider(service=InstalledFileLocator.class) public class InstallManager extends InstalledFileLocator{ // special directories in NB files layout @@ -324,6 +327,18 @@ } public File locate(String relativePath, String codeNameBase, boolean localized) { + // Rarely returns anything so don't bother optimizing. + Set files = locateAll(relativePath, codeNameBase, localized); + return files.isEmpty() ? null : files.iterator().next(); + } + + public @Override Set locateAll(String relativePath, String codeNameBase, boolean localized) { + synchronized (InstallManager.class) { + if (clusters.isEmpty()) { + return Collections.emptySet(); + } + } + // XXX #28729: use codeNameBase to search only in the appropriate places if (relativePath.length() == 0) { throw new IllegalArgumentException("Cannot look up \"\" in InstalledFileLocator.locate"); // NOI18N } @@ -355,32 +370,31 @@ ext = name.substring(i); } String[] suffixes = org.netbeans.Util.getLocalizingSuffixesFast(); + Set files = new HashSet(); for (int j = 0; j < suffixes.length; j++) { String locName = baseName + suffixes[j] + ext; - File f = locateExactPath(prefix, locName); - if (f != null) { - return f; - } + files.addAll(locateExactPath(prefix, locName)); } - return null; + return files; } else { return locateExactPath(prefix, name); } } - + /** Search all top dirs for a file. */ - private static File locateExactPath(String prefix, String name) { + private static Set locateExactPath(String prefix, String name) { + Set files = new HashSet(); synchronized(InstallManager.class) { File[] dirs = clusters.toArray(new File[clusters.size()]); for (int i = 0; i < dirs.length; i++) { File f = makeFile(dirs[i], prefix, name); if (f.exists()) { - return f; + files.add(f); } } } - return null; + return files; } private static File makeFile(File dir, String prefix, String name) { --- a/core.startup/src/org/netbeans/core/startup/InstalledFileLocatorImpl.java +++ a/core.startup/src/org/netbeans/core/startup/InstalledFileLocatorImpl.java @@ -44,8 +44,10 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -53,6 +55,7 @@ import org.netbeans.Util; import org.openide.filesystems.FileUtil; import org.openide.modules.InstalledFileLocator; +import org.openide.util.lookup.ServiceProvider; /** * Ability to locate NBM-installed files. @@ -60,14 +63,11 @@ * and finally ${netbeans.home}. * @author Jesse Glick */ -@org.openide.util.lookup.ServiceProvider(service=org.openide.modules.InstalledFileLocator.class) +@ServiceProvider(service=InstalledFileLocator.class) public final class InstalledFileLocatorImpl extends InstalledFileLocator { - /** Default constructor for lookup. */ - public InstalledFileLocatorImpl() {} - - private static final File[] dirs; - static { + private final File[] dirs; + public InstalledFileLocatorImpl() { List _dirs = new ArrayList(); addDir(_dirs, System.getProperty("netbeans.user")); String nbdirs = System.getProperty("netbeans.dirs"); // #27151 @@ -123,30 +123,21 @@ } /** - * Currently just searches user dir and install dir(s). - * @see "#28729 for a suggested better impl in AU" + * Searches user dir and install dir(s). */ public File locate(String relativePath, String codeNameBase, boolean localized) { - if (relativePath.length() == 0) { - throw new IllegalArgumentException("Cannot look up \"\" in InstalledFileLocator.locate"); // NOI18N - } - if (relativePath.charAt(0) == '/') { - throw new IllegalArgumentException("Paths passed to InstalledFileLocator.locate should not start with '/': " + relativePath); // NOI18N - } - int slashIdx = relativePath.lastIndexOf('/'); - if (slashIdx == relativePath.length() - 1) { - throw new IllegalArgumentException("Paths passed to InstalledFileLocator.locate should not end in '/': " + relativePath); // NOI18N - } - - String prefix, name; - if (slashIdx != -1) { - prefix = relativePath.substring(0, slashIdx + 1); - name = relativePath.substring(slashIdx + 1); - assert name.length() > 0; - } else { - prefix = ""; - name = relativePath; - } + Set files = doLocate(relativePath, localized, true); + return files.isEmpty() ? null : files.iterator().next(); + } + + public @Override Set locateAll(String relativePath, String codeNameBase, boolean localized) { + return doLocate(relativePath, localized, false); + } + + private Set doLocate(String relativePath, boolean localized, boolean single) { + String[] prefixAndName = prefixAndName(relativePath); + String prefix = prefixAndName[0]; + String name = prefixAndName[1]; synchronized (InstalledFileLocatorImpl.class) { if (localized) { int i = name.lastIndexOf('.'); @@ -158,63 +149,116 @@ baseName = name.substring(0, i); ext = name.substring(i); } - String[] suffixes = org.netbeans.Util.getLocalizingSuffixesFast(); - for (int j = 0; j < suffixes.length; j++) { - String locName = baseName + suffixes[j] + ext; - File f = locateExactPath(prefix, locName); - if (f != null) { - return f; + Set files = null; + for (String suffix : org.netbeans.Util.getLocalizingSuffixesFast()) { + String locName = baseName + suffix + ext; + Set f = locateExactPath(prefix, locName, single); + if (!f.isEmpty()) { + if (single) { + return f; + } else if (files == null) { + files = f; + } else { + files = new LinkedHashSet(files); + files.addAll(f); + } } } - return null; + return files != null ? files : Collections.emptySet(); } else { - return locateExactPath(prefix, name); + return locateExactPath(prefix, name, single); } } } - + /** Search all top dirs for a file. */ - private static File locateExactPath(String prefix, String name) { + private Set locateExactPath(String prefix, String name, boolean single) { assert Thread.holdsLock(InstalledFileLocatorImpl.class); + Set files = null; if (fileCache != null) { - Map> fileCachePerPrefix = fileCache.get(prefix); - if (fileCachePerPrefix == null) { - fileCachePerPrefix = new HashMap>(dirs.length * 2); - for (int i = 0; i < dirs.length; i++) { - File root = dirs[i]; - File d; - if (prefix.length() > 0) { - assert prefix.charAt(prefix.length() - 1) == '/'; - d = new File(root, prefix.substring(0, prefix.length() - 1).replace('/', File.separatorChar)); - } else { - d = root; - } - if (d.isDirectory()) { - String[] kids = d.list(); - if (kids != null) { - fileCachePerPrefix.put(root, new HashSet(Arrays.asList(kids))); - } else { - Util.err.warning("could not read files in " + d); - } - } - } - fileCache.put(prefix, fileCachePerPrefix); - } + Map> fileCachePerPrefix = fileCachePerPrefix(prefix); for (int i = 0; i < dirs.length; i++) { Set names = fileCachePerPrefix.get(dirs[i]); if (names != null && names.contains(name)) { - return makeFile(dirs[i], prefix, name); + File f = makeFile(dirs[i], prefix, name); + if (single) { + return Collections.singleton(f); + } else if (files == null) { + files = Collections.singleton(f); + } else { + files = new LinkedHashSet(files); + files.add(f); + } } } } else { for (int i = 0; i < dirs.length; i++) { File f = makeFile(dirs[i], prefix, name); if (f.exists()) { - return f; + if (single) { + return Collections.singleton(f); + } else if (files == null) { + files = Collections.singleton(f); + } else { + files = new LinkedHashSet(files); + files.add(f); + } } } } - return null; + return files != null ? files : Collections.emptySet(); + } + + private static String[] prefixAndName(String relativePath) { + if (relativePath.length() == 0) { + throw new IllegalArgumentException("Cannot look up \"\" in InstalledFileLocator.locate"); // NOI18N + } + if (relativePath.charAt(0) == '/') { + throw new IllegalArgumentException("Paths passed to InstalledFileLocator.locate should not start with '/': " + relativePath); // NOI18N + } + int slashIdx = relativePath.lastIndexOf('/'); + if (slashIdx == relativePath.length() - 1) { + throw new IllegalArgumentException("Paths passed to InstalledFileLocator.locate should not end in '/': " + relativePath); // NOI18N + } + + String prefix, name; + if (slashIdx != -1) { + prefix = relativePath.substring(0, slashIdx + 1); + name = relativePath.substring(slashIdx + 1); + assert name.length() > 0; + } else { + prefix = ""; + name = relativePath; + } + return new String[] {prefix, name}; + } + + private Map> fileCachePerPrefix(String prefix) { + assert Thread.holdsLock(InstalledFileLocatorImpl.class); + Map> fileCachePerPrefix = fileCache.get(prefix); + if (fileCachePerPrefix == null) { + fileCachePerPrefix = new HashMap>(dirs.length * 2); + for (int i = 0; i < dirs.length; i++) { + File root = dirs[i]; + File d; + if (prefix.length() > 0) { + assert prefix.charAt(prefix.length() - 1) == '/'; + d = new File(root, prefix.substring(0, prefix.length() - 1).replace('/', File.separatorChar)); + } else { + d = root; + } + if (d.isDirectory()) { + String[] kids = d.list(); + if (kids != null) { + fileCachePerPrefix.put(root, new HashSet(Arrays.asList(kids))); + } else { + Util.err.warning("could not read files in " + d); + } + } + } + fileCache.put(prefix, fileCachePerPrefix); + } + return fileCachePerPrefix; } private static File makeFile(File dir, String prefix, String name) { --- a/core.startup/src/org/netbeans/core/startup/ModuleHistory.java +++ a/core.startup/src/org/netbeans/core/startup/ModuleHistory.java @@ -47,26 +47,13 @@ // and ModuleInfo-related things). Should be possible to use without // the rest of core. -import org.openide.modules.SpecificationVersion; - /** Representation of the history of the module. - * This includes information such as: whether the module - * has been installed before and now just needs to be restored - * (or in fact where it was installed); the previous version - * of the module, in case it needs to be upgraded. - * Used for communication - * between the module lister and the module installer. * @author Jesse Glick */ - +// XXX no longer useful, could probably delete, and deprecate Module.history public final class ModuleHistory { private final String jar; - private int oldMajorVers; - private SpecificationVersion oldSpecVers; - private boolean upgraded; - private byte[] installerState; - private boolean installerStateChanged = false; /** Create a module history with essential information. * You also need to specify a relative or absolute JAR name. @@ -74,10 +61,6 @@ public ModuleHistory(String jar) { assert jar != null; this.jar = jar; - upgraded = false; - oldMajorVers = -1; - oldSpecVers = null; - installerState = null; } /** @@ -88,66 +71,4 @@ return jar; } - /** True if this module has been installed before. */ - boolean isPreviouslyInstalled() { - return upgraded; - } - - /** The old major version of the module, - * before an upgrade. - * -1 if unspecified, or it has never been installed before. - */ - int getOldMajorVersion() { - return oldMajorVers; - } - - /** The old specification version of the module, - * before an upgrade. - * null if unspecified, or it has never been installed before. - */ - SpecificationVersion getOldSpecificationVersion() { - return oldSpecVers; - } - - /** Signal that a module has been previously installed, - * marking it as a possible candidate for upgrade. - */ - void upgrade(int oldMajorVersion, SpecificationVersion oldSpecificationVersion) { - upgraded = true; - oldMajorVers = oldMajorVersion; - oldSpecVers = oldSpecificationVersion; - } - - /** Get the stored state of the ModuleInstall, if any. - * Currently this would be a serialized bytestream. - * null if unknown or there was no stored state. - */ - byte[] getInstallerState() { - return installerState; - } - - /** Set the stored state of the ModuleInstall. - * This may be null to indicate that no state - * needs to be stored. Otherwise it would currently - * be a serialized bytestream. - */ - void setInstallerState(byte[] state) { - if (installerState != null && state != null) { - installerStateChanged = true; - } - installerState = state; - } - - /** True if the state of the installer has changed dynamically. */ - boolean getInstallerStateChanged() { - return installerStateChanged; - } - - /** Reset history after an uninstall. */ - void resetHistory() { - upgraded = false; - installerState = null; - installerStateChanged = false; - } - } --- a/core.startup/src/org/netbeans/core/startup/ModuleList.java +++ a/core.startup/src/org/netbeans/core/startup/ModuleList.java @@ -45,7 +45,6 @@ import java.beans.PropertyChangeListener; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.CharArrayWriter; import java.io.DataOutputStream; import java.io.File; @@ -53,14 +52,12 @@ import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; -import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PushbackInputStream; import java.io.Writer; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -69,6 +66,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.jar.JarFile; import java.util.logging.Level; import java.util.logging.Logger; import org.netbeans.DuplicateException; @@ -89,14 +87,11 @@ import org.openide.filesystems.FileUtil; import org.openide.modules.Dependency; import org.openide.modules.InstalledFileLocator; -import org.openide.modules.ModuleInstall; import org.openide.modules.SpecificationVersion; import org.openide.util.Parameters; import org.openide.util.RequestProcessor; import org.openide.util.Utilities; import org.openide.util.WeakSet; -import org.openide.util.io.NbObjectInputStream; -import org.openide.util.io.NbObjectOutputStream; import org.openide.xml.EntityCatalog; import org.openide.xml.XMLUtil; import org.xml.sax.Attributes; @@ -139,8 +134,6 @@ private boolean triggered = false; /** listener for changes in modules, etc.; see comment on class Listener */ private final Listener listener = new Listener(); - /** any module install sers from externalizedModules.ser, from class name to data */ - private final Map compatibilitySers = new HashMap(100); /** atomic actions I have used to change Modules/*.xml */ private final Set myAtomicActions = Collections.synchronizedSet(new WeakSet(100)); @@ -189,11 +182,43 @@ if (!f.isFile()) throw new FileNotFoundException(f.getAbsolutePath()); return f; } else { - f = InstalledFileLocator.getDefault().locate(jar, name, false); - if (f != null) { - return f; + Set jars = InstalledFileLocator.getDefault().locateAll(jar, name, false); + if (jars.isEmpty()) { + throw new FileNotFoundException(jar); + } else if (jars.size() == 1) { + return jars.iterator().next(); } else { - throw new FileNotFoundException(jar); + // Pick the newest one available. + int major = -1; + SpecificationVersion spec = null; + File newest = null; + for (File candidate : jars) { + int candidateMajor = -1; + SpecificationVersion candidateSpec = null; + JarFile jf = new JarFile(candidate); + try { + java.util.jar.Attributes attr = jf.getManifest().getMainAttributes(); + String codename = attr.getValue("OpenIDE-Module"); + if (codename != null) { + int slash = codename.lastIndexOf('/'); + if (slash != -1) { + candidateMajor = Integer.parseInt(codename.substring(slash + 1)); + } + } + String sv = attr.getValue("OpenIDE-Module-Specification-Version"); + if (sv != null) { + candidateSpec = new SpecificationVersion(sv); + } + } finally { + jf.close(); + } + if (newest == null || candidateMajor > major || (spec != null && candidateSpec != null && candidateSpec.compareTo(spec) > 0)) { + newest = candidate; + major = candidateMajor; + spec = candidateSpec; + } + } + return newest; } } } @@ -301,197 +326,6 @@ ev.log(Events.PERF_END, "ModuleList.installNew"); // NOI18N } - /** Record initial condition of a module installer. - * First, if any stored state of the installer was found on disk, - * that is if it is kept in the ModuleHistory, then deserialize - * it (to the found installer). If there are deserialization errors, - * this step is simply skipped. Or, if there was no prerecorded state, - * and the ModuleInstall class overrides writeExternal or a similar - * serialization-related method, thus indicating that it wishes to - * serialize state, then this initial object is serialized to the - * history's state for comparison with the later value. If there are - * problems with this serialization, the history receives a dummy state - * (empty bytestream) to indicate that something should be saved later. - * If there is no prerecorded state and no writeExternal method, it is - * assumed that serialization is irrelevant to this installer and so the - * state is left null and nothing will be done here or in the postpare method. - * If this installer was mentioned in externalizedModules.ser, also try to - * handle the situation (determine if the state was useful or not etc.). - * Access from NbInstaller. - */ - void installPrepare(Module m, ModuleInstall inst) { - if (! (m.getHistory() instanceof ModuleHistory)) { - LOG.fine(m + " had strange history " + m.getHistory() + ", ignoring..."); - return; - } - ModuleHistory hist = (ModuleHistory)m.getHistory(); - // We might have loaded something from externalizedModules.ser before. - byte[] compatSer = compatibilitySers.get(inst.getClass().getName()); - if (compatSer != null) { - LOG.fine("Had some old-style state for " + m); - if (isReallyExternalizable(inst.getClass())) { - // OK, maybe it was not useless, let's see... - // Compare virgin state to what we had; if different, load - // the old state and record that we want to track state. - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(1000); - new NbObjectOutputStream(baos).writeObject(inst); - baos.close(); - if (Utilities.compareObjects(compatSer, baos.toByteArray())) { - LOG.fine("Old-style state for " + m + " was gratuitous"); - // leave hist.installerState null - } else { - LOG.fine("Old-style state for " + m + " was useful, loading it..."); - // Make sure it is recorded as "changed" in history by writing something - // fake now. In installPostpare, we will load the new installer state - // and call setInstallerState again, so the result will be written to disk. - hist.setInstallerState(new byte[0]); - // And also load it into the actual installer. - InputStream is = new ByteArrayInputStream(compatSer); - Object o = new NbObjectInputStream(is).readObject(); - if (o != inst) throw new ClassCastException("Stored " + o + " but expecting " + inst); // NOI18N - } - } catch (Exception e) { - LOG.log(Level.WARNING, null, e); - // Try later to continue. - hist.setInstallerState(new byte[0]); - } catch (LinkageError le) { - LOG.log(Level.WARNING, null, le); - // Try later to continue. - hist.setInstallerState(new byte[0]); - } - } else { - LOG.fine(m + " did not want to store install state"); - // leave hist.installerState null - } - } else if (hist.getInstallerState() != null) { - // We already have some state, load it now. - LOG.fine("Loading install state for " + m); - try { - InputStream is = new ByteArrayInputStream(hist.getInstallerState()); - // Note: NBOOS requires the system class loader to be in order. - // Technically we have not yet fired any changes in it. However, - // assuming that we are not in the first block of modules to be - // loaded (that is, core = bootstraps) this will work because the - // available systemClassLoader is just appended to. Better would - // probably be to use a special ObjectInputStream resolving - // specifically to the module's classloader, as this is far more - // direct and possibly more reliable. - Object o = new NbObjectInputStream(is).readObject(); - // The joys of SharedClassObject. The deserialization itself actually - // is assumed to overwrite the state of the install singleton. We - // can only confirm that we actually deserialized the same thing we - // were expecting too (it is too late to revert anything of course). - if (o != inst) throw new ClassCastException("Stored " + o + " but expecting " + inst); // NOI18N - } catch (Exception e) { - // IOException, ClassNotFoundException, and maybe unchecked stuff - LOG.log(Level.WARNING, null, e); - // Nothing else to do, hope that the install object was not corrupted - // by the failed deserialization! If it was, it cannot be saved now. - } catch (LinkageError le) { - LOG.log(Level.WARNING, null, le); - } - } else { - // Virgin installer. First we check if it really cares about serialization - // at all, because it not we do not want to waste time forcing it. - if (isReallyExternalizable(inst.getClass())) { - LOG.fine("Checking pre-install state of " + m); - LOG.warning("Warning: use of writeExternal (or writeReplace) in " + inst.getClass().getName() + " is deprecated; use normal settings instead"); - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(1000); - new NbObjectOutputStream(baos).writeObject(inst); - baos.close(); - // Keep track of the installer's state before we installed it. - // This will be compared to its state afterwards so we can - // avoid writing anything if nothing changed, thus avoid - // polluting the disk. - hist.setInstallerState(baos.toByteArray()); - } catch (Exception e) { - LOG.log(Level.WARNING, null, e); - // Remember that it is *supposed* to be serializable to something. - hist.setInstallerState(new byte[0]); - } catch (LinkageError le) { - LOG.log(Level.WARNING, null, le); - hist.setInstallerState(new byte[0]); - } - } else { - // It does not want to store anything. Leave the installer state null - // and continue. - LOG.fine(m + " did not want to store install state"); - } - } - } - /** Check if a class (extends ModuleInstall) is truly externalizable, - * e.g. overrides writeExternal. - */ - private static boolean isReallyExternalizable(Class clazz) { - Class c; - for (c = clazz; c != ModuleInstall.class && c != Object.class; c = c.getSuperclass()) { - try { - c.getDeclaredMethod("writeExternal", ObjectOutput.class); // NOI18N - // [PENDING] check that m is public, nonstatic, returns Void.TYPE and includes at most - // IOException and unchecked exceptions in its clauses, else die - // OK, it does something nontrivial. - return true; - } catch (NoSuchMethodException nsme) { - // Didn't find it at this level, continue. - } - try { - c.getDeclaredMethod("writeReplace"); // NOI18N - // [PENDING] check that m is nonstatic, returns Object, throws ObjectStreamException - // Designates a serializable replacer, this is special. - return true; - } catch (NoSuchMethodException nsme) { - // Again keep on looking. - } - } - // Hit a superclass. - if (c == Object.class) throw new IllegalArgumentException("Class " + clazz + " was not a ModuleInstall"); // NOI18N - // Hit ModuleInstall. Did not find anything. Assumed to not do anything during externalization. - return false; - } - - /** Acknowledge later conditions of a module installer. - * If the module history indicates a nonnull installer state, then we - * try to serialize the current state to a temporary buffer and compare - * to the previous state. If the serialization succeeds, and they differ, - * then the new state is recorded in the history and will later be written to disk. - * Access from NbInstaller. - */ - void installPostpare(Module m, ModuleInstall inst) { - if (! (m.getHistory() instanceof ModuleHistory)) { - LOG.fine(m + " had strange history " + m.getHistory() + ", ignoring..."); - return; - } - ModuleHistory hist = (ModuleHistory)m.getHistory(); - if (hist.getInstallerState() != null) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(1000); - new NbObjectOutputStream(baos).writeObject(inst); - baos.close(); - byte[] old = hist.getInstallerState(); - byte[] nue = baos.toByteArray(); - if (Utilities.compareObjects(old, nue)) { - // State has not changed. - LOG.fine(m + " did not change installer state (" + old.length + " bytes), not writing anything"); - } else { - // It did change. Store new version. - LOG.fine(m + " changed installer state after loading"); - hist.setInstallerState(nue); - } - } catch (Exception e) { - LOG.log(Level.WARNING, null, e); - // We could not compare, so don't bother writing out any old state. - //hist.setInstallerState(null); - } catch (LinkageError le) { - LOG.log(Level.WARNING, null, le); - } - } else { - // Nothing stored (does not writeExternal), do nothing. - LOG.fine(m + " has no saved state"); - } - } - /** Read an XML file using an XMLReader and parse into a map of properties. * One distinguished property 'name' is the code name base * and is taken from the root element. Others are taken @@ -579,16 +413,12 @@ * @return some parsed value suitable for the status map */ private Object processStatusParam(String k, String v) throws NumberFormatException { - if (k == "release") { // NOI18N - return Integer.parseInt(v); - } else if (k == "enabled" // NOI18N + if (k == "enabled" // NOI18N || k == "autoload" // NOI18N || k == "eager" // NOI18N || k == "reloadable" // NOI18N ) { return Boolean.valueOf(v); - } else if (k == "specversion") { // NOI18N - return new SpecificationVersion(v); } else { // Other properties are of type String. // Intern the smaller ones which are likely to be repeated somewhere. @@ -610,25 +440,6 @@ && ((Boolean)m.get("eager")).booleanValue() // NOI18N && m.get("enabled") != null) // NOI18N throw new IOException("Eager modules cannot specify enablement"); // NOI18N - // Compatibility: - String origin = (String)m.remove("origin"); // NOI18N - if (origin != null) { - String jar = (String)m.get("jar"); // NOI18N - String newjar; - if (origin.equals("user") || origin.equals("installation")) { // NOI18N - newjar = "modules/" + jar; // NOI18N - } else if (origin.equals("user/autoload") || origin.equals("installation/autoload")) { // NOI18N - newjar = "modules/autoload/" + jar; // NOI18N - } else if (origin.equals("user/eager") || origin.equals("installation/eager")) { // NOI18N - newjar = "modules/eager/" + jar; // NOI18N - } else if (origin.equals("adhoc")) { // NOI18N - newjar = jar; - } else { - throw new IOException("Unrecognized origin " + origin + " for " + jar); // NOI18N - } - LOG.warning("Upgrading 'jar' param from " + jar + " to " + newjar + " and removing 'origin' " + origin); - m.put("jar", newjar); // NOI18N - } } // Encoding irrelevant for these getBytes() calls: all are ASCII... @@ -874,7 +685,7 @@ for (Map.Entry entry: new TreeMap(m).entrySet()) { String name = entry.getKey(); if ( - name.equals("installerState") || name.equals("name") || // NOI18N + name.equals("name") || // NOI18N name.equals("deps") // NOI18N ) { // Skip this one, it is a pseudo-param. @@ -936,36 +747,6 @@ lock.releaseLock(); } //nue.lastApprovedChange = nue.file.lastModified().getTime(); - // Now check up on the installer ser. - byte[] data = (byte[])nue.diskProps.get("installerState"); // NOI18N - if (data != null) { - String installerName = (String)nue.diskProps.get("installer"); // NOI18N - FileObject ser = folder.getFileObject(installerName); - if (ser == null) { - // Need to make it. - int idx = installerName.lastIndexOf('.'); // NOI18N - ser = folder.createData(installerName.substring(0, idx), installerName.substring(idx + 1)); - } - // Now write it. - lock = ser.lock(); - try { - OutputStream os = ser.getOutputStream(lock); - try { - os.write(data); - } finally { - os.close(); - } - } finally { - lock.releaseLock(); - } - } else { - /* Probably not right: - // Delete any existing one. - if (ser != null) { - ser.delete(); - } - */ - } } }; myAtomicActions.add(aa); @@ -1153,23 +934,11 @@ /** Compute what properties we would want to store in XML * for this module. I.e. 'name', 'reloadable', etc. - * The special property 'installerState' may be set (only - * if the normal property 'installer' is also set) and - * will be a byte[] rather than a string, which means that - * the indicated installer state should be written out. */ private Map computeProperties(Module m) { if (m.isFixed() || ! m.isValid()) throw new IllegalArgumentException("fixed or invalid: " + m); // NOI18N Map p = new HashMap(); p.put("name", m.getCodeNameBase()); // NOI18N - int rel = m.getCodeNameRelease(); - if (rel >= 0) { - p.put("release", rel); // NOI18N - } - SpecificationVersion spec = m.getSpecificationVersion(); - if (spec != null) { - p.put("specversion", spec); // NOI18N - } if (!m.isAutoload() && !m.isEager()) { p.put("enabled", m.isEnabled()); // NOI18N } @@ -1179,10 +948,6 @@ if (m.getHistory() instanceof ModuleHistory) { ModuleHistory hist = (ModuleHistory) m.getHistory(); p.put("jar", hist.getJar()); // NOI18N - if (hist.getInstallerStateChanged()) { - p.put("installer", m.getCodeNameBase().replace('.', '-') + ".ser"); // NOI18N - p.put("installerState", hist.getInstallerState()); // NOI18N - } } return p; } @@ -1636,7 +1401,7 @@ } private void stepCheckMisc(Map> dirtyprops) { LOG.fine("ModuleList: stepCheckMisc"); - String[] toCheck = {"jar", "autoload", "eager", "release", "specversion"}; // NOI18N + String[] toCheck = {"jar", "autoload", "eager"}; // NOI18N for (Map.Entry> entry : dirtyprops.entrySet()) { String cnb = entry.getKey(); Map props = entry.getValue(); @@ -1804,10 +1569,6 @@ continue; } ModuleHistory history = new ModuleHistory(jar); // NOI18N - Integer prevReleaseI = (Integer) props.get("release"); // NOI18N - int prevRelease = prevReleaseI == null ? -1 : prevReleaseI.intValue(); - SpecificationVersion prevSpec = (SpecificationVersion) props.get("specversion"); // NOI18N - history.upgrade(prevRelease, prevSpec); Boolean reloadableB = (Boolean) props.get("reloadable"); // NOI18N boolean reloadable = reloadableB != null ? reloadableB.booleanValue() : false; boolean enabled = enabledB != null ? enabledB.booleanValue() : false; @@ -1815,29 +1576,6 @@ boolean autoload = autoloadB != null ? autoloadB.booleanValue() : false; Boolean eagerB = (Boolean) props.get("eager"); // NOI18N boolean eager = eagerB != null ? eagerB.booleanValue() : false; - String installer = (String) props.get("installer"); // NOI18N - if (installer != null) { - String nameDashes = name.replace('.', '-'); - if (!installer.equals(nameDashes + ".ser")) { - throw new IOException("Incorrect installer ser name: " + installer); // NOI18N - } - // Load from disk in mentioned file. - FileObject installerSer = folder.getFileObject(nameDashes, "ser"); // NOI18N - if (installerSer == null) { - throw new IOException("No such install ser: " + installer + "; I see only: " + Arrays.asList(folder.getChildren())); // NOI18N - } - // Hope the stored state is not >Integer.MAX_INT! :-) - byte[] buf = new byte[(int) installerSer.getSize()]; - InputStream is2 = installerSer.getInputStream(); - try { - is2.read(buf); - } finally { - is2.close(); - } - history.setInstallerState(buf); - // Quasi-prop which is stored separately. - props.put("installerState", buf); // NOI18N - } NbInstaller.register(name, props.get("deps")); // NOI18N Module m = mgr.create(jarFile, history, reloadable, autoload, eager); NbInstaller.register(null, null); --- a/core.startup/src/org/netbeans/core/startup/NbInstaller.java +++ a/core.startup/src/org/netbeans/core/startup/NbInstaller.java @@ -417,47 +417,11 @@ if (instClazz != null) { ModuleInstall inst = SharedClassObject.findObject(instClazz, true); if (load) { - if (moduleList != null) { - moduleList.installPrepare(m, inst); - } - // restore, install, or upgrade as appropriate - Object history = m.getHistory(); - if (history instanceof ModuleHistory) { - ModuleHistory h = (ModuleHistory)history; - if (h.isPreviouslyInstalled()) { - // Check whether we have changed versions. - SpecificationVersion oldSpec = h.getOldSpecificationVersion(); - SpecificationVersion nueSpec = m.getSpecificationVersion(); - if (m.getCodeNameRelease() != h.getOldMajorVersion() || - (oldSpec == null ^ nueSpec == null) || - (oldSpec != null && nueSpec != null && oldSpec.compareTo(nueSpec) != 0)) { - // Yes, different version; upgrade from the old one. - ev.log(Events.UPDATE, m); - inst.updated(h.getOldMajorVersion(), oldSpec == null ? null : oldSpec.toString()); - } else { - // Same version as before. - ev.log(Events.RESTORE, m); - inst.restored(); - } - } else { - // First time. - ev.log(Events.INSTALL, m); - inst.installed(); - } - } else { - // Probably fixed module. Just restore. - ev.log(Events.RESTORE, m); - inst.restored(); - } - if (moduleList != null) { - moduleList.installPostpare(m, inst); - } + ev.log(Events.RESTORE, m); + inst.restored(); } else { ev.log(Events.UNINSTALL, m); inst.uninstalled(); - if (m.getHistory() instanceof ModuleHistory) { - ((ModuleHistory)m.getHistory()).resetHistory(); - } } } } --- a/core.startup/test/unit/src/org/netbeans/core/startup/InstalledFileLocatorImplTest.java +++ a/core.startup/test/unit/src/org/netbeans/core/startup/InstalledFileLocatorImplTest.java @@ -45,6 +45,9 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.Locale; import org.netbeans.junit.NbTestCase; import org.openide.modules.InstalledFileLocator; @@ -149,5 +152,19 @@ assertEquals("[no cache] but look in all dirs for most specific resource first", file(nbhome, "h_ja"), ifl.locate("h", null, true)); assertEquals("[no cache] localized lookup a no-op for nonlocalized files", file(nbuser, "a/b"), ifl.locate("a/b", null, true)); } + + public void testLocateAll() throws Exception { + InstalledFileLocatorImpl.prepareCache(); + doTestLocateAll(); + InstalledFileLocatorImpl.discardCache(); + doTestLocateAll(); + } + private void doTestLocateAll() { + assertEquals(new HashSet(Arrays.asList(file(nbuser, "a/b"), file(nbhome, "a/b"))), ifl.locateAll("a/b", null, false)); + assertEquals(Collections.emptySet(), ifl.locateAll("nonexistent", null, false)); + assertEquals(Collections.emptySet(), ifl.locateAll("nonexistent", null, true)); + assertEquals(new HashSet(Arrays.asList(file(nbhome, "loc/y.html"), file(nbdir2, "loc/y_foo.html"))), ifl.locateAll("loc/y.html", null, true)); + assertEquals(new HashSet(Arrays.asList(file(nbdir1, "e/f"), file(nbhome, "e/f"))), ifl.locateAll("e/f", null, false)); + } } --- a/nbbuild/antsrc/org/netbeans/nbbuild/CreateModuleXML.java +++ a/nbbuild/antsrc/org/netbeans/nbbuild/CreateModuleXML.java @@ -47,7 +47,6 @@ import java.util.jar.*; import java.io.*; import java.util.zip.ZipEntry; -import org.apache.tools.ant.types.Mapper; /** Create XML files corresponding to the set of known modules * without actually running the IDE. @@ -206,17 +205,10 @@ } int idx = codename.lastIndexOf('/'); String codenamebase; - int rel; if (idx == -1) { codenamebase = codename; - rel = -1; } else { codenamebase = codename.substring(0, idx); - try { - rel = Integer.parseInt(codename.substring(idx + 1)); - } catch (NumberFormatException e) { - throw new BuildException("Invalid OpenIDE-Module '" + codename + "' in " + module, getLocation()); - } } File xml = new File(xmldir, codenamebase.replace('.', '-') + ".xml"); if (xml.exists()) { @@ -262,7 +254,6 @@ displayname = codename; } names.add(displayname); - String spec = attr.getValue("OpenIDE-Module-Specification-Version"); if (isHidden) { File h = new File(xml.getParentFile(), xml.getName() + "_hidden"); h.createNewFile(); @@ -282,13 +273,7 @@ pw.println(" " + isEnabled + ""); } pw.println(" " + kid.replace(File.separatorChar, '/') + ""); - if (rel != -1) { - pw.println(" " + rel + ""); - } pw.println(" false"); - if (spec != null) { - pw.println(" " + spec + ""); - } pw.println(""); pw.flush(); pw.close(); --- a/openide.modules/src/org/openide/modules/InstalledFileLocator.java +++ a/openide.modules/src/org/openide/modules/InstalledFileLocator.java @@ -43,6 +43,9 @@ import java.io.File; import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; import org.openide.util.Lookup; import org.openide.util.LookupEvent; import org.openide.util.LookupListener; @@ -70,6 +73,22 @@ return null; } + public @Override Set locateAll(String relativePath, String codeNameBase, boolean localized) { + Set result = null; + for (InstalledFileLocator ifl : getInstances()) { + Set added = ifl.locateAll(relativePath, codeNameBase, localized); + // avoid allocating extra lists, under the assumption there is only one result: + if (!added.isEmpty()) { + if (result == null) { + result = added; + } else { + result = new LinkedHashSet(result); + result.addAll(added); + } + } + } + return result != null ? result : Collections.emptySet(); + } }; private static InstalledFileLocator[] instances = null; @@ -108,9 +127,7 @@ * useful where a directory can contain many items that may be merged between e.g. * the installation and user directories. For example, the docs folder * (used e.g. for Javadoc) might contain several ZIP files in both the installation and - * user areas. There is currently no supported way to enumerate all such files. Therefore - * searching for a directory should be attempted only when there is just one module which - * is expected to provide that directory and all of its contents. The module may assume + * user areas. Use {@link #locateAll} if you need all results. The module may assume * that all contained files are in the same relative structure in the directory as in * the normal NBM-based installation; unusual locator implementations may need to create * temporary directories with matching structures to return from this method, in case the @@ -182,6 +199,20 @@ * @return the requested File, if it can be found, else null */ public abstract File locate(String relativePath, String codeNameBase, boolean localized); + + /** + * Similar to {@link #locate} but can return multiple results. + * The default implementation returns a list with zero or one elements according to {@link #locate}. + * @param relativePath a path from install root + * @param codeNameBase name of the supplying module or null + * @param localized true to perform a localized/branded search + * @return a (possibly empty) set of files + * @since XXX + */ + public Set locateAll(String relativePath, String codeNameBase, boolean localized) { + File f = locate(relativePath, codeNameBase, localized); + return f != null ? Collections.singleton(f) : Collections.emptySet(); + } /** * Get a master locator.