    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    }