InputReader.java revision b9cc48a43ed984587c939d02fba5316bf5c0df6e
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.util.WarningPrinter;
25import proguard.classfile.visitor.*;
26import proguard.io.*;
27
28import java.io.IOException;
29
30/**
31 * This class reads the input class files.
32 *
33 * @author Eric Lafortune
34 */
35public class InputReader
36{
37    private final Configuration configuration;
38
39
40    /**
41     * Creates a new InputReader to read input class files as specified by the
42     * given configuration.
43     */
44    public InputReader(Configuration configuration)
45    {
46        this.configuration = configuration;
47    }
48
49
50    /**
51     * Fills the given program class pool and library class pool by reading
52     * class files, based on the current configuration.
53     */
54    public void execute(ClassPool programClassPool,
55                        ClassPool libraryClassPool) throws IOException
56    {
57        // Check if we have at least some input classes.
58        if (configuration.programJars == null)
59        {
60            throw new IOException("The input is empty. You have to specify one or more '-injars' options");
61        }
62
63        // Perform some sanity checks on the class paths.
64        checkInputOutput(configuration.libraryJars,
65                         configuration.programJars);
66        checkInputOutput(configuration.programJars,
67                         configuration.programJars);
68
69        WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn);
70        WarningPrinter notePrinter    = new WarningPrinter(System.out, configuration.note);
71
72        DuplicateClassPrinter duplicateClassPrinter = new DuplicateClassPrinter(notePrinter);
73
74        // Read the program class files.
75        // Prepare a data entry reader to filter all classes,
76        // which are then decoded to classes by a class reader,
77        // which are then put in the class pool by a class pool filler.
78        readInput("Reading program ",
79                  configuration.programJars,
80                  new ClassFilter(
81                  new ClassReader(false,
82                                  configuration.skipNonPublicLibraryClasses,
83                                  configuration.skipNonPublicLibraryClassMembers,
84                                  warningPrinter,
85                  new ClassPresenceFilter(programClassPool, duplicateClassPrinter,
86                  new ClassPoolFiller(programClassPool)))));
87
88        // Check if we have at least some input classes.
89        if (programClassPool.size() == 0)
90        {
91            throw new IOException("The input doesn't contain any classes. Did you specify the proper '-injars' options?");
92        }
93
94        // Read the library class files, if any.
95        if (configuration.libraryJars != null)
96        {
97            // Prepare a data entry reader to filter all classes,
98            // which are then decoded to classes by a class reader,
99            // which are then put in the class pool by a class pool filler.
100            readInput("Reading library ",
101                      configuration.libraryJars,
102                      new ClassFilter(
103                      new ClassReader(true,
104                                      configuration.skipNonPublicLibraryClasses,
105                                      configuration.skipNonPublicLibraryClassMembers,
106                                      warningPrinter,
107                      new ClassPresenceFilter(programClassPool, duplicateClassPrinter,
108                      new ClassPresenceFilter(libraryClassPool, duplicateClassPrinter,
109                      new ClassPoolFiller(libraryClassPool))))));
110        }
111
112        // Print out a summary of the notes, if necessary.
113        int noteCount = notePrinter.getWarningCount();
114        if (noteCount > 0)
115        {
116            System.err.println("Note: there were " + noteCount +
117                               " duplicate class definitions.");
118            System.out.println("      (http://proguard.sourceforge.net/manual/troubleshooting.html#duplicateclass)");
119        }
120
121        // Print out a summary of the warnings, if necessary.
122        int warningCount = warningPrinter.getWarningCount();
123        if (warningCount > 0)
124        {
125            System.err.println("Warning: there were " + warningCount +
126                               " classes in incorrectly named files.");
127            System.err.println("         You should make sure all file names correspond to their class names.");
128            System.err.println("         The directory hierarchies must correspond to the package hierarchies.");
129            System.err.println("         (http://proguard.sourceforge.net/manual/troubleshooting.html#unexpectedclass)");
130
131            if (!configuration.ignoreWarnings)
132            {
133                System.err.println("         If you don't mind the mentioned classes not being written out,");
134                System.err.println("         you could try your luck using the '-ignorewarnings' option.");
135                throw new IOException("Please correct the above warnings first.");
136            }
137        }
138    }
139
140
141    /**
142     * Performs some sanity checks on the class paths.
143     */
144    private void checkInputOutput(ClassPath inputClassPath,
145                                  ClassPath outputClassPath)
146    throws IOException
147    {
148        if (inputClassPath == null ||
149            outputClassPath == null)
150        {
151            return;
152        }
153
154        for (int index1 = 0; index1 < inputClassPath.size(); index1++)
155        {
156            ClassPathEntry entry1 = inputClassPath.get(index1);
157            if (!entry1.isOutput())
158            {
159                for (int index2 = 0; index2 < outputClassPath.size(); index2++)
160                {
161                    ClassPathEntry entry2 = outputClassPath.get(index2);
162                    if (entry2.isOutput() &&
163                        entry2.getName().equals(entry1.getName()))
164                    {
165                        throw new IOException("Input jars and output jars must be different ["+entry1.getName()+"]");
166                    }
167                }
168            }
169        }
170    }
171
172
173    /**
174     * Reads all input entries from the given class path.
175     */
176    private void readInput(String          messagePrefix,
177                           ClassPath       classPath,
178                           DataEntryReader reader) throws IOException
179    {
180        readInput(messagePrefix,
181                  classPath,
182                  0,
183                  classPath.size(),
184                  reader);
185    }
186
187
188    /**
189     * Reads all input entries from the given section of the given class path.
190     */
191    public void readInput(String          messagePrefix,
192                          ClassPath       classPath,
193                          int             fromIndex,
194                          int             toIndex,
195                          DataEntryReader reader) throws IOException
196    {
197        for (int index = fromIndex; index < toIndex; index++)
198        {
199            ClassPathEntry entry = classPath.get(index);
200            if (!entry.isOutput())
201            {
202                readInput(messagePrefix, entry, reader);
203            }
204        }
205    }
206
207
208    /**
209     * Reads the given input class path entry.
210     */
211    private void readInput(String          messagePrefix,
212                           ClassPathEntry  classPathEntry,
213                           DataEntryReader dataEntryReader) throws IOException
214    {
215        try
216        {
217            // Create a reader that can unwrap jars, wars, ears, and zips.
218            DataEntryReader reader =
219                DataEntryReaderFactory.createDataEntryReader(messagePrefix,
220                                                             classPathEntry,
221                                                             dataEntryReader);
222
223            // Create the data entry pump.
224            DirectoryPump directoryPump =
225                new DirectoryPump(classPathEntry.getFile());
226
227            // Pump the data entries into the reader.
228            directoryPump.pumpDataEntries(reader);
229        }
230        catch (IOException ex)
231        {
232            throw (IOException)new IOException("Can't read [" + classPathEntry + "] (" + ex.getMessage() + ")").initCause(ex);
233        }
234    }
235}
236