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.ClassUtil;
25import proguard.io.*;
26
27import java.io.IOException;
28import java.util.*;
29
30/**
31 * This class writes the output class files.
32 *
33 * @author Eric Lafortune
34 */
35public class OutputWriter
36{
37    private final Configuration configuration;
38
39
40    /**
41     * Creates a new OutputWriter to write output class files as specified by
42     * the given configuration.
43     */
44    public OutputWriter(Configuration configuration)
45    {
46        this.configuration = configuration;
47    }
48
49
50    /**
51     * Writes the given class pool to class files, based on the current
52     * configuration.
53     */
54    public void execute(ClassPool programClassPool) throws IOException
55    {
56        ClassPath programJars = configuration.programJars;
57
58        // Perform a check on the first jar.
59        ClassPathEntry firstEntry = programJars.get(0);
60        if (firstEntry.isOutput())
61        {
62            throw new IOException("The output jar [" + firstEntry.getName() +
63                                  "] must be specified after an input jar, or it will be empty.");
64        }
65
66        // Perform some checks on the output jars.
67        for (int index = 0; index < programJars.size() - 1; index++)
68        {
69            ClassPathEntry entry = programJars.get(index);
70            if (entry.isOutput())
71            {
72                // Check if all but the last output jars have filters.
73                if (entry.getFilter()    == null &&
74                    entry.getJarFilter() == null &&
75                    entry.getWarFilter() == null &&
76                    entry.getEarFilter() == null &&
77                    entry.getZipFilter() == null &&
78                    programJars.get(index + 1).isOutput())
79                {
80                    throw new IOException("The output jar [" + entry.getName() +
81                                          "] must have a filter, or all subsequent jars will be empty.");
82                }
83
84                // Check if the output jar name is different from the input jar names.
85                for (int inIndex = 0; inIndex < programJars.size(); inIndex++)
86                {
87                    ClassPathEntry otherEntry = programJars.get(inIndex);
88
89                    if (!otherEntry.isOutput() &&
90                        entry.getFile().equals(otherEntry.getFile()))
91                    {
92                        throw new IOException("The output jar [" + entry.getName() +
93                                              "] must be different from all input jars.");
94                    }
95                }
96            }
97        }
98
99        int firstInputIndex = 0;
100        int lastInputIndex  = 0;
101
102        // Go over all program class path entries.
103        for (int index = 0; index < programJars.size(); index++)
104        {
105            // Is it an input entry?
106            ClassPathEntry entry = programJars.get(index);
107            if (!entry.isOutput())
108            {
109                // Remember the index of the last input entry.
110                lastInputIndex = index;
111            }
112            else
113            {
114                // Check if this the last output entry in a series.
115                int nextIndex = index + 1;
116                if (nextIndex == programJars.size() ||
117                    !programJars.get(nextIndex).isOutput())
118                {
119                    // Write the processed input entries to the output entries.
120                    writeOutput(programClassPool,
121                                programJars,
122                                firstInputIndex,
123                                lastInputIndex + 1,
124                                nextIndex);
125
126                    // Start with the next series of input entries.
127                    firstInputIndex = nextIndex;
128                }
129            }
130        }
131    }
132
133
134    /**
135     * Transfers the specified input jars to the specified output jars.
136     */
137    private void writeOutput(ClassPool programClassPool,
138                             ClassPath classPath,
139                             int       fromInputIndex,
140                             int       fromOutputIndex,
141                             int       toOutputIndex)
142    throws IOException
143    {
144        try
145        {
146            // Construct the writer that can write jars, wars, ears, zips, and
147            // directories, cascading over the specified output entries.
148            DataEntryWriter writer =
149                DataEntryWriterFactory.createDataEntryWriter(classPath,
150                                                             fromOutputIndex,
151                                                             toOutputIndex);
152
153            // The writer will be used to write possibly obfuscated class files.
154            DataEntryReader classRewriter =
155                new ClassRewriter(programClassPool, writer);
156
157            // The writer will also be used to write resource files.
158            DataEntryReader resourceCopier =
159                new DataEntryCopier(writer);
160
161            DataEntryReader resourceRewriter = resourceCopier;
162
163            // Wrap the resource writer with a filter and a data entry rewriter,
164            // if required.
165            if (configuration.adaptResourceFileContents != null)
166            {
167                resourceRewriter =
168                    new NameFilter(configuration.adaptResourceFileContents,
169                    new NameFilter("META-INF/**",
170                        new ManifestRewriter(programClassPool, writer),
171                        new DataEntryRewriter(programClassPool, writer)),
172                    resourceRewriter);
173            }
174
175            // Wrap the resource writer with a filter and a data entry renamer,
176            // if required.
177            if (configuration.adaptResourceFileNames != null)
178            {
179                Map packagePrefixMap = createPackagePrefixMap(programClassPool);
180
181                resourceRewriter =
182                    new NameFilter(configuration.adaptResourceFileNames,
183                    new DataEntryObfuscator(programClassPool,
184                                            packagePrefixMap,
185                                            resourceRewriter),
186                    resourceRewriter);
187            }
188
189            DataEntryReader directoryRewriter = null;
190
191            // Wrap the directory writer with a filter and a data entry renamer,
192            // if required.
193            if (configuration.keepDirectories != null)
194            {
195                Map packagePrefixMap = createPackagePrefixMap(programClassPool);
196
197                directoryRewriter =
198                    new NameFilter(configuration.keepDirectories,
199                    new DataEntryRenamer(packagePrefixMap,
200                                         resourceCopier,
201                                         resourceCopier));
202            }
203
204            // Create the reader that can write class files and copy directories
205            // and resource files to the main writer.
206            DataEntryReader reader =
207                new ClassFilter(    classRewriter,
208                new DirectoryFilter(directoryRewriter,
209                                    resourceRewriter));
210
211            // Go over the specified input entries and write their processed
212            // versions.
213            new InputReader(configuration).readInput("  Copying resources from program ",
214                                                     classPath,
215                                                     fromInputIndex,
216                                                     fromOutputIndex,
217                                                     reader);
218
219            // Close all output entries.
220            writer.close();
221        }
222        catch (IOException ex)
223        {
224            throw new IOException("Can't write [" + classPath.get(fromOutputIndex).getName() + "] (" + ex.getMessage() + ")");
225        }
226    }
227
228
229    /**
230     * Creates a map of old package prefixes to new package prefixes, based on
231     * the given class pool.
232     */
233    private static Map createPackagePrefixMap(ClassPool classPool)
234    {
235        Map PackagePrefixMap = new HashMap();
236
237        Iterator iterator = classPool.classNames();
238        while (iterator.hasNext())
239        {
240            String className     = (String)iterator.next();
241            String PackagePrefix = ClassUtil.internalPackagePrefix(className);
242
243            String mappedNewPackagePrefix = (String)PackagePrefixMap.get(PackagePrefix);
244            if (mappedNewPackagePrefix == null ||
245                !mappedNewPackagePrefix.equals(PackagePrefix))
246            {
247                String newClassName     = classPool.getClass(className).getName();
248                String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName);
249
250                PackagePrefixMap.put(PackagePrefix, newPackagePrefix);
251            }
252        }
253
254        return PackagePrefixMap;
255    }
256}
257