KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > nodes > Children


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.openide.nodes;
21
22 import java.lang.ref.Reference JavaDoc;
23 import java.lang.ref.WeakReference JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.Arrays JavaDoc;
26 import java.util.Collection JavaDoc;
27 import java.util.Collections JavaDoc;
28 import java.util.Comparator JavaDoc;
29 import java.util.Enumeration JavaDoc;
30 import java.util.HashMap JavaDoc;
31 import java.util.HashSet JavaDoc;
32 import java.util.Iterator JavaDoc;
33 import java.util.LinkedList JavaDoc;
34 import java.util.List JavaDoc;
35 import java.util.Set JavaDoc;
36 import java.util.TreeSet JavaDoc;
37 import java.util.logging.Level JavaDoc;
38 import java.util.logging.Logger JavaDoc;
39 import org.openide.util.Enumerations;
40 import org.openide.util.Mutex;
41
42 /** Container for array of nodes.
43 * Can be {@link Node#Node associated} with a node and then
44 * all children in the array have that node set as a parent, and this list
45 * will be returned as the node's children.
46 *
47 * <p>Probably you want to subclass {@link Children.Keys}.
48 * Subclassing <code>Children</code> directly is not recommended.
49 *
50 * @author Jaroslav Tulach
51 */

52 public abstract class Children extends Object JavaDoc {
53     /** A package internal accessor object to provide priviledged
54      * access to children.
55      */

56     static final Mutex.Privileged PR = new Mutex.Privileged();
57
58     /** Lock for access to hierarchy of all node lists.
59     * Anyone who needs to ensure that there will not
60     * be shared accesses to hierarchy nodes can use this
61     * mutex.
62     * <P>
63     * All operations on the hierarchy of nodes (add, remove, etc.) are
64     * done in the {@link Mutex#writeAccess} method of this lock, so if someone
65     * needs for a certain amount of time to forbid modification,
66     * he can execute his code in {@link Mutex#readAccess}.
67     */

68     public static final Mutex MUTEX = new Mutex(PR);
69
70     /** The object representing an empty set of children. Should
71     * be used to represent the children of leaf nodes. The same
72     * object may be used by all such nodes.
73     */

74     public static final Children LEAF = new Empty();
75     private static final Object JavaDoc LOCK = new Object JavaDoc();
76     private static final Logger JavaDoc LOG_GET_ARRAY = Logger.getLogger(
77             "org.openide.nodes.Children.getArray"
78         ); // NOI18N
79

80     /** parent node for all nodes in this list (can be null) */
81     private Node parent;
82
83     /** mapping from entries to info about them */
84     private java.util.Map JavaDoc<Entry,Info> map;
85
86     /** collection of all entries */
87     private Collection JavaDoc<? extends Entry> entries = Collections.emptyList();
88
89     private Reference JavaDoc<ChildrenArray> array = new WeakReference JavaDoc<ChildrenArray>(null);
90
91     /** Obtains references to array holder. If it does not exist, it is
92     * created.
93      *
94      * @param cannotWorkBetter array of size 1 or null, will contain true, if
95      * the getArray cannot be initialized (we are under read access
96      * and another thread is responsible for initialization, in such case
97      * give up on computation of best result
98     */

99     private Thread JavaDoc initThread;
100
101     /*
102       private StringBuffer debug = new StringBuffer ();
103
104       private void printStackTrace() {
105         Exception e = new Exception ();
106         java.io.StringWriter w1 = new java.io.StringWriter ();
107         java.io.PrintWriter w = new java.io.PrintWriter (w1);
108         e.printStackTrace(w);
109         w.close ();
110         debug.append (w1.toString ());
111         debug.append ('\n');
112       }
113     */

114
115     /** Constructor.
116     */

117     public Children() {
118     }
119
120     /** Setter of parent node for this list of children. Each children in the list
121     * will have this node set as parent. The parent node will return nodes in
122     * this list as its children.
123     * <P>
124     * This method is called from the Node constructor
125     *
126     * @param n node to attach to
127     * @exception IllegalStateException when this object is already used with
128     * different node
129     */

130     final void attachTo(final Node n) throws IllegalStateException JavaDoc {
131         // special treatment for LEAF object.
132
if (this == LEAF) {
133             // do not attaches the node because the LEAF cannot have children
134
// and that is why it need not set parent node for them
135
return;
136         }
137
138         synchronized (this) {
139             if (parent != null) {
140                 // already used
141
throw new IllegalStateException JavaDoc(
142                     "An instance of Children may not be used for more than one parent node."
143                 ); // NOI18N
144
}
145
146             // attach itself as a node list for given node
147
parent = n;
148         }
149
150         // this is the only place where parent is changed,
151
// but only under readAccess => double check if
152
// it happened correctly
153
try {
154             PR.enterReadAccess();
155
156             Node[] nodes = testNodes();
157
158             if (nodes == null) {
159                 return;
160             }
161
162             // fire the change
163
for (int i = 0; i < nodes.length; i++) {
164                 Node node = nodes[i];
165                 node.assignTo(Children.this, i);
166                 node.fireParentNodeChange(null, parent);
167             }
168         } finally {
169             PR.exitReadAccess();
170         }
171     }
172
173     /** Called when the node changes it's children to different nodes.
174     *
175     * @param n node to detach from
176     * @exception IllegalStateException if children were already detached
177     */

178     final void detachFrom() {
179         // special treatment for LEAF object.
180
if (this == LEAF) {
181             // no need to do anything
182
return;
183         }
184
185         Node oldParent = null;
186
187         synchronized (this) {
188             if (parent == null) {
189                 // already detached
190
throw new IllegalStateException JavaDoc("Trying to detach children which do not have parent"); // NOI18N
191
}
192
193             // remember old parent
194
oldParent = parent;
195
196             // attach itself as a node list for given node
197
parent = null;
198         }
199
200         // this is the only place where parent is changed,
201
// but only under readAccess => double check if
202
// it happened correctly
203
try {
204             PR.enterReadAccess();
205
206             Node[] nodes = testNodes();
207
208             if (nodes == null) {
209                 return;
210             }
211
212             // fire the change
213
for (int i = 0; i < nodes.length; i++) {
214                 Node node = nodes[i];
215                 node.deassignFrom(Children.this);
216                 node.fireParentNodeChange(oldParent, null);
217             }
218         } finally {
219             PR.exitReadAccess();
220         }
221     }
222
223     /** Get the parent node of these children.
224     * @return the node attached to this children object, or <code>null</code> if there is none yet
225     */

226     protected final Node getNode() {
227         return parent;
228     }
229
230     /** Allows access to the clone method for Node.
231     * @return cloned hierarchy
232     * @exception CloneNotSupportedException if not supported
233     */

234     final Object JavaDoc cloneHierarchy() throws CloneNotSupportedException JavaDoc {
235         return clone();
236     }
237
238     /** Handles cloning in the right way, that can be later extended by
239     * subclasses. Of course each subclass that wishes to support cloning
240     * must implement the <code>Cloneable</code> interface, otherwise this method throws
241     * <code>CloneNotSupportedException</code>.
242     *
243     * @return cloned version of this object, with the same class, uninitialized and without
244     * a parent node
245     * *exception CloneNotSupportedException if <code>Cloneable</code> interface is not implemented
246     */

247     protected Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
248         Children ch = (Children) super.clone();
249
250         ch.parent = null;
251         ch.map = null;
252         ch.entries = Collections.emptyList();
253         ch.array = new WeakReference JavaDoc<ChildrenArray>(null);
254
255         return ch;
256     }
257
258     /**
259      * Add nodes to this container but <strong>do not call this method</strong>.
260      * If you think you need to do this probably you really wanted to use
261      * {@link Children.Keys#setKeys} instead.
262     * The parent node of these nodes
263     * is changed to the parent node of this list. Each node can be added
264     * only once. If there is some reason a node cannot be added, for example
265     * if the node expects only a special type of subnodes, the method should
266     * do nothing and return <code>false</code> to signal that the addition has not been successful.
267     * <P>
268     * This method should be implemented by subclasses to filter some nodes, etc.
269     *
270     * @param nodes set of nodes to add to the list
271     * @return <code>true</code> if successfully added
272     */

273     public abstract boolean add(final Node[] nodes);
274
275     /** Remove nodes from the list. Only nodes that are present are
276     * removed.
277     *
278     * @param nodes nodes to be removed
279     * @return <code>true</code> if the nodes could be removed
280     */

281     public abstract boolean remove(final Node[] nodes);
282
283     /** Get the nodes as an enumeration.
284     * @return enumeration of nodes
285     */

286     public final Enumeration JavaDoc<Node> nodes() {
287         return Enumerations.array(getNodes());
288     }
289
290     /** Find a child node by name.
291     * This may be overridden in subclasses to provide a more advanced way of finding the
292     * child, but the default implementation simply scans through the list of nodes
293     * to find the first one with the requested name.
294     * <p>Normally the list of nodes should have been computed by the time this returns,
295     * but see {@link #getNodes()} for an important caveat as to why this may not
296     * be doing what you want and what to do instead.
297     * @param name (code) name of child node to find or <code>null</code> if any arbitrary child may
298     * be returned
299     * @return the node or <code>null</code> if it could not be found
300     */

