View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   * 
19   * Author: Siamak Haschemi
20   * Contact: haschemi@informatik.hu-berlin.de
21   */
22  package net.sourceforge.osgi.deployment.maven.manifest;
23  
24  import java.io.DataOutputStream;
25  import java.io.IOException;
26  import java.io.OutputStream;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.List;
30  import java.util.regex.Pattern;
31  
32  /**
33   * The {@link OrderedManifest} stores manifest entries in a defined order.
34   * 
35   * @author Siamak Haschemi, haschemi@informatik.hu-berlin.de
36   */
37  public class OrderedManifest {
38    /**
39     * <code>Manifest-Version</code> manifest attribute. This attribute indicates the version number of the manifest standard to which a JAR file's manifest
40     * conforms.
41     * 
42     * @see <a href="http://java.sun.com/j2se/1.4.2/docs/guide/jar/jar.html#JAR%20Manifest"> Manifest and Signature Specification</a>
43     */
44    public static final String MANIFEST_VERSION = "Manifest-Version";
45    
46    /**
47     * A pattern to verify the header of a manifest entry.
48     */
49    public static final Pattern HEADER_PATTERN = Pattern.compile("[A-Za-z0-9][-a-zA-Z0-9_]+");
50  
51    /**
52     * <code>Content-Type</code> manifest attribute.
53     */
54    protected static final String CONTENT_TYPE = "Content-Type";
55  
56    /**
57     * An empty line on the manifest.
58     */
59    protected static final ManifestEntry EMPTY_LINE = new ManifestEntry("", "");
60  
61    private static final int MAXIMUM_MANIFEST_LINE_WIDTH = 72;
62    private final List< ManifestEntry > m_entries = Collections.synchronizedList(new ArrayList< ManifestEntry >());
63  
64    /**
65     * This class represents an entry in the {@link OrderedManifest}.
66     * 
67     * @author Siamak Haschemi, haschemi@informatik.hu-berlin.de
68     */
69    private static class ManifestEntry {
70      private final String m_name;
71      private final String m_value;
72  
73      public ManifestEntry(final String p_name, final String p_value) {
74        if (p_name == null || p_value == null) {
75          throw new IllegalArgumentException("NULL is not allowed as parameter");
76        }
77  
78        final boolean nameEmpty = p_name.trim().length() == 0;
79        final boolean nameNotEmpty = p_name.trim().length() > 0;
80        final boolean valueNotEmpty = p_value.trim().length() > 0;
81  
82        if (nameEmpty && valueNotEmpty) {
83          throw new IllegalArgumentException("name param was empty, so value param has also to be.");
84        }
85  
86        if (nameNotEmpty && !HEADER_PATTERN.matcher(p_name).matches()) {
87          throw new IllegalArgumentException("Header name not matches OSGi specification.");
88        }
89        m_name = p_name;
90        m_value = p_value;
91      }
92  
93      public boolean equals(final Object p_obj) {
94        if (p_obj == null) {
95          return false;
96        }
97  
98        if (!this.getClass().equals(p_obj.getClass())) {
99          return false;
100       }
101 
102       final ManifestEntry obj = (ManifestEntry) p_obj;
103       if (m_name.equals(obj.m_name) && m_value.equals(obj.m_value)) {
104         return true;
105       }
106       return false;
107     }
108 
109     public int hashCode() {
110       return m_name.hashCode() + m_value.hashCode();
111     }
112   };
113 
114   /**
115    * Add a {@link ManifestEntry} to the {@link OrderedManifest}.
116    * 
117    * @param p_manifestEntry
118    *          the {@link ManifestEntry} to add
119    */
120   public final void add(final ManifestEntry p_manifestEntry) {
121     m_entries.add(p_manifestEntry);
122   }
123 
124   /**
125    * Add a key/value pair to the {@link OrderedManifest}.
126    * 
127    * @param p_header
128    *          The header to add
129    * @param p_value
130    *          the value for the header to add
131    */
132   public final void add(final String p_header, final String p_value) {
133     m_entries.add(new ManifestEntry(p_header, p_value));
134   }
135 
136   /**
137    * Write the current contents of the {@link OrderedManifest} into a {@link OutputStream}.
138    * 
139    * @param p_out
140    *          the {@link OutputStream} to write into
141    * @throws IOException
142    *           If the writing of the {@link OrderedManifest} fails.
143    */
144   public final void write(final OutputStream p_out) throws IOException {
145     final DataOutputStream dos = new DataOutputStream(p_out);
146 
147     for (final ManifestEntry manifestEntry : m_entries) {
148       if (manifestEntry.equals(EMPTY_LINE)) {
149         dos.writeBytes("\r\n");
150         continue;
151       }
152 
153       final StringBuffer buffer = new StringBuffer(manifestEntry.m_name);
154       buffer.append(": ");
155 
156       String value = manifestEntry.m_value;
157       final byte[] vb = value.getBytes("UTF8");
158       // value = new String(vb, 0, 0, vb.length);
159       value = new String(vb, "UTF8");
160       buffer.append(value);
161 
162       buffer.append("\r\n");
163       OrderedManifest.make72Safe(buffer);
164       dos.writeBytes(buffer.toString());
165     }
166     dos.writeBytes("\r\n");
167     dos.flush();
168   }
169 
170   /**
171    * <p>
172    * From the manifest specification:
173    * </p>
174    * <p>
175    * <i>"No line may be longer than 72 bytes (not characters), in its UTF8-encoded form. If a value would make the initial line longer than this, it should be
176    * continued on extra lines (each starting with a single SPACE). "</i> <a
177    * href="http://java.sun.com/j2se/1.4.2/docs/guide/jar/jar.html">http://java.sun.com/j2se/1.4.2/docs/guide/jar/jar.html</a>
178    * </p>
179    * Adds line breaks to enforce a maximum 72 bytes per line.
180    * 
181    * @param p_line
182    *          the line to make safe
183    */
184   static void make72Safe(final StringBuffer p_line) {
185     final String lineBreak = "\r\n";
186     final String space = " ";
187     final String lineBreakAndSpace = lineBreak + space;
188 
189     int length = p_line.length();
190     if (length > MAXIMUM_MANIFEST_LINE_WIDTH) {
191       int index = MAXIMUM_MANIFEST_LINE_WIDTH - lineBreak.length();
192       while (index < length - 2) {
193         p_line.insert(index, lineBreakAndSpace);
194         index += MAXIMUM_MANIFEST_LINE_WIDTH;
195         length += lineBreakAndSpace.length();
196       }
197     }
198   }
199 }