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