Index: loaders/manifest.mf =================================================================== RCS file: /shared/data/ccvs/repository/openide/loaders/manifest.mf,v retrieving revision 1.28 diff -u -r1.28 manifest.mf --- loaders/manifest.mf 8 Jun 2006 10:16:18 -0000 1.28 +++ loaders/manifest.mf 2 Nov 2006 16:05:45 -0000 @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.openide.loaders -OpenIDE-Module-Specification-Version: 5.11 +OpenIDE-Module-Specification-Version: 6.0 OpenIDE-Module-Localizing-Bundle: org/openide/loaders/Bundle.properties Index: loaders/api/apichanges.xml =================================================================== RCS file: /shared/data/ccvs/repository/openide/loaders/api/apichanges.xml,v retrieving revision 1.22 diff -u -r1.22 apichanges.xml --- loaders/api/apichanges.xml 1 Jul 2006 09:08:15 -0000 1.22 +++ loaders/api/apichanges.xml 2 Nov 2006 16:05:45 -0000 @@ -83,6 +83,24 @@ + + + DataNode.getLookup contains FileObject + + + + + +

+ Since now, each DataNode constructed without using own lookup, + shall have FileObject(s) associated with its + DataObject + available in its own lookup. +

+
+ + +
Index: loaders/nbproject/project.xml =================================================================== RCS file: /shared/data/ccvs/repository/openide/loaders/nbproject/project.xml,v retrieving revision 1.22 diff -u -r1.22 project.xml --- loaders/nbproject/project.xml 28 Aug 2006 17:29:45 -0000 1.22 +++ loaders/nbproject/project.xml 2 Nov 2006 16:05:45 -0000 @@ -85,7 +85,7 @@ - 6.5 + 7.0 @@ -113,70 +113,70 @@ - - - unit - - org.openide.loaders - - - - - org.openide.options - - - - org.openide.compat - - - - org.netbeans.core - - - - org.netbeans.swing.plaf - - - - org.netbeans.modules.settings - - - - org.openide.util - - - - org.netbeans.core.ui - - - - org.netbeans.modules.progress.ui - - - org.netbeans.modules.editor.mimelookup.impl - - - org.netbeans.core.startup - - - - org.netbeans.bootstrap - - - - - qa-functional - - org.openide.loaders - - - - - org.netbeans.core - - - - + + + unit + + org.openide.loaders + + + + + org.openide.options + + + + org.openide.compat + + + + org.netbeans.core + + + + org.netbeans.swing.plaf + + + + org.netbeans.modules.settings + + + + org.openide.util + + + + org.netbeans.core.ui + + + + org.netbeans.modules.progress.ui + + + org.netbeans.modules.editor.mimelookup.impl + + + org.netbeans.core.startup + + + + org.netbeans.bootstrap + + + + + qa-functional + + org.openide.loaders + + + + + org.netbeans.core + + + + org.openide.awt Index: loaders/src/org/openide/loaders/DataNode.java =================================================================== RCS file: /shared/data/ccvs/repository/openide/loaders/src/org/openide/loaders/DataNode.java,v retrieving revision 1.31 diff -u -r1.31 DataNode.java --- loaders/src/org/openide/loaders/DataNode.java 19 Sep 2006 12:48:45 -0000 1.31 +++ loaders/src/org/openide/loaders/DataNode.java 2 Nov 2006 16:05:45 -0000 @@ -77,6 +77,9 @@ this.obj = obj; propL = new PropL (); + if (lookup == null) { + setCookieSet(CookieSet.createGeneric(propL)); + } obj.addPropertyChangeListener (org.openide.util.WeakListeners.propertyChange (propL, obj)); @@ -622,6 +625,15 @@ return p; } + + /** Update files, if we are using CookieSet + */ + private void updateFilesInCookieSet(Set obj) { + if (ownLookup()) { + return; + } + getCookieSet().assign(FileObject.class, obj.toArray(new FileObject[0])); + } /** Support for firing property change. * @param ev event describing the change @@ -636,12 +648,16 @@ } if (DataObject.PROP_PRIMARY_FILE.equals(ev.getPropertyName())) { - // the node is not interested in children changes propL.updateStatusListener(); setName(obj.getName(), false); + updateFilesInCookieSet(obj.files()); return; } + if (DataObject.PROP_FILES.equals(ev.getPropertyName())) { + updateFilesInCookieSet(obj.files()); + } + if (DataObject.PROP_NAME.equals(ev.getPropertyName())) { DataNode.super.setName(obj.getName()); updateDisplayName(); @@ -751,7 +767,7 @@ * properties to this node. */ private class PropL extends Object - implements PropertyChangeListener, FileStatusListener { + implements PropertyChangeListener, FileStatusListener, CookieSet.Before { /** weak version of this listener */ private FileStatusListener weakL; /** previous filesystem we were attached to */ @@ -825,6 +841,12 @@ } } } + } + } + + public void beforeLookup(Class clazz) { + if (clazz.isAssignableFrom(FileObject.class)) { + updateFilesInCookieSet(obj.files()); } } } Index: loaders/src/org/openide/loaders/MultiDataObject.java =================================================================== RCS file: /shared/data/ccvs/repository/openide/loaders/src/org/openide/loaders/MultiDataObject.java,v retrieving revision 1.24 diff -u -r1.24 MultiDataObject.java --- loaders/src/org/openide/loaders/MultiDataObject.java 28 Oct 2006 21:57:36 -0000 1.24 +++ loaders/src/org/openide/loaders/MultiDataObject.java 2 Nov 2006 16:05:45 -0000 @@ -259,6 +259,7 @@ } firePropertyChangeLater (PROP_FILES, null, null); + updateFilesInCookieSet(); if (fe.isImportant ()) { checkConsistency(this); @@ -736,7 +737,7 @@ if (cookieSet != null) return cookieSet; // sets empty sheet and adds a listener to it - setCookieSet (new CookieSet (), false); + setCookieSet (CookieSet.createGeneric(getChangeListener()), false); return cookieSet; } } @@ -770,7 +771,10 @@ ) { firingProcessor.post(new Runnable () { public void run () { - firePropertyChange (name, oldV, newV); + firePropertyChange (name, oldV, newV); + if (PROP_FILES.equals(name) || PROP_PRIMARY_FILE.equals(name)) { + updateFilesInCookieSet(); + } } }); } @@ -807,17 +811,12 @@ checked = true; } - private ChangeListener chLis; + private ChangeAndBefore chLis; - final ChangeListener getChangeListener() { - if (chLis == null) { - chLis = new ChangeListener() { - /** State changed */ - public void stateChanged (ChangeEvent ev) { - fireCookieChange (); - } - }; - } + final ChangeAndBefore getChangeListener() { + if (chLis == null) { + chLis = new ChangeAndBefore(); + } return chLis; } @@ -1123,6 +1122,24 @@ */ void notifyFileDataCreated(FileEvent fe) { checked = false; + } + + final void updateFilesInCookieSet() { + getCookieSet().assign(FileObject.class, files().toArray(new FileObject[0])); + } + + /** Change listener and implementation of before. + */ + private final class ChangeAndBefore implements ChangeListener, CookieSet.Before { + public void stateChanged (ChangeEvent ev) { + fireCookieChange (); + } + + public void beforeLookup(Class clazz) { + if (clazz.isAssignableFrom(FileObject.class)) { + updateFilesInCookieSet(); + } + } } /** Entry replace. Index: loaders/test/unit/src/org/openide/loaders/DataLoaderOrigTest.java =================================================================== RCS file: /shared/data/ccvs/repository/openide/loaders/test/unit/src/org/openide/loaders/DataLoaderOrigTest.java,v retrieving revision 1.3 diff -u -r1.3 DataLoaderOrigTest.java --- loaders/test/unit/src/org/openide/loaders/DataLoaderOrigTest.java 1 Jul 2006 09:08:27 -0000 1.3 +++ loaders/test/unit/src/org/openide/loaders/DataLoaderOrigTest.java 2 Nov 2006 16:05:45 -0000 @@ -36,6 +36,10 @@ public DataLoaderOrigTest(String name) { super(name); } + + protected void setUp() throws IOException { + clearWorkDir(); + } public void testSimpleLoader() throws Exception { DataLoader l = DataLoader.getLoader(SimpleUniFileLoader.class); Index: loaders/test/unit/src/org/openide/loaders/DataShadowLookupTest.java =================================================================== RCS file: loaders/test/unit/src/org/openide/loaders/DataShadowLookupTest.java diff -N loaders/test/unit/src/org/openide/loaders/DataShadowLookupTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ loaders/test/unit/src/org/openide/loaders/DataShadowLookupTest.java 2 Nov 2006 16:05:45 -0000 @@ -0,0 +1,135 @@ +/* + * 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-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.openide.loaders; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.junit.MockServices; +import org.netbeans.junit.NbTestCase; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileSystem; +import org.openide.filesystems.FileUtil; +import org.openide.filesystems.Repository; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.util.Enumerations; +import org.openide.util.lookup.Lookups; + +/** Test things about shadows and broken shadows, etc. + * @author Jaroslav Tulach + */ +public class DataShadowLookupTest extends NbTestCase +implements java.net.URLStreamHandlerFactory { + /** original object */ + private DataObject original; + /** folder to work with */ + private DataFolder folder; + /** fs we work on */ + private FileSystem lfs; + + private Logger err; + + static { + // to handle nbfs urls... + // java.net.URL.setURLStreamHandlerFactory (new DataShadowLookupTest(null)); + MockServices.setServices(new Class[] { Pool.class }); + } + + public DataShadowLookupTest (String name) { + super(name); + } + + protected Level logLevel() { + return Level.INFO; + } + + protected void setUp() throws Exception { + + lfs = Repository.getDefault ().getDefaultFileSystem (); + + FileObject[] delete = lfs.getRoot().getChildren(); + for (int i = 0; i < delete.length; i++) { + delete[i].delete(); + } + + + FileObject fo = FileUtil.createData (lfs.getRoot (), getName () + "/folder/original.string"); + assertNotNull(fo); + original = DataObject.find (fo); + assertFalse ("Just to be sure that this is not shadow", original instanceof DataShadow); + assertEquals ("It is the right class", StringObject.class, original.getClass ()); + fo = FileUtil.createFolder (lfs.getRoot (), getName () + "/modify"); + assertNotNull(fo); + assertTrue (fo.isFolder ()); + folder = DataFolder.findFolder (fo); + + Repository.getDefault ().addFileSystem (lfs); + + err = Logger.getLogger(getName()); + } + + public java.net.URLStreamHandler createURLStreamHandler(String protocol) { + if (protocol.equals ("nbfs")) { + return FileUtil.nbfsURLStreamHandler (); + } + return null; + } + + public void testStringIsInLookupOfDataShadow() throws Exception { + DataShadow shade = original.createShadow(folder); + + { + String s = (String)original.getNodeDelegate().getLookup().lookup(String.class); + assertNotNull("String is in the original's lookup", s); + } + + assertSame(shade.getOriginal(), original); + String s = (String)shade.getNodeDelegate().getLookup().lookup(String.class); + assertNotNull("String is in the lookup", s); + assertEquals("It is the name of the original", original.getName(), s); + } + + public static final class Pool extends DataLoaderPool { + protected Enumeration loaders() { + return Enumerations.singleton(StringLoader.findObject(StringLoader.class, true)); + } + + } + + private static final class StringLoader extends UniFileLoader { + public StringLoader() { + super("org.openide.loaders.StringObject"); + getExtensions().addExtension("string"); + } + + protected MultiDataObject createMultiObject(FileObject primaryFile) throws DataObjectExistsException, IOException { + return new StringObject(this, primaryFile); + } + + } // end of StringLoader + + private static final class StringObject extends MultiDataObject { + public StringObject(StringLoader l, FileObject fo) throws DataObjectExistsException { + super(fo, l); + } + + protected Node createNodeDelegate() { + return new DataNode(this, Children.LEAF, Lookups.singleton(getName())); + } + } // end of StringObject + + +} Index: loaders/test/unit/src/org/openide/loaders/FileObjectInLookupTest.java =================================================================== RCS file: loaders/test/unit/src/org/openide/loaders/FileObjectInLookupTest.java diff -N loaders/test/unit/src/org/openide/loaders/FileObjectInLookupTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ loaders/test/unit/src/org/openide/loaders/FileObjectInLookupTest.java 2 Nov 2006 16:05:45 -0000 @@ -0,0 +1,205 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.openide.loaders; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; +import java.util.Enumeration; +import org.openide.filesystems.*; +import org.netbeans.junit.*; +import org.openide.nodes.Children; +import org.openide.nodes.CookieSet; +import org.openide.nodes.Node; +import org.openide.text.DataEditorSupport; +import org.openide.util.Enumerations; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; + +/* + */ +public class FileObjectInLookupTest extends NbTestCase { + private FileSystem lfs; + + public FileObjectInLookupTest(String name) { + super(name); + } + + protected void setUp() throws Exception { + MockServices.setServices(OwnDataLoaderPool.class); + clearWorkDir (); + lfs = TestUtilHid.createLocalFileSystem (getWorkDir (), new String[] { + "adir/", + "adir/file.txt", + "adir/file.own" + }); + Repository.getDefault().addFileSystem(lfs); + Enumeration en = DataLoaderPool.getDefault().allLoaders(); + while (en.hasMoreElements()) { + if (en.nextElement() instanceof OwnDataLoader) { + return; + } + } + fail("OwnDataLoader shall be registered"); + } + + protected void tearDown() throws Exception { + Repository.getDefault().removeFileSystem(lfs); + } + + protected boolean runInEQ() { + return true; + } + + public void testFOInsideFolder() throws Exception { + DataFolder f = DataFolder.findFolder(lfs.findResource("adir")); + assertFileObjects(f); + f.rename("bdir"); + assertFileObjects(f); + } + + public void testFOInsideADefaultDataObject() throws Exception { + DataObject obj = DataObject.find(lfs.findResource("adir/file.txt")); + assertFileObjects(obj); + obj.rename("kuk"); + assertFileObjects(obj); + obj.move(obj.getFolder().getFolder()); + assertFileObjects(obj); + } + + public void testOwnLoader() throws Exception { + DataObject obj = DataObject.find(lfs.findResource("adir/file.own")); + assertEquals(OwnDataLoader.class, obj.getLoader().getClass()); + assertFileObjects(obj); + obj.rename("kuk"); + assertFileObjects(obj); + obj.move(obj.getFolder().getFolder()); + assertFileObjects(obj); + } + + public void testShadow() throws Exception { + DataObject obj = DataObject.find(lfs.findResource("adir/file.own")); + DataShadow shadow = obj.createShadow(obj.getFolder().getFolder()); + assertEquals(OwnDataLoader.class, obj.getLoader().getClass()); + + assertEquals("DataObject for the shadow is the shadow", shadow, shadow.getCookie(DataObject.class)); + + assertFileObjects(obj); + assertFileObjects("However FileObject of a shadow are delegated to the original", shadow, obj.files()); + obj.rename("kuk"); + assertFileObjects(obj); + assertFileObjects("However FileObject of a shadow are delegated to the original", shadow, obj.files()); + obj.move(obj.getFolder().getFolder()); + assertFileObjects(obj); + assertFileObjects("However FileObject of a shadow are delegated to the original", shadow, obj.files()); + shadow.rename("somenewshadow"); + assertFileObjects(obj); + assertFileObjects("However FileObject of a shadow are delegated to the original", shadow, obj.files()); + obj.delete(); + /* + DataObject broken = DataObject.find(shadow.getPrimaryFile()); + if (shadow == broken) { + fail("They should be different: " + shadow + " != " + broken); + } + assertEquals("DataObject for the shadow is now the shadow", broken, broken.getCookie(DataObject.class)); + assertFileObjects(broken); + */ + } + + private static void assertFileObjects(DataObject obj) { + assertFileObjects("", obj, obj.files()); + } + + private static void assertFileObjects(String msg, DataObject obj, Collection expect) { + Collection allcol = obj.getNodeDelegate().getLookup().lookupAll(FileObject.class); + List all = new ArrayList(allcol); + Enumeration files = Collections.enumeration(expect); + int i = 0; + while (files.hasMoreElements()) { + FileObject fo = files.nextElement(); + if (i >= all.size()) { + fail(msg + "\nThere should be more elements, but there is only " + all.size() + "\nAll: " + all + "\nCurrent: " + fo); + } + + if (fo.equals(all.get(i))) { + i++; + continue; + } + fail(msg + "\nError at position " + i + " expected: " + fo + " but was: " + all.get(i) + "\nAll: " + all); + } + } + + public static final class OwnDataLoaderPool extends DataLoaderPool { + protected Enumeration loaders() { + return Enumerations.singleton(OwnDataLoader.getLoader(OwnDataLoader.class)); + } + } + + + public static class OwnDataLoader extends UniFileLoader { + private static final long serialVersionUID = 1L; + + public OwnDataLoader() { + super("org.openide.loaders.OwnDataObject"); + } + + protected String defaultDisplayName() { + return NbBundle.getMessage(OwnDataLoader.class, "LBL_Own_loader_name"); + } + + protected void initialize() { + super.initialize(); + getExtensions().addExtension("own"); + } + + protected MultiDataObject createMultiObject(FileObject primaryFile) throws DataObjectExistsException, IOException { + return new OwnDataObject(primaryFile, this); + } + } + static class OwnDataObject extends MultiDataObject implements Lookup.Provider { + + public OwnDataObject(FileObject pf, OwnDataLoader loader) throws DataObjectExistsException, IOException { + super(pf, loader); + CookieSet cookies = getCookieSet(); + cookies.add((Node.Cookie) DataEditorSupport.create(this, getPrimaryEntry(), cookies)); + } + + protected Node createNodeDelegate() { + return new OwnDataNode(this, getLookup()); + } + + public Lookup getLookup() { + return getCookieSet().getLookup(); + } + } + + static class OwnDataNode extends DataNode { + private static final String IMAGE_ICON_BASE = "SET/PATH/TO/ICON/HERE"; + + public OwnDataNode(OwnDataObject obj, Lookup lookup) { + super(obj, Children.LEAF, lookup); + // setIconBaseWithExtension(IMAGE_ICON_BASE); + } + + } + +} Index: nodes/apichanges.xml =================================================================== RCS file: /shared/data/ccvs/repository/openide/nodes/apichanges.xml,v retrieving revision 1.7 diff -u -r1.7 apichanges.xml --- nodes/apichanges.xml 18 Aug 2006 18:03:43 -0000 1.7 +++ nodes/apichanges.xml 2 Nov 2006 16:05:45 -0000 @@ -23,7 +23,33 @@ Nodes API - + + + CookieSet can hold any objects and not just cookies + + + + + + New method + CookieSet.createGeneric has been added. It allows to create + an instance of + CookieSet that can contain any object, not just + Cookies. + This addition change is accompanied with two additional changes: + + CookieSet now implements + Lookup.Provider and thus has a method getLookup to + allow queries for of its content. + Also there is a new method + + assign(clazz, instances) that allows to add/remove + plain old java objects to the CookieSet. + + + + + BeanNode constructor allows passing Lookup instance Index: nodes/nbproject/project.properties =================================================================== RCS file: /shared/data/ccvs/repository/openide/nodes/nbproject/project.properties,v retrieving revision 1.9 diff -u -r1.9 project.properties --- nodes/nbproject/project.properties 18 Aug 2006 18:03:43 -0000 1.9 +++ nodes/nbproject/project.properties 2 Nov 2006 16:05:45 -0000 @@ -22,4 +22,4 @@ javadoc.arch=${basedir}/../arch/arch-openide-nodes.xml javadoc.apichanges=${basedir}/apichanges.xml -spec.version.base=6.9.0 +spec.version.base=7.0 Index: nodes/src/org/openide/nodes/AbstractNode.java =================================================================== RCS file: /shared/data/ccvs/repository/openide/nodes/src/org/openide/nodes/AbstractNode.java,v retrieving revision 1.10 diff -u -r1.10 AbstractNode.java --- nodes/src/org/openide/nodes/AbstractNode.java 14 Aug 2006 12:00:12 -0000 1.10 +++ nodes/src/org/openide/nodes/AbstractNode.java 2 Nov 2006 16:05:45 -0000 @@ -169,6 +169,13 @@ // not called from constructor (see e.g. DataNode) super.setName(""); // NOI18N } + + /** Fake node constructor with given CookieSet + */ + AbstractNode(CookieSet set) { + super(Children.LEAF); + lookup = set; + } /** Clone the node. If the object implements {@link Cloneable}, * that is used; otherwise a {@link FilterNode filter node} Index: nodes/src/org/openide/nodes/CookieSet.java =================================================================== RCS file: /shared/data/ccvs/repository/openide/nodes/src/org/openide/nodes/CookieSet.java,v retrieving revision 1.5 diff -u -r1.5 CookieSet.java --- nodes/src/org/openide/nodes/CookieSet.java 14 Aug 2006 12:00:11 -0000 1.5 +++ nodes/src/org/openide/nodes/CookieSet.java 2 Nov 2006 16:05:45 -0000 @@ -26,6 +26,9 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; +import org.openide.util.Lookup; +import org.openide.util.lookup.AbstractLookup; +import org.openide.util.lookup.InstanceContent; /** Support class for storing cookies and @@ -35,7 +38,7 @@ * * @author Jaroslav Tulach */ -public final class CookieSet extends Object { +public final class CookieSet extends Object implements Lookup.Provider { /** variable to allow effecient communication with NodeLookup, Node.Cookie or Class or Set */ private static ThreadLocal QUERY_MODE = new ThreadLocal(); @@ -44,11 +47,58 @@ /** set of listeners */ private EventListenerList listeners = new EventListenerList(); + + /** potential instance content */ + private final CookieSetLkp ic; + /** lookup to use return from the cookie set, if initialized */ + private Lookup lookup; /** Default constructor. */ public CookieSet() { + this(null, null); + } + + private CookieSet(CookieSetLkp ic, Lookup lookup) { + this.ic = ic; + this.lookup = lookup; + } + + /** Factory method to create new, general purpose cookie set. + * The general purpose means that it is possible to store + * any object, into the cookie set and then obtain it using {@link #getLookup} + * and queries on the returned {@link Lookup}. The before object can + * be passed in if one wants to do a lazy initialization of the {@link CookieSet} + * content. + * + * @param before the interface to support lazy initialization + * @return new cookie set that can contain not only {@link Node.Cookie} but also + * any plain old java object + * @see #assign + * @since 7.0 + */ + public static CookieSet createGeneric(Before before) { + CookieSetLkp al = new CookieSetLkp(before); + return new CookieSet(al, al); + } + + /** The lookup associated with this cookie set. Keeps track of + * the same things that are in the cookie set, but presents them + * as being inside the lookup. + * + * @return the lookup representing this cookie set + * @since 7.0 + */ + public Lookup getLookup() { + synchronized (QUERY_MODE) { + if (lookup == null) { + AbstractNode an = new AbstractNode(this); + lookup = an.getLookup(); + } + } + return lookup; } + /** Add a new cookie to the set. If a cookie of the same * actual (not representation!) class is already there, * it is replaced. @@ -59,23 +109,39 @@ * @param cookie cookie to add */ public void add(Node.Cookie cookie) { + addImpl((Object)cookie); + fireChangeEvent(); + } + + private void addImpl(Object cookie) { synchronized (this) { registerCookie(cookie.getClass(), cookie); } - - fireChangeEvent(); + if (ic != null) { + ic.add(cookie); + } } /** Remove a cookie from the set. * @param cookie the cookie to remove */ public void remove(Node.Cookie cookie) { + removeImpl((Object)cookie); + fireChangeEvent(); + } + + void removeImpl(Object cookie) { synchronized (this) { unregisterCookie(cookie.getClass(), cookie); } - - fireChangeEvent(); + if (ic != null) { + ic.remove(cookie); + } } + + /** Associates a given set of instances with + */ + /** Get a cookie. * @@ -83,6 +149,10 @@ * @return a cookie assignable to the representation class, or null if there is none */ public T getCookie(Class clazz) { + if (ic != null) { + ic.beforeLookupImpl(clazz); + } + Node.Cookie ret = null; Object queryMode = QUERY_MODE.get(); @@ -90,13 +160,17 @@ R r = findR(clazz); if (r == null) { - return null; - } - - ret = r.cookie(); + if (queryMode == null || ic == null) { + return null; + } + } else { + ret = r.cookie(); - if (queryMode instanceof Set) { - ((Set) queryMode).addAll(map.keySet()); + if (queryMode instanceof Set) { + @SuppressWarnings("unchecked") + Set keys = (Set)queryMode; + keys.addAll(map.keySet()); + } } } @@ -110,10 +184,34 @@ // unwrap the cookie ret = ((CookieEntry) ret).getCookie(true); } + } else if (ret == null) { + if (ic != null && + (!Node.Cookie.class.isAssignableFrom(clazz) || clazz == Node.Cookie.class) + ) { + enhancedQueryMode(lookup, clazz); + ret = null; + } } return clazz.cast(ret); } + + static void enhancedQueryMode(Lookup lookup, Class clazz) { + Object type = QUERY_MODE.get(); + if (type != clazz) { + return; + } + Collection> items = lookup.lookupResult(clazz).allItems(); + if (items.size() == 0) { + return; + } + AbstractLookup.Pair[] arr = new AbstractLookup.Pair[items.size()]; + Iterator it = items.iterator(); + for (int i = 0; i < arr.length; i++) { + arr[i] = new PairWrap(it.next()); + } + QUERY_MODE.set(arr); + } /** Add a listener to changes in the cookie set. * @param l the listener to add @@ -128,7 +226,8 @@ public void removeChangeListener(ChangeListener l) { listeners.remove(ChangeListener.class, l); } - + + /** Node lookup starts its non-important query. */ static Object entryQueryMode(Class c) { @@ -149,12 +248,14 @@ /** Exits query mode. */ - static org.openide.util.lookup.AbstractLookup.Pair exitQueryMode(Object prev) { + static Collection exitQueryMode(Object prev) { Object cookie = QUERY_MODE.get(); QUERY_MODE.set(prev); if (cookie instanceof CookieSet.CookieEntry) { - return new CookieEntryPair((CookieSet.CookieEntry) cookie); + return Collections.singleton((AbstractLookup.Pair)new CookieEntryPair((CookieSet.CookieEntry) cookie)); + } else if (cookie instanceof AbstractLookup.Pair[]) { + return Arrays.asList((AbstractLookup.Pair[])cookie); } else { return null; } @@ -174,7 +275,7 @@ /** Fires change event */ - private void fireChangeEvent() { + final void fireChangeEvent() { Object[] arr = listeners.getListenerList(); if (arr.length > 0) { @@ -200,7 +301,7 @@ * @param c class or null * @param cookie cookie to attach */ - private void registerCookie(Class c, Node.Cookie cookie) { + private void registerCookie(Class c, Object cookie) { if ((c == null) || !Node.Cookie.class.isAssignableFrom(c)) { return; } @@ -213,7 +314,7 @@ map.put(c, r); } - r.add(cookie); + r.add((Node.Cookie)cookie); registerCookie(c.getSuperclass(), cookie); @@ -230,7 +331,7 @@ * @param c class or null * @param cookie cookie to attach */ - private void unregisterCookie(Class c, Node.Cookie cookie) { + private void unregisterCookie(Class c, Object cookie) { if ((c == null) || !Node.Cookie.class.isAssignableFrom(c)) { return; } @@ -242,7 +343,7 @@ if (r != null) { // remove the cookie - r.remove(cookie); + r.remove((Node.Cookie)cookie); } unregisterCookie(c.getSuperclass(), cookie); @@ -263,7 +364,9 @@ synchronized (this) { registerCookie(cookieClass, new CookieEntry(factory, cookieClass)); } - + if (ic != null) { + ic.add(new FactAndClass(cookieClass, factory), C.INSTANCE); + } fireChangeEvent(); } @@ -279,6 +382,11 @@ } } + if (ic != null) { + for (Class c : cookieClass) { + ic.add(new FactAndClass(c, factory), C.INSTANCE); + } + } fireChangeEvent(); } @@ -306,6 +414,9 @@ } } } + if (ic != null) { + ic.remove(new FactAndClass(cookieClass, factory), C.INSTANCE); + } fireChangeEvent(); } @@ -336,10 +447,69 @@ } } } + + if (ic != null) { + for (Class c : cookieClass) { + ic.remove(new FactAndClass(c, factory), C.INSTANCE); + } + } fireChangeEvent(); } - + + /** Removes all instances of clazz from the set and replaces them + * with newly provided instance(s). + * + * @param clazz the root clazz for cookies to remove + * @param instances the one or more instances to put into the lookup + * + * @since 7.0 + */ + public void assign(Class clazz, T... instances) { + if (Node.Cookie.class.isAssignableFrom(clazz)) { + Class cookieClazz = clazz.asSubclass(Node.Cookie.class); + for(;;) { + Node.Cookie cookie = getCookie(cookieClazz); + if (cookie != null) { + removeImpl(cookie); + } else { + break; + } + } + for (T t : instances) { + addImpl(t); + } + + fireChangeEvent(); + } else if (ic != null) { + synchronized (this) { + for (T t : instances) { + registerCookie(t.getClass(), t); + } + } + ic.replaceInstances(clazz, instances, this); + } + } + + /** Assignes a trigger that gets called everytime given class is about + * to be queried. Can be used only for cookie set created with + * {@link CookieSet#create(true)} and for classes that are not + * subclasses of Node.Cookie. + * + * @param clazz the trigger class (not subclass of Node.Cookie) + * @param run runnable to run when the task is queried + * + public void beforeLookup(Class clazz, Runnable run) { + if (Node.Cookie.class.isAssignableFrom(clazz) || clazz == Object.class) { + throw new IllegalArgumentException("Too generic class: " + clazz); // NOI18N + } + if (ic == null) { + throw new IllegalStateException("Can be used only on CookieSet.create(true)"); // NOI18N + } + ic.registerBeforeLookup(clazz, run); + } + */ + /** Finds a result in a map. */ private R findR(Class c) { @@ -365,6 +535,13 @@ */ T createCookie(Class klass); } + + /** Allows to update content of the cookie set just before + * a query for a given class is made. + */ + public interface Before { + public void beforeLookup(Class clazz); + } /** Entry for one Cookie */ private static class CookieEntry implements Node.Cookie { @@ -405,55 +582,7 @@ return ret; } - } - // end of CookieEntry - - /** Pair that represents an entry. - */ - private static final class CookieEntryPair extends org.openide.util.lookup.AbstractLookup.Pair { - private CookieEntry entry; - - public CookieEntryPair(CookieEntry e) { - this.entry = e; - } - - protected boolean creatorOf(Object obj) { - return obj == entry.getCookie(false); - } - - public String getDisplayName() { - return getId(); - } - - public String getId() { - return entry.klass.getName(); - } - - public Object getInstance() { - return entry.getCookie(true); - } - - public Class getType() { - return entry.klass; - } - - protected boolean instanceOf(Class c) { - return c.isAssignableFrom(entry.klass); - } - - public int hashCode() { - return entry.hashCode() + 5; - } - - public boolean equals(Object obj) { - if (obj instanceof CookieEntryPair) { - return ((CookieEntryPair) obj).entry == entry; - } - - return false; - } - } - // end of CookieEntryPair + } // end of CookieEntry /** Implementation of the result. */ @@ -513,6 +642,145 @@ */ public Node.Cookie cookie() { return ((cookies == null) || cookies.isEmpty()) ? null : cookies.get(0); + } + } + + /** Pair that wraps another Lookup.Item + */ + private static final class PairWrap extends AbstractLookup.Pair { + private Lookup.Item item; + private boolean created; + + public PairWrap(Lookup.Item item) { + this.item = item; + } + + protected boolean instanceOf(Class c) { + Class k = c; + return k.isAssignableFrom(getType()); + } + + protected boolean creatorOf(Object obj) { + return created && getInstance() == obj; + } + + public Object getInstance() { + created = true; + return item.getInstance(); + } + + public Class getType() { + return item.getType(); + } + + public String getId() { + return item.getId(); + } + + public String getDisplayName() { + return item.getDisplayName(); + } + + public int hashCode() { + return 777 + item.hashCode(); + } + + public boolean equals(Object object) { + if (object instanceof PairWrap) { + PairWrap p = (PairWrap)object; + return item.equals(p.item); + } + return false; + } + } // end of PairWrap + + /** Pair that represents an entry. + */ + private static final class CookieEntryPair extends AbstractLookup.Pair { + private CookieEntry entry; + + public CookieEntryPair(CookieEntry e) { + this.entry = e; + } + + protected boolean creatorOf(Object obj) { + return obj == entry.getCookie(false); + } + + public String getDisplayName() { + return getId(); + } + + public String getId() { + return entry.klass.getName(); + } + + public Object getInstance() { + return entry.getCookie(true); + } + + public Class getType() { + return entry.klass; + } + + protected boolean instanceOf(Class c) { + Class k = c; + return k.isAssignableFrom(entry.klass); + } + + public int hashCode() { + return entry.hashCode() + 5; + } + + public boolean equals(Object obj) { + if (obj instanceof CookieEntryPair) { + return ((CookieEntryPair) obj).entry == entry; + } + + return false; + } + } // end of CookieEntryPair + + private static final class FactAndClass { + final Class clazz; + final Factory factory; + + public FactAndClass(Class clazz, Factory factory) { + this.clazz = clazz; + this.factory = factory; + } + + public int hashCode() { + return clazz.hashCode() + factory.hashCode(); + } + + public boolean equals(Object o) { + if (o instanceof FactAndClass) { + FactAndClass f = (FactAndClass)o; + return f.clazz.equals(clazz) && f.factory == factory; + } + return false; + } + } + + private static class C implements InstanceContent.Convertor { + static final C INSTANCE = new C(); + + + public Node.Cookie convert(CookieSet.FactAndClass obj) { + return obj.factory.createCookie(obj.clazz); + } + + public Class type(CookieSet.FactAndClass obj) { + return obj.clazz; + } + + public String id(CookieSet.FactAndClass obj) { + return obj.clazz.getName(); + } + + public String displayName(CookieSet.FactAndClass obj) { + return obj.clazz.getName(); } } } Index: nodes/src/org/openide/nodes/CookieSetLkp.java =================================================================== RCS file: nodes/src/org/openide/nodes/CookieSetLkp.java diff -N nodes/src/org/openide/nodes/CookieSetLkp.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ nodes/src/org/openide/nodes/CookieSetLkp.java 2 Nov 2006 16:05:45 -0000 @@ -0,0 +1,318 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ +package org.openide.nodes; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import org.openide.util.lookup.AbstractLookup; +import org.openide.util.lookup.AbstractLookup.Pair; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.openide.util.Lookup; +import org.openide.util.lookup.InstanceContent; + + +/** Content for a cookie set. + */ +final class CookieSetLkp extends AbstractLookup { + private final CookieSet.Before before; + + public CookieSetLkp(CookieSet.Before b) { + this.before = b; + } + + public void add(Object obj) { + addPair(new SimpleItem(obj)); + } + public final void add(T inst, InstanceContent.Convertor conv) { + addPair(new ConvertingItem(inst, conv)); + } + + public void remove(Object obj) { + removePair(new SimpleItem(obj)); + } + public final void remove(T inst, InstanceContent.Convertor conv) { + removePair(new ConvertingItem(inst, conv)); + } + + void superRemovePair(Pair pair) { + removePair(pair); + } + + private ThreadLocal isInReplaceInst = new ThreadLocal(); + void replaceInstances(Class clazz, T[] instances, CookieSet set) { + Iterator it; + Set toRemove; + List pairs; + + Object prev = isInReplaceInst.get(); + try { + isInReplaceInst.set(this); + + it = lookupResult(Object.class).allItems().iterator(); + toRemove = new HashSet(lookupResult(clazz).allItems()); + pairs = new ArrayList(); + + boolean change = false; + int index = 0; + while (it.hasNext()) { + Lookup.Item item = it.next(); + assert item instanceof AbstractLookup.Pair; + + if (toRemove.remove(item)) { + if (index < instances.length) { + if (item instanceof SimpleItem) { + SimpleItem simple = (SimpleItem)item; + if (simple.obj == instances[index]) { + index++; + pairs.add(simple); + continue; + } + } + + change = true; + pairs.add(new SimpleItem(instances[index++])); + } else { + change = true; + } + } else { + pairs.add((AbstractLookup.Pair)item); + } + } + assert toRemove.isEmpty(); + + while (index < instances.length) { + change = true; + pairs.add(new SimpleItem(instances[index++])); + } + + if (change) { + setPairs(pairs); + set.fireChangeEvent(); + } + } finally { + isInReplaceInst.set(prev); + } + } + + protected void beforeLookup(Lookup.Template template) { + beforeLookupImpl(template.getType()); + } + + final void beforeLookupImpl(Class clazz) { + if (before != null && isInReplaceInst.get() == null) { + before.beforeLookup(clazz); + } + } + + /** Instance of one item representing an object. + */ + final static class SimpleItem extends Pair { + private T obj; + + /** Create an item. + * @obj object to register + */ + public SimpleItem(T obj) { + if (obj == null) { + throw new NullPointerException(); + } + this.obj = obj; + } + + /** Tests whether this item can produce object + * of class c. + */ + public boolean instanceOf(Class c) { + return c.isInstance(obj); + } + + /** Get instance of registered object. If convertor is specified then + * method InstanceLookup.Convertor.convertor is used and weak reference + * to converted object is saved. + * @return the instance of the object. + */ + public T getInstance() { + return obj; + } + + public boolean equals(Object o) { + if (o instanceof SimpleItem) { + return obj.equals(((SimpleItem) o).obj); + } else { + return false; + } + } + + public int hashCode() { + return obj.hashCode(); + } + + /** An identity of the item. + * @return string representing the item, that can be used for + * persistance purposes to locate the same item next time + */ + public String getId() { + return "IL[" + obj.toString(); // NOI18N + } + + /** Getter for display name of the item. + */ + public String getDisplayName() { + return obj.toString(); + } + + /** Method that can test whether an instance of a class has been created + * by this item. + * + * @param obj the instance + * @return if the item has already create an instance and it is the same + * as obj. + */ + protected boolean creatorOf(Object obj) { + return obj == this.obj; + } + + /** The class of this item. + * @return the correct class + */ + @SuppressWarnings("unchecked") + public Class getType() { + return (Class)obj.getClass(); + } + } // end of SimpleItem + + /** Instance of one item registered in the map. + */ + final static class ConvertingItem extends Pair { + /** registered object */ + private T obj; + + /** Reference to converted object. */ + private WeakReference ref; + + /** convertor to use */ + private InstanceContent.Convertor conv; + + /** Create an item. + * @obj object to register + * @conv a convertor, can be null. + */ + public ConvertingItem(T obj, InstanceContent.Convertor conv) { + this.obj = obj; + this.conv = conv; + } + + /** Tests whether this item can produce object + * of class c. + */ + public boolean instanceOf(Class c) { + return c.isAssignableFrom(getType()); + } + + /** Returns converted object or null if obj has not been converted yet + * or reference was cleared by garbage collector. + */ + private R getConverted() { + if (ref == null) { + return null; + } + + return ref.get(); + } + + /** Get instance of registered object. If convertor is specified then + * method InstanceLookup.Convertor.convertor is used and weak reference + * to converted object is saved. + * @return the instance of the object. + */ + public synchronized R getInstance() { + R converted = getConverted(); + + if (converted == null) { + converted = conv.convert(obj); + ref = new WeakReference(converted); + } + + return converted; + } + + public boolean equals(Object o) { + if (o instanceof ConvertingItem) { + return obj.equals(((ConvertingItem) o).obj); + } else { + return false; + } + } + + public int hashCode() { + return obj.hashCode(); + } + + /** An identity of the item. + * @return string representing the item, that can be used for + * persistance purposes to locate the same item next time + */ + public String getId() { + return conv.id(obj); + } + + /** Getter for display name of the item. + */ + public String getDisplayName() { + return conv.displayName(obj); + } + + /** Method that can test whether an instance of a class has been created + * by this item. + * + * @param obj the instance + * @return if the item has already create an instance and it is the same + * as obj. + */ + protected boolean creatorOf(Object obj) { + if (conv == null) { + return obj == this.obj; + } else { + return obj == getConverted(); + } + } + + /** The class of this item. + * @return the correct class + */ + @SuppressWarnings("unchecked") + public Class getType() { + R converted = getConverted(); + + if (converted == null) { + return conv.type(obj); + } + + return (Class)converted.getClass(); + } + } // end of ConvertingItem +} Index: nodes/src/org/openide/nodes/Node.java =================================================================== RCS file: /shared/data/ccvs/repository/openide/nodes/src/org/openide/nodes/Node.java,v retrieving revision 1.12 diff -u -r1.12 Node.java --- nodes/src/org/openide/nodes/Node.java 28 Oct 2006 21:57:35 -0000 1.12 +++ nodes/src/org/openide/nodes/Node.java 2 Nov 2006 16:05:45 -0000 @@ -691,7 +691,11 @@ Lookup l = internalLookup(true); if (l != null) { - return l.lookup(type); + Object obj = l.lookup(type); + if (Node.Cookie.class.isInstance(obj)) { + return type.cast(obj); + } + CookieSet.enhancedQueryMode(l, type); } return null; Index: nodes/src/org/openide/nodes/NodeLookup.java =================================================================== RCS file: /shared/data/ccvs/repository/openide/nodes/src/org/openide/nodes/NodeLookup.java,v retrieving revision 1.4 diff -u -r1.4 NodeLookup.java --- nodes/src/org/openide/nodes/NodeLookup.java 14 Aug 2006 12:00:11 -0000 1.4 +++ nodes/src/org/openide/nodes/NodeLookup.java 2 Nov 2006 16:05:45 -0000 @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import org.openide.util.Lookup.Template; import org.openide.util.lookup.AbstractLookup; @@ -62,29 +63,36 @@ * @param c class to query * @param colleciton to put Pair into if found */ - private static void addCookie(Node node, Class c, + private static void addCookie(Node node, Class c, Collection collection, java.util.Map fromPairToClass) { Object res; - AbstractLookup.Pair pair; + Collection pairs; Object prev = CookieSet.entryQueryMode(c); try { - res = node.getCookie(c); + @SuppressWarnings("unchecked") + Class fake = (Class)c; + res = node.getCookie(fake); } finally { - pair = CookieSet.exitQueryMode(prev); + pairs = CookieSet.exitQueryMode(prev); } - if (pair == null) { + if (pairs == null) { if (res == null) { return; } - pair = new LookupItem(res); + pairs = Collections.singleton((AbstractLookup.Pair)new LookupItem(res)); } - collection.add(pair); - fromPairToClass.put(pair, c); + collection.addAll(pairs); + for (AbstractLookup.Pair p : pairs) { + Class oldClazz = fromPairToClass.get(p); + if (oldClazz == null || c.isAssignableFrom(oldClazz)) { + fromPairToClass.put(p, c); + } + } } /** Notifies subclasses that a query is about to be processed. @@ -113,14 +121,14 @@ updateLookupAsCookiesAreChanged(c); } - // fallthru and update Node.Cookie if not yet - type = Node.Cookie.class; + // update Node.Cookie if not yet + if (!queriedCookieClasses.contains(Node.Cookie.class)) { + updateLookupAsCookiesAreChanged(Node.Cookie.class); + } } - if (Node.Cookie.class.isAssignableFrom(type)) { - if (!queriedCookieClasses.contains(type)) { - updateLookupAsCookiesAreChanged(type); - } + if (!queriedCookieClasses.contains(type)) { + updateLookupAsCookiesAreChanged(type); } } @@ -140,7 +148,7 @@ } instances = new java.util.LinkedHashSet(queriedCookieClasses.size()); - fromPairToQueryClass = new java.util.HashMap(); + fromPairToQueryClass = new java.util.LinkedHashMap(); java.util.Iterator it = /* #74334 */new ArrayList(queriedCookieClasses).iterator(); LookupItem nodePair = new LookupItem(node); @@ -159,6 +167,10 @@ public int compare(AbstractLookup.Pair p1, AbstractLookup.Pair p2) { Class c1 = m.get(p1); Class c2 = m.get(p2); + + if (c1 == c2) { + return 0; + } if (c1.isAssignableFrom(c2)) { return -1; Index: nodes/test/unit/src/org/openide/nodes/CookieSetTest.java =================================================================== RCS file: /shared/data/ccvs/repository/openide/nodes/test/unit/src/org/openide/nodes/CookieSetTest.java,v retrieving revision 1.3 diff -u -r1.3 CookieSetTest.java --- nodes/test/unit/src/org/openide/nodes/CookieSetTest.java 10 Aug 2006 19:41:01 -0000 1.3 +++ nodes/test/unit/src/org/openide/nodes/CookieSetTest.java 2 Nov 2006 16:05:45 -0000 @@ -19,6 +19,7 @@ package org.openide.nodes; +import com.sun.org.apache.bcel.internal.generic.ARRAYLENGTH; import junit.framework.*; import junit.textui.TestRunner; import java.util.*; @@ -29,6 +30,10 @@ import org.netbeans.junit.*; import javax.swing.event.ChangeListener; +import org.openide.util.Lookup; +import org.openide.util.LookupEvent; +import org.openide.util.LookupListener; +import org.openide.util.lookup.InstanceContent; /** Tests behaviour of CookieSet. * @@ -39,9 +44,6 @@ super(name); } - public static void main(String[] args) { - TestRunner.run(new NbTestSuite(CookieSetTest.class)); - } public void testAddRemove () throws Exception { CookieSet set = new CookieSet (); @@ -87,6 +89,8 @@ assertEquals ("One change expected", l.cnt (), 1); assertEquals ("C1 cookie", c1, set.getCookie (Node.Cookie.class)); assertNull ("Null index", set.getCookie (Index.class)); + + assertCookieSet(set); } /** Adding smaller and bigger and removing smaller. @@ -105,6 +109,7 @@ assertEquals ("C2 index", c2, set.getCookie (Index.class)); assertEquals ("C2 cookie", c2, set.getCookie (Node.Cookie.class)); + assertCookieSet(set); } /** Adding bigger and smaller and removing bigger. @@ -127,6 +132,8 @@ assertEquals ("C1 cookie", c1, set.getCookie (Node.Cookie.class)); assertEquals ("Null index", null, set.getCookie (Index.class)); + + assertCookieSet(set); } /** Tests behaviour of modifications via factory. @@ -169,6 +176,8 @@ // remove of a factory set.remove(C1.class, l); assertNull("Removed factory still returns a cookie", set.getCookie (C1.class)); + + assertCookieSet(set); } /** Tests behaviour of modifications via factory. @@ -198,6 +207,7 @@ assertNull ("Still nobody registered as C2", set.getCookie (C2.class)); + assertCookieSet(set); } public void testCookieSetThruLookupReturnsTheSame () throws Exception { @@ -235,6 +245,7 @@ set.add (c1); assertEquals ("Smaller takes preceedence", c1, set.getCookie (Node.Cookie.class)); assertEquals ("Smaller even in lookup", c1, n.getLookup ().lookup (Node.Cookie.class)); + assertCookieSet(set); } public void testCookieSetThruLookupImprovedVersionIssue47411 () throws Exception { @@ -280,12 +291,96 @@ assertEquals (null, an.getLookup ().lookup (SaveCookie.class)); assertEquals (a, an.getLookup ().lookup (OpenCookie.class)); assertEquals (x, an.getLookup ().lookup (EditCookie.class)); + assertCookieSet(set); + } + + private static void assertCookieSet(CookieSet en) { + if (en.getCookie(Node.Cookie.class) == null) { + assertEquals("Should be empty", 0, en.getLookup().lookupAll(Node.Cookie.class).size()); + return; + } + + Lookup.Result all = en.getLookup().lookupResult(Node.Cookie.class); + int cnt = 0; + for (Class c : all.allClasses()) { + Object o = en.getLookup().lookup(c); + assertEquals("Query for " + c, o, en.getCookie(c)); + cnt++; + } + + if (cnt == 0) { + fail("There shall be at least one object in lookup: " + cnt); + } + } + + public void testOneChangeInLookupWhenAddingMultipleElements() throws Exception { + CookieSet general = CookieSet.createGeneric(null); + + L listener = new L(); + Lookup.Result res = general.getLookup().lookupResult(String.class); + res.addLookupListener(listener); + res.allItems(); + + assertEquals("No change", 0, listener.cnt()); + + general.assign(String.class, "Ahoj", "Jardo"); + + assertEquals("One change", 1, listener.cnt()); + assertEquals("Two items", 2, res.allItems().size()); + + general.assign(String.class, "Ahoj", "Jardo"); + assertEquals("No change", 0, listener.cnt()); + assertEquals("Still two items", 2, res.allItems().size()); + + general.assign(String.class, "Ahoj"); + assertEquals("Yet one change", 1, listener.cnt()); + assertEquals("One item", 1, res.allItems().size()); + } + + public void testOneChangeInLookupWhenAddingMultipleElementsWithBefore() throws Exception { + class B implements CookieSet.Before { + CookieSet general; + private String[] asg; + public void assign(String... arr) { + asg = arr; + } + + public void beforeLookup(Class c) { + if (asg != null) { + general.assign(String.class, asg); + asg = null; + } + } + } + B b = new B(); + + b.general = CookieSet.createGeneric(b); + + L listener = new L(); + Lookup.Result res = b.general.getLookup().lookupResult(String.class); + res.addLookupListener(listener); + res.allItems(); + + assertEquals("No change", 0, listener.cnt()); + + b.assign("Ahoj", "Jardo"); + + assertEquals("Two items", 2, res.allItems().size()); + assertEquals("One change", 1, listener.cnt()); + + b.assign("Ahoj", "Jardo"); + assertEquals("Still two items", 2, res.allItems().size()); + assertEquals("No change", 0, listener.cnt()); + + b.assign("Ahoj"); + assertEquals("One item", 1, res.allItems().size()); + assertEquals("Yet one change", 1, listener.cnt()); } /** Change listener. */ private static final class L extends Object - implements ChangeListener, CookieSet.Factory { + implements LookupListener, ChangeListener, CookieSet.Factory { private int count; public void stateChanged(javax.swing.event.ChangeEvent changeEvent) { @@ -308,6 +403,10 @@ return new C1 (); } return new C2 (); + } + + public void resultChanged(LookupEvent ev) { + count++; } } Index: nodes/test/unit/src/org/openide/nodes/NodeLookupTest.java =================================================================== RCS file: /shared/data/ccvs/repository/openide/nodes/test/unit/src/org/openide/nodes/NodeLookupTest.java,v retrieving revision 1.4 diff -u -r1.4 NodeLookupTest.java --- nodes/test/unit/src/org/openide/nodes/NodeLookupTest.java 2 Aug 2006 06:53:30 -0000 1.4 +++ nodes/test/unit/src/org/openide/nodes/NodeLookupTest.java 2 Nov 2006 16:05:45 -0000 @@ -25,7 +25,9 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import junit.framework.Test; import org.netbeans.junit.NbTestCase; +import org.netbeans.junit.NbTestSuite; import org.openide.cookies.SaveCookie; import org.openide.util.Lookup; import org.openide.util.LookupListener; @@ -41,6 +43,11 @@ super(name); } + public static Test suite() { + //return new NodeLookupTest("testChangeInObjectVisibleInLookupThruFilterNodeWhenItOverridesGetCookie"); + return new NbTestSuite(NodeLookupTest.class); + } + public void testChangesAreFiredFromLookup () { CountableLookup lkp = new CountableLookup (); Node node = new AbstractNode (createChildren (), lkp); @@ -282,6 +289,12 @@ assertTrue ("And it is the one", c.iterator ().next () == fn); } + public void testChangeInObjectVisibleInLookup () { + CookieNode n = new CookieNode (); + n.setSet(CookieSet.createGeneric(null)); + checkInstanceInLookup (new Integer(1), n.cookieSet(), n.getLookup ()); + checkInstanceInLookup (new Node.Cookie() {}, n.cookieSet(), n.getLookup ()); + } public void testChangeInCookieVisibleInLookup () { CookieNode n = new CookieNode (); checkInstanceInLookup (new Node.Cookie() {}, n.cookieSet(), n.getLookup ()); @@ -292,36 +305,32 @@ FilterNode f = new FilterNode (n); checkInstanceInLookup (new Node.Cookie() {}, n.cookieSet(), f.getLookup ()); } + + public void testChangeInObjectVisibleInLookupThruFilterNode () { + CookieNode n = new CookieNode (); + n.setSet(CookieSet.createGeneric(null)); + FilterNode f = new FilterNode (n); + checkInstanceInLookup (new Node.Cookie() {}, n.cookieSet(), f.getLookup ()); + checkInstanceInLookup (new Integer(2), n.cookieSet(), f.getLookup ()); + } public void testChangeInCookieVisibleInLookupThruFilterNodeWhenItOverridesGetCookie () { CookieNode n = new CookieNode (); - class MyFilterNode extends FilterNode implements javax.swing.event.ChangeListener { - public CookieSet set = new CookieSet (); - - public MyFilterNode (Node n) { - super (n); - set.addChangeListener(this); - } - - public Node.Cookie getCookie (Class cl) { - Node.Cookie c = super.getCookie (cl); - if (c != null) { - return c; - } - return set.getCookie (cl); - } - - public void stateChanged (javax.swing.event.ChangeEvent ev) { - fireCookieChange (); - } - } - - MyFilterNode f = new MyFilterNode (n); + MyFilterNode f = new MyFilterNode (n, false); checkInstanceInLookup (new Node.Cookie() {}, n.cookieSet(), f.getLookup ()); checkInstanceInLookup (new Node.Cookie() {}, f.set, f.getLookup ()); } + public void testChangeInObjectVisibleInLookupThruFilterNodeWhenItOverridesGetCookie () { + CookieNode n = new CookieNode (); + n.setSet(CookieSet.createGeneric(null)); + + MyFilterNode f = new MyFilterNode (n, true); + + checkInstanceInLookup (new Integer(3), n.cookieSet(), f.getLookup ()); + checkInstanceInLookup (new Integer(4), f.set, f.getLookup ()); + } public void testFilterNodeDelegatesCorrectly () { class CN extends CookieNode implements SaveCookie { @@ -359,26 +368,26 @@ } } - private void checkInstanceInLookup (Node.Cookie obj, CookieSet ic, Lookup l) { + private void checkInstanceInLookup (Object obj, CookieSet ic, Lookup l) { Listener listener = new Listener (); Lookup.Result res = l.lookupResult(Object.class); Collection justToEnsureChangesToListenerWillBeFired = res.allItems (); res.addLookupListener(listener); - ic.add (obj); + ic.assign(obj.getClass(), obj); listener.assertEvents ("One change in lookup", -1, 1); assertEquals ("Can access cookie in the content", obj, l.lookup (obj.getClass ())); - ic.remove (obj); + ic.assign(obj.getClass()); listener.assertEvents ("One change in lookup", -1, 1); - ic.add (obj); + ic.assign(obj.getClass(), obj); listener.assertEvents ("One change in lookup", -1, 1); assertEquals ("Can access cookie in the content", obj, l.lookup (obj.getClass ())); - ic.remove (obj); + ic.assign(obj.getClass()); listener.assertEvents ("One change in lookup", -1, 1); } @@ -757,6 +766,27 @@ protected void beforeLookup (Lookup.Template t) { super.beforeLookup (t); queries.add (t.getType ()); + } + } + class MyFilterNode extends FilterNode implements javax.swing.event.ChangeListener { + public final CookieSet set; + + public MyFilterNode (Node n, boolean generalCookieSet) { + super (n); + set = generalCookieSet ? CookieSet.createGeneric(null) : new CookieSet(); + set.addChangeListener(this); + } + + public Node.Cookie getCookie (Class cl) { + Node.Cookie c = super.getCookie (cl); + if (c != null) { + return c; + } + return set.getCookie (cl); + } + + public void stateChanged (javax.swing.event.ChangeEvent ev) { + fireCookieChange (); } } }