3

The following code is taken from Oracle jdk1.8.0_40 AbstractListModel class.

   /**
     * <code>AbstractListModel</code> subclasses must call this method
     * <b>after</b>
     * one or more elements of the list change.  The changed elements
     * are specified by the closed interval index0, index1 -- the endpoints
     * are included.  Note that
     * index0 need not be less than or equal to index1.
     *
     * @param source the <code>ListModel</code> that changed, typically "this"
     * @param index0 one end of the new interval
     * @param index1 the other end of the new interval
     * @see EventListenerList
     * @see DefaultListModel
     */
    protected void fireContentsChanged(Object source, int index0, int index1)
    {
        Object[] listeners = listenerList.getListenerList();
        ListDataEvent e = null;

        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ListDataListener.class) {
                if (e == null) {
                    e = new ListDataEvent(source, ListDataEvent.CONTENTS_CHANGED, index0, index1);
                }
                ((ListDataListener)listeners[i+1]).contentsChanged(e);
            }
        }
    }

My questions are

  • Why does the iteration start at listeners.length - 2 what about the listeners.length - 1 element?
  • Why is the event fired for every other element (i -= 2)?
  • Why is the iteration going in reverse order?

A link to the code in openjdk as well.

Can't Tell
  • 12,714
  • 9
  • 63
  • 91
  • Have a look at `addListDataListener` to understand why it is implemented like this. Also note that the listeners are invoked in the reverse order of their addition to the model. – Dakshinamurthy Karra Jul 29 '15 at 11:46
  • I suppose the question about `listeners.length - 2` and `i -= 2` are linked. I cannot check the code right now, but the immediate explanation I can think of is that maybe two elements are added to the `listenerList` each time a listener is registered and only one of them should be notified. – Chop Jul 29 '15 at 11:47
  • 2
    Javadoc and source code for `javax.swing.event.EventListenerList` provide comprehensive documentation on why the instance of that class is used in this way. – Oleg Estekhin Jul 29 '15 at 11:49
  • @OlegEstekhin I only read the Javadoc for `getListenerList`. I thought that would have the answer and since it didn't I posted here. But on another note, shouldn't the javadoc for `getListenerList` contain this info? – Can't Tell Jul 29 '15 at 12:10

4 Answers4

6

The listeners array contains the listeners' Class objects in the even indices and the listener instances in the odd indices.

Therefore the loop checks the type of each even index in the listeners array

if (listeners[i] == ListDataListener.class

but fires the event only for odd indices :

((ListDataListener)listeners[i+1]).contentsChanged(e);

listeners.length - 1 is not skipped. Since when i == listeners.length - 2, i+1 == listeners.length - 1.

I'm not sure about the reason for the reverse order iteration.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • The reverse order is because the model fires the events in the reverse order of their addition. – Dakshinamurthy Karra Jul 29 '15 at 11:47
  • 3
    @KDM The question is why the model is doing that. – Eran Jul 29 '15 at 11:49
  • It has to. If it doesn't iterate in the reverse order, the first added listener is fired first and that may not be what is required. All the event firing in Swing does the same thing. – Dakshinamurthy Karra Jul 29 '15 at 11:52
  • 2
    @KDM: that’s not an explanation. After all, the order of event notification is unspecified. My data models all use the [Multicaster](http://docs.oracle.com/javase/8/docs/api/java/awt/AWTEventMulticaster.html) pattern which has no stable ordering at all and Swing doesn’t mind. The real reason seems to be an esoteric optimization, comparing the loop variable with zero is considered to be more efficient than comparing with the dynamic value of an array length. – Holger Jul 29 '15 at 13:55
  • @Holger: not sure about all events, but at least the events that are consumeable should fire in the reverse order. – Dakshinamurthy Karra Jul 29 '15 at 15:50
  • 1
    @KDM: consumable events means `InputEvent`s and these are fired by AWT in no specified order. Just look at the multicaster I have linked previously and how it works. Initially it will send in *insertion order* (not backwards) and it will shuffle the listeners around once you remove one in the middle. The documentation of [`InputEvent`](http://docs.oracle.com/javase/8/docs/api/java/awt/event/InputEvent.html) clearly says that consuming is meant to alter the behavior of the *source* of the event not other listeners. There is no evidence of backward insertion order in AWT’s notifications… – Holger Jul 29 '15 at 16:00
  • @Holger: The `Component#add*Listeners` internally use `AWTEventMultiCaster` and ensures that the events are fired in the reverse order. If they are not fired in the reverse order, it is not possible for a subclass to cancel the default behaviour of its super class. IMO, if we subclass a component we should honour the `consume` contract. `AWTEventMultiCaster` is nothing but a highly efficient `EventListenerList` as far as the functionality goes. – Dakshinamurthy Karra Jul 30 '15 at 05:30
  • @KDM: `AWTEventMultiCaster` does *not* maintain a stable order. As said, the order may change when listeners are removed, and even without removal, a simple test will show you that listeners in the AWT are *never* invoked in backward order. More important, there in nothing in the specification which backs your claim. And subclasses shouldn’t register listeners on itself, there are `protected` methods which can be easily overridden, if a subclass wants to alter the behavior… – Holger Jul 30 '15 at 08:29
2

The reason they're iterating in reverse order is so they don't have to evaluate .length every time. They're doing it as a performance optimization. Here's a related question.

Community
  • 1
  • 1
Luminous
  • 1,771
  • 2
  • 24
  • 43
1

As per the code to add a new listener, shown here:

public void addListDataListener(ListDataListener l) {
    listenerList.add(ListDataListener.class, l);
}

the list actually contains pairs of Class instances and object instances.

With regards to the iteration order, perhaps it's a deliberate approach to notifier newer listeners first?

Vlad
  • 18,195
  • 4
  • 41
  • 71
1

If you look at EventListenerList: the list is built with two elements, one of them is the listener object, the other the class of the object. That explains the 2-by-2 iteration and the check with the other element towards a class.

I find this code pretty ugly, lots of repetitions of this weird iteration loop. I don't have the answer about this rather strane implementation, but IMO the reasons are probably to keep compatibility with the legacy JAVA -keep the APIs the same, and maybe performances, probably with the introduction of generics in java 1.5.

Wy iterate in reverse order? No idea why it's implemented like that. Maybe an implementation decision or an specification that requires to call latest added listeners first. I tried to find another good reason but I couldn't... Moreover it used to iterate in reverse older since at least 1.6 (I did not check with older versions).

Loic
  • 1,088
  • 7
  • 19