diff -r dd9894d29b15 api.debugger/apichanges.xml --- a/api.debugger/apichanges.xml Fri Feb 20 12:19:19 2009 +0100 +++ b/api.debugger/apichanges.xml Fri Feb 20 17:56:10 2009 +0100 @@ -309,6 +309,23 @@ + + + Support for listening to changes of property values. + + + + + +

+ addPropertyChangeListener() and removePropertyChangeListener() + methods added to Properties class. +

+
+ + +
+ diff -r dd9894d29b15 api.debugger/manifest.mf --- a/api.debugger/manifest.mf Fri Feb 20 12:19:19 2009 +0100 +++ b/api.debugger/manifest.mf Fri Feb 20 17:56:10 2009 +0100 @@ -1,4 +1,4 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.api.debugger/1 OpenIDE-Module-Localizing-Bundle: org/netbeans/api/debugger/Bundle.properties -OpenIDE-Module-Specification-Version: 1.16 +OpenIDE-Module-Specification-Version: 1.17 diff -r dd9894d29b15 api.debugger/src/org/netbeans/api/debugger/Properties.java --- a/api.debugger/src/org/netbeans/api/debugger/Properties.java Fri Feb 20 12:19:19 2009 +0100 +++ b/api.debugger/src/org/netbeans/api/debugger/Properties.java Fri Feb 20 17:56:10 2009 +0100 @@ -44,12 +44,15 @@ import java.beans.Customizer; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; import java.lang.reflect.Array; import java.util.ArrayList; @@ -62,6 +65,7 @@ import java.util.Map; import java.util.Set; +import java.util.WeakHashMap; import org.openide.ErrorManager; import org.openide.filesystems.FileLock; import org.openide.filesystems.FileObject; @@ -319,6 +323,43 @@ */ public abstract Properties getProperties (String propertyName); + /** + * Add a property change listener to this properties instance. + * The listener fires a property change event when a new value of some property + * is set. + *

+ * Please note, that this properties object is not collected from memory + * sooner than all it's listeners. Therefore it's not necessray to + * keep a strong reference to this object while holding the listener. + * + * @param l The property change listener + * @throws UnsupportedOperationException if not supported. The default + * properties implementation retrieved by {@link #getDefault()} supports + * adding/removing listeners. + */ + public void addPropertyChangeListener(PropertyChangeListener l) { + throw new UnsupportedOperationException("Unsupported listening on "+getClass()+" properties."); + } + + /** + * Remove a property change listener from this properties instance. + *

+ * Please note, that this properties object is not collected from memory + * sooner than all it's listeners. Therefore it's not necessray to + * keep a strong reference to this object while holding the listener. + * OTOH it is necessary to remove all listeners or release all strong + * references to the listeners to allow collection of this properties + * object. + * + * @param l The property change listener + * @throws UnsupportedOperationException if not supported. The default + * properties implementation retrieved by {@link #getDefault()} supports + * adding/removing listeners. + */ + public void removePropertyChangeListener(PropertyChangeListener l) { + throw new UnsupportedOperationException("Unsupported listening on "+getClass()+" properties."); + } + // innerclasses ............................................................ @@ -493,6 +534,10 @@ private PrimitiveRegister impl = new PrimitiveRegister (); + private final Map> childProperties = new HashMap>(); + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + private final Map propertiesHeldByListener = new WeakHashMap(); + private void initReaders () { register = new HashMap(); readersList = DebuggerManager.getDebuggerManager().lookup(null, Reader.class); @@ -593,6 +638,7 @@ } else { impl.setProperty (propertyName, value); } + pcs.firePropertyChange(propertyName, null, value); } public int getInt (String propertyName, int defaultValue) { @@ -608,6 +654,7 @@ public void setInt (String propertyName, int value) { impl.setProperty (propertyName, Integer.toString (value)); + pcs.firePropertyChange(propertyName, null, value); } public char getChar (String propertyName, char defaultValue) { @@ -619,6 +666,7 @@ public void setChar (String propertyName, char value) { impl.setProperty (propertyName, Character.toString(value)); + pcs.firePropertyChange(propertyName, null, value); } public float getFloat (String propertyName, float defaultValue) { @@ -634,6 +682,7 @@ public void setFloat (String propertyName, float value) { impl.setProperty (propertyName, Float.toString (value)); + pcs.firePropertyChange(propertyName, null, value); } public long getLong (String propertyName, long defaultValue) { @@ -649,6 +698,7 @@ public void setLong (String propertyName, long value) { impl.setProperty (propertyName, Long.toString (value)); + pcs.firePropertyChange(propertyName, null, value); } public double getDouble (String propertyName, double defaultValue) { @@ -664,6 +714,7 @@ public void setDouble (String propertyName, double value) { impl.setProperty (propertyName, Double.toString (value)); + pcs.firePropertyChange(propertyName, null, value); } public boolean getBoolean (String propertyName, boolean defaultValue) { @@ -675,6 +726,7 @@ public void setBoolean (String propertyName, boolean value) { impl.setProperty (propertyName, value ? "true" : "false"); + pcs.firePropertyChange(propertyName, null, value); } public byte getByte (String propertyName, byte defaultValue) { @@ -690,6 +742,7 @@ public void setByte (String propertyName, byte value) { impl.setProperty (propertyName, Byte.toString (value)); + pcs.firePropertyChange(propertyName, null, value); } public short getShort (String propertyName, short defaultValue) { @@ -705,6 +758,7 @@ public void setShort (String propertyName, short value) { impl.setProperty (propertyName, Short.toString (value)); + pcs.firePropertyChange(propertyName, null, value); } public Object getObject (String propertyName, Object defaultValue) { @@ -758,36 +812,29 @@ synchronized(impl) { if (value == null) { impl.setProperty (propertyName, "# null"); - return; + } else if (value instanceof String) { + setString (propertyName, (String) value); + } else if (value instanceof Map) { + setMap (propertyName, (Map) value); + } else if (value instanceof Collection) { + setCollection (propertyName, (Collection) value); + } else if (value instanceof Object[]) { + setArray (propertyName, (Object[]) value); + } else { + + // find register + Reader r = findReader (value.getClass ().getName ()); + if (r == null) { + ErrorManager.getDefault().log ("Can not write object " + value); + return; + } + + // write + r.write (value, getProperties (propertyName)); + impl.setProperty (propertyName, "# " + value.getClass ().getName ()); } - if (value instanceof String) { - setString (propertyName, (String) value); - return; - } - if (value instanceof Map) { - setMap (propertyName, (Map) value); - return; - } - if (value instanceof Collection) { - setCollection (propertyName, (Collection) value); - return; - } - if (value instanceof Object[]) { - setArray (propertyName, (Object[]) value); - return; - } - - // find register - Reader r = findReader (value.getClass ().getName ()); - if (r == null) { - ErrorManager.getDefault().log ("Can not write object " + value); - return; - } - - // write - r.write (value, getProperties (propertyName)); - impl.setProperty (propertyName, "# " + value.getClass ().getName ()); } + pcs.firePropertyChange(propertyName, null, value); } public Object[] getArray (String propertyName, Object[] defaultValue) { @@ -829,6 +876,7 @@ for (i = 0; i < k; i++) p.setObject ("" + i, value [i]); } + pcs.firePropertyChange(propertyName, null, value); } public Collection getCollection (String propertyName, Collection defaultValue) { @@ -881,6 +929,7 @@ i++; } } + pcs.firePropertyChange(propertyName, null, value); } public Map getMap (String propertyName, Map defaultValue) { @@ -931,12 +980,42 @@ i++; } } + pcs.firePropertyChange(propertyName, null, value); } public Properties getProperties (String propertyName) { - return new DelegatingProperties (this, propertyName); + synchronized (childProperties) { + Reference propRef = childProperties.get(propertyName); + if (propRef != null) { + Properties p = propRef.get(); + if (p != null) { + return p; + } + } + Properties p = new DelegatingProperties (this, propertyName); + propRef = new WeakReference(p); + childProperties.put(propertyName, propRef); + return p; + } } - + + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + synchronized (propertiesHeldByListener) { + propertiesHeldByListener.put(l, this); + } + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + synchronized (propertiesHeldByListener) { + propertiesHeldByListener.remove(l); + } + } + + private static ClassLoader classLoader; private static ClassLoader getClassLoader () { if (classLoader == null) @@ -951,6 +1030,10 @@ private Properties delegatingProperties; private String root; + private final Map> childProperties = + new HashMap>(); + private final Map delegatingListeners = + new WeakHashMap(); DelegatingProperties (Properties properties, String root) { @@ -1063,7 +1146,60 @@ } public Properties getProperties (String propertyName) { - return new DelegatingProperties (delegatingProperties, root + '.' + propertyName); + synchronized (childProperties) { + Reference propRef = childProperties.get(propertyName); + if (propRef != null) { + Properties p = propRef.get(); + if (p != null) { + return p; + } + } + Properties p = new DelegatingProperties (delegatingProperties, root + '.' + propertyName); + propRef = new WeakReference(p); + childProperties.put(propertyName, propRef); + return p; + } } + + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + PropertyChangeListener delegate = new DelegatingPropertyChangeListener(l); + synchronized (delegatingListeners) { + delegatingListeners.put(l, delegate); + } + delegatingProperties.addPropertyChangeListener(delegate); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + PropertyChangeListener delegate; + synchronized (delegatingListeners) { + delegate = delegatingListeners.get(l); + } + if (delegate != null) { + delegatingProperties.removePropertyChangeListener(delegate); + } + } + + private class DelegatingPropertyChangeListener implements PropertyChangeListener { + + private PropertyChangeListener delegate; + + public DelegatingPropertyChangeListener(PropertyChangeListener delegate) { + this.delegate = delegate; + } + + public void propertyChange(PropertyChangeEvent evt) { + PropertyChangeEvent delegateEvt = new PropertyChangeEvent( + DelegatingProperties.this, + evt.getPropertyName().substring(root.length() + 1), + evt.getOldValue(), + evt.getNewValue()); + delegateEvt.setPropagationId(evt.getPropagationId()); + delegate.propertyChange(delegateEvt); + } + + } + } } diff -r dd9894d29b15 spi.debugger.ui/test/unit/src/org/netbeans/api/debugger/PropertiesTest.java --- a/spi.debugger.ui/test/unit/src/org/netbeans/api/debugger/PropertiesTest.java Fri Feb 20 12:19:19 2009 +0100 +++ b/spi.debugger.ui/test/unit/src/org/netbeans/api/debugger/PropertiesTest.java Fri Feb 20 17:56:10 2009 +0100 @@ -42,6 +42,9 @@ package org.netbeans.api.debugger; import java.awt.Rectangle; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.ArrayList; import java.util.Collection; @@ -49,6 +52,7 @@ import java.util.HashMap; import java.util.Map; import junit.framework.TestCase; +import org.netbeans.junit.NbTestCase; /** * Test of the Properties class @@ -132,7 +136,60 @@ assertEquals(t2, p.getObject("test 2", null)); assertEquals(t3, p.getObject("test 3", null)); } - + + public void testListeners() throws Exception { + // First test that the properties can be collected: + Properties p = Properties.getDefault(); + p = p.getProperties("listening"); + WeakReference pRef = new WeakReference(p); + p = null; + System.gc(); + NbTestCase.assertGC("The Properties are not collected.", pRef); + //System.err.println("testListeners(): Properties can be collected O.K."); + + // Then attach a listener and test that they are not collected: + p = Properties.getDefault().getProperties("listening"); + final PropertyChangeEvent[] evtRef = new PropertyChangeEvent[] { null }; + PropertyChangeListener l = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + evtRef[0] = evt; + } + }; + p.addPropertyChangeListener(l); + pRef = new WeakReference(p); + p = null; + System.gc(); + boolean collected; + try { + NbTestCase.assertGC("The Properties are not collected.", pRef); + collected = true; + } catch (AssertionError ae) { + collected = false; + } + assertFalse("Properties were collected even when we hold a listener!", collected); + //System.err.println("testListeners(): Properties were not collected with a listener O.K."); + + // The properties we listen on still live... + p = Properties.getDefault().getProperties("listening"); + p.setDouble("double", Double.NEGATIVE_INFINITY); + assertNotNull("Property change was not received.", evtRef[0]); + assertEquals("double", evtRef[0].getPropertyName()); + assertEquals(Double.NEGATIVE_INFINITY, evtRef[0].getNewValue()); + //System.err.println("testListeners(): Properties event O.K."); + + pRef.get().removePropertyChangeListener(l); + p = null; + System.gc(); + try { + NbTestCase.assertGC("The Properties are not collected after remove of listener.", pRef); + } catch (AssertionError ae) { + // TODO: File a defect for NbTestCase + if (!ae.getMessage().endsWith("Not found!!!")) { + throw ae; + } + } + } + /** Stress test of multi-threaded get/set */ public void testStressGetSet() throws Exception { Properties p = Properties.getDefault();