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 }