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(/*@non_null*/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 }