package org.greenstone.gatherer.file; import java.io.*; import java.util.ArrayList; import java.util.Enumeration; import javax.swing.*; import javax.swing.filechooser.*; import javax.swing.tree.*; import org.greenstone.gatherer.DebugStream; import org.greenstone.gatherer.metadata.FilenameEncoding; import org.greenstone.gatherer.util.ArrayTools; public abstract class FileNode implements MutableTreeNode { protected boolean allows_children = true; protected ArrayList child_nodes = null; protected ArrayList child_nodes_unfiltered = null; protected File file = null; protected FileSystemModel model = null; protected MutableTreeNode parent = null; protected String urlEncodedFileName = ""; protected String urlEncodedFilePath = ""; protected String filenameEncoding = ""; /** The string that is displayed as the filename. Attempts to be in the correct encoding. */ protected String displayFileName = null; public FileNode(File file) { this.file = file; if (file != null) { // Files cannot have children if(file.isFile()) { // Cache this result to prevent unceasing missing disk messages being thrown if the // removable media was, um, removed after directory mapped this.allows_children = false; } filenameEncoding = ""; urlEncodedFilePath = FilenameEncoding.calcURLEncodedFilePath(file); urlEncodedFileName = FilenameEncoding.calcURLEncodedFileName(urlEncodedFilePath); // work out the display string (extra special processing for CollectionTreeNodes) displayFileName = calcDisplayString(); } } public String getURLEncodedFileName() { return urlEncodedFileName; } public String getURLEncodedFilePath() { return urlEncodedFilePath; } public String getFilenameEncoding() { return filenameEncoding; } /** This method returns a string representation of the filenodes in the tree, * that can then be displayed in the tree. Overridden in subclass CollectionTreeNode. * Turn FilenameEncoding.DEBUGGING on to see URLEncoded filenames. */ protected String calcDisplayString() { if(FilenameEncoding.DEBUGGING) { return getURLEncodedFileName(); } else { return file.getName(); } } /** Returns the children of the node as an Enumeration. */ public Enumeration children() { return new FileEnumeration(); } /** Returns true if the receiver allows children. */ public boolean getAllowsChildren() { return allows_children; } /** Returns the child TreeNode at index childIndex. */ public TreeNode getChildAt(int index) { return (TreeNode) child_nodes.get(index); } /** Returns the number of children TreeNodes the receiver contains. */ public int getChildCount() { map(); // Use the number of (filtered) child nodes if (child_nodes != null) { return child_nodes.size(); } return 0; } /** Returns the index of node in the receivers children. */ public int getIndex(TreeNode node) { if (child_nodes != null) { return child_nodes.indexOf(node); } return -1; } /** Returns the parent TreeNode of the receiver. */ public TreeNode getParent() { return parent; } /** Adds child to the receiver at index. */ public void insert(MutableTreeNode child, int index) { DebugStream.println("Insert " + child + " in " + this + " at index " + index + " [Model: " + model + "]"); if (child == null) { return; } try { FileNode child_node = (FileNode) child; child_nodes.add(index, child_node); child_node.model = model; child_node.parent = this; } catch (Exception exception) { DebugStream.printStackTrace(exception); } } /** Returns true if the receiver is a leaf. */ public boolean isLeaf() { return (allows_children == false); } /** Removes the child at index from the receiver. */ public void remove(int index) { if (index >= 0 && index < child_nodes.size()) { child_nodes.remove(index); } } /** Removes node from the receiver. */ public void remove(MutableTreeNode node) { remove(getIndex(node)); } /** Removes the receiver from its parent. */ public void removeFromParent() { parent.remove(this); parent = null; } /** Resets the user object of the receiver to object. */ public void setUserObject(Object object) { try { file = (File) object; } catch (Exception exception) { DebugStream.printStackTrace(exception); } } // ------------------------------------------------------------------------------- public void add(MutableTreeNode child) { insert(child, child_nodes.size()); } protected abstract FileNode addChildNode(File file); /** Compare two FileNodes for equality. */ public boolean equals(FileNode node) { if (node == null) { // Definitely not a match return false; } if (file != null) { return file.equals(node.getFile()); } else { return toString().equals(node.toString()); } } /** Retrieve the file node at the given index, regardless of filters set. */ public FileNode getChildAtUnfiltered(int index) { if (index >= 0 && index < size()) { return (FileNode) child_nodes_unfiltered.get(index); } return null; } public File getFile() { return file; } /** 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 boolean isFileSystemRoot() { if (file != null) { return FileSystemView.getFileSystemView().isFileSystemRoot(file); } else { return false; } } /** Overridden if necessary by subclasses. */ public boolean isInLoadedCollection() { return false; } /** Overridden if necessary by subclasses. */ public boolean isReadOnly() { return false; } public void map() { // If this node has already been mapped, don't bother doing it again if (child_nodes != null) { return; } child_nodes = new ArrayList(); // General case, only map if children are allowed if (file != null && getAllowsChildren()) { File[] files = file.listFiles(); if (files != null && files.length > 0) { // Sort the child files ArrayTools.sort(files); // Now add them to child_nodes_unfiltered child_nodes_unfiltered = new ArrayList(); for (int i = 0; i < files.length; i++) { FileNode child_node = this.addChildNode(files[i]); child_nodes_unfiltered.add(child_node); } // Apply the filters set in the model FileFilter[] filters = model.getFilters(); for (int i = 0; filters != null && i < filters.length; i++) { files = ArrayTools.filter(files, filters[i].filter, filters[i].exclude); } // Add the files left after filtering to child_nodes for (int i = 0, j = 0; (i < child_nodes_unfiltered.size() && j < files.length); i++) { // Use the FileNode object in child_nodes_unfiltered rather than creating another FileNode file_node = (FileNode) child_nodes_unfiltered.get(i); if (file_node.getFile().equals(files[j])) { child_nodes.add(file_node); j++; } } // in case any filename encodings had gone stale, // (recalculate these and) refresh the display name refreshDescendantEncodings(); } model.nodeStructureChanged(this); } } public void refresh() { unmap(); map(); } // overridden in subclass CollectionTreeNode to reset and reencode display strings public void resetDescendantEncodings() {} public void refreshDescendantEncodings() {} public void setModel(FileSystemModel model) { this.model = model; } public void setParent(MutableTreeNode parent) { this.parent = parent; } /** Return the total number of child files for the file this node represents, irrespective of filters set. */ public int size() { if (child_nodes_unfiltered != null) { return child_nodes_unfiltered.size(); } return 0; } public String toString() { if (isFileSystemRoot()) { return file.getAbsolutePath(); } else { if(displayFileName == null) { displayFileName = calcDisplayString(); } return displayFileName; //return file.getName(); } } /** Unmap this node's children. */ public void unmap() { DebugStream.println("Unmapping " + this + "..."); child_nodes_unfiltered = null; child_nodes = null; } private class FileEnumeration implements Enumeration { private int index = 0; /** Tests if this enumeration contains more elements. */ public boolean hasMoreElements() { return (index < child_nodes.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 < child_nodes.size()) { result = child_nodes.get(index); index++; } return result; } } }