Source for java.util.jar.Manifest

   1: /* Manifest.java -- Reads, writes and manipulaties jar manifest files
   2:    Copyright (C) 2000, 2004 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   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)
   9: any later version.
  10: 
  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.
  15: 
  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., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  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
  24: combination.
  25: 
  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. */
  37: 
  38: package java.util.jar;
  39: 
  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;
  50: import java.util.Map;
  51: 
  52: /**
  53:  * Reads, writes and manipulaties jar manifest files.
  54:  * XXX
  55:  * 
  56:  * @since 1.2
  57:  * @author Mark Wielaard (mark@klomp.org)
  58:  */
  59: public class Manifest implements Cloneable
  60: {
  61:   // Fields
  62: 
  63:   /** The main attributes of the manifest (jar file). */
  64:   private final Attributes mainAttr;
  65: 
  66:   /** A map of atrributes for all entries described in this Manifest. */
  67:   private final Map entries;
  68: 
  69:   // Constructors
  70: 
  71:   /**
  72:    * Creates a new empty Manifest.
  73:    */
  74:   public Manifest()
  75:   {
  76:     mainAttr = new Attributes();
  77:     entries = new Hashtable();
  78:   }
  79: 
  80:   /**
  81:    * Creates a Manifest from the supplied input stream.
  82:    *
  83:    * @see #read(InputStream)
  84:    * @see #write(OutputStream)
  85:    *
  86:    * @param in 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
  89:    */
  90:   public Manifest(InputStream in) throws IOException
  91:   {
  92:     this();
  93:     read(in);
  94:   }
  95: 
  96:   /**
  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
 103:    * original manifest.
 104:    *
 105:    * @see #clone()
 106:    * @param man the Manifest to copy from
 107:    */
 108:   public Manifest(Manifest man)
 109:   {
 110:     mainAttr = new Attributes(man.getMainAttributes());
 111:     entries = new Hashtable(man.getEntries());
 112:   }
 113: 
 114:   // Methods
 115: 
 116:   /**
 117:    * Gets the main attributes of this Manifest.
 118:    */
 119:   public Attributes getMainAttributes()
 120:   {
 121:     return mainAttr;
 122:   }
 123: 
 124:   /**
 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.
 128:    */
 129:   public Map getEntries()
 130:   {
 131:     return entries;
 132:   }
 133: 
 134:   /**
 135:    * Returns the Attributes associated with the Entry.
 136:    * <p>
 137:    * Implemented as:
 138:    * <code>return (Attributes)getEntries().get(entryName)</code>
 139:    *
 140:    * @param entryName the name of the entry to look up
 141:    * @return the attributes associated with the entry or null when none
 142:    */
 143:   public Attributes getAttributes(String entryName)
 144:   {
 145:     return (Attributes) getEntries().get(entryName);
 146:   }
 147: 
 148:   /**
 149:    * Clears the main attributes and removes all the entries from the
 150:    * manifest.
 151:    */
 152:   public void clear()
 153:   {
 154:     mainAttr.clear();
 155:     entries.clear();
 156:   }
 157: 
 158:   /**
 159:    * XXX
 160:    */
 161:   public void read(InputStream in) throws IOException
 162:   {
 163:     BufferedReader br =
 164:       new BufferedReader(new InputStreamReader(in, "8859_1"));
 165:     read_main_section(getMainAttributes(), br);
 166:     read_individual_sections(getEntries(), br);
 167:   }
 168: 
 169:   // Private Static methods for reading the Manifest file from BufferedReader
 170: 
 171:   private static void read_main_section(Attributes attr,
 172:                     BufferedReader br) throws IOException
 173:   {
 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");
 180:   }
 181: 
 182:   /**
 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.
 186:    */
 187:   private static void read_version_info(Attributes attr,
 188:                     BufferedReader br) throws IOException
 189:   {
 190:     String version_header = Attributes.Name.MANIFEST_VERSION.toString();
 191:     try
 192:       {
 193:     String value = expect_header(version_header, br);
 194:     attr.putValue(Attributes.Name.MANIFEST_VERSION, value);
 195:       }
 196:     catch (IOException ioe)
 197:       {
 198:     throw new JarException("Manifest should start with a " +
 199:                    version_header + ": " + ioe.getMessage());
 200:       }
 201:   }
 202: 
 203:   private static String expect_header(String header, BufferedReader br)
 204:     throws IOException
 205:   {
 206:     String s = br.readLine();
 207:     if (s == null)
 208:       {
 209:     throw new JarException("unexpected end of file");
 210:       }
 211:     return expect_header(header, br, s);
 212:   }
 213: 
 214:   private static String expect_header(String header, BufferedReader br,
 215:                       String s) throws IOException
 216:   {
 217:     try
 218:       {
 219:     String name = s.substring(0, header.length() + 1);
 220:     if (name.equalsIgnoreCase(header + ":"))
 221:       {
 222:         String value_start = s.substring(header.length() + 2);
 223:         return read_header_value(value_start, br);
 224:       }
 225:       }
 226:     catch (IndexOutOfBoundsException iobe)
 227:       {
 228:       }
 229:     // If we arrive here, something went wrong
 230:     throw new JarException("unexpected '" + s + "'");
 231:   }
 232: 
 233:   private static String read_header_value(String s, BufferedReader br)
 234:     throws IOException
 235:   {
 236:     boolean try_next = true;
 237:     while (try_next)
 238:       {
 239:     // Lets see if there is something on the next line
 240:     br.mark(1);
 241:     if (br.read() == ' ')
 242:       {
 243:         s += br.readLine();
 244:       }
 245:     else
 246:       {
 247:         br.reset();
 248:         try_next = false;
 249:       }
 250:       }
 251:     return s;
 252:   }
 253: 
 254:   private static void read_attributes(Attributes attr,
 255:                       BufferedReader br) throws IOException
 256:   {
 257:     String s = br.readLine();
 258:     while (s != null && (!s.equals("")))
 259:       {
 260:     read_attribute(attr, s, br);
 261:     s = br.readLine();
 262:       }
 263:   }
 264: 
 265:   private static void read_attribute(Attributes attr, String s,
 266:                      BufferedReader br) throws IOException
 267:   {
 268:     try
 269:       {
 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);
 275:       }
 276:     catch (IndexOutOfBoundsException iobe)
 277:       {
 278:     throw new JarException("Manifest contains a bad header: " + s);
 279:       }
 280:   }
 281: 
 282:   private static void read_individual_sections(Map entries,
 283:                            BufferedReader br) throws
 284:     IOException
 285:   {
 286:     String s = br.readLine();
 287:     while (s != null && (!s.equals("")))
 288:       {
 289:     Attributes attr = read_section_name(s, br, entries);
 290:     read_attributes(attr, br);
 291:     s = br.readLine();
 292:       }
 293:   }
 294: 
 295:   private static Attributes read_section_name(String s, BufferedReader br,
 296:                           Map entries) throws JarException
 297:   {
 298:     try
 299:       {
 300:     String name = expect_header("Name", br, s);
 301:     Attributes attr = new Attributes();
 302:     entries.put(name, attr);
 303:     return attr;
 304:       }
 305:     catch (IOException ioe)
 306:       {
 307:     throw new JarException
 308:       ("Section should start with a Name header: " + ioe.getMessage());
 309:       }
 310:   }
 311: 
 312:   /**
 313:    * XXX
 314:    */
 315:   public void write(OutputStream out) throws IOException
 316:   {
 317:     PrintWriter pw =
 318:       new PrintWriter(new
 319:               BufferedWriter(new OutputStreamWriter(out, "8859_1")));
 320:     write_main_section(getMainAttributes(), pw);
 321:     pw.println();
 322:     write_individual_sections(getEntries(), pw);
 323:     if (pw.checkError())
 324:       {
 325:     throw new JarException("Error while writing manifest");
 326:       }
 327:   }
 328: 
 329:   // Private Static functions for writing the Manifest file to a PrintWriter
 330: 
 331:   private static void write_main_section(Attributes attr,
 332:                      PrintWriter pw) throws JarException
 333:   {
 334:     write_version_info(attr, pw);
 335:     write_main_attributes(attr, pw);
 336:   }
 337: 
 338:   private static void write_version_info(Attributes attr, PrintWriter pw)
 339:   {
 340:     // First check if there is already a version attribute set
 341:     String version = attr.getValue(Attributes.Name.MANIFEST_VERSION);
 342:     if (version == null)
 343:       {
 344:     version = "1.0";
 345:       }
 346:     write_header(Attributes.Name.MANIFEST_VERSION.toString(), version, pw);
 347:   }
 348: 
 349:   private static void write_header(String name, String value, PrintWriter pw)
 350:   {
 351:     pw.print(name + ": ");
 352: 
 353:     int last = 68 - name.length();
 354:     if (last > value.length())
 355:       {
 356:     pw.println(value);
 357:       }
 358:     else
 359:       {
 360:     pw.println(value.substring(0, last));
 361:       }
 362:     while (last < value.length())
 363:       {
 364:     pw.print(" ");
 365:     int end = (last + 69);
 366:     if (end > value.length())
 367:       {
 368:         pw.println(value.substring(last));
 369:       }
 370:     else
 371:       {
 372:         pw.println(value.substring(last, end));
 373:       }
 374:     last = end;
 375:       }
 376:   }
 377: 
 378:   private static void write_main_attributes(Attributes attr, PrintWriter pw) 
 379:     throws JarException
 380:   {
 381:     Iterator it = attr.entrySet().iterator();
 382:     while (it.hasNext())
 383:       {
 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()))
 387:       {
 388:         write_attribute_entry(entry, pw);
 389:       }
 390:       }
 391:   }
 392: 
 393:   private static void write_attribute_entry(Map.Entry entry, PrintWriter pw) 
 394:     throws JarException
 395:   {
 396:     String name = entry.getKey().toString();
 397:     String value = entry.getValue().toString();
 398: 
 399:     if (name.equalsIgnoreCase("Name"))
 400:       {
 401:     throw new JarException("Attributes cannot be called 'Name'");
 402:       }
 403:     if (name.startsWith("From"))
 404:       {
 405:     throw new
 406:       JarException("Header cannot start with the four letters 'From'" +
 407:                name);
 408:       }
 409:     write_header(name, value, pw);
 410:   }
 411: 
 412:   private static void write_individual_sections(Map entries, PrintWriter pw)
 413:     throws JarException
 414:   {
 415: 
 416:     Iterator it = entries.entrySet().iterator();
 417:     while (it.hasNext())
 418:       {
 419:     Map.Entry entry = (Map.Entry) it.next();
 420:     write_header("Name", entry.getKey().toString(), pw);
 421:     write_entry_attributes((Attributes) entry.getValue(), pw);
 422:     pw.println();
 423:       }
 424:   }
 425: 
 426:   private static void write_entry_attributes(Attributes attr, PrintWriter pw) 
 427:     throws JarException
 428:   {
 429:     Iterator it = attr.entrySet().iterator();
 430:     while (it.hasNext())
 431:       {
 432:     Map.Entry entry = (Map.Entry) it.next();
 433:     write_attribute_entry(entry, pw);
 434:       }
 435:   }
 436: 
 437:   /**
 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>.
 444:    */
 445:   public Object clone()
 446:   {
 447:     return new Manifest(this);
 448:   }
 449: 
 450:   /**
 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.
 455:    */
 456:   public boolean equals(Object o)
 457:   {
 458:     return (o instanceof Manifest) &&
 459:       (mainAttr.equals(((Manifest) o).mainAttr)) &&
 460:       (entries.equals(((Manifest) o).entries));
 461:   }
 462: 
 463:   /**
 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.
 466:    */
 467:   public int hashCode()
 468:   {
 469:     return mainAttr.hashCode() ^ entries.hashCode();
 470:   }
 471: 
 472: }