0001: /*
0002: * Copyright 1996-2007 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package sun.tools.jar;
0027:
0028: import java.io.*;
0029: import java.util.*;
0030: import java.util.zip.*;
0031: import java.util.jar.*;
0032: import java.util.jar.Manifest;
0033: import java.text.MessageFormat;
0034: import sun.misc.JarIndex;
0035:
0036: /**
0037: * This class implements a simple utility for creating files in the JAR
0038: * (Java Archive) file format. The JAR format is based on the ZIP file
0039: * format, with optional meta-information stored in a MANIFEST entry.
0040: */
0041: public class Main {
0042: String program;
0043: PrintStream out, err;
0044: String fname, mname, ename;
0045: String zname = "";
0046: String[] files;
0047: String rootjar = null;
0048: Hashtable filesTable = new Hashtable();
0049: Vector paths = new Vector();
0050: Vector v;
0051: CRC32 crc32 = new CRC32();
0052: /*
0053: * cflag: create
0054: * uflag: update
0055: * xflag: xtract
0056: * tflag: table
0057: * vflag: verbose
0058: * flag0: no zip compression (store only)
0059: * Mflag: DO NOT generate a manifest file (just ZIP)
0060: * iflag: generate jar index
0061: */
0062: boolean cflag, uflag, xflag, tflag, vflag, flag0, Mflag, iflag;
0063:
0064: static final String MANIFEST = JarFile.MANIFEST_NAME;
0065: static final String MANIFEST_DIR = "META-INF/";
0066: static final String VERSION = "1.0";
0067: static final char SEPARATOR = File.separatorChar;
0068: static final String INDEX = JarIndex.INDEX_NAME;
0069:
0070: private static ResourceBundle rsrc;
0071:
0072: /**
0073: * If true, maintain compatibility with JDK releases prior to 6.0 by
0074: * timestamping extracted files with the time at which they are extracted.
0075: * Default is to use the time given in the archive.
0076: */
0077: private static final boolean useExtractionTime = Boolean
0078: .getBoolean("sun.tools.jar.useExtractionTime");
0079:
0080: /**
0081: * Initialize ResourceBundle
0082: */
0083: static {
0084: try {
0085: rsrc = ResourceBundle
0086: .getBundle("sun.tools.jar.resources.jar");
0087: } catch (MissingResourceException e) {
0088: throw new Error("Fatal: Resource for jar is missing");
0089: }
0090: }
0091:
0092: private String getMsg(String key) {
0093: try {
0094: return (rsrc.getString(key));
0095: } catch (MissingResourceException e) {
0096: throw new Error("Error in message file");
0097: }
0098: }
0099:
0100: private String formatMsg(String key, String arg) {
0101: String msg = getMsg(key);
0102: String[] args = new String[1];
0103: args[0] = arg;
0104: return MessageFormat.format(msg, (Object[]) args);
0105: }
0106:
0107: private String formatMsg2(String key, String arg, String arg1) {
0108: String msg = getMsg(key);
0109: String[] args = new String[2];
0110: args[0] = arg;
0111: args[1] = arg1;
0112: return MessageFormat.format(msg, (Object[]) args);
0113: }
0114:
0115: public Main(PrintStream out, PrintStream err, String program) {
0116: this .out = out;
0117: this .err = err;
0118: this .program = program;
0119: }
0120:
0121: private boolean ok;
0122:
0123: /*
0124: * Starts main program with the specified arguments.
0125: */
0126: public synchronized boolean run(String args[]) {
0127: ok = true;
0128: if (!parseArgs(args)) {
0129: return false;
0130: }
0131: try {
0132: if (cflag || uflag) {
0133: if (fname != null) {
0134: // The name of the zip file as it would appear as its own
0135: // zip file entry. We use this to make sure that we don't
0136: // add the zip file to itself.
0137: zname = fname.replace(File.separatorChar, '/');
0138: if (zname.startsWith("./")) {
0139: zname = zname.substring(2);
0140: }
0141: }
0142: }
0143: if (cflag) {
0144: Manifest manifest = null;
0145: InputStream in = null;
0146:
0147: if (!Mflag) {
0148: if (mname != null) {
0149: in = new FileInputStream(mname);
0150: manifest = new Manifest(
0151: new BufferedInputStream(in));
0152: } else {
0153: manifest = new Manifest();
0154: }
0155: addVersion(manifest);
0156: addCreatedBy(manifest);
0157: if (isAmbigousMainClass(manifest)) {
0158: if (in != null) {
0159: in.close();
0160: }
0161: return false;
0162: }
0163: if (ename != null) {
0164: addMainClass(manifest, ename);
0165: }
0166: }
0167: OutputStream out;
0168: if (fname != null) {
0169: out = new FileOutputStream(fname);
0170: } else {
0171: out = new FileOutputStream(FileDescriptor.out);
0172: if (vflag) {
0173: // Disable verbose output so that it does not appear
0174: // on stdout along with file data
0175: // error("Warning: -v option ignored");
0176: vflag = false;
0177: }
0178: }
0179: create(new BufferedOutputStream(out), expand(files),
0180: manifest);
0181: if (in != null) {
0182: in.close();
0183: }
0184: out.close();
0185: } else if (uflag) {
0186: File inputFile = null, tmpFile = null;
0187: FileInputStream in;
0188: FileOutputStream out;
0189: if (fname != null) {
0190: inputFile = new File(fname);
0191: String path = inputFile.getParent();
0192: tmpFile = File.createTempFile("tmp", null,
0193: new File((path == null) ? "." : path));
0194: in = new FileInputStream(inputFile);
0195: out = new FileOutputStream(tmpFile);
0196: } else {
0197: in = new FileInputStream(FileDescriptor.in);
0198: out = new FileOutputStream(FileDescriptor.out);
0199: vflag = false;
0200: }
0201: InputStream manifest = (!Mflag && (mname != null)) ? (new FileInputStream(
0202: mname))
0203: : null;
0204: expand(files);
0205: boolean updateOk = update(in, new BufferedOutputStream(
0206: out), manifest);
0207: if (ok) {
0208: ok = updateOk;
0209: }
0210: in.close();
0211: out.close();
0212: if (manifest != null) {
0213: manifest.close();
0214: }
0215: if (fname != null) {
0216: // on Win32, we need this delete
0217: inputFile.delete();
0218: if (!tmpFile.renameTo(inputFile)) {
0219: tmpFile.delete();
0220: throw new IOException(
0221: getMsg("error.write.file"));
0222: }
0223: tmpFile.delete();
0224: }
0225: } else if (xflag || tflag) {
0226: InputStream in;
0227: if (fname != null) {
0228: in = new FileInputStream(fname);
0229: } else {
0230: in = new FileInputStream(FileDescriptor.in);
0231: }
0232: if (xflag) {
0233: extract(new BufferedInputStream(in), files);
0234: } else {
0235: list(new BufferedInputStream(in), files);
0236: }
0237: in.close();
0238: } else if (iflag) {
0239: genIndex(rootjar, files);
0240: }
0241: } catch (IOException e) {
0242: fatalError(e);
0243: ok = false;
0244: } catch (Error ee) {
0245: ee.printStackTrace();
0246: ok = false;
0247: } catch (Throwable t) {
0248: t.printStackTrace();
0249: ok = false;
0250: }
0251: out.flush();
0252: err.flush();
0253: return ok;
0254: }
0255:
0256: /*
0257: * Parse command line arguments.
0258: */
0259: boolean parseArgs(String args[]) {
0260: /* Preprocess and expand @file arguments */
0261: try {
0262: args = CommandLine.parse(args);
0263: } catch (FileNotFoundException e) {
0264: fatalError(formatMsg("error.cant.open", e.getMessage()));
0265: return false;
0266: } catch (IOException e) {
0267: fatalError(e);
0268: return false;
0269: }
0270: /* parse flags */
0271: int count = 1;
0272: try {
0273: String flags = args[0];
0274: if (flags.startsWith("-")) {
0275: flags = flags.substring(1);
0276: }
0277: for (int i = 0; i < flags.length(); i++) {
0278: switch (flags.charAt(i)) {
0279: case 'c':
0280: if (xflag || tflag || uflag) {
0281: usageError();
0282: return false;
0283: }
0284: cflag = true;
0285: break;
0286: case 'u':
0287: if (cflag || xflag || tflag) {
0288: usageError();
0289: return false;
0290: }
0291: uflag = true;
0292: break;
0293: case 'x':
0294: if (cflag || uflag || tflag) {
0295: usageError();
0296: return false;
0297: }
0298: xflag = true;
0299: break;
0300: case 't':
0301: if (cflag || uflag || xflag) {
0302: usageError();
0303: return false;
0304: }
0305: tflag = true;
0306: break;
0307: case 'M':
0308: Mflag = true;
0309: break;
0310: case 'v':
0311: vflag = true;
0312: break;
0313: case 'f':
0314: fname = args[count++];
0315: break;
0316: case 'm':
0317: mname = args[count++];
0318: break;
0319: case '0':
0320: flag0 = true;
0321: break;
0322: case 'i':
0323: // do not increase the counter, files will contain rootjar
0324: rootjar = args[count++];
0325: iflag = true;
0326: break;
0327: case 'e':
0328: ename = args[count++];
0329: break;
0330: default:
0331: error(formatMsg("error.illegal.option", String
0332: .valueOf(flags.charAt(i))));
0333: usageError();
0334: return false;
0335: }
0336: }
0337: } catch (ArrayIndexOutOfBoundsException e) {
0338: usageError();
0339: return false;
0340: }
0341: if (!cflag && !tflag && !xflag && !uflag && !iflag) {
0342: error(getMsg("error.bad.option"));
0343: usageError();
0344: return false;
0345: }
0346: /* parse file arguments */
0347: int n = args.length - count;
0348: if (n > 0) {
0349: int k = 0;
0350: String[] nameBuf = new String[n];
0351: try {
0352: for (int i = count; i < args.length; i++) {
0353: if (args[i].equals("-C")) {
0354: /* change the directory */
0355: String dir = args[++i];
0356: dir = (dir.endsWith(File.separator) ? dir
0357: : (dir + File.separator));
0358: dir = dir.replace(File.separatorChar, '/');
0359: while (dir.indexOf("//") > -1) {
0360: dir = dir.replace("//", "/");
0361: }
0362: paths.addElement(dir.replace(
0363: File.separatorChar, '/'));
0364: nameBuf[k++] = dir + args[++i];
0365: } else {
0366: nameBuf[k++] = args[i];
0367: }
0368: }
0369: } catch (ArrayIndexOutOfBoundsException e) {
0370: usageError();
0371: return false;
0372: }
0373: files = new String[k];
0374: System.arraycopy(nameBuf, 0, files, 0, k);
0375: } else if (cflag && (mname == null)) {
0376: error(getMsg("error.bad.cflag"));
0377: usageError();
0378: return false;
0379: } else if (uflag) {
0380: if ((mname != null) || (ename != null)) {
0381: /* just want to update the manifest */
0382: return true;
0383: } else {
0384: error(getMsg("error.bad.uflag"));
0385: usageError();
0386: return false;
0387: }
0388: }
0389: return true;
0390: }
0391:
0392: /*
0393: * Expands list of files to process into full list of all files that
0394: * can be found by recursively descending directories.
0395: */
0396: String[] expand(String[] files) {
0397: v = new Vector();
0398: expand(null, files, v, filesTable);
0399: files = new String[v.size()];
0400: for (int i = 0; i < files.length; i++) {
0401: files[i] = ((File) v.elementAt(i)).getPath();
0402: }
0403: return files;
0404: }
0405:
0406: void expand(File dir, String[] files, Vector v, Hashtable t) {
0407: if (files == null) {
0408: return;
0409: }
0410: for (int i = 0; i < files.length; i++) {
0411: File f;
0412: if (dir == null) {
0413: f = new File(files[i]);
0414: } else {
0415: f = new File(dir, files[i]);
0416: }
0417: if (f.isFile()) {
0418: if (!t.contains(f)) {
0419: t.put(entryName(f.getPath()), f);
0420: v.addElement(f);
0421: }
0422: } else if (f.isDirectory()) {
0423: String dirPath = f.getPath();
0424: dirPath = (dirPath.endsWith(File.separator)) ? dirPath
0425: : (dirPath + File.separator);
0426: t.put(entryName(dirPath), f);
0427: v.addElement(f);
0428: expand(f, f.list(), v, t);
0429: } else {
0430: error(formatMsg("error.nosuch.fileordir", String
0431: .valueOf(f)));
0432: ok = false;
0433: }
0434: }
0435: }
0436:
0437: /*
0438: * Creates a new JAR file.
0439: */
0440: void create(OutputStream out, String[] files, Manifest manifest)
0441: throws IOException {
0442: ZipOutputStream zos = new JarOutputStream(out);
0443: if (flag0) {
0444: zos.setMethod(ZipOutputStream.STORED);
0445: }
0446: if (manifest != null) {
0447: if (vflag) {
0448: output(getMsg("out.added.manifest"));
0449: }
0450: ZipEntry e = new ZipEntry(MANIFEST_DIR);
0451: e.setTime(System.currentTimeMillis());
0452: e.setSize(0);
0453: e.setCrc(0);
0454: zos.putNextEntry(e);
0455: e = new ZipEntry(MANIFEST);
0456: e.setTime(System.currentTimeMillis());
0457: if (flag0) {
0458: crc32Manifest(e, manifest);
0459: }
0460: zos.putNextEntry(e);
0461: manifest.write(zos);
0462: zos.closeEntry();
0463: }
0464: for (int i = 0; i < files.length; i++) {
0465: addFile(zos, new File(files[i]));
0466: }
0467: zos.close();
0468: }
0469:
0470: /*
0471: * update an existing jar file.
0472: */
0473: boolean update(InputStream in, OutputStream out,
0474: InputStream newManifest) throws IOException {
0475: Hashtable t = filesTable;
0476: Vector v = this .v;
0477: ZipInputStream zis = new ZipInputStream(in);
0478: ZipOutputStream zos = new JarOutputStream(out);
0479: ZipEntry e = null;
0480: boolean foundManifest = false;
0481: byte[] buf = new byte[1024];
0482: int n = 0;
0483: boolean updateOk = true;
0484:
0485: if (t.containsKey(INDEX)) {
0486: addIndex((JarIndex) t.get(INDEX), zos);
0487: }
0488:
0489: // put the old entries first, replace if necessary
0490: while ((e = zis.getNextEntry()) != null) {
0491: String name = e.getName();
0492:
0493: boolean isManifestEntry = name.toUpperCase(
0494: java.util.Locale.ENGLISH).equals(MANIFEST);
0495: if ((name.toUpperCase().equals(INDEX) && t
0496: .containsKey(INDEX))
0497: || (Mflag && isManifestEntry)) {
0498: continue;
0499: } else if (isManifestEntry
0500: && ((newManifest != null) || (ename != null))) {
0501: foundManifest = true;
0502: if (newManifest != null) {
0503: // Don't read from the newManifest InputStream, as we
0504: // might need it below, and we can't re-read the same data
0505: // twice.
0506: FileInputStream fis = new FileInputStream(mname);
0507: boolean ambigous = isAmbigousMainClass(new Manifest(
0508: fis));
0509: fis.close();
0510: if (ambigous) {
0511: return false;
0512: }
0513: }
0514:
0515: // Update the manifest.
0516: Manifest old = new Manifest(zis);
0517: if (newManifest != null) {
0518: old.read(newManifest);
0519: }
0520: updateManifest(old, zos);
0521: } else {
0522: if (!t.containsKey(name)) { // copy the old stuff
0523:
0524: // do our own compression
0525: ZipEntry e2 = new ZipEntry(name);
0526: e2.setMethod(e.getMethod());
0527: e2.setTime(e.getTime());
0528: e2.setComment(e.getComment());
0529: e2.setExtra(e.getExtra());
0530: if (e.getMethod() == ZipEntry.STORED) {
0531: e2.setSize(e.getSize());
0532: e2.setCrc(e.getCrc());
0533: }
0534: zos.putNextEntry(e2);
0535: while ((n = zis.read(buf, 0, buf.length)) != -1) {
0536: zos.write(buf, 0, n);
0537: }
0538: } else { // replace with the new files
0539: addFile(zos, (File) (t.get(name)));
0540: t.remove(name);
0541: }
0542: }
0543: }
0544: t.remove(INDEX);
0545:
0546: // add the remaining new files
0547: if (!t.isEmpty()) {
0548: for (int i = 0; i < v.size(); i++) {
0549: File f = (File) v.elementAt(i);
0550: if (t.containsValue(f)) {
0551: addFile(zos, f);
0552: }
0553: }
0554: }
0555: if (!foundManifest) {
0556: if (newManifest != null) {
0557: Manifest m = new Manifest(newManifest);
0558: updateOk = !isAmbigousMainClass(m);
0559: if (updateOk) {
0560: updateManifest(m, zos);
0561: }
0562: } else if (ename != null) {
0563: updateManifest(new Manifest(), zos);
0564: }
0565: }
0566: zis.close();
0567: zos.close();
0568: return updateOk;
0569: }
0570:
0571: private void addIndex(JarIndex index, ZipOutputStream zos)
0572: throws IOException {
0573: ZipEntry e = new ZipEntry(INDEX);
0574: e.setTime(System.currentTimeMillis());
0575: if (flag0) {
0576: e.setMethod(ZipEntry.STORED);
0577: File ifile = File.createTempFile("index", null, new File(
0578: "."));
0579: BufferedOutputStream bos = new BufferedOutputStream(
0580: new FileOutputStream(ifile));
0581: index.write(bos);
0582: crc32File(e, ifile);
0583: bos.close();
0584: ifile.delete();
0585: }
0586: zos.putNextEntry(e);
0587: index.write(zos);
0588: if (vflag) {
0589: // output(getMsg("out.update.manifest"));
0590: }
0591: }
0592:
0593: private void updateManifest(Manifest m, ZipOutputStream zos)
0594: throws IOException {
0595: addVersion(m);
0596: addCreatedBy(m);
0597: if (ename != null) {
0598: addMainClass(m, ename);
0599: }
0600: ZipEntry e = new ZipEntry(MANIFEST);
0601: e.setTime(System.currentTimeMillis());
0602: if (flag0) {
0603: e.setMethod(ZipEntry.STORED);
0604: crc32Manifest(e, m);
0605: }
0606: zos.putNextEntry(e);
0607: m.write(zos);
0608: if (vflag) {
0609: output(getMsg("out.update.manifest"));
0610: }
0611: }
0612:
0613: private String entryName(String name) {
0614: name = name.replace(File.separatorChar, '/');
0615: String matchPath = "";
0616: for (int i = 0; i < paths.size(); i++) {
0617: String path = (String) paths.elementAt(i);
0618: if (name.startsWith(path)
0619: && (path.length() > matchPath.length())) {
0620: matchPath = path;
0621: }
0622: }
0623: name = name.substring(matchPath.length());
0624:
0625: if (name.startsWith("/")) {
0626: name = name.substring(1);
0627: } else if (name.startsWith("./")) {
0628: name = name.substring(2);
0629: }
0630: return name;
0631: }
0632:
0633: private void addVersion(Manifest m) {
0634: Attributes global = m.getMainAttributes();
0635: if (global.getValue(Attributes.Name.MANIFEST_VERSION) == null) {
0636: global.put(Attributes.Name.MANIFEST_VERSION, VERSION);
0637: }
0638: }
0639:
0640: private void addCreatedBy(Manifest m) {
0641: Attributes global = m.getMainAttributes();
0642: if (global.getValue(new Attributes.Name("Created-By")) == null) {
0643: String javaVendor = System.getProperty("java.vendor");
0644: String jdkVersion = System.getProperty("java.version");
0645: global.put(new Attributes.Name("Created-By"), jdkVersion
0646: + " (" + javaVendor + ")");
0647: }
0648: }
0649:
0650: private void addMainClass(Manifest m, String mainApp) {
0651: Attributes global = m.getMainAttributes();
0652:
0653: // overrides any existing Main-Class attribute
0654: global.put(Attributes.Name.MAIN_CLASS, mainApp);
0655: }
0656:
0657: private boolean isAmbigousMainClass(Manifest m) {
0658: if (ename != null) {
0659: Attributes global = m.getMainAttributes();
0660: if ((global.get(Attributes.Name.MAIN_CLASS) != null)) {
0661: error(getMsg("error.bad.eflag"));
0662: usageError();
0663: return true;
0664: }
0665: }
0666: return false;
0667: }
0668:
0669: /*
0670: * Adds a new file entry to the ZIP output stream.
0671: */
0672: void addFile(ZipOutputStream zos, File file) throws IOException {
0673: String name = file.getPath();
0674: boolean isDir = file.isDirectory();
0675:
0676: if (isDir) {
0677: name = name.endsWith(File.separator) ? name
0678: : (name + File.separator);
0679: }
0680: name = entryName(name);
0681:
0682: if (name.equals("") || name.equals(".") || name.equals(zname)) {
0683: return;
0684: } else if ((name.equals(MANIFEST_DIR) || name.equals(MANIFEST))
0685: && !Mflag) {
0686: if (vflag) {
0687: output(formatMsg("out.ignore.entry", name));
0688: }
0689: return;
0690: }
0691:
0692: long size = isDir ? 0 : file.length();
0693:
0694: if (vflag) {
0695: out.print(formatMsg("out.adding", name));
0696: }
0697: ZipEntry e = new ZipEntry(name);
0698: e.setTime(file.lastModified());
0699: if (size == 0) {
0700: e.setMethod(ZipEntry.STORED);
0701: e.setSize(0);
0702: e.setCrc(0);
0703: } else if (flag0) {
0704: e.setSize(size);
0705: e.setMethod(ZipEntry.STORED);
0706: crc32File(e, file);
0707: }
0708: zos.putNextEntry(e);
0709: if (!isDir) {
0710: byte[] buf = new byte[1024];
0711: int len;
0712: InputStream is = new BufferedInputStream(
0713: new FileInputStream(file));
0714: while ((len = is.read(buf, 0, buf.length)) != -1) {
0715: zos.write(buf, 0, len);
0716: }
0717: is.close();
0718: }
0719: zos.closeEntry();
0720: /* report how much compression occurred. */
0721: if (vflag) {
0722: size = e.getSize();
0723: long csize = e.getCompressedSize();
0724: out.print(formatMsg2("out.size", String.valueOf(size),
0725: String.valueOf(csize)));
0726: if (e.getMethod() == ZipEntry.DEFLATED) {
0727: long ratio = 0;
0728: if (size != 0) {
0729: ratio = ((size - csize) * 100) / size;
0730: }
0731: output(formatMsg("out.deflated", String.valueOf(ratio)));
0732: } else {
0733: output(getMsg("out.stored"));
0734: }
0735: }
0736: }
0737:
0738: /*
0739: * compute the crc32 of a file. This is necessary when the ZipOutputStream
0740: * is in STORED mode.
0741: */
0742: private void crc32Manifest(ZipEntry e, Manifest m)
0743: throws IOException {
0744: crc32.reset();
0745: CRC32OutputStream os = new CRC32OutputStream(crc32);
0746: m.write(os);
0747: e.setSize((long) os.n);
0748: e.setCrc(crc32.getValue());
0749: }
0750:
0751: /*
0752: * compute the crc32 of a file. This is necessary when the ZipOutputStream
0753: * is in STORED mode.
0754: */
0755: private void crc32File(ZipEntry e, File f) throws IOException {
0756: InputStream is = new BufferedInputStream(new FileInputStream(f));
0757: byte[] buf = new byte[1024];
0758: crc32.reset();
0759: int r = 0;
0760: int nread = 0;
0761: long len = f.length();
0762: while ((r = is.read(buf)) != -1) {
0763: nread += r;
0764: crc32.update(buf, 0, r);
0765: }
0766: is.close();
0767: if (nread != (int) len) {
0768: throw new JarException(formatMsg("error.incorrect.length",
0769: f.getPath()));
0770: }
0771: e.setCrc(crc32.getValue());
0772: }
0773:
0774: /*
0775: * Extracts specified entries from JAR file.
0776: */
0777: void extract(InputStream in, String files[]) throws IOException {
0778: ZipInputStream zis = new ZipInputStream(in);
0779: ZipEntry e;
0780: // Set of all directory entries specified in archive. Dissallows
0781: // null entries. Disallows all entries if using pre-6.0 behavior.
0782: Set<ZipEntry> dirs = new HashSet<ZipEntry>() {
0783: public boolean add(ZipEntry e) {
0784: return ((e == null || useExtractionTime) ? false
0785: : super .add(e));
0786: }
0787: };
0788:
0789: while ((e = zis.getNextEntry()) != null) {
0790: if (files == null) {
0791: dirs.add(extractFile(zis, e));
0792:
0793: } else {
0794: String name = e.getName();
0795: for (int i = 0; i < files.length; i++) {
0796: String file = files[i].replace(File.separatorChar,
0797: '/');
0798: if (name.startsWith(file)) {
0799: dirs.add(extractFile(zis, e));
0800: break;
0801: }
0802: }
0803: }
0804: }
0805:
0806: // Update timestamps of directories specified in archive with their
0807: // timestamps as given in the archive. We do this after extraction,
0808: // instead of during, because creating a file in a directory changes
0809: // that directory's timestamp.
0810: for (ZipEntry dirEntry : dirs) {
0811: long lastModified = dirEntry.getTime();
0812: if (lastModified != -1) {
0813: File dir = new File(dirEntry.getName().replace('/',
0814: File.separatorChar));
0815: dir.setLastModified(lastModified);
0816: }
0817: }
0818: }
0819:
0820: /*
0821: * Extracts next entry from JAR file, creating directories as needed. If
0822: * the entry is for a directory which doesn't exist prior to this
0823: * invocation, returns that entry, otherwise returns null.
0824: */
0825: ZipEntry extractFile(ZipInputStream zis, ZipEntry e)
0826: throws IOException {
0827: ZipEntry rc = null;
0828: String name = e.getName();
0829: File f = new File(e.getName().replace('/', File.separatorChar));
0830: if (e.isDirectory()) {
0831: if (f.exists()) {
0832: if (!f.isDirectory()) {
0833: throw new IOException(formatMsg("error.create.dir",
0834: f.getPath()));
0835: }
0836: } else {
0837: if (!f.mkdirs()) {
0838: throw new IOException(formatMsg("error.create.dir",
0839: f.getPath()));
0840: } else {
0841: rc = e;
0842: }
0843: }
0844:
0845: if (vflag) {
0846: output(formatMsg("out.create", name));
0847: }
0848: } else {
0849: if (f.getParent() != null) {
0850: File d = new File(f.getParent());
0851: if (!d.exists() && !d.mkdirs() || !d.isDirectory()) {
0852: throw new IOException(formatMsg("error.create.dir",
0853: d.getPath()));
0854: }
0855: }
0856: OutputStream os = new FileOutputStream(f);
0857: byte[] b = new byte[512];
0858: int len;
0859: while ((len = zis.read(b, 0, b.length)) != -1) {
0860: os.write(b, 0, len);
0861: }
0862: zis.closeEntry();
0863: os.close();
0864: if (vflag) {
0865: if (e.getMethod() == ZipEntry.DEFLATED) {
0866: output(formatMsg("out.inflated", name));
0867: } else {
0868: output(formatMsg("out.extracted", name));
0869: }
0870: }
0871: }
0872: if (!useExtractionTime) {
0873: long lastModified = e.getTime();
0874: if (lastModified != -1) {
0875: f.setLastModified(lastModified);
0876: }
0877: }
0878: return rc;
0879: }
0880:
0881: /*
0882: * Lists contents of JAR file.
0883: */
0884: void list(InputStream in, String files[]) throws IOException {
0885: ZipInputStream zis = new ZipInputStream(in);
0886: ZipEntry e;
0887: while ((e = zis.getNextEntry()) != null) {
0888: String name = e.getName();
0889: /*
0890: * In the case of a compressed (deflated) entry, the entry size
0891: * is stored immediately following the entry data and cannot be
0892: * determined until the entry is fully read. Therefore, we close
0893: * the entry first before printing out its attributes.
0894: */
0895: zis.closeEntry();
0896: if (files == null) {
0897: printEntry(e);
0898: } else {
0899: for (int i = 0; i < files.length; i++) {
0900: String file = files[i].replace(File.separatorChar,
0901: '/');
0902: if (name.startsWith(file)) {
0903: printEntry(e);
0904: break;
0905: }
0906: }
0907: }
0908: }
0909: }
0910:
0911: /**
0912: * Output the class index table to the INDEX.LIST file of the
0913: * root jar file.
0914: */
0915: void dumpIndex(String rootjar, JarIndex index) throws IOException {
0916: filesTable.put(INDEX, index);
0917: File scratchFile = File.createTempFile("scratch", null,
0918: new File("."));
0919: File jarFile = new File(rootjar);
0920: boolean updateOk = update(new FileInputStream(jarFile),
0921: new FileOutputStream(scratchFile), null);
0922: jarFile.delete();
0923: if (!scratchFile.renameTo(jarFile)) {
0924: scratchFile.delete();
0925: throw new IOException(getMsg("error.write.file"));
0926: }
0927: scratchFile.delete();
0928: }
0929:
0930: private Hashtable jarTable = new Hashtable();
0931:
0932: /*
0933: * Generate the transitive closure of the Class-Path attribute for
0934: * the specified jar file.
0935: */
0936: Vector getJarPath(String jar) throws IOException {
0937: Vector files = new Vector();
0938: files.add(jar);
0939: jarTable.put(jar, jar);
0940:
0941: // take out the current path
0942: String path = jar.substring(0, Math.max(0,
0943: jar.lastIndexOf('/') + 1));
0944:
0945: // class path attribute will give us jar file name with
0946: // '/' as separators, so we need to change them to the
0947: // appropriate one before we open the jar file.
0948: JarFile rf = new JarFile(jar.replace('/', File.separatorChar));
0949:
0950: if (rf != null) {
0951: Manifest man = rf.getManifest();
0952: if (man != null) {
0953: Attributes attr = man.getMainAttributes();
0954: if (attr != null) {
0955: String value = attr
0956: .getValue(Attributes.Name.CLASS_PATH);
0957: if (value != null) {
0958: StringTokenizer st = new StringTokenizer(value);
0959: while (st.hasMoreTokens()) {
0960: String ajar = st.nextToken();
0961: if (!ajar.endsWith("/")) { // it is a jar file
0962: ajar = path.concat(ajar);
0963: /* check on cyclic dependency */
0964: if (jarTable.get(ajar) == null) {
0965: files.addAll(getJarPath(ajar));
0966: }
0967: }
0968: }
0969: }
0970: }
0971: }
0972: }
0973: rf.close();
0974: return files;
0975: }
0976:
0977: /**
0978: * Generate class index file for the specified root jar file.
0979: */
0980: void genIndex(String rootjar, String[] files) throws IOException {
0981: Vector jars = getJarPath(rootjar);
0982: int njars = jars.size();
0983: String[] jarfiles;
0984:
0985: if (njars == 1 && files != null) {
0986: // no class-path attribute defined in rootjar, will
0987: // use command line specified list of jars
0988: for (int i = 0; i < files.length; i++) {
0989: jars.addAll(getJarPath(files[i]));
0990: }
0991: njars = jars.size();
0992: }
0993: jarfiles = (String[]) jars.toArray(new String[njars]);
0994: JarIndex index = new JarIndex(jarfiles);
0995: dumpIndex(rootjar, index);
0996: }
0997:
0998: /*
0999: * Prints entry information.
1000: */
1001: void printEntry(ZipEntry e) throws IOException {
1002: if (vflag) {
1003: StringBuffer sb = new StringBuffer();
1004: String s = Long.toString(e.getSize());
1005: for (int i = 6 - s.length(); i > 0; --i) {
1006: sb.append(' ');
1007: }
1008: sb.append(s).append(' ').append(
1009: new Date(e.getTime()).toString());
1010: sb.append(' ').append(e.getName());
1011: output(sb.toString());
1012: } else {
1013: output(e.getName());
1014: }
1015: }
1016:
1017: /*
1018: * Print usage message and die.
1019: */
1020: void usageError() {
1021: error(getMsg("usage"));
1022: }
1023:
1024: /*
1025: * A fatal exception has been caught. No recovery possible
1026: */
1027: void fatalError(Exception e) {
1028: e.printStackTrace();
1029: }
1030:
1031: /*
1032: * A fatal condition has been detected; message is "s".
1033: * No recovery possible
1034: */
1035: void fatalError(String s) {
1036: error(program + ": " + s);
1037: }
1038:
1039: /**
1040: * Print an output message; like verbose output and the like
1041: */
1042: protected void output(String s) {
1043: out.println(s);
1044: }
1045:
1046: /**
1047: * Print an error mesage; like something is broken
1048: */
1049: protected void error(String s) {
1050: err.println(s);
1051: }
1052:
1053: /*
1054: * Main routine to start program.
1055: */
1056: public static void main(String args[]) {
1057: Main jartool = new Main(System.out, System.err, "jar");
1058: System.exit(jartool.run(args) ? 0 : 1);
1059: }
1060: }
1061:
1062: /*
1063: * an OutputStream that doesn't send its output anywhere, (but could).
1064: * It's here to find the CRC32 of a manifest, necessary for STORED only
1065: * mode in ZIP.
1066: */
1067: final class CRC32OutputStream extends java.io.OutputStream {
1068: CRC32 crc;
1069: int n = 0;
1070:
1071: CRC32OutputStream(CRC32 crc) {
1072: this .crc = crc;
1073: }
1074:
1075: public void write(int r) throws IOException {
1076: crc.update(r);
1077: n++;
1078: }
1079:
1080: public void write(byte[] b) throws IOException {
1081: crc.update(b, 0, b.length);
1082: n += b.length;
1083: }
1084:
1085: public void write(byte[] b, int off, int len) throws IOException {
1086: crc.update(b, off, len);
1087: n += len - off;
1088: }
1089: }
|