@@ -25,24 +25,18 @@ import java.util.Iterator;
import java.util.jar.Attributes;
import org.apache.myrmidon.api.AbstractTask;
import org.apache.myrmidon.api.TaskException;
import org.apache.tools.ant.types.EnumeratedAttribute;
/**
* Class to manage Manifest information
*
* @author <a href="mailto:peter@apache.org">Peter Donald</a>
* @author <a href="mailto:conor@apache.org">Conor MacNeill</a>
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
* @author <a href="mailto:peter@apache.org">Peter Donald</a>
* @version $Revision$ $Date$
*/
public class Manifest
extends AbstractTask
{
/**
* The standard manifest version header
*/
public final static String ATTRIBUTE_MANIFEST_VERSION = Attributes.Name.MANIFEST_VERSION.toString();
/**
* The standard Signature Version header
*/
@@ -119,11 +113,11 @@ public class Manifest
BufferedReader reader = new BufferedReader( r );
// This should be the manifest version
String nextSectionName = m_mainSection.read( reader );
String readManifestVersion = m_mainSection.getAttributeValue( ATTRIBUTE_MANIFEST_VERSION );
String readManifestVersion = m_mainSection.getAttributeValue( Attributes.Name.MANIFEST_VERSION.toString() );
if( readManifestVersion != null )
{
m_manifestVersion = readManifestVersion;
m_mainSection.removeAttribute( ATTRIBUTE_MANIFEST_VERSION );
m_mainSection.removeAttribute( Attributes.Name.MANIFEST_VERSION.toString() );
}
String line = null;
@@ -137,7 +131,7 @@ public class Manifest
Section section = new Section();
if( nextSectionName == null )
{
Attribute sectionName = new Attribute( line );
Attribute sectionName = ManifestUtil.build Attribute( line );
if( !sectionName.getName().equalsIgnoreCase( ATTRIBUTE_NAME ) )
{
throw new ManifestException( "Manifest sections should start with a \"" + ATTRIBUTE_NAME +
@@ -150,7 +144,7 @@ public class Manifest
// we have already started reading this section
// this line is the first attribute. set it and then let the normal
// read handle the rest
Attribute firstAttribute = new Attribute( line );
Attribute firstAttribute = ManifestUtil.build Attribute( line );
section.addAttributeAndCheck( firstAttribute );
}
@@ -438,7 +432,7 @@ public class Manifest
public void write( PrintWriter writer )
throws IOException, TaskException
{
writer.println( ATTRIBUTE_ MANIFEST_VERSION + ": " + m_manifestVersion );
writer.println( Attributes.Name. MANIFEST_VERSION + ": " + m_manifestVersion );
String signatureVersion = m_mainSection.getAttributeValue( ATTRIBUTE_SIGNATURE_VERSION );
if( signatureVersion != null )
{
@@ -465,493 +459,4 @@ public class Manifest
}
}
/**
* Class to hold manifest attributes
*
* @author RT
*/
public static class Attribute
{
/**
* The attribute's name
*/
private String name = null;
/**
* The attribute's value
*/
private String value = null;
/**
* Construct an empty attribute
*/
public Attribute()
{
}
/**
* Construct an attribute by parsing a line from the Manifest
*
* @param line the line containing the attribute name and value
* @exception ManifestException Description of Exception
* @throws ManifestException if the line is not valid
*/
public Attribute( String line )
throws ManifestException
{
parse( line );
}
/**
* 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;
this.value = value;
}
/**
* Set the Attribute's name
*
* @param name the attribute's name
*/
public void setName( String name )
{
this.name = name;
}
/**
* Set the Attribute's value
*
* @param value the attribute's value
*/
public void setValue( String value )
{
this.value = value;
}
/**
* Get the Attribute's name
*
* @return the attribute's name.
*/
public String getName()
{
return name;
}
/**
* Get the Attribute's value
*
* @return the attribute's value.
*/
public String getValue()
{
return value;
}
/**
* Add a continuation line from the Manifest file When lines are too
* long in a manifest, they are continued on the next line by starting
* with a space. This method adds the continuation data to the attribute
* value by skipping the first character.
*
* @param line The feature to be added to the Continuation attribute
*/
public void addContinuation( String line )
{
value += line.substring( 1 );
}
public boolean equals( Object rhs )
{
if( !( rhs instanceof Attribute ) )
{
return false;
}
Attribute rhsAttribute = (Attribute)rhs;
return ( name != null && rhsAttribute.name != null &&
name.toLowerCase().equals( rhsAttribute.name.toLowerCase() ) &&
value != null && value.equals( rhsAttribute.value ) );
}
/**
* Parse a line into name and value pairs
*
* @param line the line to be parsed
* @throws ManifestException if the line does not contain a colon
* separating the name and value
*/
public void parse( String line )
throws ManifestException
{
int index = line.indexOf( ": " );
if( index == -1 )
{
throw new ManifestException( "Manifest line \"" + line + "\" is not valid as it does not " +
"contain a name and a value separated by ': ' " );
}
name = line.substring( 0, index );
value = line.substring( index + 2 );
}
/**
* Write the attribute out to a print writer.
*
* @param writer the Writer to which the attribute is written
* @throws IOException if the attribte value cannot be written
*/
public void write( PrintWriter writer )
throws IOException
{
String line = name + ": " + value;
while( line.getBytes().length > MAX_LINE_LENGTH )
{
// try to find a MAX_LINE_LENGTH byte section
int breakIndex = MAX_LINE_LENGTH;
String section = line.substring( 0, breakIndex );
while( section.getBytes().length > MAX_LINE_LENGTH && breakIndex > 0 )
{
breakIndex--;
section = line.substring( 0, breakIndex );
}
if( breakIndex == 0 )
{
throw new IOException( "Unable to write manifest line " + name + ": " + value );
}
writer.println( section );
line = " " + line.substring( breakIndex );
}
writer.println( line );
}
}
/**
* Helper class for Manifest's mode attribute.
*/
public static class ManifestMode extends EnumeratedAttribute
{
public String[] getValues()
{
return new String[]{"update", "replace"};
}
}
/**
* Class to represent an individual section in the Manifest. A section
* consists of a set of attribute values, separated from other sections by a
* blank line.
*
* @author RT
*/
public static class Section
{
private ArrayList warnings = new ArrayList();
/**
* 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();
/**
* Set the Section's name
*
* @param name the section's name
*/
public void setName( String name )
{
this.name = name;
}
/**
* Get the value of the attribute with the name given.
*
* @param attributeName the name of the attribute to be returned.
* @return the attribute's value or null if the attribute does not exist
* in the section
*/
public String getAttributeValue( String attributeName )
{
Object attribute = attributes.get( attributeName.toLowerCase() );
if( attribute == null )
{
return null;
}
if( attribute instanceof Attribute )
{
return ( (Attribute)attribute ).getValue();
}
else
{
String value = "";
for( Iterator e = ( (ArrayList)attribute ).iterator(); e.hasNext(); )
{
Attribute classpathAttribute = (Attribute)e.next();
value += classpathAttribute.getValue() + " ";
}
return value.trim();
}
}
/**
* Get the Section's name
*
* @return the section's name.
*/
public String getName()
{
return name;
}
public Iterator getWarnings()
{
return warnings.iterator();
}
/**
* 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, TaskException
{
if( attribute.getName() == null || attribute.getValue() == null )
{
throw new TaskException( "Attributes must have name and value" );
}
if( attribute.getName().equalsIgnoreCase( ATTRIBUTE_NAME ) )
{
warnings.add( "\"" + 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.getName().toLowerCase().startsWith( ATTRIBUTE_FROM.toLowerCase() ) )
{
warnings.add( "Manifest attributes should not start with \"" +
ATTRIBUTE_FROM + "\" in \"" + attribute.getName() + ": " + attribute.getValue() + "\"" );
}
else
{
// classpath attributes go into a vector
String attributeName = attribute.getName().toLowerCase();
if( attributeName.equals( ATTRIBUTE_CLASSPATH ) )
{
ArrayList classpathAttrs = (ArrayList)attributes.get( attributeName );
if( classpathAttrs == null )
{
classpathAttrs = new ArrayList();
attributes.put( attributeName, classpathAttrs );
}
classpathAttrs.add( attribute );
}
else if( attributes.containsKey( attributeName ) )
{
throw new ManifestException( "The attribute \"" + attribute.getName() + "\" may not " +
"occur more than once in the same section" );
}
else
{
attributes.put( attributeName, attribute );
}
}
return null;
}
public void addAttribute( Attribute attribute )
throws ManifestException, TaskException
{
String check = addAttributeAndCheck( attribute );
if( check != null )
{
throw new TaskException( "Specify the section name using the \"name\" attribute of the <section> element rather " +
"than using a \"Name\" manifest attribute" );
}
}
public boolean equals( Object rhs )
{
if( !( rhs instanceof Section ) )
{
return false;
}
Section rhsSection = (Section)rhs;
if( attributes.size() != rhsSection.attributes.size() )
{
return false;
}
for( Enumeration e = attributes.elements(); e.hasMoreElements(); )
{
Attribute attribute = (Attribute)e.nextElement();
Attribute rshAttribute = (Attribute)rhsSection.attributes.get( attribute.getName().toLowerCase() );
if( !attribute.equals( rshAttribute ) )
{
return false;
}
}
return true;
}
/**
* Merge in another section
*
* @param section the section to be merged with this one.
* @throws ManifestException if the sections cannot be merged.
*/
public void merge( Section section )
throws ManifestException
{
if( name == null && section.getName() != null ||
name != null && !( name.equalsIgnoreCase( section.getName() ) ) )
{
throw new ManifestException( "Unable to merge sections with different names" );
}
for( Enumeration e = section.attributes.keys(); e.hasMoreElements(); )
{
String attributeName = (String)e.nextElement();
if( attributeName.equals( ATTRIBUTE_CLASSPATH ) &&
attributes.containsKey( attributeName ) )
{
// classpath entries are vetors which are merged
ArrayList classpathAttrs = (ArrayList)section.attributes.get( attributeName );
ArrayList ourClasspathAttrs = (ArrayList)attributes.get( attributeName );
for( Iterator e2 = classpathAttrs.iterator(); e2.hasNext(); )
{
ourClasspathAttrs.add( e2.next() );
}
}
else
{
// the merge file always wins
attributes.put( attributeName, section.attributes.get( attributeName ) );
}
}
// add in the warnings
for( Iterator e = section.warnings.iterator(); e.hasNext(); )
{
warnings.add( e.next() );
}
}
/**
* Read a section through a reader
*
* @param reader the reader from which the section is read
* @return the name of the next section if it has been read as part of
* this section - This only happens if the Manifest is malformed.
* @throws ManifestException if the section is not valid according to
* the JAR spec
* @throws IOException if the section cannot be read from the reader.
*/
public String read( BufferedReader reader )
throws ManifestException, IOException, TaskException
{
Attribute attribute = null;
while( true )
{
String line = reader.readLine();
if( line == null || line.length() == 0 )
{
return null;
}
if( line.charAt( 0 ) == ' ' )
{
// continuation line
if( attribute == null )
{
if( name != null )
{
// a continuation on the first line is a continuation of the name - concatenate
// this line and the name
name += line.substring( 1 );
}
else
{
throw new ManifestException( "Can't start an attribute with a continuation line " + line );
}
}
else
{
attribute.addContinuation( line );
}
}
else
{
attribute = new Attribute( line );
String nameReadAhead = addAttributeAndCheck( attribute );
if( nameReadAhead != null )
{
return nameReadAhead;
}
}
}
}
/**
* Remove tge given attribute from the section
*
* @param attributeName the name of the attribute to be removed.
*/
public void removeAttribute( String attributeName )
{
attributes.remove( attributeName.toLowerCase() );
}
/**
* Write the section out to a print writer.
*
* @param writer the Writer to which the section is written
* @throws IOException if the section cannot be written
*/
public void write( PrintWriter writer )
throws IOException
{
if( name != null )
{
Attribute nameAttr = new Attribute( ATTRIBUTE_NAME, name );
nameAttr.write( writer );
}
for( Enumeration e = attributes.elements(); e.hasMoreElements(); )
{
Object object = e.nextElement();
if( object instanceof Attribute )
{
Attribute attribute = (Attribute)object;
attribute.write( writer );
}
else
{
ArrayList attrList = (ArrayList)object;
for( Iterator e2 = attrList.iterator(); e2.hasNext(); )
{
Attribute attribute = (Attribute)e2.next();
attribute.write( writer );
}
}
}
writer.println();
}
}
}