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 extends Properties> 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();