0N/A/*
4857N/A * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage java.util.jar;
0N/A
0N/Aimport java.io.FilterInputStream;
0N/Aimport java.io.DataOutputStream;
0N/Aimport java.io.InputStream;
0N/Aimport java.io.OutputStream;
0N/Aimport java.io.IOException;
0N/Aimport java.util.Map;
0N/Aimport java.util.HashMap;
0N/Aimport java.util.Iterator;
0N/A
0N/A/**
0N/A * The Manifest class is used to maintain Manifest entry names and their
0N/A * associated Attributes. There are main Manifest Attributes as well as
0N/A * per-entry Attributes. For information on the Manifest format, please
0N/A * see the
0N/A * <a href="../../../../technotes/guides/jar/jar.html">
0N/A * Manifest format specification</a>.
0N/A *
0N/A * @author David Connelly
0N/A * @see Attributes
0N/A * @since 1.2
0N/A */
0N/Apublic class Manifest implements Cloneable {
0N/A // manifest main attributes
0N/A private Attributes attr = new Attributes();
0N/A
0N/A // manifest entries
0N/A private Map entries = new HashMap();
0N/A
0N/A /**
0N/A * Constructs a new, empty Manifest.
0N/A */
0N/A public Manifest() {
0N/A }
0N/A
0N/A /**
0N/A * Constructs a new Manifest from the specified input stream.
0N/A *
0N/A * @param is the input stream containing manifest data
0N/A * @throws IOException if an I/O error has occured
0N/A */
0N/A public Manifest(InputStream is) throws IOException {
0N/A read(is);
0N/A }
0N/A
0N/A /**
0N/A * Constructs a new Manifest that is a copy of the specified Manifest.
0N/A *
0N/A * @param man the Manifest to copy
0N/A */
0N/A public Manifest(Manifest man) {
0N/A attr.putAll(man.getMainAttributes());
0N/A entries.putAll(man.getEntries());
0N/A }
0N/A
0N/A /**
0N/A * Returns the main Attributes for the Manifest.
0N/A * @return the main Attributes for the Manifest
0N/A */
0N/A public Attributes getMainAttributes() {
0N/A return attr;
0N/A }
0N/A
0N/A /**
0N/A * Returns a Map of the entries contained in this Manifest. Each entry
0N/A * is represented by a String name (key) and associated Attributes (value).
0N/A * The Map permits the {@code null} key, but no entry with a null key is
0N/A * created by {@link #read}, nor is such an entry written by using {@link
0N/A * #write}.
0N/A *
0N/A * @return a Map of the entries contained in this Manifest
0N/A */
0N/A public Map<String,Attributes> getEntries() {
0N/A return entries;
0N/A }
0N/A
0N/A /**
0N/A * Returns the Attributes for the specified entry name.
0N/A * This method is defined as:
0N/A * <pre>
0N/A * return (Attributes)getEntries().get(name)
0N/A * </pre>
0N/A * Though {@code null} is a valid {@code name}, when
0N/A * {@code getAttributes(null)} is invoked on a {@code Manifest}
0N/A * obtained from a jar file, {@code null} will be returned. While jar
0N/A * files themselves do not allow {@code null}-named attributes, it is
0N/A * possible to invoke {@link #getEntries} on a {@code Manifest}, and
0N/A * on that result, invoke {@code put} with a null key and an
0N/A * arbitrary value. Subsequent invocations of
0N/A * {@code getAttributes(null)} will return the just-{@code put}
0N/A * value.
0N/A * <p>
0N/A * Note that this method does not return the manifest's main attributes;
0N/A * see {@link #getMainAttributes}.
0N/A *
0N/A * @param name entry name
0N/A * @return the Attributes for the specified entry name
0N/A */
0N/A public Attributes getAttributes(String name) {
0N/A return getEntries().get(name);
0N/A }
0N/A
0N/A /**
0N/A * Clears the main Attributes as well as the entries in this Manifest.
0N/A */
0N/A public void clear() {
0N/A attr.clear();
0N/A entries.clear();
0N/A }
0N/A
0N/A /**
0N/A * Writes the Manifest to the specified OutputStream.
0N/A * Attributes.Name.MANIFEST_VERSION must be set in
0N/A * MainAttributes prior to invoking this method.
0N/A *
0N/A * @param out the output stream
0N/A * @exception IOException if an I/O error has occurred
0N/A * @see #getMainAttributes
0N/A */
0N/A public void write(OutputStream out) throws IOException {
0N/A DataOutputStream dos = new DataOutputStream(out);
0N/A // Write out the main attributes for the manifest
0N/A attr.writeMain(dos);
0N/A // Now write out the pre-entry attributes
0N/A Iterator it = entries.entrySet().iterator();
0N/A while (it.hasNext()) {
0N/A Map.Entry e = (Map.Entry)it.next();
0N/A StringBuffer buffer = new StringBuffer("Name: ");
0N/A String value = (String)e.getKey();
0N/A if (value != null) {
0N/A byte[] vb = value.getBytes("UTF8");
0N/A value = new String(vb, 0, 0, vb.length);
0N/A }
0N/A buffer.append(value);
0N/A buffer.append("\r\n");
0N/A make72Safe(buffer);
0N/A dos.writeBytes(buffer.toString());
0N/A ((Attributes)e.getValue()).write(dos);
0N/A }
0N/A dos.flush();
0N/A }
0N/A
0N/A /**
0N/A * Adds line breaks to enforce a maximum 72 bytes per line.
0N/A */
0N/A static void make72Safe(StringBuffer line) {
0N/A int length = line.length();
0N/A if (length > 72) {
0N/A int index = 70;
0N/A while (index < length - 2) {
0N/A line.insert(index, "\r\n ");
0N/A index += 72;
0N/A length += 3;
0N/A }
0N/A }
0N/A return;
0N/A }
0N/A
0N/A /**
0N/A * Reads the Manifest from the specified InputStream. The entry
0N/A * names and attributes read will be merged in with the current
0N/A * manifest entries.
0N/A *
0N/A * @param is the input stream
0N/A * @exception IOException if an I/O error has occurred
0N/A */
0N/A public void read(InputStream is) throws IOException {
0N/A // Buffered input stream for reading manifest data
0N/A FastInputStream fis = new FastInputStream(is);
0N/A // Line buffer
0N/A byte[] lbuf = new byte[512];
0N/A // Read the main attributes for the manifest
0N/A attr.read(fis, lbuf);
0N/A // Total number of entries, attributes read
0N/A int ecount = 0, acount = 0;
0N/A // Average size of entry attributes
0N/A int asize = 2;
0N/A // Now parse the manifest entries
0N/A int len;
0N/A String name = null;
0N/A boolean skipEmptyLines = true;
0N/A byte[] lastline = null;
0N/A
0N/A while ((len = fis.readLine(lbuf)) != -1) {
0N/A if (lbuf[--len] != '\n') {
0N/A throw new IOException("manifest line too long");
0N/A }
0N/A if (len > 0 && lbuf[len-1] == '\r') {
0N/A --len;
0N/A }
0N/A if (len == 0 && skipEmptyLines) {
0N/A continue;
0N/A }
0N/A skipEmptyLines = false;
0N/A
0N/A if (name == null) {
0N/A name = parseName(lbuf, len);
0N/A if (name == null) {
0N/A throw new IOException("invalid manifest format");
0N/A }
0N/A if (fis.peek() == ' ') {
0N/A // name is wrapped
0N/A lastline = new byte[len - 6];
0N/A System.arraycopy(lbuf, 6, lastline, 0, len - 6);
0N/A continue;
0N/A }
0N/A } else {
0N/A // continuation line
0N/A byte[] buf = new byte[lastline.length + len - 1];
0N/A System.arraycopy(lastline, 0, buf, 0, lastline.length);
0N/A System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
0N/A if (fis.peek() == ' ') {
0N/A // name is wrapped
0N/A lastline = buf;
0N/A continue;
0N/A }
0N/A name = new String(buf, 0, buf.length, "UTF8");
0N/A lastline = null;
0N/A }
0N/A Attributes attr = getAttributes(name);
0N/A if (attr == null) {
0N/A attr = new Attributes(asize);
0N/A entries.put(name, attr);
0N/A }
0N/A attr.read(fis, lbuf);
0N/A ecount++;
0N/A acount += attr.size();
0N/A //XXX: Fix for when the average is 0. When it is 0,
0N/A // you get an Attributes object with an initial
0N/A // capacity of 0, which tickles a bug in HashMap.
0N/A asize = Math.max(2, acount / ecount);
0N/A
0N/A name = null;
0N/A skipEmptyLines = true;
0N/A }
0N/A }
0N/A
0N/A private String parseName(byte[] lbuf, int len) {
0N/A if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' &&
0N/A toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' &&
0N/A lbuf[4] == ':' && lbuf[5] == ' ') {
0N/A try {
0N/A return new String(lbuf, 6, len - 6, "UTF8");
0N/A }
0N/A catch (Exception e) {
0N/A }
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A private int toLower(int c) {
0N/A return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c;
0N/A }
0N/A
0N/A /**
0N/A * Returns true if the specified Object is also a Manifest and has
0N/A * the same main Attributes and entries.
0N/A *
0N/A * @param o the object to be compared
0N/A * @return true if the specified Object is also a Manifest and has
0N/A * the same main Attributes and entries
0N/A */
0N/A public boolean equals(Object o) {
0N/A if (o instanceof Manifest) {
0N/A Manifest m = (Manifest)o;
0N/A return attr.equals(m.getMainAttributes()) &&
0N/A entries.equals(m.getEntries());
0N/A } else {
0N/A return false;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Returns the hash code for this Manifest.
0N/A */
0N/A public int hashCode() {
0N/A return attr.hashCode() + entries.hashCode();
0N/A }
0N/A
0N/A /**
0N/A * Returns a shallow copy of this Manifest. The shallow copy is
0N/A * implemented as follows:
0N/A * <pre>
0N/A * public Object clone() { return new Manifest(this); }
0N/A * </pre>
0N/A * @return a shallow copy of this Manifest
0N/A */
0N/A public Object clone() {
0N/A return new Manifest(this);
0N/A }
0N/A
0N/A /*
0N/A * A fast buffered input stream for parsing manifest files.
0N/A */
0N/A static class FastInputStream extends FilterInputStream {
0N/A private byte buf[];
0N/A private int count = 0;
0N/A private int pos = 0;
0N/A
0N/A FastInputStream(InputStream in) {
0N/A this(in, 8192);
0N/A }
0N/A
0N/A FastInputStream(InputStream in, int size) {
0N/A super(in);
0N/A buf = new byte[size];
0N/A }
0N/A
0N/A public int read() throws IOException {
0N/A if (pos >= count) {
0N/A fill();
0N/A if (pos >= count) {
0N/A return -1;
0N/A }
0N/A }
0N/A return buf[pos++] & 0xff;
0N/A }
0N/A
0N/A public int read(byte[] b, int off, int len) throws IOException {
0N/A int avail = count - pos;
0N/A if (avail <= 0) {
0N/A if (len >= buf.length) {
0N/A return in.read(b, off, len);
0N/A }
0N/A fill();
0N/A avail = count - pos;
0N/A if (avail <= 0) {
0N/A return -1;
0N/A }
0N/A }
0N/A if (len > avail) {
0N/A len = avail;
0N/A }
0N/A System.arraycopy(buf, pos, b, off, len);
0N/A pos += len;
0N/A return len;
0N/A }
0N/A
0N/A /*
0N/A * Reads 'len' bytes from the input stream, or until an end-of-line
0N/A * is reached. Returns the number of bytes read.
0N/A */
0N/A public int readLine(byte[] b, int off, int len) throws IOException {
0N/A byte[] tbuf = this.buf;
0N/A int total = 0;
0N/A while (total < len) {
0N/A int avail = count - pos;
0N/A if (avail <= 0) {
0N/A fill();
0N/A avail = count - pos;
0N/A if (avail <= 0) {
0N/A return -1;
0N/A }
0N/A }
0N/A int n = len - total;
0N/A if (n > avail) {
0N/A n = avail;
0N/A }
0N/A int tpos = pos;
0N/A int maxpos = tpos + n;
0N/A while (tpos < maxpos && tbuf[tpos++] != '\n') ;
0N/A n = tpos - pos;
0N/A System.arraycopy(tbuf, pos, b, off, n);
0N/A off += n;
0N/A total += n;
0N/A pos = tpos;
0N/A if (tbuf[tpos-1] == '\n') {
0N/A break;
0N/A }
0N/A }
0N/A return total;
0N/A }
0N/A
0N/A public byte peek() throws IOException {
0N/A if (pos == count)
0N/A fill();
4857N/A if (pos == count)
4857N/A return -1; // nothing left in buffer
0N/A return buf[pos];
0N/A }
0N/A
0N/A public int readLine(byte[] b) throws IOException {
0N/A return readLine(b, 0, b.length);
0N/A }
0N/A
0N/A public long skip(long n) throws IOException {
0N/A if (n <= 0) {
0N/A return 0;
0N/A }
0N/A long avail = count - pos;
0N/A if (avail <= 0) {
0N/A return in.skip(n);
0N/A }
0N/A if (n > avail) {
0N/A n = avail;
0N/A }
0N/A pos += n;
0N/A return n;
0N/A }
0N/A
0N/A public int available() throws IOException {
0N/A return (count - pos) + in.available();
0N/A }
0N/A
0N/A public void close() throws IOException {
0N/A if (in != null) {
0N/A in.close();
0N/A in = null;
0N/A buf = null;
0N/A }
0N/A }
0N/A
0N/A private void fill() throws IOException {
0N/A count = pos = 0;
0N/A int n = in.read(buf, 0, buf.length);
0N/A if (n > 0) {
0N/A count = n;
0N/A }
0N/A }
0N/A }
0N/A}