blob: 79997891312727c35996517c481d6e1da1fea880 [file] [log] [blame]
Guillaume Nodet457f4302014-06-14 10:39:55 +00001/*
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 */
19package org.apache.felix.bundleplugin;
20
21import java.io.IOException;
22import java.io.OutputStream;
23import java.util.Arrays;
24import java.util.HashSet;
25import java.util.Map;
26import java.util.Set;
27import java.util.TreeMap;
28import java.util.TreeSet;
29import java.util.jar.Attributes;
30import java.util.jar.Manifest;
31
32import org.apache.felix.utils.manifest.Parser;
33import org.osgi.framework.Constants;
34
35public class ManifestWriter {
36
37 /**
38 * Unfortunately we have to write our own manifest :-( because of a stupid
39 * bug in the manifest code. It tries to handle UTF-8 but the way it does it
40 * it makes the bytes platform dependent. So the following code outputs the
41 * manifest. A Manifest consists of
42 *
43 * <pre>
44 * 'Manifest-Version: 1.0\r\n'
45 * main-attributes *
46 * \r\n
47 * name-section
48 *
49 * main-attributes ::= attributes
50 * attributes ::= key ': ' value '\r\n'
51 * name-section ::= 'Name: ' name '\r\n' attributes
52 * </pre>
53 *
54 * Lines in the manifest should not exceed 72 bytes (! this is where the
55 * manifest screwed up as well when 16 bit unicodes were used).
56 * <p>
57 * As a bonus, we can now sort the manifest!
58 */
59 static byte[] CONTINUE = new byte[] {
60 '\r', '\n', ' '
61 };
62
63 static Set<String> NICE_HEADERS = new HashSet<String>(
64 Arrays.asList(
65 Constants.IMPORT_PACKAGE,
66 Constants.DYNAMICIMPORT_PACKAGE,
67 Constants.IMPORT_SERVICE,
68 Constants.REQUIRE_CAPABILITY,
69 Constants.EXPORT_PACKAGE,
70 Constants.EXPORT_SERVICE,
71 Constants.PROVIDE_CAPABILITY
72 )
73 );
74
75 /**
76 * Main function to output a manifest properly in UTF-8.
77 *
78 * @param manifest
79 * The manifest to output
80 * @param out
81 * The output stream
82 * @throws IOException
83 * when something fails
84 */
85 public static void outputManifest(Manifest manifest, OutputStream out, boolean nice) throws IOException {
86 writeEntry(out, "Manifest-Version", "1.0", nice);
87 attributes(manifest.getMainAttributes(), out, nice);
88
89 TreeSet<String> keys = new TreeSet<String>();
90 for (Object o : manifest.getEntries().keySet())
91 keys.add(o.toString());
92
93 for (String key : keys) {
94 write(out, 0, "\r\n");
95 writeEntry(out, "Name", key, nice);
96 attributes(manifest.getAttributes(key), out, nice);
97 }
98 out.flush();
99 }
100
101 /**
102 * Write out an entry, handling proper unicode and line length constraints
103 */
104 private static void writeEntry(OutputStream out, String name, String value, boolean nice) throws IOException {
105 if (nice && NICE_HEADERS.contains(name)) {
106 int n = write(out, 0, name + ": ");
107 String[] parts = Parser.parseDelimitedString(value, ",");
108 if (parts.length > 1) {
109 write(out, 0, "\r\n ");
110 n = 1;
111 }
112 for (int i = 0; i < parts.length; i++) {
113 if (i < parts.length - 1) {
114 write(out, n, parts[i] + ",");
115 write(out, 0, "\r\n ");
116 } else {
117 write(out, n, parts[i]);
118 write(out, 0, "\r\n");
119 }
120 n = 1;
121 }
122 } else {
123 int n = write(out, 0, name + ": ");
124 write(out, n, value);
125 write(out, 0, "\r\n");
126 }
127 }
128
129 /**
130 * Convert a string to bytes with UTF8 and then output in max 72 bytes
131 *
132 * @param out
133 * the output string
134 * @param i
135 * the current width
136 * @param s
137 * the string to output
138 * @return the new width
139 * @throws IOException
140 * when something fails
141 */
142 private static int write(OutputStream out, int i, String s) throws IOException {
143 byte[] bytes = s.getBytes("UTF8");
144 return write(out, i, bytes);
145 }
146
147 /**
148 * Write the bytes but ensure that the line length does not exceed 72
149 * characters. If it is more than 70 characters, we just put a cr/lf +
150 * space.
151 *
152 * @param out
153 * The output stream
154 * @param width
155 * The nr of characters output in a line before this method
156 * started
157 * @param bytes
158 * the bytes to output
159 * @return the nr of characters in the last line
160 * @throws IOException
161 * if something fails
162 */
163 private static int write(OutputStream out, int width, byte[] bytes) throws IOException {
164 int w = width;
165 for (int i = 0; i < bytes.length; i++) {
166 if (w >= 72) { // we need to add the \n\r!
167 out.write(CONTINUE);
168 w = 1;
169 }
170 out.write(bytes[i]);
171 w++;
172 }
173 return w;
174 }
175
176 /**
177 * Output an Attributes map. We will sort this map before outputing.
178 *
179 * @param value
180 * the attrbutes
181 * @param out
182 * the output stream
183 * @throws IOException
184 * when something fails
185 */
186 private static void attributes(Attributes value, OutputStream out, boolean nice) throws IOException {
187 TreeMap<String,String> map = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
188 for (Map.Entry<Object,Object> entry : value.entrySet()) {
189 map.put(entry.getKey().toString(), entry.getValue().toString());
190 }
191
192 map.remove("Manifest-Version"); // get rid of
193 // manifest
194 // version
195 for (Map.Entry<String,String> entry : map.entrySet()) {
196 writeEntry(out, entry.getKey(), entry.getValue(), nice);
197 }
198 }
Guillaume Nodet457f4302014-06-14 10:39:55 +0000199}