View Javadoc
1   /**
2    *
3    * Copyright 2004 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.codehaus.plexus.archiver.jar;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.Reader;
24  import java.io.StringWriter;
25  import java.io.Writer;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Enumeration;
29  import java.util.Hashtable;
30  import java.util.Iterator;
31  import java.util.Locale;
32  import java.util.StringTokenizer;
33  import java.util.Vector;
34  import java.util.jar.Attributes;
35  import org.codehaus.plexus.archiver.ArchiverException;
36  
37  /**
38   * Holds the data of a jar manifest.
39   * <p>
40   * Manifests are processed according to the
41   * <a href="http://java.sun.com/j2se/1.4/docs/guide/jar/jar.html">Jar
42   * file specification.</a>
43   * Specifically, a manifest element consists of
44   * a set of attributes and sections. These sections in turn may contain
45   * attributes. Note in particular that this may result in manifest lines
46   * greater than 72 bytes (including line break) being wrapped and continued
47   * on the next line. If an application can not handle the continuation
48   * mechanism, it is a defect in the application, not this task.</p>
49   *
50   * @since Ant 1.4
51   */
52  public class Manifest
53      extends java.util.jar.Manifest implements Iterable<String>
54  {
55  
56      /**
57       * The Name Attribute is the first in a named section
58       */
59      private static final String ATTRIBUTE_NAME = ManifestConstants.ATTRIBUTE_NAME;
60  
61      /**
62       * The From Header is disallowed in a Manifest
63       */
64      private static final String ATTRIBUTE_FROM = ManifestConstants.ATTRIBUTE_FROM;
65  
66      /**
67       * Default Manifest version if one is not specified
68       */
69      private static final String DEFAULT_MANIFEST_VERSION = ManifestConstants.DEFAULT_MANIFEST_VERSION;
70  
71      /**
72       * The max length of a line in a Manifest
73       */
74      private static final int MAX_LINE_LENGTH = 72;
75  
76      /**
77       * Max length of a line section which is continued. Need to allow
78       * for the CRLF.
79       */
80      private static final int MAX_SECTION_LENGTH = MAX_LINE_LENGTH - 2;
81  
82      /**
83       * The End-Of-Line marker in manifests
84       */
85      static final String EOL = "\r\n";
86  
87      public static class BaseAttribute
88      {
89  
90          /**
91           * The attribute's name
92           */
93          protected String name = null;
94  
95          /**
96           * Get the Attribute's name
97           *
98           * @return the attribute's name.
99           */
100         public String getName()
101         {
102             return name;
103         }
104 
105         @Override
106         public boolean equals( Object o )
107         {
108             if ( this == o )
109             {
110                 return true;
111             }
112             if ( !( o instanceof BaseAttribute ) )
113             {
114                 return false;
115             }
116 
117             BaseAttribute that = (BaseAttribute) o;
118 
119             return !( name != null ? !name.equals( that.name ) : that.name != null );
120 
121         }
122 
123         @Override
124         public int hashCode()
125         {
126             return name != null ? name.hashCode() : 0;
127         }
128 
129     }
130 
131     /**
132      * An attribute for the manifest.
133      * Those attributes that are not nested into a section will be added to the "Main" section.
134      */
135     public static class Attribute
136         extends BaseAttribute implements Iterable<String>
137     {
138 
139         /**
140          * The attribute's value
141          */
142         private Vector<String> values = new Vector<String>();
143 
144         /**
145          * For multivalued attributes, this is the index of the attribute
146          * currently being defined.
147          */
148         private int currentIndex = 0;
149 
150         /**
151          * Construct an empty attribute
152          */
153         public Attribute()
154         {
155         }
156 
157         /**
158          * Construct a manifest by specifying its name and value
159          *
160          * @param name the attribute's name
161          * @param value the Attribute's value
162          */
163         public Attribute( String name, String value )
164         {
165             this.name = name;
166             setValue( value );
167         }
168 
169         @Override
170         public Iterator<String> iterator()
171         {
172             return values.iterator();
173         }
174 
175         /**
176          * @see java.lang.Object#hashCode
177          */
178         @Override
179         public int hashCode()
180         {
181             int hashCode = super.hashCode();
182             hashCode += values.hashCode();
183             return hashCode;
184         }
185 
186         /**
187          * @see java.lang.Object#equals
188          */
189         @Override
190         public boolean equals( Object rhs )
191         {
192             if ( super.equals( rhs ) )
193             {
194                 return false;
195             }
196             if ( rhs == null || rhs.getClass() != getClass() )
197             {
198                 return false;
199             }
200 
201             if ( rhs == this )
202             {
203                 return true;
204             }
205 
206             Attribute rhsAttribute = (Attribute) rhs;
207             String lhsKey = getKey();
208             String rhsKey = rhsAttribute.getKey();
209             //noinspection SimplifiableIfStatement,ConstantConditions
210             if ( ( lhsKey == null && rhsKey != null ) || ( lhsKey != null && rhsKey == null ) || !lhsKey.equals(
211                 rhsKey ) )
212             {
213                 return false;
214             }
215 
216             return rhsAttribute.values != null && values.equals( rhsAttribute.values );
217         }
218 
219         /**
220          * Set the Attribute's name; required
221          *
222          * @param name the attribute's name
223          */
224         public void setName( String name )
225         {
226             this.name = name;
227         }
228 
229         /**
230          * Get the attribute's Key - its name in lower case.
231          *
232          * @return the attribute's key.
233          */
234         public String getKey()
235         {
236             return getKey( name );
237         }
238 
239         /**
240          * Get the key for the specified attribute name - its name in lower case.
241          *
242          * @return the attribute's key.
243          */
244         private static String getKey( String name )
245         {
246             if ( name == null )
247             {
248                 return null;
249             }
250             return name.toLowerCase( Locale.ENGLISH );
251         }
252 
253         /**
254          * Set the Attribute's value; required
255          *
256          * @param value the attribute's value
257          */
258         public void setValue( String value )
259         {
260             if ( currentIndex >= values.size() )
261             {
262                 values.addElement( value );
263                 currentIndex = values.size() - 1;
264             }
265             else
266             {
267                 values.setElementAt( value, currentIndex );
268             }
269         }
270 
271         /**
272          * Get the Attribute's value.
273          *
274          * @return the attribute's value.
275          */
276         public String getValue()
277         {
278             if ( values.size() == 0 )
279             {
280                 return null;
281             }
282 
283             String fullValue = "";
284             for ( String value : values )
285             {
286                 fullValue += value + " ";
287             }
288             return fullValue.trim();
289         }
290 
291         /**
292          * Add a new value to this attribute - making it multivalued.
293          *
294          * @param value the attribute's additional value
295          */
296         public void addValue( String value )
297         {
298             currentIndex++;
299             setValue( value );
300         }
301 
302         /**
303          * Writes the attribute out to a writer.
304          *
305          * @param writer the Writer to which the attribute is written
306          *
307          * @throws IOException if the attribute value cannot be written
308          */
309         void write( Writer writer )
310             throws IOException
311         {
312             for ( String value : values )
313             {
314                 writeValue( writer, value );
315             }
316         }
317 
318         /**
319          * Write a single attribute value out. Should handle multiple lines of attribute value.
320          *
321          * @param writer the Writer to which the attribute is written
322          * @param value the attribute value
323          *
324          * @throws IOException if the attribute value cannot be written
325          */
326         private void writeValue( Writer writer, String value )
327             throws IOException
328         {
329             String nameValue = name + ": " + value;
330 
331             StringTokenizer tokenizer = new StringTokenizer( nameValue, "\n\r" );
332 
333             String prefix = "";
334 
335             while ( tokenizer.hasMoreTokens() )
336             {
337                 writeLine( writer, prefix + tokenizer.nextToken() );
338                 prefix = " ";
339             }
340         }
341 
342         /**
343          * Write a single Manifest line. Should handle more than 72 bytes of line
344          *
345          * @param writer the Writer to which the attribute is written
346          * @param line the manifest line to be written
347          *
348          * @throws java.io.IOException when Io excepts
349          */
350         private void writeLine( Writer writer, String line )
351             throws IOException
352         {
353             // Make sure we have at most 70 bytes in UTF-8 as specified excluding line break
354             while ( line.getBytes( "UTF-8" ).length > MAX_SECTION_LENGTH )
355             {
356                 // Try to find a MAX_SECTION_LENGTH
357                 // Use the minimum because we operate on at most chars and not bytes here otherwise
358                 // if we have more bytes than chars we will die in an IndexOutOfBoundsException.
359                 int breakIndex = Math.min( line.length(), MAX_SECTION_LENGTH );
360                 String section = line.substring( 0, breakIndex );
361                 while ( section.getBytes( "UTF-8" ).length > MAX_SECTION_LENGTH && breakIndex > 0 )
362                 {
363                     breakIndex--;
364                     section = line.substring( 0, breakIndex );
365                 }
366                 if ( breakIndex == 0 )
367                 {
368                     throw new IOException( "Unable to write manifest line " + line );
369                 }
370                 writer.write( section + EOL );
371                 line = " " + line.substring( breakIndex );
372             }
373             writer.write( line + EOL );
374         }
375 
376     }
377 
378     public class ExistingAttribute
379         extends Attribute implements Iterable<String>
380     {
381 
382         private final Attributes attributes;
383 
384         public ExistingAttribute( Attributes attributes, String name )
385         {
386             this.attributes = attributes;
387             this.name = name;
388         }
389 
390         @Override
391         public Iterator<String> iterator()
392         {
393             return getKeys( attributes ).iterator();
394         }
395 
396         @Override
397         public void setName( String name )
398         {
399             throw new UnsupportedOperationException( "Cant do this" );
400         }
401 
402         @Override
403         public String getKey()
404         {
405             return name;
406         }
407 
408         @Override
409         public void setValue( String value )
410         {
411             attributes.putValue( name, value );
412         }
413 
414         @Override
415         public String getValue()
416         {
417             return attributes.getValue( name );
418         }
419 
420         @Override
421         public void addValue( String value )
422         {
423             String value1 = getValue();
424             value1 = ( value1 != null ) ? " " + value : value;
425             setValue( value1 );
426         }
427 
428         @Override
429         void write( Writer writer )
430             throws IOException
431         {
432             throw new UnsupportedOperationException( "Cant do this" );
433         }
434 
435     }
436 
437     private static Collection<String> getKeys( Attributes attributes )
438     {
439         Collection<String> result = new ArrayList<String>();
440         for ( Object objectObjectEntry : attributes.keySet() )
441         {
442             result.add( objectObjectEntry.toString() );
443         }
444         return result;
445     }
446 
447     /**
448      * A manifest section - you can nest attribute elements into sections.
449      * A section consists of a set of attribute values,
450      * separated from other sections by a blank line.
451      */
452     public static class Section implements Iterable<String>
453     {
454 
455         /**
456          * Warnings for this section
457          */
458         private Vector<String> warnings = new Vector<String>();
459 
460         /**
461          * The section's name if any. The main section in a
462          * manifest is unnamed.
463          */
464         private String name = null;
465 
466         /**
467          * The section's attributes.
468          */
469         private Hashtable<String, Attribute> attributes = new Hashtable<String, Attribute>();
470 
471         /**
472          * Index used to retain the attribute ordering
473          */
474         private Vector<String> attributeIndex = new Vector<String>();
475 
476         /**
477          * The name of the section; optional -default is the main section.
478          *
479          * @param name the section's name
480          */
481         public void setName( String name )
482         {
483             this.name = name;
484         }
485 
486         /**
487          * Get the Section's name.
488          *
489          * @return the section's name.
490          */
491         public String getName()
492         {
493             return name;
494         }
495 
496         @Override
497         public Iterator<String> iterator()
498         {
499             return attributes.keySet().iterator();
500         }
501 
502         /**
503          * Get a attribute of the section
504          *
505          * @param attributeName the name of the attribute
506          *
507          * @return a Manifest.Attribute instance if the attribute is
508          * single-valued, otherwise a Vector of Manifest.Attribute
509          * instances.
510          */
511         public Attribute getAttribute( String attributeName )
512         {
513             return attributes.get( attributeName.toLowerCase() );
514         }
515 
516         /**
517          * Add an attribute to the section.
518          *
519          * @param attribute the attribute to be added to the section
520          *
521          * @throws ManifestException if the attribute is not valid.
522          */
523         public void addConfiguredAttribute( Attribute attribute )
524             throws ManifestException
525         {
526             String check = addAttributeAndCheck( attribute );
527             if ( check != null )
528             {
529                 throw new ManifestException(
530                     "Specify the section name using " + "the \"name\" attribute of the <section> element rather "
531                         + "than using a \"Name\" manifest attribute" );
532             }
533         }
534 
535         /**
536          * Add an attribute to the section
537          *
538          * @param attribute the attribute to be added.
539          *
540          * @return the value of the attribute if it is a name
541          * attribute - null other wise
542          *
543          * @throws ManifestException if the attribute already
544          * exists in this section.
545          */
546         public String addAttributeAndCheck( Attribute attribute )
547             throws ManifestException
548         {
549             if ( attribute.getName() == null || attribute.getValue() == null )
550             {
551                 throw new ManifestException( "Attributes must have name and value" );
552             }
553             if ( attribute.getKey().equalsIgnoreCase( ATTRIBUTE_NAME ) )
554             {
555                 warnings.addElement(
556                     "\"" + ATTRIBUTE_NAME + "\" attributes " + "should not occur in the main section and must be the "
557                         + "first element in all other sections: \"" + attribute.getName() + ": " + attribute.getValue()
558                         + "\"" );
559                 return attribute.getValue();
560             }
561 
562             if ( attribute.getKey().startsWith( Attribute.getKey( ATTRIBUTE_FROM ) ) )
563             {
564                 warnings.addElement( "Manifest attributes should not start " + "with \"" + ATTRIBUTE_FROM + "\" in \""
565                                          + attribute.getName() + ": " + attribute.getValue() + "\"" );
566             }
567             else
568             {
569                 // classpath attributes go into a vector
570                 String attributeKey = attribute.getKey();
571                 if ( attributeKey.equalsIgnoreCase( ManifestConstants.ATTRIBUTE_CLASSPATH ) )
572                 {
573                     Attribute classpathAttribute = attributes.get( attributeKey );
574 
575                     if ( classpathAttribute == null )
576                     {
577                         storeAttribute( attribute );
578                     }
579                     else
580                     {
581                         warnings.addElement( "Multiple Class-Path attributes " + "are supported but violate the Jar "
582                                                  + "specification and may not be correctly "
583                                                  + "processed in all environments" );
584 
585                         for ( String value : attribute )
586                         {
587                             classpathAttribute.addValue( value );
588                         }
589                     }
590                 }
591                 else if ( attributes.containsKey( attributeKey ) )
592                 {
593                     throw new ManifestException( "The attribute \"" + attribute.getName() + "\" may not occur more "
594                                                      + "than once in the same section" );
595                 }
596                 else
597                 {
598                     storeAttribute( attribute );
599                 }
600             }
601             return null;
602         }
603 
604         /**
605          * Store an attribute and update the index.
606          *
607          * @param attribute the attribute to be stored
608          */
609         protected void storeAttribute( Attribute attribute )
610         {
611             if ( attribute == null )
612             {
613                 return;
614             }
615 
616             String attributeKey = attribute.getKey();
617             attributes.put( attributeKey, attribute );
618             if ( !attributeIndex.contains( attributeKey ) )
619             {
620                 attributeIndex.addElement( attributeKey );
621             }
622         }
623 
624         /**
625          * Get the warnings for this section.
626          *
627          * @return an Enumeration of warning strings.
628          */
629         public Enumeration<String> getWarnings()
630         {
631             return warnings.elements();
632         }
633 
634         /**
635          * @see java.lang.Object#hashCode
636          */
637         @Override
638         public int hashCode()
639         {
640             int hashCode = 0;
641 
642             if ( name != null )
643             {
644                 hashCode += name.hashCode();
645             }
646 
647             hashCode += attributes.hashCode();
648             return hashCode;
649         }
650 
651         /**
652          * @see java.lang.Object#equals
653          */
654         @Override
655         public boolean equals( Object rhs )
656         {
657             if ( rhs == null || rhs.getClass() != getClass() )
658             {
659                 return false;
660             }
661 
662             if ( rhs == this )
663             {
664                 return true;
665             }
666 
667             Section rhsSection = (Section) rhs;
668 
669             return rhsSection.attributes != null && attributes.equals( rhsSection.attributes );
670         }
671 
672     }
673 
674     public class ExistingSection implements Iterable<String>
675     {
676 
677         private final Attributes backingAttributes;
678 
679         private final String sectionName;
680 
681         public ExistingSection( Attributes backingAttributes, String sectionName )
682         {
683             this.backingAttributes = backingAttributes;
684             this.sectionName = sectionName;
685         }
686 
687         @Override
688         public Iterator<String> iterator()
689         {
690             return getKeys( backingAttributes ).iterator();
691         }
692 
693         public ExistingAttribute getAttribute( String attributeName )
694         {
695             Attributes.Name name = new Attributes.Name( attributeName );
696             return backingAttributes.containsKey( name )
697                        ? new ExistingAttribute( backingAttributes, attributeName )
698                        : null;
699 
700         }
701 
702         public String getName()
703         {
704             return sectionName;
705         }
706 
707         public String getAttributeValue( String attributeName )
708         {
709             return backingAttributes.getValue( attributeName );
710         }
711 
712         public void removeAttribute( String attributeName )
713         {
714             backingAttributes.remove( new Attributes.Name( attributeName ) );
715         }
716 
717         public void addConfiguredAttribute( Attribute attribute )
718             throws ManifestException
719         {
720             backingAttributes.putValue( attribute.getName(), attribute.getValue() );
721         }
722 
723         public String addAttributeAndCheck( Attribute attribute )
724             throws ManifestException
725         {
726             return remap( backingAttributes, attribute );
727         }
728 
729         @Override
730         public int hashCode()
731         {
732             return backingAttributes.hashCode();
733         }
734 
735         @Override
736         public boolean equals( Object rhs )
737         {
738             return rhs instanceof ExistingSection && backingAttributes.equals(
739                 ( (ExistingSection) rhs ).backingAttributes );
740         }
741 
742     }
743 
744     @Override
745     public Iterator<String> iterator()
746     {
747         return getEntries().keySet().iterator();
748     }
749 
750     /**
751      * The main section of this manifest
752      */
753     private Section mainSection = new Section();
754 
755     /**
756      * Construct a manifest from Ant's default manifest file.
757      *
758      * @return the default manifest.
759      *
760      * @throws ArchiverException if there is a problem loading the
761      * default manifest
762      */
763     public static Manifest getDefaultManifest()
764         throws ArchiverException
765     {
766         final Manifest defaultManifest = new Manifest();
767         defaultManifest.getMainAttributes().putValue( "Manifest-Version", "1.0" );
768 
769         String createdBy = "Plexus Archiver";
770 
771         final String plexusArchiverVersion = JdkManifestFactory.getArchiverVersion();
772 
773         if ( plexusArchiverVersion != null )
774         {
775             createdBy += " " + plexusArchiverVersion;
776         }
777 
778         defaultManifest.getMainAttributes().putValue( "Created-By", createdBy );
779 
780         return defaultManifest;
781     }
782 
783     /**
784      * Construct an empty manifest
785      */
786     public Manifest()
787     {
788         setManifestVersion();
789     }
790 
791     private void setManifestVersion()
792     {
793         getMainAttributes().put( Attributes.Name.MANIFEST_VERSION, "1.0" );
794     }
795 
796     /**
797      * Read a manifest file from the given reader
798      *
799      * @param r is the reader from which the Manifest is read
800      *
801      * @throws ManifestException if the manifest is not valid according
802      * to the JAR spec
803      * @throws IOException if the manifest cannot be read from the reader.
804      * @deprecated This constructor does not properly map characters to bytes. Use
805      * {@link #Manifest(InputStream)}. Will be removed in 4.0.
806      */
807     @Deprecated
808     public Manifest( Reader r )
809         throws ManifestException, IOException
810     {
811         super( getInputStream( r ) );
812         setManifestVersion();
813     }
814 
815     public Manifest( InputStream is )
816         throws IOException
817     {
818         super( is );
819         setManifestVersion();
820     }
821 
822     /**
823      * Add a section to the manifest
824      *
825      * @param section the manifest section to be added
826      *
827      * @throws ManifestException if the secti0on is not valid.
828      */
829     public void addConfiguredSection( Section section )
830         throws ManifestException
831     {
832         String sectionName = section.getName();
833         if ( sectionName == null )
834         {
835             throw new ManifestException( "Sections must have a name" );
836         }
837         Attributes attributes = getOrCreateAttributes( sectionName );
838         for ( String s : section.attributes.keySet() )
839         {
840 
841             Attribute attribute = section.getAttribute( s );
842             attributes.putValue( attribute.getName(), attribute.getValue() );
843         }
844     }
845 
846     private Attributes getOrCreateAttributes( String name )
847     {
848         Attributes attributes = getAttributes( name );
849         if ( attributes == null )
850         {
851             attributes = new Attributes();
852             getEntries().put( name, attributes );
853         }
854         return attributes;
855     }
856 
857     /**
858      * Add an attribute to the manifest - it is added to the main section.
859      *
860      * @param attribute the attribute to be added.
861      *
862      * @throws ManifestException if the attribute is not valid.
863      */
864     public void addConfiguredAttribute( Attribute attribute )
865         throws ManifestException
866     {
867         remap( getMainAttributes(), attribute );
868     }
869 
870     /**
871      * Writes the manifest out to a writer.
872      *
873      * @param writer the Writer to which the manifest is written
874      *
875      * @throws IOException if the manifest cannot be written
876      */
877     public void write( Writer writer )
878         throws IOException
879     {
880         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
881         super.write( byteArrayOutputStream );
882         // We know that UTF-8 is the encoding of the JAR file specification
883         writer.write( byteArrayOutputStream.toString( "UTF-8" ) );
884     }
885 
886     /**
887      * Convert the manifest to its string representation
888      *
889      * @return a multiline string with the Manifest as it
890      * appears in a Manifest file.
891      */
892     @Override
893     public String toString()
894     {
895         StringWriter sw = new StringWriter();
896         try
897         {
898             write( sw );
899         }
900         catch ( IOException e )
901         {
902             return null;
903         }
904         return sw.toString();
905     }
906 
907     /**
908      * Get the warnings for this manifest.
909      *
910      * @return an enumeration of warning strings
911      */
912     Enumeration<String> getWarnings()
913     {
914         Vector<String> warnings = new Vector<String>();
915 
916         Enumeration<String> warnEnum = mainSection.getWarnings();
917         while ( warnEnum.hasMoreElements() )
918         {
919             warnings.addElement( warnEnum.nextElement() );
920         }
921 
922         return warnings.elements();
923     }
924 
925     /**
926      * Get the version of the manifest
927      *
928      * @return the manifest's version string
929      */
930     public String getManifestVersion()
931     {
932         /*
933          The version of this manifest
934          */
935         return DEFAULT_MANIFEST_VERSION;
936     }
937 
938     /**
939      * Get the main section of the manifest
940      *
941      * @return the main section of the manifest
942      */
943     public ExistingSection getMainSection()
944     {
945         return new ExistingSection( getMainAttributes(), null );
946     }
947 
948     /**
949      * Get a particular section from the manifest
950      *
951      * @param name the name of the section desired.
952      *
953      * @return the specified section or null if that section
954      * does not exist in the manifest
955      */
956     public ExistingSection getSection( String name )
957     {
958         Attributes attributes = getAttributes( name );
959         if ( attributes != null )
960         {
961             return new ExistingSection( attributes, name );
962         }
963         return null;
964     }
965 
966     @Deprecated
967     private static InputStream getInputStream( Reader r )
968         throws IOException
969     {
970         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
971         int read;
972         while ( ( read = r.read() ) != -1 )
973         {
974             byteArrayOutputStream.write( read );
975         }
976         return new ByteArrayInputStream( byteArrayOutputStream.toByteArray() );
977     }
978 
979     public static String remap( Attributes backingAttributes, Attribute attribute )
980         throws ManifestException
981     {
982         if ( attribute.getKey() == null || attribute.getValue() == null )
983         {
984             throw new ManifestException( "Attributes must have name and value" );
985         }
986 
987         String attributeKey = attribute.getKey();
988         if ( attributeKey.equalsIgnoreCase( ManifestConstants.ATTRIBUTE_CLASSPATH ) )
989         {
990             String classpathAttribute = backingAttributes.getValue( attributeKey );
991 
992             if ( classpathAttribute == null )
993             {
994                 classpathAttribute = attribute.getValue();
995             }
996             else
997             {
998                 classpathAttribute += " " + attribute.getValue();
999             }
1000             backingAttributes.putValue( ManifestConstants.ATTRIBUTE_CLASSPATH, classpathAttribute );
1001         }
1002         else
1003         {
1004             backingAttributes.putValue( attribute.getName(), attribute.getValue() );
1005             if ( attribute.getKey().equalsIgnoreCase( ATTRIBUTE_NAME ) )
1006             {
1007                 return attribute.getValue();
1008             }
1009         }
1010         return null;
1011 
1012     }
1013 
1014 }