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.ClassConstants;
24import proguard.classfile.util.ClassUtil;
25import proguard.util.ListUtil;
26
27import java.io.*;
28import java.util.List;
29
30
31/**
32 * This class writes ProGuard configurations to a file.
33 *
34 * @author Eric Lafortune
35 */
36public 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     */
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     */
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     */
73    public ConfigurationWriter(PrintWriter writer) throws IOException
74    {
75        this.writer = writer;
76    }
77
78
79    /**
80     * Closes this ConfigurationWriter.
81     */
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     */
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
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
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
238    private void writeOption(String optionName, boolean flag)
239    {
240        if (flag)
241        {
242            writer.println(optionName);
243        }
244    }
245
246
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
258    private void writeOption(String optionName, List arguments)
259    {
260        writeOption(optionName, arguments, false);
261    }
262
263
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
291    private void writeOption(String optionName, String arguments)
292    {
293        writeOption(optionName, arguments, false);
294    }
295
296
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
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
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
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
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
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
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
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
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     */
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     */
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     */
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