Source Code Cross Referenced for FileSystemTreeModel.java in  » Scripting » oscript-2.10.4 » ti » chimera » fs » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Scripting » oscript 2.10.4 » ti.chimera.fs 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        /*=============================================================================
002:         *     Copyright Texas Instruments 2001, 2002. All Rights Reserved.
003:         * 
004:         *  This program is free software; you can redistribute it and/or modify
005:         *  it under the terms of the GNU General Public License as published by
006:         *  the Free Software Foundation; either version 2 of the License, or
007:         *  (at your option) any later version.
008:         * 
009:         *  This program is distributed in the hope that it will be useful,
010:         *  but WITHOUT ANY WARRANTY; without even the implied warranty of
011:         *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012:         *  GNU General Public License for more details.
013:         * 
014:         *  You should have received a copy of the GNU General Public License
015:         *  along with this program; if not, write to the Free Software
016:         *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
017:         */
018:
019:        package ti.chimera.fs;
020:
021:        import java.util.*;
022:        import java.lang.ref.*;
023:        import javax.swing.tree.TreePath;
024:
025:        import ti.swing.treetable.*;
026:
027:        import oscript.exceptions.ProgrammingErrorException;
028:        import oscript.fs.AbstractFile;
029:        import oscript.fs.AbstractFileSystem;
030:        import oscript.util.WorkerThread;
031:
032:        /**
033:         * Class that makes a TreeModel of the file system {@link oscript.fs.AbstractFileSystem}.
034:         * There are some additional "add-ons" that you should use to ensure proper
035:         * behavior of your tree.  (XXX I should probably just add a factory method to
036:         * install the TreeCellRenderer and TreeExpansionListener, and any other steps
037:         * that may get added in the future....)
038:         * <pre>
039:         *   JTree tree = new JTree( new FileSystemTreeModel( "/", null ) )
040:         *   tree.setCellRenderer( new FileSystemTreeModel.FileSystemTreeCellRenderer() );
041:         *   tree.addTreeExpansionListener( new FileSystemTreeModel.FileSystemTreeExpansionListener() );
042:         * </pre>
043:         * 
044:         * @author Rob Clark
045:         * @version 0.1
046:         */
047:        public class FileSystemTreeModel extends AbstractTreeModel {
048:            /* how it works:
049:             *    There is two levels of caching, which serve two purposes:
050:             * 
051:             *    1)  WatchedDirectory:  there is a single instance of this per directory
052:             *        which is shared amongst all FileSystemTreeModel-s.  This is where
053:             *        the actual access to the filesystem happens (from the context of a
054:             *        worker thread).  The actual directory in the filesystem has it's
055:             *        timestamp periodically polled (from the worker thread), and if it
056:             *        changes, the worker will resolve the new children (which may be
057:             *        expensive), and pass the new array of children to all the
058:             *        FileSystemNode-s which represent the same directory.
059:             * 
060:             *    2)  FileSystemNode:    this is where the children of a parent node are
061:             *        cached.  The set of children may be smaller than the actual children
062:             *        in the filesystem (from WatchedDirectory), due to whatever filter 
063:             *        may be installed.  Since different FileSystemTreeModel-s may have 
064:             *        different filters, this is not shared between filesystems.
065:             */
066:
067:            /**
068:             * The path of the root file
069:             */
070:            private String rootPath;
071:
072:            /**
073:             * The default filter shows all files.
074:             */
075:            private static FileSystemTreeFilter SHOW_ALL_FILES_FILTER = new FileSystemTreeFilter() {
076:
077:                public boolean accept(AbstractFile file) {
078:                    return true;
079:                }
080:
081:            };
082:
083:            private FileSystemTreeFilter filter = SHOW_ALL_FILES_FILTER;
084:
085:            /**
086:             * Class Constructor.
087:             * 
088:             * @param path        the path to the root node of the tree
089:             * @param filter      the filter, or <code>null</code>
090:             */
091:            public FileSystemTreeModel(String path, FileSystemTreeFilter filter) {
092:                this (pathToFile(path), filter);
093:            }
094:
095:            /**
096:             * Class Constructor.
097:             * 
098:             * @param root        the root node of the tree
099:             * @param filter      the filter, or <code>null</code>
100:             */
101:            public FileSystemTreeModel(AbstractFile root,
102:                    FileSystemTreeFilter filter) {
103:                super (new FileSystemNode(null, root));
104:
105:                rootPath = root.getPath();
106:
107:                // the following hacks needed, because we can't reference "this" at
108:                // the time when we need to construct the root node:
109:                ((FileSystemNode) getRoot()).outer = this ;
110:                fsNodeTable.put(((FileSystemNode) getRoot()).getFile(),
111:                        getRoot());
112:
113:                if (filter != null)
114:                    this .filter = filter;
115:
116:                startWatchingNode(((FileSystemNode) getRoot()));
117:            }
118:
119:            public boolean isLeaf(Object node) {
120:                return !((FileSystemNode) node).getFile().isDirectory();
121:            }
122:
123:            public Object getChild(Object parent, int idx) {
124:                return ((FileSystemNode) parent).getChild(idx);
125:            }
126:
127:            public int getChildCount(Object parent) {
128:                return ((FileSystemNode) parent).getChildCount();
129:            }
130:
131:            /*=======================================================================*/
132:            /**
133:             * A cache of file-system nodes, to minimize repeated resolve() calls...
134:             * probably this caching should actually go on in AbstractFileSystem
135:             * layer.
136:             */
137:            private Hashtable fsNodeTable = new Hashtable();
138:
139:            /**
140:             * Cached lookup of FileSystemNode
141:             */
142:            public FileSystemNode getFileSystemNode(String path) {
143:                return getFileSystemNode(pathToFile(path));
144:            }
145:
146:            /**
147:             * Cached lookup of FileSystemNode
148:             */
149:            public FileSystemNode getFileSystemNode(AbstractFile file) {
150:                FileSystemNode fsn = (FileSystemNode) (fsNodeTable.get(file));
151:
152:                if (fsn == null) {
153:                    fsn = new FileSystemNode(this , file);
154:                    fsNodeTable.put(file, fsn);
155:                }
156:
157:                return fsn;
158:            }
159:
160:            /**
161:             * Given a collection of files, apply this FileSystemTreeModel-s filter,
162:             * and return the result as an array of FileSystemNode
163:             */
164:            private FileSystemNode[] getFilteredChildren(Collection files) {
165:                LinkedList fileList = new LinkedList();
166:
167:                for (Iterator itr = files.iterator(); itr.hasNext();) {
168:                    AbstractFile file = (AbstractFile) (itr.next());
169:                    if (filter.accept(file))
170:                        fileList.add(file);
171:                }
172:
173:                java.util.Collections.sort(fileList, new Comparator() {
174:
175:                    // the following sillyness of caching f.isDirectory()
176:                    // is because File#isDirectory() seems to be a very
177:                    // expensive operation on windoze... I guess redmond
178:                    // never heard of caching... duh!
179:                    private Map isDirectoryCache = new WeakHashMap();
180:
181:                    private boolean isDirectory(AbstractFile f) {
182:                        Boolean b = (Boolean) (isDirectoryCache.get(f));
183:                        if (b == null) {
184:                            b = Boolean.valueOf(f.isDirectory());
185:                            isDirectoryCache.put(f, b);
186:                        }
187:                        return b.booleanValue();
188:                    }
189:
190:                    public int compare(Object obj1, Object obj2) {
191:                        AbstractFile f1 = (AbstractFile) obj1;
192:                        AbstractFile f2 = (AbstractFile) obj2;
193:                        if (isDirectory(f1) != isDirectory(f2))
194:                            return f1.isDirectory() ? -1 : 1;
195:                        return f1.getName().compareToIgnoreCase(f2.getName());
196:                    }
197:
198:                });
199:
200:                FileSystemNode[] children = new FileSystemNode[fileList.size()];
201:
202:                int i = 0;
203:                for (Iterator itr = fileList.iterator(); itr.hasNext();)
204:                    children[i++] = getFileSystemNode((AbstractFile) (itr
205:                            .next()));
206:
207:                return children;
208:            }
209:
210:            /**
211:             * Filter which controls what files are visible in the model.
212:             */
213:            public interface FileSystemTreeFilter {
214:                /**
215:                 * Test whether the file is accepted by this filter.
216:                 */
217:                public boolean accept(AbstractFile file);
218:            }
219:
220:            private static final FileSystemNode[] INITIAL_CHILDREN = new FileSystemNode[0];
221:            private static final int NODES_CHANGED = 0;
222:            private static final int NODES_REMOVED = 1;
223:            private static final int NODES_INSERTED = 2;
224:            private static final int STRUCTURE_CHANGED = 3;
225:
226:            /**
227:             * Because the AbstractFileSystem.children() can be expensive, we cache
228:             * the results in the FileSystemNode.
229:             */
230:            public static class FileSystemNode {
231:                private AbstractFile file;
232:                private FileSystemNode[] children = INITIAL_CHILDREN;
233:                private FileSystemTreeModel outer;
234:                private long lastModified = -1;
235:                private long length = -1;
236:
237:                FileSystemNode(FileSystemTreeModel outer, AbstractFile file) {
238:                    this .outer = outer;
239:                    this .file = file;
240:                }
241:
242:                /**
243:                 * Access this node's parent.
244:                 */
245:                public FileSystemNode getParent() {
246:                    String path = file.getPath();
247:                    if ((path.indexOf(outer.rootPath) == 0)
248:                            && (path.length() > outer.rootPath.length()))
249:                        return outer.getFileSystemNode(path.substring(0, path
250:                                .lastIndexOf('/')));
251:                    return null;
252:                }
253:
254:                /**
255:                 * Get the file represented by this node.
256:                 */
257:                public AbstractFile getFile() {
258:                    return file;
259:                }
260:
261:                /**
262:                 * Return <code>true</code> if we are still waiting on the worker thread to
263:                 * know the children of this node, or any parent of this node.
264:                 */
265:                public boolean pending() {
266:                    if (children == INITIAL_CHILDREN)
267:                        return true;
268:
269:                    FileSystemNode fsn = getParent();
270:                    if (fsn != null)
271:                        return fsn.pending();
272:
273:                    return false;
274:                }
275:
276:                /**
277:                 * Get the length of the file.  If this info is needed from the AWT thread,
278:                 * this should be called instead of <code>getFile().length()</code>, to
279:                 * avoid potentially expensive filesystem access from the AWT thread.  If
280:                 * this method returns <code>-1</code>, that means that the length is not
281:                 * known yet.  In this case, a tree event will be fired once it is known.
282:                 */
283:                public long length() {
284:                    if (length == -1)
285:                        asyncLoadAttributes();
286:                    return length;
287:                }
288:
289:                /**
290:                 * Get the modification time of the file.  If this info is needed from the 
291:                 * AWT thread, this should be called instead of <code>getFile().lastModified()</code>, 
292:                 * to avoid potentially expensive filesystem access from the AWT thread.  If
293:                 * this method returns <code>-1</code>, that means that the modification 
294:                 * time is not known yet.  In this case, a tree event will be fired once it
295:                 * is known.
296:                 */
297:                public long lastModified() {
298:                    if (lastModified == -1)
299:                        asyncLoadAttributes();
300:                    return lastModified;
301:                }
302:
303:                private void asyncLoadAttributes() {
304:                    WorkerThread.invokeLater(new Runnable() {
305:                        public void run() {
306:                            fileChanged(null);
307:                        }
308:                    });
309:                }
310:
311:                /**
312:                 * Get the number of children
313:                 */
314:                public int getChildCount() {
315:                    return children.length;
316:                }
317:
318:                /**
319:                 * Get the child at the specified index (0..getChildCount()-1)
320:                 * @param idx    the child index
321:                 * @return the child node
322:                 */
323:                public FileSystemNode getChild(int idx) {
324:                    return children[idx];
325:                }
326:
327:                private void updateTree(final int type,
328:                        final FileSystemNode[] newChildren,
329:                        final int[] childIdxs, final Object[] children) {
330:                    final Object[] path = outer.getTreePath(file).getPath();
331:                    javax.swing.SwingUtilities.invokeLater(new Runnable() {
332:
333:                        public void run() {
334:                            if (newChildren != null) {
335:                                boolean wasPending = pending();
336:                                FileSystemNode.this .children = newChildren;
337:                                if (wasPending)
338:                                    outer.handlePendingExpansions();
339:                            }
340:
341:                            switch (type) {
342:                            case NODES_CHANGED:
343:                                outer.fireTreeNodesChanged(FileSystemNode.this ,
344:                                        path, childIdxs, children);
345:                                break;
346:                            case NODES_REMOVED:
347:                                outer.fireTreeNodesRemoved(FileSystemNode.this ,
348:                                        path, childIdxs, children);
349:                                break;
350:                            case NODES_INSERTED:
351:                                outer.fireTreeNodesInserted(
352:                                        FileSystemNode.this , path, childIdxs,
353:                                        children);
354:                                break;
355:                            case STRUCTURE_CHANGED:
356:                                outer.fireTreeStructureChanged(
357:                                        FileSystemNode.this , path, childIdxs,
358:                                        children);
359:                                break;
360:                            default:
361:                                throw new ProgrammingErrorException(
362:                                        "bad type: " + type);
363:                            }
364:                        }
365:                    });
366:                }
367:
368:                /**
369:                 * Called from a worker thread, when it is detected that the file associated 
370:                 * with this node has changed.
371:                 * @param files   the children of this directory, or <code>null</code.
372:                 */
373:                void fileChanged(Collection files) {
374:                    boolean changedAttr = false;
375:                    long newLastModified = file.lastModified();
376:                    long newLength = file.length();
377:
378:                    if ((lastModified != newLastModified)
379:                            || (length != newLength)) {
380:                        lastModified = newLastModified;
381:                        length = newLength;
382:                        changedAttr = true;
383:                    }
384:
385:                    if (files != null) {
386:                        FileSystemNode[] oldChildren = children;
387:                        FileSystemNode[] newChildren = outer
388:                                .getFilteredChildren(files);
389:
390:                        if ((oldChildren != null) && (newChildren != null)) {
391:                            Collection added = notIn(newChildren, oldChildren);
392:                            Collection removed = notIn(oldChildren, newChildren);
393:
394:                            /* *********************************************************************
395:                             * NOTE:  in the case of multiple added/removed, this doesn't behave
396:                             *        quite right... to avoid making the tree (view) go haywire,
397:                             *        for now multiple adds/removes will fireTreeNodesChanged
398:                             *        instead of fireNodesInserted/fireNodesRemoved
399:                             */
400:                            if ((added.size() > 1) || (removed.size() > 1)) {
401:                                updateTree(STRUCTURE_CHANGED, newChildren,
402:                                        null, null);
403:                                return;
404:                            }
405:
406:                            // handle added children:
407:                            if (added.size() > 0) {
408:                                int[] childIdxs = new int[added.size()];
409:                                Object[] children = new Object[added.size()];
410:                                int i = 0;
411:
412:                                for (Iterator itr = added.iterator(); itr
413:                                        .hasNext();) {
414:                                    FileSystemNode n = (FileSystemNode) (itr
415:                                            .next());
416:                                    childIdxs[i] = findIdx(newChildren, n);
417:                                    children[i] = n;
418:                                }
419:
420:                                updateTree(NODES_INSERTED, newChildren,
421:                                        childIdxs, children);
422:                            }
423:
424:                            // handle removed children:
425:                            if (removed.size() > 0) {
426:                                int[] childIdxs = new int[removed.size()];
427:                                Object[] children = new Object[removed.size()];
428:                                int i = 0;
429:
430:                                for (Iterator itr = removed.iterator(); itr
431:                                        .hasNext();) {
432:                                    FileSystemNode n = (FileSystemNode) (itr
433:                                            .next());
434:                                    childIdxs[i] = findIdx(oldChildren, n);
435:                                    children[i] = n;
436:                                }
437:
438:                                updateTree(NODES_REMOVED, newChildren,
439:                                        childIdxs, children);
440:                            }
441:                        } else {
442:                            updateTree(STRUCTURE_CHANGED, newChildren, null,
443:                                    null);
444:                            return;
445:                        }
446:                    }
447:
448:                    if (changedAttr)
449:                        updateTree(NODES_CHANGED, null, null, null);
450:                }
451:
452:                /**
453:                 */
454:                public int hashCode() {
455:                    return getFile().hashCode();
456:                }
457:
458:                /**
459:                 * Two nodes are determined to be equal if they represent the
460:                 * same file.
461:                 */
462:                public boolean equals(Object obj) {
463:                    return (obj instanceof  FileSystemNode)
464:                            && getFile().equals(
465:                                    ((FileSystemNode) obj).getFile());
466:                }
467:
468:                /**
469:                 * Cast to string, which is useful if a default-cell-renderer is used.
470:                 */
471:                public String toString() {
472:                    return getFile().getName();
473:                }
474:            }
475:
476:            /**
477:             * Table that maps file extension to icon.
478:             */
479:            private static Hashtable iconTable = new Hashtable();
480:
481:            /**
482:             * Maps file extension to info string describing file type.
483:             */
484:            private static Hashtable infoTable = new Hashtable();
485:
486:            /**
487:             * Set the icon to draw, and the description of the type, for files of 
488:             * the specified type (as determined by file extension).  If an icon or
489:             * description has already been registered for this file type, the new 
490:             * icon will replace the existing one.
491:             * 
492:             * @param extension    the file type extension
493:             * @param icon         the icon to display for this file type, or <code>null</code>
494:             * @param info         an info string describing the file type, or <code>null</code>
495:             */
496:            public static void setFileInfo(String extension,
497:                    javax.swing.Icon icon, String info) {
498:                if (icon != null)
499:                    iconTable.put(extension, icon);
500:
501:                if (info != null)
502:                    infoTable.put(extension, info);
503:            }
504:
505:            /**
506:             * Get the icon associated with the specified file type.
507:             * 
508:             * @param extension    the file type extension
509:             * @return the icon to displayed for this file type, or <code>null</code> if none
510:             */
511:            public static javax.swing.Icon getFileIcon(String extension) {
512:                return (javax.swing.Icon) (iconTable.get(extension));
513:            }
514:
515:            public static String getFileInfo(String extension) {
516:                return (String) (infoTable.get(extension));
517:            }
518:
519:            /**
520:             * A {@link javax.swing.tree.TreeCellRenderer} that uses the icons set
521:             * with {@link #setFileIcon}.  To make the correct icons be displayed,
522:             * when you create a new tree, you need to set it's renderer to be an
523:             * instance of this class (or a subclass).
524:             * <pre>
525:             *   JTree tree = new JTree( new FileSystemTreeModel( "/", null ) )
526:             *   tree.setCellRenderer( new FileSystemTreeModel.FileSystemTreeCellRenderer() );
527:             * </pre>
528:             */
529:            public static class FileSystemTreeCellRenderer extends
530:                    javax.swing.tree.DefaultTreeCellRenderer {
531:                public java.awt.Component getTreeCellRendererComponent(
532:                        javax.swing.JTree tree, Object value, boolean selected,
533:                        boolean expanded, boolean leaf, int row,
534:                        boolean hasFocus) {
535:                    super .getTreeCellRendererComponent(tree, value, selected,
536:                            expanded, leaf, row, hasFocus);
537:
538:                    AbstractFile file = ((FileSystemNode) value).getFile();
539:                    javax.swing.Icon icon = getFileIcon(getExtension(file));
540:                    String info = getFileInfo(getExtension(file));
541:
542:                    if (icon != null)
543:                        setIcon(icon);
544:
545:                    if (info != null)
546:                        setToolTipText(info);
547:
548:                    return this ;
549:                }
550:            }
551:
552:            private static String getExtension(AbstractFile file) {
553:                String type = null;
554:
555:                if (file instanceof  AbstractFileSystem.MountPointFile)
556:                    type = ((AbstractFileSystem.MountPointFile) file)
557:                            .getMountType();
558:
559:                if ((type == null) && file.isDirectory())
560:                    type = "/dir/";
561:
562:                if (type == null)
563:                    type = file.getExtension();
564:
565:                return type;
566:            }
567:
568:            /**
569:             * contains FileSystemNode -> path mappings for nodes that were expanded while
570:             * still waiting for structure info (ie. children) from worker thread.
571:             */
572:            private static Map pendingExpansionMap = new WeakHashMap();
573:
574:            private void handlePendingExpansions() {
575:                restart: for (Iterator itr = pendingExpansionMap.keySet()
576:                        .iterator(); itr.hasNext();) {
577:                    FileSystemNode fsn = (FileSystemNode) (itr.next());
578:                    if ((fsNodeTable.get(fsn.getFile()) == fsn)
579:                            && !fsn.pending()) {
580:                        Reference ref = (Reference) (pendingExpansionMap
581:                                .get(fsn));
582:                        if (ref != null) {
583:                            javax.swing.JTree tree = (javax.swing.JTree) (ref
584:                                    .get());
585:                            if (tree != null) {
586:                                TreePath path = getTreePath(fsn.getFile());
587:                                tree.expandPath(path);
588:                                tree.scrollPathToVisible(path);
589:                                pendingExpansionMap.remove(fsn);
590:                                break restart;
591:                            }
592:                        }
593:                    }
594:                }
595:            }
596:
597:            /**
598:             * A {@link javax.swing.event.TreeExpansionListener} which helps the
599:             * <code>TreeModel</code> track what files are visible, so that the
600:             * model can make intelligent choices about what files to watch (poll)
601:             * for changes.  To ensure that the model notices changes to the file
602:             * system, and therefore the tree gets the appropriate update events
603:             * when the filesystem changes, you need to add this expansion listener,
604:             * for example:
605:             * <pre>
606:             *   JTree tree = new JTree( new FileSystemTreeModel( "/", null ) )
607:             *   tree.addTreeExpansionListener( new FileSystemTreeModel.FileSystemTreeExpansionListener() );
608:             * </pre>
609:             */
610:            public static class FileSystemTreeExpansionListener implements 
611:                    javax.swing.event.TreeExpansionListener {
612:                /**
613:                 * Called whenever an item in the tree has been expanded.
614:                 */
615:                public void treeExpanded(
616:                        javax.swing.event.TreeExpansionEvent evt) {
617:                    FileSystemNode fsn = (FileSystemNode) (evt.getPath()
618:                            .getLastPathComponent());
619:
620:                    if (fsn.pending())
621:                        pendingExpansionMap.put(fsn, new WeakReference(evt
622:                                .getSource()));
623:
624:                    Object[] nodes = evt.getPath().getPath();
625:                    for (int i = 0; i < nodes.length; i++)
626:                        startWatchingNode((FileSystemNode) (nodes[i]));
627:                }
628:
629:                /**
630:                 * Called whenever an item in the tree has been collapsed.
631:                 */
632:                public void treeCollapsed(
633:                        javax.swing.event.TreeExpansionEvent evt) {
634:                    // just in case:
635:                    pendingExpansionMap.remove(evt.getPath()
636:                            .getLastPathComponent());
637:                    Object[] nodes = evt.getPath().getPath();
638:                    for (int i = 0; i < nodes.length; i++)
639:                        stopWatchingNode((FileSystemNode) (nodes[i]));
640:                }
641:            }
642:
643:            /* How we keep track of what to watch:
644:             *   In order to keep track of the files that need watching in a way that
645:             *   is efficient when it comes to figure out what files to check each time
646:             *   we poll, and at the same time not prevent the file and FileSystemNodes
647:             *   from being GC'd, we keep a weak-hash-map to map file to info about
648:             *   the watched file, which includes the last-modified time and a list of
649:             *   weak-references to the FileSystemNodes that need notification of the
650:             *   change.  Since the FileSystemNode holds a reference to the file, the
651:             *   file won't be GC'd and removed from the weak-hash-map until all the
652:             *   FileSystemNodes are GC'd, so there is no problem with WatchedDirectory
653:             *   being removed from the weak-hash-map while there is still a tree
654:             *   interested in updates to that file.
655:             *   
656:             *   All synchronization is handled at the "topmost" function in the API,
657:             *   ie startWatchingNode, stopWatchingNode, and scanFiles
658:             */
659:
660:            /**
661:             * Tracks the pertainent info associated with a watched file, such as the
662:             * last-modified time from the last time we scanned this file, and the
663:             * list of nodes to notify of the change.
664:             */
665:            private static class WatchedDirectory {
666:                private AbstractFile file;
667:                private Collection children; // collection of AbstractFile
668:                private long lastModified;
669:                private Set nodeReferenceSet = new HashSet();
670:                private FileSystemNodeReference[] nodeReferences = null;
671:
672:                WatchedDirectory(AbstractFile file) {
673:                    this .file = file;
674:                    this .lastModified = 0;
675:                }
676:
677:                AbstractFile getFile() {
678:                    return file;
679:                }
680:
681:                /**
682:                 * Add a node to the list of nodes that will be notified (via fileChanged()
683:                 * if the file changes.  This will not hold a hard reference to the node and
684:                 * will not prevent the node from being GCd.
685:                 */
686:                synchronized void addFileSystemNode(final FileSystemNode node) {
687:                    nodeReferenceSet.add(new FileSystemNodeReference(node));
688:                    nodeReferences = null;
689:                    WorkerThread.invokeLater(new Runnable() {
690:                        public void run() {
691:                            if (children != null)
692:                                node.fileChanged(children);
693:                            else
694:                                scan();
695:                        }
696:                    });
697:                }
698:
699:                /**
700:                 * Remove a node from the list of nodes to notify when file changes.
701:                 */
702:                synchronized void removeFileSystemNode(FileSystemNode node) {
703:                    nodeReferenceSet.remove(new FileSystemNodeReference(node));
704:                    nodeReferences = null;
705:                }
706:
707:                private synchronized FileSystemNodeReference[] getNodeReferences() {
708:                    if (nodeReferences == null) {
709:                        nodeReferences = (FileSystemNodeReference[]) (nodeReferenceSet
710:                                .toArray(new FileSystemNodeReference[nodeReferenceSet
711:                                        .size()]));
712:                    }
713:                    return nodeReferences;
714:                }
715:
716:                /**
717:                 * Scan file, notify nodes if file has changed.  Returns <code>true</code>
718:                 * if this node should continue to be scanned, else <code>false</code> if
719:                 * there are no more nodes that haven't been GC'd that would need to be
720:                 * notified if the file has changed.
721:                 */
722:                boolean scan() {
723:                    // XXX prune GC'd entries from the list... perhaps this should be
724:                    // done with a ReferenceQueue and a seperate thread?
725:                    for (Iterator itr = nodeReferenceSet.iterator(); itr
726:                            .hasNext();)
727:                        if (((FileSystemNodeReference) (itr.next()))
728:                                .getFileSystemNode() == null)
729:                            itr.remove();
730:
731:                    // first check for the case that there are no more nodes to watch,
732:                    // and if that is the case remove ourself from the table of files
733:                    // to check.  This has to be done regardless of whether the file
734:                    // changes, so we are not dependent on a file change to stop the
735:                    // timer:
736:                    if (nodeReferenceSet.size() == 0)
737:                        return false;
738:
739:                    if (lastModified != file.lastModified()) {
740:                        lastModified = file.lastModified();
741:
742:                        FileSystemNodeReference[] nodeReferences = getNodeReferences();
743:                        try {
744:                            children = AbstractFileSystem.children(file);
745:                        } catch (java.io.IOException e) {
746:                            lastModified = 0;
747:                            e.printStackTrace(); // ????
748:                            return false;
749:                        }
750:
751:                        // notify watched nodes of the change:
752:                        for (int i = 0; i < nodeReferences.length; i++) {
753:                            FileSystemNode node = nodeReferences[i]
754:                                    .getFileSystemNode();
755:                            if (node != null)
756:                                node.fileChanged(children);
757:                        }
758:                    }
759:
760:                    return true;
761:                }
762:            }
763:
764:            /**
765:             * A weak-reference used to track the watched FileSystemNode without preventing
766:             * it from being GC'd
767:             */
768:            private static class FileSystemNodeReference extends WeakReference {
769:                private int hashCode;
770:
771:                FileSystemNodeReference(FileSystemNode node) {
772:                    super (node);
773:                    hashCode = node.hashCode();
774:                }
775:
776:                public int hashCode() {
777:                    return hashCode;
778:                }
779:
780:                public FileSystemNode getFileSystemNode() {
781:                    return (FileSystemNode) get();
782:                }
783:
784:                public boolean equals(Object obj) {
785:                    if (obj instanceof  FileSystemNodeReference) {
786:                        FileSystemNode this Node = getFileSystemNode();
787:                        FileSystemNode otherNode = ((FileSystemNodeReference) obj)
788:                                .getFileSystemNode();
789:
790:                        if ((this Node != null) && (otherNode != null))
791:                            return this Node == otherNode; // XXX: uses == because .equals() compares node's files
792:                    }
793:
794:                    return false;
795:                }
796:            }
797:
798:            /**
799:             * Track the files to poll.
800:             */
801:            private static Map fileToInfoMap = new TreeMap(new Comparator() {
802:
803:                public int compare(Object obj1, Object obj2) {
804:                    String p1 = ((AbstractFile) obj1).getPath();
805:                    String p2 = ((AbstractFile) obj2).getPath();
806:                    int result = p1.length() - p2.length();
807:                    if (result == 0)
808:                        result = p1.compareTo(p2);
809:                    return result;
810:                }
811:
812:            });
813:
814:            /**
815:             * Timer that is created to periodically scan watched files.
816:             */
817:            private static Runnable fileWatcherRunnable = null;
818:
819:            /**
820:             * Start monitoring the file associated with the specified node.  If the
821:             * file is determined to have changed, then the node's fileChanged()
822:             * method is called.
823:             */
824:            private static synchronized void startWatchingNode(
825:                    FileSystemNode node) {
826:                AbstractFile file = node.getFile();
827:                WatchedDirectory wd = (WatchedDirectory) (fileToInfoMap
828:                        .get(file));
829:
830:                if (wd == null) {
831:                    wd = new WatchedDirectory(file);
832:                    fileToInfoMap.put(file, wd);
833:                }
834:
835:                wd.addFileSystemNode(node);
836:
837:                if (fileWatcherRunnable == null) {
838:                    fileWatcherRunnable = new Runnable() {
839:                        public void run() {
840:                            scanFiles();
841:                        }
842:                    };
843:                    WorkerThread.addRunnable(fileWatcherRunnable, 1000);
844:                }
845:            }
846:
847:            /**
848:             * Stop monitoring the file associated with the node on behalf of the node.
849:             * If no other nodes are associated with the same file, the file will no
850:             * longer be monitored.
851:             */
852:            private static synchronized void stopWatchingNode(
853:                    FileSystemNode node) {
854:                AbstractFile file = node.getFile();
855:                WatchedDirectory wd = (WatchedDirectory) (fileToInfoMap
856:                        .get(file));
857:
858:                if (wd == null)
859:                    return;
860:
861:                wd.removeFileSystemNode(node);
862:            }
863:
864:            /**
865:             * Scan the watched files.  This is called periodically to scan all watched
866:             * files.
867:             */
868:            private static void scanFiles() {
869:                WatchedDirectory[] wds;
870:
871:                synchronized (FileSystemTreeModel.class) {
872:                    wds = (WatchedDirectory[]) (fileToInfoMap.values()
873:                            .toArray(new WatchedDirectory[fileToInfoMap.size()]));
874:                }
875:
876:                for (int i = 0; i < wds.length; i++)
877:                    if (wds[i].scan())
878:                        wds[i] = null; // don't need to remove this node
879:
880:                synchronized (FileSystemTreeModel.class) {
881:                    for (int i = 0; i < wds.length; i++)
882:                        if (wds[i] != null)
883:                            fileToInfoMap.remove(wds[i].getFile());
884:                }
885:            }
886:
887:            //////////////////////////////////////////////////////////////////////////////
888:            // some utilities:
889:
890:            /**
891:             * find index of item within an array
892:             */
893:            private static final int findIdx(Object[] arr, Object item) {
894:                for (int i = 0; i < arr.length; i++)
895:                    if (item.equals(arr[i]))
896:                        return i;
897:                return -1;
898:            }
899:
900:            /**
901:             * return a set of nodes that are in a, but not in b.. returned as an array
902:             * of file objects (or <code>null</code> if none)
903:             */
904:            private static final Collection notIn(FileSystemNode[] a,
905:                    FileSystemNode[] b) {
906:                Collection v = new Vector();
907:
908:                for (int i = 0; i < a.length; i++)
909:                    v.add(a[i]);
910:
911:                for (int i = 0; i < b.length; i++)
912:                    v.remove(b[i]);
913:
914:                return v;
915:            }
916:
917:            /** 
918:             * Utility to go from AbstractFile to TreePath
919:             */
920:            public TreePath getTreePath(AbstractFile file) {
921:                LinkedList nodeList = new LinkedList();
922:
923:                FileSystemNode node = getFileSystemNode(file);
924:                while (node != null) {
925:                    nodeList.addFirst(node);
926:                    node = node.getParent();
927:                }
928:
929:                return new TreePath(nodeList.toArray());
930:            }
931:
932:            //  private void pathToNodeList( LinkedList nodeList, String path )
933:            //  {
934:            //    FileSystemNode node = getFileSystemNode( path.equals("") ? "/" : path );
935:            //    
936:            //    if( (path.indexOf(rootPath) == 0) && (path.length() > rootPath.length()) )
937:            //      pathToNodeList( nodeList, path.substring( 0, path.lastIndexOf('/') ) );
938:            //    
939:            //    nodeList.add(node);
940:            //  }
941:
942:            /**
943:             * utility to go from path to file... catches and deals with exceptions,
944:             * but could return null.
945:             */
946:            protected static final AbstractFile pathToFile(String path) {
947:                try {
948:                    return AbstractFileSystem.resolve(path);
949:                } catch (java.io.IOException e) {
950:                    e.printStackTrace();
951:                    return null;
952:                }
953:            }
954:            //////////////////////////////////////////////////////////////////////////////
955:        }
956:
957:        /*
958:         *   Local Variables:
959:         *   tab-width: 2
960:         *   indent-tabs-mode: nil
961:         *   mode: java
962:         *   c-indentation-style: java
963:         *   c-basic-offset: 2
964:         *   eval: (c-set-offset 'substatement-open '0)
965:         *   eval: (c-set-offset 'case-label '+)
966:         *   eval: (c-set-offset 'inclass '+)
967:         *   eval: (c-set-offset 'inline-open '0)
968:         *   End:
969:         */
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.