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

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

There is a newer version: 4.10.0
Show 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() ); } /** * 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. * * @return the default manifest. * * @throws ArchiverException if there is a problem loading the * default manifest */ public static Manifest getDefaultManifest() throws ArchiverException { final Manifest defaultManifest = new Manifest(); defaultManifest.getMainAttributes().putValue( "Manifest-Version", "1.0" ); String createdBy = "Plexus Archiver"; final String plexusArchiverVersion = JdkManifestFactory.getArchiverVersion(); if ( plexusArchiverVersion != null ) { createdBy += " " + plexusArchiverVersion; } defaultManifest.getMainAttributes().putValue( "Created-By", createdBy ); return defaultManifest; } /** * 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