Index: core/src/org/netbeans/core/projects/ModuleLayeredFileSystem.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/projects/ModuleLayeredFileSystem.java,v retrieving revision 1.12 diff -u -r1.12 ModuleLayeredFileSystem.java --- core/src/org/netbeans/core/projects/ModuleLayeredFileSystem.java 17 Jan 2002 15:46:06 -0000 1.12 +++ core/src/org/netbeans/core/projects/ModuleLayeredFileSystem.java 22 Feb 2002 12:03:14 -0000 @@ -7,43 +7,94 @@ * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original - * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2002 Sun * Microsystems, Inc. All Rights Reserved. */ package org.netbeans.core.projects; import java.beans.*; -import java.io.File; -import java.io.IOException; -import java.net.URL; +import java.io.*; +import java.net.*; import java.util.*; +import org.xml.sax.SAXException; + +import org.openide.ErrorManager; import org.openide.TopManager; +import org.openide.filesystems.*; import org.openide.filesystems.FileSystem; -import org.openide.filesystems.MultiFileSystem; -import org.openide.filesystems.XMLFileSystem; -import org.openide.ErrorManager; + +import org.netbeans.core.perftool.StartLog; /** Layered file system serving itself as either the user or installation layer. * Holds one layer of a writable system directory, and some number * of module layers. - * @author Jesse Glick + * @author Jesse Glick, Jaroslav Tulach */ public class ModuleLayeredFileSystem extends MultiFileSystem { /** serial version UID */ private static final long serialVersionUID = 782910986724201983L; + + static final ErrorManager err = ErrorManager.getDefault().getInstance("org.netbeans.core.projects"); // NOI18N + + /** current list of URLs - r/o; or null if not yet set */ + private List urls; // List + /** cache file, or null */ + private final File cacheFile; /** Create layered filesystem based on a supplied writable layer. * @param writableLayer the writable layer to use, typically a LocalFileSystem + * @param cacheDir a directory in which to store a cache, or null for no caching */ - ModuleLayeredFileSystem (FileSystem writableLayer) { - super (new FileSystem[] { writableLayer, new XMLFileSystem () }); + ModuleLayeredFileSystem (FileSystem writableLayer, File cacheDir) throws IOException, SAXException { + this(writableLayer, false, cacheFile(cacheDir)); + } + + private ModuleLayeredFileSystem(FileSystem writableLayer, boolean ignored, File cacheFile) throws IOException, SAXException { + super(new FileSystem[] { + writableLayer, + (cacheFile != null && cacheFile.isFile()) ? + loadCache(cacheFile) : + new XMLFileSystem() + }); + this.cacheFile = cacheFile; + // Wish to permit e.g. a user-installed module to mask files from a // root-installed module, so propagate masks up this high. // SystemFileSystem leaves this off, so that the final file system // will not show them if there are some left over. setPropagateMasks (true); + + urls = null; + } + + private static XMLFileSystem loadCache(File cacheFile) throws IOException, SAXException { + StartLog.logStart("Loading " + cacheFile); + XMLFileSystem xmlfs = new XMLFileSystem(cacheFile.toURL()); + StartLog.logEnd("Loading " + cacheFile); + return xmlfs; + } + + // See #20168 for information on layer caching. + private static File cacheFile(File cacheDir) { + if (cacheDir != null && Boolean.getBoolean("netbeans.cache.layers")) { + try { + if (!cacheDir.isDirectory()) { + if (!cacheDir.mkdirs()) { + throw new IOException("Could not make dir: " + cacheDir); // NOI18N + } + } + File f = new File(cacheDir, "all-layers.xml"); // NOI18N + err.log("Using layer cache in " + f); + return f; + } catch (IOException ex) { + err.notify(ex); + } + } + // Misleading: + //err.log("Not using any layer cache"); + return null; } /** Get all layers. @@ -60,13 +111,6 @@ return getDelegates ()[0]; } - /** Get the XML layer. - * @return the XML layer - */ - final XMLFileSystem getXMLLayer () { - return (XMLFileSystem)getDelegates ()[1]; - } - /** Creates the system file system. */ public static FileSystem create (File x, File y) @@ -99,57 +143,183 @@ } /** Change the list of module layers URLs. - * @param urls the urls describing module layers to use. List (List (URL)) + * @param urls the urls describing module layers to use. List */ public void setURLs (final List urls) throws Exception { if (urls.contains(null)) throw new NullPointerException("urls=" + urls); // NOI18N + if (err.isLoggable(ErrorManager.INFORMATIONAL)) { + err.log("setURLs: " + urls); + } + if (this.urls != null && urls.equals(this.urls)) { + err.log("no-op"); + return; + } + + StartLog.logStart("setURLs"); + + final XMLFileSystem xmlfs = (XMLFileSystem)getDelegates()[1]; + + final File stampFile; + final Stamp stamp; + if (cacheFile != null) { + stampFile = new File(cacheFile.getParentFile(), "layer-stamp.txt"); // NOI18N + stamp = new Stamp(urls); + } else { + stampFile = null; + stamp = null; + } + if (cacheFile != null && cacheFile.isFile() && stampFile.isFile()) { + err.log("Stamp of new URLs: " + stamp.getHash()); + BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(stampFile), "UTF-8")); // NOI18N + try { + String line = r.readLine(); + long hash = Long.parseLong(line); + err.log("Stamp in the cache: " + hash); + if (hash == stamp.getHash()) { + err.log("Cache hit!"); + this.urls = urls; + StartLog.logEnd("setURLs"); + return; + } + } finally { + r.close(); + } + } // #17656: don't hold synch lock while firing changes, it could be dangerous... runAtomicAction(new AtomicAction() { public void run() throws IOException { synchronized (ModuleLayeredFileSystem.this) { try { - getXMLLayer().setXmlUrls((URL[])urls.toArray(new URL[urls.size()])); + if (cacheFile != null) { + err.log("Rewriting cache in " + cacheFile); + // Cf. XMLFileSystem impl. + System.setProperty("netbeans.cache.layers.filename", cacheFile.getAbsolutePath()); + if (cacheFile.isFile() && !cacheFile.delete()) { + throw new IOException("Deletion failed: " + cacheFile); // NOI18N + } + } + xmlfs.setXmlUrls((URL[])urls.toArray(new URL[urls.size()])); } catch (PropertyVetoException pve) { IOException ioe = new IOException(pve.toString()); - ErrorManager.getDefault().annotate(ioe, pve); + err.annotate(ioe, pve); throw ioe; + } finally { + if (cacheFile != null) { + System.setProperty("netbeans.cache.layers.filename", ""); + if (cacheFile.isFile()) { + // Write out new stamp too. + Writer wr = new OutputStreamWriter(new FileOutputStream(stampFile), "UTF-8"); // NOI18N + try { + wr.write(String.valueOf(stamp.getHash())); + wr.write("\nLine above is identifying hash key, do not edit!\nBelow is metadata about all-layer.xml, for debugging purposes.\n"); // NOI18N + wr.write(stamp.toString()); + } finally { + wr.close(); + } + } else { + err.log(ErrorManager.WARNING, "WARNING - XMLFileSystem did not manage to create " + cacheFile); + } + } } } } }); + this.urls = urls; firePropertyChange ("layers", null, null); // NOI18N + + StartLog.logEnd("setURLs"); } /** Adds few URLs. */ - public void addURLs (Collection urls) throws Exception { + public void addURLs(Collection urls) throws Exception { if (urls.contains(null)) throw new NullPointerException("urls=" + urls); // NOI18N - FileSystem[] delegates = getDelegates (); - XMLFileSystem fs = delegates.length < 2 ? null : (XMLFileSystem)delegates[1]; - ArrayList arr = new ArrayList (); - if (fs != null) { - arr.addAll (Arrays.asList (fs.getXmlUrls ())); - } - arr.addAll (urls); - setURLs (arr); + ArrayList arr = new ArrayList(); + if (this.urls != null) arr.addAll(this.urls); + arr.addAll(urls); + setURLs(arr); } /** Removes few URLs. - * */ - public void removeURLs (Collection urls) throws Exception { - FileSystem[] delegates = getDelegates (); - XMLFileSystem fs = delegates.length < 2 ? null : (XMLFileSystem)delegates[1]; - if (fs == null) { - // no filesystem available - return; + public void removeURLs(Collection urls) throws Exception { + if (urls.contains(null)) throw new NullPointerException("urls=" + urls); // NOI18N + ArrayList arr = new ArrayList(); + if (this.urls != null) arr.addAll(this.urls); + arr.removeAll(urls); + setURLs(arr); + } + + /** Represents a hash of a bunch of jar: URLs and the associated JAR timestamps. + */ + private static final class Stamp implements Comparator { + private final List urls; // List + private final long[] times; + private final long hash; + public Stamp(List urls) throws IOException { + this.urls = new ArrayList(urls); + Collections.sort(this.urls, this); + times = new long[this.urls.size()]; + long x = 17L; + Iterator it = this.urls.iterator(); + int i = 0; + while (it.hasNext()) { + URL u = (URL)it.next(); + String s = u.toString(); + x += 3199876987199633L; + x ^= s.hashCode(); + File extracted = findFile(s); + if (extracted != null) { + x ^= (times[i++] = extracted.lastModified()); + } else { + times[i++] = 0L; + } + } + hash = x; + } + private static File findFile(String u) throws IOException { + if (u.startsWith("jar:") && u.lastIndexOf("!/") != -1) { // NOI18N + u = u.substring(4, u.lastIndexOf("!/")); + } + if (u.startsWith("file:")) { // NOI18N + //return new File(u.substring(5)); + // XXX is this algorithm really portable? Unfortunately Java + // platform provides no better way that I know of. + URL fu = new URL(u); + String path = URLDecoder.decode(fu.getPath()); + if (File.separatorChar != '/') { + path = path.replace('/', File.separatorChar); + } + return new File(path); + } else { + return null; + } + } + public long getHash() { + return hash; + } + public String toString() { + StringBuffer buf = new StringBuffer(); + Iterator it = urls.iterator(); + int i = 0; + while (it.hasNext()) { + long t = times[i++]; + if (t == 0L) { + buf.append(""); // NOI18N + } else { + buf.append(new Date(t)); + } + buf.append('\t'); + buf.append(it.next()); + buf.append('\n'); + } + return buf.toString(); + } + public int compare(Object o1, Object o2) { + return ((URL)o1).toString().compareTo(((URL)o2).toString()); } - ArrayList arr = new ArrayList (); - arr.addAll (Arrays.asList (fs.getXmlUrls ())); - arr.removeAll (urls); - setURLs (arr); } - + } Index: core/src/org/netbeans/core/projects/SystemFileSystem.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/projects/SystemFileSystem.java,v retrieving revision 1.24 diff -u -r1.24 SystemFileSystem.java --- core/src/org/netbeans/core/projects/SystemFileSystem.java 17 Jan 2002 15:46:06 -0000 1.24 +++ core/src/org/netbeans/core/projects/SystemFileSystem.java 22 Feb 2002 12:03:15 -0000 @@ -7,7 +7,7 @@ * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original - * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2002 Sun * Microsystems, Inc. All Rights Reserved. */ @@ -22,6 +22,8 @@ import java.util.*; import javax.swing.JMenuItem; +import org.xml.sax.SAXException; + import org.openide.ErrorManager; import org.openide.TopManager; import org.openide.filesystems.*; @@ -393,9 +395,15 @@ } FileSystem[] arr = new FileSystem[home == null ? 2 : 3]; - arr[0] = new ModuleLayeredFileSystem (user); - if (home != null) { - arr[1] = new ModuleLayeredFileSystem (home); + try { + arr[0] = new ModuleLayeredFileSystem (user, null); + if (home != null) { + arr[1] = new ModuleLayeredFileSystem (home, new File(userDir, "cache")); // NOI18N + } + } catch (SAXException saxe) { + IOException ioe = new IOException(saxe.toString()); + ModuleLayeredFileSystem.err.annotate(ioe, saxe); + throw ioe; } FixedFileSystem.deflt = new FixedFileSystem ("org.netbeans.core.projects.FixedFileSystem", "Automatic Manifest Installation"); // NOI18N Index: openide/src/org/openide/filesystems/XMLFileSystem.java =================================================================== RCS file: /cvs/openide/src/org/openide/filesystems/XMLFileSystem.java,v retrieving revision 1.52 diff -u -r1.52 XMLFileSystem.java --- openide/src/org/openide/filesystems/XMLFileSystem.java 28 Jan 2002 14:08:17 -0000 1.52 +++ openide/src/org/openide/filesystems/XMLFileSystem.java 22 Feb 2002 12:03:16 -0000 @@ -7,7 +7,7 @@ * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original - * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2002 Sun * Microsystems, Inc. All Rights Reserved. */ @@ -16,13 +16,14 @@ import java.lang.ref.*; import java.io.*; import java.lang.reflect.*; - import java.beans.*; import java.util.*; import java.net.*; +import java.util.zip.CRC32; import org.openide.util.enum.EmptyEnumeration; import org.openide.util.NbBundle; +import org.openide.xml.XMLUtil; import org.xml.sax.*; import org.xml.sax.helpers.ParserFactory; @@ -202,6 +203,7 @@ } private synchronized void setXmlUrls (URL[] urls, boolean validate) throws IOException, PropertyVetoException { +//long time = System.currentTimeMillis(); ResourceElem rootElem; String oldDisplayName = getDisplayName (); if (urls.length == 0) { @@ -241,7 +243,12 @@ handler.urlContext = act; xp.parse(act.toString()); } +//time = System.currentTimeMillis() - time; +//System.err.println("Parsing took " + time + "ms"); +//time = System.currentTimeMillis(); refreshChildrenInAtomicAction ((AbstractFolder)getRoot (),rootElem ); +//time = System.currentTimeMillis() - time; +//System.err.println("Notifying took " + time + "ms"); } catch (IOException iox) { urlsToXml = origUrls; throw iox; @@ -250,11 +257,158 @@ ExternalUtil.copyAnnotation (x,e); throw x; } finally { + String cachename = System.getProperty("netbeans.cache.layers.filename"); + if (cachename != null && cachename.length() > 0) { + // Refer to org.netbeans.core.projects.ModuleLayeredFileSystem and #20168. + File cacheFile = new File(cachename); + OutputStream os = new FileOutputStream(cacheFile); + try { + writeOut(rootElem, os, Boolean.getBoolean("netbeans.cache.layers.prettyprint"), cacheFile.getParentFile()); + } finally { + os.close(); + } + } rootElem = null; } firePropertyChange (PROP_DISPLAY_NAME, oldDisplayName, getDisplayName ()); } + private void writeOut(ResourceElem root, OutputStream out, boolean pretty, File datadir) throws IOException { + Writer wr = new OutputStreamWriter(out, "UTF-8"); + wr.write("\n"); + wr.write("\n"); + wr.write("\n"); + wr.write(""); + if (pretty) wr.write('\n'); + } + writeFolder(wr, root, 1, pretty, datadir); + wr.write("\n"); + wr.close(); + } + + private static final String SPACES = " "; // NOI18N + private static final int SPACES_LENGTH = SPACES.length(); + private static final int INDENT = 4; + private String space(int n) { + int size = n * INDENT; + if (size <= SPACES_LENGTH) { + return SPACES.substring(0, size); + } else { + StringBuffer buf = new StringBuffer(size); + for (int i = 0; i < size; i++) buf.append(' '); + return buf.toString(); + } + } + + private void writeFolder(Writer wr, ResourceElem elem, int depth, boolean pretty, File datadir) throws IOException { + if (elem.children == null) return; + Iterator it = new TreeSet(elem.children.keySet()).iterator(); + + while (it.hasNext()) { + String name = (String)it.next(); + ResourceElem child = (ResourceElem)elem.children.get(name); + + if( child.isFolder()) { + if (pretty) wr.write(space(depth)); + wr.write("'); + if (pretty) wr.write('\n'); + } + writeFolder(wr, child, depth + 1, pretty, datadir); + if (pretty) wr.write(space(depth)); + wr.write(""); + if (pretty) wr.write('\n'); + } else { + String uri = child.getURI(); + if (pretty) wr.write(space(depth)); + wr.write("", idx)) != -1) { + // E.g. "foo]]>bar" -> idx = 3 + // escaped to "foo]]>]]>]]>\n"); + if (pretty) wr.write(space(depth + 1)); + wr.write("'); + if (pretty) wr.write('\n'); + } + */ + if (writeAttrs(wr, child.getAttr(false), depth + 1, pretty)) { + wr.write("/>"); + if (pretty) wr.write('\n'); + } else { + if (pretty) wr.write(space(depth)); + wr.write(""); + if (pretty) wr.write('\n'); + } + } + } + + } + + private boolean writeAttrs( Writer wr, XMLMapAttr attrs, int depth, boolean pretty) throws IOException { + if (attrs == null) return true; // empty attrs + + Iterator attri = new TreeSet(attrs.map.keySet()).iterator(); + if (!attri.hasNext()) return true; + + wr.write('>'); + if (pretty) wr.write('\n'); + while (attri.hasNext()) { + String aname = (String)attri.next(); + XMLMapAttr.Attr a = (XMLMapAttr.Attr)attrs.map.get(aname); + if (pretty) wr.write(space(depth)); + wr.write(""); + if (pretty) wr.write('\n'); + } + + return false; + } + /** * @return if value of lastModified should be cached */