org.codehaus.plexus.archiver.jar.Manifest Maven / Gradle / Ivy
/**
*
* 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;
}
}