package org.greenstone.gatherer.file; import java.io.File; import java.util.*; import javax.swing.filechooser.*; import javax.swing.tree.*; import org.greenstone.gatherer.file.FileFilter; import org.greenstone.gatherer.file.FileSystemModel; import org.greenstone.gatherer.util.ArrayTools; public class FileNode implements MutableTreeNode { static final private int FALSE = 0; static final private int TRUE = 1; static final private int UNKNOWN = 2; private ArrayList children; private boolean children_readonly = true; private boolean readonly = true; private File file; private FileSystemModel model; private int allows_children = UNKNOWN; private MutableTreeNode parent; private String title; public FileNode(File file) { ///ystem.err.println("New FileNode(" + file.getAbsolutePath() + ")"); this.file = file; } public FileNode(File file, boolean readonly) { this(file); this.children_readonly = readonly; this.readonly = readonly; } public FileNode(File file, FileSystemModel model) { this(file); this.model = model; } public FileNode(File file, FileSystemModel model, boolean readonly) { this(file, readonly); this.model = model; } public FileNode(File file, FileSystemModel model, String title) { this(file, model); this.title = title; } public FileNode(File file, String title) { this(file); this.title = title; } public FileNode(File file, String title, boolean readonly) { this(file, readonly); this.title = title; } public FileNode(File file, FileSystemModel model, String title, boolean readonly) { this(file, model, readonly); this.title = title; } /** The special 'dummy' root node, that is not based on a particular file, but instead holds several special directory mappings. */ public FileNode(String title) { this.children = new ArrayList(); this.title = title; } /** Returns the children of the receiver as an Enumeration. */ public Enumeration children() { return new FileEnumeration(); } /** Compare two filenodes for equality. */ public boolean equals(FileNode node) { boolean result = false; if(node != null) { if(file != null) { result = (file.equals(node.getFile())); } else { result = toString().equals(node.toString()); } } return result; } /** Returns true if the receiver allows children. We have to cache the result of this call to prevent unceasing missing disk messages being thrown if the removable media was, um, removed after directory mapped. */ public boolean getAllowsChildren() { if(readonly) { if(allows_children == UNKNOWN) { // If the file is non-null but doesn't exist (as is the case for removable media), return true anyway. if(file != null) { if(isFileSystemRoot()) { allows_children = TRUE; } else if(file.exists() && file.isDirectory()) { allows_children = TRUE; } // Any mapped directories always allow children. else if(getParent() != null && getParent().getParent() == null) { allows_children = TRUE; } else { allows_children = FALSE; } } // Allows children is always true for dummy nodes. else { allows_children = TRUE; } } return (allows_children == TRUE); } else { return (file == null || file.isDirectory()); } } /** Returns the child TreeNode at index childIndex. */ public TreeNode getChildAt(int index) { TreeNode result = null; map(); if(0 <= index && index < children.size()) { result = (TreeNode) children.get(index); } else { result = new DefaultMutableTreeNode("Error"); } return result; } /** Returns the number of children TreeNodes the receiver contains. */ public int getChildCount() { int size = 0; // We don't automatically map if this is a system root, or we risk the 50,000 Disk not found error messages of death. if(isFileSystemRoot()) { size = 1; // Size is always non-zero for a system root } else { map(); } ///ystem.err.println(this + ".getChildCount() = " + children.size()); if(children != null) { size = children.size(); } return size; } public File getFile() { return file; } /** Returns the index of node in the receivers children. */ public int getIndex(TreeNode node) { map(); return children.indexOf(node); } /** Returns the parent TreeNode of the receiver. */ public TreeNode getParent() { return parent; } /** Retrieves the tree path from the root node to this node. */ public TreeNode[] getPath() { int count = 0; TreeNode current = this; while(current != null) { count++; current = current.getParent(); } TreeNode[] path = new TreeNode[count]; current = this; while(current != null) { path[count - 1] = current; count--; current = current.getParent(); } return path; } public void insert(MutableTreeNode child) { insert(child, children.size()); } /** Adds child to the receiver at index. */ public void insert(MutableTreeNode child, int index) { ///ystem.err.println("Insert " + child + " in " + this + " at index " + index); //map(); try { children.add(index, child); // Set parent and model. FileNode new_child = (FileNode) child; new_child.setModel(model); new_child.setParent(this); new_child.setReadOnly(readonly); } catch(Exception error) { error.printStackTrace(); } } /** Returns true if the receiver is a leaf. */ public boolean isLeaf() { return !getAllowsChildren(); } public boolean isReadOnly() { return readonly; } public boolean isFileSystemRoot() { boolean result = false; if(file != null) { result = FileSystemView.getFileSystemView().isFileSystemRoot(file); } return result; } public void map() { // Only map if there are no children. if(children == null && file != null && getAllowsChildren()) { ///ystem.err.println("Map: " + this); children = new ArrayList(); File[] files = file.listFiles(); if(files != null && files.length > 0) { ArrayTools tools = new ArrayTools(); // Apply the filters set in the model. FileFilter[] filters = model.getFilters(); for(int i = 0; filters != null && i < filters.length; i++) { files = tools.filter(files, filters[i].filter, filters[i].exclude); } // Sort the remaining files. tools.sort(files, true); // Now add them to children. for(int i = 0; i < files.length; i++) { FileNode child = new FileNode(files[i], model, children_readonly); child.setParent(this); children.add(child); } } model.nodeStructureChanged(this); } else { ///ystem.err.println("Can't map: " + this + ". No file or doesn't allow children."); } } /** Removes the child at index from the receiver. */ public void remove(int index) { if(0 <= index && index < children.size()) { children.remove(index); } } /** Removes node from the receiver. */ public void remove(MutableTreeNode node){ int index = getIndex(node); if(index != -1) { children.remove(index); } } /** Removes the receiver from its parent. */ public void removeFromParent() { parent.remove(this); parent = null; } public void setChildrenReadOnly(boolean children_readonly) { this.children_readonly = children_readonly; } public void setFile(File file) { this.file = file; } public void setModel(FileSystemModel model) { this.model = model; } public void setParent(MutableTreeNode parent) { this.parent = parent; } public void setReadOnly(boolean readonly) { this.readonly = readonly; } /** Resets the user object of the receiver to object. */ public void setUserObject(Object object) { try { file = (File) object; title = null; } catch(Exception error) { error.printStackTrace(); } } public String toString() { if(title == null) { if(isFileSystemRoot()) { title = file.getAbsolutePath(); } else { title = file.getName(); } } return title; } /** Unmap this nodes children. */ public void unmap() { // You cannot unmap nodes that have no file basis. if(file != null) { ///ystem.err.println("Unmap: " + this); children = null; } else { ///ystem.err.println("No file for " + this + " - can't unmap."); } } private class FileEnumeration implements Enumeration { private int index = 0; /** Tests if this enumeration contains more elements. */ public boolean hasMoreElements() { return (index < children.size()); } /** Returns the next element of this enumeration if this enumeration object has at least one more element to provide. */ public Object nextElement() { Object result = null; if(index < children.size()) { result = children.get(index); index++; } return result; } } }