301     public Node findChild(String JavaDoc name) {
302         Node[] list = getNodes();
303
304         if (list.length == 0) {
305             return null;
306         }
307
308         if (name == null) {
309             // return any node
310
return list[0];
311         }
312
313         for (int i = 0; i < list.length; i++) {
314             if (name.equals(list[i].getName())) {
315                 // ok, we have found it
316
return list[i];
317             }
318         }
319
320         return null;
321     }
322
323     /** Method that can be used to test whether the children content has
324     * ever been used or it is still not initalized.
325     * @return true if children has been used before
326     * @see #addNotify
327     */

328     protected final boolean isInitialized() {
329         ChildrenArray arr = array.get();
330
331         return (arr != null) && arr.isInitialized();
332     }
333
334     /** Get's the node at given position. Package private right now
335      * to allow tests to use it.
336      */

337     final Node getNodeAt(int i) {
338         return getNodes()[i];
339     }
340
341     /** Get a (sorted) array of nodes in this list.
342      * If the children object is not yet initialized,
343      * it will be (using {@link #addNotify}) before
344      * the nodes are returned.
345      * <p><strong>Warning:</strong> not all children
346      * implementations do a complete calculation at
347      * this point, see {@link #getNodes(boolean)}
348      * @return array of nodes
349      */

350
351     // private static String off = ""; // NOI18N
352
public final Node[] getNodes() {
353         //Thread.dumpStack();
354
//System.err.println(off + "getNodes: " + getNode ());
355
boolean[] results = new boolean[2];
356
357         for (;;) {
358             results[1] = isInitialized();
359
360             // initializes the ChildrenArray possibly calls
361
// addNotify if this is for the first time
362
ChildrenArray array = getArray(results); // fils results[0]
363

364             Node[] nodes;
365
366             try {
367                 PR.enterReadAccess();
368
369                 nodes = array.nodes();
370             } finally {
371                 PR.exitReadAccess();
372             }
373
374             final boolean IS_LOG_GET_ARRAY = LOG_GET_ARRAY.isLoggable(Level.FINE);
375             if (IS_LOG_GET_ARRAY) {
376                 LOG_GET_ARRAY.fine(" length : " + nodes.length); // NOI18N
377
LOG_GET_ARRAY.fine(" entries : " + entries); // NOI18N
378
LOG_GET_ARRAY.fine(" init now : " + isInitialized()); // NOI18N
379
}
380             // if not initialized that means that after
381
// we computed the nodes, somebody changed them (as a
382
// result of addNotify) => we have to compute them
383
// again
384
if (results[1]) {
385                 // otherwise it is ok.
386
return nodes;
387             }
388
389             if (results[0]) {
390                 // looks like the result cannot be computed, just give empty one
391
return (nodes == null) ? new Node[0] : nodes;
392             }
393         }
394     }
395
396     /** Get a (sorted) array of nodes in this list.
397      *
398      * This method is usefull if you need a fully initialized array of nodes
399      * for things like MenuView, node navigation from scripts/tests and so on.
400      * But in general if you are trying to get useful data by calling
401      * this method, you are probably doing something wrong.
402      * Usually you should be asking some underlying model
403      * for information, not the nodes for children. For example,
404      * <code>DataFolder.getChildren()</code>
405      * is a much more appropriate way to get what you want for the case of folder children.
406      *
407      * If you're extending children, you should make sure this method
408      * will return a complete list of nodes. The default implementation will do
409      * this correctly so long as your subclass implement findChild(null)
410      * to initialize all subnodes.
411      *
412      * <p><strong>Note:</strong>You should not call this method from inside
413      * <code>{@link org.openide.nodes.Children#MUTEX Children.MUTEX}.readAccess()</code>.
414      * If you do so, the <code>Node</code> will be unable to update its state
415      * before you leave the <code>readAccess()</code>.
416      *
417      * @since 2.17
418      *
419      * @param optimalResult whether to try to get a fully initialized array
420      * or to simply delegate to {@link #getNodes()}
421      * @return array of nodes
422      */

423     public Node[] getNodes(boolean optimalResult) {
424         ChildrenArray hold;
425         Node find;
426         if (optimalResult) {
427             final boolean IS_LOG_GET_ARRAY = LOG_GET_ARRAY.isLoggable(Level.FINE);
428             if (IS_LOG_GET_ARRAY) {
429                 LOG_GET_ARRAY.fine("computing optimal result");// NOI18N
430
}
431             hold = getArray(null);
432             if (IS_LOG_GET_ARRAY) {
433                 LOG_GET_ARRAY.fine("optimal result is here: " + hold);// NOI18N
434
}
435             find = findChild(null);
436             if (IS_LOG_GET_ARRAY) {
437                 LOG_GET_ARRAY.fine("Find child got: " + find); // NOI18N
438
}
439         }
440
441         return getNodes();
442     }
443
444     /** Get the number of nodes in the list.
445     * @return the count
446     */

447     public final int getNodesCount() {
448         return getNodes().length;
449     }
450
451     //
452
// StateNotifications
453
//
454

455     /** Called when children are first asked for nodes.
456      * Typical implementations at this time calculate
457      * their node list (or keys for {@link Children.Keys} etc.).<BR>
458      * Notice: call to getNodes() inside of this method will return
459      * an empty array of nodes.
460      * @see #isInitialized
461     */

462     protected void addNotify() {
463     }
464
465     /** Called when the list of nodes for this children
466      * object is no longer needed.
467      * Typical implementations at this time remove all
468      * children to save memory (or clear the keys for
469      * {@link Children.Keys} etc.).
470     */

471     protected void removeNotify() {
472     }
473
474     /** Method that can be overriden in subclasses to
475     * do additional work and then call addNotify.
476     */

477     void callAddNotify() {
478         addNotify();
479     }
480
481     //
482
// ChildrenArray operations call only under lock
483
//
484

485     /** @return either nodes associated with this children or null if
486     * they are not created
487     */

488     private Node[] testNodes() {
489         ChildrenArray arr = array.get();
490
491         return (arr == null) ? null : arr.nodes();
492     }
493
494     private ChildrenArray getArray(boolean[] cannotWorkBetter) {
495         final boolean IS_LOG_GET_ARRAY = LOG_GET_ARRAY.isLoggable(Level.FINE);
496
497         ChildrenArray arr;
498         boolean doInitialize = false;
499         synchronized (LOCK) {
500             arr = array.get();
501
502             if (arr == null) {
503                 arr = new ChildrenArray();
504
505                 // register the array with the children
506
registerChildrenArray(arr, true);
507                 doInitialize = true;
508                 initThread = Thread.currentThread();
509             }
510         }
511
512         if (doInitialize) {
513             if (IS_LOG_GET_ARRAY) {
514                 LOG_GET_ARRAY.fine("Initialize " + this + " on " + Thread.currentThread()); // NOI18N
515
}
516
517             // this call can cause a lot of callbacks => be prepared
518
// to handle them as clean as possible
519
try {
520                 this.callAddNotify();
521
522                 if (IS_LOG_GET_ARRAY) {
523                     LOG_GET_ARRAY.fine("addNotify successfully called for " + this + " on " + Thread.currentThread()); // NOI18N
524
}
525             } finally {
526                 boolean notifyLater;
527                 notifyLater = MUTEX.isReadAccess();
528
529                 if (IS_LOG_GET_ARRAY) {
530                     LOG_GET_ARRAY.fine(
531                         "notifyAll for " + this + " on " + Thread.currentThread() + " notifyLater: " + notifyLater
532                     ); // NOI18N
533
}
534
535                 // now attach to children, so when children == null => we are
536
// not fully initialized!!!!
537
arr.children = this;
538                 class SetAndNotify implements Runnable JavaDoc {
539                     public ChildrenArray toSet;
540                     public Children whatSet;
541
542                     public void run() {
543                         synchronized (LOCK) {
544                             initThread = null;
545                             LOCK.notifyAll();
546                         }
547                         
548                         if (IS_LOG_GET_ARRAY) {
549                             LOG_GET_ARRAY.fine(
550                                 "notifyAll done"
551                             ); // NOI18N
552
}
553                         
554                     }
555                 }
556
557                 SetAndNotify setAndNotify = new SetAndNotify();
558                 setAndNotify.toSet = arr;
559                 setAndNotify.whatSet = this;
560
561                 if (notifyLater) {
562                     // the notify to the lock has to be done later than
563
// setKeys is executed, otherwise the result of addNotify
564
// might not be visible to other threads
565
// fix for issue 50308
566
MUTEX.postWriteRequest(setAndNotify);
567                 } else {
568                     setAndNotify.run();
569                 }
570             }
571         } else {
572             // otherwise, if not initialize yet (arr.children) wait
573
// for the initialization to finish, but only if we can wait
574
if (MUTEX.isReadAccess() || MUTEX.isWriteAccess() || (initThread == Thread.currentThread())) {
575                 // fail, we are in read access
576
if (IS_LOG_GET_ARRAY) {
577                     LOG_GET_ARRAY.log(Level.FINE,
578                         "cannot initialize better " + this + // NOI18N
579
" on " + Thread.currentThread() + // NOI18N
580
" read access: " + MUTEX.isReadAccess() + // NOI18N
581
" initThread: " + initThread, // NOI18N
582
new Exception JavaDoc("StackTrace") // NOI18N
583
);
584                 }
585
586                 if (cannotWorkBetter != null) {
587                     cannotWorkBetter[0] = true;
588                 }
589
590                 return arr;
591             }
592
593             // otherwise we can wait
594
synchronized (LOCK) {
595                 while (initThread != null) {
596                     if (IS_LOG_GET_ARRAY) {
597                         LOG_GET_ARRAY.fine(
598                             "waiting for children for " + this + // NOI18N
599
" on " + Thread.currentThread() // NOI18N
600
);
601                     }
602
603                     try {
604                         LOCK.wait();
605                     } catch (InterruptedException JavaDoc ex) {
606                     }
607                 }
608             }
609             if (IS_LOG_GET_ARRAY) {
610                 LOG_GET_ARRAY.fine(
611                     " children are here for " + this + // NOI18N
612
" on " + Thread.currentThread() + // NOI18N
613
" children " + arr.children
614                 );
615             }
616         }
617
618         return arr;
619     }
620
621     /** Clears the nodes
622     */

