• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
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         // 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         // Check if the first of two subsequent the output jars has a filter.
67         for (int index = 0; index < programJars.size() - 1; index++)
68         {
69             ClassPathEntry entry = programJars.get(index);
70             if (entry.isOutput())
71             {
72                 if (entry.getFilter()    == null &&
73                     entry.getJarFilter() == null &&
74                     entry.getWarFilter() == null &&
75                     entry.getEarFilter() == null &&
76                     entry.getZipFilter() == null &&
77                     programJars.get(index + 1).isOutput())
78                 {
79                     throw new IOException("The output jar [" + entry.getName() +
80                                           "] must have a filter, or all subsequent output jars will be empty.");
81                 }
82             }
83         }
84 
85         // Check if the output jar names are different from the input jar names.
86         for (int outIndex = 0; outIndex < programJars.size() - 1; outIndex++)
87         {
88             ClassPathEntry entry = programJars.get(outIndex);
89             if (entry.isOutput())
90             {
91                 for (int inIndex = 0; inIndex < programJars.size(); inIndex++)
92                 {
93                     ClassPathEntry otherEntry = programJars.get(inIndex);
94 
95                     if (!otherEntry.isOutput() &&
96                         entry.getFile().equals(otherEntry.getFile()))
97                     {
98                         throw new IOException("The output jar [" + entry.getName() +
99                                               "] must be different from all input jars.");
100                     }
101                 }
102             }
103         }
104 
105         // Check for potential problems with mixed-case class names on
106         // case-insensitive file systems.
107         if (configuration.obfuscate                          &&
108             configuration.useMixedCaseClassNames             &&
109             configuration.classObfuscationDictionary == null &&
110             (configuration.note == null ||
111              !configuration.note.isEmpty()))
112         {
113             String os = System.getProperty("os.name").toLowerCase();
114             if (os.startsWith("windows") ||
115                 os.startsWith("mac os"))
116             {
117                 // Go over all program class path entries.
118                 for (int index = 0; index < programJars.size(); index++)
119                 {
120                     // Is it an output directory?
121                     ClassPathEntry entry = programJars.get(index);
122                     if (entry.isOutput() &&
123                         !entry.isJar() &&
124                         !entry.isWar() &&
125                         !entry.isEar() &&
126                         !entry.isZip())
127                     {
128                         System.out.println("Note: you're writing the processed class files to a directory [" + entry.getName() +"].");
129                         System.out.println("      This will likely cause problems with obfuscated mixed-case class names.");
130                         System.out.println("      You should consider writing the output to a jar file, or otherwise");
131                         System.out.println("      specify '-dontusemixedcaseclassnames'.");
132 
133                         break;
134                     }
135                 }
136             }
137         }
138 
139         int firstInputIndex = 0;
140         int lastInputIndex  = 0;
141 
142         // Go over all program class path entries.
143         for (int index = 0; index < programJars.size(); index++)
144         {
145             // Is it an input entry?
146             ClassPathEntry entry = programJars.get(index);
147             if (!entry.isOutput())
148             {
149                 // Remember the index of the last input entry.
150                 lastInputIndex = index;
151             }
152             else
153             {
154                 // Check if this the last output entry in a series.
155                 int nextIndex = index + 1;
156                 if (nextIndex == programJars.size() ||
157                     !programJars.get(nextIndex).isOutput())
158                 {
159                     // Write the processed input entries to the output entries.
160                     writeOutput(programClassPool,
161                                 programJars,
162                                 firstInputIndex,
163                                 lastInputIndex + 1,
164                                 nextIndex);
165 
166                     // Start with the next series of input entries.
167                     firstInputIndex = nextIndex;
168                 }
169             }
170         }
171     }
172 
173 
174     /**
175      * Transfers the specified input jars to the specified output jars.
176      */
writeOutput(ClassPool programClassPool, ClassPath classPath, int fromInputIndex, int fromOutputIndex, int toOutputIndex)177     private void writeOutput(ClassPool programClassPool,
178                              ClassPath classPath,
179                              int       fromInputIndex,
180                              int       fromOutputIndex,
181                              int       toOutputIndex)
182     throws IOException
183     {
184         try
185         {
186             // Construct the writer that can write jars, wars, ears, zips, and
187             // directories, cascading over the specified output entries.
188             DataEntryWriter writer =
189                 DataEntryWriterFactory.createDataEntryWriter(classPath,
190                                                              fromOutputIndex,
191                                                              toOutputIndex);
192 
193             // The writer will be used to write possibly obfuscated class files.
194             DataEntryReader classRewriter =
195                 new ClassRewriter(programClassPool, writer);
196 
197             // The writer will also be used to write resource files.
198             DataEntryReader resourceCopier =
199                 new DataEntryCopier(writer);
200 
201             DataEntryReader resourceRewriter = resourceCopier;
202 
203             // Wrap the resource writer with a filter and a data entry rewriter,
204             // if required.
205             if (configuration.adaptResourceFileContents != null)
206             {
207                 resourceRewriter =
208                     new NameFilter(configuration.adaptResourceFileContents,
209                     new NameFilter("META-INF/MANIFEST.MF,META-INF/*.SF",
210                         new ManifestRewriter(programClassPool, writer),
211                         new DataEntryRewriter(programClassPool, writer)),
212                     resourceRewriter);
213             }
214 
215             // Wrap the resource writer with a filter and a data entry renamer,
216             // if required.
217             if (configuration.adaptResourceFileNames != null)
218             {
219                 Map packagePrefixMap = createPackagePrefixMap(programClassPool);
220 
221                 resourceRewriter =
222                     new NameFilter(configuration.adaptResourceFileNames,
223                     new DataEntryObfuscator(programClassPool,
224                                             packagePrefixMap,
225                                             resourceRewriter),
226                     resourceRewriter);
227             }
228 
229             DataEntryReader directoryRewriter = null;
230 
231             // Wrap the directory writer with a filter and a data entry renamer,
232             // if required.
233             if (configuration.keepDirectories != null)
234             {
235                 Map packagePrefixMap = createPackagePrefixMap(programClassPool);
236 
237                 directoryRewriter =
238                     new NameFilter(configuration.keepDirectories,
239                     new DataEntryRenamer(packagePrefixMap,
240                                          resourceCopier,
241                                          resourceCopier));
242             }
243 
244             // Create the reader that can write class files and copy directories
245             // and resource files to the main writer.
246             DataEntryReader reader =
247                 new ClassFilter(    classRewriter,
248                 new DirectoryFilter(directoryRewriter,
249                                     resourceRewriter));
250 
251             // Go over the specified input entries and write their processed
252             // versions.
253             new InputReader(configuration).readInput("  Copying resources from program ",
254                                                      classPath,
255                                                      fromInputIndex,
256                                                      fromOutputIndex,
257                                                      reader);
258 
259             // Close all output entries.
260             writer.close();
261         }
262         catch (IOException ex)
263         {
264             throw (IOException)new IOException("Can't write [" + classPath.get(fromOutputIndex).getName() + "] (" + ex.getMessage() + ")").initCause(ex);
265         }
266     }
267 
268 
269     /**
270      * Creates a map of old package prefixes to new package prefixes, based on
271      * the given class pool.
272      */
createPackagePrefixMap(ClassPool classPool)273     private static Map createPackagePrefixMap(ClassPool classPool)
274     {
275         Map packagePrefixMap = new HashMap();
276 
277         Iterator iterator = classPool.classNames();
278         while (iterator.hasNext())
279         {
280             String className     = (String)iterator.next();
281             String packagePrefix = ClassUtil.internalPackagePrefix(className);
282 
283             String mappedNewPackagePrefix = (String)packagePrefixMap.get(packagePrefix);
284             if (mappedNewPackagePrefix == null ||
285                 !mappedNewPackagePrefix.equals(packagePrefix))
286             {
287                 String newClassName     = classPool.getClass(className).getName();
288                 String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName);
289 
290                 packagePrefixMap.put(packagePrefix, newPackagePrefix);
291             }
292         }
293 
294         return packagePrefixMap;
295     }
296 }
297