Shrinker.java revision cfead78069f3dc32998dc118ee08cab3867acea2
1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2011 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.*;
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        // Automatically mark the parameterless constructors of seed classes,
71        // mainly for convenience and for backward compatibility.
72        ClassVisitor classUsageMarker =
73            new MultiClassVisitor(new ClassVisitor[]
74            {
75                usageMarker,
76                new NamedMethodVisitor(ClassConstants.INTERNAL_METHOD_NAME_INIT,
77                                       ClassConstants.INTERNAL_METHOD_TYPE_INIT,
78                                       usageMarker)
79            });
80
81        ClassPoolVisitor classPoolvisitor =
82            ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
83                                                                    classUsageMarker,
84                                                                    usageMarker,
85                                                                    true,
86                                                                    false,
87                                                                    false);
88        // Mark the seeds.
89        programClassPool.accept(classPoolvisitor);
90        libraryClassPool.accept(classPoolvisitor);
91
92        // Mark interfaces that have to be kept.
93        programClassPool.classesAccept(new InterfaceUsageMarker(usageMarker));
94
95        // Mark the inner class and annotation information that has to be kept.
96        programClassPool.classesAccept(
97            new UsedClassFilter(usageMarker,
98            new AllAttributeVisitor(true,
99            new MultiAttributeVisitor(new AttributeVisitor[]
100            {
101                new InnerUsageMarker(usageMarker),
102                new AnnotationUsageMarker(usageMarker),
103            }))));
104
105        // Should we explain ourselves?
106        if (configuration.whyAreYouKeeping != null)
107        {
108            System.out.println();
109
110            // Create a visitor for explaining classes and class members.
111            ShortestUsagePrinter shortestUsagePrinter =
112                new ShortestUsagePrinter((ShortestUsageMarker)usageMarker,
113                                         configuration.verbose);
114
115            ClassPoolVisitor whyClassPoolvisitor =
116                ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.whyAreYouKeeping,
117                                                                        shortestUsagePrinter,
118                                                                        shortestUsagePrinter);
119
120            // Mark the seeds.
121            programClassPool.accept(whyClassPoolvisitor);
122            libraryClassPool.accept(whyClassPoolvisitor);
123        }
124
125        if (configuration.printUsage != null)
126        {
127            PrintStream ps = isFile(configuration.printUsage) ?
128                new PrintStream(new BufferedOutputStream(new FileOutputStream(configuration.printUsage))) :
129                System.out;
130
131            // Print out items that will be removed.
132            programClassPool.classesAcceptAlphabetically(
133                new UsagePrinter(usageMarker, true, ps));
134
135            if (ps != System.out)
136            {
137                ps.close();
138            }
139        }
140
141        // Discard unused program classes.
142        int originalProgramClassPoolSize = programClassPool.size();
143
144        ClassPool newProgramClassPool = new ClassPool();
145        programClassPool.classesAccept(
146            new UsedClassFilter(usageMarker,
147            new MultiClassVisitor(
148            new ClassVisitor[] {
149                new ClassShrinker(usageMarker),
150                new ClassPoolFiller(newProgramClassPool)
151            })));
152
153        programClassPool.clear();
154
155        // Check if we have at least some output classes.
156        int newProgramClassPoolSize = newProgramClassPool.size();
157        if (newProgramClassPoolSize == 0)
158        {
159            throw new IOException("The output jar is empty. Did you specify the proper '-keep' options?");
160        }
161
162        if (configuration.verbose)
163        {
164            System.out.println("Removing unused program classes and class elements...");
165            System.out.println("  Original number of program classes: " + originalProgramClassPoolSize);
166            System.out.println("  Final number of program classes:    " + newProgramClassPoolSize);
167        }
168
169        return newProgramClassPool;
170    }
171
172
173    /**
174     * Returns whether the given file is actually a file, or just a placeholder
175     * for the standard output.
176     */
177    private boolean isFile(File file)
178    {
179        return file.getPath().length() > 0;
180    }
181}
182