1 /* 2 * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 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 import java.io.File; 25 import java.io.IOException; 26 import java.io.OutputStream; 27 import java.nio.file.Files; 28 import java.nio.file.Path; 29 import java.nio.file.Paths; 30 import java.nio.file.StandardCopyOption; 31 import java.util.ArrayList; 32 import java.util.Enumeration; 33 import java.util.List; 34 import java.util.Set; 35 import java.util.jar.JarEntry; 36 import java.util.jar.JarFile; 37 import java.util.jar.JarOutputStream; 38 import java.util.jar.Manifest; 39 import java.util.stream.Collectors; 40 import java.util.stream.Stream; 41 42 /** 43 * This class consists exclusively of static utility methods that are useful 44 * for creating and manipulating JAR files. 45 */ 46 47 public final class JarUtils { 48 private JarUtils() { } 49 50 /** 51 * Creates a JAR file. 52 * 53 * Equivalent to {@code jar cfm <jarfile> <manifest> -C <dir> file...} 54 * 55 * The input files are resolved against the given directory. Any input 56 * files that are directories are processed recursively. 57 */ 58 public static void createJarFile(Path jarfile, Manifest man, Path dir, Path... file) 59 throws IOException 60 { 61 // create the target directory 62 Path parent = jarfile.getParent(); 63 if (parent != null) 64 Files.createDirectories(parent); 65 66 List<Path> entries = new ArrayList<>(); 67 for (Path entry : file) { 68 Files.find(dir.resolve(entry), Integer.MAX_VALUE, 69 (p, attrs) -> attrs.isRegularFile()) 70 .map(e -> dir.relativize(e)) 71 .forEach(entries::add); 72 } 73 74 try (OutputStream out = Files.newOutputStream(jarfile); 75 JarOutputStream jos = new JarOutputStream(out)) 76 { 77 if (man != null) { 78 JarEntry je = new JarEntry(JarFile.MANIFEST_NAME); 79 jos.putNextEntry(je); 80 man.write(jos); 81 jos.closeEntry(); 82 } 83 84 for (Path entry : entries) { 85 String name = toJarEntryName(entry); 86 jos.putNextEntry(new JarEntry(name)); 87 Files.copy(dir.resolve(entry), jos); 88 jos.closeEntry(); 89 } 90 } 91 } 92 93 /** 94 * Creates a JAR file. 95 * 96 * Equivalent to {@code jar cf <jarfile> -C <dir> file...} 97 * 98 * The input files are resolved against the given directory. Any input 99 * files that are directories are processed recursively. 100 */ 101 public static void createJarFile(Path jarfile, Path dir, Path... file) 102 throws IOException 103 { 104 createJarFile(jarfile, null, dir, file); 105 } 106 107 /** 108 * Creates a JAR file. 109 * 110 * Equivalent to {@code jar cf <jarfile> -C <dir> file...} 111 * 112 * The input files are resolved against the given directory. Any input 113 * files that are directories are processed recursively. 114 */ 115 public static void createJarFile(Path jarfile, Path dir, String... input) 116 throws IOException 117 { 118 Path[] paths = Stream.of(input).map(Paths::get).toArray(Path[]::new); 119 createJarFile(jarfile, dir, paths); 120 } 121 122 /** 123 * Creates a JAR file from the contents of a directory. 124 * 125 * Equivalent to {@code jar cf <jarfile> -C <dir> .} 126 */ 127 public static void createJarFile(Path jarfile, Path dir) throws IOException { 128 createJarFile(jarfile, dir, Paths.get(".")); 129 } 130 131 /** 132 * Update a JAR file. 133 * 134 * Equivalent to {@code jar uf <jarfile> -C <dir> file...} 135 * 136 * The input files are resolved against the given directory. Any input 137 * files that are directories are processed recursively. 138 */ 139 public static void updateJarFile(Path jarfile, Path dir, Path... file) 140 throws IOException 141 { 142 List<Path> entries = new ArrayList<>(); 143 for (Path entry : file) { 144 Files.find(dir.resolve(entry), Integer.MAX_VALUE, 145 (p, attrs) -> attrs.isRegularFile()) 146 .map(e -> dir.relativize(e)) 147 .forEach(entries::add); 148 } 149 150 Set<String> names = entries.stream() 151 .map(JarUtils::toJarEntryName) 152 .collect(Collectors.toSet()); 153 154 Path tmpfile = Files.createTempFile("jar", "jar"); 155 156 try (OutputStream out = Files.newOutputStream(tmpfile); 157 JarOutputStream jos = new JarOutputStream(out)) 158 { 159 // copy existing entries from the original JAR file 160 try (JarFile jf = new JarFile(jarfile.toString())) { 161 Enumeration<JarEntry> jentries = jf.entries(); 162 while (jentries.hasMoreElements()) { 163 JarEntry jentry = jentries.nextElement(); 164 if (!names.contains(jentry.getName())) { 165 jos.putNextEntry(jentry); 166 jf.getInputStream(jentry).transferTo(jos); 167 } 168 } 169 } 170 171 // add the new entries 172 for (Path entry : entries) { 173 String name = toJarEntryName(entry); 174 jos.putNextEntry(new JarEntry(name)); 175 Files.copy(dir.resolve(entry), jos); 176 } 177 } 178 179 // replace the original JAR file 180 Files.move(tmpfile, jarfile, StandardCopyOption.REPLACE_EXISTING); 181 } 182 183 /** 184 * Update a JAR file. 185 * 186 * Equivalent to {@code jar uf <jarfile> -C <dir> .} 187 */ 188 public static void updateJarFile(Path jarfile, Path dir) throws IOException { 189 updateJarFile(jarfile, dir, Paths.get(".")); 190 } 191 192 193 /** 194 * Map a file path to the equivalent name in a JAR file 195 */ 196 private static String toJarEntryName(Path file) { 197 Path normalized = file.normalize(); 198 return normalized.subpath(0, normalized.getNameCount()) // drop root 199 .toString() 200 .replace(File.separatorChar, '/'); 201 } 202 }