001 /*
002 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package java.util.jar;
027
028 import java.io.FilterInputStream;
029 import java.io.DataOutputStream;
030 import java.io.InputStream;
031 import java.io.OutputStream;
032 import java.io.IOException;
033 import java.util.Map;
034 import java.util.HashMap;
035 import java.util.Iterator;
036
037 /**
038 * The Manifest class is used to maintain Manifest entry names and their
039 * associated Attributes. There are main Manifest Attributes as well as
040 * per-entry Attributes. For information on the Manifest format, please
041 * see the
042 * <a href="../../../../technotes/guides/jar/jar.html">
043 * Manifest format specification</a>.
044 *
045 * @author David Connelly
046 * @version 1.56, 05/05/07
047 * @see Attributes
048 * @since 1.2
049 */
050 public class Manifest implements Cloneable {
051 // manifest main attributes
052 private Attributes attr = new Attributes();
053
054 // manifest entries
055 private Map entries = new HashMap();
056
057 /**
058 * Constructs a new, empty Manifest.
059 */
060 public Manifest() {
061 }
062
063 /**
064 * Constructs a new Manifest from the specified input stream.
065 *
066 * @param is the input stream containing manifest data
067 * @throws IOException if an I/O error has occured
068 */
069 public Manifest(InputStream is) throws IOException {
070 read(is);
071 }
072
073 /**
074 * Constructs a new Manifest that is a copy of the specified Manifest.
075 *
076 * @param man the Manifest to copy
077 */
078 public Manifest(Manifest man) {
079 attr.putAll(man.getMainAttributes());
080 entries.putAll(man.getEntries());
081 }
082
083 /**
084 * Returns the main Attributes for the Manifest.
085 * @return the main Attributes for the Manifest
086 */
087 public Attributes getMainAttributes() {
088 return attr;
089 }
090
091 /**
092 * Returns a Map of the entries contained in this Manifest. Each entry
093 * is represented by a String name (key) and associated Attributes (value).
094 * The Map permits the {@code null} key, but no entry with a null key is
095 * created by {@link #read}, nor is such an entry written by using {@link
096 * #write}.
097 *
098 * @return a Map of the entries contained in this Manifest
099 */
100 public Map<String, Attributes> getEntries() {
101 return entries;
102 }
103
104 /**
105 * Returns the Attributes for the specified entry name.
106 * This method is defined as:
107 * <pre>
108 * return (Attributes)getEntries().get(name)
109 * </pre>
110 * Though {@code null} is a valid {@code name}, when
111 * {@code getAttributes(null)} is invoked on a {@code Manifest}
112 * obtained from a jar file, {@code null} will be returned. While jar
113 * files themselves do not allow {@code null}-named attributes, it is
114 * possible to invoke {@link #getEntries} on a {@code Manifest}, and
115 * on that result, invoke {@code put} with a null key and an
116 * arbitrary value. Subsequent invocations of
117 * {@code getAttributes(null)} will return the just-{@code put}
118 * value.
119 * <p>
120 * Note that this method does not return the manifest's main attributes;
121 * see {@link #getMainAttributes}.
122 *
123 * @param name entry name
124 * @return the Attributes for the specified entry name
125 */
126 public Attributes getAttributes(String name) {
127 return (Attributes) getEntries().get(name);
128 }
129
130 /**
131 * Clears the main Attributes as well as the entries in this Manifest.
132 */
133 public void clear() {
134 attr.clear();
135 entries.clear();
136 }
137
138 /**
139 * Writes the Manifest to the specified OutputStream.
140 * Attributes.Name.MANIFEST_VERSION must be set in
141 * MainAttributes prior to invoking this method.
142 *
143 * @param out the output stream
144 * @exception IOException if an I/O error has occurred
145 * @see #getMainAttributes
146 */
147 public void write(OutputStream out) throws IOException {
148 DataOutputStream dos = new DataOutputStream(out);
149 // Write out the main attributes for the manifest
150 attr.writeMain(dos);
151 // Now write out the pre-entry attributes
152 Iterator it = entries.entrySet().iterator();
153 while (it.hasNext()) {
154 Map.Entry e = (Map.Entry) it.next();
155 StringBuffer buffer = new StringBuffer("Name: ");
156 String value = (String) e.getKey();
157 if (value != null) {
158 byte[] vb = value.getBytes("UTF8");
159 value = new String(vb, 0, 0, vb.length);
160 }
161 buffer.append(value);
162 buffer.append("\r\n");
163 make72Safe(buffer);
164 dos.writeBytes(buffer.toString());
165 ((Attributes) e.getValue()).write(dos);
166 }
167 dos.flush();
168 }
169
170 /**
171 * Adds line breaks to enforce a maximum 72 bytes per line.
172 */
173 static void make72Safe(StringBuffer line) {
174 int length = line.length();
175 if (length > 72) {
176 int index = 70;
177 while (index < length - 2) {
178 line.insert(index, "\r\n ");
179 index += 72;
180 length += 3;
181 }
182 }
183 return;
184 }
185
186 /**
187 * Reads the Manifest from the specified InputStream. The entry
188 * names and attributes read will be merged in with the current
189 * manifest entries.
190 *
191 * @param is the input stream
192 * @exception IOException if an I/O error has occurred
193 */
194 public void read(InputStream is) throws IOException {
195 // Buffered input stream for reading manifest data
196 FastInputStream fis = new FastInputStream(is);
197 // Line buffer
198 byte[] lbuf = new byte[512];
199 // Read the main attributes for the manifest
200 attr.read(fis, lbuf);
201 // Total number of entries, attributes read
202 int ecount = 0, acount = 0;
203 // Average size of entry attributes
204 int asize = 2;
205 // Now parse the manifest entries
206 int len;
207 String name = null;
208 boolean skipEmptyLines = true;
209 byte[] lastline = null;
210
211 while ((len = fis.readLine(lbuf)) != -1) {
212 if (lbuf[--len] != '\n') {
213 throw new IOException("manifest line too long");
214 }
215 if (len > 0 && lbuf[len - 1] == '\r') {
216 --len;
217 }
218 if (len == 0 && skipEmptyLines) {
219 continue;
220 }
221 skipEmptyLines = false;
222
223 if (name == null) {
224 name = parseName(lbuf, len);
225 if (name == null) {
226 throw new IOException("invalid manifest format");
227 }
228 if (fis.peek() == ' ') {
229 // name is wrapped
230 lastline = new byte[len - 6];
231 System.arraycopy(lbuf, 6, lastline, 0, len - 6);
232 continue;
233 }
234 } else {
235 // continuation line
236 byte[] buf = new byte[lastline.length + len - 1];
237 System.arraycopy(lastline, 0, buf, 0, lastline.length);
238 System
239 .arraycopy(lbuf, 1, buf, lastline.length,
240 len - 1);
241 if (fis.peek() == ' ') {
242 // name is wrapped
243 lastline = buf;
244 continue;
245 }
246 name = new String(buf, 0, buf.length, "UTF8");
247 lastline = null;
248 }
249 Attributes attr = getAttributes(name);
250 if (attr == null) {
251 attr = new Attributes(asize);
252 entries.put(name, attr);
253 }
254 attr.read(fis, lbuf);
255 ecount++;
256 acount += attr.size();
257 //XXX: Fix for when the average is 0. When it is 0,
258 // you get an Attributes object with an initial
259 // capacity of 0, which tickles a bug in HashMap.
260 asize = Math.max(2, acount / ecount);
261
262 name = null;
263 skipEmptyLines = true;
264 }
265 }
266
267 private String parseName(byte[] lbuf, int len) {
268 if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a'
269 && toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e'
270 && lbuf[4] == ':' && lbuf[5] == ' ') {
271 try {
272 return new String(lbuf, 6, len - 6, "UTF8");
273 } catch (Exception e) {
274 }
275 }
276 return null;
277 }
278
279 private int toLower(int c) {
280 return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c;
281 }
282
283 /**
284 * Returns true if the specified Object is also a Manifest and has
285 * the same main Attributes and entries.
286 *
287 * @param o the object to be compared
288 * @return true if the specified Object is also a Manifest and has
289 * the same main Attributes and entries
290 */
291 public boolean equals(Object o) {
292 if (o instanceof Manifest) {
293 Manifest m = (Manifest) o;
294 return attr.equals(m.getMainAttributes())
295 && entries.equals(m.getEntries());
296 } else {
297 return false;
298 }
299 }
300
301 /**
302 * Returns the hash code for this Manifest.
303 */
304 public int hashCode() {
305 return attr.hashCode() + entries.hashCode();
306 }
307
308 /**
309 * Returns a shallow copy of this Manifest. The shallow copy is
310 * implemented as follows:
311 * <pre>
312 * public Object clone() { return new Manifest(this); }
313 * </pre>
314 * @return a shallow copy of this Manifest
315 */
316 public Object clone() {
317 return new Manifest(this );
318 }
319
320 /*
321 * A fast buffered input stream for parsing manifest files.
322 */
323 static class FastInputStream extends FilterInputStream {
324 private byte buf[];
325 private int count = 0;
326 private int pos = 0;
327
328 FastInputStream(InputStream in) {
329 this (in, 8192);
330 }
331
332 FastInputStream(InputStream in, int size) {
333 super (in);
334 buf = new byte[size];
335 }
336
337 public int read() throws IOException {
338 if (pos >= count) {
339 fill();
340 if (pos >= count) {
341 return -1;
342 }
343 }
344 return buf[pos++] & 0xff;
345 }
346
347 public int read(byte[] b, int off, int len) throws IOException {
348 int avail = count - pos;
349 if (avail <= 0) {
350 if (len >= buf.length) {
351 return in.read(b, off, len);
352 }
353 fill();
354 avail = count - pos;
355 if (avail <= 0) {
356 return -1;
357 }
358 }
359 if (len > avail) {
360 len = avail;
361 }
362 System.arraycopy(buf, pos, b, off, len);
363 pos += len;
364 return len;
365 }
366
367 /*
368 * Reads 'len' bytes from the input stream, or until an end-of-line
369 * is reached. Returns the number of bytes read.
370 */
371 public int readLine(byte[] b, int off, int len)
372 throws IOException {
373 byte[] tbuf = this .buf;
374 int total = 0;
375 while (total < len) {
376 int avail = count - pos;
377 if (avail <= 0) {
378 fill();
379 avail = count - pos;
380 if (avail <= 0) {
381 return -1;
382 }
383 }
384 int n = len - total;
385 if (n > avail) {
386 n = avail;
387 }
388 int tpos = pos;
389 int maxpos = tpos + n;
390 while (tpos < maxpos && tbuf[tpos++] != '\n')
391 ;
392 n = tpos - pos;
393 System.arraycopy(tbuf, pos, b, off, n);
394 off += n;
395 total += n;
396 pos = tpos;
397 if (tbuf[tpos - 1] == '\n') {
398 break;
399 }
400 }
401 return total;
402 }
403
404 public byte peek() throws IOException {
405 if (pos == count)
406 fill();
407 return buf[pos];
408 }
409
410 public int readLine(byte[] b) throws IOException {
411 return readLine(b, 0, b.length);
412 }
413
414 public long skip(long n) throws IOException {
415 if (n <= 0) {
416 return 0;
417 }
418 long avail = count - pos;
419 if (avail <= 0) {
420 return in.skip(n);
421 }
422 if (n > avail) {
423 n = avail;
424 }
425 pos += n;
426 return n;
427 }
428
429 public int available() throws IOException {
430 return (count - pos) + in.available();
431 }
432
433 public void close() throws IOException {
434 if (in != null) {
435 in.close();
436 in = null;
437 buf = null;
438 }
439 }
440
441 private void fill() throws IOException {
442 count = pos = 0;
443 int n = in.read(buf, 0, buf.length);
444 if (n > 0) {
445 count = n;
446 }
447 }
448 }
449 }
|