This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

Bug 95885 - Add org.openide.util.ChangeSupport
Summary: Add org.openide.util.ChangeSupport
Status: RESOLVED FIXED
Alias: None
Product: platform
Classification: Unclassified
Component: -- Other -- (show other bugs)
Version: 6.x
Hardware: All All
: P3 blocker (vote)
Assignee: Andrei Badea
URL:
Keywords: API, API_REVIEW_FAST
Depends on:
Blocks:
 
Reported: 2007-02-18 14:45 UTC by Andrei Badea
Modified: 2008-12-22 11:52 UTC (History)
3 users (show)

See Also:
Issue Type: ENHANCEMENT
Exception Reporter:


Attachments
Proposed change (10.57 KB, text/plain)
2007-02-18 14:46 UTC, Andrei Badea
Details
Generic support is imho more useful and actually cleaner in its API (9.74 KB, patch)
2007-02-20 07:25 UTC, Jaroslav Tulach
Details | Diff
Proposed change (revised) (9.84 KB, text/plain)
2007-03-05 21:07 UTC, Andrei Badea
Details
Using ChangeSupport in projects (32.03 KB, text/plain)
2007-03-19 22:24 UTC, Andrei Badea
Details
Using ChangeSupport in java/api (3.35 KB, text/plain)
2007-03-19 22:25 UTC, Andrei Badea
Details
Using ChangeSupport in java/j2seproject (27.98 KB, text/plain)
2007-03-19 22:26 UTC, Andrei Badea
Details
Using ChangeSupport in j2ee/earproject, j2ee/ejbjarproject, j2ee/persistence (55.34 KB, text/plain)
2007-03-19 22:28 UTC, Andrei Badea
Details
Using ChangeSupport in web/project (36.55 KB, text/plain)
2007-03-19 22:29 UTC, Andrei Badea
Details
Using ChangeSupport in apisupport/project (w/o templates) (21.21 KB, text/plain)
2007-03-20 23:27 UTC, Andrei Badea
Details
Commit log of the update of other modules to use ChangeSupport (20.53 KB, text/plain)
2007-03-27 00:45 UTC, Andrei Badea
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Andrei Badea 2007-02-18 14:45:22 UTC
I propose the addition of a ChangeSupport class (the equivalent of
PropertyChangeSupport for ChangeListener's) to org.openide.util. It should help
us get rid of code like:

    public void fireChange() {
        ChangeEvent e = new ChangeEvent(this);
        List<ChangeListener> listenersCopy = null;
        synchronized (this) {
            listenersCopy = new ArrayList(listeners);
        }
        for (ChangeListener l : listenersCopy) {
            l.stateChanged(e);
        }

copy-pasted all over the code base. Admittedly, CopyOnWriteArrayList makes
managing and firing change listeners easier, but I still prefer being able to
delegate to a (even simple) support class instead of implementing my own
fireChange() method.

Please review.
Comment 1 Andrei Badea 2007-02-18 14:46:46 UTC
Created attachment 38644 [details]
Proposed change
Comment 2 Jesse Glick 2007-02-18 19:55:37 UTC
Generally looks good. Do you plan to update any of the numerous existing classes
which could use it? 


[JG01] I think just a public constructor would be more straightforward than a
factory method. There are good reasons to use factory methods, but I don't think
any of them apply here.


[JG02] Does the fireChange(ChangeEvent) method need to be public? I can't think
of any situation where I would call it instead of simply fireChange().


[JG03] Is there any particular reason why null is accepted for a listener in
add/removeChangeListener? I can't think of any situation where that would not
indicate a bug in some other code.


[JG04] The getChangeListeners() method does not look thread-safe. If addCL is
called between the call to size() and toArray(...) then no big deal - the
provided array will be discarded and a different one created. But if removeCL is
called in between, you may be passing a larger array than needed, which means it
may contain nulls.

Also I am unsure what the intended use case for this method is. I cannot think
of any code I have written which would ever call it. Perhaps a boolean
hasChangeListeners() would be used in a few cases.
Comment 3 Andrei Badea 2007-02-18 21:48:52 UTC
JG01: I don't see a reason to use a factory method either, but I would use it
even if only because it's more flexible than the constructor. It is also quite
visible in the Javadoc, so I wouldn't be afraid that users won't find it.
Actually for me the strongest reason to use a constructor would be consistency
with PropertyChangeSupport.

JG02: No, I was just attempting to be consistent with PropertyChangeSupport. No
strong opinion, probably it can be made private. I just noticed some code in
j2ee which could use it -- it passes received ChangeEvent's to its own
registered listeners, but that is probably wrong, as those listeners will
receive a ChangeEvent from a source other that the one they have registered to.

JG03: Consistency with PCS again (and do I wonder why that class allows nulls).
No strong opinion, but I wonder how implementations of addChangeListener() and
fireChange() through the code base handle nulls (although the general pattern
seems to be to skip any checks for null in both aCL() and fireChange(), so any
nulls passed to aCL() should have already resulted in NPEs).

JG04: Ah yes, thanks for catching it. You have probably already guessed the
reason for this method -- yes, PCS. I also have a test where I need to check the
number of listeners, and this method seemed more flexible than
getListenerCount(). However, I just realized I am testing for zero in that test
anyway, so I will replace this method with hasListeners().

As for updating existing code, I plan to change a few usages in the Java EE
area, but I'm not comfortable with changing other people's code without being
asked for it. I could e.g. do projects and ant/project if you agree.
Comment 4 Jaroslav Tulach 2007-02-19 07:54:00 UTC
Y01 I do not want to treat openide/util as place that can contain everything. 
It is one of the modules that is on classpath - e.g. visible to every other 
module's classloader and I really thing it should be kept as small as 
possible. Preferably create core/util and dump these utilities there.

Y02 If you really want to create such support and keep it in openide/util, 
then please make it generally useful, e.g. think about generic support for 
listeners. Something like:
<Listener extends java.util.EventListener, Support extends Listener & 
java.util.Set<Listener>> Support createSupport(Class<Listener> listenerClass)
I mean try to create something like
http://java.sun.com/j2se/1.4.2/docs/api/java/awt/AWTEventMulticaster.html
but dynamically typed.

Y03 Possibly make the support for "weak" listeners. E.g. allow the listeners 
to be held via weak references. I know Hrebejk always thought that this should 
be the default. Moreover it could indicate where to put the createSupport 
method - it could be in WeakListeners.createSupport(...., boolean weak).
Comment 5 Andrei Badea 2007-02-19 13:12:17 UTC
Y02-3: this would indeed be more generic, but it would require much more time
than I intended to invest in this. I will have a look at it, but I make no promises.

I'm not sure the returned support should be a Set. I would prefer to return my
own class containing just needed methods such as addListener() and removeListener().

Who will be the clients of listener types other than PropertyChangeListener and
ChangeListener, and also of the weak listener support? o.n.editor.Registry could
probably have a use for the weak ChangeListener support. What else? (I'm not
saying there are no such usages, I just don't know of any.)

Y01: as you probably expect, I'm not going to create a new module with a single
class. Does anyone have any other candidates for such a module?
Comment 6 Jesse Glick 2007-02-19 17:42:28 UTC
Y01 - should not be dealt with in this issue. openide/util is the only
appropriate *existing* place we have for such a class. If someone wants to file
a separate API review to split openide/util into a "core" and an "optional"
piece, great; that will involve some effort to arbitrarily divide up its classes
and create an appropriate ModuleAutoDeps, I guess, plus a couple days' work to
upgrade nb.org modules to use the right deps (running fix-dependencies on each).

Y02 - I also don't see any compelling use case for a generified support. For
less common event listener types there are typically only one or a handful of
classes which serve as event sources, anyway; PropertyChangeListener and
ChangeListener are the only generic listener interfaces we use. I also do not
see a straightforward way to generify a fireChange() method to handle other
kinds of listeners, which generally require specialized means of constructing
event objects.

Y03 - might be useful to future code, though AFAIK there would be no users of it
at the moment; all our code follows the normal pattern of holding listeners
strongly, with clients bearing the responsibility of using WeakListeners if they
need it. Certainly it would be dangerous (probably incompatible) to suddenly
change the semantics of existing event sources. Generally I am suspicious of
using weak listeners transparently, as they can introduce nondeterministic
behavior - probably better if their usage is clearly visible in the code which
might be using them incorrectly. Adding a method to the support class such as

public void addWeakChangeListener(ChangeListener l);

(optionally also a remove method) would be fine, and probably it is fine for
event sources to have similarly named methods. IMHO such a feature should not be
in the WeakListeners class because (a) it would not use the same implementation
at all; (b) a factory method is useless because you need to define the signature
of its return value anyway; (c) the normal (non-weak) case is clearly and
directly analogous to PropertyChangeSupport, which is a well-known JRE utility
class whose style we should follow.
Comment 7 Jaroslav Tulach 2007-02-20 07:25:37 UTC
Created attachment 38700 [details]
Generic support is imho more useful and actually cleaner in its API
Comment 8 Jesse Glick 2007-02-20 17:36:20 UTC
I don't like Yarda's proposed interface much. It is less convenient and
intuitive to use and doesn't buy me anything if I am just using ChangeListener,
which is the only common use case for this API that I know of. Compare:

class Source1 {
  final ChangeSupport cs = new ChangeSupport(this);
  public void addChangeListener(ChangeListener l) {
    cs.addChangeListener(l);
  }
  public void removeChangeListener(ChangeListener l) {
    cs.removeChangeListener(l);
  }
  void stuffHappened() {
    cs.fireChange();
  }
}

class Source2 {
  final Collection<ChangeListener> listeners = new
CopyOnWriteArrayList<ChangeListener>();
  public void addChangeListener(ChangeListener l) {
    listeners.addChangeListener(l);
  }
  public void removeChangeListener(ChangeListener l) {
    listeners.removeChangeListener(l);
  }
  void stuffHappened() {
    if (!listeners.isEmpty()) {
      ChangeEvent ev = new ChangeEvent(this);
      for (ChangeListener l : listeners) {
        l.stateChanged(ev);
      }
    }
  }
}

class Source3 {
  final Collection<ChangeListener> listeners = new
CopyOnWriteArrayList<ChangeListener>();
  final ChangeListener cs = WeakListeners.createSupport(ChangeListener.class,
listeners);
  public void addChangeListener(ChangeListener l) {
    listeners.addChangeListener(l);
  }
  public void removeChangeListener(ChangeListener l) {
    listeners.removeChangeListener(l);
  }
  void stuffHappened() {
    if (!listeners.isEmpty()) {
      ChangeEvent ev = new ChangeEvent(this);
      cs.stateChanged(ev);
    }
  }
}

Using the "support" under Yarda's proposal is actually just as much code as
doing without it. Further, rather than using a simple idiom every knows - same
as PropertyChangeSupport - you have to know to look in WeakListeners, then
basically copy the sample from the Javadoc to use the unfamiliar idiom. Even in
the uncommon case where you need to support a different event interface (e.g.
FileChangeListener), it would be simpler to write it directly using
CopyOnWriteArrayList.
Comment 9 Andrei Badea 2007-02-20 21:10:09 UTC
I can't but agree.

Yarda's proposal is perhaps cleaner, but its weakness is its genericity -- it
can't support custom operations such as fireChange(), which is exactly what is
needed from a ChangeListener support. I can't imagine an user comparing e.g.
PropertyChangeSupport and the support returned by
WeakListeners.createSupport(PropertyChangeListener.class, listeners) and
choosing the latter. I believe the same would be true for the ChangeSupport I
proposed.
Comment 10 Andrei Badea 2007-03-05 21:07:06 UTC
Created attachment 39184 [details]
Proposed change (revised)
Comment 11 Andrei Badea 2007-03-05 21:09:19 UTC
I attached a new version of ChangeSupport that addresses Jesse's comments. It
removes the factory method, makes fireChange(ChangeEvent) private and introduces
hasListeners() instead of getChangeListeners(). Null parameters to
add/removeChangeListener() are still allowed.

I also considered adding the following constructor:

ChangeSupport(boolean unique);

to allow or disallow duplicate registrations of the same listener. There are
places in NetBeans where listeners are held in a Set, so this would come in
useful. But I think it would be better if all listener sources behaved the same
way, so I don't plan to add this constructor.

I will modify a few existing modules to use this class and attach the diffs here
in a few days. No plans to do whole top-level directories though--I am viewing
these changes mainly as a way to notify the readers of the respective CVS
mailing lists that this class is available.
Comment 12 Jesse Glick 2007-03-06 01:18:30 UTC
Revised version looks fine to me.

I don't see any good reason to treat listeners as Set<ChangeListener> rather
than List<ChangeListener>; that just makes add and remove behave less
symmetrically, as add followed by add followed by remove is different from add
followed by remove followed by add. (Even with List they are not quite symmetric
since remove followed by add is different from add followed by remove.)
Comment 13 Andrei Badea 2007-03-19 22:24:39 UTC
Created attachment 39657 [details]
Using ChangeSupport in projects
Comment 14 Andrei Badea 2007-03-19 22:25:41 UTC
Created attachment 39660 [details]
Using ChangeSupport in java/api
Comment 15 Andrei Badea 2007-03-19 22:26:25 UTC
Created attachment 39661 [details]
Using ChangeSupport in java/j2seproject
Comment 16 Andrei Badea 2007-03-19 22:28:42 UTC
Created attachment 39662 [details]
Using ChangeSupport in j2ee/earproject, j2ee/ejbjarproject, j2ee/persistence
Comment 17 Andrei Badea 2007-03-19 22:29:16 UTC
Created attachment 39663 [details]
Using ChangeSupport in web/project
Comment 18 Andrei Badea 2007-03-19 22:36:16 UTC
I plan to commit the attached changes after getting a review for projects,
java/api and java/j2seproject. Hopefully by the end of the week.
Comment 19 Jesse Glick 2007-03-20 02:59:04 UTC
projects, java/api, and java/j2seproject diffs looks good to me. I could use it
in apisupport/project too. (Including in templates, though that is a bit more
subtle as you need to check for users of an older NB version.)
Comment 20 Andrei Badea 2007-03-20 23:27:08 UTC
Created attachment 39723 [details]
Using ChangeSupport in apisupport/project (w/o templates)
Comment 21 Andrei Badea 2007-03-20 23:35:28 UTC
I did apisupport/project (please review), but without the templates. It would
take me more time to do them than to you or whoever owns the module, and I'd
prefer to spend that time scrathing one of my other itches. And they perhaps
deserve a separate issue anyway.
Comment 22 Jesse Glick 2007-03-21 01:24:15 UTC
apisupport/project looks good again. (You got to use hasListeners()!) And yes,
any changes to templates need to be done by me or someone else working on
apisupport. It is not a high priority anyway.
Comment 23 Andrei Badea 2007-03-23 21:33:57 UTC
I didn't reassign to myself timely and now I have to wait a working day before
committing, so I plan to commit Monday evening. Hope I'm not blocking anyone.

Thanks to reviewers for all comments and to people on CC for moral support :-)
Comment 24 Jesse Glick 2007-03-26 22:50:02 UTC
So I guess FIXED, thanks.

I am working on using it in some additional core-team modules.
Comment 25 Andrei Badea 2007-03-27 00:41:37 UTC
Commit log. Late because I spent some time ensuring the broken commit validation
was not caused by this.

Checking in openide/util/apichanges.xml;
/cvs/openide/util/apichanges.xml,v  <--  apichanges.xml
new revision: 1.23; previous revision: 1.22
done
Checking in openide/util/nbproject/project.properties;
/cvs/openide/util/nbproject/project.properties,v  <--  project.properties
new revision: 1.26; previous revision: 1.25
done
RCS file: /cvs/openide/util/src/org/openide/util/ChangeSupport.java,v
done
Checking in openide/util/src/org/openide/util/ChangeSupport.java;
/cvs/openide/util/src/org/openide/util/ChangeSupport.java,v  <--  ChangeSupport.java
initial revision: 1.1
done
RCS file: /cvs/openide/util/test/unit/src/org/openide/util/ChangeSupportTest.java,v
done
Checking in openide/util/test/unit/src/org/openide/util/ChangeSupportTest.java;
/cvs/openide/util/test/unit/src/org/openide/util/ChangeSupportTest.java,v  <-- 
ChangeSupportTest.java
initial revision: 1.1
done
Comment 26 Andrei Badea 2007-03-27 00:45:06 UTC
Created attachment 39992 [details]
Commit log of the update of other modules to use ChangeSupport