| /* Attributes.java -- Reads, writes and manipulaties jar manifest files | |
| Copyright (C) 2000 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA | |
| 02111-1307 USA. | |
| As a special exception, if you link this library with other files to | |
| produce an executable, this library does not by itself cause the | |
| resulting executable to be covered by the GNU General Public License. | |
| This exception does not however invalidate any other reasons why the | |
| executable file might be covered by the GNU General Public License. */ | |
| package java.util.jar; | |
| import java.io.BufferedReader; | |
| import java.io.BufferedWriter; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.io.InputStreamReader; | |
| import java.io.OutputStream; | |
| import java.io.OutputStreamWriter; | |
| import java.io.PrintWriter; | |
| import java.io.Reader; | |
| import java.io.Writer; | |
| import java.util.Hashtable; | |
| import java.util.Iterator; | |
| import java.util.Map; | |
| import java.util.Set; | |
| /** | |
| * Reads, writes and manipulaties jar manifest files. | |
| * XXX | |
| * | |
| * @since 1.2 | |
| * @author Mark Wielaard (mark@klomp.org) | |
| */ | |
| public class Manifest implements Cloneable | |
| { | |
| // Fields | |
| /** The main attributes of the manifest (jar file). */ | |
| private final Attributes mainAttr; | |
| /** A map of atrributes for all entries described in this Manifest. */ | |
| private final Map entries; | |
| // Constructors | |
| /** | |
| * Creates a new empty Manifest. | |
| */ | |
| public Manifest() | |
| { | |
| mainAttr = new Attributes(); | |
| entries = new Hashtable(); | |
| } | |
| /** | |
| * Creates a Manifest from the supplied input stream. | |
| * | |
| * @see read(Inputstream) | |
| * @see write(OutputStream) | |
| * | |
| * @param InputStream the input stream to read the manifest from | |
| * @exception IOException when an i/o exception occurs or the input stream | |
| * does not describe a valid manifest | |
| */ | |
| public Manifest(InputStream in) throws IOException | |
| { | |
| this(); | |
| read(in); | |
| } | |
| /** | |
| * Creates a Manifest from another Manifest. | |
| * Makes a deep copy of the main attributes, but a shallow copy of | |
| * the other entries. This means that you can freely add, change or remove | |
| * the main attributes or the entries of the new manifest without effecting | |
| * the original manifest, but adding, changing or removing attributes from | |
| * a particular entry also changes the attributes of that entry in the | |
| * original manifest. | |
| * | |
| * @see clone() | |
| * @param man the Manifest to copy from | |
| */ | |
| public Manifest(Manifest man) | |
| { | |
| mainAttr = new Attributes(man.getMainAttributes()); | |
| entries = new Hashtable(man.getEntries()); | |
| } | |
| // Methods | |
| /** | |
| * Gets the main attributes of this Manifest. | |
| */ | |
| public Attributes getMainAttributes() | |
| { | |
| return mainAttr; | |
| } | |
| /** | |
| * Gets a map of entry Strings to Attributes for all the entries described | |
| * in this manifest. Adding, changing or removing from this entries map | |
| * changes the entries of this manifest. | |
| */ | |
| public Map getEntries() | |
| { | |
| return entries; | |
| } | |
| /** | |
| * Returns the Attributes associated with the Entry. | |
| * <p> | |
| * Implemented as: | |
| * <code>return (Attributes)getEntries().get(entryName)</code> | |
| * | |
| * @param entryName the name of the entry to look up | |
| * @return the attributes associated with the entry or null when none | |
| */ | |
| public Attributes getAttributes(String entryName) | |
| { | |
| return (Attributes) getEntries().get(entryName); | |
| } | |
| /** | |
| * Clears the main attributes and removes all the entries from the | |
| * manifest. | |
| */ | |
| public void clear() | |
| { | |
| mainAttr.clear(); | |
| entries.clear(); | |
| } | |
| /** | |
| * XXX | |
| */ | |
| public void read(InputStream in) throws IOException | |
| { | |
| BufferedReader br = | |
| new BufferedReader(new InputStreamReader(in, "8859_1")); | |
| read_main_section(getMainAttributes(), br); | |
| read_individual_sections(getEntries(), br); | |
| } | |
| // Private Static methods for reading the Manifest file from BufferedReader | |
| private static void read_main_section(Attributes attr, | |
| BufferedReader br) throws IOException | |
| { | |
| read_version_info(attr, br); | |
| read_attributes(attr, br); | |
| } | |
| private static void read_version_info(Attributes attr, | |
| BufferedReader br) throws IOException | |
| { | |
| String version_header = Attributes.Name.MANIFEST_VERSION.toString(); | |
| try | |
| { | |
| String value = expect_header(version_header, br); | |
| attr.putValue(version_header, value); | |
| } | |
| catch (IOException ioe) | |
| { | |
| throw new JarException("Manifest should start with a " + | |
| version_header + ": " + ioe.getMessage()); | |
| } | |
| } | |
| private static String expect_header(String header, BufferedReader br) | |
| throws IOException | |
| { | |
| String s = br.readLine(); | |
| if (s == null) | |
| { | |
| throw new JarException("unexpected end of file"); | |
| } | |
| return expect_header(header, br, s); | |
| } | |
| private static String expect_header(String header, BufferedReader br, | |
| String s) throws IOException | |
| { | |
| try | |
| { | |
| String name = s.substring(0, header.length() + 1); | |
| if (name.equalsIgnoreCase(header + ":")) | |
| { | |
| String value_start = s.substring(header.length() + 2); | |
| return read_header_value(value_start, br); | |
| } | |
| } | |
| catch (IndexOutOfBoundsException iobe) | |
| { | |
| } | |
| // If we arrive here, something went wrong | |
| throw new JarException("unexpected '" + s + "'"); | |
| } | |
| private static String read_header_value(String s, BufferedReader br) | |
| throws IOException | |
| { | |
| boolean try_next = true; | |
| while (try_next) | |
| { | |
| // Lets see if there is something on the next line | |
| br.mark(1); | |
| if (br.read() == ' ') | |
| { | |
| s += br.readLine(); | |
| } | |
| else | |
| { | |
| br.reset(); | |
| try_next = false; | |
| } | |
| } | |
| return s; | |
| } | |
| private static void read_attributes(Attributes attr, | |
| BufferedReader br) throws IOException | |
| { | |
| String s = br.readLine(); | |
| while (s != null && (!s.equals(""))) | |
| { | |
| read_attribute(attr, s, br); | |
| s = br.readLine(); | |
| } | |
| } | |
| private static void read_attribute(Attributes attr, String s, | |
| BufferedReader br) throws IOException | |
| { | |
| try | |
| { | |
| int colon = s.indexOf(": "); | |
| String name = s.substring(0, colon); | |
| String value_start = s.substring(colon + 2); | |
| String value = read_header_value(value_start, br); | |
| attr.putValue(name, value); | |
| } | |
| catch (IndexOutOfBoundsException iobe) | |
| { | |
| throw new JarException("Manifest contains a bad header: " + s); | |
| } | |
| } | |
| private static void read_individual_sections(Map entries, | |
| BufferedReader br) throws | |
| IOException | |
| { | |
| String s = br.readLine(); | |
| while (s != null && (!s.equals(""))) | |
| { | |
| Attributes attr = read_section_name(s, br, entries); | |
| read_attributes(attr, br); | |
| s = br.readLine(); | |
| } | |
| } | |
| private static Attributes read_section_name(String s, BufferedReader br, | |
| Map entries) throws JarException | |
| { | |
| try | |
| { | |
| String name = expect_header("Name", br, s); | |
| Attributes attr = new Attributes(); | |
| entries.put(name, attr); | |
| return attr; | |
| } | |
| catch (IOException ioe) | |
| { | |
| throw new JarException | |
| ("Section should start with a Name header: " + ioe.getMessage()); | |
| } | |
| } | |
| /** | |
| * XXX | |
| */ | |
| public void write(OutputStream out) throws IOException | |
| { | |
| PrintWriter pw = | |
| new PrintWriter(new | |
| BufferedWriter(new OutputStreamWriter(out, "8859_1"))); | |
| write_main_section(getMainAttributes(), pw); | |
| pw.println(); | |
| write_individual_sections(getEntries(), pw); | |
| if (pw.checkError()) | |
| { | |
| throw new JarException("Error while writing manifest"); | |
| } | |
| } | |
| // Private Static functions for writing the Manifest file to a PrintWriter | |
| private static void write_main_section(Attributes attr, | |
| PrintWriter pw) throws JarException | |
| { | |
| write_version_info(attr, pw); | |
| write_main_attributes(attr, pw); | |
| } | |
| private static void write_version_info(Attributes attr, PrintWriter pw) | |
| { | |
| // First check if there is already a version attribute set | |
| String version = attr.getValue(Attributes.Name.MANIFEST_VERSION); | |
| if (version == null) | |
| { | |
| version = "1.0"; | |
| } | |
| write_header(Attributes.Name.MANIFEST_VERSION.toString(), version, pw); | |
| } | |
| private static void write_header(String name, String value, PrintWriter pw) | |
| { | |
| pw.print(name + ": "); | |
| int last = 68 - name.length(); | |
| if (last > value.length()) | |
| { | |
| pw.println(value); | |
| } | |
| else | |
| { | |
| pw.println(value.substring(0, last)); | |
| } | |
| while (last < value.length()) | |
| { | |
| pw.print(" "); | |
| int end = (last + 69); | |
| if (end > value.length()) | |
| { | |
| pw.println(value.substring(last)); | |
| } | |
| else | |
| { | |
| pw.println(value.substring(last, end)); | |
| } | |
| last = end; | |
| } | |
| } | |
| private static void write_main_attributes(Attributes attr, PrintWriter pw) | |
| throws JarException | |
| { | |
| Iterator it = attr.entrySet().iterator(); | |
| while (it.hasNext()) | |
| { | |
| Map.Entry entry = (Map.Entry) it.next(); | |
| // Don't print the manifest version again | |
| if (!Attributes.Name.MANIFEST_VERSION.equals(entry.getKey())) | |
| { | |
| write_attribute_entry(entry, pw); | |
| } | |
| } | |
| } | |
| private static void write_attribute_entry(Map.Entry entry, PrintWriter pw) | |
| throws JarException | |
| { | |
| String name = entry.getKey().toString(); | |
| String value = entry.getValue().toString(); | |
| if (name.equalsIgnoreCase("Name")) | |
| { | |
| throw new JarException("Attributes cannot be called 'Name'"); | |
| } | |
| if (name.startsWith("From")) | |
| { | |
| throw new | |
| JarException("Header cannot start with the four letters 'From'" + | |
| name); | |
| } | |
| write_header(name, value, pw); | |
| } | |
| private static void write_individual_sections(Map entries, PrintWriter pw) | |
| throws JarException | |
| { | |
| Iterator it = entries.entrySet().iterator(); | |
| while (it.hasNext()) | |
| { | |
| Map.Entry entry = (Map.Entry) it.next(); | |
| write_header("Name", entry.getKey().toString(), pw); | |
| write_entry_attributes((Attributes) entry.getValue(), pw); | |
| pw.println(); | |
| } | |
| } | |
| private static void write_entry_attributes(Attributes attr, PrintWriter pw) | |
| throws JarException | |
| { | |
| Iterator it = attr.entrySet().iterator(); | |
| while (it.hasNext()) | |
| { | |
| Map.Entry entry = (Map.Entry) it.next(); | |
| write_attribute_entry(entry, pw); | |
| } | |
| } | |
| /** | |
| * Makes a deep copy of the main attributes, but a shallow copy of | |
| * the other entries. This means that you can freely add, change or remove | |
| * the main attributes or the entries of the new manifest without effecting | |
| * the original manifest, but adding, changing or removing attributes from | |
| * a particular entry also changes the attributes of that entry in the | |
| * original manifest. Calls <CODE>new Manifest(this)</CODE>. | |
| */ | |
| public Object clone() | |
| { | |
| return new Manifest(this); | |
| } | |
| /** | |
| * Checks if another object is equal to this Manifest object. | |
| * Another Object is equal to this Manifest object if it is an instance of | |
| * Manifest and the main attributes and the entries of the other manifest | |
| * are equal to this one. | |
| */ | |
| public boolean equals(Object o) | |
| { | |
| return (o instanceof Manifest) && | |
| (mainAttr.equals(((Manifest) o).mainAttr)) && | |
| (entries.equals(((Manifest) o).entries)); | |
| } | |
| /** | |
| * Calculates the hash code of the manifest. Implemented by a xor of the | |
| * hash code of the main attributes with the hash code of the entries map. | |
| */ | |
| public int hashCode() | |
| { | |
| return mainAttr.hashCode() ^ entries.hashCode(); | |
| } | |
| } |