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 */ 31 package build.tools.jarreorder; 32 33 import java.io.BufferedReader; 34 import java.io.File; 35 import java.io.FileNotFoundException; 36 import java.io.FileReader; 37 import java.io.IOException; 38 import java.util.Collections; 39 import java.util.HashSet; 40 import java.io.PrintStream; 41 import java.io.FileOutputStream; 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.Set; 45 46 public 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 System.err.println("Included order file " + s 166 + " is also excluded, skipping."); 167 } else if (new File(s).exists()) { 168 allFiles.add(s); 169 } else { 170 System.err.println("Included order file " + s 171 + " missing, skipping."); 172 } 173 } 174 175 // Print final results. 176 for (String str : allFiles) { 177 out.println(str); 178 } 179 out.flush(); 180 out.close(); 181 } 182 183 /* 184 * Read a file containing a list of files and directories into a List. 185 */ 186 private List<String> readListFromFile(String fileName, 187 boolean addClassSuffix) { 188 189 BufferedReader br = null; 190 List<String> list = new ArrayList<String>(); 191 // If you see "-" for the name, just assume nothing was provided. 192 if ("-".equals(fileName)) { 193 return list; 194 } 195 try { 196 br = new BufferedReader(new FileReader(fileName)); 197 // Read the input file a path at a time. # in column 1 is a comment. 198 while (true) { 199 String path = br.readLine(); 200 if (path == null) { 201 break; 202 } 203 // Look for comments 204 path = path.trim(); 205 if (path.length() == 0 206 || path.charAt(0) == '#') { 207 continue; 208 } 209 // Add trailing .class if necessary 210 if (addClassSuffix && !path.endsWith(".class")) { 211 path = path + ".class"; 212 } 213 // Normalize the path 214 path = cleanPath(new File(path)); 215 // Add to list 216 if (path != null && path.length() > 0 && !list.contains(path)) { 217 list.add(path); 218 } 219 } 220 br.close(); 221 } catch (FileNotFoundException e) { 222 System.err.println("Can't find file \"" + fileName + "\"."); 223 System.exit(1); 224 } catch (IOException e) { 225 e.printStackTrace(); 226 System.exit(2); 227 } 228 return list; 229 } 230 231 /* 232 * Expands inputSet (files or dirs) into full set of all files that 233 * can be found by recursively descending directories. 234 * @param dir root directory 235 * @param inputSet set of files or dirs to look into 236 * @param processed files or dirs already processed 237 * @return set of files 238 */ 239 private Set<String> expand(File dir, 240 Set<String> inputSet, 241 Set<String> processed) { 242 Set<String> includedFiles = new HashSet<String>(); 243 if (inputSet.isEmpty()) { 244 return includedFiles; 245 } 246 for (String name : inputSet) { 247 // Depending on start location 248 File f = (dir == null) ? new File(name) 249 : new File(dir, name); 250 // Normalized path to use 251 String path = cleanPath(f); 252 if (path != null && path.length() > 0 253 && !processed.contains(path)) { 254 if (f.isFile()) { 255 // Not in the excludeList, add it to both lists 256 includedFiles.add(path); 257 processed.add(path); 258 } else if (f.isDirectory()) { 259 // Add the directory entries 260 String[] dirList = f.list(); 261 Set<String> dirInputSet = new HashSet<String>(); 262 for (String x : dirList) { 263 dirInputSet.add(x); 264 } 265 // Process all entries in this directory 266 Set<String> subList = expand(f, dirInputSet, processed); 267 includedFiles.addAll(subList); 268 processed.add(path); 269 } 270 } 271 } 272 return includedFiles; 273 } 274 275 private String cleanPath(File f) { 276 String path = f.getPath(); 277 if (f.isFile()) { 278 path = cleanFilePath(path); 279 } else if (f.isDirectory()) { 280 path = cleanDirPath(path); 281 } else { 282 System.err.println("WARNING: Path does not exist as file or directory: " + path); 283 path = null; 284 } 285 return path; 286 } 287 288 private String cleanFilePath(String path) { 289 // Remove leading and trailing whitespace 290 path = path.trim(); 291 // Make all / and \ chars one 292 if (File.separatorChar == '/') { 293 path = path.replace('\\', '/'); 294 } else { 295 path = path.replace('/', '\\'); 296 } 297 // Remove leading ./ 298 if (path.startsWith("." + File.separator)) { 299 path = path.substring(2); 300 } 301 return path; 302 } 303 304 private String cleanDirPath(String path) { 305 path = cleanFilePath(path); 306 // Make sure it ends with a file separator 307 if (!path.endsWith(File.separator)) { 308 path = path + File.separator; 309 } 310 return path; 311 } 312 313 }