001    /* Copyright 2000, 2001, Compaq Computer Corporation */
002    
003    package javafe.genericfile;
004    
005    
006    import java.io.InputStream;
007    import java.io.IOException;
008    
009    import java.util.zip.ZipEntry;
010    import java.util.zip.ZipFile;
011    
012    
013    /**
014     * A ZipGenericFile represents a zipfile-entry file
015     * (java.util.zip.ZipEntry) as a GenericFile.
016     *
017     * WARNING: ZipEntry's (but not ZipFile's) always use "/" as their
018     * separator.
019     */
020    
021    public class ZipGenericFile implements GenericFile {
022    
023        //@ invariant underlyingZipFile != null;
024        public ZipFile  underlyingZipFile;
025    
026        //@ invariant underlyingZipEntry != null;
027        public ZipEntry underlyingZipEntry;
028    
029    
030        /***************************************************
031         *                                                 *
032         * Creation:                                       *
033         *                                                 *
034         **************************************************/
035    
036        /** Create a generic file representing a ZipEntry in a ZipFile: */
037        //@ requires file != null && entry != null;
038        public ZipGenericFile(ZipFile file, ZipEntry entry) {
039            underlyingZipFile = file;
040            underlyingZipEntry = entry;
041        }
042    
043    
044        /***************************************************
045         *                                                 *
046         * Operations on ZipGenericFiles:                  *
047         *                                                 *
048         **************************************************/
049    
050        /**
051         * Return a name that uniquely identifies us to the user.
052         *
053         * Warning: the result may not be a conventional filename or use
054         * the system separators.
055         */
056        public String getHumanName() {
057            return underlyingZipFile.getName() + ":"
058                    + underlyingZipEntry.toString();
059        }
060    
061    
062        /**
063         * Return a String that canonically represents the identity of our
064         * underlying file.
065         *
066         * This function must be defined such that if two GenericFiles
067         * return non-null canonical ID's then the IDs are the same
068         * (modulo .equals) => the GenericFiles represent the same
069         * underlying file.  Ideally, under normal circumstances, the =>
070         * is actually a <=>.
071         *
072         * This function should only return null in exceptional
073         * cases, such as when an I/O error in the underlying storage media
074         * prevents construction of a canonical ID.
075         *
076         * Convention: Canonical IDs start with <X> where X is the
077         * fully-qualified name of the class that mediates I/O to the
078         * underlying file.  E.g., java.io.File for a normal disk file.
079         */
080        public String getCanonicalID() {
081            /*
082             * WARNING: this doesn't quite implement the spec.  In
083             * particular, we can't canonicalize underlyingZipFile's
084             * pathname since we can't convert it to an absolute pathname
085             * (don't know current directory when it was created.)
086             */
087            return "<java.util.zip.ZipEntry>("
088                + underlyingZipFile.getName().length() + ")"
089                + underlyingZipFile.getName()
090                    + ":" + underlyingZipEntry.getName();
091        }
092    
093    
094        /**
095         * Return our local name, the name that distinguishes us
096         * within the directory that contains us.
097         *
098         * E.g., "/a/b/c" has local name "c", "/e/r/" has local name "r", and
099         * "/" has local name "".  (assuming "/" is the separator char)
100         */
101        public String getLocalName() {
102            String path = underlyingZipEntry.getName();
103            while (path.endsWith("/"))
104                path = path.substring(0,path.length()-1);
105    
106            int index = path.lastIndexOf('/');
107            if (index== -1)
108                return path;
109            else
110                return path.substring(index+1, path.length());
111        }
112    
113    
114        /**
115         * Do we represent a directory?
116         */
117        public boolean isDirectory() {
118            return underlyingZipEntry.isDirectory();
119        }
120    
121    
122        /**
123         * Open the file we represent as an InputStream.<p>
124         *
125         * java.io.IOEXception may be thrown for many reasons, including no
126         * such file and read permission denied.<p>
127         */
128        public InputStream getInputStream() throws IOException {
129            return underlyingZipFile.getInputStream(underlyingZipEntry);
130        }
131    
132    
133        /**
134         * Returns the time that the file represented by us was last
135         * modified.<p>
136         *
137         * The return value is system dependent and should only be used to
138         * compare with other values returned by last modified. It should
139         * not be interpreted as an absolute time.<p>
140         *
141         * If a last-modified time is not available (e.g., underlying file
142         * doesn't exist, no time specified in a zipentry, etc.), then 0L
143         * is returned.<p>
144         */
145        public long lastModified() {
146            long time = underlyingZipEntry.getTime();
147    
148            /*
149             * getTime returns -1L if no time was specified in the zipfile;
150             * convert this to 0L so we behave the same as
151             * File.lastModified():
152             */
153            if (time<0)
154                time = 0;
155    
156            return time;
157        }
158    
159    
160        /**
161         * Attempt to return a GenericFile that describes the file in the
162         * same "directory" as us that has the local name <code>n</code>. <p>
163         *
164         * No attempt is made to verify whether or not that file exists.<p>
165         *
166         * In cases where the notion of "containing directory" makes no
167         * sense (e.g., streams or root directories), null is returned.
168         */
169        public GenericFile getSibling(String n) { 
170            String name = underlyingZipEntry.getName();
171    
172            // Root directory (never appears in real zipfiles) has no siblings:
173            if (name.equals(""))
174                return null;
175    
176            // get "parent", including trailing separator if one:
177            int index = name.lastIndexOf('/');
178            name = name.substring(0,index+1) + n;
179    
180            ZipEntry siblingEntry = underlyingZipFile.getEntry(name);
181            if (siblingEntry == null) {
182                // Return something that will cause a file-not-found error
183                // to be raised when an attempt is made to open the entry
184                siblingEntry = new ZipEntry(name);
185            }
186            return new ZipGenericFile(underlyingZipFile, siblingEntry);
187        }
188    }