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.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; /** 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; // Files cannot have children if (file != null && 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; } displayFileName = calcDisplayString(); } /** This method returns a string representation of the filenodes in the Collection * Tree, that can then be displayed in the tree. * We'll initially assume that the filenames are utf8 encoded and so convert the * filename into utf8 for proper presentation in the Collection tree pane. * If the filenames are not utf8, then the conversion would have introduced funny * characters. Therefore, when converting to utf8, if the converted filename * contains the special character '\ufffd', then we know the conversion did not work * and we return the original string which may or may not be properly presented by * default. * See http://java.sun.com/j2se/1.4.2/docs/api/java/nio/charset/CharsetDecoder.html * which says "How a decoding error is handled depends upon the action requested for * that type of error, which is described by an instance of the CodingErrorAction class. * The possible error actions are to ignore the erroneous input, report the error to * the invoker via the returned CoderResult object, or replace the erroneous input with * the current value of the replacement string. The replacement has the initial value * "\uFFFD"; its value may be changed via the replaceWith method." * The following made me think that String(byte[], String charsetName) constructor may * use the replacement value \uFFFD. * http://www.experts-exchange.com/Programming/Programming_Languages/Java/Q_20512969.html * mentions the following which made me think of this: * convertedStr = convertedStr.replace('\ufffd', ' '); */ protected String calcDisplayString() { String filename = file.getName(); try{ String utf8filename = new String(filename.getBytes(), "UTF8"); if(utf8filename.indexOf('\ufffd') == -1) { return utf8filename; } else { // contains the character indicating that it's invalid utf8 // return the original string return filename; } } catch(java.io.UnsupportedEncodingException e) { return filename; } } /** 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++; } } } model.nodeStructureChanged(this); } } public void refresh() { unmap(); map(); } 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; } } /** 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; } } }