623     private void clearNodes() {
624         ChildrenArray arr = array.get();
625
626         //System.err.println(off + " clearNodes: " + getNode ());
627
if (arr != null) {
628             // clear the array
629
arr.clear();
630         }
631     }
632
633     /** Forces finalization of nodes for given info.
634     * Called from finalizer of Info.
635     */

636     final void finalizeNodes() {
637         ChildrenArray arr = array.get();
638
639         if (arr != null) {
640             arr.finalizeNodes();
641         }
642     }
643
644     /** Registration of ChildrenArray.
645     * @param chArr the associated ChildrenArray
646     * @param weak use weak or hard reference
647     */

648     final void registerChildrenArray(final ChildrenArray chArr, boolean weak) {
649         final boolean IS_LOG_GET_ARRAY = LOG_GET_ARRAY.isLoggable(Level.FINE);
650         if (IS_LOG_GET_ARRAY) {
651             LOG_GET_ARRAY.fine("registerChildrenArray: " + chArr + " weak: " + weak); // NOI18N
652
}
653         if (weak) {
654             this.array = new WeakReference JavaDoc<ChildrenArray>(chArr);
655         } else {
656             // hold the children hard
657
this.array = new WeakReference JavaDoc<ChildrenArray>(chArr) {
658                 @Override JavaDoc
659                 public ChildrenArray get() {
660                     return chArr;
661                 }
662             };
663         }
664         
665         chArr.pointedBy(this.array);
666         if (IS_LOG_GET_ARRAY) {
667             LOG_GET_ARRAY.fine("pointed by: " + chArr + " to: " + this.array); // NOI18N
668
}
669     }
670
671     /** Finalized.
672     */

673     final void finalizedChildrenArray(Reference JavaDoc caller) {
674         final boolean IS_LOG_GET_ARRAY = LOG_GET_ARRAY.isLoggable(Level.FINE);
675         // usually in removeNotify setKeys is called => better require write access
676
try {
677             PR.enterWriteAccess();
678
679             if (IS_LOG_GET_ARRAY) {
680                 LOG_GET_ARRAY.fine("previous array: " + array + " caller: " + caller);
681             }
682             if (array == caller) {
683                 // really finalized and not reconstructed
684
removeNotify();
685             }
686
687             /*
688             else {
689                 System.out.println("Strange removeNotify " + caller + " : " + value );
690             }
691             */

692         } finally {
693             PR.exitWriteAccess();
694         }
695     }
696
697     /** Computes the nodes now.
698     */

699     final Node[] justComputeNodes() {
700         if (map == null) {
701             map = Collections.synchronizedMap(new HashMap JavaDoc<Entry,Info>(17));
702
703             // debug.append ("Map initialized\n"); // NOI18N
704
// printStackTrace();
705
}
706
707         List JavaDoc<Node> l = new LinkedList JavaDoc<Node>();
708         for (Entry entry : entries) {
709             Info info = findInfo(entry);
710
711             try {
712                 l.addAll(info.nodes());
713             } catch (RuntimeException JavaDoc ex) {
714                 NodeOp.warning(ex);
715             }
716         }
717
718         Node[] arr = l.toArray(new Node[l.size()]);
719
720         // initialize parent nodes
721
for (int i = 0; i < arr.length; i++) {
722             Node n = arr[i];
723             n.assignTo(this, i);
724             n.fireParentNodeChange(null, parent);
725         }
726
727         return arr;
728     }
729
730     /** Finds info for given entry, or registers
731     * it, if not registered yet.
732     */

733     private Info findInfo(Entry entry) {
734         synchronized(map) {
735             Info info = map.get(entry);
736
737             if (info == null) {
738                 info = new Info(entry);
739                 map.put(entry, info);
740
741                 // debug.append ("Put: " + entry + " info: " + info); // NOI18N
742
// debug.append ('\n');
743
// printStackTrace();
744
}
745             return info;
746         }
747     }
748
749     //
750
// Entries
751
//
752

753     /** Access to copy of current entries.
754     * @return copy of entries in the objects
755     */

756     final List JavaDoc<Entry> getEntries() {
757         return new ArrayList JavaDoc<Entry>(this.entries);
758     }
759
760     final void setEntries(Collection JavaDoc<? extends Entry> entries) {
761         final boolean IS_LOG_GET_ARRAY = LOG_GET_ARRAY.isLoggable(Level.FINE);
762         // current list of nodes
763
ChildrenArray holder = array.get();
764
765         if (IS_LOG_GET_ARRAY) {
766             LOG_GET_ARRAY.fine("setEntries for " + this + " on " + Thread.currentThread()); // NOI18N
767
LOG_GET_ARRAY.fine(" values: " + entries); // NOI18N
768
LOG_GET_ARRAY.fine(" holder: " + holder); // NOI18N
769
}
770         if (holder == null) {
771             // debug.append ("Set1: " + entries); // NOI18N
772
// printStackTrace();
773
this.entries = entries;
774
775             if (map != null) {
776                 map.keySet().retainAll(new HashSet JavaDoc<Entry>(entries));
777             }
778
779             return;
780         }
781
782         Node[] current = holder.nodes();
783
784         if (current == null) {
785             // the initialization is not finished yet =>
786
// debug.append ("Set2: " + entries); // NOI18N
787
// printStackTrace();
788
this.entries = entries;
789
790             if (map != null) {
791                 map.keySet().retainAll(new HashSet JavaDoc<Entry>(entries));
792             }
793
794             return;
795         }
796
797         // if there are old items in the map, remove them to
798
// reflect current state
799
map.keySet().retainAll(new HashSet JavaDoc<Entry>(this.entries));
800
801         // what should be removed
802
Set JavaDoc<Entry> toRemove = new HashSet JavaDoc<Entry>(map.keySet());
803         Set JavaDoc<Entry> entriesSet = new HashSet JavaDoc<Entry>(entries);
804         toRemove.removeAll(entriesSet);
805
806         if (!toRemove.isEmpty()) {
807             // notify removing, the set must be ready for
808
// callbacks with questions
809
updateRemove(current, toRemove);
810             current = holder.nodes();
811         }
812
813         // change the order of entries, notifies
814
// it and again brings children to up-to-date state
815
Collection JavaDoc<Info> toAdd = updateOrder(current, entries);
816
817         if (!toAdd.isEmpty()) {
818             // toAdd contains Info objects that should
819
// be added
820
updateAdd(toAdd, entries);
821         }
822     }
823
824     private void checkInfo(Info info, Entry entry, Collection JavaDoc<? extends Entry> entries, java.util.Map JavaDoc<Entry,Info> map) {
825         if (info == null) {
826             throw new IllegalStateException JavaDoc(
827                 "Error in " + getClass().getName() + " with entry " + entry + " from among " + entries + " in " + map + // NOI18N
828
" probably caused by faulty key implementation." + // NOI18N
829
" The key hashCode() and equals() methods must behave as for an IMMUTABLE object" + // NOI18N
830
" and the hashCode() must return the same value for equals() keys."
831             ); // NOI18N
832
}
833     }
834     
835     /** Removes the objects from the children.
836     */

837     private void updateRemove(Node[] current, Set JavaDoc<Entry> toRemove) {
838         List JavaDoc<Node> nodes = new LinkedList JavaDoc<Node>();
839
840         for (Entry en : toRemove) {
841             Info info = map.remove(en);
842
843             //debug.append ("Removed: " + en + " info: " + info); // NOI18N
844
//debug.append ('\n');
845
//printStackTrace();
846
checkInfo(info, en, null, map);
847             
848             nodes.addAll(info.nodes());
849         }
850
851         // modify the current set of entries and empty the list of nodes
852
// so it has to be recreated again
853
//debug.append ("Current : " + this.entries + '\n'); // NOI18N
854
this.entries.removeAll(toRemove);
855
856         //debug.append ("Removing: " + toRemove + '\n'); // NOI18N
857
//debug.append ("New : " + this.entries + '\n'); // NOI18N
858
//printStackTrace();
859
clearNodes();
860
861         notifyRemove(nodes, current);
862     }
863
864     /** Notifies that a set of nodes has been removed from
865     * children. It is necessary that the system is already
866     * in consistent state, so any callbacks will return
867     * valid values.
868     *
869     * @param nodes list of removed nodes
870     * @param current state of nodes
871     * @return array of nodes that were deleted
872     */

