1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.xnap.commons.gui.tree;
22
23 import java.io.File;
24 import java.io.FileFilter;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Comparator;
28 import java.util.Hashtable;
29 import java.util.List;
30 import javax.swing.event.TreeModelEvent;
31 import javax.swing.event.TreeModelListener;
32
33 public class FileTreeModel extends AbstractTreeModel
34 {
35
36 private List<Object> subRoots;
37 private Hashtable<String,List<FileNode>> subChildren = new Hashtable<String,List<FileNode>>();
38 private File cachedDir;
39 private boolean cacheSorted;
40 private File[] cache;
41 private FileComparator comparator;
42 private FileFilter filter = new DefaultFilter();
43 private boolean sort = true;
44
45 public FileTreeModel(String root, File[] roots)
46 {
47 super(root);
48
49 if (roots != null) {
50 subRoots = new ArrayList<Object>(roots.length);
51 for (int i = 0; i < roots.length; i++) {
52 addSubRoot(roots[i]);
53 }
54 }
55 else {
56 subRoots = new ArrayList<Object>();
57 }
58 }
59
60 public FileTreeModel(String root)
61 {
62 this(root, null);
63 }
64
65 public boolean isLeaf(Object node)
66 {
67 return false;
68 }
69
70 public int getChildCount(Object node)
71 {
72 if (node instanceof File && ((File)node).canRead()) {
73 return getSubDirs((File)node, false).length;
74 }
75 else if (node instanceof String) {
76 if (node.equals(root)) {
77 return subRoots.size();
78 }
79 return subChildren.get(node).size();
80 }
81 else {
82 return 0;
83 }
84 }
85
86 public Object getChild(Object parent, int index)
87 {
88 if (parent instanceof File) {
89 File[] children = getSubDirs((File)parent, sort);
90
91 if (index >= children.length) {
92 return null;
93 }
94 return new FileNode(children[index]);
95 }
96 else if (parent instanceof String) {
97 if (parent.equals(root) && index < subRoots.size())
98 return subRoots.get(index);
99
100 List<FileNode> v = subChildren.get(parent);
101 return (index < v.size()) ? v.get(index) : null;
102 }
103 else {
104 return null;
105 }
106 }
107
108 public int getIndexOfChild(Object parent, Object child)
109 {
110 if (parent instanceof File) {
111 File[] children = getSubDirs((File)parent, sort);
112
113 if (children == null)
114 return -1;
115
116 for (int i = 0; i < children.length; i++) {
117 if (children[i] == child)
118 return i;
119 }
120 }
121 else if (parent instanceof String) {
122 if (parent.equals(root)) {
123 return subRoots.indexOf(child);
124 }
125 return ((List)subChildren.get(parent)).indexOf(child);
126 }
127
128 return -1;
129 }
130
131 /***
132 * Guaranteed to not return null.
133 * @param f
134 * @param doSort
135 * @return
136 */
137 private File[] getSubDirs(File f, boolean doSort)
138 {
139 if (f == cachedDir && cacheSorted == doSort)
140 return cache;
141
142 File[] children = f.listFiles(filter);
143 if (children == null) {
144 cache = new File[0];
145 }
146 else {
147 cache = children;
148 if (doSort) {
149 Arrays.sort(cache, getComparator());
150 }
151 }
152 cachedDir = f;
153 cacheSorted = doSort;
154 return cache;
155 }
156
157 public void addSubRoot(String name)
158 {
159 if (subRoots.contains(name))
160 return;
161
162 subRoots.add(name);
163
164
165 subChildren.put(name, new ArrayList<FileNode>());
166
167 Object[] path = { root };
168 int[] indices = { subRoots.size() - 1 };
169 Object[] children = { name };
170 fireTreeNodesInserted(new TreeModelEvent(this, path, indices,
171 children));
172
173 }
174
175 public void addSubRoot(File f)
176 {
177 if (subRoots.contains(f))
178 return;
179
180 subRoots.add(f);
181
182 Object[] path = { root };
183 int[] indices = { subRoots.size() - 1 };
184 Object[] children = { f };
185 fireTreeNodesInserted(new TreeModelEvent(this, path, indices,
186 children));
187 }
188
189 public void removeSubRoots()
190 {
191
192 for (int i = 0; i < subRoots.size(); i++)
193 subChildren.remove(subRoots.get(i));
194
195 subRoots.clear();
196
197 Object[] path = { root };
198 fireTreeStructureChanged(new TreeModelEvent(this, path));
199 }
200
201 public void removeChildrenOfSubRoot(String s)
202 {
203 if (!subRoots.contains(s))
204 return;
205
206 subChildren.get(s).clear();
207
208 Object[] path = { root, s };
209 fireTreeStructureChanged(new TreeModelEvent(this, path));
210 }
211
212 public void addChildOfSubRoot(File file, String subRoot)
213 {
214 addChildOfSubRoot(file, subRoot, null);
215 }
216
217 public void addChildOfSubRoot(File file, String subRoot, String label)
218 {
219 addSubRoot(subRoot);
220
221 List<FileNode> children = subChildren.get(subRoot);
222 FileNode node = new FileNode(file, true, label);
223
224 if (children.contains(node))
225 return;
226 children.add(node);
227
228 fireTreeNodesInserted(new TreeModelEvent(this,
229 new Object[] { root, subRoot },
230 new int[] { children.size() - 1 },
231 new Object[] { file }));
232 }
233
234 public void removeChildOfSubRoot(File file, String subRoot)
235 {
236 if (!subRoots.contains(subRoot))
237 return;
238
239 List<FileNode> children = subChildren.get(subRoot);
240
241 FileNode node = new FileNode(file, true);
242 int index = children.indexOf(node);
243 if (index == -1)
244 return;
245 children.remove(index);
246
247 fireTreeNodesRemoved(new TreeModelEvent(this,
248 new Object[] { root, subRoot },
249 new int[] { index },
250 new Object[] { node }));
251 }
252
253 /***
254 * Create comparator lazily.
255 */
256 private FileComparator getComparator()
257 {
258 if (comparator == null) {
259 comparator = new FileComparator();
260 }
261 return comparator;
262 }
263
264
265 static class FileComparator implements Comparator<File>
266 {
267
268 public int compare(File o1, File o2)
269 {
270 return o1.getAbsolutePath().compareToIgnoreCase(o2.getAbsolutePath());
271 }
272 }
273
274 /***
275 * Sets a file filter that is used for listing directories in the tree.
276 * <p>
277 * The default filter excludes hidden files and files that are not directories.
278 * <p>
279 * @param filter can be <code>null</code>
280 */
281 public void setFileFilter(FileFilter filter)
282 {
283 this.filter = filter;
284 }
285
286 /***
287 * Returns the currently set file filter.
288 * @return can be <code>null</code>
289 */
290 public FileFilter getFileFilter()
291 {
292 return filter;
293 }
294
295 private class DefaultFilter implements FileFilter
296 {
297 public boolean accept(File file)
298 {
299 return file.isDirectory() && !file.isHidden();
300 }
301 }
302
303 /***
304 * Sets whether the listed files of a directory should be sorted.
305 * <p>
306 * Fires {@link TreeModelListener#treeStructureChanged(javax.swing.event.TreeModelEvent)}
307 * with the root node as parameter to make sure the whole tree is updated
308 * correctly.
309 * @param sort
310 */
311 public void setSortListedFiles(boolean sort)
312 {
313 if (this.sort != sort) {
314 this.sort = sort;
315 fireTreeStructureChanged(new TreeModelEvent(this, new Object[] { getRoot() }));
316 }
317 }
318
319 /***
320 * Returns whether the listed files of a directory are being sorted.
321 */
322 public boolean getSortListedFiles()
323 {
324 return sort;
325 }
326 }