6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 package jdk.test.lib.util;
25
26 import java.io.FileInputStream;
27 import java.io.FileNotFoundException;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.nio.file.Files;
31 import java.nio.file.InvalidPathException;
32 import java.nio.file.Path;
33 import java.nio.file.Paths;
34 import java.util.Enumeration;
35 import java.util.HashMap;
36 import java.util.Map;
37 import java.util.jar.JarEntry;
38 import java.util.jar.JarFile;
39 import java.util.jar.JarOutputStream;
40 import java.util.jar.Manifest;
41
42 /**
43 * Common library for various test jar file utility functions.
44 */
45 public final class JarUtils {
46
47 /**
48 * Create jar file with specified files. If a specified file does not exist,
49 * a new jar entry will be created with the file name itself as the content.
50 */
51 public static void createJar(String dest, String... files)
52 throws IOException {
53 try (JarOutputStream jos = new JarOutputStream(
54 new FileOutputStream(dest), new Manifest())) {
55 for (String file : files) {
56 System.out.println(String.format("Adding %s to %s",
57 file, dest));
58
59 // add an archive entry, and write a file
60 jos.putNextEntry(new JarEntry(file));
61 try (FileInputStream fis = new FileInputStream(file)) {
62 fis.transferTo(jos);
63 } catch (FileNotFoundException e) {
64 jos.write(file.getBytes());
65 }
66 }
67 }
68 System.out.println();
69 }
70
71 /**
72 * Add or remove specified files to existing jar file. If a specified file
73 * to be updated or added does not exist, the jar entry will be created
74 * with the file name itself as the content.
75 *
76 * @param src the original jar file name
77 * @param dest the new jar file name
78 * @param files the files to update. The list is broken into 2 groups
79 * by a "-" string. The files before in the 1st group will
80 * be either updated or added. The files in the 2nd group
81 * will be removed. If no "-" exists, all files belong to
82 * the 1st group.
83 */
84 public static void updateJar(String src, String dest, String... files)
85 throws IOException {
86 Map<String,Object> changes = new HashMap<>();
87 boolean update = true;
88 for (String file : files) {
89 if (file.equals("-")) {
90 update = false;
91 } else if (update) {
92 try {
93 Path p = Paths.get(file);
94 if (Files.exists(p)) {
95 changes.put(file, p);
96 } else {
97 changes.put(file, file);
98 }
99 } catch (InvalidPathException e) {
100 // Fallback if file not a valid Path.
101 changes.put(file, file);
102 }
103 } else {
104 changes.put(file, Boolean.FALSE);
105 }
106 }
107 updateJar(src, dest, changes);
108 }
109
110 /**
111 * Update content of a jar file.
112 *
113 * @param src the original jar file name
114 * @param dest the new jar file name
115 * @param changes a map of changes, key is jar entry name, value is content.
116 * Value can be Path, byte[] or String. If key exists in
117 * src but value is Boolean FALSE. The entry is removed.
118 * Existing entries in src not a key is unmodified.
119 * @throws IOException
120 */
121 public static void updateJar(String src, String dest,
122 Map<String,Object> changes)
123 throws IOException {
124
125 // What if input changes is immutable?
126 changes = new HashMap<>(changes);
127
128 System.out.printf("Creating %s from %s...\n", dest, src);
129 try (JarOutputStream jos = new JarOutputStream(
130 new FileOutputStream(dest))) {
131
132 try (JarFile srcJarFile = new JarFile(src)) {
133 Enumeration<JarEntry> entries = srcJarFile.entries();
134 while (entries.hasMoreElements()) {
135 JarEntry entry = entries.nextElement();
136 String name = entry.getName();
137 if (changes.containsKey(name)) {
138 System.out.println(String.format("- Update %s", name));
139 updateEntry(jos, name, changes.get(name));
140 changes.remove(name);
155
156 private static void updateEntry(JarOutputStream jos, String name, Object content)
157 throws IOException {
158 if (content instanceof Boolean) {
159 if (((Boolean) content).booleanValue()) {
160 throw new RuntimeException("Boolean value must be FALSE");
161 }
162 } else {
163 jos.putNextEntry(new JarEntry(name));
164 if (content instanceof Path) {
165 Files.newInputStream((Path) content).transferTo(jos);
166 } else if (content instanceof byte[]) {
167 jos.write((byte[]) content);
168 } else if (content instanceof String) {
169 jos.write(((String) content).getBytes());
170 } else {
171 throw new RuntimeException("Unknown type " + content.getClass());
172 }
173 }
174 }
175 }
|
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 package jdk.test.lib.util;
25
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.FileNotFoundException;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.OutputStream;
32 import java.nio.file.Files;
33 import java.nio.file.InvalidPathException;
34 import java.nio.file.Path;
35 import java.nio.file.Paths;
36 import java.nio.file.StandardCopyOption;
37 import java.util.ArrayList;
38 import java.util.Enumeration;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 import java.util.jar.JarEntry;
44 import java.util.jar.JarFile;
45 import java.util.jar.JarOutputStream;
46 import java.util.jar.Manifest;
47 import java.util.stream.Collectors;
48 import java.util.stream.Stream;
49
50 /**
51 * This class consists exclusively of static utility methods that are useful
52 * for creating and manipulating JAR files.
53 */
54 public final class JarUtils {
55 private JarUtils() { }
56
57 /**
58 * Creates a JAR file.
59 *
60 * Equivalent to {@code jar cfm <jarfile> <manifest> -C <dir> file...}
61 *
62 * The input files are resolved against the given directory. Any input
63 * files that are directories are processed recursively.
64 */
65 public static void createJarFile(Path jarfile, Manifest man, Path dir, Path... files)
66 throws IOException
67 {
68 // create the target directory
69 Path parent = jarfile.getParent();
70 if (parent != null) {
71 Files.createDirectories(parent);
72 }
73
74 List<Path> entries = findAllRegularFiles(dir, files);
75
76 try (OutputStream out = Files.newOutputStream(jarfile);
77 JarOutputStream jos = new JarOutputStream(out)) {
78 if (man != null) {
79 JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);
80 jos.putNextEntry(je);
81 man.write(jos);
82 jos.closeEntry();
83 }
84
85 for (Path entry : entries) {
86 String name = toJarEntryName(entry);
87 jos.putNextEntry(new JarEntry(name));
88 Files.copy(dir.resolve(entry), jos);
89 jos.closeEntry();
90 }
91 }
92 }
93
94 /**
95 * Creates a JAR file.
96 *
97 * Equivalent to {@code jar cf <jarfile> -C <dir> file...}
98 *
99 * The input files are resolved against the given directory. Any input
100 * files that are directories are processed recursively.
101 */
102 public static void createJarFile(Path jarfile, Path dir, Path... files)
103 throws IOException
104 {
105 createJarFile(jarfile, null, dir, files);
106 }
107
108 /**
109 * Creates a JAR file from the contents of a directory.
110 *
111 * Equivalent to {@code jar cf <jarfile> -C <dir> .}
112 */
113 public static void createJarFile(Path jarfile, Path dir) throws IOException {
114 createJarFile(jarfile, dir, Paths.get("."));
115 }
116
117
118 /**
119 * Creates a JAR file.
120 *
121 * Equivalent to {@code jar cf <jarfile> -C <dir> file...}
122 *
123 * The input files are resolved against the given directory. Any input
124 * files that are directories are processed recursively.
125 */
126 public static void createJarFile(Path jarfile, Path dir, String... input)
127 throws IOException
128 {
129 Path[] paths = Stream.of(input).map(Paths::get).toArray(Path[]::new);
130 createJarFile(jarfile, dir, paths);
131 }
132
133 /**
134 * Updates a JAR file.
135 *
136 * Equivalent to {@code jar uf <jarfile> -C <dir> file...}
137 *
138 * The input files are resolved against the given directory. Any input
139 * files that are directories are processed recursively.
140 */
141 public static void updateJarFile(Path jarfile, Path dir, Path... files)
142 throws IOException
143 {
144 List<Path> entries = findAllRegularFiles(dir, files);
145
146 Set<String> names = entries.stream()
147 .map(JarUtils::toJarEntryName)
148 .collect(Collectors.toSet());
149
150 Path tmpfile = Files.createTempFile("jar", "jar");
151
152 try (OutputStream out = Files.newOutputStream(tmpfile);
153 JarOutputStream jos = new JarOutputStream(out)) {
154 // copy existing entries from the original JAR file
155 try (JarFile jf = new JarFile(jarfile.toString())) {
156 Enumeration<JarEntry> jentries = jf.entries();
157 while (jentries.hasMoreElements()) {
158 JarEntry jentry = jentries.nextElement();
159 if (!names.contains(jentry.getName())) {
160 jos.putNextEntry(jentry);
161 jf.getInputStream(jentry).transferTo(jos);
162 }
163 }
164 }
165
166 // add the new entries
167 for (Path entry : entries) {
168 String name = toJarEntryName(entry);
169 jos.putNextEntry(new JarEntry(name));
170 Files.copy(dir.resolve(entry), jos);
171 }
172 }
173
174 // replace the original JAR file
175 Files.move(tmpfile, jarfile, StandardCopyOption.REPLACE_EXISTING);
176 }
177
178 /**
179 * Updates a JAR file.
180 *
181 * Equivalent to {@code jar uf <jarfile> -C <dir> .}
182 */
183 public static void updateJarFile(Path jarfile, Path dir) throws IOException {
184 updateJarFile(jarfile, dir, Paths.get("."));
185 }
186
187
188 /**
189 * Create jar file with specified files. If a specified file does not exist,
190 * a new jar entry will be created with the file name itself as the content.
191 */
192 @Deprecated
193 public static void createJar(String dest, String... files)
194 throws IOException {
195 try (JarOutputStream jos = new JarOutputStream(
196 new FileOutputStream(dest), new Manifest())) {
197 for (String file : files) {
198 System.out.println(String.format("Adding %s to %s",
199 file, dest));
200
201 // add an archive entry, and write a file
202 jos.putNextEntry(new JarEntry(file));
203 try (FileInputStream fis = new FileInputStream(file)) {
204 fis.transferTo(jos);
205 } catch (FileNotFoundException e) {
206 jos.write(file.getBytes());
207 }
208 }
209 }
210 System.out.println();
211 }
212
213 /**
214 * Add or remove specified files to existing jar file. If a specified file
215 * to be updated or added does not exist, the jar entry will be created
216 * with the file name itself as the content.
217 *
218 * @param src the original jar file name
219 * @param dest the new jar file name
220 * @param files the files to update. The list is broken into 2 groups
221 * by a "-" string. The files before in the 1st group will
222 * be either updated or added. The files in the 2nd group
223 * will be removed. If no "-" exists, all files belong to
224 * the 1st group.
225 */
226 @Deprecated
227 public static void updateJar(String src, String dest, String... files)
228 throws IOException {
229 Map<String,Object> changes = new HashMap<>();
230 boolean update = true;
231 for (String file : files) {
232 if (file.equals("-")) {
233 update = false;
234 } else if (update) {
235 try {
236 Path p = Paths.get(file);
237 if (Files.exists(p)) {
238 changes.put(file, p);
239 } else {
240 changes.put(file, file);
241 }
242 } catch (InvalidPathException e) {
243 // Fallback if file not a valid Path.
244 changes.put(file, file);
245 }
246 } else {
247 changes.put(file, Boolean.FALSE);
248 }
249 }
250 updateJar(src, dest, changes);
251 }
252
253 /**
254 * Update content of a jar file.
255 *
256 * @param src the original jar file name
257 * @param dest the new jar file name
258 * @param changes a map of changes, key is jar entry name, value is content.
259 * Value can be Path, byte[] or String. If key exists in
260 * src but value is Boolean FALSE. The entry is removed.
261 * Existing entries in src not a key is unmodified.
262 * @throws IOException
263 */
264 @Deprecated
265 public static void updateJar(String src, String dest,
266 Map<String,Object> changes)
267 throws IOException {
268
269 // What if input changes is immutable?
270 changes = new HashMap<>(changes);
271
272 System.out.printf("Creating %s from %s...\n", dest, src);
273 try (JarOutputStream jos = new JarOutputStream(
274 new FileOutputStream(dest))) {
275
276 try (JarFile srcJarFile = new JarFile(src)) {
277 Enumeration<JarEntry> entries = srcJarFile.entries();
278 while (entries.hasMoreElements()) {
279 JarEntry entry = entries.nextElement();
280 String name = entry.getName();
281 if (changes.containsKey(name)) {
282 System.out.println(String.format("- Update %s", name));
283 updateEntry(jos, name, changes.get(name));
284 changes.remove(name);
299
300 private static void updateEntry(JarOutputStream jos, String name, Object content)
301 throws IOException {
302 if (content instanceof Boolean) {
303 if (((Boolean) content).booleanValue()) {
304 throw new RuntimeException("Boolean value must be FALSE");
305 }
306 } else {
307 jos.putNextEntry(new JarEntry(name));
308 if (content instanceof Path) {
309 Files.newInputStream((Path) content).transferTo(jos);
310 } else if (content instanceof byte[]) {
311 jos.write((byte[]) content);
312 } else if (content instanceof String) {
313 jos.write(((String) content).getBytes());
314 } else {
315 throw new RuntimeException("Unknown type " + content.getClass());
316 }
317 }
318 }
319
320 /**
321 * Maps a file path to the equivalent name in a JAR file
322 */
323 private static String toJarEntryName(Path file) {
324 Path normalized = file.normalize();
325 return normalized.subpath(0, normalized.getNameCount()) // drop root
326 .toString()
327 .replace(File.separatorChar, '/');
328 }
329
330 private static List<Path> findAllRegularFiles(Path dir, Path[] files) throws IOException {
331 List<Path> entries = new ArrayList<>();
332 for (Path file : files) {
333 try (Stream<Path> stream = Files.find(dir.resolve(file), Integer.MAX_VALUE,
334 (p, attrs) -> attrs.isRegularFile())) {
335 stream.map(dir::relativize)
336 .forEach(entries::add);
337 }
338 }
339 return entries;
340 }
341 }
|