2 * This program is free software: you can redistribute it and/or modify
3 * it under the terms of the GNU Lesser General Public License as published
4 * by the Free Software Foundation, either version 3 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU Lesser General Public License for more details.
12 * You should have received a copy of the GNU Lesser General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 // Can be found at: https://code.google.com/archive/p/aephyr/source/default/source
16 // package aephyr.swing;
17 package be
.nikiroo
.fanfix_swing
.gui
.utils
;
19 import java
.text
.Collator
;
20 import java
.util
.ArrayList
;
21 import java
.util
.Arrays
;
22 import java
.util
.Collection
;
23 import java
.util
.Collections
;
24 import java
.util
.Comparator
;
25 import java
.util
.Enumeration
;
26 import java
.util
.HashSet
;
27 import java
.util
.List
;
29 import java
.util
.HashMap
;
30 import java
.util
.Iterator
;
31 import java
.util
.regex
.Matcher
;
32 import java
.util
.regex
.Pattern
;
34 import javax
.swing
.JTree
;
35 import javax
.swing
.SortOrder
;
36 import javax
.swing
.event
.EventListenerList
;
37 import javax
.swing
.event
.TreeExpansionEvent
;
38 import javax
.swing
.event
.TreeExpansionListener
;
39 import javax
.swing
.event
.TreeModelEvent
;
40 import javax
.swing
.event
.TreeModelListener
;
41 import javax
.swing
.event
.TreeWillExpandListener
;
42 import javax
.swing
.tree
.ExpandVetoException
;
43 import javax
.swing
.tree
.TreeModel
;
44 import javax
.swing
.tree
.TreePath
;
47 public class TreeModelTransformer
<N
> implements TreeModel
{
49 public TreeModelTransformer(JTree tree
, TreeModel model
) {
51 throw new IllegalArgumentException();
53 throw new IllegalArgumentException();
56 handler
= createHandler();
62 private TreeModel model
;
64 private Handler handler
;
66 private Filter
<N
> filter
;
68 private TreePath filterStartPath
;
70 private int filterDepthLimit
;
72 private SortOrder sortOrder
= SortOrder
.UNSORTED
;
74 private Map
<Object
,Converter
> converters
;
76 protected EventListenerList listenerList
= new EventListenerList();
78 protected Handler
createHandler() {
82 protected void addListeners() {
83 tree
.addTreeExpansionListener(handler
);
84 model
.addTreeModelListener(handler
);
87 protected void removeListeners() {
88 tree
.removeTreeExpansionListener(handler
);
89 model
.removeTreeModelListener(handler
);
92 public void dispose() {
96 public TreeModel
getModel() {
100 private Converter
getConverter(Object node
) {
101 return converters
== null ?
null : converters
.get(node
);
104 int convertRowIndexToView(Object parent
, int index
) {
105 Converter converter
= getConverter(parent
);
106 if (converter
!= null)
107 return converter
.convertRowIndexToView(index
);
111 int convertRowIndexToModel(Object parent
, int index
) {
112 Converter converter
= getConverter(parent
);
113 if (converter
!= null)
114 return converter
.convertRowIndexToModel(index
);
119 public Object
getChild(Object parent
, int index
) {
120 return model
.getChild(parent
, convertRowIndexToModel(parent
, index
));
124 public int getChildCount(Object parent
) {
125 Converter converter
= getConverter(parent
);
126 if (converter
!= null)
127 return converter
.getChildCount();
128 return model
.getChildCount(parent
);
132 public int getIndexOfChild(Object parent
, Object child
) {
133 int index
= model
.getIndexOfChild(parent
, child
);
136 return convertRowIndexToView(parent
, index
);
140 public Object
getRoot() {
141 return model
.getRoot();
145 public boolean isLeaf(Object node
) {
146 return model
.isLeaf(node
);
150 public void valueForPathChanged(TreePath path
, Object newValue
) {
151 model
.valueForPathChanged(path
, newValue
);
155 public void addTreeModelListener(TreeModelListener l
) {
156 listenerList
.add(TreeModelListener
.class, l
);
160 public void removeTreeModelListener(TreeModelListener l
) {
161 listenerList
.remove(TreeModelListener
.class, l
);
165 * Set the comparator that compares nodes in sorting.
167 * @see #getComparator()
169 public void setComparator(Comparator
<N
> comparator
) {
170 handler
.setComparator(comparator
);
174 * @return comparator that compares nodes
175 * @see #setComparator(Comparator)
177 public Comparator
<N
> getComparator() {
178 return handler
.getComparator();
181 public void setSortOrder(SortOrder newOrder
) {
182 SortOrder oldOrder
= sortOrder
;
183 if (oldOrder
== newOrder
)
185 sortOrder
= newOrder
;
186 ArrayList
<TreePath
> paths
= null;
189 if (oldOrder
== SortOrder
.DESCENDING
) {
196 if (oldOrder
== SortOrder
.ASCENDING
) {
206 fireTreeStructureChangedAndExpand(new TreePath(getRoot()), paths
, true);
209 public SortOrder
getSortOrder() {
213 public void toggleSortOrder() {
214 setSortOrder(sortOrder
== SortOrder
.ASCENDING ?
215 SortOrder
.DESCENDING
: SortOrder
.ASCENDING
);
220 * Flip all sorted paths.
222 private void flip() {
223 for (Converter c
: converters
.values()) {
232 private static void flip(int[] array
) {
233 for (int left
=0, right
=array
.length
-1;
234 left
<right
; left
++, right
--) {
235 int tmp
= array
[left
];
236 array
[left
] = array
[right
];
241 private void unsort() {
242 if (filter
== null) {
245 Iterator
<Converter
> cons
= converters
.values().iterator();
246 while (cons
.hasNext()) {
247 Converter converter
= cons
.next();
248 if (!converter
.isFiltered()) {
251 Arrays
.sort(converter
.viewToModel
);
258 * Sort root and expanded descendants.
259 * @return list of paths that were sorted
261 private ArrayList
<TreePath
> sort() {
262 if (converters
== null)
263 converters
= createConvertersMap(); //new IdentityHashMap<Object,Converter>();
264 return sortHierarchy(new TreePath(model
.getRoot()));
268 * Sort path and expanded descendants.
270 * @return list of paths that were sorted
272 private ArrayList
<TreePath
> sortHierarchy(TreePath path
) {
273 ValueIndexPair
<N
>[] pairs
= createValueIndexPairArray(20);
274 ArrayList
<TreePath
> list
= new ArrayList
<TreePath
>();
275 pairs
= sort(path
.getLastPathComponent(), pairs
);
277 Enumeration
<TreePath
> paths
= tree
.getExpandedDescendants(path
);
279 while (paths
.hasMoreElements()) {
280 path
= paths
.nextElement();
281 pairs
= sort(path
.getLastPathComponent(), pairs
);
287 private ValueIndexPair
<N
>[] sort(Object node
, ValueIndexPair
<N
>[] pairs
) {
288 Converter converter
= getConverter(node
);
289 TreeModel mdl
= model
;
291 if (converter
!= null) {
292 vtm
= converter
.viewToModel
;
293 if (pairs
.length
< vtm
.length
)
294 pairs
= createValueIndexPairArray(vtm
.length
);
295 for (int i
=vtm
.length
; --i
>=0;) {
297 pairs
[i
].index
= idx
;
298 pairs
[i
].value
= (N
)mdl
.getChild(node
, idx
);
301 int count
= mdl
.getChildCount(node
);
304 if (pairs
.length
< count
)
305 pairs
= createValueIndexPairArray(count
);
306 for (int i
=count
; --i
>=0;) {
308 pairs
[i
].value
= (N
)mdl
.getChild(node
, i
);
310 vtm
= new int[count
];
312 Arrays
.sort(pairs
, 0, vtm
.length
, handler
);
313 for (int i
=vtm
.length
; --i
>=0;)
314 vtm
[i
] = pairs
[i
].index
;
315 if (converter
== null) {
316 converters
.put(node
, new Converter(vtm
, false));
318 if (sortOrder
== SortOrder
.DESCENDING
)
323 private ValueIndexPair
<N
>[] createValueIndexPairArray(int len
) {
324 ValueIndexPair
<N
>[] pairs
= new ValueIndexPair
[len
];
325 for (int i
=len
; --i
>=0;)
326 pairs
[i
] = new ValueIndexPair
<N
>();
330 public void setFilter(Filter
<N
> filter
) {
331 setFilter(filter
, null);
334 public void setFilter(Filter
<N
> filter
, TreePath startingPath
) {
335 setFilter(filter
, null, -1);
338 public void setFilter(Filter
<N
> filter
, TreePath startingPath
, int depthLimit
) {
339 if (filter
== null && startingPath
!= null)
340 throw new IllegalArgumentException();
341 if (startingPath
!= null && startingPath
.getPathCount() == 1)
343 Filter
<N
> oldFilter
= this.filter
;
344 TreePath oldStartPath
= filterStartPath
;
345 this.filter
= filter
;
346 filterStartPath
= startingPath
;
347 filterDepthLimit
= depthLimit
;
348 applyFilter(oldFilter
, oldStartPath
, null, true);
351 public Filter
<N
> getFilter() {
355 public TreePath
getFilterStartPath() {
356 return filterStartPath
;
359 private void applyFilter(Filter
<N
> oldFilter
, TreePath oldStartPath
, Collection
<TreePath
> expanded
, boolean sort
) {
360 TreePath startingPath
= filterStartPath
;
361 ArrayList
<TreePath
> expand
= null;
362 if (filter
== null) {
365 if (converters
== null || startingPath
== null) {
366 converters
= createConvertersMap();
367 } else if (oldFilter
!= null) {
368 // unfilter the oldStartPath if oldStartPath isn't descendant of startingPath
369 if (oldStartPath
== null) {
370 converters
= createConvertersMap();
371 fireTreeStructureChangedAndExpand(new TreePath(getRoot()), null, true);
372 } else if (!startingPath
.isDescendant(oldStartPath
)) {
373 Object node
= oldStartPath
.getLastPathComponent();
374 handler
.removeConverter(getConverter(node
), node
);
375 fireTreeStructureChangedAndExpand(oldStartPath
, null, true);
378 expand
= new ArrayList
<TreePath
>();
379 TreePath path
= startingPath
!= null ? startingPath
: new TreePath(getRoot());
380 if (!applyFilter(filter
, path
, expand
, filterDepthLimit
)) {
381 converters
.put(path
.getLastPathComponent(), new Converter(Converter
.EMPTY
, true));
384 if (startingPath
== null)
385 startingPath
= new TreePath(getRoot());
386 fireTreeStructureChanged(startingPath
);
387 if (expanded
!= null)
388 expand
.retainAll(expanded
);
390 if (sort
&& sortOrder
!= SortOrder
.UNSORTED
) {
392 converters
= createConvertersMap();
393 if (startingPath
.getPathCount() > 1 && oldFilter
!= null) {
394 // upgrade startingPath or sort oldStartPath
395 if (oldStartPath
== null) {
396 startingPath
= new TreePath(getRoot());
397 } else if (oldStartPath
.isDescendant(startingPath
)) {
398 startingPath
= oldStartPath
;
399 } else if (!startingPath
.isDescendant(oldStartPath
)) {
400 expand
= sortHierarchy(oldStartPath
);
401 fireTreeStructureChanged(oldStartPath
);
405 expand
= sortHierarchy(startingPath
);
406 fireTreeStructureChanged(startingPath
);
412 private boolean applyFilter(Filter
<N
> filter
, TreePath path
, ArrayList
<TreePath
> expand
) {
413 int depthLimit
= filterDepthLimit
;
414 if (depthLimit
>= 0) {
415 depthLimit
-= filterStartPath
.getPathCount() - path
.getPathCount();
419 return applyFilter(filter
, path
, expand
, depthLimit
);
422 private boolean applyFilter(Filter
<N
> filter
, TreePath path
, ArrayList
<TreePath
> expand
, int depthLimit
) {
423 Object node
= path
.getLastPathComponent();
424 int count
= model
.getChildCount(node
);
425 int[] viewToModel
= null;
427 boolean needsExpand
= false;
428 boolean isExpanded
= false;
431 for (int i
=0; i
<count
; i
++) {
432 Object child
= model
.getChild(node
, i
);
433 boolean leaf
= model
.isLeaf(child
);
434 if (filter
.acceptNode(path
, (N
)child
, leaf
)) {
435 if (viewToModel
== null)
436 viewToModel
= new int[count
-i
];
437 viewToModel
[viewIndex
++] = i
;
439 } else if (depthLimit
!= 0 && !leaf
) {
440 if (applyFilter(filter
, path
.pathByAddingChild(child
), expand
, depthLimit
)) {
441 if (viewToModel
== null)
442 viewToModel
= new int[count
-i
];
443 viewToModel
[viewIndex
++] = i
;
448 if (needsExpand
&& expand
!= null && !isExpanded
&& path
.getPathCount() > 1) {
451 if (viewToModel
!= null) {
452 if (viewIndex
< viewToModel
.length
)
453 viewToModel
= Arrays
.copyOf(viewToModel
, viewIndex
);
454 // a node must have a converter to signify that tree modifications
455 // need to query the filter, so have to put in converter
456 // even if viewIndex == viewToModel.length
457 converters
.put(node
, new Converter(viewToModel
, true));
464 private void expandPaths(ArrayList
<TreePath
> paths
) {
465 if (paths
== null || paths
.isEmpty())
468 for (TreePath path
: paths
)
469 tre
.expandPath(path
);
473 private void fireTreeStructureChangedAndExpand(TreePath path
, ArrayList
<TreePath
> list
, boolean retainSelection
) {
474 Enumeration
<TreePath
> paths
= list
!= null ?
475 Collections
.enumeration(list
) : tree
.getExpandedDescendants(path
);
476 TreePath
[] sel
= retainSelection ? tree
.getSelectionPaths() : null;
477 fireTreeStructureChanged(path
);
479 while (paths
.hasMoreElements())
480 tree
.expandPath(paths
.nextElement());
482 tree
.setSelectionPaths(sel
);
487 protected void fireTreeStructureChanged(TreePath path
) {
488 Object
[] listeners
= listenerList
.getListenerList();
489 TreeModelEvent e
= null;
490 for (int i
= listeners
.length
-2; i
>=0; i
-=2) {
491 if (listeners
[i
]==TreeModelListener
.class) {
493 e
= new TreeModelEvent(this, path
, null, null);
494 ((TreeModelListener
)listeners
[i
+1]).treeStructureChanged(e
);
499 protected void fireTreeNodesChanged(TreePath path
, int[] childIndices
, Object
[] childNodes
) {
500 Object
[] listeners
= listenerList
.getListenerList();
501 TreeModelEvent e
= null;
502 for (int i
= listeners
.length
-2; i
>=0; i
-=2) {
503 if (listeners
[i
]==TreeModelListener
.class) {
505 e
= new TreeModelEvent(this, path
, childIndices
, childNodes
);
506 ((TreeModelListener
)listeners
[i
+1]).treeNodesChanged(e
);
511 protected void fireTreeNodesInserted(TreePath path
, int[] childIndices
, Object
[] childNodes
) {
512 Object
[] listeners
= listenerList
.getListenerList();
513 TreeModelEvent e
= null;
514 for (int i
= listeners
.length
-2; i
>=0; i
-=2) {
515 if (listeners
[i
]==TreeModelListener
.class) {
517 e
= new TreeModelEvent(this, path
, childIndices
, childNodes
);
518 ((TreeModelListener
)listeners
[i
+1]).treeNodesInserted(e
);
523 protected void fireTreeNodesRemoved(TreePath path
, int[] childIndices
, Object
[] childNodes
) {
524 Object
[] listeners
= listenerList
.getListenerList();
525 TreeModelEvent e
= null;
526 for (int i
= listeners
.length
-2; i
>=0; i
-=2) {
527 if (listeners
[i
]==TreeModelListener
.class) {
529 e
= new TreeModelEvent(this, path
, childIndices
, childNodes
);
530 ((TreeModelListener
)listeners
[i
+1]).treeNodesRemoved(e
);
536 protected class Handler
implements Comparator
<ValueIndexPair
<N
>>,
537 TreeModelListener
, TreeExpansionListener
{
539 private Comparator
<N
> comparator
;
541 private Collator collator
= Collator
.getInstance();
543 void setComparator(Comparator
<N
> cmp
) {
545 collator
= cmp
== null ? Collator
.getInstance() : null;
548 Comparator
<N
> getComparator() {
552 // TODO, maybe switch to TreeWillExpandListener?
553 // TreeExpansionListener was used in case an expanded node
554 // had children that would also be expanded, but it is impossible
555 // for hidden nodes' expansion state to survive a SortOrder change
556 // since they are all erased when the tree structure change event
557 // is fired after changing the SortOrder.
560 public void treeCollapsed(TreeExpansionEvent e
) {}
563 public void treeExpanded(TreeExpansionEvent e
) {
564 if (sortOrder
!= SortOrder
.UNSORTED
) {
565 TreePath path
= e
.getPath();
566 Converter converter
= getConverter(path
.getLastPathComponent());
567 if (converter
== null) {
568 ArrayList
<TreePath
> paths
= sortHierarchy(path
);
569 fireTreeStructureChangedAndExpand(path
, paths
, false);
574 private boolean isFiltered(Object node
) {
575 Converter c
= getConverter(node
);
576 return c
== null ?
false : c
.isFiltered();
579 private boolean acceptable(TreePath path
, Object
[] childNodes
, int index
, ArrayList
<TreePath
> expand
) {
580 return acceptable(path
, childNodes
, index
) ||
581 applyFilter(filter
, path
.pathByAddingChild(childNodes
[index
]), expand
);
585 public void treeNodesChanged(TreeModelEvent e
) {
586 treeNodesChanged(e
.getTreePath(), e
.getChildIndices(), e
.getChildren());
589 protected void treeNodesChanged(TreePath path
, int[] childIndices
, Object
[] childNodes
) {
590 if (childIndices
== null) {
591 // path should be root path
594 applyFilter(null, null, null, true);
597 Converter converter
= getConverter(path
.getLastPathComponent());
598 ArrayList
<TreePath
> expand
= null;
599 if (converter
!= null) {
600 expand
= new ArrayList
<TreePath
>();
602 for (int i
=0; i
<childIndices
.length
; i
++) {
603 int idx
= converter
.convertRowIndexToView(childIndices
[i
]);
605 // see if the filter dislikes the nodes new state
606 if (converter
.isFiltered() &&
607 !isFiltered(childNodes
[i
]) &&
608 !acceptable(path
, childNodes
, i
)) {
609 // maybe it likes a child nodes state
610 if (!applyFilter(filter
, path
.pathByAddingChild(childNodes
[i
]), expand
))
611 remove(path
, childNodes
[i
]);
614 childNodes
[childIndex
] = childNodes
[i
];
615 childIndices
[childIndex
++] = idx
;
616 } else if (acceptable(path
, childNodes
, i
, expand
)) {
617 int viewIndex
= insert(path
.getLastPathComponent(),
618 childNodes
[i
], childIndices
[i
], converter
);
619 fireTreeNodesInserted(path
, indices(viewIndex
), nodes(childNodes
[i
]));
622 if (childIndex
== 0) {
623 maybeFireStructureChange(path
, expand
);
626 if (sortOrder
!= SortOrder
.UNSORTED
&& converter
.getChildCount() > 1) {
627 sort(path
.getLastPathComponent(), createValueIndexPairArray(converter
.getChildCount()));
628 fireTreeStructureChangedAndExpand(path
, null, true);
632 if (childIndex
!= childIndices
.length
) {
633 childIndices
= Arrays
.copyOf(childIndices
, childIndex
);
634 childNodes
= Arrays
.copyOf(childNodes
, childIndex
);
636 } else if (filter
!= null && isFilteredOut(path
)) {
637 // see if the filter likes the nodes new states
638 expand
= new ArrayList
<TreePath
>();
641 for (int i
=0; i
<childIndices
.length
; i
++) {
642 if (acceptable(path
, childNodes
, i
, expand
)) {
644 vtm
= new int[childIndices
.length
-i
];
645 vtm
[idx
++] = childIndices
[i
];
648 // filter in path if appropriate
650 filterIn(vtm
, idx
, path
, expand
);
653 // must fire tree nodes changed even if a
654 // structure change will be fired because the
655 // expanded paths need to be updated first
656 fireTreeNodesChanged(path
, childIndices
, childNodes
);
657 maybeFireStructureChange(path
, expand
);
661 * Helper method for treeNodesChanged...
665 private void maybeFireStructureChange(TreePath path
, ArrayList
<TreePath
> expand
) {
666 if (expand
!= null && !expand
.isEmpty()) {
667 Enumeration
<TreePath
> expanded
= tree
.getExpandedDescendants(path
);
668 fireTreeStructureChanged(path
);
669 if (expanded
!= null)
670 while (expanded
.hasMoreElements())
671 tree
.expandPath(expanded
.nextElement());
677 public void treeNodesInserted(TreeModelEvent e
) {
678 treeNodesInserted(e
.getTreePath(), e
.getChildIndices(), e
.getChildren());
681 protected void treeNodesInserted(TreePath path
, int[] childIndices
, Object
[] childNodes
) {
682 Object parent
= path
.getLastPathComponent();
683 Converter converter
= getConverter(parent
);
684 ArrayList
<TreePath
> expand
= null;
685 if (converter
!= null) {
686 // if (childIndices.length > 3 && !converter.isFiltered()
687 // && childIndices.length > converter.getChildCount()/10) {
688 // TreePath expand = sortHierarchy(path);
689 // fireTreeStructureChangedAndExpand(expand);
693 for (int i
=0; i
<childIndices
.length
; i
++) {
694 if (converter
.isFiltered()) {
695 // path hasn't met the filter criteria, so childNodes must be filtered
697 expand
= new ArrayList
<TreePath
>();
698 if (!applyFilter(filter
, path
.pathByAddingChild(childNodes
[i
]), expand
))
701 // shift the appropriate cached modelIndices
702 int[] vtm
= converter
.viewToModel
;
703 int modelIndex
= childIndices
[i
];
704 for (int j
=vtm
.length
; --j
>=0;) {
705 if (vtm
[j
] >= modelIndex
)
708 // insert modelIndex to converter
709 int viewIndex
= insert(parent
, childNodes
[i
], modelIndex
, converter
);
710 childNodes
[childIndex
] = childNodes
[i
];
711 childIndices
[childIndex
++] = viewIndex
;
715 if (childIndex
!= childIndices
.length
) {
716 childIndices
= Arrays
.copyOf(childIndices
, childIndex
);
717 childNodes
= Arrays
.copyOf(childNodes
, childIndex
);
719 if (childIndex
> 1 && sortOrder
!= SortOrder
.UNSORTED
) {
720 sort(childIndices
, childNodes
);
722 } else if (filter
!= null && isFilteredOut(path
)) {
723 // apply filter to inserted nodes
726 expand
= new ArrayList
<TreePath
>();
727 for (int i
=0; i
<childIndices
.length
; i
++) {
728 if (acceptable(path
, childNodes
, i
, expand
)) {
730 vtm
= new int[childIndices
.length
-i
];
731 vtm
[idx
++] = childIndices
[i
];
734 // filter in path if appropriate
736 filterIn(vtm
, idx
, path
, expand
);
739 fireTreeNodesInserted(path
, childIndices
, childNodes
);
744 public void treeNodesRemoved(TreeModelEvent e
) {
745 treeNodesRemoved(e
.getTreePath(), e
.getChildIndices(), e
.getChildren());
749 private boolean isFilterStartPath(TreePath path
) {
750 if (filterStartPath
== null)
751 return path
.getPathCount() == 1;
752 return filterStartPath
.equals(path
);
755 protected void treeNodesRemoved(TreePath path
, int[] childIndices
, Object
[] childNodes
) {
756 Object parent
= path
.getLastPathComponent();
757 Converter converter
= getConverter(parent
);
758 if (converter
!= null) {
760 for (int i
=0; i
<childNodes
.length
; i
++) {
761 removeConverter(childNodes
[i
]);
762 int viewIndex
= converter
.convertRowIndexToView(childIndices
[i
]);
763 if (viewIndex
>= 0) {
764 childNodes
[len
] = childNodes
[i
];
765 childIndices
[len
++] = viewIndex
;
770 if (converter
.isFiltered() && converter
.getChildCount() == len
) {
771 ArrayList
<TreePath
> expand
= new ArrayList
<TreePath
>();
772 if (applyFilter(filter
, path
, expand
)) {
773 expand
.retainAll(getExpandedPaths(path
));
774 if (sortOrder
!= SortOrder
.UNSORTED
)
776 fireTreeStructureChangedAndExpand(path
, expand
, true);
777 } else if (isFilterStartPath(path
)) {
778 converters
.put(parent
, new Converter(Converter
.EMPTY
, true));
779 fireTreeStructureChanged(path
);
781 remove(path
.getParentPath(), parent
);
785 if (len
!= childIndices
.length
) {
786 childIndices
= Arrays
.copyOf(childIndices
, len
);
787 childNodes
= Arrays
.copyOf(childNodes
, len
);
789 if (len
> 1 && sortOrder
!= SortOrder
.UNSORTED
) {
790 sort(childIndices
, childNodes
);
792 if (childIndices
.length
== 1) {
793 converter
.remove(converter
.convertRowIndexToModel(childIndices
[0]));
795 converter
.remove(childIndices
);
797 } else if (filter
!= null && isFilteredOut(path
)) {
800 fireTreeNodesRemoved(path
, childIndices
, childNodes
);
803 private Collection
<TreePath
> getExpandedPaths(TreePath path
) {
804 Enumeration
<TreePath
> en
= tree
.getExpandedDescendants(path
);
806 return Collections
.emptySet();
807 HashSet
<TreePath
> expanded
= new HashSet
<TreePath
>();
808 while (en
.hasMoreElements())
809 expanded
.add(en
.nextElement());
814 public void treeStructureChanged(TreeModelEvent e
) {
815 if (converters
!= null) {
816 // not enough information to properly clean up
817 // reapply filter/sort
818 converters
= createConvertersMap();
819 TreePath
[] sel
= tree
.getSelectionPaths();
820 if (filter
!= null) {
821 applyFilter(null, null, getExpandedPaths(new TreePath(getRoot())), false);
823 if (sortOrder
!= SortOrder
.UNSORTED
) {
824 TreePath path
= new TreePath(getRoot());
825 ArrayList
<TreePath
> expand
= sortHierarchy(path
);
826 fireTreeStructureChangedAndExpand(path
, expand
, false);
829 tree
.clearSelection();
830 TreePath changedPath
= e
.getTreePath();
831 for (TreePath path
: sel
) {
832 if (!changedPath
.isDescendant(path
))
833 tree
.addSelectionPath(path
);
837 fireTreeStructureChanged(e
.getTreePath());
843 public final int compare(ValueIndexPair
<N
> a
, ValueIndexPair
<N
> b
) {
844 return compareNodes(a
.value
, b
.value
);
848 protected int compareNodes(N a
, N b
) {
849 if (comparator
!= null)
850 return comparator
.compare(a
, b
);
851 return collator
.compare(a
.toString(), b
.toString());
854 private void removeConverter(Object node
) {
855 Converter c
= getConverter(node
);
857 removeConverter(c
, node
);
860 private void removeConverter(Converter converter
, Object node
) {
861 for (int i
=converter
.getChildCount(); --i
>=0;) {
862 int index
= converter
.convertRowIndexToModel(i
);
863 Object child
= model
.getChild(node
, index
);
864 Converter c
= getConverter(child
);
866 removeConverter(c
, child
);
868 converters
.remove(node
);
871 private boolean isFilteredOut(TreePath path
) {
872 if (filterStartPath
!= null && !filterStartPath
.isDescendant(path
))
874 TreePath parent
= path
.getParentPath();
875 // root should always have a converter if filter is non-null,
876 // so if parent is ever null, there is a bug somewhere else
877 Converter c
= getConverter(parent
.getLastPathComponent());
879 return getIndexOfChild(
880 parent
.getLastPathComponent(),
881 path
.getLastPathComponent()) < 0;
883 return isFilteredOut(parent
);
886 private void filterIn(int[] vtm
, int vtmLength
, TreePath path
, ArrayList
<TreePath
> expand
) {
887 Object node
= path
.getLastPathComponent();
888 if (vtmLength
!= vtm
.length
)
889 vtm
= Arrays
.copyOf(vtm
, vtmLength
);
890 Converter converter
= new Converter(vtm
, true);
891 converters
.put(node
, converter
);
892 insert(path
.getParentPath(), node
);
893 tree
.expandPath(path
);
897 private boolean acceptable(TreePath path
, Object
[] nodes
, int index
) {
898 Object node
= nodes
[index
];
899 return filter
.acceptNode(path
, (N
)node
, model
.isLeaf(node
));
902 private int ascInsertionIndex(int[] vtm
, Object parent
, N node
, int idx
) {
903 for (int i
=vtm
.length
; --i
>=0;) {
904 int cmp
= compareNodes(node
, (N
)model
.getChild(parent
, vtm
[i
]));
905 if (cmp
> 0 || (cmp
== 0 && idx
> vtm
[i
])) {
913 private int dscInsertionIndex(int[] vtm
, Object parent
, N node
, int idx
) {
914 for (int i
=vtm
.length
; --i
>=0;) {
915 int cmp
= compareNodes(node
, (N
)model
.getChild(parent
, vtm
[i
]));
918 } else if (cmp
== 0 && idx
< vtm
[i
]) {
927 * Inserts the specified path and node and any parent paths as necessary.
929 * Fires appropriate event.
933 private void insert(TreePath path
, Object node
) {
934 Object parent
= path
.getLastPathComponent();
935 Converter converter
= converters
.get(parent
);
936 int modelIndex
= model
.getIndexOfChild(parent
, node
);
937 if (converter
== null) {
938 converter
= new Converter(indices(modelIndex
), true);
939 converters
.put(parent
, converter
);
940 insert(path
.getParentPath(), parent
);
942 int viewIndex
= insert(parent
, node
, modelIndex
, converter
);
943 fireTreeNodesInserted(path
, indices(viewIndex
), nodes(node
));
948 * Inserts node into parent in correct sort order.
950 * Responsibility of caller to fire appropriate event with the returned viewIndex.
957 private int insert(Object parent
, Object node
, int modelIndex
, Converter converter
) {
958 int[] vtm
= converter
.viewToModel
;
962 viewIndex
= ascInsertionIndex(vtm
, parent
, (N
)node
, modelIndex
);
965 viewIndex
= dscInsertionIndex(vtm
, parent
, (N
)node
, modelIndex
);
967 default: case UNSORTED
:
968 viewIndex
= unsortedInsertionIndex(vtm
, modelIndex
);
971 int[] a
= new int[vtm
.length
+1];
972 System
.arraycopy(vtm
, 0, a
, 0, viewIndex
);
973 System
.arraycopy(vtm
, viewIndex
, a
, viewIndex
+1, vtm
.length
-viewIndex
);
974 a
[viewIndex
] = modelIndex
;
975 converter
.viewToModel
= a
;
979 private void remove(TreePath path
, Object node
) {
980 Object parent
= path
.getLastPathComponent();
981 if (path
.getPathCount() == 1 || (filterStartPath
!= null && filterStartPath
.equals(path
))) {
982 removeConverter(node
);
983 converters
.put(parent
, new Converter(Converter
.EMPTY
, true));
984 fireTreeNodesRemoved(path
, indices(0), nodes(node
));
987 Converter converter
= converters
.get(parent
);
988 int modelIndex
= model
.getIndexOfChild(parent
, node
);
989 int viewIndex
= converter
.remove(modelIndex
);
992 removeConverter(node
);
993 fireTreeNodesRemoved(path
, indices(viewIndex
), nodes(node
));
995 case Converter
.ONLY_INDEX
:
996 // if (path.getParentPath() == null) {
997 // // reached filter root
998 // removeConverter(node);
999 // converters.put(parent, new Converter(Converter.EMPTY, true));
1000 // fireTreeNodesRemoved(path, indices(0), nodes(node));
1003 remove(path
.getParentPath(), parent
);
1005 case Converter
.INDEX_NOT_FOUND
:
1006 removeConverter(node
);
1016 private static int unsortedInsertionIndex(int[] vtm
, int idx
) {
1017 for (int i
=vtm
.length
; --i
>=0;)
1023 private static void sort(int[] childIndices
, Object
[] childNodes
) {
1024 int len
= childIndices
.length
;
1025 ValueIndexPair
[] pairs
= new ValueIndexPair
[len
];
1026 for (int i
=len
; --i
>=0;)
1027 pairs
[i
] = new ValueIndexPair
<Object
>(childIndices
[i
], childNodes
[i
]);
1029 for (int i
=len
; --i
>=0;) {
1030 childIndices
[i
] = pairs
[i
].index
;
1031 childNodes
[i
] = pairs
[i
].value
;
1035 private static int[] indices(int...indices
) {
1039 private static Object
[] nodes(Object
...nodes
) {
1047 * This class has a dual purpose, both related to comparing/sorting.
1049 * The Handler class sorts an array of ValueIndexPair based on the value.
1050 * Used for sorting the view.
1052 * ValueIndexPair sorts itself based on the index.
1053 * Used for sorting childIndices for fire* methods.
1055 private static class ValueIndexPair
<N
> implements Comparable
<ValueIndexPair
<N
>> {
1058 ValueIndexPair(int idx
, N val
) {
1067 public int compareTo(ValueIndexPair
<N
> o
) {
1068 return index
- o
.index
;
1072 private static class Converter
{
1074 static final int[] EMPTY
= new int[0];
1076 static final int ONLY_INDEX
= -2;
1078 static final int INDEX_NOT_FOUND
= -1;
1080 Converter(int[] vtm
, boolean filtered
) {
1082 isFiltered
= filtered
;
1085 private int[] viewToModel
;
1087 private boolean isFiltered
;
1089 // public boolean equals(Converter conv) {
1090 // if (conv == null)
1092 // if (isFiltered != conv.isFiltered)
1094 // return Arrays.equals(viewToModel, conv.viewToModel);
1097 boolean isFiltered() {
1101 void remove(int viewIndices
[]) {
1102 int len
= viewToModel
.length
- viewIndices
.length
;
1104 viewToModel
= EMPTY
;
1106 int[] oldVTM
= viewToModel
;
1107 int[] newVTM
= new int[len
];
1108 for (int oldIndex
=0, newIndex
=0, removeIndex
=0;
1109 newIndex
<newVTM
.length
;
1110 newIndex
++, oldIndex
++) {
1111 while (removeIndex
< viewIndices
.length
&& oldIndex
== viewIndices
[removeIndex
]) {
1112 int idx
= oldVTM
[oldIndex
];
1115 for (int i
=newIndex
; --i
>=0;)
1116 if (newVTM
[i
] > idx
)
1118 for (int i
=oldIndex
; i
<oldVTM
.length
; i
++)
1119 if (oldVTM
[i
] > idx
)
1122 newVTM
[newIndex
] = oldVTM
[oldIndex
];
1124 viewToModel
= newVTM
;
1130 * @return viewIndex that was removed<br>
1131 * or <code>ONLY_INDEX</code> if the modelIndex is the only one in the view<br>
1132 * or <code>INDEX_NOT_FOUND</code> if the modelIndex is not in the view
1134 int remove(int modelIndex
) {
1135 int[] vtm
= viewToModel
;
1136 for (int i
=vtm
.length
; --i
>=0;) {
1137 if (vtm
[i
] > modelIndex
) {
1139 } else if (vtm
[i
] == modelIndex
) {
1140 if (vtm
.length
== 1) {
1141 viewToModel
= EMPTY
;
1146 if (vtm
[i
] > modelIndex
)
1149 int[] a
= new int[vtm
.length
-1];
1151 System
.arraycopy(vtm
, 0, a
, 0, viewIndex
);
1152 int len
= a
.length
-viewIndex
;
1154 System
.arraycopy(vtm
, viewIndex
+1, a
, viewIndex
, len
);
1159 return INDEX_NOT_FOUND
;
1163 int getChildCount() {
1164 return viewToModel
.length
;
1169 * @return viewIndex corresponding to modelIndex<br>
1170 * or <code>INDEX_NOT_FOUND</code> if the modelIndex is not in the view
1172 int convertRowIndexToView(int modelIndex
) {
1173 int[] vtm
= viewToModel
;
1174 for (int i
=vtm
.length
; --i
>=0;) {
1175 if (vtm
[i
] == modelIndex
)
1178 return INDEX_NOT_FOUND
;
1181 int convertRowIndexToModel(int viewIndex
) {
1182 return viewToModel
[viewIndex
];
1186 public interface Filter
<N
> {
1187 boolean acceptNode(TreePath parent
, N node
, boolean leaf
);
1190 public static class RegexFilter
<N
> implements Filter
<N
> {
1192 public RegexFilter(Pattern pattern
, boolean leaf
) {
1193 matcher
= pattern
.matcher("");
1197 private Matcher matcher
;
1199 private boolean leafOnly
;
1201 public boolean acceptNode(TreePath parent
, N node
, boolean leaf
) {
1202 if (leafOnly
&& !leaf
)
1204 matcher
.reset(getStringValue(node
));
1205 return matcher
.find();
1208 protected String
getStringValue(N node
) {
1209 return node
.toString();
1214 private static Map
<Object
,Converter
> createConvertersMap() {
1215 return new HashMap
<Object
,Converter
>();