1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21package proguard;
22
23import proguard.classfile.ClassPool;
24import proguard.classfile.editor.ClassElementSorter;
25import proguard.classfile.visitor.*;
26import proguard.obfuscate.Obfuscator;
27import proguard.optimize.Optimizer;
28import proguard.preverify.*;
29import proguard.shrink.Shrinker;
30
31import java.io.*;
32
33/**
34 * Tool for shrinking, optimizing, obfuscating, and preverifying Java classes.
35 *
36 * @author Eric Lafortune
37 */
38public class ProGuard
39{
40    public static final String VERSION = "ProGuard, version 4.4";
41
42    private final Configuration configuration;
43    private       ClassPool     programClassPool = new ClassPool();
44    private final ClassPool     libraryClassPool = new ClassPool();
45
46
47    /**
48     * Creates a new ProGuard object to process jars as specified by the given
49     * configuration.
50     */
51    public ProGuard(Configuration configuration)
52    {
53        this.configuration = configuration;
54    }
55
56
57    /**
58     * Performs all subsequent ProGuard operations.
59     */
60    public void execute() throws IOException
61    {
62        System.out.println(VERSION);
63
64        GPL.check();
65
66        if (configuration.printConfiguration != null)
67        {
68            printConfiguration();
69        }
70
71        if (configuration.programJars != null     &&
72            configuration.programJars.hasOutput() &&
73            new UpToDateChecker(configuration).check())
74        {
75            return;
76        }
77
78        readInput();
79
80        if (configuration.shrink    ||
81            configuration.optimize  ||
82            configuration.obfuscate ||
83            configuration.preverify)
84        {
85            initialize();
86        }
87
88        if (configuration.targetClassVersion != 0)
89        {
90            target();
91        }
92
93        if (configuration.printSeeds != null)
94        {
95            printSeeds();
96        }
97
98        if (configuration.shrink)
99        {
100            shrink();
101        }
102
103        if (configuration.preverify)
104        {
105            inlineSubroutines();
106        }
107
108        if (configuration.optimize)
109        {
110            for (int optimizationPass = 0;
111                 optimizationPass < configuration.optimizationPasses;
112                 optimizationPass++)
113            {
114                if (!optimize())
115                {
116                    // Stop optimizing if the code doesn't improve any further.
117                    break;
118                }
119
120                // Shrink again, if we may.
121                if (configuration.shrink)
122                {
123                    // Don't print any usage this time around.
124                    configuration.printUsage       = null;
125                    configuration.whyAreYouKeeping = null;
126
127                    shrink();
128                }
129            }
130        }
131
132        if (configuration.obfuscate)
133        {
134            obfuscate();
135        }
136
137        if (configuration.preverify)
138        {
139            preverify();
140        }
141
142        if (configuration.shrink    ||
143            configuration.optimize  ||
144            configuration.obfuscate ||
145            configuration.preverify)
146        {
147            sortClassElements();
148        }
149
150        if (configuration.programJars.hasOutput())
151        {
152            writeOutput();
153        }
154
155        if (configuration.dump != null)
156        {
157            dump();
158        }
159    }
160
161
162    /**
163     * Prints out the configuration that ProGuard is using.
164     */
165    private void printConfiguration() throws IOException
166    {
167        if (configuration.verbose)
168        {
169            System.out.println("Printing configuration to [" + fileName(configuration.printConfiguration) + "]...");
170        }
171
172        PrintStream ps = createPrintStream(configuration.printConfiguration);
173        try
174        {
175            new ConfigurationWriter(ps).write(configuration);
176        }
177        finally
178        {
179            closePrintStream(ps);
180        }
181    }
182
183
184    /**
185     * Reads the input class files.
186     */
187    private void readInput() throws IOException
188    {
189        if (configuration.verbose)
190        {
191            System.out.println("Reading input...");
192        }
193
194        // Fill the program class pool and the library class pool.
195        new InputReader(configuration).execute(programClassPool, libraryClassPool);
196    }
197
198
199    /**
200     * Initializes the cross-references between all classes, performs some
201     * basic checks, and shrinks the library class pool.
202     */
203    private void initialize() throws IOException
204    {
205        if (configuration.verbose)
206        {
207            System.out.println("Initializing...");
208        }
209
210        new Initializer(configuration).execute(programClassPool, libraryClassPool);
211    }
212
213
214    /**
215     * Sets that target versions of the program classes.
216     */
217    private void target() throws IOException
218    {
219        if (configuration.verbose)
220        {
221            System.out.println("Setting target versions...");
222        }
223
224        new Targeter(configuration).execute(programClassPool);
225    }
226
227
228    /**
229     * Prints out classes and class members that are used as seeds in the
230     * shrinking and obfuscation steps.
231     */
232    private void printSeeds() throws IOException
233    {
234        if (configuration.verbose)
235        {
236            System.out.println("Printing kept classes, fields, and methods...");
237        }
238
239        // Check if we have at least some keep commands.
240        if (configuration.keep == null)
241        {
242            throw new IOException("You have to specify '-keep' options for the shrinking step.");
243        }
244
245        PrintStream ps = createPrintStream(configuration.printSeeds);
246        try
247        {
248            // Create a visitor for printing out the seeds. We're  printing out
249            // the program elements that are preserved against shrinking,
250            // optimization, or obfuscation.
251            SimpleClassPrinter printer = new SimpleClassPrinter(false, ps);
252            ClassPoolVisitor classPoolvisitor =
253                ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
254                                                                        new ProgramClassFilter(printer),
255                                                                        new ProgramMemberFilter(printer),
256                                                                        true,
257                                                                        true,
258                                                                        true);
259
260            // Print out the seeds.
261            programClassPool.accept(classPoolvisitor);
262            libraryClassPool.accept(classPoolvisitor);
263        }
264        finally
265        {
266            closePrintStream(ps);
267        }
268    }
269
270
271    /**
272     * Performs the shrinking step.
273     */
274    private void shrink() throws IOException
275    {
276        if (configuration.verbose)
277        {
278            System.out.println("Shrinking...");
279
280            // We'll print out some explanation, if requested.
281            if (configuration.whyAreYouKeeping != null)
282            {
283                System.out.println("Explaining why classes and class members are being kept...");
284            }
285
286            // We'll print out the usage, if requested.
287            if (configuration.printUsage != null)
288            {
289                System.out.println("Printing usage to [" + fileName(configuration.printUsage) + "]...");
290            }
291        }
292
293        // Perform the actual shrinking.
294        programClassPool =
295            new Shrinker(configuration).execute(programClassPool, libraryClassPool);
296    }
297
298
299    /**
300     * Performs the subroutine inlining step.
301     */
302    private void inlineSubroutines()
303    {
304        if (configuration.verbose)
305        {
306            System.out.println("Inlining subroutines...");
307        }
308
309        // Perform the actual inlining.
310        new SubroutineInliner(configuration).execute(programClassPool);
311    }
312
313
314    /**
315     * Performs the optimization step.
316     */
317    private boolean optimize() throws IOException
318    {
319        if (configuration.verbose)
320        {
321            System.out.println("Optimizing...");
322        }
323
324        // Perform the actual optimization.
325        return new Optimizer(configuration).execute(programClassPool, libraryClassPool);
326    }
327
328
329    /**
330     * Performs the obfuscation step.
331     */
332    private void obfuscate() throws IOException
333    {
334        if (configuration.verbose)
335        {
336            System.out.println("Obfuscating...");
337
338            // We'll apply a mapping, if requested.
339            if (configuration.applyMapping != null)
340            {
341                System.out.println("Applying mapping [" + fileName(configuration.applyMapping) + "]");
342            }
343
344            // We'll print out the mapping, if requested.
345            if (configuration.printMapping != null)
346            {
347                System.out.println("Printing mapping to [" + fileName(configuration.printMapping) + "]...");
348            }
349        }
350
351        // Perform the actual obfuscation.
352        new Obfuscator(configuration).execute(programClassPool, libraryClassPool);
353    }
354
355
356    /**
357     * Performs the preverification step.
358     */
359    private void preverify()
360    {
361        if (configuration.verbose)
362        {
363            System.out.println("Preverifying...");
364        }
365
366        // Perform the actual preverification.
367        new Preverifier(configuration).execute(programClassPool);
368    }
369
370
371    /**
372     * Sorts the elements of all program classes.
373     */
374    private void sortClassElements()
375    {
376        programClassPool.classesAccept(new ClassElementSorter());
377    }
378
379
380    /**
381     * Writes the output class files.
382     */
383    private void writeOutput() throws IOException
384    {
385        if (configuration.verbose)
386        {
387            System.out.println("Writing output...");
388        }
389
390        // Write out the program class pool.
391        new OutputWriter(configuration).execute(programClassPool);
392    }
393
394
395    /**
396     * Prints out the contents of the program classes.
397     */
398    private void dump() throws IOException
399    {
400        if (configuration.verbose)
401        {
402            System.out.println("Printing classes to [" + fileName(configuration.dump) + "]...");
403        }
404
405        PrintStream ps = createPrintStream(configuration.dump);
406        try
407        {
408            programClassPool.classesAccept(new ClassPrinter(ps));
409        }
410        finally
411        {
412            closePrintStream(ps);
413        }
414    }
415
416
417    /**
418     * Returns a print stream for the given file, or the standard output if
419     * the file name is empty.
420     */
421    private PrintStream createPrintStream(File file)
422    throws FileNotFoundException
423    {
424        return isFile(file) ?
425            new PrintStream(new BufferedOutputStream(new FileOutputStream(file))) :
426            System.out;
427    }
428
429
430    /**
431     * Closes the given print stream, or closes it if is the standard output.
432     * @param printStream
433     */
434    private void closePrintStream(PrintStream printStream)
435    {
436        if (printStream == System.out)
437        {
438            printStream.flush();
439        }
440        else
441        {
442            printStream.close();
443        }
444    }
445
446
447    /**
448     * Returns the absolute file name for the given file, or the standard output
449     * if the file name is empty.
450     */
451    private String fileName(File file)
452    {
453        return isFile(file) ?
454            file.getAbsolutePath() :
455            "standard output";
456    }
457
458
459    /**
460     * Returns whether the given file is actually a file, or just a placeholder
461     * for the standard output.
462     */
463    private boolean isFile(File file)
464    {
465        return file.getPath().length() > 0;
466    }
467
468
469    /**
470     * The main method for ProGuard.
471     */
472    public static void main(String[] args)
473    {
474        if (args.length == 0)
475        {
476            System.out.println(VERSION);
477            System.out.println("Usage: java proguard.ProGuard [options ...]");
478            System.exit(1);
479        }
480
481        // Create the default options.
482        Configuration configuration = new Configuration();
483
484        try
485        {
486            // Parse the options specified in the command line arguments.
487            ConfigurationParser parser = new ConfigurationParser(args);
488
489            try
490            {
491                parser.parse(configuration);
492            }
493            finally
494            {
495                parser.close();
496            }
497
498            // Execute ProGuard with these options.
499            new ProGuard(configuration).execute();
500        }
501        catch (Exception ex)
502        {
503            if (configuration.verbose)
504            {
505                // Print a verbose stack trace.
506                ex.printStackTrace();
507            }
508            else
509            {
510                // Print just the stack trace message.
511                System.err.println("Error: "+ex.getMessage());
512            }
513
514            System.exit(1);
515        }
516
517        System.exit(0);
518    }
519}
520