873     Node[] notifyRemove(Collection JavaDoc<Node> nodes, Node[] current) {
874         //System.err.println("notifyRemove from: " + getNode ());
875
//System.err.println("notifyRemove: " + nodes);
876
//System.err.println("Current : " + Arrays.asList (current));
877
//Thread.dumpStack();
878
//Keys.last.printStackTrace();
879
// [TODO] Children do not have always a parent
880
// see Services->FIRST ($SubLevel.class)
881
// during a deserialization it may have parent == null
882
Node[] arr = nodes.toArray(new Node[nodes.size()]);
883
884         if (parent == null) {
885             return arr;
886         }
887
888         // fire change of nodes
889
parent.fireSubNodesChange(false, // remove
890
arr, current
891         );
892
893         // fire change of parent
894
Iterator JavaDoc it = nodes.iterator();
895
896         while (it.hasNext()) {
897             Node n = (Node) it.next();
898             n.deassignFrom(this);
899             n.fireParentNodeChange(parent, null);
900         }
901
902         return arr;
903     }
904
905     /** Updates the order of entries.
906     * @param current current state of nodes
907     * @param entries new set of entries
908     * @return list of infos that should be added
909     */

910     private List JavaDoc<Info> updateOrder(Node[] current, Collection JavaDoc<? extends Entry> newEntries) {
911         List JavaDoc<Info> toAdd = new LinkedList JavaDoc<Info>();
912
913         // that assignes entries their begining position in the array
914
// of nodes
915
java.util.Map JavaDoc<Info,Integer JavaDoc> offsets = new HashMap JavaDoc<Info,Integer JavaDoc>();
916
917         {
918             int previousPos = 0;
919
920             for (Entry entry : entries) {
921                 Info info = map.get(entry);
922                 checkInfo(info, entry, entries, map);
923
924                 offsets.put(info, previousPos);
925
926                 previousPos += info.length();
927             }
928         }
929
930         // because map can contain some additional items,
931
// that has not been garbage collected yet,
932
// retain only those that are in current list of
933
// entries
934
map.keySet().retainAll(new HashSet JavaDoc<Entry>(entries));
935
936         int[] perm = new int[current.length];
937         int currentPos = 0;
938         int permSize = 0;
939         List JavaDoc<Entry> reorderedEntries = null;
940
941         for (Entry entry : newEntries) {
942             Info info = map.get(entry);
943
944             if (info == null) {
945                 // this info has to be added
946
info = new Info(entry);
947                 toAdd.add(info);
948             } else {
949                 int len = info.length();
950
951                 if (reorderedEntries == null) {
952                     reorderedEntries = new LinkedList JavaDoc<Entry>();
953                 }
954
955                 reorderedEntries.add(entry);
956
957                 // already there => test if it should not be reordered
958
Integer JavaDoc previousInt = offsets.get(info);
959
960                 /*
961                         if (previousInt == null) {
962                           System.err.println("Offsets: " + offsets);
963                           System.err.println("Info: " + info);
964                           System.err.println("Entry: " + info.entry);
965                           System.err.println("This entries: " + this.entries);
966                           System.err.println("Entries: " + entries);
967                           System.err.println("Map: " + map);
968
969                           System.err.println("---------vvvvv");
970                           System.err.println(debug);
971                           System.err.println("---------^^^^^");
972
973                         }
974                 */

975                 int previousPos = previousInt;
976
977                 if (currentPos != previousPos) {
978                     for (int i = 0; i < len; i++) {
979                         perm[previousPos + i] = 1 + currentPos + i;
980                     }
981
982                     permSize += len;
983                 }
984             }
985
986             currentPos += info.length();
987         }
988
989         if (permSize > 0) {
990             // now the perm array contains numbers 1 to ... and
991
// 0 one places where no permutation occures =>
992
// decrease numbers, replace zeros
993
for (int i = 0; i < perm.length; i++) {
994                 if (perm[i] == 0) {
995                     // fixed point
996
perm[i] = i;
997                 } else {
998                     // decrease
999
perm[i]--;
1000                }
1001            }
1002
1003            // reorderedEntries are not null
1004
this.entries = reorderedEntries;
1005
1006            // debug.append ("Set3: " + this.entries); // NOI18N
1007
// printStackTrace();
1008
// notify the permutation to the parent
1009
clearNodes();
1010
1011            //System.err.println("Paremutaiton! " + getNode ());
1012
Node p = parent;
1013
1014            if (p != null) {
1015                p.fireReorderChange(perm);
1016            }
1017        }
1018
1019        return toAdd;
1020    }
1021
1022    /** Updates the state of children by adding given Infos.
1023    * @param infos list of Info objects to add
1024    * @param entries the final state of entries that should occur
1025    */

1026    private void updateAdd(Collection JavaDoc<Info> infos, Collection JavaDoc<? extends Entry> entries) {
1027        List JavaDoc<Node> nodes = new LinkedList JavaDoc<Node>();
1028        for (Info info : infos) {
1029            nodes.addAll(info.nodes());
1030            map.put(info.entry, info);
1031
1032            // debug.append ("updateadd: " + info.entry + " info: " + info + '\n'); // NOI18N
1033
// printStackTrace();
1034
}
1035
1036        this.entries = entries;
1037
1038        // debug.append ("Set4: " + entries); // NOI18N
1039
// printStackTrace();
1040
clearNodes();
1041
1042        notifyAdd(nodes);
1043    }
1044
1045    /** Notifies that a set of nodes has been add to
1046    * children. It is necessary that the system is already
1047    * in consistent state, so any callbacks will return
1048    * valid values.
1049    *
1050    * @param nodes list of removed nodes
1051    */

1052    private void notifyAdd(Collection JavaDoc<Node> nodes) {
1053        // notify about parent change
1054
for (Node n : nodes) {
1055            n.assignTo(this, -1);
1056            n.fireParentNodeChange(null, parent);
1057        }
1058
1059        Node[] arr = nodes.toArray(new Node[nodes.size()]);
1060
1061        Node n = parent;
1062
1063        if (n != null) {
1064            n.fireSubNodesChange(true, arr, null);
1065        }
1066    }
1067
1068    /** Refreshes content of one entry. Updates the state of children
1069    * appropriately.
1070    */

1071    final void refreshEntry(Entry entry) {
1072        // current list of nodes
1073
ChildrenArray holder = array.get();
1074
1075        if (holder == null) {
1076            return;
1077        }
1078
1079        Node[] current = holder.nodes();
1080
1081        if (current == null) {
1082            // the initialization is not finished yet =>
1083
return;
1084        }
1085
1086        // because map can contain some additional items,
1087
// that has not been garbage collected yet,
1088
// retain only those that are in current list of
1089
// entries
1090
map.keySet().retainAll(new HashSet JavaDoc<Entry>(this.entries));
1091
1092        Info info = map.get(entry);
1093
1094        if (info == null) {
1095            // refresh of entry that is not present =>
1096
return;
1097        }
1098
1099        Collection JavaDoc<Node> oldNodes = info.nodes();
1100        Collection JavaDoc<Node> newNodes = info.entry.nodes();
1101
1102        if (oldNodes.equals(newNodes)) {
1103            // nodes are the same =>
1104
return;
1105        }
1106
1107        Set JavaDoc<Node> toRemove = new HashSet JavaDoc<Node>(oldNodes);
1108        toRemove.removeAll(newNodes);
1109
1110        if (!toRemove.isEmpty()) {
1111            // notify removing, the set must be ready for
1112
// callbacks with questions
1113
// modifies the list associated with the info
1114
oldNodes.removeAll(toRemove);
1115            clearNodes();
1116
1117            // now everything should be consistent => notify the remove
1118
notifyRemove(toRemove, current);
1119
1120            current = holder.nodes();
1121        }
1122
1123        List JavaDoc<Node> toAdd = refreshOrder(entry, oldNodes, newNodes);
1124        info.useNodes(newNodes);
1125
1126        if (!toAdd.isEmpty()) {
1127            // modifies the list associated with the info
1128
clearNodes();
1129            notifyAdd(toAdd);
1130        }
1131    }
1132
1133    /** Updates the order of nodes after a refresh.
1134    * @param entry the refreshed entry
1135    * @param oldNodes nodes that are currently in the list
1136    * @param newNodes new nodes (defining the order of oldNodes and some more)
1137    * @return list of infos that should be added
1138    */

1139    private List JavaDoc<Node> refreshOrder(Entry entry, Collection JavaDoc<Node> oldNodes, Collection JavaDoc<Node> newNodes) {
1140        List JavaDoc<Node> toAdd = new LinkedList JavaDoc<Node>();
1141
1142        int currentPos = 0;
1143
1144        // cycle thru all entries to find index of the entry
1145
Iterator JavaDoc<? extends Entry> it1 = this.entries.iterator();
1146
1147        for (;;) {
1148            Entry e = it1.next();
1149
1150            if (e.equals(entry)) {
1151                break;
1152            }
1153
1154            Info info = findInfo(e);
1155            currentPos += info.length();
1156        }
1157
1158        Set JavaDoc<Node> oldNodesSet = new HashSet JavaDoc<Node>(oldNodes);
1159        Set JavaDoc<Node> toProcess = new HashSet JavaDoc<Node>(oldNodesSet);
1160
1161        Node[] permArray = new Node[oldNodes.size()];
1162        Iterator JavaDoc<Node> it2 = newNodes.iterator();
1163
1164        int pos = 0;
1165
1166        while (it2.hasNext()) {
1167            Node n = it2.next();
1168
1169            if (oldNodesSet.remove(n)) {
1170                // the node is in the old set => test for permuation
1171
permArray[pos++] = n;
1172            } else {
1173                if (!toProcess.contains(n)) {
1174                    // if the node has not been processed yet
1175
toAdd.add(n);
1176                } else {
1177                    it2.remove();
1178                }
1179            }
1180        }
1181
1182        // JST: If you get IllegalArgumentException in following code
1183
// then it can be cause by wrong synchronization between
1184
// equals and hashCode methods. First of all check them!
1185
int[] perm = NodeOp.computePermutation(oldNodes.toArray(new Node[oldNodes.size()]), permArray);
1186
1187        if (perm != null) {
1188            // apply the permutation
1189
clearNodes();
1190
1191            // temporarily change the nodes the entry should use
1192
findInfo(entry).useNodes(Arrays.asList(permArray));
1193
1194            Node p = parent;
1195
1196            if (p != null) {
1197                p.fireReorderChange(perm);
1198            }
1199        }
1200
1201        return toAdd;
1202    }
1203
1204    /** Interface that provides a set of nodes.
1205    */

