All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.plexus.archiver.jar.Manifest Maven / Gradle / Ivy

The newest version!
/**
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.codehaus.plexus.archiver.jar;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.jar.Attributes;

import org.codehaus.plexus.archiver.ArchiverException;

/**
 * Holds the data of a jar manifest.
 * 

* Manifests are processed according to the * Jar * file specification. * Specifically, a manifest element consists of * a set of attributes and sections. These sections in turn may contain * attributes. Note in particular that this may result in manifest lines * greater than 72 bytes (including line break) being wrapped and continued * on the next line. If an application can not handle the continuation * mechanism, it is a defect in the application, not this task.

* * @since Ant 1.4 */ public class Manifest extends java.util.jar.Manifest implements Iterable { /** * The Name Attribute is the first in a named section */ private static final String ATTRIBUTE_NAME = ManifestConstants.ATTRIBUTE_NAME; /** * The From Header is disallowed in a Manifest */ private static final String ATTRIBUTE_FROM = ManifestConstants.ATTRIBUTE_FROM; /** * Default Manifest version if one is not specified */ private static final String DEFAULT_MANIFEST_VERSION = ManifestConstants.DEFAULT_MANIFEST_VERSION; /** * The max length of a line in a Manifest */ private static final int MAX_LINE_LENGTH = 72; /** * Max length of a line section which is continued. Need to allow * for the CRLF. */ private static final int MAX_SECTION_LENGTH = MAX_LINE_LENGTH - 2; /** * The End-Of-Line marker in manifests */ static final String EOL = "\r\n"; public static class BaseAttribute { /** * The attribute's name */ protected String name = null; /** * Get the Attribute's name * * @return the attribute's name. */ public String getName() { return name; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof BaseAttribute)) { return false; } BaseAttribute that = (BaseAttribute) o; return !(name != null ? !name.equals(that.name) : that.name != null); } @Override public int hashCode() { return name != null ? name.hashCode() : 0; } } /** * An attribute for the manifest. * Those attributes that are not nested into a section will be added to the "Main" section. */ public static class Attribute extends BaseAttribute implements Iterable { /** * The attribute's value */ private Vector values = new Vector(); /** * For multivalued attributes, this is the index of the attribute * currently being defined. */ private int currentIndex = 0; /** * Construct an empty attribute */ public Attribute() {} /** * Construct a manifest by specifying its name and value * * @param name the attribute's name * @param value the Attribute's value */ public Attribute(String name, String value) { this.name = name; setValue(value); } @Override public Iterator iterator() { return values.iterator(); } /** * @see java.lang.Object#hashCode */ @Override public int hashCode() { int hashCode = super.hashCode(); hashCode += values.hashCode(); return hashCode; } /** * @see java.lang.Object#equals */ @Override public boolean equals(Object rhs) { if (super.equals(rhs)) { return false; } if (rhs == null || rhs.getClass() != getClass()) { return false; } if (rhs == this) { return true; } Attribute rhsAttribute = (Attribute) rhs; String lhsKey = getKey(); String rhsKey = rhsAttribute.getKey(); //noinspection SimplifiableIfStatement,ConstantConditions if ((lhsKey == null && rhsKey != null) || (lhsKey != null && rhsKey == null) || !lhsKey.equals(rhsKey)) { return false; } return rhsAttribute.values != null && values.equals(rhsAttribute.values); } /** * Set the Attribute's name; required * * @param name the attribute's name */ public void setName(String name) { this.name = name; } /** * Get the attribute's Key - its name in lower case. * * @return the attribute's key. */ public String getKey() { return getKey(name); } /** * Get the key for the specified attribute name - its name in lower case. * * @return the attribute's key. */ private static String getKey(String name) { if (name == null) { return null; } return name.toLowerCase(Locale.ENGLISH); } /** * Set the Attribute's value; required * * @param value the attribute's value */ public void setValue(String value) { if (currentIndex >= values.size()) { values.addElement(value); currentIndex = values.size() - 1; } else { values.setElementAt(value, currentIndex); } } /** * Get the Attribute's value. * * @return the attribute's value. */ public String getValue() { if (values.size() == 0) { return null; } String fullValue = ""; for (String value : values) { fullValue += value + " "; } return fullValue.trim(); } /** * Add a new value to this attribute - making it multivalued. * * @param value the attribute's additional value */ public void addValue(String value) { currentIndex++; setValue(value); } /** * Writes the attribute out to a writer. * * @param writer the Writer to which the attribute is written * * @throws IOException if the attribute value cannot be written */ void write(Writer writer) throws IOException { for (String value : values) { writeValue(writer, value); } } /** * Write a single attribute value out. Should handle multiple lines of attribute value. * * @param writer the Writer to which the attribute is written * @param value the attribute value * * @throws IOException if the attribute value cannot be written */ private void writeValue(Writer writer, String value) throws IOException { String nameValue = name + ": " + value; StringTokenizer tokenizer = new StringTokenizer(nameValue, "\n\r"); String prefix = ""; while (tokenizer.hasMoreTokens()) { writeLine(writer, prefix + tokenizer.nextToken()); prefix = " "; } } /** * Write a single Manifest line. Should handle more than 72 bytes of line * * @param writer the Writer to which the attribute is written * @param line the manifest line to be written * * @throws java.io.IOException when Io excepts */ private void writeLine(Writer writer, String line) throws IOException { // Make sure we have at most 70 bytes in UTF-8 as specified excluding line break while (line.getBytes("UTF-8").length > MAX_SECTION_LENGTH) { // Try to find a MAX_SECTION_LENGTH // Use the minimum because we operate on at most chars and not bytes here otherwise // if we have more bytes than chars we will die in an IndexOutOfBoundsException. int breakIndex = Math.min(line.length(), MAX_SECTION_LENGTH); String section = line.substring(0, breakIndex); while (section.getBytes("UTF-8").length > MAX_SECTION_LENGTH && breakIndex > 0) { breakIndex--; section = line.substring(0, breakIndex); } if (breakIndex == 0) { throw new IOException("Unable to write manifest line " + line); } writer.write(section + EOL); line = " " + line.substring(breakIndex); } writer.write(line + EOL); } } public class ExistingAttribute extends Attribute implements Iterable { private final Attributes attributes; public ExistingAttribute(Attributes attributes, String name) { this.attributes = attributes; this.name = name; } @Override public Iterator iterator() { return getKeys(attributes).iterator(); } @Override public void setName(String name) { throw new UnsupportedOperationException("Cant do this"); } @Override public String getKey() { return name; } @Override public void setValue(String value) { attributes.putValue(name, value); } @Override public String getValue() { return attributes.getValue(name); } @Override public void addValue(String value) { String value1 = getValue(); value1 = (value1 != null) ? " " + value : value; setValue(value1); } @Override void write(Writer writer) throws IOException { throw new UnsupportedOperationException("Cant do this"); } } private static Collection getKeys(Attributes attributes) { Collection result = new ArrayList(); for (Object objectObjectEntry : attributes.keySet()) { result.add(objectObjectEntry.toString()); } return result; } /** * A manifest section - you can nest attribute elements into sections. * A section consists of a set of attribute values, * separated from other sections by a blank line. */ public static class Section implements Iterable { /** * Warnings for this section */ private Vector warnings = new Vector(); /** * The section's name if any. The main section in a * manifest is unnamed. */ private String name = null; /** * The section's attributes. */ private Hashtable attributes = new Hashtable(); /** * Index used to retain the attribute ordering */ private Vector attributeIndex = new Vector(); /** * The name of the section; optional -default is the main section. * * @param name the section's name */ public void setName(String name) { this.name = name; } /** * Get the Section's name. * * @return the section's name. */ public String getName() { return name; } @Override public Iterator iterator() { return attributes.keySet().iterator(); } /** * Get a attribute of the section * * @param attributeName the name of the attribute * * @return a Manifest.Attribute instance if the attribute is * single-valued, otherwise a Vector of Manifest.Attribute * instances. */ public Attribute getAttribute(String attributeName) { return attributes.get(attributeName.toLowerCase(Locale.ENGLISH)); } /** * Add an attribute to the section. * * @param attribute the attribute to be added to the section * * @throws ManifestException if the attribute is not valid. */ public void addConfiguredAttribute(Attribute attribute) throws ManifestException { String check = addAttributeAndCheck(attribute); if (check != null) { throw new ManifestException( "Specify the section name using " + "the \"name\" attribute of the
element rather " + "than using a \"Name\" manifest attribute"); } } /** * Add an attribute to the section * * @param attribute the attribute to be added. * * @return the value of the attribute if it is a name * attribute - null other wise * * @throws ManifestException if the attribute already * exists in this section. */ public String addAttributeAndCheck(Attribute attribute) throws ManifestException { if (attribute.getName() == null || attribute.getValue() == null) { throw new ManifestException("Attributes must have name and value"); } if (attribute.getKey().equalsIgnoreCase(ATTRIBUTE_NAME)) { warnings.addElement("\"" + ATTRIBUTE_NAME + "\" attributes " + "should not occur in the main section and must be the " + "first element in all other sections: \"" + attribute.getName() + ": " + attribute.getValue() + "\""); return attribute.getValue(); } if (attribute.getKey().startsWith(Attribute.getKey(ATTRIBUTE_FROM))) { warnings.addElement("Manifest attributes should not start " + "with \"" + ATTRIBUTE_FROM + "\" in \"" + attribute.getName() + ": " + attribute.getValue() + "\""); } else { // classpath attributes go into a vector String attributeKey = attribute.getKey(); if (attributeKey.equalsIgnoreCase(ManifestConstants.ATTRIBUTE_CLASSPATH)) { Attribute classpathAttribute = attributes.get(attributeKey); if (classpathAttribute == null) { storeAttribute(attribute); } else { warnings.addElement("Multiple Class-Path attributes " + "are supported but violate the Jar " + "specification and may not be correctly " + "processed in all environments"); for (String value : attribute) { classpathAttribute.addValue(value); } } } else if (attributes.containsKey(attributeKey)) { throw new ManifestException("The attribute \"" + attribute.getName() + "\" may not occur more " + "than once in the same section"); } else { storeAttribute(attribute); } } return null; } /** * Store an attribute and update the index. * * @param attribute the attribute to be stored */ protected void storeAttribute(Attribute attribute) { if (attribute == null) { return; } String attributeKey = attribute.getKey(); attributes.put(attributeKey, attribute); if (!attributeIndex.contains(attributeKey)) { attributeIndex.addElement(attributeKey); } } /** * Get the warnings for this section. * * @return an Enumeration of warning strings. */ public Enumeration getWarnings() { return warnings.elements(); } /** * @see java.lang.Object#hashCode */ @Override public int hashCode() { int hashCode = 0; if (name != null) { hashCode += name.hashCode(); } hashCode += attributes.hashCode(); return hashCode; } /** * @see java.lang.Object#equals */ @Override public boolean equals(Object rhs) { if (rhs == null || rhs.getClass() != getClass()) { return false; } if (rhs == this) { return true; } Section rhsSection = (Section) rhs; return rhsSection.attributes != null && attributes.equals(rhsSection.attributes); } } public class ExistingSection implements Iterable { private final Attributes backingAttributes; private final String sectionName; public ExistingSection(Attributes backingAttributes, String sectionName) { this.backingAttributes = backingAttributes; this.sectionName = sectionName; } @Override public Iterator iterator() { return getKeys(backingAttributes).iterator(); } public ExistingAttribute getAttribute(String attributeName) { Attributes.Name name = new Attributes.Name(attributeName); return backingAttributes.containsKey(name) ? new ExistingAttribute(backingAttributes, attributeName) : null; } public String getName() { return sectionName; } public String getAttributeValue(String attributeName) { return backingAttributes.getValue(attributeName); } public void removeAttribute(String attributeName) { backingAttributes.remove(new Attributes.Name(attributeName)); } public void addConfiguredAttribute(Attribute attribute) throws ManifestException { backingAttributes.putValue(attribute.getName(), attribute.getValue()); } public String addAttributeAndCheck(Attribute attribute) throws ManifestException { return remap(backingAttributes, attribute); } @Override public int hashCode() { return backingAttributes.hashCode(); } @Override public boolean equals(Object rhs) { return rhs instanceof ExistingSection && backingAttributes.equals(((ExistingSection) rhs).backingAttributes); } } @Override public Iterator iterator() { return getEntries().keySet().iterator(); } /** * The main section of this manifest */ private Section mainSection = new Section(); /** * Construct a manifest from Ant's default manifest file. * * @param minimalDefaultManifest * indicates whether a minimal manifest will be created, thus having only * {@code Manifest-Version: 1.0} in it. * * @return the default manifest. * * @throws ArchiverException * if there is a problem loading the default manifest */ public static Manifest getDefaultManifest(boolean minimalDefaultManifest) throws ArchiverException { final Manifest defaultManifest = new Manifest(); defaultManifest.getMainAttributes().putValue("Manifest-Version", "1.0"); if (!minimalDefaultManifest) { String createdBy = "Plexus Archiver"; final String plexusArchiverVersion = JdkManifestFactory.getArchiverVersion(); if (plexusArchiverVersion != null) { createdBy += " " + plexusArchiverVersion; } defaultManifest.getMainAttributes().putValue("Created-By", createdBy); } return defaultManifest; } /** * @see #getDefaultManifest(boolean) */ public static Manifest getDefaultManifest() throws ArchiverException { return getDefaultManifest(false); } /** * Construct an empty manifest */ public Manifest() { setManifestVersion(); } private void setManifestVersion() { getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); } /** * Read a manifest file from the given reader * * @param r is the reader from which the Manifest is read * * @throws ManifestException if the manifest is not valid according * to the JAR spec * @throws IOException if the manifest cannot be read from the reader. * @deprecated This constructor does not properly map characters to bytes. Use * {@link #Manifest(InputStream)}. Will be removed in 4.0. */ @Deprecated public Manifest(Reader r) throws ManifestException, IOException { super(getInputStream(r)); setManifestVersion(); } public Manifest(InputStream is) throws IOException { super(is); setManifestVersion(); } /** * Add a section to the manifest * * @param section the manifest section to be added * * @throws ManifestException if the secti0on is not valid. */ public void addConfiguredSection(Section section) throws ManifestException { String sectionName = section.getName(); if (sectionName == null) { throw new ManifestException("Sections must have a name"); } Attributes attributes = getOrCreateAttributes(sectionName); for (String s : section.attributes.keySet()) { Attribute attribute = section.getAttribute(s); attributes.putValue(attribute.getName(), attribute.getValue()); } } private Attributes getOrCreateAttributes(String name) { Attributes attributes = getAttributes(name); if (attributes == null) { attributes = new Attributes(); getEntries().put(name, attributes); } return attributes; } /** * Add an attribute to the manifest - it is added to the main section. * * @param attribute the attribute to be added. * * @throws ManifestException if the attribute is not valid. */ public void addConfiguredAttribute(Attribute attribute) throws ManifestException { remap(getMainAttributes(), attribute); } /** * Writes the manifest out to a writer. * * @param writer the Writer to which the manifest is written * * @throws IOException if the manifest cannot be written */ public void write(Writer writer) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); super.write(byteArrayOutputStream); // We know that UTF-8 is the encoding of the JAR file specification writer.write(byteArrayOutputStream.toString("UTF-8")); } /** * Convert the manifest to its string representation * * @return a multiline string with the Manifest as it * appears in a Manifest file. */ @Override public String toString() { StringWriter sw = new StringWriter(); try { write(sw); } catch (IOException e) { return null; } return sw.toString(); } /** * Get the warnings for this manifest. * * @return an enumeration of warning strings */ Enumeration getWarnings() { Vector warnings = new Vector(); Enumeration warnEnum = mainSection.getWarnings(); while (warnEnum.hasMoreElements()) { warnings.addElement(warnEnum.nextElement()); } return warnings.elements(); } /** * Get the version of the manifest * * @return the manifest's version string */ public String getManifestVersion() { /* The version of this manifest */ return DEFAULT_MANIFEST_VERSION; } /** * Get the main section of the manifest * * @return the main section of the manifest */ public ExistingSection getMainSection() { return new ExistingSection(getMainAttributes(), null); } /** * Get a particular section from the manifest * * @param name the name of the section desired. * * @return the specified section or null if that section * does not exist in the manifest */ public ExistingSection getSection(String name) { Attributes attributes = getAttributes(name); if (attributes != null) { return new ExistingSection(attributes, name); } return null; } @Deprecated private static InputStream getInputStream(Reader r) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); int read; while ((read = r.read()) != -1) { byteArrayOutputStream.write(read); } return new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); } public static String remap(Attributes backingAttributes, Attribute attribute) throws ManifestException { if (attribute.getKey() == null || attribute.getValue() == null) { throw new ManifestException("Attributes must have name and value"); } String attributeKey = attribute.getKey(); if (attributeKey.equalsIgnoreCase(ManifestConstants.ATTRIBUTE_CLASSPATH)) { String classpathAttribute = backingAttributes.getValue(attributeKey); if (classpathAttribute == null) { classpathAttribute = attribute.getValue(); } else { classpathAttribute += " " + attribute.getValue(); } backingAttributes.putValue(ManifestConstants.ATTRIBUTE_CLASSPATH, classpathAttribute); } else { backingAttributes.putValue(attribute.getName(), attribute.getValue()); if (attribute.getKey().equalsIgnoreCase(ATTRIBUTE_NAME)) { return attribute.getValue(); } } return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy