Index: editor/fold/src/org/netbeans/modules/editor/fold/FoldHierarchyExecution.java =================================================================== RCS file: /cvs/editor/fold/src/org/netbeans/modules/editor/fold/FoldHierarchyExecution.java,v --- editor/fold/src/org/netbeans/modules/editor/fold/FoldHierarchyExecution.java 22 Mar 2005 09:26:29 -0000 1.5 +++ editor/fold/src/org/netbeans/modules/editor/fold/FoldHierarchyExecution.java 24 Mar 2005 16:12:33 -0000 @@ -36,6 +36,8 @@ import org.netbeans.api.editor.fold.FoldHierarchyEvent; import org.netbeans.api.editor.fold.FoldHierarchyListener; import org.netbeans.api.editor.fold.FoldStateChange; +import org.netbeans.lib.editor.util.swing.DocumentListenerPriority; +import org.netbeans.lib.editor.util.swing.DocumentUtilities; import org.netbeans.spi.editor.fold.FoldManager; import org.netbeans.spi.editor.fold.FoldManagerFactory; import org.netbeans.spi.editor.fold.FoldOperation; @@ -532,7 +534,8 @@ public void rebuild() { // Stop listening on the original document if (lastDocument != null) { - lastDocument.removeDocumentListener(this); + // Remove document listener with specific priority + DocumentUtilities.removeDocumentListener(lastDocument, this, DocumentListenerPriority.FOLD_UPDATE); lastDocument = null; } @@ -557,7 +560,8 @@ // Start listening for changes if (!releaseOnly) { lastDocument = adoc; - lastDocument.addDocumentListener(this); + // Add document listener with specific priority + DocumentUtilities.addDocumentListener(lastDocument, this, DocumentListenerPriority.FOLD_UPDATE); } } try { Index: editor/libsrc/org/netbeans/editor/BaseCaret.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/BaseCaret.java,v --- editor/libsrc/org/netbeans/editor/BaseCaret.java 24 Feb 2005 16:38:16 -0000 1.106 +++ editor/libsrc/org/netbeans/editor/BaseCaret.java 24 Mar 2005 16:12:33 -0000 @@ -55,6 +55,7 @@ import org.netbeans.api.editor.fold.FoldHierarchyListener; import org.netbeans.api.editor.fold.FoldStateChange; import org.netbeans.api.editor.fold.FoldUtilities; +import org.netbeans.lib.editor.util.swing.DocumentListenerPriority; /** * Caret implementation @@ -312,7 +313,8 @@ protected void modelChanged(BaseDocument oldDoc, BaseDocument newDoc) { // [PENDING] !!! this body looks strange because of the bug 4200280 if (oldDoc != null && listenDoc == oldDoc) { - oldDoc.removeDocumentListener(this); + org.netbeans.lib.editor.util.swing.DocumentUtilities.removeDocumentListener( + oldDoc, this, DocumentListenerPriority.CARET_UPDATE); oldDoc.removeAtomicLockListener(this); try { @@ -332,7 +334,8 @@ modelChanged(listenDoc, null); } - newDoc.addDocumentListener(this); + org.netbeans.lib.editor.util.swing.DocumentUtilities.addDocumentListener( + newDoc, this, DocumentListenerPriority.CARET_UPDATE); listenDoc = newDoc; newDoc.addAtomicLockListener(this); @@ -449,37 +452,23 @@ return; } - /* part of fix of #18860 - Using runInEventDispatchThread() in AWT thread - * means that the code is executed immediately which can lead - * to problems once the insert/remove in document is performed - * because the update() uses views to find out the visual position - * and if the views doc listener is added AFTER the caret's listener - * then the views are not updated yet. Using SwingUtilities.invokeLater() - * should solve the problem although the view extent could flip - * once the extent would be explicitely scrolled to area that does - * not cover the caret's rectangle. It needs to be tested - * so that it does not happen. + /* After using SwingUtilities.invokeLater() due to fix of #18860 + * there is another fix of #35034 which ensures that the caret's + * document listener will be added AFTER the views hierarchy's + * document listener so the code can run synchronously again + * which should eliminate the problem with caret lag. */ -// Utilities.runInEventDispatchThread( - SwingUtilities.invokeLater( - new Runnable() { - public void run() { - JTextComponent c2 = component; - if (c2 != null) { - BaseDocument doc = Utilities.getDocument(c2); - if (doc != null) { - doc.readLock(); - try { - update(scrollRect, scrollPolicy); - } finally { - doc.readUnlock(); - } - } - } - + if (c != null) { + BaseDocument doc = Utilities.getDocument(c); + if (doc != null) { + doc.readLock(); + try { + update(scrollRect, scrollPolicy); + } finally { + doc.readUnlock(); } } - ); + } } /** Update the caret. The document is read-locked while calling this method. Index: editor/libsrc/org/netbeans/editor/BaseDocument.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/BaseDocument.java,v --- editor/libsrc/org/netbeans/editor/BaseDocument.java 19 Mar 2005 08:53:11 -0000 1.117 +++ editor/libsrc/org/netbeans/editor/BaseDocument.java 24 Mar 2005 16:12:34 -0000 @@ -27,6 +27,7 @@ import java.beans.PropertyChangeEvent; import java.util.EventListener; import java.util.HashMap; +import javax.swing.event.DocumentListener; import javax.swing.event.UndoableEditListener; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultEditorKit; @@ -44,6 +45,7 @@ import javax.swing.undo.CompoundEdit; import javax.swing.undo.CannotUndoException; import javax.swing.undo.CannotRedoException; +import org.netbeans.lib.editor.util.swing.DocumentListenerPriority; /** * Document implementation @@ -255,7 +257,7 @@ * modification. It must be notified prior acquiring of the document's lock. */ private final ThreadLocal STATUS = new ThreadLocal (); - + /** Create base document with a specified syntax. * @param kitClass class used to initialize this document with proper settings * category based on the editor kit for which this document is created @@ -266,6 +268,8 @@ this.kitClass = kitClass; setDocumentProperties(createDocumentProperties(getDocumentProperties())); + super.addDocumentListener( + org.netbeans.lib.editor.util.swing.DocumentUtilities.initPriorityListening(this)); putProperty(GapStart.class, getDocumentContent()); putProperty("supportsModificationListener", Boolean.TRUE); // NOI18N @@ -1368,6 +1372,16 @@ protected final int getAtomicDepth() { return atomicDepth; + } + + public void addDocumentListener(DocumentListener listener) { + org.netbeans.lib.editor.util.swing.DocumentUtilities.addDocumentListener( + this, listener, DocumentListenerPriority.DEFAULT); + } + + public void removeDocumentListener(DocumentListener listener) { + org.netbeans.lib.editor.util.swing.DocumentUtilities.removeDocumentListener( + this, listener, DocumentListenerPriority.DEFAULT); } protected BaseDocumentEvent createDocumentEvent(int pos, int length, Index: editor/util/manifest.mf =================================================================== RCS file: /cvs/editor/util/manifest.mf,v --- editor/util/manifest.mf 22 Mar 2005 09:26:31 -0000 1.5 +++ editor/util/manifest.mf 24 Mar 2005 16:12:36 -0000 @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.editor.util/1 OpenIDE-Module-Localizing-Bundle: org/netbeans/lib/editor/util/Bundle.properties -OpenIDE-Module-Specification-Version: 1.3 +OpenIDE-Module-Specification-Version: 1.4 Index: editor/util/api/apichanges.xml =================================================================== RCS file: /cvs/editor/util/api/apichanges.xml,v --- editor/util/api/apichanges.xml 22 Mar 2005 09:26:32 -0000 1.1 +++ editor/util/api/apichanges.xml 24 Mar 2005 16:12:36 -0000 @@ -76,10 +76,25 @@ + Added document listeners ordering support + + + + + +

+ Added document listeners ordering support through + DocumentUtilities.addDocumentListener(doc, listener, priority). +

+
+ +
+ + Repackaged to org.netbeans.lib.editor.util package - +

Index: editor/util/src/org/netbeans/lib/editor/util/PriorityListenerList.java =================================================================== RCS file: editor/util/src/org/netbeans/lib/editor/util/PriorityListenerList.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ editor/util/src/org/netbeans/lib/editor/util/PriorityListenerList.java 24 Mar 2005 16:12:36 -0000 @@ -0,0 +1,196 @@ +/* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.lib.editor.util; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.EventListener; +import java.util.List; + +/** + * Listener list that layers the maintained listeners + * according to the given priority index. + *
+ * Simply said it's an array of listener arrays. The priority index defines + * the event listeners array holding all the listeners with the given priority index. + * + * @author Miloslav Metelka + * @version 1.00 + */ + +public class PriorityListenerList implements Serializable { + + static final long serialVersionUID = 0L; + + private static final EventListener[] EMPTY_LISTENER_ARRAY = new EventListener[0]; + + private static final EventListener[][] EMPTY_LISTENER_ARRAY_ARRAY = new EventListener[0][]; + + private transient EventListener[][] listenersArray = EMPTY_LISTENER_ARRAY_ARRAY; + + /** + * Add listener with the given priority. + * + * @param listener listener to be added. + * @param priority >=0 index defining priority + * with which the listener should be fired. + *
+ * The higher the priority the sooner the listener will be fired. + *
+ * It's guaranteed that all the listeners with higher priority index will be fired + * sooner than listeners with lower priority. + *
+ * The number of priority levels should be limited to reasonably + * low number. + * @throws IndexOutOfBoundsException when priority < 0 + */ + public synchronized void add(EventListener listener, int priority) { + EventListener[][] newListenersArray; + if (priority >= listenersArray.length) { + newListenersArray = new EventListener[priority + 1][]; + System.arraycopy(listenersArray, 0, newListenersArray, 0, listenersArray.length); + for (int i = listenersArray.length; i < priority; i++) { + newListenersArray[i] = EMPTY_LISTENER_ARRAY; + } + newListenersArray[priority] = new EventListener[] { listener }; + + } else { // Add into existing listeners + newListenersArray = (EventListener[][])listenersArray.clone(); + EventListener[] listeners = listenersArray[priority]; + EventListener[] newListeners = new EventListener[listeners.length + 1]; + System.arraycopy(listeners, 0, newListeners, 1, listeners.length); + newListeners[0] = listener; + newListenersArray[priority] = newListeners; + } + + listenersArray = newListenersArray; + } + + /** + * Remove listener with the given priority index. + * + * @param listener listener to be removed. + * @param priority >=0 index defining priority + * with which the listener was originally added. + *
+ * If the listener was not added or it was added with different + * priority then no action happens. + * @throws IndexOutOfBoundsException when priority < 0 + */ + public synchronized void remove(EventListener listener, int priority) { + if (priority < listenersArray.length) { + EventListener[] listeners = listenersArray[priority]; + int index = listeners.length - 1; + while (index >= 0 && listeners[index] != listener) { + index--; + } + if (index >= 0) { + EventListener[] newListeners; + boolean removeHighestPriorityLevel; + if (listeners.length == 1) { + newListeners = EMPTY_LISTENER_ARRAY; + removeHighestPriorityLevel = (priority == listenersArray.length - 1); + } else { + newListeners = new EventListener[listeners.length - 1]; + System.arraycopy(listeners, 0, newListeners, 0, index); + System.arraycopy(listeners, index + 1, newListeners, index, + newListeners.length - index); + removeHighestPriorityLevel = false; + } + + EventListener[][] newListenersArray; + if (removeHighestPriorityLevel) { + newListenersArray = new EventListener[listenersArray.length - 1][]; + System.arraycopy(listenersArray, 0, newListenersArray, 0, newListenersArray.length); + } else { // levels count stays the same + newListenersArray = (EventListener[][])listenersArray.clone(); + newListenersArray[priority] = newListeners; + } + + listenersArray = newListenersArray; + } + } + } + + /** + * Return the actual array of listeners arrays maintained by this listeners list. + *
+ * WARNING! + * Absolutely NO modification should be done on the contents of the returned + * data. + * + *

+ * The higher index means sooner firing. Listeners with the same priority + * are ordered so that the one added sooner has higher index than the one + * added later. So the following firing mechanism should be used:

+     *
+     *  private void fireMyEvent(MyEvent evt) {
+     *    EventListener[][] listenersArray = priorityListenerList.getListenersArray();
+     *    for (int priority = listenersArray.length - 1; priority >= 0; priority--) {
+     *      EventListener[] listeners = listenersArray[priority];
+     *      for (int i = listeners.length - 1; i >= 0; i--) {
+     *        ((MyListener)listeners[i]).notify(evt);
+     *      }
+     *    } 
+     *  }
+     * 
+ */ + public EventListener[][] getListenersArray() { + return listenersArray; + } + + // Serialization support. + private void writeObject(ObjectOutputStream s) throws IOException { + s.defaultWriteObject(); + + // Save serializable event listeners + int priority = listenersArray.length - 1; // max priority + s.writeInt(priority); // write max priority + for (; priority >= 0; priority--) { + EventListener[] listeners = listenersArray[priority]; + // Write in opposite order of adding + for (int i = 0; i < listeners.length; i++) { + EventListener listener = listeners[i]; + if (listener instanceof Serializable) { + s.writeObject(listener); + } + } + s.writeObject(null); + } + } + + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + + int priority = s.readInt(); + listenersArray = (priority != -1) + ? new EventListener[priority + 1][] + : EMPTY_LISTENER_ARRAY_ARRAY; + + for (; priority >= 0; priority--) { + List listeners = new ArrayList(); + EventListener listenerOrNull; + while (null != (listenerOrNull = (EventListener)s.readObject())) { + listeners.add(listenerOrNull); + } + listenersArray[priority] = (EventListener[])listeners.toArray( + new EventListener[listeners.size()]); + } + } + +} Index: editor/util/src/org/netbeans/lib/editor/util/swing/DocumentListenerPriority.java =================================================================== RCS file: editor/util/src/org/netbeans/lib/editor/util/swing/DocumentListenerPriority.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ editor/util/src/org/netbeans/lib/editor/util/swing/DocumentListenerPriority.java 24 Mar 2005 16:12:36 -0000 @@ -0,0 +1,73 @@ +/* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.lib.editor.util.swing; + +/** +* Priorities of firing of document listeners being added to a document. +* +* @author Miloslav Metelka +* @version 1.00 +*/ + +public final class DocumentListenerPriority { + + /** + * Fold update gets notified first (prior view updates etc.). + */ + public static final DocumentListenerPriority FOLD_UPDATE + = new DocumentListenerPriority(2, "fold-update"); + + /** + * Default level is used for all listeners added + * by regular {@link javax.swing.text.Document#addDocumentListener( + * javax.swing.event.DocumentListener)} method. + */ + public static final DocumentListenerPriority DEFAULT + = new DocumentListenerPriority(1, "default"); + + /** + * Caret udpate gets notified as last. + */ + public static final DocumentListenerPriority CARET_UPDATE + = new DocumentListenerPriority(0, "caret-update"); + + + private int priority; + + private String description; + + /** + * Construct new DocumentListenerPriority. + * + * @param priority higher priority means sooner firing. + * @param description textual description of the priority. + */ + private DocumentListenerPriority(int priority, String description) { + this.priority = priority; + this.description = description; + } + + public int getPriority() { + return priority; + } + + public String getDescription() { + return description; + } + + public String toString() { + return getDescription(); + } + +} Index: editor/util/src/org/netbeans/lib/editor/util/swing/DocumentUtilities.java =================================================================== RCS file: editor/util/src/org/netbeans/lib/editor/util/swing/DocumentUtilities.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ editor/util/src/org/netbeans/lib/editor/util/swing/DocumentUtilities.java 24 Mar 2005 16:12:36 -0000 @@ -0,0 +1,117 @@ +/* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.lib.editor.util.swing; + +import javax.swing.event.DocumentListener; +import javax.swing.text.Document; + +/** + * Various utility methods related to swing text documents. + * + * @author Miloslav Metelka + * @version 1.00 + */ + +public final class DocumentUtilities { + + private DocumentUtilities() { + // No instances + } + + /** + * Add document listener to document with given priority. + * + * @param doc document to which the listener should be added. + * @param listener document listener to add. + * @param priority priority with which the listener should be added. + * If the document does not support document listeners ordering + * then the listener is added in a regular way by using + * {@link javax.swing.text.Document#addDocumentListener( + * javax.swing.event.DocumentListener)} method. + */ + public static void addDocumentListener(Document doc, DocumentListener listener, + DocumentListenerPriority priority) { + + PriorityDocumentListenerList priorityDocumentListenerList + = (PriorityDocumentListenerList)doc.getProperty(PriorityDocumentListenerList.class); + if (priorityDocumentListenerList != null) { + priorityDocumentListenerList.add(listener, priority.getPriority()); + } else { // default to regular adding + doc.addDocumentListener(listener); + } + } + + /** + * Remove document listener that was previously added to the document + * with given priority. + * + * @param doc document from which the listener should be removed. + * @param listener document listener to remove. + * @param priority priority with which the listener should be removed. + * It should correspond to the priority with which the listener + * was added originally. + */ + public static void removeDocumentListener(Document doc, DocumentListener listener, + DocumentListenerPriority priority) { + + PriorityDocumentListenerList priorityDocumentListenerList + = (PriorityDocumentListenerList)doc.getProperty(PriorityDocumentListenerList.class); + if (priorityDocumentListenerList != null) { + priorityDocumentListenerList.remove(listener, priority.getPriority()); + } else { // default to regular removing + doc.removeDocumentListener(listener); + } + } + + /** + * This method should be used by swing document implementations that + * want to support document listeners prioritization. + *
+ * It should be called from document's constructor in the following way:
+     *
+     * class MyDocument extends AbstractDocument {
+     *
+     *     MyDocument() {
+     *         super.addDocumentListener(DocumentUtilities.initPriorityListening(this));
+     *     }
+     *
+     *     public void addDocumentListener(DocumentListener listener) {
+     *         DocumentUtilities.addDocumentListener(this, listener, DocumentListenerPriority.DEFAULT);
+     *     }
+     *
+     *     public void removeDocumentListener(DocumentListener listener) {
+     *         DocumentUtilities.removeDocumentListener(this, listener, DocumentListenerPriority.DEFAULT);
+     *     }
+     *
+     * }
+ * + * + * @param doc document to be initialized. + * @return the document listener instance that should be added as a document + * listener typically by using super.addDocumentListener() + * in document's constructor. + * @throws IllegalStateException when the document already has + * the property initialized. + */ + public static DocumentListener initPriorityListening(Document doc) { + if (doc.getProperty(PriorityDocumentListenerList.class) != null) { + throw new IllegalStateException( + "PriorityDocumentListenerList already initialized for doc=" + doc); // NOI18N + } + PriorityDocumentListenerList instance = new PriorityDocumentListenerList(); + doc.putProperty(PriorityDocumentListenerList.class, instance); + return instance; + } + +} Index: editor/util/src/org/netbeans/lib/editor/util/swing/PriorityDocumentListenerList.java =================================================================== RCS file: editor/util/src/org/netbeans/lib/editor/util/swing/PriorityDocumentListenerList.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ editor/util/src/org/netbeans/lib/editor/util/swing/PriorityDocumentListenerList.java 24 Mar 2005 16:12:36 -0000 @@ -0,0 +1,77 @@ +/* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.lib.editor.util.swing; + +import java.util.EventListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.Document; +import org.netbeans.lib.editor.util.PriorityListenerList; + +/** + * Priority listener list that acts as DocumentListener itself + * firing all added document listeners according to their priority. + * + * @author Miloslav Metelka + * @version 1.00 + */ + +class PriorityDocumentListenerList extends PriorityListenerList implements DocumentListener { + + /** + * Implementation of DocumentListener's method fires all the added + * listeners according to their priority. + */ + public void insertUpdate(DocumentEvent evt) { + // Fire the prioritized listeners + EventListener[][] listenersArray = getListenersArray(); + for (int priority = listenersArray.length - 1; priority >= 0; priority--) { + EventListener[] listeners = listenersArray[priority]; + for (int i = listeners.length - 1; i >= 0; i--) { + ((DocumentListener)listeners[i]).insertUpdate(evt); + } + } + } + + /** + * Implementation of DocumentListener's method fires all the added + * listeners according to their priority. + */ + public void removeUpdate(DocumentEvent evt) { + // Fire the prioritized listeners + EventListener[][] listenersArray = getListenersArray(); + for (int priority = listenersArray.length - 1; priority >= 0; priority--) { + EventListener[] listeners = listenersArray[priority]; + for (int i = listeners.length - 1; i >= 0; i--) { + ((DocumentListener)listeners[i]).removeUpdate(evt); + } + } + } + + /** + * Implementation of DocumentListener's method fires all the added + * listeners according to their priority. + */ + public void changedUpdate(DocumentEvent evt) { + // Fire the prioritized listeners + EventListener[][] listenersArray = getListenersArray(); + for (int priority = listenersArray.length - 1; priority >= 0; priority--) { + EventListener[] listeners = listenersArray[priority]; + for (int i = listeners.length - 1; i >= 0; i--) { + ((DocumentListener)listeners[i]).changedUpdate(evt); + } + } + } + +} Index: editor/util/test/cfg-unit.xml =================================================================== RCS file: /cvs/editor/util/test/cfg-unit.xml,v --- editor/util/test/cfg-unit.xml 29 Jun 2004 09:32:14 -0000 1.2 +++ editor/util/test/cfg-unit.xml 24 Mar 2005 16:12:36 -0000 @@ -47,14 +47,6 @@ - - - - - - - - Index: editor/util/test/unit/src/org/netbeans/lib/editor/util/PriorityListenerListTest.java =================================================================== RCS file: editor/util/test/unit/src/org/netbeans/lib/editor/util/PriorityListenerListTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ editor/util/test/unit/src/org/netbeans/lib/editor/util/PriorityListenerListTest.java 24 Mar 2005 16:12:37 -0000 @@ -0,0 +1,217 @@ +/* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.lib.editor.util; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EventListener; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import org.netbeans.junit.NbTest; +import org.netbeans.junit.NbTestCase; +import org.netbeans.junit.NbTestSuite; +import org.openide.util.io.NbMarshalledObject; + + +/** + * Random test of GapList correctness. + * + * @author mmetelka + */ +public class PriorityListenerListTest extends NbTestCase { + + private static final boolean debug = false; + + private static final int SET_RATIO_2 = 50; + + public PriorityListenerListTest(java.lang.String testName) { + super(testName); + } + + protected void setUp() throws java.lang.Exception { + super.setUp(); + } + + protected void tearDown() throws java.lang.Exception { + super.tearDown(); + } + + public void testAddAndRemoveListenersOnThreeLevels() { + int TEST_PRIORITY_1 = 0; + int TEST_PRIORITY_2 = 3; + int TEST_PRIORITY_3 = 2; + + PriorityListenerListCouple couple = new PriorityListenerListCouple(); + L l1 = new L(); + L l11 = new L(); + L l2 = new L(); + L l21 = new L(); + L l3 = new L(); + couple.add(l1, TEST_PRIORITY_1); + couple.add(l2, TEST_PRIORITY_2); + couple.add(l3, TEST_PRIORITY_3); + couple.add(l21, TEST_PRIORITY_2); + couple.add(l11, TEST_PRIORITY_1); + couple.remove(l1, TEST_PRIORITY_1); + couple.remove(l2, TEST_PRIORITY_1); // should do nothing + couple.remove(l2, TEST_PRIORITY_2); + couple.remove(l21, TEST_PRIORITY_2); + couple.remove(l3, TEST_PRIORITY_3); // should remove the levels 2 and 3 + couple.checkLastPriority(1); + couple.add(l3, TEST_PRIORITY_3); + } + + public void testNegativePriorities() { + try { + PriorityListenerList ll = new PriorityListenerList(); + ll.add(new L(), -1); + fail("Should not get here"); + } catch (IndexOutOfBoundsException e) { + // Invalid priority properly catched + } + + try { + PriorityListenerList ll = new PriorityListenerList(); + ll.remove(new L(), -1); + fail("Should not get here"); + } catch (IndexOutOfBoundsException e) { + // Invalid priority properly catched + } + } + + public void testSerialization() throws Exception { + PriorityListenerList ll = new PriorityListenerList(); + ll.add(new L(), 3); + ll.add(new L(), 1); + ll.add(new L(), 1); + + NbMarshalledObject mo = new NbMarshalledObject(ll); + PriorityListenerList sll = (PriorityListenerList)mo.get(); + EventListener[][] lla = ll.getListenersArray(); + EventListener[][] slla = sll.getListenersArray(); + assertEquals(lla.length, slla.length); + for (int priority = lla.length - 1; priority >= 0; priority--) { + assertEquals(lla[priority].length, slla[priority].length); + } + } + + private static final class L implements EventListener, Serializable { + + static final long serialVersionUID = 12345L; + + private int notified; + + public void notifyChange() { + notified++; + } + + public int getNotified() { + return notified; + } + + } + + private static final class PriorityListenerListImitation extends HashMap { + + public synchronized void add(EventListener listener, int priority) { + assertTrue(priority >= 0); + // Add to begining so that fired as last (comply with PriorityListenerList) + getList(priority, true).add(0, listener); + } + + public synchronized void remove(EventListener listener, int priority) { + assertTrue(priority >= 0); + List l = getList(priority, false); + for (int i = l.size() - 1; i >= 0; i--) { + if (l.get(i) == listener) { + l.remove(i); + break; + } + } + } + + public synchronized List getList(int priority) { + return getList(priority, false); + } + + public synchronized void checkEquals(PriorityListenerList priorityListenerList) { + // Check the same listeners are stored in imitation + EventListener[][] listenersArray = priorityListenerList.getListenersArray(); + for (int priority = listenersArray.length - 1; priority >= 0; priority--) { + EventListener[] listeners = listenersArray[priority]; + for (int i = listeners.length - 1; i >= 0; i--) { + assertTrue(getList(priority).get(i) == listeners[i]); + } + } + + // Check there are no extra priorities in the imitation + for (Iterator it = entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry)it.next(); + Integer priorityInteger = (Integer)entry.getKey(); + if (((List)entry.getValue()).size() > 0) { + assertTrue (priorityInteger.intValue() < listenersArray.length); + } + } + } + + private List getList(int priority, boolean forceCreation) { + Integer priorityInteger = new Integer(priority); + List l = (List)get(priorityInteger); + if (l == null) { + if (forceCreation) { + l = new ArrayList(); + put(priorityInteger, l); + } else { // just getting the value + l = Collections.EMPTY_LIST; + } + } + return l; + } + + } + + private static final class PriorityListenerListCouple { + + PriorityListenerList priorityListenerList; + + PriorityListenerListImitation imitation; + + public PriorityListenerListCouple() { + priorityListenerList = new PriorityListenerList(); + imitation = new PriorityListenerListImitation(); + } + + public void add(EventListener listener, int priority) { + priorityListenerList.add(listener, priority); + imitation.add(listener, priority); + imitation.checkEquals(priorityListenerList); + } + + public void remove(EventListener listener, int priority) { + priorityListenerList.remove(listener, priority); + imitation.remove(listener, priority); + imitation.checkEquals(priorityListenerList); + } + + public void checkLastPriority(int priority) { + assertTrue(priorityListenerList.getListenersArray().length - 1 == priority); + } + + } + +}