0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018:
0019: package org.apache.tools.ant.taskdefs;
0020:
0021: import java.io.ByteArrayInputStream;
0022: import java.io.ByteArrayOutputStream;
0023: import java.io.File;
0024: import java.io.FileOutputStream;
0025: import java.io.FileInputStream;
0026: import java.io.IOException;
0027: import java.io.InputStream;
0028: import java.io.UnsupportedEncodingException;
0029: import java.io.InputStreamReader;
0030: import java.io.OutputStreamWriter;
0031: import java.io.PrintWriter;
0032: import java.io.Reader;
0033: import java.util.ArrayList;
0034: import java.util.Collections;
0035: import java.util.Comparator;
0036: import java.util.Enumeration;
0037: import java.util.HashSet;
0038: import java.util.Iterator;
0039: import java.util.List;
0040: import java.util.StringTokenizer;
0041: import java.util.TreeMap;
0042: import java.util.Vector;
0043: import java.util.zip.ZipEntry;
0044: import java.util.zip.ZipFile;
0045: import org.apache.tools.ant.BuildException;
0046: import org.apache.tools.ant.Project;
0047: import org.apache.tools.ant.types.EnumeratedAttribute;
0048: import org.apache.tools.ant.types.Path;
0049: import org.apache.tools.ant.types.ResourceCollection;
0050: import org.apache.tools.ant.types.ZipFileSet;
0051: import org.apache.tools.ant.types.spi.Service;
0052: import org.apache.tools.zip.JarMarker;
0053: import org.apache.tools.zip.ZipExtraField;
0054: import org.apache.tools.zip.ZipOutputStream;
0055:
0056: /**
0057: * Creates a JAR archive.
0058: *
0059: * @since Ant 1.1
0060: *
0061: * @ant.task category="packaging"
0062: */
0063: public class Jar extends Zip {
0064: /** The index file name. */
0065: private static final String INDEX_NAME = "META-INF/INDEX.LIST";
0066:
0067: /** The manifest file name. */
0068: private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
0069:
0070: /**
0071: * List of all known SPI Services
0072: */
0073: private List serviceList = new ArrayList();
0074:
0075: /** merged manifests added through addConfiguredManifest */
0076: private Manifest configuredManifest;
0077: /** shadow of the above if upToDate check alters the value */
0078: private Manifest savedConfiguredManifest;
0079:
0080: /** merged manifests added through filesets */
0081: private Manifest filesetManifest;
0082:
0083: /**
0084: * Manifest of original archive, will be set to null if not in
0085: * update mode.
0086: */
0087: private Manifest originalManifest;
0088:
0089: /**
0090: * whether to merge fileset manifests;
0091: * value is true if filesetmanifest is 'merge' or 'mergewithoutmain'
0092: */
0093: private FilesetManifestConfig filesetManifestConfig;
0094:
0095: /**
0096: * whether to merge the main section of fileset manifests;
0097: * value is true if filesetmanifest is 'merge'
0098: */
0099: private boolean mergeManifestsMain = true;
0100:
0101: /** the manifest specified by the 'manifest' attribute **/
0102: private Manifest manifest;
0103:
0104: /** The encoding to use when reading in a manifest file */
0105: private String manifestEncoding;
0106:
0107: /**
0108: * The file found from the 'manifest' attribute. This can be
0109: * either the location of a manifest, or the name of a jar added
0110: * through a fileset. If its the name of an added jar, the
0111: * manifest is looked for in META-INF/MANIFEST.MF
0112: */
0113: private File manifestFile;
0114:
0115: /** jar index is JDK 1.3+ only */
0116: private boolean index = false;
0117:
0118: /**
0119: * whether to really create the archive in createEmptyZip, will
0120: * get set in getResourcesToAdd.
0121: */
0122: private boolean createEmpty = false;
0123:
0124: /**
0125: * Stores all files that are in the root of the archive (i.e. that
0126: * have a name that doesn't contain a slash) so they can get
0127: * listed in the index.
0128: *
0129: * Will not be filled unless the user has asked for an index.
0130: *
0131: * @since Ant 1.6
0132: */
0133: private Vector rootEntries;
0134:
0135: /**
0136: * Path containing jars that shall be indexed in addition to this archive.
0137: *
0138: * @since Ant 1.6.2
0139: */
0140: private Path indexJars;
0141:
0142: /**
0143: * Extra fields needed to make Solaris recognize the archive as a jar file.
0144: *
0145: * @since Ant 1.6.3
0146: */
0147: private static final ZipExtraField[] JAR_MARKER = new ZipExtraField[] { JarMarker
0148: .getInstance() };
0149:
0150: // CheckStyle:VisibilityModifier OFF - bc
0151: protected String emptyBehavior = "create";
0152:
0153: // CheckStyle:VisibilityModifier ON
0154:
0155: /** constructor */
0156: public Jar() {
0157: super ();
0158: archiveType = "jar";
0159: emptyBehavior = "create";
0160: setEncoding("UTF8");
0161: rootEntries = new Vector();
0162: }
0163:
0164: /**
0165: * Not used for jar files.
0166: * @param we not used
0167: * @ant.attribute ignore="true"
0168: */
0169: public void setWhenempty(WhenEmpty we) {
0170: log(
0171: "JARs are never empty, they contain at least a manifest file",
0172: Project.MSG_WARN);
0173: }
0174:
0175: /**
0176: * Indicates if a jar file should be created when it would only contain a
0177: * manifest file.
0178: * Possible values are: <code>fail</code> (throw an exception
0179: * and halt the build); <code>skip</code> (do not create
0180: * any archive, but issue a warning); <code>create</code>
0181: * (make an archive with only a manifest file).
0182: * Default is <code>create</code>;
0183: * @param we a <code>WhenEmpty</code> enumerated value
0184: */
0185: public void setWhenmanifestonly(WhenEmpty we) {
0186: emptyBehavior = we.getValue();
0187: }
0188:
0189: /**
0190: * Set the destination file.
0191: * @param jarFile the destination file
0192: * @deprecated since 1.5.x.
0193: * Use setDestFile(File) instead.
0194: */
0195: public void setJarfile(File jarFile) {
0196: setDestFile(jarFile);
0197: }
0198:
0199: /**
0200: * Set whether or not to create an index list for classes.
0201: * This may speed up classloading in some cases.
0202: * @param flag a <code>boolean</code> value
0203: */
0204: public void setIndex(boolean flag) {
0205: index = flag;
0206: }
0207:
0208: /**
0209: * The character encoding to use in the manifest file.
0210: *
0211: * @param manifestEncoding the character encoding
0212: */
0213: public void setManifestEncoding(String manifestEncoding) {
0214: this .manifestEncoding = manifestEncoding;
0215: }
0216:
0217: /**
0218: * Allows the manifest for the archive file to be provided inline
0219: * in the build file rather than in an external file.
0220: *
0221: * @param newManifest an embedded manifest element
0222: * @throws ManifestException on error
0223: */
0224: public void addConfiguredManifest(Manifest newManifest)
0225: throws ManifestException {
0226: if (configuredManifest == null) {
0227: configuredManifest = newManifest;
0228: } else {
0229: configuredManifest.merge(newManifest);
0230: }
0231: savedConfiguredManifest = configuredManifest;
0232: }
0233:
0234: /**
0235: * The manifest file to use. This can be either the location of a manifest,
0236: * or the name of a jar added through a fileset. If its the name of an added
0237: * jar, the task expects the manifest to be in the jar at META-INF/MANIFEST.MF.
0238: *
0239: * @param manifestFile the manifest file to use.
0240: */
0241: public void setManifest(File manifestFile) {
0242: if (!manifestFile.exists()) {
0243: throw new BuildException("Manifest file: " + manifestFile
0244: + " does not exist.", getLocation());
0245: }
0246:
0247: this .manifestFile = manifestFile;
0248: }
0249:
0250: private Manifest getManifest(File manifestFile) {
0251:
0252: Manifest newManifest = null;
0253: FileInputStream fis = null;
0254: InputStreamReader isr = null;
0255: try {
0256: fis = new FileInputStream(manifestFile);
0257: if (manifestEncoding == null) {
0258: isr = new InputStreamReader(fis);
0259: } else {
0260: isr = new InputStreamReader(fis, manifestEncoding);
0261: }
0262: newManifest = getManifest(isr);
0263: } catch (UnsupportedEncodingException e) {
0264: throw new BuildException(
0265: "Unsupported encoding while reading manifest: "
0266: + e.getMessage(), e);
0267: } catch (IOException e) {
0268: throw new BuildException("Unable to read manifest file: "
0269: + manifestFile + " (" + e.getMessage() + ")", e);
0270: } finally {
0271: if (isr != null) {
0272: try {
0273: isr.close();
0274: } catch (IOException e) {
0275: // do nothing
0276: }
0277: }
0278: }
0279: return newManifest;
0280: }
0281:
0282: /**
0283: * @return null if jarFile doesn't contain a manifest, the
0284: * manifest otherwise.
0285: * @since Ant 1.5.2
0286: */
0287: private Manifest getManifestFromJar(File jarFile)
0288: throws IOException {
0289: ZipFile zf = null;
0290: try {
0291: zf = new ZipFile(jarFile);
0292:
0293: // must not use getEntry as "well behaving" applications
0294: // must accept the manifest in any capitalization
0295: Enumeration e = zf.entries();
0296: while (e.hasMoreElements()) {
0297: ZipEntry ze = (ZipEntry) e.nextElement();
0298: if (ze.getName().equalsIgnoreCase(MANIFEST_NAME)) {
0299: InputStreamReader isr = new InputStreamReader(zf
0300: .getInputStream(ze), "UTF-8");
0301: return getManifest(isr);
0302: }
0303: }
0304: return null;
0305: } finally {
0306: if (zf != null) {
0307: try {
0308: zf.close();
0309: } catch (IOException e) {
0310: // XXX - log an error? throw an exception?
0311: }
0312: }
0313: }
0314: }
0315:
0316: private Manifest getManifest(Reader r) {
0317:
0318: Manifest newManifest = null;
0319: try {
0320: newManifest = new Manifest(r);
0321: } catch (ManifestException e) {
0322: log("Manifest is invalid: " + e.getMessage(),
0323: Project.MSG_ERR);
0324: throw new BuildException("Invalid Manifest: "
0325: + manifestFile, e, getLocation());
0326: } catch (IOException e) {
0327: throw new BuildException("Unable to read manifest file"
0328: + " (" + e.getMessage() + ")", e);
0329: }
0330: return newManifest;
0331: }
0332:
0333: /**
0334: * Behavior when a Manifest is found in a zipfileset or zipgroupfileset file.
0335: * Valid values are "skip", "merge", and "mergewithoutmain".
0336: * "merge" will merge all of manifests together, and merge this into any
0337: * other specified manifests.
0338: * "mergewithoutmain" merges everything but the Main section of the manifests.
0339: * Default value is "skip".
0340: *
0341: * Note: if this attribute's value is not "skip", the created jar will not
0342: * be readable by using java.util.jar.JarInputStream
0343: *
0344: * @param config setting for found manifest behavior.
0345: */
0346: public void setFilesetmanifest(FilesetManifestConfig config) {
0347: filesetManifestConfig = config;
0348: mergeManifestsMain = "merge".equals(config.getValue());
0349:
0350: if (filesetManifestConfig != null
0351: && !filesetManifestConfig.getValue().equals("skip")) {
0352:
0353: doubleFilePass = true;
0354: }
0355: }
0356:
0357: /**
0358: * Adds a zipfileset to include in the META-INF directory.
0359: *
0360: * @param fs zipfileset to add
0361: */
0362: public void addMetainf(ZipFileSet fs) {
0363: // We just set the prefix for this fileset, and pass it up.
0364: fs.setPrefix("META-INF/");
0365: super .addFileset(fs);
0366: }
0367:
0368: /**
0369: * Add a path to index jars.
0370: * @param p a path
0371: * @since Ant 1.6.2
0372: */
0373: public void addConfiguredIndexJars(Path p) {
0374: if (indexJars == null) {
0375: indexJars = new Path(getProject());
0376: }
0377: indexJars.append(p);
0378: }
0379:
0380: /**
0381: * A nested SPI service element.
0382: * @param service the nested element.
0383: * @since Ant 1.7
0384: */
0385: public void addConfiguredService(Service service) {
0386: // Check if the service is configured correctly
0387: service.check();
0388: serviceList.add(service);
0389: }
0390:
0391: /**
0392: * Write SPI Information to JAR
0393: */
0394: private void writeServices(ZipOutputStream zOut) throws IOException {
0395: Iterator serviceIterator;
0396: Service service;
0397:
0398: serviceIterator = serviceList.iterator();
0399: while (serviceIterator.hasNext()) {
0400: service = (Service) serviceIterator.next();
0401: //stolen from writeManifest
0402: super .zipFile(service.getAsStream(), zOut,
0403: "META-INF/service/" + service.getType(), System
0404: .currentTimeMillis(), null,
0405: ZipFileSet.DEFAULT_FILE_MODE);
0406: }
0407: }
0408:
0409: /**
0410: * Initialize the zip output stream.
0411: * @param zOut the zip output stream
0412: * @throws IOException on I/O errors
0413: * @throws BuildException on other errors
0414: */
0415: protected void initZipOutputStream(ZipOutputStream zOut)
0416: throws IOException, BuildException {
0417:
0418: if (!skipWriting) {
0419: Manifest jarManifest = createManifest();
0420: writeManifest(zOut, jarManifest);
0421: writeServices(zOut);
0422: }
0423: }
0424:
0425: private Manifest createManifest() throws BuildException {
0426: try {
0427: Manifest finalManifest = Manifest.getDefaultManifest();
0428:
0429: if (manifest == null) {
0430: if (manifestFile != null) {
0431: // if we haven't got the manifest yet, attempt to
0432: // get it now and have manifest be the final merge
0433: manifest = getManifest(manifestFile);
0434: }
0435: }
0436:
0437: /*
0438: * Precedence: manifestFile wins over inline manifest,
0439: * over manifests read from the filesets over the original
0440: * manifest.
0441: *
0442: * merge with null argument is a no-op
0443: */
0444:
0445: if (isInUpdateMode()) {
0446: finalManifest.merge(originalManifest);
0447: }
0448: finalManifest.merge(filesetManifest);
0449: finalManifest.merge(configuredManifest);
0450: finalManifest.merge(manifest, !mergeManifestsMain);
0451:
0452: return finalManifest;
0453:
0454: } catch (ManifestException e) {
0455: log("Manifest is invalid: " + e.getMessage(),
0456: Project.MSG_ERR);
0457: throw new BuildException("Invalid Manifest", e,
0458: getLocation());
0459: }
0460: }
0461:
0462: private void writeManifest(ZipOutputStream zOut, Manifest manifest)
0463: throws IOException {
0464: for (Enumeration e = manifest.getWarnings(); e
0465: .hasMoreElements();) {
0466: log("Manifest warning: " + (String) e.nextElement(),
0467: Project.MSG_WARN);
0468: }
0469:
0470: zipDir(null, zOut, "META-INF/", ZipFileSet.DEFAULT_DIR_MODE,
0471: JAR_MARKER);
0472: // time to write the manifest
0473: ByteArrayOutputStream baos = new ByteArrayOutputStream();
0474: OutputStreamWriter osw = new OutputStreamWriter(baos,
0475: Manifest.JAR_ENCODING);
0476: PrintWriter writer = new PrintWriter(osw);
0477: manifest.write(writer);
0478: writer.flush();
0479:
0480: ByteArrayInputStream bais = new ByteArrayInputStream(baos
0481: .toByteArray());
0482: super .zipFile(bais, zOut, MANIFEST_NAME, System
0483: .currentTimeMillis(), null,
0484: ZipFileSet.DEFAULT_FILE_MODE);
0485: super .initZipOutputStream(zOut);
0486: }
0487:
0488: /**
0489: * Finalize the zip output stream.
0490: * This creates an index list if the index attribute is true.
0491: * @param zOut the zip output stream
0492: * @throws IOException on I/O errors
0493: * @throws BuildException on other errors
0494: */
0495: protected void finalizeZipOutputStream(ZipOutputStream zOut)
0496: throws IOException, BuildException {
0497:
0498: if (index) {
0499: createIndexList(zOut);
0500: }
0501: }
0502:
0503: /**
0504: * Create the index list to speed up classloading.
0505: * This is a JDK 1.3+ specific feature and is enabled by default. See
0506: * <a href="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#JAR%20Index">
0507: * the JAR index specification</a> for more details.
0508: *
0509: * @param zOut the zip stream representing the jar being built.
0510: * @throws IOException thrown if there is an error while creating the
0511: * index and adding it to the zip stream.
0512: */
0513: private void createIndexList(ZipOutputStream zOut)
0514: throws IOException {
0515: ByteArrayOutputStream baos = new ByteArrayOutputStream();
0516: // encoding must be UTF8 as specified in the specs.
0517: PrintWriter writer = new PrintWriter(new OutputStreamWriter(
0518: baos, "UTF8"));
0519:
0520: // version-info blankline
0521: writer.println("JarIndex-Version: 1.0");
0522: writer.println();
0523:
0524: // header newline
0525: writer.println(zipFile.getName());
0526:
0527: writeIndexLikeList(new ArrayList(addedDirs.keySet()),
0528: rootEntries, writer);
0529: writer.println();
0530:
0531: if (indexJars != null) {
0532: Manifest mf = createManifest();
0533: Manifest.Attribute classpath = mf.getMainSection()
0534: .getAttribute(Manifest.ATTRIBUTE_CLASSPATH);
0535: String[] cpEntries = null;
0536: if (classpath != null && classpath.getValue() != null) {
0537: StringTokenizer tok = new StringTokenizer(classpath
0538: .getValue(), " ");
0539: cpEntries = new String[tok.countTokens()];
0540: int c = 0;
0541: while (tok.hasMoreTokens()) {
0542: cpEntries[c++] = tok.nextToken();
0543: }
0544: }
0545: String[] indexJarEntries = indexJars.list();
0546: for (int i = 0; i < indexJarEntries.length; i++) {
0547: String name = findJarName(indexJarEntries[i], cpEntries);
0548: if (name != null) {
0549: ArrayList dirs = new ArrayList();
0550: ArrayList files = new ArrayList();
0551: grabFilesAndDirs(indexJarEntries[i], dirs, files);
0552: if (dirs.size() + files.size() > 0) {
0553: writer.println(name);
0554: writeIndexLikeList(dirs, files, writer);
0555: writer.println();
0556: }
0557: }
0558: }
0559: }
0560:
0561: writer.flush();
0562: ByteArrayInputStream bais = new ByteArrayInputStream(baos
0563: .toByteArray());
0564: super .zipFile(bais, zOut, INDEX_NAME, System
0565: .currentTimeMillis(), null,
0566: ZipFileSet.DEFAULT_FILE_MODE);
0567: }
0568:
0569: /**
0570: * Overridden from Zip class to deal with manifests and index lists.
0571: * @param is the input stream
0572: * @param zOut the zip output stream
0573: * @param vPath the name this entry shall have in the archive
0574: * @param lastModified last modification time for the entry.
0575: * @param fromArchive the original archive we are copying this
0576: * entry from, will be null if we are not copying from an archive.
0577: * @param mode the Unix permissions to set.
0578: * @throws IOException on error
0579: */
0580: protected void zipFile(InputStream is, ZipOutputStream zOut,
0581: String vPath, long lastModified, File fromArchive, int mode)
0582: throws IOException {
0583: if (MANIFEST_NAME.equalsIgnoreCase(vPath)) {
0584: if (!doubleFilePass || (doubleFilePass && skipWriting)) {
0585: filesetManifest(fromArchive, is);
0586: }
0587: } else if (INDEX_NAME.equalsIgnoreCase(vPath) && index) {
0588: log("Warning: selected " + archiveType
0589: + " files include a META-INF/INDEX.LIST which will"
0590: + " be replaced by a newly generated one.",
0591: Project.MSG_WARN);
0592: } else {
0593: if (index && vPath.indexOf("/") == -1) {
0594: rootEntries.addElement(vPath);
0595: }
0596: super .zipFile(is, zOut, vPath, lastModified, fromArchive,
0597: mode);
0598: }
0599: }
0600:
0601: private void filesetManifest(File file, InputStream is)
0602: throws IOException {
0603: if (manifestFile != null && manifestFile.equals(file)) {
0604: // If this is the same name specified in 'manifest', this
0605: // is the manifest to use
0606: log("Found manifest " + file, Project.MSG_VERBOSE);
0607: try {
0608: if (is != null) {
0609: InputStreamReader isr;
0610: if (manifestEncoding == null) {
0611: isr = new InputStreamReader(is);
0612: } else {
0613: isr = new InputStreamReader(is,
0614: manifestEncoding);
0615: }
0616: manifest = getManifest(isr);
0617: } else {
0618: manifest = getManifest(file);
0619: }
0620: } catch (UnsupportedEncodingException e) {
0621: throw new BuildException(
0622: "Unsupported encoding while reading "
0623: + "manifest: " + e.getMessage(), e);
0624: }
0625: } else if (filesetManifestConfig != null
0626: && !filesetManifestConfig.getValue().equals("skip")) {
0627: // we add this to our group of fileset manifests
0628: log("Found manifest to merge in file " + file,
0629: Project.MSG_VERBOSE);
0630:
0631: try {
0632: Manifest newManifest = null;
0633: if (is != null) {
0634: InputStreamReader isr;
0635: if (manifestEncoding == null) {
0636: isr = new InputStreamReader(is);
0637: } else {
0638: isr = new InputStreamReader(is,
0639: manifestEncoding);
0640: }
0641: newManifest = getManifest(isr);
0642: } else {
0643: newManifest = getManifest(file);
0644: }
0645:
0646: if (filesetManifest == null) {
0647: filesetManifest = newManifest;
0648: } else {
0649: filesetManifest.merge(newManifest);
0650: }
0651: } catch (UnsupportedEncodingException e) {
0652: throw new BuildException(
0653: "Unsupported encoding while reading "
0654: + "manifest: " + e.getMessage(), e);
0655: } catch (ManifestException e) {
0656: log("Manifest in file " + file + " is invalid: "
0657: + e.getMessage(), Project.MSG_ERR);
0658: throw new BuildException("Invalid Manifest", e,
0659: getLocation());
0660: }
0661: } else {
0662: // assuming 'skip' otherwise
0663: // don't warn if skip has been requested explicitly, warn if user
0664: // didn't set the attribute
0665:
0666: // Hide warning also as it makes no sense since
0667: // the filesetmanifest attribute itself has been
0668: // hidden
0669:
0670: //int logLevel = filesetManifestConfig == null ?
0671: // Project.MSG_WARN : Project.MSG_VERBOSE;
0672: //log("File " + file
0673: // + " includes a META-INF/MANIFEST.MF which will be ignored. "
0674: // + "To include this file, set filesetManifest to a value other "
0675: // + "than 'skip'.", logLevel);
0676: }
0677: }
0678:
0679: /**
0680: * Collect the resources that are newer than the corresponding
0681: * entries (or missing) in the original archive.
0682: *
0683: * <p>If we are going to recreate the archive instead of updating
0684: * it, all resources should be considered as new, if a single one
0685: * is. Because of this, subclasses overriding this method must
0686: * call <code>super.getResourcesToAdd</code> and indicate with the
0687: * third arg if they already know that the archive is
0688: * out-of-date.</p>
0689: *
0690: * @param rcs The resource collections to grab resources from
0691: * @param zipFile intended archive file (may or may not exist)
0692: * @param needsUpdate whether we already know that the archive is
0693: * out-of-date. Subclasses overriding this method are supposed to
0694: * set this value correctly in their call to
0695: * super.getResourcesToAdd.
0696: * @return an array of resources to add for each fileset passed in as well
0697: * as a flag that indicates whether the archive is uptodate.
0698: *
0699: * @exception BuildException if it likes
0700: */
0701: protected ArchiveState getResourcesToAdd(ResourceCollection[] rcs,
0702: File zipFile, boolean needsUpdate) throws BuildException {
0703:
0704: // need to handle manifest as a special check
0705: if (zipFile.exists()) {
0706: // if it doesn't exist, it will get created anyway, don't
0707: // bother with any up-to-date checks.
0708:
0709: try {
0710: originalManifest = getManifestFromJar(zipFile);
0711: if (originalManifest == null) {
0712: log(
0713: "Updating jar since the current jar has no manifest",
0714: Project.MSG_VERBOSE);
0715: needsUpdate = true;
0716: } else {
0717: Manifest mf = createManifest();
0718: if (!mf.equals(originalManifest)) {
0719: log(
0720: "Updating jar since jar manifest has changed",
0721: Project.MSG_VERBOSE);
0722: needsUpdate = true;
0723: }
0724: }
0725: } catch (Throwable t) {
0726: log("error while reading original manifest in file: "
0727: + zipFile.toString() + t.getMessage(),
0728: Project.MSG_WARN);
0729: needsUpdate = true;
0730: }
0731:
0732: } else {
0733: // no existing archive
0734: needsUpdate = true;
0735: }
0736:
0737: createEmpty = needsUpdate;
0738: return super .getResourcesToAdd(rcs, zipFile, needsUpdate);
0739: }
0740:
0741: /**
0742: * Create an empty jar file.
0743: * @param zipFile the file to create
0744: * @return true for historic reasons
0745: * @throws BuildException on error
0746: */
0747: protected boolean createEmptyZip(File zipFile)
0748: throws BuildException {
0749: if (!createEmpty) {
0750: return true;
0751: }
0752:
0753: if (emptyBehavior.equals("skip")) {
0754: log("Warning: skipping " + archiveType + " archive "
0755: + zipFile + " because no files were included.",
0756: Project.MSG_WARN);
0757: return true;
0758: } else if (emptyBehavior.equals("fail")) {
0759: throw new BuildException("Cannot create " + archiveType
0760: + " archive " + zipFile
0761: + ": no files were included.", getLocation());
0762: }
0763:
0764: ZipOutputStream zOut = null;
0765: try {
0766: log("Building MANIFEST-only jar: "
0767: + getDestFile().getAbsolutePath());
0768: zOut = new ZipOutputStream(new FileOutputStream(
0769: getDestFile()));
0770:
0771: zOut.setEncoding(getEncoding());
0772: if (isCompress()) {
0773: zOut.setMethod(ZipOutputStream.DEFLATED);
0774: } else {
0775: zOut.setMethod(ZipOutputStream.STORED);
0776: }
0777: initZipOutputStream(zOut);
0778: finalizeZipOutputStream(zOut);
0779: } catch (IOException ioe) {
0780: throw new BuildException(
0781: "Could not create almost empty JAR archive" + " ("
0782: + ioe.getMessage() + ")", ioe,
0783: getLocation());
0784: } finally {
0785: // Close the output stream.
0786: try {
0787: if (zOut != null) {
0788: zOut.close();
0789: }
0790: } catch (IOException ex) {
0791: // Ignore close exception
0792: }
0793: createEmpty = false;
0794: }
0795: return true;
0796: }
0797:
0798: /**
0799: * Make sure we don't think we already have a MANIFEST next time this task
0800: * gets executed.
0801: *
0802: * @see Zip#cleanUp
0803: */
0804: protected void cleanUp() {
0805: super .cleanUp();
0806:
0807: // we want to save this info if we are going to make another pass
0808: if (!doubleFilePass || (doubleFilePass && !skipWriting)) {
0809: manifest = null;
0810: configuredManifest = savedConfiguredManifest;
0811: filesetManifest = null;
0812: originalManifest = null;
0813: }
0814: rootEntries.removeAllElements();
0815: }
0816:
0817: /**
0818: * reset to default values.
0819: *
0820: * @see Zip#reset
0821: *
0822: * @since 1.44, Ant 1.5
0823: */
0824: public void reset() {
0825: super .reset();
0826: emptyBehavior = "create";
0827: configuredManifest = null;
0828: filesetManifestConfig = null;
0829: mergeManifestsMain = false;
0830: manifestFile = null;
0831: index = false;
0832: }
0833:
0834: /**
0835: * The manifest config enumerated type.
0836: */
0837: public static class FilesetManifestConfig extends
0838: EnumeratedAttribute {
0839: /**
0840: * Get the list of valid strings.
0841: * @return the list of values - "skip", "merge" and "mergewithoutmain"
0842: */
0843: public String[] getValues() {
0844: return new String[] { "skip", "merge", "mergewithoutmain" };
0845: }
0846: }
0847:
0848: /**
0849: * Writes the directory entries from the first and the filenames
0850: * from the second list to the given writer, one entry per line.
0851: *
0852: * @param dirs a list of directories
0853: * @param files a list of files
0854: * @param writer the writer to write to
0855: * @throws IOException on error
0856: * @since Ant 1.6.2
0857: */
0858: protected final void writeIndexLikeList(List dirs, List files,
0859: PrintWriter writer) throws IOException {
0860: // JarIndex is sorting the directories by ascending order.
0861: // it has no value but cosmetic since it will be read into a
0862: // hashtable by the classloader, but we'll do so anyway.
0863: Collections.sort(dirs);
0864: Collections.sort(files);
0865: Iterator iter = dirs.iterator();
0866: while (iter.hasNext()) {
0867: String dir = (String) iter.next();
0868:
0869: // try to be smart, not to be fooled by a weird directory name
0870: dir = dir.replace('\\', '/');
0871: if (dir.startsWith("./")) {
0872: dir = dir.substring(2);
0873: }
0874: while (dir.startsWith("/")) {
0875: dir = dir.substring(1);
0876: }
0877: int pos = dir.lastIndexOf('/');
0878: if (pos != -1) {
0879: dir = dir.substring(0, pos);
0880: }
0881:
0882: // looks like nothing from META-INF should be added
0883: // and the check is not case insensitive.
0884: // see sun.misc.JarIndex
0885: if (dir.startsWith("META-INF")) {
0886: continue;
0887: }
0888: // name newline
0889: writer.println(dir);
0890: }
0891:
0892: iter = files.iterator();
0893: while (iter.hasNext()) {
0894: writer.println(iter.next());
0895: }
0896: }
0897:
0898: /**
0899: * try to guess the name of the given file.
0900: *
0901: * <p>If this jar has a classpath attribute in its manifest, we
0902: * can assume that it will only require an index of jars listed
0903: * there. try to find which classpath entry is most likely the
0904: * one the given file name points to.</p>
0905: *
0906: * <p>In the absence of a classpath attribute, assume the other
0907: * files will be placed inside the same directory as this jar and
0908: * use their basename.</p>
0909: *
0910: * <p>if there is a classpath and the given file doesn't match any
0911: * of its entries, return null.</p>
0912: *
0913: * @param fileName the name to look for
0914: * @param classpath the classpath to look in (may be null)
0915: * @return the matching entry, or null if the file is not found
0916: * @since Ant 1.6.2
0917: */
0918: protected static final String findJarName(String fileName,
0919: String[] classpath) {
0920: if (classpath == null) {
0921: return (new File(fileName)).getName();
0922: }
0923: fileName = fileName.replace(File.separatorChar, '/');
0924: TreeMap matches = new TreeMap(new Comparator() {
0925: // longest match comes first
0926: public int compare(Object o1, Object o2) {
0927: if (o1 instanceof String && o2 instanceof String) {
0928: return ((String) o2).length()
0929: - ((String) o1).length();
0930: }
0931: return 0;
0932: }
0933: });
0934:
0935: for (int i = 0; i < classpath.length; i++) {
0936: if (fileName.endsWith(classpath[i])) {
0937: matches.put(classpath[i], classpath[i]);
0938: } else {
0939: int slash = classpath[i].indexOf("/");
0940: String candidate = classpath[i];
0941: while (slash > -1) {
0942: candidate = candidate.substring(slash + 1);
0943: if (fileName.endsWith(candidate)) {
0944: matches.put(candidate, classpath[i]);
0945: break;
0946: }
0947: slash = candidate.indexOf("/");
0948: }
0949: }
0950: }
0951:
0952: return matches.size() == 0 ? null : (String) matches
0953: .get(matches.firstKey());
0954: }
0955:
0956: /**
0957: * Grab lists of all root-level files and all directories
0958: * contained in the given archive.
0959: * @param file the zip file to examine
0960: * @param dirs where to place the directories found
0961: * @param files where to place the files found
0962: * @since Ant 1.7
0963: * @throws IOException on error
0964: */
0965: protected static final void grabFilesAndDirs(String file,
0966: List dirs, List files) throws IOException {
0967: org.apache.tools.zip.ZipFile zf = null;
0968: try {
0969: zf = new org.apache.tools.zip.ZipFile(file, "utf-8");
0970: Enumeration entries = zf.getEntries();
0971: HashSet dirSet = new HashSet();
0972: while (entries.hasMoreElements()) {
0973: org.apache.tools.zip.ZipEntry ze = (org.apache.tools.zip.ZipEntry) entries
0974: .nextElement();
0975: String name = ze.getName();
0976: // META-INF would be skipped anyway, avoid index for
0977: // manifest-only jars.
0978: if (!name.startsWith("META-INF/")) {
0979: if (ze.isDirectory()) {
0980: dirSet.add(name);
0981: } else if (name.indexOf("/") == -1) {
0982: files.add(name);
0983: } else {
0984: // a file, not in the root
0985: // since the jar may be one without directory
0986: // entries, add the parent dir of this file as
0987: // well.
0988: dirSet.add(name.substring(0, name
0989: .lastIndexOf("/") + 1));
0990: }
0991: }
0992: }
0993: dirs.addAll(dirSet);
0994: } finally {
0995: if (zf != null) {
0996: zf.close();
0997: }
0998: }
0999: }
1000: }
|