1 /* |
|
2 * Copyright (c) 2000, 2013, 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 [-m] [-o <outputfile>] <order_list> <exclude_list> <file> ...\n" |
|
55 + " -m activate module mode, where every direct sub\n" |
|
56 + " directory of the current working directory\n" |
|
57 + " will be assumed to be a separate source root\n" |
|
58 + " order_list is a file containing names of files to load\n" |
|
59 + " in order at the end of a jar file unless\n" |
|
60 + " excluded in the exclude list.\n" |
|
61 + " exclude_list is a file containing names of files/directories\n" |
|
62 + " NOT to be included in a jar file.\n" |
|
63 + "\n" |
|
64 + "The order_list or exclude_list may be replaced by a \"-\" if no\n" |
|
65 + "data is to be provided.\n" |
|
66 + "\n" |
|
67 + " The remaining arguments are files or directories to be included\n" |
|
68 + " in a jar file, from which will be excluded those entries which\n" |
|
69 + " appear in the exclude list.\n"; |
|
70 System.err.println(help); |
|
71 } |
|
72 |
|
73 |
|
74 /* |
|
75 * Create the file list to be included in a jar file, such that the |
|
76 * list will appear in a specific order, and allowing certain |
|
77 * files and directories to be excluded. |
|
78 * |
|
79 * Command path arguments are |
|
80 * - optional -m for module mode. |
|
81 * - optional -o outputfile |
|
82 * - name of a file containing a set of files to be included in a jar file. |
|
83 * - name of a file containing a set of files (or directories) to be |
|
84 * excluded from the jar file. |
|
85 * - names of files or directories to be searched for files to include |
|
86 * in the jar file. |
|
87 */ |
|
88 public static void main(String[] args) { |
|
89 JarReorder jr = new JarReorder(); |
|
90 jr.run(args); |
|
91 } |
|
92 |
|
93 private void run(String args[]) { |
|
94 |
|
95 int arglen = args.length; |
|
96 int argpos = 0; |
|
97 |
|
98 boolean moduleMode = false; |
|
99 |
|
100 if (arglen > 0) { |
|
101 // Check for module mode |
|
102 if (args[argpos].equals("-m")) { |
|
103 moduleMode = true; |
|
104 argpos++; |
|
105 arglen--; |
|
106 } |
|
107 // Look for "-o outputfilename" option |
|
108 if (arglen >= 2 && args[argpos].equals("-o")) { |
|
109 try { |
|
110 out = new PrintStream(new FileOutputStream(args[argpos+1])); |
|
111 } catch (FileNotFoundException e) { |
|
112 System.err.println("Error: " + e.getMessage()); |
|
113 e.printStackTrace(System.err); |
|
114 System.exit(1); |
|
115 } |
|
116 argpos += 2; |
|
117 arglen -= 2; |
|
118 } else { |
|
119 System.err.println("Error: Illegal arg count"); |
|
120 System.exit(1); |
|
121 } |
|
122 } else { |
|
123 out = System.out; |
|
124 } |
|
125 |
|
126 // Should be 2 or more args left |
|
127 if (arglen <= 2) { |
|
128 usage(); |
|
129 System.exit(1); |
|
130 } |
|
131 |
|
132 // Read the ordered set of files to be included in rt.jar. |
|
133 // Read the set of files/directories to be excluded from rt.jar. |
|
134 String classListFile = args[argpos]; |
|
135 String excludeListFile = args[argpos + 1]; |
|
136 argpos += 2; |
|
137 arglen -= 2; |
|
138 |
|
139 // If we run module mode, this will contain the list of subdirs to use |
|
140 // as source roots. Otherwise it will just contain the current working |
|
141 // dir. |
|
142 List<File> moduleDirs = findModuleDirs(moduleMode); |
|
143 |
|
144 // Create 2 lists and a set of processed files |
|
145 List<String> orderList = readListFromFile(classListFile, true, moduleDirs); |
|
146 List<String> excludeList = readListFromFile(excludeListFile, false, moduleDirs); |
|
147 Set<String> processed = new HashSet<String>(); |
|
148 |
|
149 // Create set of all files and directories excluded, then expand |
|
150 // that list completely |
|
151 Set<String> excludeSet = new HashSet<String>(excludeList); |
|
152 Set<String> allFilesExcluded = expand(null, excludeSet, processed); |
|
153 |
|
154 // Indicate all these have been processed, orderList too, kept to end. |
|
155 processed.addAll(orderList); |
|
156 |
|
157 // The remaining arguments are names of files/directories to be included |
|
158 // in the jar file. |
|
159 Set<String> inputSet = new HashSet<String>(); |
|
160 for (int i = 0; i < arglen; ++i) { |
|
161 String name = args[argpos + i]; |
|
162 for (File dir : moduleDirs) { |
|
163 String cleanName = cleanPath(new File(dir, name)); |
|
164 if ( cleanName != null && cleanName.length() > 0 && !inputSet.contains(cleanName) ) { |
|
165 inputSet.add(cleanName); |
|
166 } |
|
167 } |
|
168 } |
|
169 |
|
170 // Expand file/directory input so we get a complete set (except ordered) |
|
171 // Should be everything not excluded and not in order list. |
|
172 Set<String> allFilesIncluded = expand(null, inputSet, processed); |
|
173 |
|
174 // Create simple sorted list so we can add ordered items at end. |
|
175 List<String> allFiles = new ArrayList<String>(allFilesIncluded); |
|
176 Collections.sort(allFiles); |
|
177 |
|
178 // Now add the ordered set to the end of the list. |
|
179 // Add in REVERSE ORDER, so that the first element is closest to |
|
180 // the end (and the index). |
|
181 for (int i = orderList.size() - 1; i >= 0; --i) { |
|
182 String s = orderList.get(i); |
|
183 if (allFilesExcluded.contains(s)) { |
|
184 // Disable this warning until 8005688 is fixed |
|
185 // System.err.println("Included order file " + s |
|
186 // + " is also excluded, skipping."); |
|
187 } else if (new File(s).exists()) { |
|
188 allFiles.add(s); |
|
189 } else { |
|
190 System.err.println("Included order file " + s |
|
191 + " missing, skipping."); |
|
192 } |
|
193 } |
|
194 |
|
195 // Print final results. |
|
196 for (String str : allFiles) { |
|
197 // If running in module mode, each line must be prepended with a |
|
198 // '-C dir ' which points to the source root where that file is |
|
199 // found. |
|
200 if (moduleMode) { |
|
201 int firstPathSep = str.indexOf(File.separator); |
|
202 String moduleDir; |
|
203 if (firstPathSep < 0) { |
|
204 moduleDir = "."; |
|
205 } else { |
|
206 moduleDir = str.substring(0, firstPathSep); |
|
207 } |
|
208 String filePath = str.substring(firstPathSep + 1); |
|
209 out.println("-C " + moduleDir + " " + filePath); |
|
210 } else { |
|
211 out.println(str); |
|
212 } |
|
213 } |
|
214 out.flush(); |
|
215 out.close(); |
|
216 } |
|
217 |
|
218 /* |
|
219 * Read a file containing a list of files and directories into a List. |
|
220 */ |
|
221 private List<String> readListFromFile(String fileName, |
|
222 boolean addClassSuffix, List<File> moduleDirs) { |
|
223 |
|
224 BufferedReader br = null; |
|
225 List<String> list = new ArrayList<String>(); |
|
226 // If you see "-" for the name, just assume nothing was provided. |
|
227 if ("-".equals(fileName)) { |
|
228 return list; |
|
229 } |
|
230 try { |
|
231 br = new BufferedReader(new FileReader(fileName)); |
|
232 // Read the input file a path at a time. # in column 1 is a comment. |
|
233 while (true) { |
|
234 String path = br.readLine(); |
|
235 if (path == null) { |
|
236 break; |
|
237 } |
|
238 // Look for comments |
|
239 path = path.trim(); |
|
240 if (path.length() == 0 |
|
241 || path.charAt(0) == '#') { |
|
242 continue; |
|
243 } |
|
244 // Add trailing .class if necessary |
|
245 if (addClassSuffix && !path.endsWith(".class")) { |
|
246 path = path + ".class"; |
|
247 } |
|
248 // Look for file in each module source root |
|
249 boolean pathFound = false; |
|
250 for (File dir : moduleDirs) { |
|
251 File file = new File(dir, path); |
|
252 if (file.exists()) { |
|
253 pathFound = true; |
|
254 // Normalize the path |
|
255 String cleanPath = cleanPath(new File(dir, path)); |
|
256 // Add to list |
|
257 if (cleanPath != null && cleanPath.length() > 0 && !list.contains(cleanPath)) { |
|
258 list.add(cleanPath); |
|
259 } |
|
260 } |
|
261 } |
|
262 if (!pathFound) { |
|
263 System.err.println("WARNING: Path does not exist as file or directory: " + path); |
|
264 } |
|
265 } |
|
266 br.close(); |
|
267 } catch (FileNotFoundException e) { |
|
268 System.err.println("Can't find file \"" + fileName + "\"."); |
|
269 System.exit(1); |
|
270 } catch (IOException e) { |
|
271 e.printStackTrace(); |
|
272 System.exit(2); |
|
273 } |
|
274 return list; |
|
275 } |
|
276 |
|
277 /* |
|
278 * Expands inputSet (files or dirs) into full set of all files that |
|
279 * can be found by recursively descending directories. |
|
280 * @param dir root directory |
|
281 * @param inputSet set of files or dirs to look into |
|
282 * @param processed files or dirs already processed |
|
283 * @return set of files |
|
284 */ |
|
285 private Set<String> expand(File dir, |
|
286 Set<String> inputSet, |
|
287 Set<String> processed) { |
|
288 Set<String> includedFiles = new HashSet<String>(); |
|
289 if (inputSet.isEmpty()) { |
|
290 return includedFiles; |
|
291 } |
|
292 for (String name : inputSet) { |
|
293 // Depending on start location |
|
294 File f = (dir == null) ? new File(name) |
|
295 : new File(dir, name); |
|
296 // Normalized path to use |
|
297 String path = cleanPath(f); |
|
298 if (path != null && path.length() > 0 |
|
299 && !processed.contains(path)) { |
|
300 if (f.isFile()) { |
|
301 // Not in the excludeList, add it to both lists |
|
302 includedFiles.add(path); |
|
303 processed.add(path); |
|
304 } else if (f.isDirectory()) { |
|
305 // Add the directory entries |
|
306 String[] dirList = f.list(); |
|
307 Set<String> dirInputSet = new HashSet<String>(); |
|
308 for (String x : dirList) { |
|
309 dirInputSet.add(x); |
|
310 } |
|
311 // Process all entries in this directory |
|
312 Set<String> subList = expand(f, dirInputSet, processed); |
|
313 includedFiles.addAll(subList); |
|
314 processed.add(path); |
|
315 } |
|
316 } |
|
317 } |
|
318 return includedFiles; |
|
319 } |
|
320 |
|
321 /** |
|
322 * Find all module sub directories to be used as source roots. |
|
323 * @param moduleMode If true, assume sub directories are modules, otherwise |
|
324 * just use current working directory. |
|
325 * @return List of all found source roots |
|
326 */ |
|
327 private List<File> findModuleDirs(boolean moduleMode) { |
|
328 File cwd = new File("."); |
|
329 List<File> moduleDirs = new ArrayList<File>(); |
|
330 if (moduleMode) { |
|
331 for (File f : cwd.listFiles()) { |
|
332 if (f.isDirectory()) { |
|
333 moduleDirs.add(f); |
|
334 } |
|
335 } |
|
336 } else { |
|
337 moduleDirs.add(cwd); |
|
338 } |
|
339 return moduleDirs; |
|
340 } |
|
341 |
|
342 private String cleanPath(File f) { |
|
343 String path = f.getPath(); |
|
344 if (f.isFile()) { |
|
345 path = cleanFilePath(path); |
|
346 } else if (f.isDirectory()) { |
|
347 path = cleanDirPath(path); |
|
348 } else { |
|
349 System.err.println("WARNING: Path does not exist as file or directory: " + path); |
|
350 path = null; |
|
351 } |
|
352 return path; |
|
353 } |
|
354 |
|
355 private String cleanFilePath(String path) { |
|
356 // Remove leading and trailing whitespace |
|
357 path = path.trim(); |
|
358 // Make all / and \ chars one |
|
359 if (File.separatorChar == '/') { |
|
360 path = path.replace('\\', '/'); |
|
361 } else { |
|
362 path = path.replace('/', '\\'); |
|
363 } |
|
364 // Remove leading ./ |
|
365 while (path.startsWith("." + File.separator)) { |
|
366 path = path.substring(2); |
|
367 } |
|
368 return path; |
|
369 } |
|
370 |
|
371 private String cleanDirPath(String path) { |
|
372 path = cleanFilePath(path); |
|
373 // Make sure it ends with a file separator |
|
374 if (!path.endsWith(File.separator)) { |
|
375 path = path + File.separator; |
|
376 } |
|
377 // Remove any /./ in the path. |
|
378 if (path.endsWith(File.separator + "." + File.separator)) { |
|
379 path = path.substring(0, path.length() - 2); |
|
380 } |
|
381 return path; |
|
382 } |
|
383 |
|
384 } |
|