1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package net.grinder.console.swingui;
23
24 import java.io.File;
25 import java.io.FileFilter;
26 import java.io.FilenameFilter;
27
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.List;
32 import javax.swing.event.EventListenerList;
33 import javax.swing.event.TreeModelEvent;
34 import javax.swing.event.TreeModelListener;
35 import javax.swing.tree.TreeModel;
36 import javax.swing.tree.TreePath;
37
38 import net.grinder.console.editor.Buffer;
39 import net.grinder.console.editor.EditorModel;
40 import net.grinder.console.distribution.FileChangeWatcher;
41 import net.grinder.util.WeakValueHashMap;
42
43
44
45
46
47
48
49 final class FileTreeModel implements TreeModel {
50
51 private final EventListenerList m_listeners = new EventListenerList();
52 private final EditorModel m_editorModel;
53 private final FileFilter m_distributionFileFilter;
54
55 private final FilenameFilter m_directoryFilter =
56 new FilenameFilter() {
57 public boolean accept(File dir, String name) {
58 final File file = new File(dir, name);
59 return file.isDirectory() && m_distributionFileFilter.accept(file);
60 }
61 };
62
63 private final FilenameFilter m_fileFilter =
64 new FilenameFilter() {
65 public boolean accept(File dir, String name) {
66 final File file = new File(dir, name);
67 return file.isFile() && m_distributionFileFilter.accept(file);
68 }
69 };
70
71
72
73
74 private final WeakValueHashMap<File, Node> m_filesToNodes =
75 new WeakValueHashMap<File, Node>();
76
77
78
79
80
81 private final WeakValueHashMap<Buffer, FileNode> m_buffersToFileNodes =
82 new WeakValueHashMap<Buffer, FileNode>();
83
84 private RootNode m_rootNode;
85
86 FileTreeModel(EditorModel editorModel,
87 FileFilter distributionFileFilter,
88 File initialRootDirectory) {
89 m_editorModel = editorModel;
90 m_distributionFileFilter = distributionFileFilter;
91 setRootDirectory(initialRootDirectory);
92 }
93
94 public void setRootDirectory(File rootDirectory) {
95 m_rootNode = new RootNode(rootDirectory);
96 fireTreeStructureChanged(m_rootNode);
97 }
98
99 public void refresh() {
100 m_rootNode.refresh();
101 fireTreeStructureChanged(m_rootNode);
102 }
103
104 public Object getRoot() {
105 return m_rootNode;
106 }
107
108 public Object getChild(Object parent, int index) {
109
110 if (parent instanceof DirectoryNode) {
111 final DirectoryNode directoryNode = (DirectoryNode)parent;
112
113 if (directoryNode.belongsToModel(this)) {
114 return directoryNode.getChild(index);
115 }
116 }
117
118 return null;
119 }
120
121 public int getChildCount(Object parent) {
122
123 if (parent instanceof DirectoryNode) {
124 final DirectoryNode directoryNode = (DirectoryNode)parent;
125
126 if (directoryNode.belongsToModel(this)) {
127 return directoryNode.getChildCount();
128 }
129 }
130
131 return 0;
132 }
133
134 public int getIndexOfChild(Object parent, Object child) {
135
136 if (parent == null || child == null) {
137
138 return -1;
139 }
140
141 if (parent instanceof DirectoryNode) {
142 final DirectoryNode directoryNode = (DirectoryNode)parent;
143
144 if (directoryNode.belongsToModel(this)) {
145 return directoryNode.getIndexOfChild((Node)child);
146 }
147 }
148
149 return -1;
150 }
151
152 public boolean isLeaf(Object node) {
153 if (node instanceof FileNode) {
154 final FileNode fileNode = (FileNode)node;
155
156 if (fileNode.belongsToModel(this)) {
157 return true;
158 }
159 }
160
161 return false;
162 }
163
164 public void addTreeModelListener(TreeModelListener listener) {
165 m_listeners.add(TreeModelListener.class, listener);
166 }
167
168 public void removeTreeModelListener(TreeModelListener listener) {
169 m_listeners.remove(TreeModelListener.class, listener);
170 }
171
172 private void fireTreeStructureChanged(Node node) {
173 final Object[] listeners = m_listeners.getListenerList();
174
175 final TreeModelEvent event = new TreeModelEvent(this, node.getPath());
176
177 for (int i = listeners.length - 2; i >= 0; i -= 2) {
178 ((TreeModelListener)listeners[i + 1]).treeStructureChanged(event);
179 }
180 }
181
182 private void fireTreeNodesChanged(TreePath path) {
183 final Object[] listeners = m_listeners.getListenerList();
184
185 final TreeModelEvent event = new TreeModelEvent(this, path);
186
187 for (int i = listeners.length - 2; i >= 0; i -= 2) {
188 ((TreeModelListener)listeners[i + 1]).treeNodesChanged(event);
189 }
190 }
191
192 public void valueForPathChanged(TreePath path, Object newValue) {
193 fireTreeNodesChanged(path);
194 }
195
196
197
198
199
200
201
202
203
204 public Node findNode(File file) {
205 final Node existingNode = m_filesToNodes.get(file);
206
207 if (existingNode != null) {
208 return existingNode;
209 }
210
211
212 final File[] paths = fileToArrayOfParentPaths(file);
213
214 Node treeStructureChangedNode = null;
215
216 for (int i = 0; i < paths.length - 1; ++i) {
217 final Node node = m_filesToNodes.get(paths[i]);
218
219 if (node instanceof DirectoryNode) {
220 final DirectoryNode directoryNode = (DirectoryNode)node;
221
222 if (directoryNode.getChildForFile(paths[i + 1]) == null) {
223 directoryNode.refresh();
224 treeStructureChangedNode = directoryNode;
225
226 if (directoryNode.getChildForFile(paths[i + 1]) == null) {
227 return null;
228 }
229 }
230 }
231 }
232
233 if (treeStructureChangedNode != null) {
234 fireTreeStructureChanged(treeStructureChangedNode);
235 }
236
237 return m_filesToNodes.get(file);
238 }
239
240 private File[] fileToArrayOfParentPaths(File file) {
241 final List<File> list = new ArrayList<File>();
242
243 File f = file;
244
245 while (f != null) {
246 list.add(f);
247 f = f.getParentFile();
248 }
249
250 Collections.reverse(list);
251
252 return list.toArray(new File[list.size()]);
253 }
254
255 public FileNode findFileNode(Buffer buffer) {
256 return m_buffersToFileNodes.get(buffer);
257 }
258
259
260
261
262
263
264
265
266 public class RefreshChangedDirectoriesListener
267 implements FileChangeWatcher.FileChangedListener {
268
269 public void filesChanged(File[] files) {
270
271
272
273 for (int i = 0; i < files.length; ++i) {
274
275 final Node node = findNode(files[i]);
276
277
278 if (node instanceof DirectoryNode) {
279 ((DirectoryNode)node).refresh();
280 fireTreeStructureChanged(node);
281 }
282 }
283 }
284 }
285
286
287
288
289 public abstract class Node implements FileTree.Node {
290
291 private final File m_file;
292 private final TreePath m_path;
293
294 protected Node(Node parentNode, File file) {
295 m_file = file;
296
297 if (parentNode != null) {
298 m_path = parentNode.getPath().pathByAddingChild(this);
299 }
300 else {
301 m_path = new TreePath(this);
302 }
303
304 m_filesToNodes.put(file, this);
305 }
306
307 public String toString() {
308 return m_file.getName();
309 }
310
311 public Buffer getBuffer() {
312 return null;
313 }
314
315 public final File getFile() {
316 return m_file;
317 }
318
319 public final TreePath getPath() {
320 return m_path;
321 }
322
323 public boolean canOpen() {
324 return false;
325 }
326
327 boolean belongsToModel(FileTreeModel model) {
328 return FileTreeModel.this == model;
329 }
330 }
331
332
333
334
335 public final class FileNode extends Node {
336
337 private Buffer m_buffer;
338
339 private FileNode(DirectoryNode parentNode, File file) {
340 super(parentNode, file);
341
342 setBuffer(m_editorModel.getBufferForFile(file));
343 }
344
345 public void setBuffer(Buffer buffer) {
346 if (m_buffer != null) {
347 m_buffersToFileNodes.remove(m_buffer);
348 }
349
350 m_buffer = buffer;
351
352 if (buffer != null) {
353 m_buffersToFileNodes.put(buffer, this);
354 }
355 }
356
357 public Buffer getBuffer() {
358 return m_buffer;
359 }
360
361 public boolean canOpen() {
362 return true;
363 }
364 }
365
366
367
368
369 private class DirectoryNode extends Node {
370
371 private final File[] m_noFiles = new File[0];
372
373 private File[] m_childDirectories = m_noFiles;
374 private DirectoryNode[] m_childDirectoryNodes;
375 private File[] m_childFiles = m_noFiles;
376 private FileNode[] m_childFileNodes;
377
378 DirectoryNode(DirectoryNode parentNode, File file) {
379 super(parentNode, file);
380
381 refresh();
382 }
383
384 public void refresh() {
385 for (int i = 0; i < m_childDirectories.length; ++i) {
386 final DirectoryNode oldDirectoryNode =
387 (DirectoryNode)m_filesToNodes.remove(m_childDirectories[i]);
388
389
390
391 if (oldDirectoryNode != null) {
392 oldDirectoryNode.refresh();
393 }
394 }
395
396 for (int i = 0; i < m_childFiles.length; ++i) {
397 final FileNode oldFileNode =
398 (FileNode)m_filesToNodes.remove(m_childFiles[i]);
399
400 if (oldFileNode != null) {
401 oldFileNode.setBuffer(null);
402 }
403 }
404
405 final File[] directories = getFile().listFiles(m_directoryFilter);
406
407 if (directories != null) {
408 Arrays.sort(directories);
409 m_childDirectories = directories;
410 }
411 else {
412 m_childDirectories = m_noFiles;
413 }
414
415 m_childDirectoryNodes = new DirectoryNode[m_childDirectories.length];
416
417 final File[] files = getFile().listFiles(m_fileFilter);
418
419 if (files != null) {
420 Arrays.sort(files);
421 m_childFiles = files;
422 }
423 else {
424 m_childFiles = m_noFiles;
425 }
426
427 m_childFileNodes = new FileNode[m_childFiles.length];
428 }
429
430 final Node getChildForFile(File file) {
431 if (file.isDirectory()) {
432 for (int i = 0; i < m_childDirectories.length; ++i) {
433 if (m_childDirectories[i].equals(file)) {
434 return getChild(i);
435 }
436 }
437 }
438 else {
439 for (int i = 0; i < m_childFiles.length; ++i) {
440 if (m_childFiles[i].equals(file)) {
441 return getChild(i + m_childDirectories.length);
442 }
443 }
444 }
445
446
447 return null;
448 }
449
450 public final Node getChild(int index) {
451 if (index < m_childDirectories.length) {
452 if (m_childDirectoryNodes[index] == null) {
453 m_childDirectoryNodes[index] =
454 new DirectoryNode(this, m_childDirectories[index]);
455 }
456
457 return m_childDirectoryNodes[index];
458 }
459 else if (index < m_childDirectories.length + m_childFiles.length) {
460 final int fileIndex = index - m_childDirectories.length;
461
462 if (m_childFileNodes[fileIndex] == null) {
463 m_childFileNodes[fileIndex] =
464 new FileNode(this, m_childFiles[fileIndex]);
465 }
466
467 return m_childFileNodes[fileIndex];
468 }
469 else {
470 return null;
471 }
472 }
473
474 public final int getChildCount() {
475 return m_childDirectories.length + m_childFiles.length;
476 }
477
478 public final int getIndexOfChild(Node child) {
479 for (int i = 0; i < m_childDirectories.length; ++i) {
480 if (m_childDirectories[i].equals(child.getFile())) {
481 return i;
482 }
483 }
484
485 for (int i = 0; i < m_childFiles.length; ++i) {
486 if (m_childFiles[i].equals(child.getFile())) {
487 return m_childDirectories.length + i;
488 }
489 }
490
491 return -1;
492 }
493 }
494
495
496
497
498 private final class RootNode extends DirectoryNode {
499
500 private RootNode(File file) {
501 super(null, file);
502 }
503
504 public String toString() {
505 return getFile().getPath();
506 }
507 }
508 }