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.shrink;
22
23import proguard.*;
24import proguard.classfile.ClassPool;
25import proguard.classfile.attribute.visitor.*;
26import proguard.classfile.visitor.*;
27
28import java.io.*;
29
30/**
31 * This class shrinks class pools according to a given configuration.
32 *
33 * @author Eric Lafortune
34 */
35public class Shrinker
36{
37    private final Configuration configuration;
38
39
40    /**
41     * Creates a new Shrinker.
42     */
43    public Shrinker(Configuration configuration)
44    {
45        this.configuration = configuration;
46    }
47
48
49    /**
50     * Performs shrinking of the given program class pool.
51     */
52    public ClassPool execute(ClassPool programClassPool,
53                             ClassPool libraryClassPool) throws IOException
54    {
55        // Check if we have at least some keep commands.
56        if (configuration.keep == null)
57        {
58            throw new IOException("You have to specify '-keep' options for the shrinking step.");
59        }
60
61        // Clean up any old visitor info.
62        programClassPool.classesAccept(new ClassCleaner());
63        libraryClassPool.classesAccept(new ClassCleaner());
64
65        // Create a visitor for marking the seeds.
66        UsageMarker usageMarker = configuration.whyAreYouKeeping == null ?
67            new UsageMarker() :
68            new ShortestUsageMarker();
69
70        ClassPoolVisitor classPoolvisitor =
71            ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
72                                                                    usageMarker,
73                                                                    usageMarker,
74                                                                    true,
75                                                                    false,
76                                                                    false);
77        // Mark the seeds.
78        programClassPool.accept(classPoolvisitor);
79        libraryClassPool.accept(classPoolvisitor);
80
81        // Mark interfaces that have to be kept.
82        programClassPool.classesAccept(new InterfaceUsageMarker(usageMarker));
83
84        // Mark the inner class and annotation information that has to be kept.
85        programClassPool.classesAccept(
86            new UsedClassFilter(usageMarker,
87            new AllAttributeVisitor(true,
88            new MultiAttributeVisitor(new AttributeVisitor[]
89            {
90                new InnerUsageMarker(usageMarker),
91                new AnnotationUsageMarker(usageMarker),
92            }))));
93
94        // Should we explain ourselves?
95        if (configuration.whyAreYouKeeping != null)
96        {
97            System.out.println();
98
99            // Create a visitor for explaining classes and class members.
100            ShortestUsagePrinter shortestUsagePrinter =
101                new ShortestUsagePrinter((ShortestUsageMarker)usageMarker,
102                                         configuration.verbose);
103
104            ClassPoolVisitor whyClassPoolvisitor =
105                ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.whyAreYouKeeping,
106                                                                        shortestUsagePrinter,
107                                                                        shortestUsagePrinter);
108
109            // Mark the seeds.
110            programClassPool.accept(whyClassPoolvisitor);
111            libraryClassPool.accept(whyClassPoolvisitor);
112        }
113
114        if (configuration.printUsage != null)
115        {
116            PrintStream ps = isFile(configuration.printUsage) ?
117                new PrintStream(new BufferedOutputStream(new FileOutputStream(configuration.printUsage))) :
118                System.out;
119
120            // Print out items that will be removed.
121            programClassPool.classesAcceptAlphabetically(
122                new UsagePrinter(usageMarker, true, ps));
123
124            if (ps != System.out)
125            {
126                ps.close();
127            }
128        }
129
130        // Discard unused program classes.
131        int originalProgramClassPoolSize = programClassPool.size();
132
133        ClassPool newProgramClassPool = new ClassPool();
134        programClassPool.classesAccept(
135            new UsedClassFilter(usageMarker,
136            new MultiClassVisitor(
137            new ClassVisitor[] {
138                new ClassShrinker(usageMarker),
139                new ClassPoolFiller(newProgramClassPool)
140            })));
141
142        programClassPool.clear();
143
144        // Check if we have at least some output classes.
145        int newProgramClassPoolSize = newProgramClassPool.size();
146        if (newProgramClassPoolSize == 0)
147        {
148            throw new IOException("The output jar is empty. Did you specify the proper '-keep' options?");
149        }
150
151        if (configuration.verbose)
152        {
153            System.out.println("Removing unused program classes and class elements...");
154            System.out.println("  Original number of program classes: " + originalProgramClassPoolSize);
155            System.out.println("  Final number of program classes:    " + newProgramClassPoolSize);
156        }
157
158        return newProgramClassPool;
159    }
160
161
162    /**
163     * Returns whether the given file is actually a file, or just a placeholder
164     * for the standard output.
165     */
166    private boolean isFile(File file)
167    {
168        return file.getPath().length() > 0;
169    }
170}
171