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