git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@271212 13f79535-47bb-0310-9956-ffa450edef68master
| @@ -0,0 +1,119 @@ | |||
| /* | |||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||
| * | |||
| * This software is published under the terms of the Apache Software License | |||
| * version 1.1, a copy of which has been included with this distribution in | |||
| * the LICENSE.txt file. | |||
| */ | |||
| package org.apache.tools.ant.taskdefs.manifest; | |||
| import java.io.PrintWriter; | |||
| import java.io.IOException; | |||
| /** | |||
| * Class to hold manifest attributes | |||
| * | |||
| * @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 Attribute | |||
| { | |||
| /** | |||
| * The attribute's name | |||
| */ | |||
| private String m_name; | |||
| /** | |||
| * The attribute's value | |||
| */ | |||
| private String m_value; | |||
| /** | |||
| * 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( final String name, final String value ) | |||
| { | |||
| m_name = name; | |||
| m_value = value; | |||
| } | |||
| /** | |||
| * Set the Attribute's name | |||
| * | |||
| * @param name the attribute's name | |||
| */ | |||
| public void setName( final String name ) | |||
| { | |||
| m_name = name; | |||
| } | |||
| /** | |||
| * Set the Attribute's value | |||
| * | |||
| * @param value the attribute's value | |||
| */ | |||
| public void setValue( final String value ) | |||
| { | |||
| m_value = value; | |||
| } | |||
| /** | |||
| * Get the Attribute's name | |||
| * | |||
| * @return the attribute's name. | |||
| */ | |||
| public String getName() | |||
| { | |||
| return m_name; | |||
| } | |||
| /** | |||
| * Get the Attribute's value | |||
| * | |||
| * @return the attribute's value. | |||
| */ | |||
| public String getValue() | |||
| { | |||
| return m_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( final String line ) | |||
| { | |||
| m_value += line.substring( 1 ); | |||
| } | |||
| public boolean equals( Object object ) | |||
| { | |||
| if( !( object instanceof Attribute ) ) | |||
| { | |||
| return false; | |||
| } | |||
| final Attribute other = (Attribute)object; | |||
| return | |||
| ( null != m_name && null != other.m_name && | |||
| m_name.toLowerCase().equals( other.m_name.toLowerCase() ) && | |||
| m_value != null && m_value.equals( other.m_value ) ); | |||
| } | |||
| } | |||
| @@ -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.buildAttribute( 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.buildAttribute( 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(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| /* | |||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||
| * | |||
| * This software is published under the terms of the Apache Software License | |||
| * version 1.1, a copy of which has been included with this distribution in | |||
| * the LICENSE.txt file. | |||
| */ | |||
| package org.apache.tools.ant.taskdefs.manifest; | |||
| import org.apache.tools.ant.types.EnumeratedAttribute; | |||
| /** | |||
| * Helper class for Manifest's mode attribute. | |||
| * | |||
| * @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 ManifestMode | |||
| extends EnumeratedAttribute | |||
| { | |||
| public String[] getValues() | |||
| { | |||
| return new String[]{"update", "replace"}; | |||
| } | |||
| } | |||
| @@ -0,0 +1,78 @@ | |||
| /* | |||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||
| * | |||
| * This software is published under the terms of the Apache Software License | |||
| * version 1.1, a copy of which has been included with this distribution in | |||
| * the LICENSE.txt file. | |||
| */ | |||
| package org.apache.tools.ant.taskdefs.manifest; | |||
| import java.io.IOException; | |||
| import java.io.PrintWriter; | |||
| /** | |||
| * Utility methods for manifest stuff. | |||
| * | |||
| * @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 final class ManifestUtil | |||
| { | |||
| public static Attribute buildAttribute( final String line ) | |||
| throws ManifestException | |||
| { | |||
| final Attribute attribute = new Attribute(); | |||
| parse( attribute, line ); | |||
| return attribute; | |||
| } | |||
| /** | |||
| * 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 static void parse( final Attribute attribute, final String line ) | |||
| throws ManifestException | |||
| { | |||
| final 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 ': ' " ); | |||
| } | |||
| final String name = line.substring( 0, index ); | |||
| final String value = line.substring( index + 2 ); | |||
| attribute.setName( name ); | |||
| attribute.setValue( value ); | |||
| } | |||
| public static void write( final Attribute attribute, final PrintWriter writer ) | |||
| throws IOException | |||
| { | |||
| final String name = attribute.getName(); | |||
| final String value = attribute.getValue(); | |||
| String line = name + ": " + value; | |||
| while( line.getBytes().length > Manifest.MAX_LINE_LENGTH ) | |||
| { | |||
| // try to find a MAX_LINE_LENGTH byte section | |||
| int breakIndex = Manifest.MAX_LINE_LENGTH; | |||
| String section = line.substring( 0, breakIndex ); | |||
| while( section.getBytes().length > Manifest.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 ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,332 @@ | |||
| /* | |||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||
| * | |||
| * This software is published under the terms of the Apache Software License | |||
| * version 1.1, a copy of which has been included with this distribution in | |||
| * the LICENSE.txt file. | |||
| */ | |||
| package org.apache.tools.ant.taskdefs.manifest; | |||
| import java.io.BufferedReader; | |||
| import java.io.IOException; | |||
| import java.io.PrintWriter; | |||
| import java.util.ArrayList; | |||
| import java.util.Enumeration; | |||
| import java.util.Hashtable; | |||
| import java.util.Iterator; | |||
| import org.apache.myrmidon.api.TaskException; | |||
| /** | |||
| * 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 <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 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( Manifest.ATTRIBUTE_NAME ) ) | |||
| { | |||
| warnings.add( "\"" + Manifest.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( Manifest.ATTRIBUTE_FROM.toLowerCase() ) ) | |||
| { | |||
| warnings.add( "Manifest attributes should not start with \"" + | |||
| Manifest.ATTRIBUTE_FROM + "\" in \"" + attribute.getName() + ": " + attribute.getValue() + "\"" ); | |||
| } | |||
| else | |||
| { | |||
| // classpath attributes go into a vector | |||
| String attributeName = attribute.getName().toLowerCase(); | |||
| if( attributeName.equals( Manifest.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( Manifest.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 = ManifestUtil.buildAttribute( 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( Manifest.ATTRIBUTE_NAME, name ); | |||
| ManifestUtil.write( nameAttr, writer ); | |||
| } | |||
| for( Enumeration e = attributes.elements(); e.hasMoreElements(); ) | |||
| { | |||
| Object object = e.nextElement(); | |||
| if( object instanceof Attribute ) | |||
| { | |||
| Attribute attribute = (Attribute)object; | |||
| ManifestUtil.write( attribute, writer ); | |||
| } | |||
| else | |||
| { | |||
| ArrayList attrList = (ArrayList)object; | |||
| for( Iterator e2 = attrList.iterator(); e2.hasNext(); ) | |||
| { | |||
| Attribute attribute = (Attribute)e2.next(); | |||
| ManifestUtil.write( attribute, writer ); | |||
| } | |||
| } | |||
| } | |||
| writer.println(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,119 @@ | |||
| /* | |||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||
| * | |||
| * This software is published under the terms of the Apache Software License | |||
| * version 1.1, a copy of which has been included with this distribution in | |||
| * the LICENSE.txt file. | |||
| */ | |||
| package org.apache.tools.ant.taskdefs.manifest; | |||
| import java.io.PrintWriter; | |||
| import java.io.IOException; | |||
| /** | |||
| * Class to hold manifest attributes | |||
| * | |||
| * @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 Attribute | |||
| { | |||
| /** | |||
| * The attribute's name | |||
| */ | |||
| private String m_name; | |||
| /** | |||
| * The attribute's value | |||
| */ | |||
| private String m_value; | |||
| /** | |||
| * 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( final String name, final String value ) | |||
| { | |||
| m_name = name; | |||
| m_value = value; | |||
| } | |||
| /** | |||
| * Set the Attribute's name | |||
| * | |||
| * @param name the attribute's name | |||
| */ | |||
| public void setName( final String name ) | |||
| { | |||
| m_name = name; | |||
| } | |||
| /** | |||
| * Set the Attribute's value | |||
| * | |||
| * @param value the attribute's value | |||
| */ | |||
| public void setValue( final String value ) | |||
| { | |||
| m_value = value; | |||
| } | |||
| /** | |||
| * Get the Attribute's name | |||
| * | |||
| * @return the attribute's name. | |||
| */ | |||
| public String getName() | |||
| { | |||
| return m_name; | |||
| } | |||
| /** | |||
| * Get the Attribute's value | |||
| * | |||
| * @return the attribute's value. | |||
| */ | |||
| public String getValue() | |||
| { | |||
| return m_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( final String line ) | |||
| { | |||
| m_value += line.substring( 1 ); | |||
| } | |||
| public boolean equals( Object object ) | |||
| { | |||
| if( !( object instanceof Attribute ) ) | |||
| { | |||
| return false; | |||
| } | |||
| final Attribute other = (Attribute)object; | |||
| return | |||
| ( null != m_name && null != other.m_name && | |||
| m_name.toLowerCase().equals( other.m_name.toLowerCase() ) && | |||
| m_value != null && m_value.equals( other.m_value ) ); | |||
| } | |||
| } | |||
| @@ -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.buildAttribute( 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.buildAttribute( 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(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| /* | |||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||
| * | |||
| * This software is published under the terms of the Apache Software License | |||
| * version 1.1, a copy of which has been included with this distribution in | |||
| * the LICENSE.txt file. | |||
| */ | |||
| package org.apache.tools.ant.taskdefs.manifest; | |||
| import org.apache.tools.ant.types.EnumeratedAttribute; | |||
| /** | |||
| * Helper class for Manifest's mode attribute. | |||
| * | |||
| * @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 ManifestMode | |||
| extends EnumeratedAttribute | |||
| { | |||
| public String[] getValues() | |||
| { | |||
| return new String[]{"update", "replace"}; | |||
| } | |||
| } | |||
| @@ -0,0 +1,78 @@ | |||
| /* | |||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||
| * | |||
| * This software is published under the terms of the Apache Software License | |||
| * version 1.1, a copy of which has been included with this distribution in | |||
| * the LICENSE.txt file. | |||
| */ | |||
| package org.apache.tools.ant.taskdefs.manifest; | |||
| import java.io.IOException; | |||
| import java.io.PrintWriter; | |||
| /** | |||
| * Utility methods for manifest stuff. | |||
| * | |||
| * @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 final class ManifestUtil | |||
| { | |||
| public static Attribute buildAttribute( final String line ) | |||
| throws ManifestException | |||
| { | |||
| final Attribute attribute = new Attribute(); | |||
| parse( attribute, line ); | |||
| return attribute; | |||
| } | |||
| /** | |||
| * 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 static void parse( final Attribute attribute, final String line ) | |||
| throws ManifestException | |||
| { | |||
| final 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 ': ' " ); | |||
| } | |||
| final String name = line.substring( 0, index ); | |||
| final String value = line.substring( index + 2 ); | |||
| attribute.setName( name ); | |||
| attribute.setValue( value ); | |||
| } | |||
| public static void write( final Attribute attribute, final PrintWriter writer ) | |||
| throws IOException | |||
| { | |||
| final String name = attribute.getName(); | |||
| final String value = attribute.getValue(); | |||
| String line = name + ": " + value; | |||
| while( line.getBytes().length > Manifest.MAX_LINE_LENGTH ) | |||
| { | |||
| // try to find a MAX_LINE_LENGTH byte section | |||
| int breakIndex = Manifest.MAX_LINE_LENGTH; | |||
| String section = line.substring( 0, breakIndex ); | |||
| while( section.getBytes().length > Manifest.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 ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,332 @@ | |||
| /* | |||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||
| * | |||
| * This software is published under the terms of the Apache Software License | |||
| * version 1.1, a copy of which has been included with this distribution in | |||
| * the LICENSE.txt file. | |||
| */ | |||
| package org.apache.tools.ant.taskdefs.manifest; | |||
| import java.io.BufferedReader; | |||
| import java.io.IOException; | |||
| import java.io.PrintWriter; | |||
| import java.util.ArrayList; | |||
| import java.util.Enumeration; | |||
| import java.util.Hashtable; | |||
| import java.util.Iterator; | |||
| import org.apache.myrmidon.api.TaskException; | |||
| /** | |||
| * 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 <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 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( Manifest.ATTRIBUTE_NAME ) ) | |||
| { | |||
| warnings.add( "\"" + Manifest.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( Manifest.ATTRIBUTE_FROM.toLowerCase() ) ) | |||
| { | |||
| warnings.add( "Manifest attributes should not start with \"" + | |||
| Manifest.ATTRIBUTE_FROM + "\" in \"" + attribute.getName() + ": " + attribute.getValue() + "\"" ); | |||
| } | |||
| else | |||
| { | |||
| // classpath attributes go into a vector | |||
| String attributeName = attribute.getName().toLowerCase(); | |||
| if( attributeName.equals( Manifest.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( Manifest.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 = ManifestUtil.buildAttribute( 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( Manifest.ATTRIBUTE_NAME, name ); | |||
| ManifestUtil.write( nameAttr, writer ); | |||
| } | |||
| for( Enumeration e = attributes.elements(); e.hasMoreElements(); ) | |||
| { | |||
| Object object = e.nextElement(); | |||
| if( object instanceof Attribute ) | |||
| { | |||
| Attribute attribute = (Attribute)object; | |||
| ManifestUtil.write( attribute, writer ); | |||
| } | |||
| else | |||
| { | |||
| ArrayList attrList = (ArrayList)object; | |||
| for( Iterator e2 = attrList.iterator(); e2.hasNext(); ) | |||
| { | |||
| Attribute attribute = (Attribute)e2.next(); | |||
| ManifestUtil.write( attribute, writer ); | |||
| } | |||
| } | |||
| } | |||
| writer.println(); | |||
| } | |||
| } | |||