1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2014 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        int firstInputIndex = 0;
59        int lastInputIndex  = 0;
60
61        // Go over all program class path entries.
62        for (int index = 0; index < programJars.size(); index++)
63        {
64            // Is it an input entry?
65            ClassPathEntry entry = programJars.get(index);
66            if (!entry.isOutput())
67            {
68                // Remember the index of the last input entry.
69                lastInputIndex = index;
70            }
71            else
72            {
73                // Check if this the last output entry in a series.
74                int nextIndex = index + 1;
75                if (nextIndex == programJars.size() ||
76                    !programJars.get(nextIndex).isOutput())
77                {
78                    // Write the processed input entries to the output entries.
79                    writeOutput(programClassPool,
80                                programJars,
81                                firstInputIndex,
82                                lastInputIndex + 1,
83                                nextIndex);
84
85                    // Start with the next series of input entries.
86                    firstInputIndex = nextIndex;
87                }
88            }
89        }
90    }
91
92
93    /**
94     * Transfers the specified input jars to the specified output jars.
95     */
96    private void writeOutput(ClassPool programClassPool,
97                             ClassPath classPath,
98                             int       fromInputIndex,
99                             int       fromOutputIndex,
100                             int       toOutputIndex)
101    throws IOException
102    {
103        try
104        {
105            // Construct the writer that can write jars, wars, ears, zips, and
106            // directories, cascading over the specified output entries.
107            DataEntryWriter writer =
108                DataEntryWriterFactory.createDataEntryWriter(classPath,
109                                                             fromOutputIndex,
110                                                             toOutputIndex);
111
112            // The writer will be used to write possibly obfuscated class files.
113            DataEntryReader classRewriter =
114                new ClassRewriter(programClassPool, writer);
115
116            // The writer will also be used to write resource files.
117            DataEntryReader resourceCopier =
118                new DataEntryCopier(writer);
119
120            DataEntryReader resourceRewriter = resourceCopier;
121
122            // Adapt resource file contents and names, if necessary.
123            if (configuration.obfuscate)
124            {
125                // Wrap the resource writer with a filter and a data entry
126                // rewriter, if required.
127                if (configuration.adaptResourceFileContents != null)
128                {
129                    resourceRewriter =
130                        new NameFilter(configuration.adaptResourceFileContents,
131                        new NameFilter("META-INF/MANIFEST.MF,META-INF/*.SF",
132                            new ManifestRewriter(programClassPool, writer),
133                            new DataEntryRewriter(programClassPool, writer)),
134                        resourceRewriter);
135                }
136
137                // Wrap the resource writer with a filter and a data entry
138                // renamer, if required.
139                if (configuration.adaptResourceFileNames != null)
140                {
141                    Map packagePrefixMap = createPackagePrefixMap(programClassPool);
142
143                    resourceRewriter =
144                        new NameFilter(configuration.adaptResourceFileNames,
145                        new DataEntryObfuscator(programClassPool,
146                                                packagePrefixMap,
147                                                resourceRewriter),
148                        resourceRewriter);
149                }
150            }
151
152            DataEntryReader directoryRewriter = null;
153
154            // Wrap the directory writer with a filter and a data entry renamer,
155            // if required.
156            if (configuration.keepDirectories != null)
157            {
158                Map packagePrefixMap = createPackagePrefixMap(programClassPool);
159
160                directoryRewriter =
161                    new NameFilter(configuration.keepDirectories,
162                    new DataEntryRenamer(packagePrefixMap,
163                                         resourceCopier,
164                                         resourceCopier));
165            }
166
167            // Create the reader that can write class files and copy directories
168            // and resource files to the main writer.
169            DataEntryReader reader =
170                new ClassFilter(    classRewriter,
171                new DirectoryFilter(directoryRewriter,
172                                    resourceRewriter));
173
174            // Go over the specified input entries and write their processed
175            // versions.
176            new InputReader(configuration).readInput("  Copying resources from program ",
177                                                     classPath,
178                                                     fromInputIndex,
179                                                     fromOutputIndex,
180                                                     reader);
181
182            // Close all output entries.
183            writer.close();
184        }
185        catch (IOException ex)
186        {
187            throw (IOException)new IOException("Can't write [" + classPath.get(fromOutputIndex).getName() + "] (" + ex.getMessage() + ")").initCause(ex);
188        }
189    }
190
191
192    /**
193     * Creates a map of old package prefixes to new package prefixes, based on
194     * the given class pool.
195     */
196    private static Map createPackagePrefixMap(ClassPool classPool)
197    {
198        Map packagePrefixMap = new HashMap();
199
200        Iterator iterator = classPool.classNames();
201        while (iterator.hasNext())
202        {
203            String className     = (String)iterator.next();
204            String packagePrefix = ClassUtil.internalPackagePrefix(className);
205
206            String mappedNewPackagePrefix = (String)packagePrefixMap.get(packagePrefix);
207            if (mappedNewPackagePrefix == null ||
208                !mappedNewPackagePrefix.equals(packagePrefix))
209            {
210                String newClassName     = classPool.getClass(className).getName();
211                String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName);
212
213                packagePrefixMap.put(packagePrefix, newPackagePrefix);
214            }
215        }
216
217        return packagePrefixMap;
218    }
219}
220