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