Home » openjdk-7 » java » util » jar » [javadoc | source]

    1   /*
    2    * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   
   26   package java.util.jar;
   27   
   28   import java.io.FilterInputStream;
   29   import java.io.DataOutputStream;
   30   import java.io.InputStream;
   31   import java.io.OutputStream;
   32   import java.io.IOException;
   33   import java.util.Map;
   34   import java.util.HashMap;
   35   import java.util.Iterator;
   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="../../../../technotes/guides/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 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 occured
   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).
   93        * The Map permits the {@code null} key, but no entry with a null key is
   94        * created by {@link #read}, nor is such an entry written by using {@link
   95        * #write}.
   96        *
   97        * @return a Map of the entries contained in this Manifest
   98        */
   99       public Map<String,Attributes> getEntries() {
  100           return entries;
  101       }
  102   
  103       /**
  104        * Returns the Attributes for the specified entry name.
  105        * This method is defined as:
  106        * <pre>
  107        *      return (Attributes)getEntries().get(name)
  108        * </pre>
  109        * Though {@code null} is a valid {@code name}, when
  110        * {@code getAttributes(null)} is invoked on a {@code Manifest}
  111        * obtained from a jar file, {@code null} will be returned.  While jar
  112        * files themselves do not allow {@code null}-named attributes, it is
  113        * possible to invoke {@link #getEntries} on a {@code Manifest}, and
  114        * on that result, invoke {@code put} with a null key and an
  115        * arbitrary value.  Subsequent invocations of
  116        * {@code getAttributes(null)} will return the just-{@code put}
  117        * value.
  118        * <p>
  119        * Note that this method does not return the manifest's main attributes;
  120        * see {@link #getMainAttributes}.
  121        *
  122        * @param name entry name
  123        * @return the Attributes for the specified entry name
  124        */
  125       public Attributes getAttributes(String name) {
  126           return getEntries().get(name);
  127       }
  128   
  129       /**
  130        * Clears the main Attributes as well as the entries in this Manifest.
  131        */
  132       public void clear() {
  133           attr.clear();
  134           entries.clear();
  135       }
  136   
  137       /**
  138        * Writes the Manifest to the specified OutputStream.
  139        * Attributes.Name.MANIFEST_VERSION must be set in
  140        * MainAttributes prior to invoking this method.
  141        *
  142        * @param out the output stream
  143        * @exception IOException if an I/O error has occurred
  144        * @see #getMainAttributes
  145        */
  146       public void write(OutputStream out) throws IOException {
  147           DataOutputStream dos = new DataOutputStream(out);
  148           // Write out the main attributes for the manifest
  149           attr.writeMain(dos);
  150           // Now write out the pre-entry attributes
  151           Iterator it = entries.entrySet().iterator();
  152           while (it.hasNext()) {
  153               Map.Entry e = (Map.Entry)it.next();
  154               StringBuffer buffer = new StringBuffer("Name: ");
  155               String value = (String)e.getKey();
  156               if (value != null) {
  157                   byte[] vb = value.getBytes("UTF8");
  158                   value = new String(vb, 0, 0, vb.length);
  159               }
  160               buffer.append(value);
  161               buffer.append("\r\n");
  162               make72Safe(buffer);
  163               dos.writeBytes(buffer.toString());
  164               ((Attributes)e.getValue()).write(dos);
  165           }
  166           dos.flush();
  167       }
  168   
  169       /**
  170        * Adds line breaks to enforce a maximum 72 bytes per line.
  171        */
  172       static void make72Safe(StringBuffer line) {
  173           int length = line.length();
  174           if (length > 72) {
  175               int index = 70;
  176               while (index < length - 2) {
  177                   line.insert(index, "\r\n ");
  178                   index += 72;
  179                   length += 3;
  180               }
  181           }
  182           return;
  183       }
  184   
  185       /**
  186        * Reads the Manifest from the specified InputStream. The entry
  187        * names and attributes read will be merged in with the current
  188        * manifest entries.
  189        *
  190        * @param is the input stream
  191        * @exception IOException if an I/O error has occurred
  192        */
  193       public void read(InputStream is) throws IOException {
  194           // Buffered input stream for reading manifest data
  195           FastInputStream fis = new FastInputStream(is);
  196           // Line buffer
  197           byte[] lbuf = new byte[512];
  198           // Read the main attributes for the manifest
  199           attr.read(fis, lbuf);
  200           // Total number of entries, attributes read
  201           int ecount = 0, acount = 0;
  202           // Average size of entry attributes
  203           int asize = 2;
  204           // Now parse the manifest entries
  205           int len;
  206           String name = null;
  207           boolean skipEmptyLines = true;
  208           byte[] lastline = null;
  209   
  210           while ((len = fis.readLine(lbuf)) != -1) {
  211               if (lbuf[--len] != '\n') {
  212                   throw new IOException("manifest line too long");
  213               }
  214               if (len > 0 && lbuf[len-1] == '\r') {
  215                   --len;
  216               }
  217               if (len == 0 && skipEmptyLines) {
  218                   continue;
  219               }
  220               skipEmptyLines = false;
  221   
  222               if (name == null) {
  223                   name = parseName(lbuf, len);
  224                   if (name == null) {
  225                       throw new IOException("invalid manifest format");
  226                   }
  227                   if (fis.peek() == ' ') {
  228                       // name is wrapped
  229                       lastline = new byte[len - 6];
  230                       System.arraycopy(lbuf, 6, lastline, 0, len - 6);
  231                       continue;
  232                   }
  233               } else {
  234                   // continuation line
  235                   byte[] buf = new byte[lastline.length + len - 1];
  236                   System.arraycopy(lastline, 0, buf, 0, lastline.length);
  237                   System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
  238                   if (fis.peek() == ' ') {
  239                       // name is wrapped
  240                       lastline = buf;
  241                       continue;
  242                   }
  243                   name = new String(buf, 0, buf.length, "UTF8");
  244                   lastline = null;
  245               }
  246               Attributes attr = getAttributes(name);
  247               if (attr == null) {
  248                   attr = new Attributes(asize);
  249                   entries.put(name, attr);
  250               }
  251               attr.read(fis, lbuf);
  252               ecount++;
  253               acount += attr.size();
  254               //XXX: Fix for when the average is 0. When it is 0,
  255               // you get an Attributes object with an initial
  256               // capacity of 0, which tickles a bug in HashMap.
  257               asize = Math.max(2, acount / ecount);
  258   
  259               name = null;
  260               skipEmptyLines = true;
  261           }
  262       }
  263   
  264       private String parseName(byte[] lbuf, int len) {
  265           if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' &&
  266               toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' &&
  267               lbuf[4] == ':' && lbuf[5] == ' ') {
  268               try {
  269                   return new String(lbuf, 6, len - 6, "UTF8");
  270               }
  271               catch (Exception e) {
  272               }
  273           }
  274           return null;
  275       }
  276   
  277       private int toLower(int c) {
  278           return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c;
  279       }
  280   
  281       /**
  282        * Returns true if the specified Object is also a Manifest and has
  283        * the same main Attributes and entries.
  284        *
  285        * @param o the object to be compared
  286        * @return true if the specified Object is also a Manifest and has
  287        * the same main Attributes and entries
  288        */
  289       public boolean equals(Object o) {
  290           if (o instanceof Manifest) {
  291               Manifest m = (Manifest)o;
  292               return attr.equals(m.getMainAttributes()) &&
  293                      entries.equals(m.getEntries());
  294           } else {
  295               return false;
  296           }
  297       }
  298   
  299       /**
  300        * Returns the hash code for this Manifest.
  301        */
  302       public int hashCode() {
  303           return attr.hashCode() + entries.hashCode();
  304       }
  305   
  306       /**
  307        * Returns a shallow copy of this Manifest.  The shallow copy is
  308        * implemented as follows:
  309        * <pre>
  310        *     public Object clone() { return new Manifest(this); }
  311        * </pre>
  312        * @return a shallow copy of this Manifest
  313        */
  314       public Object clone() {
  315           return new Manifest(this);
  316       }
  317   
  318       /*
  319        * A fast buffered input stream for parsing manifest files.
  320        */
  321       static class FastInputStream extends FilterInputStream {
  322           private byte buf[];
  323           private int count = 0;
  324           private int pos = 0;
  325   
  326           FastInputStream(InputStream in) {
  327               this(in, 8192);
  328           }
  329   
  330           FastInputStream(InputStream in, int size) {
  331               super(in);
  332               buf = new byte[size];
  333           }
  334   
  335           public int read() throws IOException {
  336               if (pos >= count) {
  337                   fill();
  338                   if (pos >= count) {
  339                       return -1;
  340                   }
  341               }
  342               return buf[pos++] & 0xff;
  343           }
  344   
  345           public int read(byte[] b, int off, int len) throws IOException {
  346               int avail = count - pos;
  347               if (avail <= 0) {
  348                   if (len >= buf.length) {
  349                       return in.read(b, off, len);
  350                   }
  351                   fill();
  352                   avail = count - pos;
  353                   if (avail <= 0) {
  354                       return -1;
  355                   }
  356               }
  357               if (len > avail) {
  358                   len = avail;
  359               }
  360               System.arraycopy(buf, pos, b, off, len);
  361               pos += len;
  362               return len;
  363           }
  364   
  365           /*
  366            * Reads 'len' bytes from the input stream, or until an end-of-line
  367            * is reached. Returns the number of bytes read.
  368            */
  369           public int readLine(byte[] b, int off, int len) throws IOException {
  370               byte[] tbuf = this.buf;
  371               int total = 0;
  372               while (total < len) {
  373                   int avail = count - pos;
  374                   if (avail <= 0) {
  375                       fill();
  376                       avail = count - pos;
  377                       if (avail <= 0) {
  378                           return -1;
  379                       }
  380                   }
  381                   int n = len - total;
  382                   if (n > avail) {
  383                       n = avail;
  384                   }
  385                   int tpos = pos;
  386                   int maxpos = tpos + n;
  387                   while (tpos < maxpos && tbuf[tpos++] != '\n') ;
  388                   n = tpos - pos;
  389                   System.arraycopy(tbuf, pos, b, off, n);
  390                   off += n;
  391                   total += n;
  392                   pos = tpos;
  393                   if (tbuf[tpos-1] == '\n') {
  394                       break;
  395                   }
  396               }
  397               return total;
  398           }
  399   
  400           public byte peek() throws IOException {
  401               if (pos == count)
  402                   fill();
  403               return buf[pos];
  404           }
  405   
  406           public int readLine(byte[] b) throws IOException {
  407               return readLine(b, 0, b.length);
  408           }
  409   
  410           public long skip(long n) throws IOException {
  411               if (n <= 0) {
  412                   return 0;
  413               }
  414               long avail = count - pos;
  415               if (avail <= 0) {
  416                   return in.skip(n);
  417               }
  418               if (n > avail) {
  419                   n = avail;
  420               }
  421               pos += n;
  422               return n;
  423           }
  424   
  425           public int available() throws IOException {
  426               return (count - pos) + in.available();
  427           }
  428   
  429           public void close() throws IOException {
  430               if (in != null) {
  431                   in.close();
  432                   in = null;
  433                   buf = null;
  434               }
  435           }
  436   
  437           private void fill() throws IOException {
  438               count = pos = 0;
  439               int n = in.read(buf, 0, buf.length);
  440               if (n > 0) {
  441                   count = n;
  442               }
  443           }
  444       }
  445   }

Home » openjdk-7 » java » util » jar » [javadoc | source]