I'm checking this in. This adds 'jar -i' support and also fixes some problems here and there in jar. It also cleans up a couple oddities in related areas that I noticed while working.
This is PR 27514. Tom 2006-05-14 Tom Tromey <[EMAIL PROTECTED]> PR classpath/27514: * gnu/java/net/IndexListParser.java (JAR_INDEX_FILE): Renamed. Now constant. (JAR_INDEX_VERSION_KEY): Likewise. (IndexListParser): Updated. (getVersionInfo): Likewise. * tools/gnu/classpath/tools/jar/Indexer.java: New file. * tools/gnu/classpath/tools/jar/Action.java (run): Now throws OptionException. * tools/gnu/classpath/tools/jar/Main.java (initializeParser): Handle -i. (ModeOption): New constructor. (parsed): Updated. Use setArchiveFile. (setArchiveFile): New method. (run): Handle no-argument case. (main): Emit --help message on option error. * tools/gnu/classpath/tools/jar/Updater.java (inputJar): New field. (createManifest): New method. (run): Updated. Throws OptionException. Correctly copy zip entry. * tools/gnu/classpath/tools/jar/Creator.java (createManifest): New method. (writeManifest): Removed. (outputStream): Now a JarOutputStream. (writeCommandLineEntries): Changed parameters. Updated callers. (run): Throws OptionException. * java/util/jar/JarOutputStream.java (putNextEntry): Typo fix. * java/util/jar/Manifest.java (read): Typo fix. Index: gnu/java/net/IndexListParser.java =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/net/IndexListParser.java,v retrieving revision 1.1 diff -u -r1.1 IndexListParser.java --- gnu/java/net/IndexListParser.java 8 May 2006 21:30:06 -0000 1.1 +++ gnu/java/net/IndexListParser.java 14 May 2006 20:29:21 -0000 @@ -63,8 +63,9 @@ */ public class IndexListParser { - String filePath = "META-INF/INDEX.LIST"; - String versInfo = "JarIndex-Version: "; + public static final String JAR_INDEX_FILE = "META-INF/INDEX.LIST"; + public static final String JAR_INDEX_VERSION_KEY = "JarIndex-Version: "; + double versionNumber; ArrayList headers = new ArrayList(); @@ -80,16 +81,16 @@ try { // Parse INDEX.LIST if it exists - if (jarfile.getEntry(filePath) != null) + if (jarfile.getEntry(JAR_INDEX_FILE) != null) { BufferedReader br = new BufferedReader(new InputStreamReader(new URL(baseJarURL, - filePath).openStream())); + JAR_INDEX_FILE).openStream())); // Must start with version info String line = br.readLine(); - if (!line.startsWith(versInfo)) + if (!line.startsWith(JAR_INDEX_VERSION_KEY)) return; - versionNumber = Double.parseDouble(line.substring(versInfo.length()).trim()); + versionNumber = Double.parseDouble(line.substring(JAR_INDEX_VERSION_KEY.length()).trim()); // Blank line must be next line = br.readLine(); @@ -134,7 +135,7 @@ */ public String getVersionInfo() { - return versInfo + getVersionNumber(); + return JAR_INDEX_VERSION_KEY + getVersionNumber(); } /** Index: java/util/jar/JarOutputStream.java =================================================================== RCS file: /cvsroot/classpath/classpath/java/util/jar/JarOutputStream.java,v retrieving revision 1.7 diff -u -r1.7 JarOutputStream.java --- java/util/jar/JarOutputStream.java 2 Jul 2005 20:32:44 -0000 1.7 +++ java/util/jar/JarOutputStream.java 14 May 2006 20:29:24 -0000 @@ -101,7 +101,7 @@ /** * Prepares the JarOutputStream for writing the next entry. - * This implementation just calls <code>super.putNextEntre()</code>. + * This implementation just calls <code>super.putNextEntry()</code>. * * @param entry The information for the next entry * @exception IOException when some unexpected I/O exception occurred Index: java/util/jar/Manifest.java =================================================================== RCS file: /cvsroot/classpath/classpath/java/util/jar/Manifest.java,v retrieving revision 1.12 diff -u -r1.12 Manifest.java --- java/util/jar/Manifest.java 25 Mar 2006 11:51:25 -0000 1.12 +++ java/util/jar/Manifest.java 14 May 2006 20:29:25 -0000 @@ -1,4 +1,4 @@ -/* Manifest.java -- Reads, writes and manipulaties jar manifest files +/* Manifest.java -- Reads, writes and manipulates jar manifest files Copyright (C) 2000, 2004 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -152,7 +152,7 @@ } /** - * Read and merge a <code>Mainfest</code> from the designated input stream. + * Read and merge a <code>Manifest</code> from the designated input stream. * * @param in the input stream to read from. * @throws IOException if an I/O related exception occurs during the process. Index: tools/gnu/classpath/tools/jar/Action.java =================================================================== RCS file: /cvsroot/classpath/classpath/tools/gnu/classpath/tools/jar/Action.java,v retrieving revision 1.1 diff -u -r1.1 Action.java --- tools/gnu/classpath/tools/jar/Action.java 8 May 2006 18:38:20 -0000 1.1 +++ tools/gnu/classpath/tools/jar/Action.java 14 May 2006 20:29:26 -0000 @@ -38,6 +38,8 @@ package gnu.classpath.tools.jar; +import gnu.classpath.tools.getopt.OptionException; + import java.io.IOException; public abstract class Action @@ -46,5 +48,6 @@ { } - public abstract void run(Main parameters) throws IOException; + public abstract void run(Main parameters) + throws IOException, OptionException; } Index: tools/gnu/classpath/tools/jar/Creator.java =================================================================== RCS file: /cvsroot/classpath/classpath/tools/gnu/classpath/tools/jar/Creator.java,v retrieving revision 1.2 diff -u -r1.2 Creator.java --- tools/gnu/classpath/tools/jar/Creator.java 8 May 2006 23:51:49 -0000 1.2 +++ tools/gnu/classpath/tools/jar/Creator.java 14 May 2006 20:29:26 -0000 @@ -38,10 +38,9 @@ package gnu.classpath.tools.jar; -import gnu.classpath.SystemProperties; +import gnu.classpath.tools.getopt.OptionException; import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -52,15 +51,19 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; import java.util.zip.CRC32; import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; public class Creator extends Action { - ZipOutputStream outputStream; + JarOutputStream outputStream; HashSet writtenItems = new HashSet(); + // The manifest to use, or null if we don't want a manifest. + Manifest manifest; private long copyFile(CRC32 crc, InputStream is, OutputStream output) throws IOException @@ -172,13 +175,12 @@ return allEntries; } - private void writeCommandLineEntries(Main parameters, ZipOutputStream os) + private void writeCommandLineEntries(Main parameters) throws IOException { - outputStream = os; - outputStream.setMethod(parameters.storageMode); - - writeManifest(parameters); + // We've already written the manifest, make sure to mark it. + writtenItems.add("META-INF/"); + writtenItems.add(JarFile.MANIFEST_NAME); ArrayList allEntries = getAllEntries(parameters); Iterator it = allEntries.iterator(); @@ -189,53 +191,47 @@ } } - protected void writeCommandLineEntries(Main parameters, File zipFile) + protected Manifest createManifest(Main parameters) throws IOException { - OutputStream os = new BufferedOutputStream(new FileOutputStream(zipFile)); - writeCommandLineEntries(parameters, new ZipOutputStream(os)); - } - - protected void writeManifest(Main parameters) throws IOException - { - File manifestFile; - InputStream contents; + if (! parameters.wantManifest) + return null; if (parameters.manifestFile != null) { // User specified a manifest file. - contents = new FileInputStream(parameters.manifestFile); + InputStream contents = new FileInputStream(parameters.manifestFile); + return new Manifest(contents); } - else if (! parameters.wantManifest) - { - // User didn't want a manifest. - return; - } - else - { - String desc = ("Manifest-Version: 1.0\n" - + "Created-By: " - + SystemProperties.getProperty("java.version") - + " (GNU Classpath)\n\n"); - contents = new ByteArrayInputStream(desc.getBytes("UTF-8")); - } - // Make the META-INF directory and the manifest file. - writeFile(true, null, "META-INF/", parameters.verbose); - writeFile(false, contents, "META-INF/MANIFEST.MF", parameters.verbose); + return new Manifest(); + } + + protected void writeCommandLineEntries(Main parameters, OutputStream os) + throws IOException + { + manifest = createManifest(parameters); + outputStream = new JarOutputStream(os, manifest); + // FIXME: in Classpath this sets the method too late for the + // manifest file. + outputStream.setMethod(parameters.storageMode); + writeCommandLineEntries(parameters); } protected void close() throws IOException { - // FIXME: handle index file here ...? outputStream.finish(); outputStream.close(); } - public void run(Main parameters) throws IOException + public void run(Main parameters) throws IOException, OptionException { if (parameters.archiveFile == null || parameters.archiveFile.equals("-")) - writeCommandLineEntries(parameters, new ZipOutputStream(System.out)); + writeCommandLineEntries(parameters, System.out); else - writeCommandLineEntries(parameters, parameters.archiveFile); + { + OutputStream os + = new BufferedOutputStream(new FileOutputStream(parameters.archiveFile)); + writeCommandLineEntries(parameters, os); + } close(); } } Index: tools/gnu/classpath/tools/jar/Main.java =================================================================== RCS file: /cvsroot/classpath/classpath/tools/gnu/classpath/tools/jar/Main.java,v retrieving revision 1.2 diff -u -r1.2 Main.java --- tools/gnu/classpath/tools/jar/Main.java 8 May 2006 23:51:49 -0000 1.2 +++ tools/gnu/classpath/tools/jar/Main.java 14 May 2006 20:29:26 -0000 @@ -82,6 +82,14 @@ /** Used only while parsing, holds the first argument for -C. */ String changedDirectory; + void setArchiveFile(String filename) throws OptionException + { + if (archiveFile != null) + throw new OptionException("archive file name already set to " + + archiveFile); + archiveFile = new File(filename); + } + class HandleFile extends FileArgumentCallback { @@ -112,24 +120,36 @@ this.mode = mode; } + public ModeOption(char shortName, String description, String argName, + Class mode) + { + super(shortName, description, argName); + this.mode = mode; + } + public void parsed(String argument) throws OptionException { if (operationMode != null) throw new OptionException("operation mode already specified"); operationMode = mode; + // We know this is only the case for -i. + if (argument != null) + setArchiveFile(argument); } } private Parser initializeParser() { Parser p = new ClasspathToolParser("jar"); - p.setHeader("Usage: jar -ctxu [OPTIONS] jar-file [-C DIR FILE] FILE..."); + p.setHeader("Usage: jar -ctxui [OPTIONS] jar-file [-C DIR FILE] FILE..."); OptionGroup grp = new OptionGroup("Operation mode"); grp.add(new ModeOption('c', "create a new archive", Creator.class)); grp.add(new ModeOption('x', "extract from archive", Extractor.class)); grp.add(new ModeOption('t', "list archive contents", Lister.class)); grp.add(new ModeOption('u', "update archive", Updater.class)); + // Note that -i works in-place and explicitly requires a file name. + grp.add(new ModeOption('i', "compute archive index", "FILE", Indexer.class)); p.add(grp); grp = new OptionGroup("Operation modifiers"); @@ -137,8 +157,7 @@ { public void parsed(String argument) throws OptionException { - // FIXME: error if already set. - archiveFile = new File(argument); + setArchiveFile(argument); } }); grp.add(new Option('0', "store only; no ZIP compression") @@ -182,7 +201,6 @@ } }); p.add(grp); - // -i - need to parse classes return p; } @@ -192,11 +210,11 @@ { Parser p = initializeParser(); // Special hack to emulate old tar-style commands. - if (args[0].charAt(0) != '-') + if (args.length > 0 && args[0].charAt(0) != '-') args[0] = '-' + args[0]; p.parse(args, new HandleFile()); if (operationMode == null) - throw new OptionException("must specify one of -t, -c, -u, or -x"); + throw new OptionException("must specify one of -t, -c, -u, -x, or -i"); if (changedDirectory != null) throw new OptionException("-C argument requires both directory and filename"); Action t = (Action) operationMode.newInstance(); @@ -213,6 +231,8 @@ catch (OptionException arg) { System.err.println("jar: " + arg.getMessage()); + // FIXME: this should be pushed into the parser somehow. + System.err.println("Try 'jar --help' for more information"); System.exit(1); } catch (Exception e) Index: tools/gnu/classpath/tools/jar/Updater.java =================================================================== RCS file: /cvsroot/classpath/classpath/tools/gnu/classpath/tools/jar/Updater.java,v retrieving revision 1.2 diff -u -r1.2 Updater.java --- tools/gnu/classpath/tools/jar/Updater.java 8 May 2006 23:51:49 -0000 1.2 +++ tools/gnu/classpath/tools/jar/Updater.java 14 May 2006 20:29:26 -0000 @@ -38,31 +38,53 @@ package gnu.classpath.tools.jar; +import gnu.classpath.tools.getopt.OptionException; + +import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.Enumeration; +import java.util.jar.JarFile; +import java.util.jar.Manifest; import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; public class Updater extends Creator { - public void run(Main parameters) throws IOException + JarFile inputJar; + + protected Manifest createManifest(Main parameters) throws IOException + { + Manifest result = inputJar.getManifest(); + if (result == null) + return super.createManifest(parameters); + if (parameters.manifestFile != null) + result.read(new FileInputStream(parameters.manifestFile)); + return result; + } + + public void run(Main parameters) throws IOException, OptionException { + // Set this early so that createManifest can use it. + inputJar = new JarFile(parameters.archiveFile); + // Write all the new entries to a temporary file. File tmpFile = File.createTempFile("jarcopy", null); - writeCommandLineEntries(parameters, tmpFile); + OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile)); + writeCommandLineEntries(parameters, os); // Now read the old file and copy extra entries to the new file. - ZipFile zip = new ZipFile(parameters.archiveFile); - Enumeration e = zip.entries(); + Enumeration e = inputJar.entries(); while (e.hasMoreElements()) { ZipEntry entry = (ZipEntry) e.nextElement(); if (writtenItems.contains(entry.getName())) continue; - writeFile(entry.isDirectory(), zip.getInputStream(entry), - zip.getName(), parameters.verbose); + writeFile(entry.isDirectory(), inputJar.getInputStream(entry), + entry.getName(), parameters.verbose); } close(); Index: tools/gnu/classpath/tools/jar/Indexer.java =================================================================== RCS file: tools/gnu/classpath/tools/jar/Indexer.java diff -N tools/gnu/classpath/tools/jar/Indexer.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ tools/gnu/classpath/tools/jar/Indexer.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,148 @@ +/* Indexer.java -- add index.list file to jar + Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.classpath.tools.jar; + +import gnu.classpath.tools.getopt.OptionException; +import gnu.java.net.IndexListParser; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.StringTokenizer; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +public class Indexer + extends Updater +{ + private void indexJarFile(StringBuffer result, File fileName, + boolean verbose) + throws IOException + { + if (verbose) + System.err.println("indexing: " + fileName); + JarFile jf = new JarFile(fileName); + + // Index the files in this jar. + HashSet entries = new HashSet(); + Enumeration e = jf.entries(); + while (e.hasMoreElements()) + { + JarEntry entry = (JarEntry) e.nextElement(); + String name = entry.getName(); + if (name.startsWith("META-INF/")) + continue; + int index = name.lastIndexOf('/'); + if (index != -1) + name = name.substring(0, index); + entries.add(name); + } + if (! entries.isEmpty()) + { + result.append(fileName); + // Any line ending will do. + result.append('\n'); + Iterator i = entries.iterator(); + while (i.hasNext()) + { + result.append(i.next()); + result.append('\n'); + } + // Paragraph break. + result.append('\n'); + } + + // Now read pointed-to jars. + Manifest m = jf.getManifest(); + if (m != null) + { + File parent = fileName.getParentFile(); + Attributes attrs = m.getMainAttributes(); + String jars = attrs.getValue(Attributes.Name.CLASS_PATH); + if (jars != null) + { + StringTokenizer st = new StringTokenizer(jars, " "); + while (st.hasMoreTokens()) + { + String name = st.nextToken(); + indexJarFile(result, new File(parent, name), verbose); + } + } + } + + jf.close(); + } + + protected void writeCommandLineEntries(Main parameters, OutputStream os) + throws IOException + { + // This is a pretty lame design. We know the super call will + // only have side effects and won't actually write anything important. + super.writeCommandLineEntries(parameters, os); + + // Now compute our index file and write it. + StringBuffer contents = new StringBuffer(); + indexJarFile(contents, parameters.archiveFile, parameters.verbose); + if (contents.length() != 0) + { + contents.insert(0, (IndexListParser.JAR_INDEX_VERSION_KEY + + "1.0\n\n")); + ByteArrayInputStream in + = new ByteArrayInputStream(contents.toString().getBytes()); + writeFile(false, in, IndexListParser.JAR_INDEX_FILE, parameters.verbose); + } + } + + public void run(Main parameters) throws IOException, OptionException + { + if (! parameters.entries.isEmpty()) + throw new OptionException("can't specify file arguments when using -i"); + if (! parameters.wantManifest) + throw new OptionException("can't specify -M with -i"); + if (parameters.manifestFile != null) + throw new OptionException("can't specify -m with -i"); + super.run(parameters); + } +}