001    /* Copyright 2000, 2001, Compaq Computer Corporation */
002    
003    package javafe.filespace;
004    
005    
006    import java.io.*;
007    import java.util.zip.*;
008    import java.util.Enumeration;
009    
010    import javafe.genericfile.*;
011    
012    
013    /**
014     * This module encapsulates how to convert from a Java path-component
015     * name to the hierarchical filespace it denotes.<p>
016     *
017     * Filespaces are represented by Trees whose node's data fields contain
018     * (non-null) GenericFiles that represent the corresponding files.
019     * E.g., the node at X.Y.Z represents a file with pathname ./X/Y/Z in
020     * the hierarchy of the path component.<p>
021     *
022     * Changes made to the underlying file system may or may not be
023     * reflected in the returned filespaces.<p>
024     */
025    
026    public class PathComponent {
027    
028        /***************************************************
029         *                                                 *
030         * Creation of filespaces:                         *
031         *                                                 *
032         **************************************************/
033    
034        /**
035         * Create an empty filespace, containing only a root directory
036         */
037        //@ ensures \result != null;
038        public static Tree empty() {
039            return new LeafTree(
040                         new UnopenableFile("<root of the empty filesystem>",
041                                            true));
042        }
043    
044    
045        /**
046         * Convert from a path-component name to the filespace it denotes.<p>
047         *
048         * Throws an IOException if any errors occur while initially
049         * scanning the component.  Component must be non-null.  The result
050         * is always a non-null filespace.<p>
051         *
052         *
053         * If complain is set, open will throw IOExceptions if the
054         * path component does not exist, or if it is not a directory or a
055         * zipfile.  If complain is not set, then an empty filespace will
056         * be returned in these situations.  Either way, IOExceptions will
057         * still be thrown if an error occurs reading an actual file or
058         * directory.<p>
059         *
060         *
061         * Note: changes to the filesystem named by component may or may
062         * not be reflected in the returned Tree.<p>
063         */
064        //@ requires component != null;
065        //@ ensures \result != null;
066        public static Tree open(String component, boolean complain)
067                                     throws IOException {
068            // Make sure component refers to an existing file:
069            File root = new File(component);
070            if (!root.exists()) {
071                if (complain)
072                    throw new IOException("no such file: " + component);
073                else
074                    return empty();
075            }
076    
077            // Attempt to get the filespace contained in root
078            Tree filespace = null;
079            try {
080                if (isZipFilename(component))
081                    filespace = new ZipTree(root);
082                else if (root.isDirectory())
083                    filespace = new FileTree(root);
084            } catch (ZipException E) {
085                 throw new IOException(component + ": unable to process zipfile: "
086                    + E.getMessage());
087            } catch (IOException E) {
088                 throw new IOException("unable to open/read file: "
089                    + E.getMessage());
090            }
091    
092            // Complain if component is not a directory or a zipfile:
093            if (filespace == null) {
094                if (complain)
095                    throw new IOException("invalid classpath component: "
096                                            + component);
097                else
098                    return empty();
099            }
100    
101            return filespace;
102        }
103    
104        /**
105         * Does a filename indicate that it is in zip format? <p>
106         *
107         */
108        //@ requires name != null; 
109        protected static boolean isZipFilename(String name) {
110            return name.endsWith(".zip") || name.endsWith(".jar");
111        }
112    
113    
114        /***************************************************
115         *                                                 *
116         * Debugging functions:                            *
117         *                                                 *
118         **************************************************/
119    
120        /** A simple test driver */
121        //@ requires \nonnullelements(args);
122        public static void main(String[] args) throws IOException {
123            // Check usage:
124            if (args.length<1 || args.length>2) {
125                System.out.println("PathComponent: usage <path component> "
126                    + "[<pathname>]");
127                return;
128            }
129    
130            /*
131             * Create a new filespace from the path component args[0]:
132             */
133            Tree T;
134            try {
135                T = open(args[0], false);
136            } catch (IOException E) {
137                System.out.println("Caught " + E);
138                return;
139            };
140    
141            // If pathname given, try to print out that file then exit:
142            if (args.length==2) {
143                String pathname = args[1];
144    
145                // Strip off any leading '/'s:
146                while (pathname.length()>0 && pathname.charAt(0)=='/')
147                    pathname = pathname.substring(1,pathname.length());
148    
149                // Strip off any trailing '/'s:
150                while (pathname.length()>0 &&
151                        pathname.charAt(pathname.length()-1)=='/')
152                    pathname = pathname.substring(0,pathname.length()-1);
153    
154                Tree F = T.getQualifiedChild(pathname, '/');
155                if (F==null) {
156                    System.out.println("No such file: " + args[1]);
157                    return;
158                }
159    
160                InputStream I = ((GenericFile)F.data).getInputStream(); //@ nowarn Cast,Null;
161                for (;;) {
162                    int next = I.read();
163                    if (next<0)
164                        return;
165    
166                    System.out.write(next);
167                }
168            }
169    
170    //      T.print("");
171    
172            // Otherwise, list all the files in the filespace by their
173            // distinctive names, indicating which are directories and
174            // giving their modification times
175            Enumeration E = new TreeWalker(T);
176            while (E.hasMoreElements()) {
177                Tree node = (Tree)E.nextElement();
178                GenericFile file = (GenericFile)node.data;  //@ nowarn Cast;
179                //@ assume file != null;
180    
181                System.out.print(file.getHumanName() + " ");
182                if (file.lastModified()==0L)
183                    System.out.print("(unknown) ");
184                else
185                    System.out.print("(" + file.lastModified() + ") ");
186                if (file.isDirectory())
187                    System.out.print("[D] ");
188    
189                System.out.println();
190            }
191        }
192    }