• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
21 package proguard;
22 
23 import proguard.classfile.ClassConstants;
24 import proguard.classfile.util.ClassUtil;
25 import proguard.util.ListUtil;
26 
27 import java.io.*;
28 import java.util.List;
29 
30 
31 /**
32  * This class writes ProGuard configurations to a file.
33  *
34  * @author Eric Lafortune
35  */
36 public class ConfigurationWriter
37 {
38     private static final String[] KEEP_OPTIONS = new String[]
39     {
40         ConfigurationConstants.KEEP_OPTION,
41         ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION,
42         ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION
43     };
44 
45 
46     private final PrintWriter writer;
47     private File        baseDir;
48 
49 
50     /**
51      * Creates a new ConfigurationWriter for the given file name.
52      */
ConfigurationWriter(File configurationFile)53     public ConfigurationWriter(File configurationFile) throws IOException
54     {
55         this(new PrintWriter(new FileWriter(configurationFile)));
56 
57         baseDir = configurationFile.getParentFile();
58     }
59 
60 
61     /**
62      * Creates a new ConfigurationWriter for the given OutputStream.
63      */
ConfigurationWriter(OutputStream outputStream)64     public ConfigurationWriter(OutputStream outputStream) throws IOException
65     {
66         this(new PrintWriter(outputStream));
67     }
68 
69 
70     /**
71      * Creates a new ConfigurationWriter for the given PrintWriter.
72      */
ConfigurationWriter(PrintWriter writer)73     public ConfigurationWriter(PrintWriter writer) throws IOException
74     {
75         this.writer = writer;
76     }
77 
78 
79     /**
80      * Closes this ConfigurationWriter.
81      */
close()82     public void close() throws IOException
83     {
84         writer.close();
85     }
86 
87 
88     /**
89      * Writes the given configuration.
90      * @param configuration the configuration that is to be written out.
91      * @throws IOException if an IO error occurs while writing the configuration.
92      */
write(Configuration configuration)93     public void write(Configuration configuration) throws IOException
94     {
95         // Write the program class path (input and output entries).
96         writeJarOptions(ConfigurationConstants.INJARS_OPTION,
97                         ConfigurationConstants.OUTJARS_OPTION,
98                         configuration.programJars);
99         writer.println();
100 
101         // Write the library class path (output entries only).
102         writeJarOptions(ConfigurationConstants.LIBRARYJARS_OPTION,
103                         ConfigurationConstants.LIBRARYJARS_OPTION,
104                         configuration.libraryJars);
105         writer.println();
106 
107         // Write the other options.
108         writeOption(ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION,       !configuration.skipNonPublicLibraryClasses);
109         writeOption(ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION, !configuration.skipNonPublicLibraryClassMembers);
110         writeOption(ConfigurationConstants.KEEP_DIRECTORIES_OPTION,                           configuration.keepDirectories);
111         writeOption(ConfigurationConstants.TARGET_OPTION,                                     ClassUtil.externalClassVersion(configuration.targetClassVersion));
112         writeOption(ConfigurationConstants.FORCE_PROCESSING_OPTION,                           configuration.lastModified == Long.MAX_VALUE);
113 
114         writeOption(ConfigurationConstants.DONT_SHRINK_OPTION, !configuration.shrink);
115         writeOption(ConfigurationConstants.PRINT_USAGE_OPTION, configuration.printUsage);
116 
117         writeOption(ConfigurationConstants.DONT_OPTIMIZE_OPTION,                 !configuration.optimize);
118         writeOption(ConfigurationConstants.OPTIMIZATIONS,                        configuration.optimize ? ListUtil.commaSeparatedString(configuration.optimizations) : null);
119         writeOption(ConfigurationConstants.OPTIMIZATION_PASSES,                  configuration.optimizationPasses);
120         writeOption(ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION,     configuration.allowAccessModification);
121         writeOption(ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION, configuration.mergeInterfacesAggressively);
122 
123         writeOption(ConfigurationConstants.DONT_OBFUSCATE_OPTION,                  !configuration.obfuscate);
124         writeOption(ConfigurationConstants.PRINT_MAPPING_OPTION,                   configuration.printMapping);
125         writeOption(ConfigurationConstants.APPLY_MAPPING_OPTION,                   configuration.applyMapping);
126         writeOption(ConfigurationConstants.OBFUSCATION_DICTIONARY_OPTION,          configuration.obfuscationDictionary);
127         writeOption(ConfigurationConstants.CLASS_OBFUSCATION_DICTIONARY_OPTION,    configuration.classObfuscationDictionary);
128         writeOption(ConfigurationConstants.PACKAGE_OBFUSCATION_DICTIONARY_OPTION,  configuration.packageObfuscationDictionary);
129         writeOption(ConfigurationConstants.OVERLOAD_AGGRESSIVELY_OPTION,           configuration.overloadAggressively);
130         writeOption(ConfigurationConstants.USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION,   configuration.useUniqueClassMemberNames);
131         writeOption(ConfigurationConstants.DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION, !configuration.useMixedCaseClassNames);
132         writeOption(ConfigurationConstants.KEEP_PACKAGE_NAMES_OPTION,              configuration.keepPackageNames, true);
133         writeOption(ConfigurationConstants.FLATTEN_PACKAGE_HIERARCHY_OPTION,       configuration.flattenPackageHierarchy, true);
134         writeOption(ConfigurationConstants.REPACKAGE_CLASSES_OPTION,               configuration.repackageClasses, true);
135         writeOption(ConfigurationConstants.KEEP_ATTRIBUTES_OPTION,                 configuration.keepAttributes);
136         writeOption(ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION,    configuration.newSourceFileAttribute);
137         writeOption(ConfigurationConstants.ADAPT_CLASS_STRINGS_OPTION,             configuration.adaptClassStrings, true);
138         writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION,       configuration.adaptResourceFileNames);
139         writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION,    configuration.adaptResourceFileContents);
140 
141         writeOption(ConfigurationConstants.DONT_PREVERIFY_OPTION, !configuration.preverify);
142         writeOption(ConfigurationConstants.MICRO_EDITION_OPTION,  configuration.microEdition);
143 
144         writeOption(ConfigurationConstants.VERBOSE_OPTION,             configuration.verbose);
145         writeOption(ConfigurationConstants.DONT_NOTE_OPTION,           configuration.note, true);
146         writeOption(ConfigurationConstants.DONT_WARN_OPTION,           configuration.warn, true);
147         writeOption(ConfigurationConstants.IGNORE_WARNINGS_OPTION,     configuration.ignoreWarnings);
148         writeOption(ConfigurationConstants.PRINT_CONFIGURATION_OPTION, configuration.printConfiguration);
149         writeOption(ConfigurationConstants.DUMP_OPTION,                configuration.dump);
150 
151         writeOption(ConfigurationConstants.PRINT_SEEDS_OPTION,     configuration.printSeeds);
152         writer.println();
153 
154         // Write the "why are you keeping" options.
155         writeOptions(ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION, configuration.whyAreYouKeeping);
156 
157         // Write the keep options.
158         writeOptions(KEEP_OPTIONS, configuration.keep);
159 
160         // Write the "no side effect methods" options.
161         writeOptions(ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION, configuration.assumeNoSideEffects);
162 
163         if (writer.checkError())
164         {
165             throw new IOException("Can't write configuration");
166         }
167     }
168 
169 
writeJarOptions(String inputEntryOptionName, String outputEntryOptionName, ClassPath classPath)170     private void writeJarOptions(String    inputEntryOptionName,
171                                  String    outputEntryOptionName,
172                                  ClassPath classPath)
173     {
174         if (classPath != null)
175         {
176             for (int index = 0; index < classPath.size(); index++)
177             {
178                 ClassPathEntry entry = classPath.get(index);
179                 String optionName = entry.isOutput() ?
180                      outputEntryOptionName :
181                      inputEntryOptionName;
182 
183                 writer.print(optionName);
184                 writer.print(' ');
185                 writer.print(relativeFileName(entry.getFile()));
186 
187                 // Append the filters, if any.
188                 boolean filtered = false;
189 
190                 filtered = writeFilter(filtered, entry.getZipFilter());
191                 filtered = writeFilter(filtered, entry.getEarFilter());
192                 filtered = writeFilter(filtered, entry.getWarFilter());
193                 filtered = writeFilter(filtered, entry.getJarFilter());
194                 filtered = writeFilter(filtered, entry.getFilter());
195 
196                 if (filtered)
197                 {
198                     writer.print(ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD);
199                 }
200 
201                 writer.println();
202             }
203         }
204     }
205 
206 
writeFilter(boolean filtered, List filter)207     private boolean writeFilter(boolean filtered, List filter)
208     {
209         if (filtered)
210         {
211             writer.print(ConfigurationConstants.SEPARATOR_KEYWORD);
212         }
213 
214         if (filter != null)
215         {
216             if (!filtered)
217             {
218                 writer.print(ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD);
219             }
220 
221             for (int index = 0; index < filter.size(); index++)
222             {
223                 if (index > 0)
224                 {
225                     writer.print(ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD);
226                 }
227 
228                 writer.print(quotedString((String)filter.get(index)));
229             }
230 
231             filtered = true;
232         }
233 
234         return filtered;
235     }
236 
237 
writeOption(String optionName, boolean flag)238     private void writeOption(String optionName, boolean flag)
239     {
240         if (flag)
241         {
242             writer.println(optionName);
243         }
244     }
245 
246 
writeOption(String optionName, int argument)247     private void writeOption(String optionName, int argument)
248     {
249         if (argument != 1)
250         {
251             writer.print(optionName);
252             writer.print(' ');
253             writer.println(argument);
254         }
255     }
256 
257 
writeOption(String optionName, List arguments)258     private void writeOption(String optionName, List arguments)
259     {
260         writeOption(optionName, arguments, false);
261     }
262 
263 
writeOption(String optionName, List arguments, boolean replaceInternalClassNames)264     private void writeOption(String  optionName,
265                              List    arguments,
266                              boolean replaceInternalClassNames)
267     {
268         if (arguments != null)
269         {
270             if (arguments.isEmpty())
271             {
272                 writer.println(optionName);
273             }
274             else
275             {
276                 String argumentString = ListUtil.commaSeparatedString(arguments);
277 
278                 if (replaceInternalClassNames)
279                 {
280                     argumentString = ClassUtil.externalClassName(argumentString);
281                 }
282 
283                 writer.print(optionName);
284                 writer.print(' ');
285                 writer.println(quotedString(argumentString));
286             }
287         }
288     }
289 
290 
writeOption(String optionName, String arguments)291     private void writeOption(String optionName, String arguments)
292     {
293         writeOption(optionName, arguments, false);
294     }
295 
296 
writeOption(String optionName, String arguments, boolean replaceInternalClassNames)297     private void writeOption(String  optionName,
298                              String  arguments,
299                              boolean replaceInternalClassNames)
300     {
301         if (arguments != null)
302         {
303             if (replaceInternalClassNames)
304             {
305                 arguments = ClassUtil.externalClassName(arguments);
306             }
307 
308             writer.print(optionName);
309             writer.print(' ');
310             writer.println(quotedString(arguments));
311         }
312     }
313 
314 
writeOption(String optionName, File file)315     private void writeOption(String optionName, File file)
316     {
317         if (file != null)
318         {
319             if (file.getPath().length() > 0)
320             {
321                 writer.print(optionName);
322                 writer.print(' ');
323                 writer.println(relativeFileName(file));
324             }
325             else
326             {
327                 writer.println(optionName);
328             }
329         }
330     }
331 
332 
writeOptions(String[] optionNames, List keepClassSpecifications)333     private void writeOptions(String[] optionNames,
334                               List     keepClassSpecifications)
335     {
336         if (keepClassSpecifications != null)
337         {
338             for (int index = 0; index < keepClassSpecifications.size(); index++)
339             {
340                 writeOption(optionNames, (KeepClassSpecification)keepClassSpecifications.get(index));
341             }
342         }
343     }
344 
345 
writeOption(String[] optionNames, KeepClassSpecification keepClassSpecification)346     private void writeOption(String[]               optionNames,
347                              KeepClassSpecification keepClassSpecification)
348     {
349         // Compose the option name.
350         String optionName = optionNames[keepClassSpecification.markConditionally ? 2 :
351                                         keepClassSpecification.markClasses       ? 0 :
352                                                                               1];
353 
354         if (keepClassSpecification.allowShrinking)
355         {
356             optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
357                           ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION;
358         }
359 
360         if (keepClassSpecification.allowOptimization)
361         {
362             optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
363                           ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION;
364         }
365 
366         if (keepClassSpecification.allowObfuscation)
367         {
368             optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
369                           ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION;
370         }
371 
372         // Write out the option with the proper class specification.
373         writeOption(optionName, keepClassSpecification);
374     }
375 
376 
writeOptions(String optionName, List classSpecifications)377     private void writeOptions(String optionName,
378                               List   classSpecifications)
379     {
380         if (classSpecifications != null)
381         {
382             for (int index = 0; index < classSpecifications.size(); index++)
383             {
384                 writeOption(optionName, (ClassSpecification)classSpecifications.get(index));
385             }
386         }
387     }
388 
389 
writeOption(String optionName, ClassSpecification classSpecification)390     private void writeOption(String             optionName,
391                              ClassSpecification classSpecification)
392     {
393         writer.println();
394 
395         // Write out the comments for this option.
396         writeComments(classSpecification.comments);
397 
398         writer.print(optionName);
399         writer.print(' ');
400 
401         // Write out the required annotation, if any.
402         if (classSpecification.annotationType != null)
403         {
404             writer.print(ConfigurationConstants.ANNOTATION_KEYWORD);
405             writer.print(ClassUtil.externalType(classSpecification.annotationType));
406             writer.print(' ');
407         }
408 
409         // Write out the class access flags.
410         writer.print(ClassUtil.externalClassAccessFlags(classSpecification.requiredUnsetAccessFlags,
411                                                         ConfigurationConstants.NEGATOR_KEYWORD));
412 
413         writer.print(ClassUtil.externalClassAccessFlags(classSpecification.requiredSetAccessFlags));
414 
415         // Write out the class keyword, if we didn't write the interface
416         // keyword earlier.
417         if (((classSpecification.requiredSetAccessFlags |
418               classSpecification.requiredUnsetAccessFlags) &
419              (ClassConstants.INTERNAL_ACC_INTERFACE |
420               ClassConstants.INTERNAL_ACC_ENUM)) == 0)
421         {
422             writer.print(ConfigurationConstants.CLASS_KEYWORD);
423         }
424 
425         writer.print(' ');
426 
427         // Write out the class name.
428         writer.print(classSpecification.className != null ?
429             ClassUtil.externalClassName(classSpecification.className) :
430             ConfigurationConstants.ANY_CLASS_KEYWORD);
431 
432         // Write out the extends template, if any.
433         if (classSpecification.extendsAnnotationType != null ||
434             classSpecification.extendsClassName      != null)
435         {
436             writer.print(' ');
437             writer.print(ConfigurationConstants.EXTENDS_KEYWORD);
438             writer.print(' ');
439 
440             // Write out the required extends annotation, if any.
441             if (classSpecification.extendsAnnotationType != null)
442             {
443                 writer.print(ConfigurationConstants.ANNOTATION_KEYWORD);
444                 writer.print(ClassUtil.externalType(classSpecification.extendsAnnotationType));
445                 writer.print(' ');
446             }
447 
448             // Write out the extended class name.
449             writer.print(classSpecification.extendsClassName != null ?
450                 ClassUtil.externalClassName(classSpecification.extendsClassName) :
451                 ConfigurationConstants.ANY_CLASS_KEYWORD);
452         }
453 
454         // Write out the keep field and keep method options, if any.
455         if (classSpecification.fieldSpecifications  != null ||
456             classSpecification.methodSpecifications != null)
457         {
458             writer.print(' ');
459             writer.println(ConfigurationConstants.OPEN_KEYWORD);
460 
461             writeFieldSpecification( classSpecification.fieldSpecifications);
462             writeMethodSpecification(classSpecification.methodSpecifications);
463 
464             writer.println(ConfigurationConstants.CLOSE_KEYWORD);
465         }
466         else
467         {
468             writer.println();
469         }
470     }
471 
472 
473 
writeComments(String comments)474     private void writeComments(String comments)
475     {
476         if (comments != null)
477         {
478             int index = 0;
479             while (index < comments.length())
480             {
481                 int breakIndex = comments.indexOf('\n', index);
482                 if (breakIndex < 0)
483                 {
484                     breakIndex = comments.length();
485                 }
486 
487                 writer.print('#');
488 
489                 if (comments.charAt(index) != ' ')
490                 {
491                     writer.print(' ');
492                 }
493 
494                 writer.println(comments.substring(index, breakIndex));
495 
496                 index = breakIndex + 1;
497             }
498         }
499     }
500 
501 
writeFieldSpecification(List memberSpecifications)502     private void writeFieldSpecification(List memberSpecifications)
503     {
504         if (memberSpecifications != null)
505         {
506             for (int index = 0; index < memberSpecifications.size(); index++)
507             {
508                 MemberSpecification memberSpecification =
509                     (MemberSpecification)memberSpecifications.get(index);
510 
511                 writer.print("    ");
512 
513                 // Write out the required annotation, if any.
514                 if (memberSpecification.annotationType != null)
515                 {
516                     writer.print(ConfigurationConstants.ANNOTATION_KEYWORD);
517                     writer.println(ClassUtil.externalType(memberSpecification.annotationType));
518                     writer.print("    ");
519                 }
520 
521                 // Write out the field access flags.
522                 writer.print(ClassUtil.externalFieldAccessFlags(memberSpecification.requiredUnsetAccessFlags,
523                                                                 ConfigurationConstants.NEGATOR_KEYWORD));
524 
525                 writer.print(ClassUtil.externalFieldAccessFlags(memberSpecification.requiredSetAccessFlags));
526 
527                 // Write out the field name and descriptor.
528                 String name       = memberSpecification.name;
529                 String descriptor = memberSpecification.descriptor;
530 
531                 writer.print(descriptor == null ? name == null ?
532                     ConfigurationConstants.ANY_FIELD_KEYWORD             :
533                     ConfigurationConstants.ANY_TYPE_KEYWORD + ' ' + name :
534                     ClassUtil.externalFullFieldDescription(0,
535                                                            name == null ? ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD : name,
536                                                            descriptor));
537 
538                 writer.println(ConfigurationConstants.SEPARATOR_KEYWORD);
539             }
540         }
541     }
542 
543 
writeMethodSpecification(List memberSpecifications)544     private void writeMethodSpecification(List memberSpecifications)
545     {
546         if (memberSpecifications != null)
547         {
548             for (int index = 0; index < memberSpecifications.size(); index++)
549             {
550                 MemberSpecification memberSpecification =
551                     (MemberSpecification)memberSpecifications.get(index);
552 
553                 writer.print("    ");
554 
555                 // Write out the required annotation, if any.
556                 if (memberSpecification.annotationType != null)
557                 {
558                     writer.print(ConfigurationConstants.ANNOTATION_KEYWORD);
559                     writer.println(ClassUtil.externalType(memberSpecification.annotationType));
560                     writer.print("    ");
561                 }
562 
563                 // Write out the method access flags.
564                 writer.print(ClassUtil.externalMethodAccessFlags(memberSpecification.requiredUnsetAccessFlags,
565                                                                  ConfigurationConstants.NEGATOR_KEYWORD));
566 
567                 writer.print(ClassUtil.externalMethodAccessFlags(memberSpecification.requiredSetAccessFlags));
568 
569                 // Write out the method name and descriptor.
570                 String name       = memberSpecification.name;
571                 String descriptor = memberSpecification.descriptor;
572 
573                 writer.print(descriptor == null ? name == null ?
574                     ConfigurationConstants.ANY_METHOD_KEYWORD :
575                     ConfigurationConstants.ANY_TYPE_KEYWORD + ' ' + name + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD + ConfigurationConstants.ANY_ARGUMENTS_KEYWORD + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD :
576                     ClassUtil.externalFullMethodDescription(ClassConstants.INTERNAL_METHOD_NAME_INIT,
577                                                             0,
578                                                             name == null ? ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD : name,
579                                                             descriptor));
580 
581                 writer.println(ConfigurationConstants.SEPARATOR_KEYWORD);
582             }
583         }
584     }
585 
586 
587     /**
588      * Returns a relative file name of the given file, if possible.
589      * The file name is also quoted, if necessary.
590      */
relativeFileName(File file)591     private String relativeFileName(File file)
592     {
593         String fileName = file.getAbsolutePath();
594 
595         // See if we can convert the file name into a relative file name.
596         if (baseDir != null)
597         {
598             String baseDirName = baseDir.getAbsolutePath() + File.separator;
599             if (fileName.startsWith(baseDirName))
600             {
601                 fileName = fileName.substring(baseDirName.length());
602             }
603         }
604 
605         return quotedString(fileName);
606     }
607 
608 
609     /**
610      * Returns a quoted version of the given string, if necessary.
611      */
quotedString(String string)612     private String quotedString(String string)
613     {
614         return string.length()     == 0 ||
615                string.indexOf(' ') >= 0 ||
616                string.indexOf('@') >= 0 ||
617                string.indexOf('{') >= 0 ||
618                string.indexOf('}') >= 0 ||
619                string.indexOf('(') >= 0 ||
620                string.indexOf(')') >= 0 ||
621                string.indexOf(':') >= 0 ||
622                string.indexOf(';') >= 0 ||
623                string.indexOf(',') >= 0  ? ("'" + string + "'") :
624                                            (      string      );
625     }
626 
627 
628     /**
629      * A main method for testing configuration writing.
630      */
main(String[] args)631     public static void main(String[] args) {
632         try
633         {
634             ConfigurationWriter writer = new ConfigurationWriter(new File(args[0]));
635 
636             writer.write(new Configuration());
637         }
638         catch (Exception ex)
639         {
640             ex.printStackTrace();
641         }
642     }
643 }
644