diff --git a/openide.util/apichanges.xml b/openide.util/apichanges.xml --- a/openide.util/apichanges.xml +++ b/openide.util/apichanges.xml @@ -51,6 +51,27 @@ Actions API + + + Weak property and vetoable listeners for a specific property name. + + + + + +

+ WeakListeners class got variants of propertyChange() + and vetoableChange() methods, which take the property name. + They are to be used as an argument to + addPropertyChangeListener(String propertyName, PropertyChangeListener listener) + and addVetoableChangeListener(String propertyName, PropertyChangeListener listener) + methods respectively, and will call the appropriate remove methods with the provided + property name. +

+
+ + +
Platform dependent sound when invoking a disabled action. diff --git a/openide.util/manifest.mf b/openide.util/manifest.mf --- a/openide.util/manifest.mf +++ b/openide.util/manifest.mf @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.openide.util OpenIDE-Module-Localizing-Bundle: org/openide/util/Bundle.properties -OpenIDE-Module-Specification-Version: 8.40 +OpenIDE-Module-Specification-Version: 8.41 diff --git a/openide.util/src/org/openide/util/WeakListenerImpl.java b/openide.util/src/org/openide/util/WeakListenerImpl.java --- a/openide.util/src/org/openide/util/WeakListenerImpl.java +++ b/openide.util/src/org/openide/util/WeakListenerImpl.java @@ -96,8 +96,17 @@ * listenerClass */ protected WeakListenerImpl(Class listenerClass, java.util.EventListener l) { + this(listenerClass, l, null); + } + + /** + * @param listenerClass class/interface of the listener + * @param l listener to delegate to, l must be an instance of + * listenerClass + */ + protected WeakListenerImpl(Class listenerClass, java.util.EventListener l, String name) { this.listenerClass = listenerClass; - ref = new ListenerReference(l, this); + ref = new ListenerReference(l, name, this); } /** Setter for the source field. If a WeakReference to an underlying listener is @@ -185,6 +194,15 @@ PropertyChange(Class clazz, PropertyChangeListener l) { super(clazz, l); } + + /** Constructor. + * @param clazz required class + * @param l listener to delegate to + * @param propertyName the associated property name + */ + PropertyChange(PropertyChangeListener l, String propertyName) { + super(PropertyChangeListener.class, l, propertyName); + } /** Tests if the object we reference to still exists and * if so, delegate to it. Otherwise remove from the source @@ -217,6 +235,14 @@ super(VetoableChangeListener.class, l); } + /** Constructor. + * @param l listener to delegate to + * @param propertyName the associated property name + */ + VetoableChange(VetoableChangeListener l, String propertyName) { + super(VetoableChangeListener.class, l, propertyName); + } + /** Tests if the object we reference to still exists and * if so, delegate to it. Otherwise remove from the source * if it has removePropertyChangeListener method. @@ -513,12 +539,15 @@ private static Class lastClass; private static String lastMethodName; private static Method lastRemove; + private static Method lastNamedRemove; private static final Object LOCK = new Object(); WeakListenerImpl weakListener; + private String name; - ListenerReference(Object ref, WeakListenerImpl weakListener) { + ListenerReference(Object ref, String name, WeakListenerImpl weakListener) { super(ref, Utilities.activeReferenceQueue()); this.weakListener = weakListener; + this.name = name; } /** Requestes cleanup of the listener with a provided source. @@ -534,7 +563,7 @@ // plan new cleanup into the activeReferenceQueue with this listener and // provided source weakListener.source = new WeakReference (source) { - ListenerReference doNotGCRef = new ListenerReference(new Object(), weakListener); + ListenerReference doNotGCRef = new ListenerReference(new Object(), name, weakListener); }; } } @@ -566,14 +595,24 @@ String methodName = ref.removeMethodName(); synchronized (LOCK) { - if (lastClass == methodClass && lastRemove != null && methodName.equals(lastMethodName)) { - remove = lastRemove; + if (lastClass == methodClass) { + if (name == null) { + if (lastRemove != null && methodName.equals(lastMethodName)) { + remove = lastRemove; + } + } else { + if (lastNamedRemove != null && methodName.equals(lastMethodName)) { + remove = lastNamedRemove; + } + } } } // get the remove method or use the last one if (remove == null) { - remove = getRemoveMethod(methodClass, methodName, ref.listenerClass); + if (name == null) { + remove = getRemoveMethod(methodClass, methodName, ref.listenerClass); + } if (remove == null) { remove = getRemoveMethod(methodClass, methodName, String.class, ref.listenerClass); } @@ -585,16 +624,21 @@ synchronized (LOCK) { lastClass = methodClass; lastMethodName = methodName; - lastRemove = remove; + if (name == null) { + lastRemove = remove; + } else { + lastNamedRemove = remove; + } } } } - + try { if (remove.getParameterTypes().length == 1) { remove.invoke(src, new Object[]{ref.getImplementator()}); } else { - remove.invoke(src, new Object[]{"", ref.getImplementator()}); + String nameParam = (name == null) ? "" : name; + remove.invoke(src, new Object[]{nameParam, ref.getImplementator()}); } } catch (Exception ex) { // from invoke(), should not happen // #151415 - ignore exception from AbstractPreferences if node has been removed diff --git a/openide.util/src/org/openide/util/WeakListeners.java b/openide.util/src/org/openide/util/WeakListeners.java --- a/openide.util/src/org/openide/util/WeakListeners.java +++ b/openide.util/src/org/openide/util/WeakListeners.java @@ -285,6 +285,29 @@ return wl; } + /** Creates a weak implementation of PropertyChangeListener to be attached + * for a specific property name. Use with + * addPropertyChangeListener(String propertyName, PropertyChangeListener listener) + * method. It calls + * removePropertyChangeListener(String propertyName, PropertyChangeListener listener) + * with the given property name to unregister the listener. Be sure to pass + * the same propertyName to this method and to addPropertyChangeListener() + * method. + * + * @param l the listener to delegate to + * @param propertyName the name of the property to listen on changes + * @param source the source that the listener should detach from when + * listener l is freed, can be null + * @return a PropertyChangeListener delegating to l. + * @since 8.41 + */ + public static PropertyChangeListener propertyChange(PropertyChangeListener l, String propertyName, Object source) { + WeakListenerImpl.PropertyChange wl = new WeakListenerImpl.PropertyChange(l, propertyName); + wl.setSource(source); + + return wl; + } + /** Creates a weak implementation of VetoableChangeListener. * * @param l the listener to delegate to @@ -299,6 +322,29 @@ return wl; } + /** Creates a weak implementation of VetoableChangeListener to be attached + * for a specific property name. Use with + * addVetoableChangeListener(String propertyName, PropertyChangeListener listener) + * method. It calls + * removeVetoableChangeListener(String propertyName, PropertyChangeListener listener) + * with the given property name to unregister the listener. Be sure to pass + * the same propertyName to this method and to addVetoableChangeListener() + * method. + * + * @param l the listener to delegate to + * @param propertyName the name of the property to listen on changes + * @param source the source that the listener should detach from when + * listener l is freed, can be null + * @return a VetoableChangeListener delegating to l. + * @since 8.41 + */ + public static VetoableChangeListener vetoableChange(VetoableChangeListener l, String propertyName, Object source) { + WeakListenerImpl.VetoableChange wl = new WeakListenerImpl.VetoableChange(l, propertyName); + wl.setSource(source); + + return wl; + } + /** Creates a weak implementation of DocumentListener. * * @param l the listener to delegate to diff --git a/openide.util/test/unit/src/org/openide/util/WeakListenersTest.java b/openide.util/test/unit/src/org/openide/util/WeakListenersTest.java --- a/openide.util/test/unit/src/org/openide/util/WeakListenersTest.java +++ b/openide.util/test/unit/src/org/openide/util/WeakListenersTest.java @@ -174,12 +174,25 @@ doTestReleaseOfListener (true); } + public void testReleaseOfPropNameListenerWithNullSource () throws Exception { + doTestReleaseOfListener (false, javax.swing.AbstractButton.TEXT_CHANGED_PROPERTY); + } + + public void testReleaseOfPropNameListenerWithSource () throws Exception { + doTestReleaseOfListener (true, javax.swing.AbstractButton.TEXT_CHANGED_PROPERTY); + } + private void doTestReleaseOfListener (final boolean source) throws Exception { + doTestReleaseOfListener(source, null); + } + + private void doTestReleaseOfListener (final boolean source, final String propName) throws Exception { Listener l = new Listener (); class MyButton extends javax.swing.JButton { private Thread removedBy; private int cnt; + private int cntNamed; @Override public synchronized void removePropertyChangeListener (PropertyChangeListener l) { @@ -203,6 +216,30 @@ cnt++; notifyAll (); } + + @Override + public synchronized void removePropertyChangeListener(String propertyName, PropertyChangeListener l) { + // notify prior + LOG.fine("removePropertyChangeListener("+propertyName+"): " + source + " cnt: " + cntNamed); + if (source && cntNamed == 0) { + notifyAll (); + try { + // wait for 1 + LOG.fine("wait for 1"); + wait (); + LOG.fine("wait for 1 over"); + } catch (InterruptedException ex) { + fail ("Not happen"); + } + } + assertEquals(propName, propertyName); + LOG.fine("Super removePropertyChangeListener"); + super.removePropertyChangeListener (propertyName, l); + LOG.fine("Super over removePropertyChangeListener"); + removedBy = Thread.currentThread(); + cntNamed++; + notifyAll (); + } public synchronized void waitListener () throws Exception { int count = 0; @@ -224,11 +261,24 @@ MyButton button = new MyButton (); LOG.fine("Button is here"); - java.beans.PropertyChangeListener weakL = WeakListeners.propertyChange (l, source ? button : null); + java.beans.PropertyChangeListener weakL; + if (propName == null) { + weakL = WeakListeners.propertyChange (l, source ? button : null); + } else { + weakL = WeakListeners.propertyChange (l, propName, source ? button : null); + } LOG.fine("WeakListeners created: " + weakL); - button.addPropertyChangeListener(weakL); + if (propName == null) { + button.addPropertyChangeListener(weakL); + } else { + button.addPropertyChangeListener(propName, weakL); + } LOG.fine("WeakListeners attached"); - assertTrue ("Weak listener is there", Arrays.asList (button.getPropertyChangeListeners()).indexOf (weakL) >= 0); + if (propName == null) { + assertTrue ("Weak listener is there", Arrays.asList (button.getPropertyChangeListeners()).indexOf (weakL) >= 0); + } else { + assertTrue ("Weak listener is there", Arrays.asList (button.getPropertyChangeListeners(propName)).indexOf (weakL) >= 0); + } button.setText("Ahoj"); LOG.fine("setText changed to ahoj"); @@ -272,9 +322,19 @@ LOG.fine("Thread.sleep over"); } - assertEquals ("Weak listener has been removed", -1, Arrays.asList (button.getPropertyChangeListeners()).indexOf (weakL)); + if (propName == null) { + assertEquals ("Weak listener has been removed", -1, Arrays.asList (button.getPropertyChangeListeners()).indexOf (weakL)); + } else { + assertEquals ("Weak listener has been removed", -1, Arrays.asList (button.getPropertyChangeListeners(propName)).indexOf (weakL)); + } assertEquals ("Button released from a thread", "Active Reference Queue Daemon", button.removedBy.getName()); - assertEquals ("Unregister called just once", 1, button.cnt); + if (propName == null) { + assertEquals ("Unregister called just once", 1, button.cnt); + assertEquals ("Unregister named not called", 0, button.cntNamed); + } else { + assertEquals ("Unregister called just once", 1, button.cntNamed); + assertEquals ("Unregister unnamed not called", 0, button.cnt); + } // and because it is not here, it can be GCed Reference weakRef = new WeakReference(weakL);