# This patch file was generated by NetBeans IDE # This patch can be applied using context Tools: Apply Diff Patch action on respective folder. # It uses platform neutral UTF-8 encoding. # Above lines and this line are ignored by the patching process. Index: core/startup/src/org/netbeans/core/startup/AutomaticDependencies.java --- core/startup/src/org/netbeans/core/startup/AutomaticDependencies.java Base (1.11) +++ core/startup/src/org/netbeans/core/startup/AutomaticDependencies.java Locally Modified (Based On 1.11) @@ -688,7 +688,7 @@ private static final class Parser implements ContentHandler, ErrorHandler, EntityResolver { - private java.lang.StringBuffer buffer; + private StringBuilder buffer; private Handler handler; @@ -696,7 +696,7 @@ public Parser(final Handler handler) { this.handler = handler; - buffer = new StringBuffer(111); + buffer = new StringBuilder(111); context = new java.util.Stack(); } Index: core/startup/src/org/netbeans/core/startup/ModuleList.java --- core/startup/src/org/netbeans/core/startup/ModuleList.java Base (1.14) +++ core/startup/src/org/netbeans/core/startup/ModuleList.java Locally Modified (Based On 1.14) @@ -22,6 +22,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.CharArrayWriter; @@ -29,19 +30,27 @@ import java.io.FileNotFoundException; 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.Writer; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.logging.Level; import org.netbeans.DuplicateException; import org.netbeans.Events; @@ -61,6 +70,7 @@ import org.openide.modules.InstalledFileLocator; import org.openide.modules.ModuleInstall; import org.openide.modules.SpecificationVersion; +import org.openide.util.Exceptions; import org.openide.util.RequestProcessor; import org.openide.util.Utilities; import org.openide.util.WeakSet; @@ -106,11 +116,49 @@ private boolean triggered = false; /** listener for changes in modules, etc.; see comment on class Listener */ private final Listener listener = new Listener(); + + /** Read some stuff from a stream and skip over it. + * Newline conventions are normalized to Unix \n. + * @return true upon success, false if stream contained something else + */ + private boolean expect(InputStream is, byte[] stuff) throws IOException { + int len = stuff.length; + boolean inNewline = false; + for (int i = 0; i < len; ) { + int c = is.read(); + if (c == 10 || c == 13) { + // Normalize: s/[\r\n]+/\n/g + if (inNewline) { + continue; + } else { + inNewline = true; + c = 10; + } + } else { + inNewline = false; + } + if (c != stuff[i++]) { + return false; + } + } + if (stuff[len - 1] == 10) { + // Expecting something ending in a \n - so we have to + // read any further \r or \n and discard. + if (!is.markSupported()) throw new IOException("Mark not supported"); // NOI18N + is.mark(1); + int c = is.read(); + if (c != -1 && c != 10 && c != 13) { + // Got some non-newline character, push it back! + is.reset(); + } + } + return true; + } /** 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)); - + private static final String CACHE_FILE_NAME = "modules.cache"; /** Create the list manager. * @param mgr the module manager which will actually control the modules at runtime * @param folder the Modules/ folder on the system file system to scan/write @@ -123,6 +171,81 @@ Util.err.fine("ModuleList created, storage in " + folder); } + private boolean errorsLoadingModules = false; + private FileObject getCacheFile(boolean create) { + FileObject result = folder.getFileObject(CACHE_FILE_NAME); + if (result == null && create) { + try { + result = folder.createData(CACHE_FILE_NAME); + } catch (IOException ex) { + throw new IllegalStateException (ex); + } + } + return result; + } + + private boolean isCacheUpToDate() { + FileObject cacheFile = getCacheFile(false); + if (cacheFile == null) { + return false; + } + long date = cacheFile.lastModified().getTime(); + boolean result = true; + for (FileObject fo : folder.getChildren()) { + result &= (fo.lastModified().getTime() <= date); + if (!result) { + break; + } + } + return result; + } + + public void deleteCacheFile() { + FileObject ob = getCacheFile(false); + if (ob != null) { + try { + System.err.println("Deleting module xml cache"); + ob.delete(); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + } + + private void writeCacheFile(Collection > data) throws IOException { + if (errorsLoadingModules) { + //If some module failed to load, we do not want to write a cache + //containing partial data! Otherwise it will silently never be + //loaded for eternity. + deleteCacheFile(); + return; + } + FileObject ob = getCacheFile(true); + + FileLock lock = ob.lock(); + ObjectOutputStream str = new ObjectOutputStream (new BufferedOutputStream(ob.getOutputStream(lock))); + try { + str.writeObject(data); + } finally { + str.close(); + lock.releaseLock(); + } + } + + private Collection > loadCacheData() { + FileObject obj = getCacheFile(false); + if (obj != null) { + try { + ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(obj.getInputStream())); + return (Collection>) in.readObject(); + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + return null; + } + } + return null; + } + /** Read an initial list of modules from disk according to their stored settings. * Just reads the XML files in the Modules/ directory, and adds those to * the manager's list of modules. Errors are handled internally. @@ -131,8 +254,63 @@ * enable as needed. All discovered modules are returned. * Write mutex only. */ - public Set readInitial() { + public Set readInitial() { ev.log(Events.START_READ); + if (isCacheUpToDate()) { + return readFromCache(); + } else { + return readFromXml(); + } + } + + private Set readFromCache() { + Set read = new HashSet(); + Collection > data = loadCacheData(); + final boolean[] en = new boolean[1]; + ev.log( Events.MODULES_FILE_SCANNED, data.size() ); +outer: for (Map props : data) { + for (Map.Entry e : props.entrySet()) { + String name = (String) props.get("name"); + String nameDashes = name.replace ('.', '-'); + FileObject file = folder.getFileObject (nameDashes + ".xml"); + assert file != null; + Module m = null; + try { + m = buildModule (props, name, nameDashes, file, en); + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + //Cache data probably bad + errorsLoadingModules = true; + deleteCacheFile(); + continue outer; + } + if (m == null) { + continue outer; + } + boolean enabled = en[0]; + + read.add(m); + DiskStatus status = new DiskStatus(); + status.module = m; + status.file = file; + //status.lastApprovedChange = children[i].lastModified().getTime(); + status.pendingInstall = enabled; + // Will only really be flushed if mgr props != disk props, i.e + // if version changed or could not be enabled. + //status.pendingFlush = true; + status.diskProps = props; + statuses.put(name, status); + ev.log( Events.MODULES_FILE_PROCESSED, file ); + } + } + // Handle changes in the Modules/ folder on disk by parsing & applying them. + folder.addFileChangeListener(FileUtil.weakFileChangeListener (listener, + folder)); + ev.log(Events.FINISH_READ, read); + return read; + } + + private Set readFromXml() { final Set read = new HashSet(); try { folder.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() { @@ -141,7 +319,8 @@ ev.log( Events.MODULES_FILE_SCANNED, children.length ); XMLReader reader = null; - + List > toCache = new ArrayList > (children.length); + final boolean[] en = new boolean[1]; for (int i = 0; i < children.length; i++) { if (children[i].hasExt("ser")) { // NOI18N // Fine, skip over. @@ -149,13 +328,22 @@ // Assume this is one of ours. Note fixed naming scheme. try { String nameDashes = children[i].getName(); // NOI18N - char[] badChars = {'.', '/', '>', '='}; - for (int j = 0; j < 4; j++) { - if (nameDashes.indexOf(badChars[j]) != -1) { - throw new IllegalArgumentException("Bad name: " + nameDashes); // NOI18N + char[] chars = nameDashes.toCharArray(); + for (int j=0; j < chars.length; j++) { + switch (chars[j]) { + case '.': + case '/' : + case '>' : + case '=' : + throw new IllegalArgumentException("Bad " + + "name: " + nameDashes); // NOI18N + case '-' : + chars[j] = '.'; + default: + continue; } } - String name = nameDashes.replace('-', '.').intern(); // NOI18N + String name = new String(chars).intern(); // Now name is the code name base of the module we expect to find. // Check its format (throws IllegalArgumentException if bad): Dependency.create(Dependency.TYPE_MODULE, name); @@ -186,7 +374,54 @@ } finally { is.close(); } + toCache.add (props); if (! name.equals(props.get("name"))) throw new IOException("Code name mismatch: " /* #25011 */ + name + " vs. " + props.get("name")); // NOI18N + Module m = buildModule (props, name, nameDashes, children[i], en); + if (m == null) { + continue; + } + boolean enabled = en[0]; + read.add(m); + DiskStatus status = new DiskStatus(); + status.module = m; + status.file = children[i]; + //status.lastApprovedChange = children[i].lastModified().getTime(); + status.pendingInstall = enabled; + // Will only really be flushed if mgr props != disk props, i.e + // if version changed or could not be enabled. + //status.pendingFlush = true; + status.diskProps = props; + statuses.put(name, status); + } catch (Exception e) { + Util.err.log(Level.WARNING, "Error encountered while reading " + children[i], e); + errorsLoadingModules = true; + } + } else { + Util.err.fine("Strange file encountered in modules folder: " + children[i]); + } + ev.log( Events.MODULES_FILE_PROCESSED, children[i] ); + } + try { + writeCacheFile(toCache); + } catch (Exception e) { + Exceptions.printStackTrace(e); + } + if (Util.err.isLoggable(Level.FINE)) { + Util.err.fine("read initial XML files: statuses=" + statuses); + } + ev.log(Events.FINISH_READ, read); + }}); + } catch (IOException ioe) { + Util.err.log(Level.WARNING, null, ioe); + } + return read; + } + + private Module buildModule (Map props, String name, String nameDashes, FileObject fob, boolean[] en) throws IOException, DuplicateException { + Module m = mgr.get(name); + if (m != null) { + return m; + } String jar = (String)props.get("jar"); // NOI18N File jarFile; try { @@ -195,11 +430,11 @@ //Util.err.fine("Cannot find: " + fnfe.getMessage()); ev.log(Events.MISSING_JAR_FILE, new File(fnfe.getMessage())); try { - children[i].delete(); + fob.delete(); } catch (IOException ioe) { Util.err.log(Level.WARNING, null, ioe); } - continue; + return null; } ModuleHistory history = new ModuleHistory(jar); // NOI18N @@ -211,6 +446,7 @@ boolean reloadable = (reloadableB != null ? reloadableB.booleanValue() : false); Boolean enabledB = (Boolean)props.get("enabled"); // NOI18N boolean enabled = (enabledB != null ? enabledB.booleanValue() : false); + en[0] = enabled; Boolean autoloadB = (Boolean)props.get("autoload"); // NOI18N boolean autoload = (autoloadB != null ? autoloadB.booleanValue() : false); Boolean eagerB = (Boolean)props.get("eager"); // NOI18N @@ -220,7 +456,7 @@ 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(children)); // NOI18N + if (installerSer == null) throw new IOException("No such install ser: " + installer); // NOI18N // Hope the stored state is not >Integer.MAX_INT! :-) byte[] buf = new byte[(int)installerSer.getSize()]; InputStream is2 = installerSer.getInputStream(); @@ -233,38 +469,11 @@ // Quasi-prop which is stored separately. props.put("installerState", buf); // NOI18N } - Module m = mgr.create(jarFile, history, reloadable, autoload, eager); - read.add(m); - DiskStatus status = new DiskStatus(); - status.module = m; - status.file = children[i]; - //status.lastApprovedChange = children[i].lastModified().getTime(); - status.pendingInstall = enabled; - // Will only really be flushed if mgr props != disk props, i.e - // if version changed or could not be enabled. - //status.pendingFlush = true; - status.diskProps = props; - statuses.put(name, status); - } catch (Exception e) { - Util.err.log(Level.WARNING, "Error encountered while reading " + children[i], e); + if (m == null) { + m = mgr.create(jarFile, history, reloadable, autoload, eager); } - } else { - Util.err.fine("Strange file encountered in modules folder: " + children[i]); + return m; } - ev.log( Events.MODULES_FILE_PROCESSED, children[i] ); - } - if (Util.err.isLoggable(Level.FINE)) { - Util.err.fine("read initial XML files: statuses=" + statuses); - } - ev.log(Events.FINISH_READ, read); - // Handle changes in the Modules/ folder on disk by parsing & applying them. - folder.addFileChangeListener(FileUtil.weakFileChangeListener (listener, folder)); - }}); - } catch (IOException ioe) { - Util.err.log(Level.WARNING, null, ioe); - } - return read; - } /** * Try to find a module JAR by an XML-supplied name. @@ -378,6 +587,77 @@ } try { mgr.enable(modules); + /* + //dangerous but potential perf improvement on multi-core machines. + //Certainly unhelpful on single cpu, but worth thinking about + //To make this work we really need to walk the dep tree to preserve + //ordering and build sets that can be enabled separately + int threadCount = 2; + final ExecutorService service = Executors.newFixedThreadPool(threadCount); + final Object lock = new Object(); + final int[] count = new int[] { threadCount }; + class R implements Callable { + private final Set myModules; + private final R next; + R (R next, Set myModules) { + this.myModules = myModules; + this.next = next; + } + + public Void call() throws Exception { + System.err.println("Enabling " + myModules + " on " + Thread.currentThread()); + mgr.enable(myModules); + synchronized (lock) { + count[0]--; + if (count[0] == 0) { + lock.notifyAll(); + } + } + return null; + } + + public void start() throws InvalidException { + if (next == null) { + try { + call(); + } catch (Exception e) { + if (e instanceof InvalidException) { + throw (InvalidException) e; + } else { + Exceptions.printStackTrace(e); + //XXX hard to debug + throw new InvalidException (""); + } + } + } else { + service.submit(this); + next.start(); + } + } + } + R prev = null; + int ix = 0; + int modulesPerThread = modules.size() / threadCount; + Set curr = new HashSet (modulesPerThread); + for (Iterator iter = modules.iterator(); iter.hasNext(); ix++) { + curr.add (iter.next()); + if ((ix % modulesPerThread) == 0 || !it.hasNext()) { + prev = new R (prev, curr); + curr = new HashSet (modulesPerThread); + } + } + if (prev != null) { + //Will cascade and run the last one in this thread + prev.start(); + } + synchronized (lock) { + while (count[0] > 0) { + lock.wait(); + } + } + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + */ } catch (InvalidException ie) { Util.err.log(Level.WARNING, null, ie); Module bad = ie.getModule(); @@ -603,7 +883,7 @@ DefaultHandler handler = new DefaultHandler() { private String modName; private String paramName; - private StringBuffer data = new StringBuffer(); + private StringBuilder data = new StringBuilder(); public void startElement(String uri, String localname, @@ -807,44 +1087,6 @@ sanityCheckStatus(m); return m; } - - /** Read some stuff from a stream and skip over it. - * Newline conventions are normalized to Unix \n. - * @return true upon success, false if stream contained something else - */ - private boolean expect(InputStream is, byte[] stuff) throws IOException { - int len = stuff.length; - boolean inNewline = false; - for (int i = 0; i < len; ) { - int c = is.read(); - if (c == 10 || c == 13) { - // Normalize: s/[\r\n]+/\n/g - if (inNewline) { - continue; - } else { - inNewline = true; - c = 10; - } - } else { - inNewline = false; - } - if (c != stuff[i++]) { - return false; - } - } - if (stuff[len - 1] == 10) { - // Expecting something ending in a \n - so we have to - // read any further \r or \n and discard. - if (!is.markSupported()) throw new IOException("Mark not supported"); // NOI18N - is.mark(1); - int c = is.read(); - if (c != -1 && c != 10 && c != 13) { - // Got some non-newline character, push it back! - is.reset(); - } - } - return true; - } /** Read a maximal string until delim is encountered (which will be removed from stream). * This impl reads only ASCII, for speed. * Newline conventions are normalized to Unix \n. Index: core/startup/src/org/netbeans/core/startup/NbEvents.java --- core/startup/src/org/netbeans/core/startup/NbEvents.java Base (1.19) +++ core/startup/src/org/netbeans/core/startup/NbEvents.java Locally Modified (Based On 1.19) @@ -138,13 +138,13 @@ Module m = (Module)args[0]; // ignore args[1]: InvalidException { - StringBuffer buf = new StringBuffer(NbBundle.getMessage(NbEvents.class, "MSG_failed_install_new_unexpected", m.getDisplayName())); + StringBuilder buf = new StringBuilder(NbBundle.getMessage(NbEvents.class, "MSG_failed_install_new_unexpected", m.getDisplayName())); NbProblemDisplayer.problemMessagesForModules(buf, Collections.singleton(m), false); logger.log(Level.INFO, buf.toString()); } { - StringBuffer buf = new StringBuffer(NbBundle.getMessage(NbEvents.class, "MSG_failed_install_new_unexpected", m.getDisplayName())); + StringBuilder buf = new StringBuilder(NbBundle.getMessage(NbEvents.class, "MSG_failed_install_new_unexpected", m.getDisplayName())); NbProblemDisplayer.problemMessagesForModules(buf, Collections.singleton(m), true); notify(buf.toString(), true); } @@ -201,7 +201,7 @@ if (! Boolean.getBoolean("netbeans.moduleitem.dontverifyclassloader") && Util.err.isLoggable(Level.WARNING)) { // NOI18N Class clazz = (Class)args[1]; // Message for developers, no need for I18N. - StringBuffer b = new StringBuffer(); + StringBuilder b = new StringBuilder(); b.append("The module " + ((Module)args[0]).getDisplayName() + " loaded the class " + clazz.getName() + "\n"); // NOI18N b.append("from the wrong classloader. The expected classloader was " + args[2] + "\n"); // NOI18N b.append("whereas it was actually loaded from " + clazz.getClassLoader() + "\n"); // NOI18N Index: core/startup/src/org/netbeans/core/startup/NbInstaller.java --- core/startup/src/org/netbeans/core/startup/NbInstaller.java Base (1.43) +++ core/startup/src/org/netbeans/core/startup/NbInstaller.java Locally Modified (Based On 1.43) @@ -1047,13 +1047,13 @@ for (String[] cpjar : CLASSPATH_JARS) { if (kosher != null && name.startsWith(cpjar[0])) { // Restricted JAR. - StringBuffer entry = null; // will be set if there are any packages + StringBuilder entry = null; // will be set if there are any packages for (int k = 1; k < cpjar.length; k++) { String pkg = cpjar[k]; if (kosher.contains(pkg)) { // Module is permitted to use this package. if (entry == null) { - entry = new StringBuffer(100); + entry = new StringBuilder(100); entry.append(cpEntry.getAbsolutePath()); entry.append('['); // NOI18N } else { @@ -1112,7 +1112,7 @@ return; } // Only certain packages are exported, so list them explicitly. - StringBuffer b = new StringBuffer(100); + StringBuilder b = new StringBuilder(100); b.append('['); // NOI18N for (int i = 0; i < exports.length; i++) { if (i > 0) { Index: core/startup/src/org/netbeans/core/startup/Splash.java --- core/startup/src/org/netbeans/core/startup/Splash.java Base (1.18) +++ core/startup/src/org/netbeans/core/startup/Splash.java Locally Modified (Based On 1.18) @@ -123,11 +123,11 @@ int height = Integer.parseInt(NbBundle.getMessage(Splash.class, "SPLASH_HEIGHT")); frame.setPreferredSize(new Dimension(width, height)); - SwingUtilities.invokeLater (new SplashRunner(frame, true)); + EventQueue.invokeLater (new SplashRunner(frame, true)); } } else { - SwingUtilities.invokeLater (new SplashRunner(frame, false)); + EventQueue.invokeLater (new SplashRunner(frame, false)); } } Index: core/startup/src/org/netbeans/core/startup/TopLogging.java --- core/startup/src/org/netbeans/core/startup/TopLogging.java Base (1.26) +++ core/startup/src/org/netbeans/core/startup/TopLogging.java Locally Modified (Based On 1.26) @@ -216,7 +216,7 @@ private static String createBootClassPath() { // boot String boot = System.getProperty("sun.boot.class.path"); // NOI18N - StringBuffer sb = (boot != null ? new StringBuffer(boot) : new StringBuffer()); + StringBuilder sb = (boot != null ? new StringBuilder(boot) : new StringBuilder()); // std extensions findBootJars(System.getProperty("java.ext.dirs"), sb); @@ -228,7 +228,7 @@ * @param extensions null or path list * @param sb buffer to put results to */ - private static void findBootJars(final String extensions, final StringBuffer sb) { + private static void findBootJars(final String extensions, final StringBuilder sb) { if (extensions != null) { for (StringTokenizer st = new StringTokenizer(extensions, File.pathSeparator); st.hasMoreTokens();) { File dir = new File(st.nextToken()); @@ -605,7 +605,7 @@ */ private static final class LgStream extends PrintStream implements Runnable { private Logger log; - private StringBuffer sb = new StringBuffer(); + private StringBuilder sb = new StringBuilder(); private static RequestProcessor RP = new RequestProcessor("StdErr Flush"); private RequestProcessor.Task flush = RP.create(this, true);