1206    static interface Entry {
1207        /** Set of nodes associated with this entry.
1208        */

1209        public Collection JavaDoc<Node> nodes();
1210    }
1211
1212    /** Information about an entry. Contains number of nodes,
1213    * position in the array of nodes, etc.
1214    */

1215    final class Info extends Object JavaDoc {
1216        int length;
1217        final Entry entry;
1218
1219        public Info(Entry entry) {
1220            this.entry = entry;
1221        }
1222
1223        /** Finalizes the content of ChildrenArray.
1224        */

1225        protected void finalize() {
1226            finalizeNodes();
1227        }
1228
1229        public Collection JavaDoc<Node> nodes() {
1230            // forces creation of the array
1231
ChildrenArray arr = getArray(null);
1232
1233            return arr.nodesFor(this);
1234        }
1235
1236        public void useNodes(Collection JavaDoc<Node> nodes) {
1237            // forces creation of the array
1238
ChildrenArray arr = getArray(null);
1239
1240            arr.useNodes(this, nodes);
1241
1242            // assign all there nodes the new children
1243
for (Node n : nodes) {
1244                n.assignTo(Children.this, -1);
1245                n.fireParentNodeChange(null, parent);
1246            }
1247        }
1248
1249        public int length() {
1250            return length;
1251        }
1252
1253        @Override JavaDoc
1254        public String JavaDoc toString() {
1255            return "Children.Info[" + entry + ",length=" + length + "]"; // NOI18N
1256
}
1257    }
1258
1259    /** Empty list of children. Does not allow anybody to insert a node.
1260    * Treated especially in the attachTo method.
1261    */

1262    private static final class Empty extends Children {
1263        Empty() {
1264        }
1265
1266        /** @return false, does no action */
1267        public boolean add(Node[] nodes) {
1268            return false;
1269        }
1270
1271        /** @return false, does no action */
1272        public boolean remove(Node[] nodes) {
1273            return false;
1274        }
1275    }
1276
1277    /** Implements the storage of node children by an array.
1278    * Each new child is added at the end of the array. The nodes are
1279    * returned in the order they were inserted.
1280    *
1281    * <p><strong>
1282    * Directly subclassing this class is discouraged.
1283    * {@link Children.Keys} is preferable.
1284     * </strong>
1285    */

1286    public static class Array extends Children implements Cloneable JavaDoc {
1287        /** the entry used for all nodes in the following collection
1288        * this object is used for synchronization of operations that
1289        * need to be synchronized on this instance of Children, but
1290        * we cannot synchronize on this instance because it is public
1291        * and somebody else could synchronize too.
1292        */

1293        private Entry nodesEntry;
1294
1295        /** vector of added children */
1296        protected Collection JavaDoc<Node> nodes;
1297
1298        /** Constructs a new list and allows a subclass to
1299        * provide its own implementation of <code>Collection</code> to store
1300        * data in. The collection should be empty and should not
1301        * be directly accessed in any way after creation.
1302        *
1303        * @param c collection to store data in
1304        */

1305        protected Array(Collection JavaDoc<Node> c) {
1306            this();
1307            nodes = c;
1308        }
1309
1310        /** Constructs a new array children without any assigned collection.
1311        * The collection will be created by a call to method initCollection the
1312        * first time, children will be used.
1313        */

1314        public Array() {
1315            nodesEntry = createNodesEntry();
1316            this.setEntries(Collections.singleton(getNodesEntry()));
1317        }
1318
1319        /** Clones all nodes that are contained in the children list.
1320        *
1321        * @return the cloned array for this children
1322        */

1323        public Object JavaDoc clone() {
1324            try {
1325                final Children.Array ar = (Array) super.clone();
1326
1327                try {
1328                    PR.enterReadAccess();
1329
1330                    if (nodes != null) {
1331                        // nodes already initilized
1332
// used to create the right type of collection
1333
// clears the content of the collection
1334
// JST: hack, but I have no better idea how to write this
1335
// pls. notice that in initCollection you can test
1336
// whether nodes == null => real initialization
1337
// nodes != null => only create new empty collection
1338
ar.nodes = ar.initCollection();
1339                        ar.nodes.clear();
1340
1341                        // insert copies of the nodes
1342
for (Node n : nodes) {
1343                            ar.nodes.add(n.cloneNode());
1344                        }
1345                    }
1346                } finally {
1347                    PR.exitReadAccess();
1348                }
1349
1350                return ar;
1351            } catch (CloneNotSupportedException JavaDoc e) {
1352                // this cannot happen
1353
throw new InternalError JavaDoc();
1354            }
1355        }
1356
1357        /** Allow subclasses to create a collection, the first time the
1358        * children are used. It is called only if the collection has not
1359        * been passed in the constructor.
1360        * <P>
1361        * The current implementation returns ArrayList.
1362        *
1363        * @return empty or initialized collection to use
1364        */

1365        protected Collection JavaDoc<Node> initCollection() {
1366            return new ArrayList JavaDoc<Node>();
1367        }
1368
1369        /** This method can be called by subclasses that
1370        * directly modify the nodes collection to update the
1371        * state of the nodes appropriatelly.
1372        * This method should be called under
1373        * MUTEX.writeAccess.
1374        */

1375        final void refreshImpl() {
1376            if (isInitialized()) {
1377                Array.this.refreshEntry(getNodesEntry());
1378                super.getArray(null).nodes();
1379            } else if (nodes != null) {
1380                for (Node n : nodes) {
1381                    n.assignTo(this, -1);
1382                }
1383            }
1384        }
1385
1386        /** This method can be called by subclasses that
1387        * directly modify the nodes collection to update the
1388        * state of the nodes appropriatelly.
1389        */

1390        protected final void refresh() {
1391            MUTEX.postWriteRequest(new Runnable JavaDoc() {
1392                    public void run() {
1393                        refreshImpl();
1394                    }
1395                }
1396            );
1397        }
1398
1399        /** Getter for the entry.
1400        */

1401        final Entry getNodesEntry() {
1402            return nodesEntry;
1403        }
1404
1405        /** This method allows subclasses (only in this package) to
1406        * provide own version of entry. Usefull for SortedArray.
1407        */

1408        Entry createNodesEntry() {
1409            return new AE();
1410        }
1411
1412        /** Getter for nodes.
1413        */

1414        final Collection JavaDoc<Node> getCollection() {
1415            synchronized (getNodesEntry()) {
1416                if (nodes == null) {
1417                    nodes = initCollection();
1418                }
1419            }
1420
1421            return nodes;
1422        }
1423
1424        /*
1425        * @param arr nodes to add
1426        * @return true if changed false if not
1427        */

1428        public boolean add(final Node[] arr) {
1429            synchronized (getNodesEntry()) {
1430                if (!getCollection().addAll(Arrays.asList(arr))) {
1431                    // no change to the collection
1432
return false;
1433                }
1434
1435                ;
1436            }
1437
1438            refresh();
1439
1440            return true;
1441        }
1442
1443        /*
1444        * @param arr nodes to remove
1445        * @return true if changed false if not
1446        */

1447        public boolean remove(final Node[] arr) {
1448            synchronized (getNodesEntry()) {
1449                if (!getCollection().removeAll(Arrays.asList(arr))) {
1450                    // the collection was not changed
1451
return false;
1452                }
1453            }
1454
1455            refresh();
1456
1457            return true;
1458        }
1459
1460        /** One entry that holds all the nodes in the collection
1461        * member called nodes.
1462        */

1463        private final class AE extends Object JavaDoc implements Entry {
1464            AE() {
1465            }
1466
1467            /** List of elements.
1468            */

1469            public Collection JavaDoc<Node> nodes() {
1470                Collection JavaDoc<Node> c = getCollection();
1471
1472                if (c.isEmpty()) {
1473                    return Collections.emptyList();
1474                } else {
1475                    return new ArrayList JavaDoc<Node>(c);
1476                }
1477            }
1478
1479            @Override JavaDoc
1480            public String JavaDoc toString() {
1481                return "Children.Array.AE" + getCollection(); // NOI18N
1482
}
1483
1484        }
1485    }
1486
1487    /** Implements the storage of node children by a map.
1488    * This class also permits
1489    * association of a key with any node and to remove nodes by key.
1490    * Subclasses should reasonably
1491    * implement {@link #add} and {@link #remove}.
1492     * @param T the key type
1493    */

