Index: core/src/org/netbeans/core/LookupCache.java =================================================================== RCS file: core/src/org/netbeans/core/LookupCache.java diff -N core/src/org/netbeans/core/LookupCache.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/src/org/netbeans/core/LookupCache.java 7 Jan 2003 04:04:02 -0000 @@ -0,0 +1,407 @@ +/* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2002 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.core; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; + +import org.openide.ErrorManager; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.Repository; +import org.openide.loaders.DataFolder; +import org.openide.loaders.FolderLookup; +import org.openide.util.Lookup; +import org.openide.util.Utilities; +import org.openide.util.io.NbObjectInputStream; +import org.openide.util.io.NbObjectOutputStream; + +import org.netbeans.core.modules.Module; +import org.netbeans.core.modules.ModuleManager; +import org.netbeans.core.perftool.StartLog; + +/** + * Responsible for persisting the structure of folder lookup. + *

A cache is kept in serialized form in $userdir/cache/folder-lookup.ser. + * Unless the cache is invalidated due to changes in module JARs or + * files in $userdir/system/**, it is restored after a regular startup. + * The actual objects in lookup are not serialized - only their classes, + * instanceof information, position in the Services folder, and so on. This + * permits us to avoid calling the XML parser for every .settings object etc. + * Other forms of lookup like META-INF/services/* are not persisted. + *

Can be enabled or disabled with the system property netbeans.cache.lookup. + * @author Jesse Glick, Jaroslav Tulach + * @see "#20190" + */ +class LookupCache { + + /** whether to enable the cache for this session */ + private static final boolean ENABLED = Boolean.valueOf(System.getProperty("netbeans.cache.lookup", "true")).booleanValue(); // NOI18N + + /** private logging for this class */ + private static final ErrorManager err = ErrorManager.getDefault().getInstance("org.netbeans.core.LookupCache"); // NOI18N + + /** + * Get the Services/ folder lookup. + * May either do it the slow way, or might load quickly from a cache. + * @return the folder lookup for the system + */ + public static Lookup load() { + err.log("enabled=" + ENABLED); + if (ENABLED && cacheHit()) { + try { + return loadCache(); + } catch (Exception e) { + err.notify(ErrorManager.INFORMATIONAL, e); + } + } + return loadDirect(); + } + + /** + * Load folder lookup directly from the system file system, parsing + * as necessary (the slow way). + */ + private static Lookup loadDirect() { + FileObject services = Repository.getDefault().getDefaultFileSystem().findResource("Services"); // NOI18N + if (services != null) { + StartLog.logProgress("Got Services folder"); // NOI18N + FolderLookup f = new FolderLookup(DataFolder.findFolder(services), "SL["); // NOI18N + StartLog.logProgress("created FolderLookup"); // NOI18N + err.log("loadDirect from Services"); + return f.getLookup(); + } else { + err.log("loadDirect, but no Services"); + return Lookup.EMPTY; + } + } + + /** + * Determine if there is an existing lookup cache which can be used + * now as is. + * If there is a cache and a stamp file, and the stamp agrees with + * a calculation of the files and timestamps currently available to + * constitute the folder lookup, then the cache is used. + */ + private static boolean cacheHit() { + File f = cacheFile(); + if (f == null || !f.exists()) { + err.log("no cache file"); + return false; + } + File stampFile = stampFile(); + if (stampFile == null || !stampFile.exists()) { + err.log("no stamp file"); + return false; + } + StartLog.logStart("check for lookup cache hit"); // NOI18N + List files = relevantFiles(); // List + if (err.isLoggable(ErrorManager.INFORMATIONAL)) { + err.log("checking against " + stampFile + " for files " + files); + } + boolean hit; + try { + Stamp stamp = new Stamp(files); + long newHash = stamp.getHash(); + BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(stampFile), "UTF-8")); // NOI18N + try { + String line = r.readLine(); + long oldHash; + try { + oldHash = Long.parseLong(line); + } catch (NumberFormatException nfe) { + throw new IOException(nfe.toString()); + } + if (oldHash == newHash) { + err.log("Cache hit! with hash " + oldHash); + hit = true; + } else { + err.log("Cache miss, " + oldHash + " -> " + newHash); + hit = false; + } + } finally { + r.close(); + } + } catch (IOException ioe) { + err.notify(ErrorManager.INFORMATIONAL, ioe); + hit = false; + } + StartLog.logEnd("check for lookup cache hit"); // NOI18N + return hit; + } + + /** + * The file containing the serialized lookup cache. + */ + private static File cacheFile() { + String ud = System.getProperty("netbeans.user"); + if (ud != null) { + File cachedir = new File(ud, "cache"); // NOI18N + cachedir.mkdirs(); + return new File(cachedir, "folder-lookup.ser"); // NOI18N + } else { + return null; + } + } + + /** + * The file containing a stamp which indicates which modules were + * enabled, what versions of them, customized services, etc. + */ + private static File stampFile() { + String ud = System.getProperty("netbeans.user"); + if (ud != null) { + File cachedir = new File(ud, "cache"); // NOI18N + cachedir.mkdirs(); + return new File(cachedir, "lookup-stamp.txt"); // NOI18N + } else { + return null; + } + } + + /** + * List of all files which might be relevant to the contents of folder lookup. + * This means: all JAR files which are modules (skip their extensions and + * variants which can be assumed not to contain layer files); and all files + * contained in the system/Services/ subdirs (if any) of the home dir, + * user dir, and extra installation directories (#27151). + * For test modules, use the original JAR, not the physical JAR, + * to prevent cache misses on every restart. + * For fixed modules with layers (e.g. core.jar), add in the matching JAR, + * if that can be ascertained. + * No particular order of returned files is assumed. + */ + private static List relevantFiles() { + final List files = new ArrayList(250); // List + final ModuleManager mgr = NbTopManager.get().getModuleSystem().getManager(); + mgr.mutex().readAccess(new Runnable() { + public void run() { + Iterator it = mgr.getEnabledModules().iterator(); + while (it.hasNext()) { + Module m = (Module)it.next(); + String layer = (String)m.getAttribute("OpenIDE-Module-Layer"); // NOI18N + if (layer != null) { + if (!m.isFixed()) { + files.add(m.getJarFile()); + } else { + URL layerURL = m.getClassLoader().getResource(layer); + if (layerURL != null) { + String s = layerURL.toExternalForm(); + if (s.startsWith("jar:")) { // NOI18N + int bangSlash = s.lastIndexOf("!/"); // NOI18N + if (bangSlash != -1) { + // underlying URL inside jar:, generally file: + try { + URL layerJarURL = new URL(s.substring(4, bangSlash)); + File layerJar = Utilities.toFile(layerJarURL); + if (layerJar != null) { + files.add(layerJar); + } else { + err.log(ErrorManager.WARNING, "Weird jar: URL: " + layerJarURL); + } + } catch (MalformedURLException mfue) { + err.notify(ErrorManager.INFORMATIONAL, mfue); + } + } else { + err.log(ErrorManager.WARNING, "Malformed jar: URL: " + s); + } + } else { + err.log(ErrorManager.WARNING, "Not a jar: URL: " + s); + } + } else { + err.log(ErrorManager.WARNING, "Could not find " + layer + " in " + m); + } + } + } + // else no layer, ignore + } + } + }); + relevantFilesFromInst(files, System.getProperty("netbeans.home")); // NOI18N + relevantFilesFromInst(files, System.getProperty("netbeans.user")); // NOI18N + String nbdirs = System.getProperty("netbeans.dirs"); // NOI18N + if (nbdirs != null) { + // #27151 + StringTokenizer tok = new StringTokenizer(nbdirs, File.pathSeparator); + while (tok.hasMoreTokens()) { + relevantFilesFromInst(files, tok.nextToken()); + } + } + return files; + } + /** + * Find relevant files from an installation directory. + */ + private static void relevantFilesFromInst(List files, String instDir) { + if (instDir == null) { + return; + } + relevantFilesFrom(files, new File(new File(new File(instDir), "system"), "Services")); // NOI18N + } + /** + * Retrieve all files in a directory, recursively. + */ + private static void relevantFilesFrom(List files, File dir) { + File[] kids = dir.listFiles(); + if (kids != null) { + for (int i = 0; i < kids.length; i++) { + File f = kids[i]; + if (f.isFile()) { + files.add(f); + } else { + relevantFilesFrom(files, f); + } + } + } + } + + /** + * Load folder lookup from the disk cache. + */ + private static Lookup loadCache() throws Exception { + StartLog.logStart("load lookup cache"); + File f = cacheFile(); + err.log("loading from " + f); + InputStream is = new FileInputStream(f); + try { + ObjectInputStream ois = new NbObjectInputStream(new BufferedInputStream(is)); + Lookup l = (Lookup)ois.readObject(); + StartLog.logEnd("load lookup cache"); + return l; + } finally { + is.close(); + } + } + + /** + * Store the current contents of folder lookup to disk, hopefully to be used + * in the next session to speed startup. + * @param l the folder lookup + * @throws IOException if it could not be saved + */ + public static void store(Lookup l) throws IOException { + if (!ENABLED) { + return; + } + File f = cacheFile(); + if (f == null) { + return; + } + File stampFile = stampFile(); + if (stampFile == null) { + return; + } + StartLog.logStart("store lookup cache"); + err.log("storing to " + f + " with stamp in " + stampFile); + OutputStream os = new FileOutputStream(f); + try { + try { + ObjectOutputStream oos = new NbObjectOutputStream(new BufferedOutputStream(os)); + oos.writeObject(l); + oos.flush(); + } finally { + os.close(); + } + Stamp stamp = new Stamp(relevantFiles()); + Writer wr = new OutputStreamWriter(new FileOutputStream(stampFile), "UTF-8"); // NOI18N + try { + // Would be nice to write out as zero-padded hex. + // Unfortunately while Long.toHexString works fine, + // Long.parseLong cannot be asked to parse unsigned longs, + // so fails when the high bit is set. + wr.write(Long.toString(stamp.getHash())); + wr.write("\nLine above is identifying hash key, do not edit!\nBelow is metadata about folder lookup cache, for debugging purposes.\n"); // NOI18N + wr.write(stamp.toString()); + } finally { + wr.close(); + } + StartLog.logEnd("store lookup cache"); + } catch (IOException ioe) { + // Delete corrupted cache. + if (f.exists()) { + f.delete(); + } + if (stampFile.exists()) { + stampFile.delete(); + } + throw ioe; + } + } + + /** + * Represents a hash of a bunch of JAR or other files and their timestamps. + * Compare ModuleLayeredFileSystem's similar nested class. + * .settings files do not get their timestamps checked because generally + * changes to them do not reflect changes in the structure of lookup, only + * in the contents of one lookup instance. Otherwise autoupdate's settings + * alone would trigger a cache miss every time. Generally, all files other + * than JARs and .nbattrs (which can affect folder order) should not affect + * lookup structure by their contents, except in the pathological case which + * we do not consider that they supply zero instances or a recursive lookup + * (which even then would only lead to problems if such a file were changed + * on disk between IDE sessions, which can be expected to be very rare). + */ + private static final class Stamp { + private final List files; // List + private final long[] times; + private final long hash; + /** Create a stamp from a list of files. */ + public Stamp(List files) throws IOException { + this.files = new ArrayList(files); + Collections.sort(this.files); + times = new long[this.files.size()]; + long x = 17L; + Iterator it = this.files.iterator(); + int i = 0; + while (it.hasNext()) { + File f = (File)it.next(); + x ^= f.hashCode(); + x += 98679245L; + long m; + String name = f.getName().toLowerCase(Locale.US); + if (name.endsWith(".jar") || name.equals(".nbattrs")) { // NOI18N + m = f.lastModified(); + } else { + m = 0L; + } + x ^= (times[i++] = m); + } + hash = x; + } + /** Hash of the stamp for comparison purposes. */ + public long getHash() { + return hash; + } + /** Debugging information listing which files were used. */ + public String toString() { + StringBuffer buf = new StringBuffer(); + Iterator it = files.iterator(); + int i = 0; + while (it.hasNext()) { + long t = times[i++]; + if (t != 0L) { + buf.append(new Date(t)); + } else { + buf.append(""); // NOI18N + } + buf.append('\t'); + buf.append(it.next()); + buf.append('\n'); + } + return buf.toString(); + } + } + +} Index: core/src/org/netbeans/core/NbTopManager.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/NbTopManager.java,v retrieving revision 1.175 diff -u -r1.175 NbTopManager.java --- core/src/org/netbeans/core/NbTopManager.java 18 Dec 2002 23:41:28 -0000 1.175 +++ core/src/org/netbeans/core/NbTopManager.java 7 Jan 2003 04:04:03 -0000 @@ -538,6 +538,11 @@ } } org.netbeans.core.projects.XMLSettingsHandler.saveOptions(); + try { + ((Lkp)Lookup.getDefault()).storeCache(); + } catch (IOException ioe) { + ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ioe); + } org.netbeans.core.projects.SessionManager.getDefault().close(); } catch (ThreadDeath td) { throw td; @@ -804,17 +809,6 @@ private final void doInitializeLookup () { //System.err.println("doInitializeLookup"); - FileObject services = Repository.getDefault().getDefaultFileSystem() - .findResource("Services"); - Lookup nue; - if (services != null) { - StartLog.logProgress("Got Services folder"); // NOI18N - FolderLookup f = new FolderLookup(DataFolder.findFolder(services), "SL["); // NOI18N - StartLog.logProgress("created FolderLookup"); // NOI18N - nue = f.getLookup(); - } else { - nue = Lookup.EMPTY; - } // extend the lookup Lookup[] arr = new Lookup[] { @@ -823,7 +817,7 @@ getLookups()[2], // ModuleInfo lookup // XXX figure out how to put this ahead of MetaInfServicesLookup (for NonGuiMain): NbTopManager.get ().getInstanceLookup (), - nue, + LookupCache.load(), }; StartLog.logProgress ("prepared other Lookups"); // NOI18N @@ -831,6 +825,14 @@ StartLog.logProgress ("Lookups set"); // NOI18N //StartLog.logEnd ("NbTopManager$Lkp: initialization of FolderLookup"); // NOI18N + } + + public void storeCache() throws IOException { + Lookup[] ls = getLookups(); + if (ls.length == 5) { + // modulesClassPathInitialized has been called, so store folder lookup + LookupCache.store(ls[4]); + } } protected void beforeLookup(Lookup.Template templ) { Index: openide/src/org/openide/loaders/FolderLookup.java =================================================================== RCS file: /cvs/openide/src/org/openide/loaders/FolderLookup.java,v retrieving revision 1.19 diff -u -r1.19 FolderLookup.java --- openide/src/org/openide/loaders/FolderLookup.java 21 Dec 2002 08:40:25 -0000 1.19 +++ openide/src/org/openide/loaders/FolderLookup.java 7 Jan 2003 04:04:05 -0000 @@ -16,6 +16,9 @@ import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; @@ -23,6 +26,7 @@ import org.openide.cookies.InstanceCookie; import org.openide.ErrorManager; +import org.openide.filesystems.FileObject; import org.openide.loaders.DataObject; import org.openide.util.Lookup; import org.openide.util.lookup.AbstractLookup; @@ -247,14 +251,17 @@ /** ProxyLookup delegate so we can change the lookups on fly. */ - private static final class ProxyLkp extends ProxyLookup { + private static final class ProxyLkp extends ProxyLookup implements Serializable { + + private static final long serialVersionUID = 1L; /** FolderLookup we are associated with. */ - private FolderLookup fl; + private transient FolderLookup fl; /** Content to control the abstract lookup. */ - private AbstractLookup.Content content; + private transient AbstractLookup.Content content; + private transient boolean readFromStream; /** Constructs lookup which holds all items+lookups from underlying world. * @param folder FolderLookup to associate to */ @@ -270,12 +277,45 @@ this.content = content; } + private void writeObject (ObjectOutputStream oos) throws IOException { + Lookup[] ls = getLookups(); + for (int i = 0; i < ls.length; i++) { + oos.writeObject(ls[i]); + } + oos.writeObject(null); + oos.writeObject (fl.folder); + oos.writeObject (fl.rootName); + oos.writeObject (content); + } + + private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { + List ls = new ArrayList(); // List + Lookup l; + while ((l = (Lookup)ois.readObject()) != null) { + ls.add(l); + } + Lookup[] arr = (Lookup[])ls.toArray(new Lookup[ls.size()]); + DataFolder df = (DataFolder)ois.readObject (); + String root = (String)ois.readObject (); + + fl = new FolderLookup (df, root, true); + fl.lookup = this; + + content = (AbstractLookup.Content)ois.readObject (); + + setLookups (arr); + + readFromStream = true; + } + /** Updates internal data. * @param items Items to assign to all pairs * @param lookups delegates to delegate to (first item is null) */ public void update(Collection items, List lookups) { + readFromStream = false; + // remember the instance lookup Lookup pairs = getLookups ()[0]; @@ -292,6 +332,11 @@ /** Waits before the processing of changes is finished. */ protected void beforeLookup (Template template) { + if (readFromStream) { + // ok + return; + } + // do not wait in folder recognizer, but in all other cases if (!FolderList.isFolderRecognizerThread ()) { fl.instanceFinished (); @@ -303,12 +348,17 @@ /** Item that delegates to InstanceCookie. Item which * the internal lookup data structure is made from. */ - private static final class ICItem extends AbstractLookup.Pair { - private InstanceCookie ic; + private static final class ICItem extends AbstractLookup.Pair implements Serializable { + static final long serialVersionUID = 10L; + + /** when deserialized only primary file is stored */ + private FileObject fo; + + private transient InstanceCookie ic; /** source data object */ - private DataObject obj; + private transient DataObject obj; /** reference to created object */ - private WeakReference ref; + private transient WeakReference ref; /** root folder */ private String rootName; @@ -317,6 +367,27 @@ this.ic = ic; this.obj = obj; this.rootName = rootName; + this.fo = obj.getPrimaryFile(); + } + + /** Initializes the item + */ + public void init () { + if (ic != null) return; + + ic = (InstanceCookie)obj.getCookie (InstanceCookie.class); + if (ic == null) { + // XXX handle more gracefully + throw new IllegalStateException ("No cookie: " + obj); + } + } + + + /** Initializes the cookie from data object. + */ + private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + obj = DataObject.find (fo); } @@ -324,6 +395,8 @@ * @return the class of the item */ protected boolean instanceOf (Class clazz) { + init (); + if (ic instanceof InstanceCookie.Of) { // special handling for special cookies InstanceCookie.Of of = (InstanceCookie.Of)ic; @@ -345,6 +418,8 @@ * @return the instance of the object or null if it cannot be created */ public Object getInstance() { + init (); + try { Object obj = ic.instanceCreate(); ref = new WeakReference (obj); @@ -359,6 +434,8 @@ /** Hash code is the InstanceCookie's code. */ public int hashCode () { + init (); + return System.identityHashCode (ic); } @@ -366,6 +443,8 @@ public boolean equals (Object obj) { if (obj instanceof ICItem) { ICItem i = (ICItem)obj; + i.init (); + init (); return ic == i.ic; } return false; @@ -375,11 +454,15 @@ * @return string representing the item, that can be used for * persistance purposes to locate the same item next time */ public String getId() { + init (); + return objectName(rootName, obj); } /** Display name is extracted from name of the objects node. */ public String getDisplayName () { + init (); + return obj.getNodeDelegate ().getDisplayName (); } @@ -399,6 +482,8 @@ * @return the correct class */ public Class getType() { + init (); + try { return ic.instanceClass (); } catch (IOException ex) { Index: openide/src/org/openide/loaders/XMLDataObject.java =================================================================== RCS file: /cvs/openide/src/org/openide/loaders/XMLDataObject.java,v retrieving revision 1.125 diff -u -r1.125 XMLDataObject.java --- openide/src/org/openide/loaders/XMLDataObject.java 25 Sep 2002 11:43:23 -0000 1.125 +++ openide/src/org/openide/loaders/XMLDataObject.java 7 Jan 2003 04:04:05 -0000 @@ -1022,6 +1022,8 @@ /** result used for this lookup */ private Lookup.Result result; + private ThreadLocal QUERY = new ThreadLocal (); + //~~~~~~~~~~~~~~~~~~~~~ Task body and control of queue ~~~~~~~~~~~~~~~~~~~ /** Getter for public ID of the document. @@ -1037,18 +1039,41 @@ * * @param class to look for */ - public Object lookupCookie (Class clazz) { - waitFinished (); - - Lookup l = lookup != null ? lookup : Lookup.EMPTY; + public Object lookupCookie (final Class clazz) { + if (QUERY.get () == clazz) { + // somebody is querying for the same cookie in the same thread + // probably neverending-loop - ignore + return new InstanceCookie () { + public Class instanceClass () { + return clazz; + } + + public Object instanceCreate () throws IOException { + throw new IOException ("Cyclic reference, sorry: " + clazz); + } + + public String instanceName () { + return clazz.getName (); + } + }; + } - Lookup.Result r = result; - if (r != null) { - // just to initialize all listeners - r.allItems (); + Object previous = QUERY.get (); + try { + QUERY.set (clazz); + waitFinished (); + + Lookup l = lookup != null ? lookup : Lookup.EMPTY; + + Lookup.Result r = result; + if (r != null) { + // just to initialize all listeners + r.allItems (); + } + return l.lookup (clazz); + } finally { + QUERY.set (previous); } - - return l.lookup (clazz); } /* Index: openide/src/org/openide/util/lookup/AbstractLookup.java =================================================================== RCS file: /cvs/openide/src/org/openide/util/lookup/AbstractLookup.java,v retrieving revision 1.24 diff -u -r1.24 AbstractLookup.java --- openide/src/org/openide/util/lookup/AbstractLookup.java 21 Dec 2002 08:40:35 -0000 1.24 +++ openide/src/org/openide/util/lookup/AbstractLookup.java 7 Jan 2003 04:04:05 -0000 @@ -13,7 +13,7 @@ package org.openide.util.lookup; - +import java.io.Serializable; import java.lang.ref.*; import java.util.*; @@ -29,7 +29,9 @@ * @author Jaroslav Tulach * @since 1.9 */ -public class AbstractLookup extends Lookup { +public class AbstractLookup extends Lookup implements Serializable { + static final long serialVersionUID = 5L; + /** lock for initialization of the map */ private Content treeLock; /** the tree that registers all items */ @@ -40,7 +42,7 @@ /** set (Class, List (Reference (Result)) of all listeners that are waiting in * changes in class Class */ - private Map reg; + private transient Map reg; /** count of items in to lookup */ private int count; @@ -447,7 +449,9 @@ /** Extension to the default lookup item that offers additional information * for the data structures use in AbstractLookup */ - public static abstract class Pair extends Lookup.Item { + public static abstract class Pair extends Lookup.Item implements Serializable { + private static final long serialVersionUID = 1L; + /** possition of this item in the lookup, manipulated in addPair, removePair, setPairs methods */ int index = -1; @@ -641,12 +645,14 @@ * * @since 1.25 */ - public static class Content extends Object { + public static class Content extends Object implements Serializable { + + private static final long serialVersionUID = 1L; // one of them is always null (except attach stage) /** abstract lookup we are connected to */ private AbstractLookup al = null; - private ArrayList earlyPairs = new ArrayList(3); + private transient ArrayList earlyPairs = new ArrayList(3); /** A lookup attaches to this object. */ Index: openide/src/org/openide/util/lookup/InheritanceTree.java =================================================================== RCS file: /cvs/openide/src/org/openide/util/lookup/InheritanceTree.java,v retrieving revision 1.15 diff -u -r1.15 InheritanceTree.java --- openide/src/org/openide/util/lookup/InheritanceTree.java 3 Dec 2002 14:12:15 -0000 1.15 +++ openide/src/org/openide/util/lookup/InheritanceTree.java 7 Jan 2003 04:04:05 -0000 @@ -14,22 +14,12 @@ package org.openide.util.lookup; - +import java.io.*; import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.List; - -import org.openide.util.enum.AlterEnumeration; -import org.openide.util.enum.EmptyEnumeration; -import org.openide.util.enum.QueueEnumeration; -import org.openide.util.enum.SequenceEnumeration; -import org.openide.util.enum.SingletonEnumeration; +import java.util.*; + +import org.openide.util.Lookup; +import org.openide.util.enum.*; /** A tree to represent classes with inheritance. Description of the @@ -96,11 +86,15 @@ * * @author Jaroslav Tulach */ -final class InheritanceTree extends Object implements java.util.Comparator { +final class InheritanceTree extends Object implements Comparator, Serializable { + private static final long serialVersionUID = 1L; + /** the root item (represents Object) */ - private Node object; - /** map of queried interfaces (Class, Set) */ - private Map interfaces; + private transient Node object; + /** Map of queried interfaces. + *

Type: Map<Class, (Collection<AbstractLookup.Pair> | AbstractLookup.Pair)> + */ + private transient Map interfaces; /** Constructor */ @@ -108,6 +102,33 @@ object = new Node (java.lang.Object.class); } + private void writeObject (ObjectOutputStream oos) throws IOException { + oos.writeObject(object); + Iterator it = interfaces.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry e = (Map.Entry)it.next(); + Class c = (Class)e.getKey(); + oos.writeObject(c.getName()); + Object o = e.getValue(); + if (!(o instanceof Collection) && !(o instanceof AbstractLookup.Pair)) throw new ClassCastException(String.valueOf(o)); + oos.writeObject(o); + } + oos.writeObject(null); + } + + private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { + object = (Node)ois.readObject(); + interfaces = new WeakHashMap(); + String clazz; + ClassLoader l = (ClassLoader)Lookup.getDefault().lookup(ClassLoader.class); + while ((clazz = (String)ois.readObject()) != null) { + Object o = ois.readObject(); + if (!(o instanceof Collection) && !(o instanceof AbstractLookup.Pair)) throw new ClassCastException(String.valueOf(o)); + Class c = Class.forName(clazz, false, l); + interfaces.put(c, o); + } + } + /** Adds an item into the tree. * @param item to add @@ -745,12 +766,13 @@ /** Node in the tree. */ - static final class Node extends WeakReference { + static final class Node extends WeakReference implements Serializable { + static final long serialVersionUID = 3L; /** children nodes */ - public ArrayList children; + public ArrayList children; // List /** list of items assigned to this node (suspect to be subclasses) */ - public ArrayList items; + public ArrayList items; // List /** Constructor. */ @@ -807,5 +829,44 @@ items.add (item); return true; } + + private Object writeReplace () { + return new R (this); + } + } // End of class Node. + + private static final class R implements Serializable { + static final long serialVersionUID = 1L; + + private static ClassLoader l; + + private String clazzName; + private transient Class clazz; + private ArrayList children; + private ArrayList items; + + public R (Node n) { + this.clazzName = n.getType ().getName(); + this.children = n.children; + this.items = n.items; + } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + if (l == null) { + l = (ClassLoader)Lookup.getDefault().lookup(ClassLoader.class); + } + clazz = Class.forName(clazzName, false, l); + } + + private Object readResolve () throws ObjectStreamException { + Node n = new Node (clazz); + n.children = children; + n.items = items; + + return n; + } + + } // end of R } Index: openide/src/org/openide/util/lookup/ProxyLookup.java =================================================================== RCS file: /cvs/openide/src/org/openide/util/lookup/ProxyLookup.java,v retrieving revision 1.11 diff -u -r1.11 ProxyLookup.java --- openide/src/org/openide/util/lookup/ProxyLookup.java 3 Dec 2002 14:12:15 -0000 1.11 +++ openide/src/org/openide/util/lookup/ProxyLookup.java 7 Jan 2003 04:04:05 -0000 @@ -40,6 +40,15 @@ this.lookups = lookups; } + /** + * Create a lookup initially proxying to no others. + * Permits serializable subclasses. + * @since XXX + */ + protected ProxyLookup() { + this(new Lookup[0]); + } + public String toString() { return "ProxyLookup" + Arrays.asList(lookups); // NOI18N }