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;
}
}