Home | History | Annotate | Download | only in proguard
      1 /*
      2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
      3  *             of Java bytecode.
      4  *
      5  * Copyright (c) 2002-2009 Eric Lafortune (eric (at) 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      */
     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