1 /*
   2  * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  *
  23  */
  24 
  25 /** Encapsulates a notion of a directory tree. Designed to allow fast
  26     querying of full paths for unique filenames in the hierarchy. */
  27 
  28 import java.io.*;
  29 import java.util.*;
  30 
  31 public class DirectoryTree {
  32 
  33     /** The root of the read directoryTree */
  34     private Node rootNode;
  35 
  36     /** Subdirs to ignore; Vector of Strings */
  37     private Vector subdirsToIgnore;
  38 
  39     /** This maps file names to Lists of nodes. */
  40     private Hashtable nameToNodeListTable;
  41 
  42     /** Output "."'s as directories are read. Defaults to false. */
  43     private boolean verbose;
  44 
  45     public DirectoryTree() {
  46         subdirsToIgnore = new Vector();
  47         verbose = false;
  48     }
  49 
  50     public void addSubdirToIgnore(String subdir) {
  51         subdirsToIgnore.add(subdir);
  52     }
  53 
  54     private class FileIterator implements Iterator {
  55         private Vector nodes = new Vector();
  56 
  57         public FileIterator(Node rootNode) {
  58             if(rootNode == null) {
  59                 return;
  60             }
  61             nodes.add(rootNode);
  62             prune();
  63         }
  64         public boolean hasNext() {
  65             return nodes.size() > 0;
  66         }
  67         public Object next() {
  68             Node last = (Node)nodes.remove(nodes.size() - 1);
  69             prune();
  70             return new File(last.getName());
  71         }
  72 
  73         public void remove() {
  74             throw new RuntimeException();
  75         }
  76 
  77         private void prune() {
  78             while (nodes.size() > 0) {
  79                 Node last = (Node)nodes.get(nodes.size() - 1);
  80 
  81                 if (last.isDirectory()) {
  82                     nodes.remove(nodes.size() - 1);
  83                     nodes.addAll(last.children);
  84                 } else {
  85                     // Is at file
  86                     return;
  87                 }
  88             }
  89         }
  90     }
  91 
  92     public Iterator getFileIterator() {
  93         return new FileIterator(rootNode);
  94     }
  95 
  96     /** Output "."'s to System.out as directories are read. Defaults
  97         to false. */
  98     public void setVerbose(boolean newValue) {
  99         verbose = newValue;
 100     }
 101 
 102     public boolean getVerbose() {
 103         return verbose;
 104     }
 105 
 106     public String getRootNodeName() {
 107         return rootNode.getName();
 108     }
 109 
 110     /** Takes an absolute path to the root directory of this
 111         DirectoryTree. Throws IllegalArgumentException if the given
 112         string represents a plain file or nonexistent directory. */
 113 
 114     public void readDirectory(String baseDirectory)
 115         throws IllegalArgumentException {
 116         File root = new File(Util.normalize(baseDirectory));
 117         if (!root.isDirectory()) {
 118             return;
 119         }
 120         try {
 121             root = root.getCanonicalFile();
 122         }
 123         catch (IOException e) {
 124             throw new RuntimeException(e.toString());
 125         }
 126         rootNode = new Node(root);
 127         readDirectory(rootNode, root);
 128     }
 129 
 130     /** Queries the DirectoryTree for a file or directory name. Takes
 131         only the name of the file or directory itself (i.e., no parent
 132         directory information should be in the passed name). Returns a
 133         List of DirectoryTreeNodes specifying the full paths of all of
 134         the files or directories of this name in the DirectoryTree.
 135         Returns null if the directory tree has not been read from disk
 136         yet or if the file was not found in the tree. */
 137     public List findFile(String name) {
 138         if (rootNode == null) {
 139             return null;
 140         }
 141 
 142         if (nameToNodeListTable == null) {
 143             nameToNodeListTable = new Hashtable();
 144             try {
 145                 buildNameToNodeListTable(rootNode);
 146             } catch (IOException e) {
 147                 e.printStackTrace();
 148                 return null;
 149             }
 150         }
 151 
 152         return (List) nameToNodeListTable.get(name);
 153     }
 154 
 155     private void buildNameToNodeListTable(Node curNode)
 156       throws IOException {
 157         String fullName = curNode.getName();
 158         String parent = curNode.getParent();
 159         String separator = System.getProperty("file.separator");
 160 
 161         if (parent != null) {
 162           if (!fullName.startsWith(parent)) {
 163             throw new RuntimeException(
 164                 "Internal error: parent of file name \"" + fullName +
 165                 "\" does not match file name \"" + parent + "\""
 166             );
 167           }
 168 
 169           int len = parent.length();
 170           if (!parent.endsWith(separator)) {
 171             len += separator.length();
 172           }
 173 
 174           String fileName = fullName.substring(len);
 175 
 176           if (fileName == null) {
 177             throw new RuntimeException(
 178                 "Internal error: file name was empty"
 179             );
 180           }
 181 
 182           List nodeList = (List) nameToNodeListTable.get(fileName);
 183           if (nodeList == null) {
 184             nodeList = new Vector();
 185             nameToNodeListTable.put(fileName, nodeList);
 186           }
 187 
 188           nodeList.add(curNode);
 189         } else {
 190           if (curNode != rootNode) {
 191             throw new RuntimeException(
 192                 "Internal error: parent of file + \"" + fullName + "\"" +
 193                 " was null"
 194             );
 195           }
 196         }
 197 
 198         if (curNode.isDirectory()) {
 199           Iterator iter = curNode.getChildren();
 200           if (iter != null) {
 201             while (iter.hasNext()) {
 202               buildNameToNodeListTable((Node) iter.next());
 203             }
 204           }
 205         }
 206     }
 207 
 208     /** Reads all of the files in the given directory and adds them as
 209         children of the directory tree node. Requires that the passed
 210         node represents a directory. */
 211 
 212     private void readDirectory(Node parentNode, File parentDir) {
 213         File[] children = parentDir.listFiles();
 214         if (children == null)
 215             return;
 216         if (verbose) {
 217             System.out.print(".");
 218             System.out.flush();
 219         }
 220         for (int i = 0; i < children.length; i++) {
 221             File child = children[i];
 222             children[i] = null;
 223             boolean isDir = child.isDirectory();
 224             boolean mustSkip = false;
 225             if (isDir) {
 226                 for (Iterator iter = subdirsToIgnore.iterator();
 227                      iter.hasNext(); ) {
 228                     if (child.getName().equals((String) iter.next())) {
 229                         mustSkip = true;
 230                         break;
 231                     }
 232                 }
 233             }
 234             if (!mustSkip) {
 235                 Node childNode = new Node(child);
 236                 parentNode.addChild(childNode);
 237                 if (isDir) {
 238                     readDirectory(childNode, child);
 239                 }
 240             }
 241         }
 242     }
 243 
 244     private class Node implements DirectoryTreeNode {
 245         private File file;
 246         private Vector children;
 247 
 248         /** file must be a canonical file */
 249         Node(File file) {
 250             this.file = file;
 251             children = new Vector();
 252         }
 253 
 254         public boolean isFile() {
 255             return file.isFile();
 256         }
 257 
 258         public boolean isDirectory() {
 259             return file.isDirectory();
 260         }
 261 
 262         public String getName() {
 263             return file.getPath();
 264         }
 265 
 266         public String getParent() {
 267             return file.getParent();
 268         }
 269 
 270         public void addChild(Node n) {
 271             children.add(n);
 272         }
 273 
 274         public Iterator getChildren() throws IllegalArgumentException {
 275             return children.iterator();
 276         }
 277 
 278         public int getNumChildren() throws IllegalArgumentException {
 279             return children.size();
 280         }
 281 
 282         public DirectoryTreeNode getChild(int i)
 283             throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
 284             return (DirectoryTreeNode) children.get(i);
 285         }
 286     }
 287 }