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.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        }
119
120        // Print out a summary of the warnings, if necessary.
121        int warningCount = warningPrinter.getWarningCount();
122        if (warningCount > 0)
123        {
124            System.err.println("Warning: there were " + warningCount +
125                               " classes in incorrectly named files.");
126            System.err.println("         You should make sure all file names correspond to their class names.");
127            System.err.println("         The directory hierarchies must correspond to the package hierarchies.");
128
129            if (!configuration.ignoreWarnings)
130            {
131                System.err.println("         If you don't mind the mentioned classes not being written out,");
132                System.err.println("         you could try your luck using the '-ignorewarnings' option.");
133                throw new IOException("Please correct the above warnings first.");
134            }
135        }
136    }
137
138
139    /**
140     * Performs some sanity checks on the class paths.
141     */
142    private void checkInputOutput(ClassPath inputClassPath,
143                                  ClassPath outputClassPath)
144    throws IOException
145    {
146        if (inputClassPath == null ||
147            outputClassPath == null)
148        {
149            return;
150        }
151
152        for (int index1 = 0; index1 < inputClassPath.size(); index1++)
153        {
154            ClassPathEntry entry1 = inputClassPath.get(index1);
155            if (!entry1.isOutput())
156            {
157                for (int index2 = 0; index2 < outputClassPath.size(); index2++)
158                {
159                    ClassPathEntry entry2 = outputClassPath.get(index2);
160                    if (entry2.isOutput() &&
161                        entry2.getName().equals(entry1.getName()))
162                    {
163                        throw new IOException("Input jars and output jars must be different ["+entry1.getName()+"]");
164                    }
165                }
166            }
167        }
168    }
169
170
171    /**
172     * Reads all input entries from the given class path.
173     */
174    private void readInput(String          messagePrefix,
175                           ClassPath       classPath,
176                           DataEntryReader reader) throws IOException
177    {
178        readInput(messagePrefix,
179                  classPath,
180                  0,
181                  classPath.size(),
182                  reader);
183    }
184
185
186    /**
187     * Reads all input entries from the given section of the given class path.
188     */
189    public void readInput(String          messagePrefix,
190                          ClassPath       classPath,
191                          int             fromIndex,
192                          int             toIndex,
193                          DataEntryReader reader) throws IOException
194    {
195        for (int index = fromIndex; index < toIndex; index++)
196        {
197            ClassPathEntry entry = classPath.get(index);
198            if (!entry.isOutput())
199            {
200                readInput(messagePrefix, entry, reader);
201            }
202        }
203    }
204
205
206    /**
207     * Reads the given input class path entry.
208     */
209    private void readInput(String          messagePrefix,
210                           ClassPathEntry  classPathEntry,
211                           DataEntryReader dataEntryReader) throws IOException
212    {
213        try
214        {
215            // Create a reader that can unwrap jars, wars, ears, and zips.
216            DataEntryReader reader =
217                DataEntryReaderFactory.createDataEntryReader(messagePrefix,
218                                                             classPathEntry,
219                                                             dataEntryReader);
220
221            // Create the data entry pump.
222            DirectoryPump directoryPump =
223                new DirectoryPump(classPathEntry.getFile());
224
225            // Pump the data entries into the reader.
226            directoryPump.pumpDataEntries(reader);
227        }
228        catch (IOException ex)
229        {
230            throw new IOException("Can't read [" + classPathEntry + "] (" + ex.getMessage() + ")");
231        }
232    }
233}
234