1494    public static class Map<T> extends Children {
1495        /** A map to use to store children in.
1496        * Keys are <code>Object</code>s, values are {@link Node}s.
1497        * Do <em>not</em> modify elements in the map! Use it only for read access.
1498        */

1499        protected java.util.Map JavaDoc<T,Node> nodes;
1500
1501        /** Constructs a new list with a supplied map object.
1502        * Should be used by subclasses desiring an alternate storage method.
1503        * The map must not be explicitly modified after creation.
1504        *
1505        * @param m the map to use for this list
1506        */

1507        protected Map(java.util.Map JavaDoc<T,Node> m) {
1508            nodes = m;
1509        }
1510
1511        /** Constructs a new list using {@link HashMap}.
1512        */

1513        public Map() {
1514        }
1515
1516        /** Getter for the map.
1517        * Ensures that the map has been initialized.
1518        */

1519        final java.util.Map JavaDoc<T,Node> getMap() {
1520            // package private only to simplify access from inner classes
1521
if (nodes == null) {
1522                nodes = initMap();
1523            }
1524
1525            return nodes;
1526        }
1527
1528        /** Called on first use.
1529        */

1530        final void callAddNotify() {
1531            this.setEntries(createEntries(getMap()));
1532
1533            super.callAddNotify();
1534        }
1535
1536        /** Method that allows subclasses (SortedMap) to redefine
1537        * order of entries.
1538        * @param map the map (Object, Node)
1539        * @return collection of (Entry)
1540        */

1541        Collection JavaDoc<? extends Entry> createEntries(java.util.Map JavaDoc<T,Node> map) {
1542            List JavaDoc<ME> l = new LinkedList JavaDoc<ME>();
1543            for (java.util.Map.Entry<T,Node> e : map.entrySet()) {
1544                l.add(new ME(e.getKey(), e.getValue()));
1545            }
1546            return l;
1547        }
1548
1549        /** Allows subclasses that directly modifies the
1550        * map with nodes to synchronize the state of the children.
1551        * This method should be called under
1552        * MUTEX.writeAccess.
1553        */

1554        final void refreshImpl() {
1555            this.setEntries(createEntries(getMap()));
1556        }
1557
1558        /** Allows subclasses that directly modifies the
1559        * map with nodes to synchronize the state of the children.
1560        */

1561        protected final void refresh() {
1562            try {
1563                PR.enterWriteAccess();
1564                refreshImpl();
1565            } finally {
1566                PR.exitWriteAccess();
1567            }
1568        }
1569
1570        /** Allows subclasses that directly modifies the
1571        * map with nodes to synchronize the state of the children.
1572        * This method should be called under
1573        * MUTEX.writeAccess.
1574        *
1575        * @param key the key that should be refreshed
1576        */

1577        final void refreshKeyImpl(T key) {
1578            this.refreshEntry(new ME(key, null));
1579        }
1580
1581        /** Allows subclasses that directly modifies the
1582        * map with nodes to synchronize the state of the children.
1583        *
1584        * @param key the key that should be refreshed
1585        */

1586        protected final void refreshKey(final T key) {
1587            try {
1588                PR.enterWriteAccess();
1589                refreshKeyImpl(key);
1590            } finally {
1591                PR.exitWriteAccess();
1592            }
1593        }
1594
1595        /** Add a collection of new key/value pairs into the map.
1596        * The supplied map may contain any keys, but the values must be {@link Node}s.
1597        *
1598        * @param map the map with pairs to add
1599        */

1600        protected final void putAll(final java.util.Map JavaDoc<? extends T,? extends Node> map) {
1601            try {
1602                PR.enterWriteAccess();
1603                nodes.putAll(map);
1604                refreshImpl();
1605
1606                // PENDING sometime we should also call refreshKey...
1607
} finally {
1608                PR.exitWriteAccess();
1609            }
1610        }
1611
1612        /** Add one key and one node to the list.
1613        * @param key the key
1614        * @param node the node
1615        */

1616        protected final void put(final T key, final Node node) {
1617            try {
1618                PR.enterWriteAccess();
1619
1620                if (nodes.put(key, node) != null) {
1621                    refreshKeyImpl(key);
1622                } else {
1623                    refreshImpl();
1624                }
1625            } finally {
1626                PR.exitWriteAccess();
1627            }
1628        }
1629
1630        /** Remove some children from the list by key.
1631        * @param keys collection of keys to remove
1632        */

1633        protected final void removeAll(final Collection JavaDoc<? extends T> keys) {
1634            try {
1635                PR.enterWriteAccess();
1636                nodes.keySet().removeAll(keys);
1637                refreshImpl();
1638            } finally {
1639                PR.exitWriteAccess();
1640            }
1641        }
1642
1643        /** Remove a given child node from the list by its key.
1644        * @param key key to remove
1645        */

1646        protected void remove(final T key) {
1647            try {
1648                PR.enterWriteAccess();
1649
1650                if (nodes.remove(key) != null) {
1651                    refreshImpl();
1652                }
1653            } finally {
1654                PR.exitWriteAccess();
1655            }
1656        }
1657
1658        /** Initialize some nodes. Allows a subclass to
1659        * provide a default map to initialize the map with.
1660        * Called only if the map has not been provided in the constructor.
1661        *
1662        * <P>
1663        * The default implementation returns <code>new HashMap (7)</code>.
1664        *
1665        * @return a map from keys to nodes
1666        */

1667        protected java.util.Map JavaDoc<T,Node> initMap() {
1668            return new HashMap JavaDoc<T,Node>(7);
1669        }
1670
1671        /** Does nothing. Should be reimplemented in a subclass wishing
1672        * to support external addition of nodes.
1673        *
1674        * @param arr nodes to add
1675        * @return <code>false</code> in the default implementation
1676        */

1677        public boolean add(Node[] arr) {
1678            return false;
1679        }
1680
1681        /** Does nothing. Should be reimplemented in a subclass wishing
1682        * to support external removal of nodes.
1683        * @param arr nodes to remove
1684        * @return <code>false</code> in the default implementation
1685        */

1686        public boolean remove(Node[] arr) {
1687            return false;
1688        }
1689
1690        /** Entry mapping one key to the node.
1691        */

1692        final static class ME extends Object JavaDoc implements Entry {
1693            /** key */
1694            public Object JavaDoc key;
1695
1696            /** node set */
1697            public Node node;
1698
1699            /** Constructor.
1700            */

1701            public ME(Object JavaDoc key, Node node) {
1702                this.key = key;
1703                this.node = node;
1704            }
1705
1706            /** Nodes */
1707            public Collection JavaDoc<Node> nodes() {
1708                return Collections.singleton(node);
1709            }
1710
1711            /** Hash code.
1712            */

1713            public int hashCode() {
1714                return key.hashCode();
1715            }
1716
1717            /** Equals.
1718            */

1719            public boolean equals(Object JavaDoc o) {
1720                if (o instanceof ME) {
1721                    ME me = (ME) o;
1722
1723                    return key.equals(me.key);
1724                }
1725
1726                return false;
1727            }
1728
1729            public String JavaDoc toString() {
1730                return "Key (" + key + ")"; // NOI18N
1731
}
1732        }
1733    }
1734
1735    /** Maintains a list of children sorted by the provided comparator in an array.
1736    * The comparator can change during the lifetime of the children, in which case
1737    * the children are resorted.
1738    */

1739    public static class SortedArray extends Children.Array {
1740        /** comparator to use */
1741        private Comparator JavaDoc<? super Node> comp;
1742
1743        /** Create an empty list of children. */
1744        public SortedArray() {
1745        }
1746
1747        /** Create an empty list with a specified storage method.
1748        *
1749        * @param c collection to store data in
1750        * @see Children.Array#Children.Array(Collection)
1751        */

1752        protected SortedArray(Collection JavaDoc<Node> c) {
1753            super(c);
1754        }
1755
1756        /** Set the comparator. The children will be resorted.
1757        * The comparator is used to compare Nodes, if no
1758        * comparator is used then nodes will be compared by
1759        * the use of natural ordering.
1760        *
1761        * @param c the new comparator
1762        */

1763        public void setComparator(final Comparator JavaDoc<? super Node> c) {
1764            try {
1765                PR.enterWriteAccess();
1766                comp = c;
1767                refresh();
1768            } finally {
1769                PR.exitWriteAccess();
1770            }
1771        }
1772
1773        /** Get the current comparator.
1774        * @return the comparator
1775        */

1776        public Comparator JavaDoc<? super Node> getComparator() {
1777            return comp;
1778        }
1779
1780        /** This method allows subclasses (only in this package) to
1781        * provide own version of entry. Useful for SortedArray.
1782        */

1783        Entry createNodesEntry() {
1784            return new SAE();
1785        }
1786
1787        /** One entry that holds all the nodes in the collection
1788        * member called nodes.
1789        */

1790        private final class SAE extends Object JavaDoc implements Entry {
1791            /** Constructor that provides the original comparator.
1792            */

1793            public SAE() {
1794            }
1795
1796            /** List of elements.
1797            */

1798            public Collection JavaDoc<Node> nodes() {
1799                List JavaDoc<Node> al = new ArrayList JavaDoc<Node>(getCollection());
1800                Collections.sort(al, comp);
1801
1802                return al;
1803            }
1804        }
1805    }
1806     // end of SortedArray
1807

1808    /** Maintains a list of children sorted by the provided comparator in a map.
1809    * Similar to {@link Children.SortedArray}.
1810    */

