1 /* Manifest.java -- Reads, writes and manipulaties jar manifest files
2 Copyright (C) 2000, 2004 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
38 package java
.util
.jar
;
40 import java
.io
.BufferedReader
;
41 import java
.io
.BufferedWriter
;
42 import java
.io
.IOException
;
43 import java
.io
.InputStream
;
44 import java
.io
.InputStreamReader
;
45 import java
.io
.OutputStream
;
46 import java
.io
.OutputStreamWriter
;
47 import java
.io
.PrintWriter
;
48 import java
.util
.Hashtable
;
49 import java
.util
.Iterator
;
53 * Reads, writes and manipulaties jar manifest files.
57 * @author Mark Wielaard (mark@klomp.org)
59 public class Manifest
implements Cloneable
63 /** The main attributes of the manifest (jar file). */
64 private final Attributes mainAttr
;
66 /** A map of atrributes for all entries described in this Manifest. */
67 private final Map entries
;
72 * Creates a new empty Manifest.
76 mainAttr
= new Attributes();
77 entries
= new Hashtable();
81 * Creates a Manifest from the supplied input stream.
83 * @see read(Inputstream)
84 * @see write(OutputStream)
86 * @param InputStream the input stream to read the manifest from
87 * @exception IOException when an i/o exception occurs or the input stream
88 * does not describe a valid manifest
90 public Manifest(InputStream in
) throws IOException
97 * Creates a Manifest from another Manifest.
98 * Makes a deep copy of the main attributes, but a shallow copy of
99 * the other entries. This means that you can freely add, change or remove
100 * the main attributes or the entries of the new manifest without effecting
101 * the original manifest, but adding, changing or removing attributes from
102 * a particular entry also changes the attributes of that entry in the
106 * @param man the Manifest to copy from
108 public Manifest(Manifest man
)
110 mainAttr
= new Attributes(man
.getMainAttributes());
111 entries
= new Hashtable(man
.getEntries());
117 * Gets the main attributes of this Manifest.
119 public Attributes
getMainAttributes()
125 * Gets a map of entry Strings to Attributes for all the entries described
126 * in this manifest. Adding, changing or removing from this entries map
127 * changes the entries of this manifest.
129 public Map
getEntries()
135 * Returns the Attributes associated with the Entry.
138 * <code>return (Attributes)getEntries().get(entryName)</code>
140 * @param entryName the name of the entry to look up
141 * @return the attributes associated with the entry or null when none
143 public Attributes
getAttributes(String entryName
)
145 return (Attributes
) getEntries().get(entryName
);
149 * Clears the main attributes and removes all the entries from the
161 public void read(InputStream in
) throws IOException
164 new BufferedReader(new InputStreamReader(in
, "8859_1"));
165 read_main_section(getMainAttributes(), br
);
166 read_individual_sections(getEntries(), br
);
169 // Private Static methods for reading the Manifest file from BufferedReader
171 private static void read_main_section(Attributes attr
,
172 BufferedReader br
) throws IOException
174 // According to the spec we should actually call read_version_info() here.
175 read_attributes(attr
, br
);
176 // Explicitly set Manifest-Version attribute if not set in Main
177 // attributes of Manifest.
178 if (attr
.getValue(Attributes
.Name
.MANIFEST_VERSION
) == null)
179 attr
.putValue(Attributes
.Name
.MANIFEST_VERSION
, "0.0");
183 * Pedantic method that requires the next attribute in the Manifest to be
184 * the "Manifest-Version". This follows the Manifest spec closely but
185 * reject some jar Manifest files out in the wild.
187 private static void read_version_info(Attributes attr
,
188 BufferedReader br
) throws IOException
190 String version_header
= Attributes
.Name
.MANIFEST_VERSION
.toString();
193 String value
= expect_header(version_header
, br
);
194 attr
.putValue(Attributes
.Name
.MANIFEST_VERSION
, value
);
196 catch (IOException ioe
)
198 throw new JarException("Manifest should start with a " +
199 version_header
+ ": " + ioe
.getMessage());
203 private static String
expect_header(String header
, BufferedReader br
)
206 String s
= br
.readLine();
209 throw new JarException("unexpected end of file");
211 return expect_header(header
, br
, s
);
214 private static String
expect_header(String header
, BufferedReader br
,
215 String s
) throws IOException
219 String name
= s
.substring(0, header
.length() + 1);
220 if (name
.equalsIgnoreCase(header
+ ":"))
222 String value_start
= s
.substring(header
.length() + 2);
223 return read_header_value(value_start
, br
);
226 catch (IndexOutOfBoundsException iobe
)
229 // If we arrive here, something went wrong
230 throw new JarException("unexpected '" + s
+ "'");
233 private static String
read_header_value(String s
, BufferedReader br
)
236 boolean try_next
= true;
239 // Lets see if there is something on the next line
241 if (br
.read() == ' ')
254 private static void read_attributes(Attributes attr
,
255 BufferedReader br
) throws IOException
257 String s
= br
.readLine();
258 while (s
!= null && (!s
.equals("")))
260 read_attribute(attr
, s
, br
);
265 private static void read_attribute(Attributes attr
, String s
,
266 BufferedReader br
) throws IOException
270 int colon
= s
.indexOf(": ");
271 String name
= s
.substring(0, colon
);
272 String value_start
= s
.substring(colon
+ 2);
273 String value
= read_header_value(value_start
, br
);
274 attr
.putValue(name
, value
);
276 catch (IndexOutOfBoundsException iobe
)
278 throw new JarException("Manifest contains a bad header: " + s
);
282 private static void read_individual_sections(Map entries
,
283 BufferedReader br
) throws
286 String s
= br
.readLine();
287 while (s
!= null && (!s
.equals("")))
289 Attributes attr
= read_section_name(s
, br
, entries
);
290 read_attributes(attr
, br
);
295 private static Attributes
read_section_name(String s
, BufferedReader br
,
296 Map entries
) throws JarException
300 String name
= expect_header("Name", br
, s
);
301 Attributes attr
= new Attributes();
302 entries
.put(name
, attr
);
305 catch (IOException ioe
)
307 throw new JarException
308 ("Section should start with a Name header: " + ioe
.getMessage());
315 public void write(OutputStream out
) throws IOException
319 BufferedWriter(new OutputStreamWriter(out
, "8859_1")));
320 write_main_section(getMainAttributes(), pw
);
322 write_individual_sections(getEntries(), pw
);
325 throw new JarException("Error while writing manifest");
329 // Private Static functions for writing the Manifest file to a PrintWriter
331 private static void write_main_section(Attributes attr
,
332 PrintWriter pw
) throws JarException
334 write_version_info(attr
, pw
);
335 write_main_attributes(attr
, pw
);
338 private static void write_version_info(Attributes attr
, PrintWriter pw
)
340 // First check if there is already a version attribute set
341 String version
= attr
.getValue(Attributes
.Name
.MANIFEST_VERSION
);
346 write_header(Attributes
.Name
.MANIFEST_VERSION
.toString(), version
, pw
);
349 private static void write_header(String name
, String value
, PrintWriter pw
)
351 pw
.print(name
+ ": ");
353 int last
= 68 - name
.length();
354 if (last
> value
.length())
360 pw
.println(value
.substring(0, last
));
362 while (last
< value
.length())
365 int end
= (last
+ 69);
366 if (end
> value
.length())
368 pw
.println(value
.substring(last
));
372 pw
.println(value
.substring(last
, end
));
378 private static void write_main_attributes(Attributes attr
, PrintWriter pw
)
381 Iterator it
= attr
.entrySet().iterator();
384 Map
.Entry entry
= (Map
.Entry
) it
.next();
385 // Don't print the manifest version again
386 if (!Attributes
.Name
.MANIFEST_VERSION
.equals(entry
.getKey()))
388 write_attribute_entry(entry
, pw
);
393 private static void write_attribute_entry(Map
.Entry entry
, PrintWriter pw
)
396 String name
= entry
.getKey().toString();
397 String value
= entry
.getValue().toString();
399 if (name
.equalsIgnoreCase("Name"))
401 throw new JarException("Attributes cannot be called 'Name'");
403 if (name
.startsWith("From"))
406 JarException("Header cannot start with the four letters 'From'" +
409 write_header(name
, value
, pw
);
412 private static void write_individual_sections(Map entries
, PrintWriter pw
)
416 Iterator it
= entries
.entrySet().iterator();
419 Map
.Entry entry
= (Map
.Entry
) it
.next();
420 write_header("Name", entry
.getKey().toString(), pw
);
421 write_entry_attributes((Attributes
) entry
.getValue(), pw
);
426 private static void write_entry_attributes(Attributes attr
, PrintWriter pw
)
429 Iterator it
= attr
.entrySet().iterator();
432 Map
.Entry entry
= (Map
.Entry
) it
.next();
433 write_attribute_entry(entry
, pw
);
438 * Makes a deep copy of the main attributes, but a shallow copy of
439 * the other entries. This means that you can freely add, change or remove
440 * the main attributes or the entries of the new manifest without effecting
441 * the original manifest, but adding, changing or removing attributes from
442 * a particular entry also changes the attributes of that entry in the
443 * original manifest. Calls <CODE>new Manifest(this)</CODE>.
445 public Object
clone()
447 return new Manifest(this);
451 * Checks if another object is equal to this Manifest object.
452 * Another Object is equal to this Manifest object if it is an instance of
453 * Manifest and the main attributes and the entries of the other manifest
454 * are equal to this one.
456 public boolean equals(Object o
)
458 return (o
instanceof Manifest
) &&
459 (mainAttr
.equals(((Manifest
) o
).mainAttr
)) &&
460 (entries
.equals(((Manifest
) o
).entries
));
464 * Calculates the hash code of the manifest. Implemented by a xor of the
465 * hash code of the main attributes with the hash code of the entries map.
467 public int hashCode()
469 return mainAttr
.hashCode() ^ entries
.hashCode();