JarReorder.java revision 8845:4be14673b9bf
1/* 2 * Copyright (c) 2000, 2010, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26/* 27 * Read an input file which is output from a java -verbose run, 28 * combine with an argument list of files and directories, and 29 * write a list of items to be included in a jar file. 30 */ 31package build.tools.jarreorder; 32 33import java.io.BufferedReader; 34import java.io.File; 35import java.io.FileNotFoundException; 36import java.io.FileReader; 37import java.io.IOException; 38import java.util.Collections; 39import java.util.HashSet; 40import java.io.PrintStream; 41import java.io.FileOutputStream; 42import java.util.ArrayList; 43import java.util.List; 44import java.util.Set; 45 46public class JarReorder { 47 48 // To deal with output 49 private PrintStream out; 50 51 private void usage() { 52 String help; 53 help = 54 "Usage: jar JarReorder [-o <outputfile>] <order_list> <exclude_list> <file> ...\n" 55 + " order_list is a file containing names of files to load\n" 56 + " in order at the end of a jar file unless\n" 57 + " excluded in the exclude list.\n" 58 + " exclude_list is a file containing names of files/directories\n" 59 + " NOT to be included in a jar file.\n" 60 + "\n" 61 + "The order_list or exclude_list may be replaced by a \"-\" if no\n" 62 + "data is to be provided.\n" 63 + "\n" 64 + " The remaining arguments are files or directories to be included\n" 65 + " in a jar file, from which will be excluded those entries which\n" 66 + " appear in the exclude list.\n"; 67 System.err.println(help); 68 } 69 70 71 /* 72 * Create the file list to be included in a jar file, such that the 73 * list will appear in a specific order, and allowing certain 74 * files and directories to be excluded. 75 * 76 * Command path arguments are 77 * - optional -o outputfile 78 * - name of a file containing a set of files to be included in a jar file. 79 * - name of a file containing a set of files (or directories) to be 80 * excluded from the jar file. 81 * - names of files or directories to be searched for files to include 82 * in the jar file. 83 */ 84 public static void main(String[] args) { 85 JarReorder jr = new JarReorder(); 86 jr.run(args); 87 } 88 89 private void run(String args[]) { 90 91 int arglen = args.length; 92 int argpos = 0; 93 94 // Look for "-o outputfilename" option 95 if (arglen > 0) { 96 if (arglen >= 2 && args[0].equals("-o")) { 97 try { 98 out = new PrintStream(new FileOutputStream(args[1])); 99 } catch (FileNotFoundException e) { 100 System.err.println("Error: " + e.getMessage()); 101 e.printStackTrace(System.err); 102 System.exit(1); 103 } 104 argpos += 2; 105 arglen -= 2; 106 } else { 107 System.err.println("Error: Illegal arg count"); 108 System.exit(1); 109 } 110 } else { 111 out = System.out; 112 } 113 114 // Should be 2 or more args left 115 if (arglen <= 2) { 116 usage(); 117 System.exit(1); 118 } 119 120 // Read the ordered set of files to be included in rt.jar. 121 // Read the set of files/directories to be excluded from rt.jar. 122 String classListFile = args[argpos]; 123 String excludeListFile = args[argpos + 1]; 124 argpos += 2; 125 arglen -= 2; 126 127 // Create 2 lists and a set of processed files 128 List<String> orderList = readListFromFile(classListFile, true); 129 List<String> excludeList = readListFromFile(excludeListFile, false); 130 Set<String> processed = new HashSet<String>(); 131 132 // Create set of all files and directories excluded, then expand 133 // that list completely 134 Set<String> excludeSet = new HashSet<String>(excludeList); 135 Set<String> allFilesExcluded = expand(null, excludeSet, processed); 136 137 // Indicate all these have been processed, orderList too, kept to end. 138 processed.addAll(orderList); 139 140 // The remaining arguments are names of files/directories to be included 141 // in the jar file. 142 Set<String> inputSet = new HashSet<String>(); 143 for (int i = 0; i < arglen; ++i) { 144 String name = args[argpos + i]; 145 name = cleanPath(new File(name)); 146 if ( name != null && name.length() > 0 && !inputSet.contains(name) ) { 147 inputSet.add(name); 148 } 149 } 150 151 // Expand file/directory input so we get a complete set (except ordered) 152 // Should be everything not excluded and not in order list. 153 Set<String> allFilesIncluded = expand(null, inputSet, processed); 154 155 // Create simple sorted list so we can add ordered items at end. 156 List<String> allFiles = new ArrayList<String>(allFilesIncluded); 157 Collections.sort(allFiles); 158 159 // Now add the ordered set to the end of the list. 160 // Add in REVERSE ORDER, so that the first element is closest to 161 // the end (and the index). 162 for (int i = orderList.size() - 1; i >= 0; --i) { 163 String s = orderList.get(i); 164 if (allFilesExcluded.contains(s)) { 165 // Disable this warning until 8005688 is fixed 166 // System.err.println("Included order file " + s 167 // + " is also excluded, skipping."); 168 } else if (new File(s).exists()) { 169 allFiles.add(s); 170 } else { 171 System.err.println("Included order file " + s 172 + " missing, skipping."); 173 } 174 } 175 176 // Print final results. 177 for (String str : allFiles) { 178 out.println(str); 179 } 180 out.flush(); 181 out.close(); 182 } 183 184 /* 185 * Read a file containing a list of files and directories into a List. 186 */ 187 private List<String> readListFromFile(String fileName, 188 boolean addClassSuffix) { 189 190 BufferedReader br = null; 191 List<String> list = new ArrayList<String>(); 192 // If you see "-" for the name, just assume nothing was provided. 193 if ("-".equals(fileName)) { 194 return list; 195 } 196 try { 197 br = new BufferedReader(new FileReader(fileName)); 198 // Read the input file a path at a time. # in column 1 is a comment. 199 while (true) { 200 String path = br.readLine(); 201 if (path == null) { 202 break; 203 } 204 // Look for comments 205 path = path.trim(); 206 if (path.length() == 0 207 || path.charAt(0) == '#') { 208 continue; 209 } 210 // Add trailing .class if necessary 211 if (addClassSuffix && !path.endsWith(".class")) { 212 path = path + ".class"; 213 } 214 // Normalize the path 215 path = cleanPath(new File(path)); 216 // Add to list 217 if (path != null && path.length() > 0 && !list.contains(path)) { 218 list.add(path); 219 } 220 } 221 br.close(); 222 } catch (FileNotFoundException e) { 223 System.err.println("Can't find file \"" + fileName + "\"."); 224 System.exit(1); 225 } catch (IOException e) { 226 e.printStackTrace(); 227 System.exit(2); 228 } 229 return list; 230 } 231 232 /* 233 * Expands inputSet (files or dirs) into full set of all files that 234 * can be found by recursively descending directories. 235 * @param dir root directory 236 * @param inputSet set of files or dirs to look into 237 * @param processed files or dirs already processed 238 * @return set of files 239 */ 240 private Set<String> expand(File dir, 241 Set<String> inputSet, 242 Set<String> processed) { 243 Set<String> includedFiles = new HashSet<String>(); 244 if (inputSet.isEmpty()) { 245 return includedFiles; 246 } 247 for (String name : inputSet) { 248 // Depending on start location 249 File f = (dir == null) ? new File(name) 250 : new File(dir, name); 251 // Normalized path to use 252 String path = cleanPath(f); 253 if (path != null && path.length() > 0 254 && !processed.contains(path)) { 255 if (f.isFile()) { 256 // Not in the excludeList, add it to both lists 257 includedFiles.add(path); 258 processed.add(path); 259 } else if (f.isDirectory()) { 260 // Add the directory entries 261 String[] dirList = f.list(); 262 Set<String> dirInputSet = new HashSet<String>(); 263 for (String x : dirList) { 264 dirInputSet.add(x); 265 } 266 // Process all entries in this directory 267 Set<String> subList = expand(f, dirInputSet, processed); 268 includedFiles.addAll(subList); 269 processed.add(path); 270 } 271 } 272 } 273 return includedFiles; 274 } 275 276 private String cleanPath(File f) { 277 String path = f.getPath(); 278 if (f.isFile()) { 279 path = cleanFilePath(path); 280 } else if (f.isDirectory()) { 281 path = cleanDirPath(path); 282 } else { 283 System.err.println("WARNING: Path does not exist as file or directory: " + path); 284 path = null; 285 } 286 return path; 287 } 288 289 private String cleanFilePath(String path) { 290 // Remove leading and trailing whitespace 291 path = path.trim(); 292 // Make all / and \ chars one 293 if (File.separatorChar == '/') { 294 path = path.replace('\\', '/'); 295 } else { 296 path = path.replace('/', '\\'); 297 } 298 // Remove leading ./ 299 if (path.startsWith("." + File.separator)) { 300 path = path.substring(2); 301 } 302 return path; 303 } 304 305 private String cleanDirPath(String path) { 306 path = cleanFilePath(path); 307 // Make sure it ends with a file separator 308 if (!path.endsWith(File.separator)) { 309 path = path + File.separator; 310 } 311 return path; 312 } 313 314} 315