1811    public static class SortedMap<T> extends Children.Map<T> {
1812        /** comparator to use */
1813        private Comparator JavaDoc<? super Node> comp;
1814
1815        /** Create an empty list. */
1816        public SortedMap() {
1817        }
1818
1819        /** Create an empty list with a specific storage method.
1820        *
1821        * @param map the map to use with this object
1822        * @see Children.Map#Children.Map(java.util.Map)
1823        */

1824        protected SortedMap(java.util.Map JavaDoc<T,Node> map) {
1825            super(map);
1826        }
1827
1828        /** Set the comparator. The children will be resorted.
1829        * The comparator is used to compare Nodes, if no
1830        * comparator is used then values will be compared by
1831        * the use of natural ordering.
1832        *
1833        * @param c the new comparator that should compare nodes
1834        */

1835        public void setComparator(final Comparator JavaDoc<? super Node> c) {
1836            try {
1837                PR.enterWriteAccess();
1838                comp = c;
1839                refresh();
1840            } finally {
1841                PR.exitWriteAccess();
1842            }
1843        }
1844
1845        /** Get the current comparator.
1846        * @return the comparator
1847        */

1848        public Comparator JavaDoc<? super Node> getComparator() {
1849            return comp;
1850        }
1851
1852        /** Method that allows subclasses (SortedMap) to redefine
1853        * order of entries.
1854        * @param map the map (Object, Node)
1855        * @return collection of (Entry)
1856        */

1857        Collection JavaDoc<? extends Entry> createEntries(java.util.Map JavaDoc<T,Node> map) {
1858            // SME objects use natural ordering
1859
Set JavaDoc<ME> l = new TreeSet JavaDoc<ME>(new SMComparator());
1860
1861            for (java.util.Map.Entry<T,Node> e : map.entrySet()) {
1862                l.add(new ME(e.getKey(), e.getValue()));
1863            }
1864
1865            return l;
1866        }
1867
1868        /** Sorted map entry can be used for comparing.
1869        */

1870        final class SMComparator implements Comparator JavaDoc<ME> {
1871            public int compare(ME me1, ME me2) {
1872                Comparator JavaDoc<? super Node> c = comp;
1873
1874                if (c == null) {
1875                    // compare keys
1876
@SuppressWarnings JavaDoc("unchecked") // we just assume that it is comparable, not statically checked
1877
int r = ((Comparable JavaDoc) me1.key).compareTo(me2.key);
1878                    return r;
1879                } else {
1880                    return c.compare(me1.node, me2.node);
1881                }
1882            }
1883        }
1884    }
1885     // end of SortedMap
1886

1887    /** Implements an array of child nodes associated nonuniquely with keys and sorted by these keys.
1888    * There is a {@link #createNodes(Object) method} that should for each
1889    * key create an array of nodes that represents the key.
1890    *
1891     * <p>This class is preferable to {@link Children.Array} because
1892     * <ol>
1893     * <li>It more clearly separates model from view and encourages use of a discrete model.
1894     * <li>It correctly handles adding, removing, and reordering children while preserving
1895     * existing node selections in a tree (or other) view where possible.
1896     * </ol>
1897     *
1898    * <p>Typical usage:
1899    * <ol>
1900    * <li>Subclass.
1901    * <li>Decide what type your key should be.
1902    * <li>Implement {@link #createNodes} to create some nodes
1903    * (usually exactly one) per key.
1904    * <li>Override {@link Children#addNotify} to compute a set of keys
1905    * and set it using {@link #setKeys(Collection)}.
1906    * The collection may be ordered.
1907    * <li>Override {@link Children#removeNotify} to just call
1908    * <code>setKeys</code> on {@link Collections#EMPTY_SET}.
1909    * <li>When your model changes, call <code>setKeys</code>
1910    * with the new set of keys. <code>Children.Keys</code> will
1911    * be smart and calculate exactly what it needs to do effficiently.
1912    * <li><i>(Optional)</i> if your notion of what the node for a
1913    * given key changes (but the key stays the same), you can
1914    * call {@link #refreshKey}. Usually this is not necessary.
1915    * </ol>
1916     * @param T the type of the key
1917    */

1918    public static abstract class Keys<T> extends Children.Array {
1919        /** the last runnable (created in method setKeys) for each children object.
1920         */

1921        private static java.util.Map JavaDoc<Keys<?>,Runnable JavaDoc> lastRuns = new HashMap JavaDoc<Keys<?>,Runnable JavaDoc>(11);
1922
1923        /** add array children before or after keys ones */
1924        private boolean before;
1925
1926        public Keys() {
1927            super();
1928        }
1929
1930        /** Special handling for clonning.
1931        */

1932        public Object JavaDoc clone() {
1933            Keys<?> k = (Keys<?>) super.clone();
1934
1935            return k;
1936        }
1937
1938        /**
1939         * @deprecated Do not use! Just call {@link #setKeys(Collection)} with a larger set.
1940         */

1941        @Deprecated JavaDoc
1942        public boolean add(Node[] arr) {
1943            return super.add(arr);
1944        }
1945
1946        /**
1947         * @deprecated Do not use! Just call {@link #setKeys(Collection)} with a smaller set.
1948         */

1949        @Deprecated JavaDoc
1950        public boolean remove(final Node[] arr) {
1951            try {
1952                PR.enterWriteAccess();
1953
1954                if (nodes != null) {
1955                    // removing from array, just if the array nodes are
1956
// really created
1957
// expecting arr.length == 1, which is the usual case
1958
for (int i = 0; i < arr.length; i++) {
1959                        if (!nodes.contains(arr[i])) {
1960                            arr[i] = null;
1961                        }
1962                    }
1963
1964                    super.remove(arr);
1965                }
1966            } finally {
1967                PR.exitWriteAccess();
1968            }
1969
1970            return true;
1971        }
1972
1973        /** Refresh the child nodes for a given key.
1974        *
1975        * @param key the key to refresh
1976        */

1977        protected final void refreshKey(final T key) {
1978            MUTEX.postWriteRequest(
1979                new Runnable JavaDoc() {
1980                    public void run() {
1981                        Keys.this.refreshEntry(new KE(key));
1982                    }
1983                }
1984            );
1985        }
1986
1987        /** Set new keys for this children object. Setting of keys
1988        * does not necessarily lead to the creation of nodes. It happens only
1989        * when the list has already been initialized.
1990        *
1991        * @param keysSet the keys for the nodes (collection of any objects)
1992        */

1993        protected final void setKeys(Collection JavaDoc<? extends T> keysSet) {
1994            boolean asserts = false;
1995            assert asserts = true;
1996            int sz = keysSet.size();
1997            if (asserts && sz < 10) {
1998                List JavaDoc<? extends T> keys = new ArrayList JavaDoc<T>(keysSet);
1999                for (int i = 0; i < sz - 1; i++) {
2000                    T a = keys.get(i);
2001                    for (int j = i + 1; j < sz; j++) {
2002                        T b = keys.get(j);
2003                        assert !(a.equals(b) && a.hashCode() != b.hashCode()) : "bad equals/hashCode in " + a + " vs. " + b;
2004                    }
2005                }
2006            }
2007
2008            final List JavaDoc<Entry> l = new ArrayList JavaDoc<Entry>(keysSet.size() + 1);
2009
2010            if (before) {
2011                l.add(getNodesEntry());
2012            }
2013
2014            KE updator = new KE();
2015            updator.updateList(keysSet, l);
2016
2017            if (!before) {
2018                l.add(getNodesEntry());
2019            }
2020
2021            applyKeys(l);
2022        }
2023
2024        /** Set keys for this list.
2025        *
2026        * @param keys the keys for the nodes
2027        * @see #setKeys(Collection)
2028        */

2029        protected final void setKeys(final T[] keys) {
2030            boolean asserts = false;
2031            assert asserts = true;
2032            int sz = keys.length;
2033            if (asserts && sz < 10) {
2034                for (int i = 0; i < sz - 1; i++) {
2035                    T a = keys[i];
2036                    for (int j = i + 1; j < sz; j++) {
2037                        T b = keys[j];
2038                        assert !(a.equals(b) && a.hashCode() != b.hashCode()) : "bad equals/hashCode in " + a + " vs. " + b;
2039                    }
2040                }
2041            }
2042
2043            final List JavaDoc<Entry> l = new ArrayList JavaDoc<Entry>(keys.length + 1);
2044
2045            KE updator = new KE();
2046
2047            if (before) {
2048                l.add(getNodesEntry());
2049            }
2050
2051            updator.updateList(keys, l);
2052
2053            if (!before) {
2054                l.add(getNodesEntry());
2055            }
2056
2057            applyKeys(l);
2058        }
2059
2060        /** Applies the keys.
2061         */

2062        private void applyKeys(final List JavaDoc<? extends Entry> l) {
2063            Runnable JavaDoc invoke = new Runnable JavaDoc() {
2064                    public void run() {
2065                        if (keysCheck(Keys.this, this)) {
2066                            // no next request after me
2067
Keys.this.setEntries(l);
2068
2069                            // clear this runnable
2070
keysExit(Keys.this, this);
2071                        }
2072                    }
2073                };
2074
2075            keysEnter(this, invoke);
2076            MUTEX.postWriteRequest(invoke);
2077        }
2078
2079        /** Set whether new nodes should be added to the beginning or end of sublists for a given key.
2080        * Generally should not be used.
2081        * @param b <code>true</code> if the children should be added before
2082        */

2083        protected final void setBefore(final boolean b) {
2084            try {
2085                PR.enterWriteAccess();
2086
2087                if (before != b) {
2088                    List JavaDoc<Entry> l = Keys.this.getEntries();
2089                    l.remove(getNodesEntry());
2090                    before = b;
2091
2092                    if (b) {
2093                        l.add(0, getNodesEntry());
2094                    } else {
2095                        l.add(getNodesEntry());
2096                    }
2097
2098                    Keys.this.setEntries(l);
2099                }
2100            } finally {
2101                PR.exitWriteAccess();
2102            }
2103        }
2104
2105        /** Create nodes for a given key.
2106        * @param key the key
2107        * @return child nodes for this key or null if there should be no
2108        * nodes for this key
2109        */

2110        protected abstract Node[] createNodes(T key);
2111
2112        /** Called when the nodes have been removed from the children.
2113        * This method should allow subclasses to clean the nodes somehow.
2114        * <p>
2115        * Current implementation notifies all listeners on the nodes
2116        * that nodes have been deleted.
2117        *
2118        * @param arr array of deleted nodes
2119        */

2120        protected void destroyNodes(Node[] arr) {
2121            for (int i = 0; i < arr.length; i++) {
2122                arr[i].fireNodeDestroyed();
2123            }
2124        }
2125
2126        /** Notifies the children class that nodes has been released.
2127        */

2128        Node[] notifyRemove(Collection JavaDoc<Node> nodes, Node[] current) {
2129            Node[] arr = super.notifyRemove(nodes, current);
2130            destroyNodes(arr);
2131
2132            return arr;
2133        }
2134
2135        /** Enter of setKeys.
2136         * @param ch the children
2137         * @param run runnable
2138         */

2139        private static synchronized void keysEnter(Keys<?> ch, Runnable JavaDoc run) {
2140            lastRuns.put(ch, run);
2141        }
2142
2143        /** Clears the entry for the children
2144         * @param ch children
2145         */

2146        private static synchronized void keysExit(Keys ch, Runnable JavaDoc r) {
2147            Runnable JavaDoc reg = lastRuns.remove(ch);
2148
2149            if ((reg != null) && !reg.equals(r)) {
2150                lastRuns.put(ch, reg);
2151            }
2152        }
2153
2154        /** Check whether the runnable is "the current" for given children.
2155         * @param ch children
2156         * @param run runnable
2157         * @return true if the runnable shoul run
2158         */

2159        private static synchronized boolean keysCheck(Keys ch, Runnable JavaDoc run) {
2160            return run == lastRuns.get(ch);
2161        }
2162
2163        /** Entry for a key
2164        */

2165        private final class KE extends Dupl<T> {
2166            /** Has default constructor that allows to create a factory
2167            * of KE objects for use with updateList
2168            */

2169            public KE() {
2170            }
2171
2172            /** Creates directly an instance of the KE object.
2173            */

2174            public KE(T key) {
2175                this.key = key;
2176            }
2177
2178            /** Nodes are taken from the create nodes.
2179            */

2180            public Collection JavaDoc<Node> nodes() {
2181                Node[] arr = createNodes(getKey());
2182
2183                if (arr == null) {
2184                    return Collections.emptyList();
2185                } else {
2186                    return new LinkedList JavaDoc<Node>(Arrays.asList(arr));
2187                }
2188            }
2189
2190            @Override JavaDoc
2191            public String JavaDoc toString() {
2192                String JavaDoc s = getKey().toString();
2193                if (s.length() > 80) {
2194                    s = s.substring(0, 80);
2195                }
2196                return "Children.Keys.KE[" + s + "," + getCnt() + "]"; // NOI18N
2197
}
2198        }
2199    }
2200     // end of Keys
2201

