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