36
37 /**
38 * The Manifest class is used to maintain Manifest entry names and their
39 * associated Attributes. There are main Manifest Attributes as well as
40 * per-entry Attributes. For information on the Manifest format, please
41 * see the
42 * <a href="{@docRoot}/../specs/jar/jar.html">
43 * Manifest format specification</a>.
44 *
45 * @author David Connelly
46 * @see Attributes
47 * @since 1.2
48 */
49 public class Manifest implements Cloneable {
50 // manifest main attributes
51 private Attributes attr = new Attributes();
52
53 // manifest entries
54 private Map<String, Attributes> entries = new HashMap<>();
55
56 /**
57 * Constructs a new, empty Manifest.
58 */
59 public Manifest() {
60 }
61
62 /**
63 * Constructs a new Manifest from the specified input stream.
64 *
65 * @param is the input stream containing manifest data
66 * @throws IOException if an I/O error has occurred
67 */
68 public Manifest(InputStream is) throws IOException {
69 read(is);
70 }
71
72 /**
73 * Constructs a new Manifest that is a copy of the specified Manifest.
74 *
75 * @param man the Manifest to copy
76 */
77 public Manifest(Manifest man) {
78 attr.putAll(man.getMainAttributes());
79 entries.putAll(man.getEntries());
80 }
81
82 /**
83 * Returns the main Attributes for the Manifest.
84 * @return the main Attributes for the Manifest
85 */
86 public Attributes getMainAttributes() {
87 return attr;
88 }
89
90 /**
91 * Returns a Map of the entries contained in this Manifest. Each entry
92 * is represented by a String name (key) and associated Attributes (value).
176 index += 74; // + line width + line break ("\r\n")
177 length += 3; // + line break ("\r\n") and space
178 }
179 return;
180 }
181
182 /**
183 * Reads the Manifest from the specified InputStream. The entry
184 * names and attributes read will be merged in with the current
185 * manifest entries.
186 *
187 * @param is the input stream
188 * @exception IOException if an I/O error has occurred
189 */
190 public void read(InputStream is) throws IOException {
191 // Buffered input stream for reading manifest data
192 FastInputStream fis = new FastInputStream(is);
193 // Line buffer
194 byte[] lbuf = new byte[512];
195 // Read the main attributes for the manifest
196 attr.read(fis, lbuf);
197 // Total number of entries, attributes read
198 int ecount = 0, acount = 0;
199 // Average size of entry attributes
200 int asize = 2;
201 // Now parse the manifest entries
202 int len;
203 String name = null;
204 boolean skipEmptyLines = true;
205 byte[] lastline = null;
206
207 while ((len = fis.readLine(lbuf)) != -1) {
208 byte c = lbuf[--len];
209 if (c != '\n' && c != '\r') {
210 throw new IOException("manifest line too long");
211 }
212 if (len > 0 && lbuf[len-1] == '\r') {
213 --len;
214 }
215 if (len == 0 && skipEmptyLines) {
216 continue;
217 }
218 skipEmptyLines = false;
219
220 if (name == null) {
221 name = parseName(lbuf, len);
222 if (name == null) {
223 throw new IOException("invalid manifest format");
224 }
225 if (fis.peek() == ' ') {
226 // name is wrapped
227 lastline = new byte[len - 6];
228 System.arraycopy(lbuf, 6, lastline, 0, len - 6);
229 continue;
230 }
231 } else {
232 // continuation line
233 byte[] buf = new byte[lastline.length + len - 1];
234 System.arraycopy(lastline, 0, buf, 0, lastline.length);
235 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
236 if (fis.peek() == ' ') {
237 // name is wrapped
238 lastline = buf;
239 continue;
240 }
241 name = new String(buf, 0, buf.length, "UTF8");
242 lastline = null;
243 }
244 Attributes attr = getAttributes(name);
245 if (attr == null) {
246 attr = new Attributes(asize);
247 entries.put(name, attr);
248 }
249 attr.read(fis, lbuf);
250 ecount++;
251 acount += attr.size();
252 //XXX: Fix for when the average is 0. When it is 0,
253 // you get an Attributes object with an initial
254 // capacity of 0, which tickles a bug in HashMap.
255 asize = Math.max(2, acount / ecount);
256
257 name = null;
258 skipEmptyLines = true;
259 }
260 }
261
262 private String parseName(byte[] lbuf, int len) {
263 if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' &&
264 toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' &&
265 lbuf[4] == ':' && lbuf[5] == ' ') {
266 try {
267 return new String(lbuf, 6, len - 6, "UTF8");
268 }
269 catch (Exception e) {
|
36
37 /**
38 * The Manifest class is used to maintain Manifest entry names and their
39 * associated Attributes. There are main Manifest Attributes as well as
40 * per-entry Attributes. For information on the Manifest format, please
41 * see the
42 * <a href="{@docRoot}/../specs/jar/jar.html">
43 * Manifest format specification</a>.
44 *
45 * @author David Connelly
46 * @see Attributes
47 * @since 1.2
48 */
49 public class Manifest implements Cloneable {
50 // manifest main attributes
51 private Attributes attr = new Attributes();
52
53 // manifest entries
54 private Map<String, Attributes> entries = new HashMap<>();
55
56 // name of the corresponding jar archive if available.
57 private String jarFilename = null;
58
59 /**
60 * Constructs a new, empty Manifest.
61 */
62 public Manifest() {
63 }
64
65 /**
66 * Constructs a new Manifest from the specified input stream.
67 *
68 * @param is the input stream containing manifest data
69 * @throws IOException if an I/O error has occurred
70 */
71 public Manifest(InputStream is) throws IOException {
72 read(is);
73 }
74
75 /**
76 * Constructs a new Manifest from the specified input stream.
77 *
78 * @param is the input stream containing manifest data
79 * @param jarFilename the name of the corresponding jar archive if available
80 * @throws IOException if an I/O error has occured
81 */
82 Manifest(InputStream is, String jarFilename) throws IOException {
83 this.jarFilename = jarFilename;
84 read(is);
85 }
86
87 /**
88 * Constructs a new Manifest that is a copy of the specified Manifest.
89 *
90 * @param man the Manifest to copy
91 */
92 public Manifest(Manifest man) {
93 attr.putAll(man.getMainAttributes());
94 entries.putAll(man.getEntries());
95 }
96
97 /**
98 * Returns the main Attributes for the Manifest.
99 * @return the main Attributes for the Manifest
100 */
101 public Attributes getMainAttributes() {
102 return attr;
103 }
104
105 /**
106 * Returns a Map of the entries contained in this Manifest. Each entry
107 * is represented by a String name (key) and associated Attributes (value).
191 index += 74; // + line width + line break ("\r\n")
192 length += 3; // + line break ("\r\n") and space
193 }
194 return;
195 }
196
197 /**
198 * Reads the Manifest from the specified InputStream. The entry
199 * names and attributes read will be merged in with the current
200 * manifest entries.
201 *
202 * @param is the input stream
203 * @exception IOException if an I/O error has occurred
204 */
205 public void read(InputStream is) throws IOException {
206 // Buffered input stream for reading manifest data
207 FastInputStream fis = new FastInputStream(is);
208 // Line buffer
209 byte[] lbuf = new byte[512];
210 // Read the main attributes for the manifest
211 int lineNumber = attr.read(fis, lbuf, jarFilename, 0);
212 // Total number of entries, attributes read
213 int ecount = 0, acount = 0;
214 // Average size of entry attributes
215 int asize = 2;
216 // Now parse the manifest entries
217 int len;
218 String name = null;
219 boolean skipEmptyLines = true;
220 byte[] lastline = null;
221
222 while ((len = fis.readLine(lbuf)) != -1) {
223 byte c = lbuf[--len];
224 lineNumber++;
225
226 if (c != '\n' && c != '\r') {
227 throw new IOException("manifest line too long ("
228 + Attributes.getErrorPosition(jarFilename, lineNumber) + ")");
229 }
230 if (len > 0 && lbuf[len-1] == '\r') {
231 --len;
232 }
233 if (len == 0 && skipEmptyLines) {
234 continue;
235 }
236 skipEmptyLines = false;
237
238 if (name == null) {
239 name = parseName(lbuf, len);
240 if (name == null) {
241 throw new IOException("invalid manifest format"
242 + Attributes.getErrorPosition(jarFilename, lineNumber) + ")");
243 }
244 if (fis.peek() == ' ') {
245 // name is wrapped
246 lastline = new byte[len - 6];
247 System.arraycopy(lbuf, 6, lastline, 0, len - 6);
248 continue;
249 }
250 } else {
251 // continuation line
252 byte[] buf = new byte[lastline.length + len - 1];
253 System.arraycopy(lastline, 0, buf, 0, lastline.length);
254 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
255 if (fis.peek() == ' ') {
256 // name is wrapped
257 lastline = buf;
258 continue;
259 }
260 name = new String(buf, 0, buf.length, "UTF8");
261 lastline = null;
262 }
263 Attributes attr = getAttributes(name);
264 if (attr == null) {
265 attr = new Attributes(asize);
266 entries.put(name, attr);
267 }
268 lineNumber = attr.read(fis, lbuf, jarFilename, lineNumber);
269 ecount++;
270 acount += attr.size();
271 //XXX: Fix for when the average is 0. When it is 0,
272 // you get an Attributes object with an initial
273 // capacity of 0, which tickles a bug in HashMap.
274 asize = Math.max(2, acount / ecount);
275
276 name = null;
277 skipEmptyLines = true;
278 }
279 }
280
281 private String parseName(byte[] lbuf, int len) {
282 if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' &&
283 toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' &&
284 lbuf[4] == ':' && lbuf[5] == ' ') {
285 try {
286 return new String(lbuf, 6, len - 6, "UTF8");
287 }
288 catch (Exception e) {
|