Index: openide/api/doc/changes/apichanges.xml =================================================================== RCS file: /cvs/openide/api/doc/changes/apichanges.xml,v retrieving revision 1.217 diff -u -r1.217 apichanges.xml --- openide/api/doc/changes/apichanges.xml 25 Aug 2004 13:34:49 -0000 1.217 +++ openide/api/doc/changes/apichanges.xml 2 Sep 2004 12:26:00 -0000 @@ -114,6 +114,27 @@ + + + Added SharedClassObject.reset method to allow subclasses to implement reset correctly + + + + + + The new SharedClassObject.reset method is called + by the infrastructure in moments when an original (at the time + of start) state of an option or any other SharedClassObject + is requested. Interested subclasses are free to implement any kind of clean + then need. The SystemOption provides a default + implementation based on fired property changed events, so + its subclasses usually do not need to do anything. + + + + + + Fixed TreeView.drag/dropActive switcher Index: openide/src/org/openide/options/SystemOption.java =================================================================== RCS file: /cvs/openide/src/org/openide/options/SystemOption.java,v retrieving revision 1.36 diff -u -r1.36 SystemOption.java --- openide/src/org/openide/options/SystemOption.java 3 Aug 2004 16:44:23 -0000 1.36 +++ openide/src/org/openide/options/SystemOption.java 2 Sep 2004 12:26:01 -0000 @@ -22,6 +22,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; +import java.util.Map; import org.openide.ErrorManager; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; @@ -50,12 +51,11 @@ private static final Object PROP_LOADING = new Object (); /** property to indicate that the option is currently loading its data */ private static final Object PROP_STORING = new Object (); + /** property that holds a Map that stores old values */ + private static final Object PROP_ORIGINAL_VALUES = new Object (); /** Default constructor. */ public SystemOption() { - // SystemOption must declare this property in order to be correctly deserialized - // by SharedClassObject.findObject function - putProperty ("netbeans.systemoption.hack", null); // NOI18N } /** Fire a property change event to all listeners. Delays @@ -68,6 +68,23 @@ protected void firePropertyChange ( String name, Object oldValue, Object newValue ) { + if (name != null && oldValue != null) { + Map originalValues = (Map)getProperty (PROP_ORIGINAL_VALUES); + if (originalValues == null) { + originalValues = new HashMap (); + putProperty (PROP_ORIGINAL_VALUES, originalValues); + } + if (originalValues.get (name) == null) { + if (getProperty (name) == null) { + // this is supposed to be setter + originalValues.put (name, new Box (oldValue)); + } else { + // regular usage of putProperty (....); + originalValues.put (name, oldValue); + } + } + } + if (getProperty (PROP_LOADING) != null) { // somebody is loading, assign any object different than // this to indicate that firing should occure @@ -78,6 +95,60 @@ super.firePropertyChange (name, oldValue, newValue); } + /** Implements the reset by setting back all properties that were + * modified. A modified property has fired a + * PropertyChangeEvent with + * non-null name and non-null old value. The name and value are + * remembered and this method sets them back to original value. + *

+ * Subclasses are free to override this method and reimplement the + * reset by themselves. + * + * @since 4.46 + */ + protected void reset () { + synchronized (getLock ()) { + Map m = (Map)getProperty (PROP_ORIGINAL_VALUES); + if (m == null || m.isEmpty ()) return; + + java.util.Iterator it = m.entrySet ().iterator (); + WHILE: while (it.hasNext ()) { + Map.Entry e = (Map.Entry)it.next (); + if (e.getValue () instanceof Box) { + Object value = ((Box)e.getValue ()).value; + try { + // gets info about all properties that were added by subclass + BeanInfo info = org.openide.util.Utilities.getBeanInfo (getClass (), SystemOption.class); + PropertyDescriptor[] desc = info.getPropertyDescriptors (); + + for (int i = 0; i < desc.length; i++) { + if (e.getKey ().equals (desc[i].getName ())) { + // our property + Method write = desc[i].getWriteMethod (); + if (write != null) { + write.invoke (this, new Object[] { value }); + } + continue WHILE; + } + } + } catch (InvocationTargetException ex) { + // exception thrown + ErrorManager.getDefault ().notify (ErrorManager.INFORMATIONAL, ex); + } catch (IllegalAccessException ex) { + ErrorManager.getDefault ().notify (ErrorManager.INFORMATIONAL, ex); + } catch (IntrospectionException ex) { + ErrorManager.getDefault ().notify (ErrorManager.INFORMATIONAL, ex); + } + } else { + putProperty (e.getKey (), e.getValue ()); + } + } + // reset all remembered values + putProperty (PROP_ORIGINAL_VALUES, null); + } + super.firePropertyChange (null, null, null); + } + /** Write all properties of this object (or subclasses) to an object output. * @param out the output stream * @exception IOException on error @@ -328,4 +399,14 @@ protected final boolean isWriteExternal () { return getProperty (PROP_STORING) != null; } + + /** A wrapper object to indicate that a setter should be called + * when reseting to default. + */ + private static final class Box extends Object { + public Object value; + public Box (Object v) { + this.value = v; + } + } // end of Box } Index: openide/src/org/openide/util/SharedClassObject.java =================================================================== RCS file: /cvs/openide/src/org/openide/util/SharedClassObject.java,v retrieving revision 1.64 diff -u -r1.64 SharedClassObject.java --- openide/src/org/openide/util/SharedClassObject.java 18 Aug 2004 21:42:59 -0000 1.64 +++ openide/src/org/openide/util/SharedClassObject.java 2 Sep 2004 12:26:01 -0000 @@ -40,8 +40,6 @@ /** Name of the method used to determine whether an option is global or not. */ static final String GLOBAL_METHOD_NAME = "isGlobal"; // NOI18N - private byte [] defaultInstance = null; - /** property change support (PropertyChangeSupport) */ private static final Object PROP_SUPPORT = new Object (); @@ -237,10 +235,6 @@ throw new NullPointerException("Tried to pass null key (value=" + value + ") to putProperty"); // NOI18N } synchronized (getLock ()) { - if (key.equals ("netbeans.systemoption.hack")) { // NOI18N - systemOption = true; - return null; - } if (waitingOnSystemOption && key != PROP_SUPPORT && prematureSystemOptionMutation == null && !dataEntry.isInInitialize() && !inReadExternal) { // See below in findObject. Note that if we are still in initialize(), @@ -472,8 +466,6 @@ } } if (created) { - obj.reset (); - // This hack was created due to the remove of SystemOptions deserialization // from project open operation, all SystemOptions are deserialized at this place // the first time anybody asks for the option. @@ -481,7 +473,7 @@ // otherwise it can cause deadlocks. // Lookup in the active session is used to find serialized state of the option, // if such state exists it is deserialized before the object is returned from lookup. - if (obj.systemOption) { + if (obj.isSystemOption ()) { // Lookup will find serialized version of searched object and deserialize it final Lookup.Result r = Lookup.getDefault().lookup(new Lookup.Template(clazz)); if (r.allInstances().isEmpty()) { @@ -518,6 +510,18 @@ return obj; } } + + /** checks whether we are instance of system option. + */ + private boolean isSystemOption () { + Class c = this.getClass (); + while (c != SharedClassObject.class) { + if ("org.openide.options.SystemOption".equals (c.getName ())) return true; // NOI18N + c = c.getSuperclass (); + } + return false; + } + // See above: private static void warn(Throwable t) { err.notify(ErrorManager.INFORMATIONAL, t); @@ -547,57 +551,19 @@ } } - /** Resets shared data to it default value. */ - private void reset () { - if (!systemOption || !isProjectOption ()) { - return; - } - - synchronized (getLock ()) { - // [PENDING] should be changed to next line after all options in layers will - // use put{get}Property and initilaize properly - // dataEntry.reset (this); - - if (defaultInstance == null) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream (1024); - ObjectOutput oo = new org.openide.util.io.NbObjectOutputStream (baos); - oo.writeObject (this); - defaultInstance = baos.toByteArray (); - } catch (IOException e) { - defaultInstance = null; - } - return; - } - - try { - ByteArrayInputStream bais = new ByteArrayInputStream (defaultInstance); - ObjectInputStream oi = new org.openide.util.io.NbObjectInputStream (bais); - oi.readObject (); - } catch (Exception e) { - // ignore and leave it as it is - } - } - } - - /** - * Test if the object is Project specific. - * @return true if the object is Project specific + /** Is called by the infrastructure in cases when a clean instance is requested. + * As instances of SharedClassObject are singletons, there is + * no way how to create new instance that would not contain the same data + * as previously existing one. This method allows all subclasses that care + * about the ability to refresh the settings (like SystemOptions) + * to be notified about the cleaning request and clean their settings themselves. + *

+ * Default implementation does nothing. + * + * @since made protected in version 4.46 */ - private boolean isProjectOption () { - try { - Class clazz = getClass (); - // the old hack with undocumented method isGlobal - Method m = clazz.getMethod(GLOBAL_METHOD_NAME, new Class[] {}); - m.setAccessible(true); - Boolean b = (Boolean) m.invoke(this, new Object[] {}); - return !b.booleanValue(); - } catch (Exception ex) { - // ignore and return default - } - return false; + protected void reset () { } - /** Class that is used as default write replace. */ Index: openide/test/unit/src/org/openide/loaders/InstanceDataObjectTest.java =================================================================== RCS file: /cvs/openide/test/unit/src/org/openide/loaders/InstanceDataObjectTest.java,v retrieving revision 1.32 diff -u -r1.32 InstanceDataObjectTest.java --- openide/test/unit/src/org/openide/loaders/InstanceDataObjectTest.java 31 Aug 2004 11:21:22 -0000 1.32 +++ openide/test/unit/src/org/openide/loaders/InstanceDataObjectTest.java 2 Sep 2004 12:26:02 -0000 @@ -564,6 +564,49 @@ assertNotNull ("Has cookie", ic); assertEquals ("And its value is x", x, ic.instanceCreate ()); } + + + public void testWeAreAbleToResetSharedClassObjectByCallingResetOnItIssue20962 () throws Exception { + FileObject lookupFO; + { + Object x = Setting.findObject (Setting.class, true); + lookupFO = lfs.findResource("/system/Services/lookupTest"); + DataFolder folderTest = DataFolder.findFolder(lookupFO); + InstanceDataObject ido = InstanceDataObject.create (folderTest, "testLookupRefresh", x, null); + lookupFO = ido.getPrimaryFile (); + WeakReference ref = new WeakReference (ido); + Setting.resetCalled = 0; + } + + InstanceDataObject ido = (InstanceDataObject)DataObject.find (lookupFO); + InstanceCookie ic = (InstanceCookie)ido.getCookie (InstanceCookie.class); + assertNotNull ("Has cookie", ic); + Object obj = ic.instanceCreate (); + assertNotNull ("Not null", obj); + assertEquals ("It is settings", Setting.class, obj.getClass ()); + + + FileLock lock = lookupFO.lock (); + OutputStream os = lookupFO.getOutputStream (lock); + + PrintWriter pw = new PrintWriter (os); + pw.println (""); + pw.println (""); + pw.println (""); + pw.println (" "); + pw.println (" "); + pw.println (" "); + pw.println (""); + pw.close (); + lock.releaseLock (); + + ic = (InstanceCookie)ido.getCookie (InstanceCookie.class); + assertNotNull ("Has cookie", ic); + assertNotNull ("Not null", obj); + assertEquals ("It is settings", Setting.class, obj.getClass ()); + Setting s = (Setting)Setting.findObject (Setting.class, true); + assertEquals ("Refresh has been called", 1, s.resetCalled); + } /** Checks whether the instance is not saved multiple times. * @@ -920,4 +963,15 @@ } } + public static final class Setting extends org.openide.options.SystemOption { + private static int resetCalled; + + protected void reset () { + resetCalled++; + } + + public String displayName () { + return "My Setting"; + } + } } Index: openide/test/unit/src/org/openide/options/SystemOptionTest.java =================================================================== RCS file: /cvs/openide/test/unit/src/org/openide/options/SystemOptionTest.java,v retrieving revision 1.3 diff -u -r1.3 SystemOptionTest.java --- openide/test/unit/src/org/openide/options/SystemOptionTest.java 15 May 2003 06:38:06 -0000 1.3 +++ openide/test/unit/src/org/openide/options/SystemOptionTest.java 2 Sep 2004 12:26:02 -0000 @@ -93,6 +93,68 @@ } } + // + // Implements reset to default values + // + + public void testSimpleResetToOldValuesWhenTheyWereInitializedInInitialize () throws Exception { + SimpleOption s = (SimpleOption)SimpleOption.findObject (SimpleOption.class, true); + + s.setX (-10); + s.setY ("-10"); + + s.reset (); + + assertEquals ("Was 3 in initialize", 3, s.getX ()); + assertEquals ("Was hello", "hello", s.getY ()); + } + + public void testSimpleResetEvenWhenWeHaveStaticInitialValues () throws Exception { + SimpleOption2 s = (SimpleOption2)SimpleOption.findObject (SimpleOption2.class, true); + + class PL implements java.beans.PropertyChangeListener { + public int cnt; + public String name; + + public void propertyChange (java.beans.PropertyChangeEvent ev) { + cnt++; + name = ev.getPropertyName (); + } + + public void assertChange (String name, int cnt) { + if (name != null) { + assertEquals ("The this property had to change", name, this.name); + this.name = null; + } + if (cnt != -1) { + assertEquals ("This number of times", cnt, this.cnt); + this.cnt = 0; + } + } + } + + PL pl = new PL (); + s.addPropertyChangeListener (pl); + + s.setX (-10); + pl.assertChange ("x", 1); + s.setX (-9); + pl.assertChange ("x", 1); + s.setY ("-10"); + pl.assertChange ("y", 1); + s.setX2 (7777); + s.setY2 ("-4444"); + + s.reset (); + + assertEquals ("Was 3 in initialize", 3, s.getX ()); + assertEquals ("Was hello", "hello", s.getY ()); + assertEquals ("2 Was 3 in initialize", 3, s.getX2 ()); + assertEquals ("2 Was hello", "hello", s.getY2 ()); + + + } + // XXX test that serialization works and matches deserialization // (hint: use MaskingURLClassLoader from SharedClassObjectTest) @@ -133,6 +195,10 @@ public void setY(String y) { putProperty("y", y, true); } + + public void reset () { + super.reset (); + } } public static final class SimpleOption2 extends SystemOption { @@ -174,6 +240,9 @@ String old = y2; y2 = nue; firePropertyChange("y2", old, nue); + } + public void reset () { + super.reset (); } }