Feb 13
I said I implemented this, and I wasn’t lying. I supply the code for the ListenersCollection here, free for your usage – It’s attached with an Apache License, so don’t lose that!
Here’s the code:
/**
* Copyright 2005-2006 Aviad Ben Dov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.crazyredpanda.util.collections;
import java.util.Collection;
import java.util.TreeSet;
import java.util.Map;
import java.util.TreeMap;
public final class ListenersCollection<Listener, EventArgs> {
public interface ListenerInvoker<Listener, EventArgs> {
void invoke(Listener l, EventArgs args);
}
private final ListenerInvoker<Listener, EventArgs> invoker;
private final Collection<Listener> listeners = new TreeSet<Listener>();
private final Map<Listener, Boolean> delayedListeners = new TreeMap<Listener, Boolean>();
private boolean isDelayed = false;
public ListenersCollection(ListenerInvoker<Listener, EventArgs> invoker) {
this.invoker = invoker;
}
public synchronized void addListener(Listener l) {
if (isDelayed) {
delayedListeners.put(l, true);
} else {
listeners.add(l);
}
}
public synchronized void removeListener(Listener l) {
if (isDelayed) {
delayedListeners.put(l, false);
} else {
listeners.remove(l);
}
}
public synchronized void fireEvent(EventArgs args) {
isDelayed = true;
for (Listener l : listeners) {
invoker.invoke(l, args);
}
isDelayed = false;
// handing delayed un/registartions
for (Map.Entry<Listener, Boolean> entry : delayedListeners.entrySet()) {
if (entry.getValue()) {
listeners.add(entry.getKey());
} else {
listeners.remove(entry.getKey());
}
}
delayedListeners.clear();
}
}
Liked Chaotic Java? It's free! But maybe you can
February 13th, 2006 at 7:23 pm
Whhops. There was a mistake and clearing the map after the handling of its entries was needed. Fixed now.
February 14th, 2006 at 3:05 am
Umm… why would there be a need for the “delayed” related stuff when you have made all your methods synchronized? When fireEvent is being executed, all calls to removeListener and addListener will be blocked in a queue and wait for fireEvent to finish and the concurrent list access exceptions won’t be thrown…
February 14th, 2006 at 6:11 am
Behrang – Did you read the post previous to this?
The reason is that in the same thread, listeners might want to remove themselves from the listeners list in the method fired from the event, which would cause an event to be thrown.
February 15th, 2006 at 12:44 am
Oops… sorry I hadn’t read the previous post carefully. Nice trick indeed.
February 15th, 2006 at 8:30 am
It’s a helpful little class. It’s useful especially for those events that you know the registrations to them is not permanent – Permanent registrations are, for example, GUI elements, internal data changes, etc.
Non permanent registrations are, for example, events that occur a fixed number of times, such as mutation of a class from one type to another (deferred data to real data, maybe), and after receiving the event there is no more need to keep being registered to it. Then, the class being registered might remove itself from the list – And this is where this little class helps keep it working.
March 6th, 2006 at 10:00 pm
[...] If you ask me, I don’t think they should be in Java. There’s a reason why they stayed out, even though as the papers show Sun and Microsoft had the fight about it way back in 1998. The reason is object-oriented design, and the “magic factory”. In any case, Java events are implemented using the observer-observable design pattern. I think what we should do is focus on easier implementations of this design pattern. I know I’m not very objective, but the ListenersCollection class is a good example. [...]
March 7th, 2006 at 2:44 pm
The delayedListeners member implements a Map with TreeMap. There is nothing wrong with this, per se, but for me the purpose of that field is more clearly expressed with SortedMap instead of Map as the field’s type.
March 7th, 2006 at 3:02 pm
Blinkley: Why so? The order of the listeners being registered does not really matter… The usage of the TreeMap is only there because it’s considered better for many insertions and removals with little direct accesses…
September 7th, 2006 at 10:39 pm
[...] An example for when this might be useful: Suppose the function pointer is used to trigger events. Suppose the event handler doesn’t want to be the instance keeping items alive, and doesn’t want to force listeners to unregister when they are disposed. With interfaces, the instance triggering the event could keep a weak reference list of all the interface instances listening to the event (as I’ve shown in an earlier post). With delegates in dotNet this is impossible – Every delegate passed is a new instance, and two delegates of the same method in the same instance are different objects. That makes such a list impossible as it will always remain empty – The list is the only one refering to that delegate (using a weak reference!), so it will be garbage collected as soon as possible. If it was the same instance for the same method in a class instance and it would have been somehow referenced by the containing instance, this would have been prevented and such a weak reference list could have been made. Also, it would have prevented multiple instantiations of the same pointer. [...]