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  */
21 package proguard;
22 
23 import proguard.classfile.ClassPool;
24 import proguard.classfile.util.ClassUtil;
25 import proguard.io.*;
26 
27 import java.io.IOException;
28 import java.util.*;
29 
30 /**
31  * This class writes the output class files.
32  *
33  * @author Eric Lafortune
34  */
35 public 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      */
OutputWriter(Configuration configuration)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      */
execute(ClassPool programClassPool)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      */
writeOutput(ClassPool programClassPool, ClassPath classPath, int fromInputIndex, int fromOutputIndex, int toOutputIndex)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      */
createPackagePrefixMap(ClassPool classPool)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