2202    /** Supporting class that provides support for duplicated
2203    * objects that still should not be equal.
2204    * <P>
2205    * It counts the number of times an object has been
2206    * added to the collection and if the same object is added
2207    * more than once it is indexed by a number.
2208    */

2209    private static abstract class Dupl<T> implements Cloneable JavaDoc, Entry {
2210        /** the key either real value or Dupl (Dupl (Dupl (... value ...)))*/
2211        protected Object JavaDoc key;
2212
2213        Dupl() {
2214        }
2215
2216        /** Updates the second collection with values from the first one.
2217        * If there is a multiple occurrence of an object in the first collection
2218        * a Dupl for the object is created to encapsulate it.
2219        */

2220        public final void updateList(Collection JavaDoc<? extends T> src, Collection JavaDoc<? super Dupl<T>> target) {
2221            java.util.Map JavaDoc<T,Object JavaDoc> map = new HashMap JavaDoc<T,Object JavaDoc>(src.size() * 2);
2222            for (T o : src) {
2223                updateListAndMap(o, target, map);
2224            }
2225        }
2226
2227        /** Updates the second collection with values from the first array.
2228        * If there is a multiple occurrence of an object in the first array
2229        * a Dupl for the object is created to encapsulate it.
2230        */

2231        public final void updateList(T[] arr, Collection JavaDoc<? super Dupl<T>> target) {
2232            java.util.Map JavaDoc<T,Object JavaDoc> map = new HashMap JavaDoc<T,Object JavaDoc>(arr.length * 2);
2233            for (T o : arr) {
2234                updateListAndMap(o, target, map);
2235            }
2236        }
2237
2238        /** Updates the linked list and the map with right
2239        * values. The map is used to count the number of times
2240        * a element occurs in the list.
2241        *
2242        * @param obj object to add
2243        * @param list the list to add obj to
2244        * @param map to track number of occurrences in the array
2245        */

2246        public final void updateListAndMap(T obj, Collection JavaDoc<? super Dupl<T>> list, java.util.Map JavaDoc<T,Object JavaDoc> map) {
2247            // optimized for first occurrence
2248
// of each object because often occurrences should be rare
2249
Object JavaDoc prev = map.put(obj, this);
2250
2251            if (prev == null) {
2252                // first occurrence of object obj
2253
list.add(createInstance(obj, 0));
2254
2255                return;
2256            } else {
2257                if (prev == this) {
2258                    // second occurrence of the object
2259
map.put(obj, 1);
2260                    list.add(createInstance(obj, 1));
2261
2262                    return;
2263                } else {
2264                    int cnt = ((Integer JavaDoc) prev) + 1;
2265                    map.put(obj, cnt);
2266                    list.add(createInstance(obj, cnt));
2267
2268                    return;
2269                }
2270            }
2271        }
2272
2273        /** Gets the key represented by this object.
2274        * @return the key
2275        */

2276        @SuppressWarnings JavaDoc("unchecked") // data structure really weird
2277
public final T getKey() {
2278            if (key instanceof Dupl) {
2279                return (T) ((Dupl) key).getKey();
2280            } else {
2281                return (T) key;
2282            }
2283        }
2284
2285        /** Counts the index of this key.
2286        * @return integer
2287        */

2288        public final int getCnt() {
2289            int cnt = 0;
2290            Dupl d = this;
2291
2292            while (d.key instanceof Dupl) {
2293                d = (Dupl) d.key;
2294                cnt++;
2295            }
2296
2297            return cnt;
2298        }
2299
2300        /** Create instance of Dupl (uses cloning of the class)
2301        */

2302        @SuppressWarnings JavaDoc("unchecked") // data structure really weird
2303
private final Dupl<T> createInstance(Object JavaDoc obj, int cnt) {
2304            try {
2305                // creates the chain of Dupl (Dupl (Dupl (obj))) where
2306
// for cnt = 0 the it would look like Dupl (obj)
2307
// for cnt = 1 like Dupl (Dupl (obj))
2308
Dupl d = (Dupl) this.clone();
2309                Dupl first = d;
2310
2311                while (cnt-- > 0) {
2312                    Dupl n = (Dupl) this.clone();
2313                    d.key = n;
2314                    d = n;
2315                }
2316
2317                d.key = obj;
2318
2319                return first;
2320            } catch (CloneNotSupportedException JavaDoc ex) {
2321                throw new InternalError JavaDoc();
2322            }
2323        }
2324
2325        @Override JavaDoc
2326        public int hashCode() {
2327            return getKey().hashCode();
2328        }
2329
2330        @Override JavaDoc
2331        public boolean equals(Object JavaDoc o) {
2332            if (o instanceof Dupl) {
2333                Dupl d = (Dupl) o;
2334
2335                return getKey().equals(d.getKey()) && (getCnt() == d.getCnt());
2336            }
2337
2338            return false;
2339        }
2340    }
2341
2342    /*
2343      static void printNodes (Node[] n) {
2344        for (int i = 0; i < n.length; i++) {
2345          System.out.println (" " + i + ". " + n[i].getName () + " number: " + System.identityHashCode (n[i]));
2346        }
2347        }
2348        */

2349    /* JST: Useful test routine ;-) *
2350    static {
2351      final TopComponent.Registry r = TopComponent.getRegistry ();
2352      r.addPropertyChangeListener (new PropertyChangeListener () {
2353        Node last = new AbstractNode (LEAF);
2354
2355        public void propertyChange (PropertyChangeEvent ev) {
2356          Node[] arr = r.getCurrentNodes ();
2357          if (arr != null && arr.length == 1) {
2358            last = arr[0];
2359          }
2360          System.out.println (
2361            "Activated node: " + last + " \nparent: " + last.getParentNode ()
2362          );
2363        }
2364      });
2365    }
2366    */

2367}
2368
Popular Tags