/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2011 Eric Lafortune (eric@graphics.cornell.edu) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard; import proguard.classfile.ClassConstants; import proguard.classfile.util.ClassUtil; import proguard.util.ListUtil; import java.io.*; import java.net.URL; import java.util.*; /** * This class parses ProGuard configurations. Configurations can be read from an * array of arguments or from a configuration file or URL. * * @author Eric Lafortune */ public class ConfigurationParser { private WordReader reader; private String nextWord; private String lastComments; /** * Creates a new ConfigurationParser for the given String arguments. */ public ConfigurationParser(String[] args) throws IOException { this(args, null); } /** * Creates a new ConfigurationParser for the given String arguments, * with the given base directory. */ public ConfigurationParser(String[] args, File baseDir) throws IOException { this(new ArgumentWordReader(args, baseDir)); } /** * Creates a new ConfigurationParser for the given lines, * with the given base directory. */ public ConfigurationParser(String lines, String description, File baseDir) throws IOException { this(new LineWordReader(new LineNumberReader(new StringReader(lines)), description, baseDir)); } /** * Creates a new ConfigurationParser for the given file. */ public ConfigurationParser(File file) throws IOException { this(new FileWordReader(file)); } /** * Creates a new ConfigurationParser for the given URL. */ public ConfigurationParser(URL url) throws IOException { this(new FileWordReader(url)); } /** * Creates a new ConfigurationParser for the given word reader. */ public ConfigurationParser(WordReader reader) throws IOException { this.reader = reader; readNextWord(); } /** * Parses and returns the configuration. * @param configuration the configuration that is updated as a side-effect. * @throws ParseException if the any of the configuration settings contains * a syntax error. * @throws IOException if an IO error occurs while reading a configuration. */ public void parse(Configuration configuration) throws ParseException, IOException { while (nextWord != null) { lastComments = reader.lastComments(); // First include directives. if (ConfigurationConstants.AT_DIRECTIVE .startsWith(nextWord) || ConfigurationConstants.INCLUDE_DIRECTIVE .startsWith(nextWord)) configuration.lastModified = parseIncludeArgument(configuration.lastModified); else if (ConfigurationConstants.BASE_DIRECTORY_DIRECTIVE .startsWith(nextWord)) parseBaseDirectoryArgument(); // Then configuration options with or without arguments. else if (ConfigurationConstants.INJARS_OPTION .startsWith(nextWord)) configuration.programJars = parseClassPathArgument(configuration.programJars, false); else if (ConfigurationConstants.OUTJARS_OPTION .startsWith(nextWord)) configuration.programJars = parseClassPathArgument(configuration.programJars, true); else if (ConfigurationConstants.LIBRARYJARS_OPTION .startsWith(nextWord)) configuration.libraryJars = parseClassPathArgument(configuration.libraryJars, false); else if (ConfigurationConstants.RESOURCEJARS_OPTION .startsWith(nextWord)) throw new ParseException("The '-resourcejars' option is no longer supported. Please use the '-injars' option for all input"); else if (ConfigurationConstants.SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION .startsWith(nextWord)) configuration.skipNonPublicLibraryClasses = parseNoArgument(true); else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION .startsWith(nextWord)) configuration.skipNonPublicLibraryClasses = parseNoArgument(false); else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION.startsWith(nextWord)) configuration.skipNonPublicLibraryClassMembers = parseNoArgument(false); else if (ConfigurationConstants.TARGET_OPTION .startsWith(nextWord)) configuration.targetClassVersion = parseClassVersion(); else if (ConfigurationConstants.FORCE_PROCESSING_OPTION .startsWith(nextWord)) configuration.lastModified = parseNoArgument(Long.MAX_VALUE); else if (ConfigurationConstants.KEEP_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, true, false, false); else if (ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, false, false, false); else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, false, true, false); else if (ConfigurationConstants.KEEP_NAMES_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, true, false, true); else if (ConfigurationConstants.KEEP_CLASS_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, false, false, true); else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, false, true, true); else if (ConfigurationConstants.PRINT_SEEDS_OPTION .startsWith(nextWord)) configuration.printSeeds = parseOptionalFile(); // After '-keep'. else if (ConfigurationConstants.KEEP_DIRECTORIES_OPTION .startsWith(nextWord)) configuration.keepDirectories = parseCommaSeparatedList("directory name", true, true, false, true, false, true, false, false, configuration.keepDirectories); else if (ConfigurationConstants.DONT_SHRINK_OPTION .startsWith(nextWord)) configuration.shrink = parseNoArgument(false); else if (ConfigurationConstants.PRINT_USAGE_OPTION .startsWith(nextWord)) configuration.printUsage = parseOptionalFile(); else if (ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION .startsWith(nextWord)) configuration.whyAreYouKeeping = parseClassSpecificationArguments(configuration.whyAreYouKeeping); else if (ConfigurationConstants.DONT_OPTIMIZE_OPTION .startsWith(nextWord)) configuration.optimize = parseNoArgument(false); else if (ConfigurationConstants.OPTIMIZATION_PASSES .startsWith(nextWord)) configuration.optimizationPasses = parseIntegerArgument(); else if (ConfigurationConstants.OPTIMIZATIONS .startsWith(nextWord)) configuration.optimizations = parseCommaSeparatedList("optimization name", true, false, false, false, false, false, false, false, configuration.optimizations); else if (ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION .startsWith(nextWord)) configuration.assumeNoSideEffects = parseClassSpecificationArguments(configuration.assumeNoSideEffects); else if (ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION .startsWith(nextWord)) configuration.allowAccessModification = parseNoArgument(true); else if (ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION .startsWith(nextWord)) configuration.mergeInterfacesAggressively = parseNoArgument(true); else if (ConfigurationConstants.DONT_OBFUSCATE_OPTION .startsWith(nextWord)) configuration.obfuscate = parseNoArgument(false); else if (ConfigurationConstants.PRINT_MAPPING_OPTION .startsWith(nextWord)) configuration.printMapping = parseOptionalFile(); else if (ConfigurationConstants.APPLY_MAPPING_OPTION .startsWith(nextWord)) configuration.applyMapping = parseFile(); else if (ConfigurationConstants.OBFUSCATION_DICTIONARY_OPTION .startsWith(nextWord)) configuration.obfuscationDictionary = parseFile(); else if (ConfigurationConstants.CLASS_OBFUSCATION_DICTIONARY_OPTION .startsWith(nextWord)) configuration.classObfuscationDictionary = parseFile(); else if (ConfigurationConstants.PACKAGE_OBFUSCATION_DICTIONARY_OPTION .startsWith(nextWord)) configuration.packageObfuscationDictionary = parseFile(); else if (ConfigurationConstants.OVERLOAD_AGGRESSIVELY_OPTION .startsWith(nextWord)) configuration.overloadAggressively = parseNoArgument(true); else if (ConfigurationConstants.USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.useUniqueClassMemberNames = parseNoArgument(true); else if (ConfigurationConstants.DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION .startsWith(nextWord)) configuration.useMixedCaseClassNames = parseNoArgument(false); else if (ConfigurationConstants.KEEP_PACKAGE_NAMES_OPTION .startsWith(nextWord)) configuration.keepPackageNames = parseCommaSeparatedList("package name", true, true, false, false, true, false, true, false, configuration.keepPackageNames); else if (ConfigurationConstants.FLATTEN_PACKAGE_HIERARCHY_OPTION .startsWith(nextWord)) configuration.flattenPackageHierarchy = ClassUtil.internalClassName(parseOptionalArgument()); else if (ConfigurationConstants.REPACKAGE_CLASSES_OPTION .startsWith(nextWord)) configuration.repackageClasses = ClassUtil.internalClassName(parseOptionalArgument()); else if (ConfigurationConstants.DEFAULT_PACKAGE_OPTION .startsWith(nextWord)) configuration.repackageClasses = ClassUtil.internalClassName(parseOptionalArgument()); else if (ConfigurationConstants.KEEP_ATTRIBUTES_OPTION .startsWith(nextWord)) configuration.keepAttributes = parseCommaSeparatedList("attribute name", true, true, false, false, true, false, false, false, configuration.keepAttributes); else if (ConfigurationConstants.KEEP_PARAMETER_NAMES_OPTION .startsWith(nextWord)) configuration.keepParameterNames = parseNoArgument(true); else if (ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION .startsWith(nextWord)) configuration.newSourceFileAttribute = parseOptionalArgument(); else if (ConfigurationConstants.ADAPT_CLASS_STRINGS_OPTION .startsWith(nextWord)) configuration.adaptClassStrings = parseCommaSeparatedList("class name", true, true, false, false, true, false, true, false, configuration.adaptClassStrings); else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION .startsWith(nextWord)) configuration.adaptResourceFileNames = parseCommaSeparatedList("resource file name", true, true, false, true, false, false, false, false, configuration.adaptResourceFileNames); else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION .startsWith(nextWord)) configuration.adaptResourceFileContents = parseCommaSeparatedList("resource file name", true, true, false, true, false, false, false, false, configuration.adaptResourceFileContents); else if (ConfigurationConstants.DONT_PREVERIFY_OPTION .startsWith(nextWord)) configuration.preverify = parseNoArgument(false); else if (ConfigurationConstants.MICRO_EDITION_OPTION .startsWith(nextWord)) configuration.microEdition = parseNoArgument(true); else if (ConfigurationConstants.VERBOSE_OPTION .startsWith(nextWord)) configuration.verbose = parseNoArgument(true); else if (ConfigurationConstants.DONT_NOTE_OPTION .startsWith(nextWord)) configuration.note = parseCommaSeparatedList("class name", true, true, false, false, true, false, true, false, configuration.note); else if (ConfigurationConstants.DONT_WARN_OPTION .startsWith(nextWord)) configuration.warn = parseCommaSeparatedList("class name", true, true, false, false, true, false, true, false, configuration.warn); else if (ConfigurationConstants.IGNORE_WARNINGS_OPTION .startsWith(nextWord)) configuration.ignoreWarnings = parseNoArgument(true); else if (ConfigurationConstants.PRINT_CONFIGURATION_OPTION .startsWith(nextWord)) configuration.printConfiguration = parseOptionalFile(); else if (ConfigurationConstants.DUMP_OPTION .startsWith(nextWord)) configuration.dump = parseOptionalFile(); else { throw new ParseException("Unknown option " + reader.locationDescription()); } } } /** * Closes the configuration. * @throws IOException if an IO error occurs while closing the configuration. */ public void close() throws IOException { if (reader != null) { reader.close(); } } private long parseIncludeArgument(long lastModified) throws ParseException, IOException { // Read the configuation file name. readNextWord("configuration file name", true, false); File file = file(nextWord); reader.includeWordReader(new FileWordReader(file)); readNextWord(); return Math.max(lastModified, file.lastModified()); } private void parseBaseDirectoryArgument() throws ParseException, IOException { // Read the base directory name. readNextWord("base directory name", true, false); reader.setBaseDir(file(nextWord)); readNextWord(); } private ClassPath parseClassPathArgument(ClassPath classPath, boolean isOutput) throws ParseException, IOException { // Create a new List if necessary. if (classPath == null) { classPath = new ClassPath(); } while (true) { // Read the next jar name. readNextWord("jar or directory name", true, false); // Create a new class path entry. ClassPathEntry entry = new ClassPathEntry(file(nextWord), isOutput); // Read the opening parenthesis or the separator, if any. readNextWord(); // Read the optional filters. if (!configurationEnd() && ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(nextWord)) { // Read all filters in an array. List[] filters = new List[5]; int counter = 0; do { // Read the filter. filters[counter++] = parseCommaSeparatedList("filter", true, false, true, true, false, true, false, false, null); } while (counter < filters.length && ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)); // Make sure there is a closing parenthesis. if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord)) { throw new ParseException("Expecting separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + "' or '" + ConfigurationConstants.SEPARATOR_KEYWORD + "', or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD + "' before " + reader.locationDescription()); } // Set all filters from the array on the entry. entry.setFilter(filters[--counter]); if (counter > 0) { entry.setJarFilter(filters[--counter]); if (counter > 0) { entry.setWarFilter(filters[--counter]); if (counter > 0) { entry.setEarFilter(filters[--counter]); if (counter > 0) { entry.setZipFilter(filters[--counter]); } } } } // Read the separator, if any. readNextWord(); } // Add the entry to the list. classPath.add(entry); if (configurationEnd()) { return classPath; } if (!nextWord.equals(ConfigurationConstants.JAR_SEPARATOR_KEYWORD)) { throw new ParseException("Expecting class path separator '" + ConfigurationConstants.JAR_SEPARATOR_KEYWORD + "' before " + reader.locationDescription()); } } } private int parseClassVersion() throws ParseException, IOException { // Read the obligatory target. readNextWord("java version"); int classVersion = ClassUtil.internalClassVersion(nextWord); if (classVersion == 0) { throw new ParseException("Unsupported java version " + reader.locationDescription()); } readNextWord(); return classVersion; } private int parseIntegerArgument() throws ParseException, IOException { try { // Read the obligatory integer. readNextWord("integer"); int integer = Integer.parseInt(nextWord); readNextWord(); return integer; } catch (NumberFormatException e) { throw new ParseException("Expecting integer argument instead of '" + nextWord + "' before " + reader.locationDescription()); } } private File parseFile() throws ParseException, IOException { // Read the obligatory file name. readNextWord("file name", true, false); // Make sure the file is properly resolved. File file = file(nextWord); readNextWord(); return file; } private File parseOptionalFile() throws ParseException, IOException { // Read the optional file name. readNextWord(true); // Didn't the user specify a file name? if (configurationEnd()) { return new File(""); } // Make sure the file is properly resolved. File file = file(nextWord); readNextWord(); return file; } private String parseOptionalArgument() throws IOException { // Read the optional argument. readNextWord(); // Didn't the user specify an argument? if (configurationEnd()) { return ""; } String argument = nextWord; readNextWord(); return argument; } private boolean parseNoArgument(boolean value) throws IOException { readNextWord(); return value; } private long parseNoArgument(long value) throws IOException { readNextWord(); return value; } private List parseKeepClassSpecificationArguments(List keepClassSpecifications, boolean markClasses, boolean markConditionally, boolean allowShrinking) throws ParseException, IOException { // Create a new List if necessary. if (keepClassSpecifications == null) { keepClassSpecifications = new ArrayList(); } //boolean allowShrinking = false; boolean allowOptimization = false; boolean allowObfuscation = false; // Read the keep modifiers. while (true) { readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD + "', '" + ClassConstants.EXTERNAL_ACC_INTERFACE + "', or '" + ClassConstants.EXTERNAL_ACC_ENUM + "'", false, true); if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord)) { // Not a comma. Stop parsing the keep modifiers. break; } readNextWord("keyword '" + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION + "', '" + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION + "', or '" + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION + "'"); if (ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION .startsWith(nextWord)) { allowShrinking = true; } else if (ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION.startsWith(nextWord)) { allowOptimization = true; } else if (ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION .startsWith(nextWord)) { allowObfuscation = true; } else { throw new ParseException("Expecting keyword '" + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION + "', '" + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION + "', or '" + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION + "' before " + reader.locationDescription()); } } // Read the class configuration. ClassSpecification classSpecification = parseClassSpecificationArguments(); // Create and add the keep configuration. keepClassSpecifications.add(new KeepClassSpecification(markClasses, markConditionally, allowShrinking, allowOptimization, allowObfuscation, classSpecification)); return keepClassSpecifications; } private List parseClassSpecificationArguments(List classSpecifications) throws ParseException, IOException { // Create a new List if necessary. if (classSpecifications == null) { classSpecifications = new ArrayList(); } // Read and add the class configuration. readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD + "', '" + ClassConstants.EXTERNAL_ACC_INTERFACE + "', or '" + ClassConstants.EXTERNAL_ACC_ENUM + "'", false, true); classSpecifications.add(parseClassSpecificationArguments()); return classSpecifications; } private ClassSpecification parseClassSpecificationArguments() throws ParseException, IOException { // Clear the annotation type. String annotationType = null; // Clear the class access modifiers. int requiredSetClassAccessFlags = 0; int requiredUnsetClassAccessFlags = 0; // Parse the class annotations and access modifiers until the class keyword. while (!ConfigurationConstants.CLASS_KEYWORD.equals(nextWord)) { // Strip the negating sign, if any. boolean negated = nextWord.startsWith(ConfigurationConstants.NEGATOR_KEYWORD); String strippedWord = negated ? nextWord.substring(1) : nextWord; // Parse the class access modifiers. int accessFlag = strippedWord.equals(ClassConstants.EXTERNAL_ACC_PUBLIC) ? ClassConstants.INTERNAL_ACC_PUBLIC : strippedWord.equals(ClassConstants.EXTERNAL_ACC_FINAL) ? ClassConstants.INTERNAL_ACC_FINAL : strippedWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE) ? ClassConstants.INTERNAL_ACC_INTERFACE : strippedWord.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT) ? ClassConstants.INTERNAL_ACC_ABSTRACT : strippedWord.equals(ClassConstants.EXTERNAL_ACC_SYNTHETIC) ? ClassConstants.INTERNAL_ACC_SYNTHETIC : strippedWord.equals(ClassConstants.EXTERNAL_ACC_ANNOTATION) ? ClassConstants.INTERNAL_ACC_ANNOTATTION : strippedWord.equals(ClassConstants.EXTERNAL_ACC_ENUM) ? ClassConstants.INTERNAL_ACC_ENUM : unknownAccessFlag(); // Is it an annotation modifier? if (accessFlag == ClassConstants.INTERNAL_ACC_ANNOTATTION) { // Already read the next word. readNextWord("annotation type or keyword '" + ClassConstants.EXTERNAL_ACC_INTERFACE + "'", false, false); // Is the next word actually an annotation type? if (!nextWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE) && !nextWord.equals(ClassConstants.EXTERNAL_ACC_ENUM) && !nextWord.equals(ConfigurationConstants.CLASS_KEYWORD)) { // Parse the annotation type. annotationType = ListUtil.commaSeparatedString( parseCommaSeparatedList("annotation type", false, false, false, false, true, false, false, true, null), false); // Continue parsing the access modifier that we just read // in the next cycle. continue; } // Otherwise just handle the annotation modifier. } if (!negated) { requiredSetClassAccessFlags |= accessFlag; } else { requiredUnsetClassAccessFlags |= accessFlag; } if ((requiredSetClassAccessFlags & requiredUnsetClassAccessFlags) != 0) { throw new ParseException("Conflicting class access modifiers for '" + strippedWord + "' before " + reader.locationDescription()); } if (strippedWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE) || strippedWord.equals(ClassConstants.EXTERNAL_ACC_ENUM) || strippedWord.equals(ConfigurationConstants.CLASS_KEYWORD)) { // The interface or enum keyword. Stop parsing the class flags. break; } // Should we read the next word? if (accessFlag != ClassConstants.INTERNAL_ACC_ANNOTATTION) { readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD + "', '" + ClassConstants.EXTERNAL_ACC_INTERFACE + "', or '" + ClassConstants.EXTERNAL_ACC_ENUM + "'", false, true); } } // Parse the class name part. String externalClassName = ListUtil.commaSeparatedString( parseCommaSeparatedList("class name or interface name", true, false, false, false, true, false, false, false, null), false); // For backward compatibility, allow a single "*" wildcard to match any // class. String className = ConfigurationConstants.ANY_CLASS_KEYWORD.equals(externalClassName) ? null : ClassUtil.internalClassName(externalClassName); // Clear the annotation type and the class name of the extends part. String extendsAnnotationType = null; String extendsClassName = null; if (!configurationEnd()) { // Parse 'implements ...' or 'extends ...' part, if any. if (ConfigurationConstants.IMPLEMENTS_KEYWORD.equals(nextWord) || ConfigurationConstants.EXTENDS_KEYWORD.equals(nextWord)) { readNextWord("class name or interface name", false, true); // Parse the annotation type, if any. if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord)) { extendsAnnotationType = ListUtil.commaSeparatedString( parseCommaSeparatedList("annotation type", true, false, false, false, true, false, false, true, null), false); } String externalExtendsClassName = ListUtil.commaSeparatedString( parseCommaSeparatedList("class name or interface name", false, false, false, false, true, false, false, false, null), false); extendsClassName = ConfigurationConstants.ANY_CLASS_KEYWORD.equals(externalExtendsClassName) ? null : ClassUtil.internalClassName(externalExtendsClassName); } } // Create the basic class specification. ClassSpecification classSpecification = new ClassSpecification(lastComments, requiredSetClassAccessFlags, requiredUnsetClassAccessFlags, annotationType, className, extendsAnnotationType, extendsClassName); // Now add any class members to this class specification. if (!configurationEnd()) { // Check the class member opening part. if (!ConfigurationConstants.OPEN_KEYWORD.equals(nextWord)) { throw new ParseException("Expecting opening '" + ConfigurationConstants.OPEN_KEYWORD + "' at " + reader.locationDescription()); } // Parse all class members. while (true) { readNextWord("class member description" + " or closing '" + ConfigurationConstants.CLOSE_KEYWORD + "'", false, true); if (nextWord.equals(ConfigurationConstants.CLOSE_KEYWORD)) { // The closing brace. Stop parsing the class members. readNextWord(); break; } parseMemberSpecificationArguments(externalClassName, classSpecification); } } return classSpecification; } private void parseMemberSpecificationArguments(String externalClassName, ClassSpecification classSpecification) throws ParseException, IOException { // Clear the annotation name. String annotationType = null; // Parse the class member access modifiers, if any. int requiredSetMemberAccessFlags = 0; int requiredUnsetMemberAccessFlags = 0; while (!configurationEnd(true)) { // Parse the annotation type, if any. if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord)) { annotationType = ListUtil.commaSeparatedString( parseCommaSeparatedList("annotation type", true, false, false, false, true, false, false, true, null), false); continue; } String strippedWord = nextWord.startsWith("!") ? nextWord.substring(1) : nextWord; // Parse the class member access modifiers. int accessFlag = strippedWord.equals(ClassConstants.EXTERNAL_ACC_PUBLIC) ? ClassConstants.INTERNAL_ACC_PUBLIC : strippedWord.equals(ClassConstants.EXTERNAL_ACC_PRIVATE) ? ClassConstants.INTERNAL_ACC_PRIVATE : strippedWord.equals(ClassConstants.EXTERNAL_ACC_PROTECTED) ? ClassConstants.INTERNAL_ACC_PROTECTED : strippedWord.equals(ClassConstants.EXTERNAL_ACC_STATIC) ? ClassConstants.INTERNAL_ACC_STATIC : strippedWord.equals(ClassConstants.EXTERNAL_ACC_FINAL) ? ClassConstants.INTERNAL_ACC_FINAL : strippedWord.equals(ClassConstants.EXTERNAL_ACC_SYNCHRONIZED) ? ClassConstants.INTERNAL_ACC_SYNCHRONIZED : strippedWord.equals(ClassConstants.EXTERNAL_ACC_VOLATILE) ? ClassConstants.INTERNAL_ACC_VOLATILE : strippedWord.equals(ClassConstants.EXTERNAL_ACC_TRANSIENT) ? ClassConstants.INTERNAL_ACC_TRANSIENT : strippedWord.equals(ClassConstants.EXTERNAL_ACC_BRIDGE) ? ClassConstants.INTERNAL_ACC_BRIDGE : strippedWord.equals(ClassConstants.EXTERNAL_ACC_VARARGS) ? ClassConstants.INTERNAL_ACC_VARARGS : strippedWord.equals(ClassConstants.EXTERNAL_ACC_NATIVE) ? ClassConstants.INTERNAL_ACC_NATIVE : strippedWord.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT) ? ClassConstants.INTERNAL_ACC_ABSTRACT : strippedWord.equals(ClassConstants.EXTERNAL_ACC_STRICT) ? ClassConstants.INTERNAL_ACC_STRICT : strippedWord.equals(ClassConstants.EXTERNAL_ACC_SYNTHETIC) ? ClassConstants.INTERNAL_ACC_SYNTHETIC : 0; if (accessFlag == 0) { // Not a class member access modifier. Stop parsing them. break; } if (strippedWord.equals(nextWord)) { requiredSetMemberAccessFlags |= accessFlag; } else { requiredUnsetMemberAccessFlags |= accessFlag; } // Make sure the user doesn't try to set and unset the same // access flags simultaneously. if ((requiredSetMemberAccessFlags & requiredUnsetMemberAccessFlags) != 0) { throw new ParseException("Conflicting class member access modifiers for " + reader.locationDescription()); } readNextWord("class member description"); } // Parse the class member type and name part. // Did we get a special wildcard? if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord) || ConfigurationConstants.ANY_FIELD_KEYWORD .equals(nextWord) || ConfigurationConstants.ANY_METHOD_KEYWORD .equals(nextWord)) { // Act according to the type of wildcard.. if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord)) { checkFieldAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); checkMethodAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); classSpecification.addField( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, null, null)); classSpecification.addMethod( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, null, null)); } else if (ConfigurationConstants.ANY_FIELD_KEYWORD.equals(nextWord)) { checkFieldAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); classSpecification.addField( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, null, null)); } else if (ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord)) { checkMethodAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); classSpecification.addMethod( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, null, null)); } // We still have to read the closing separator. readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'"); if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) { throw new ParseException("Expecting separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "' before " + reader.locationDescription()); } } else { // Make sure we have a proper type. checkJavaIdentifier("java type"); String type = nextWord; readNextWord("class member name"); String name = nextWord; // Did we get just one word before the opening parenthesis? if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(name)) { // This must be a constructor then. // Make sure the type is a proper constructor name. if (!(type.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) || type.equals(externalClassName) || type.equals(ClassUtil.externalShortClassName(externalClassName)))) { throw new ParseException("Expecting type and name " + "instead of just '" + type + "' before " + reader.locationDescription()); } // Assign the fixed constructor type and name. type = ClassConstants.EXTERNAL_TYPE_VOID; name = ClassConstants.INTERNAL_METHOD_NAME_INIT; } else { // It's not a constructor. // Make sure we have a proper name. checkJavaIdentifier("class member name"); // Read the opening parenthesis or the separating // semi-colon. readNextWord("opening '" + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD + "' or separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'"); } // Are we looking at a field, a method, or something else? if (ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) { // It's a field. checkFieldAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); // We already have a field descriptor. String descriptor = ClassUtil.internalType(type); // Add the field. classSpecification.addField( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, name, descriptor)); } else if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(nextWord)) { // It's a method. checkMethodAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); // Parse the method arguments. String descriptor = ClassUtil.internalMethodDescriptor(type, parseCommaSeparatedList("argument", true, true, true, false, true, false, false, false, null)); if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord)) { throw new ParseException("Expecting separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + "' or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD + "' before " + reader.locationDescription()); } // Read the separator after the closing parenthesis. readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'"); if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) { throw new ParseException("Expecting separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "' before " + reader.locationDescription()); } // Add the method. classSpecification.addMethod( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, name, descriptor)); } else { // It doesn't look like a field or a method. throw new ParseException("Expecting opening '" + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD + "' or separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "' before " + reader.locationDescription()); } } } /** * Reads a comma-separated list of java identifiers or of file names. If an * empty list is allowed, the reading will end after a closing parenthesis * or semi-colon. */ private List parseCommaSeparatedList(String expectedDescription, boolean readFirstWord, boolean allowEmptyList, boolean expectClosingParenthesis, boolean isFileName, boolean checkJavaIdentifiers, boolean replaceSystemProperties, boolean replaceExternalClassNames, boolean replaceExternalTypes, List list) throws ParseException, IOException { if (list == null) { list = new ArrayList(); } if (readFirstWord) { if (expectClosingParenthesis || !allowEmptyList) { // Read the first list entry. readNextWord(expectedDescription, isFileName, false); } else { // Read the first list entry, if there is any. readNextWord(isFileName); // Check if the list is empty. if (configurationEnd() || nextWord.equals(ConfigurationConstants.ANY_ATTRIBUTE_KEYWORD)) { return list; } } } while (true) { if (expectClosingParenthesis && list.size() == 0 && (ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord) || ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord))) { break; } if (checkJavaIdentifiers) { checkJavaIdentifier("java type"); } if (replaceSystemProperties) { nextWord = replaceSystemProperties(nextWord); } if (replaceExternalClassNames) { nextWord = ClassUtil.internalClassName(nextWord); } if (replaceExternalTypes) { nextWord = ClassUtil.internalType(nextWord); } list.add(nextWord); if (expectClosingParenthesis) { // Read a comma (or a closing parenthesis, or a different word). readNextWord("separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + "' or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD + "'"); } else { // Read a comma (or a different word). readNextWord(); } if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord)) { break; } // Read the next list entry. readNextWord(expectedDescription, isFileName, false); } return list; } /** * Throws a ParseException for an unexpected keyword. */ private int unknownAccessFlag() throws ParseException { throw new ParseException("Unexpected keyword " + reader.locationDescription()); } /** * Creates a properly resolved File, based on the given word. */ private File file(String word) throws ParseException { String fileName = replaceSystemProperties(word); File file = new File(fileName); // Try to get an absolute file. if (!file.isAbsolute()) { file = new File(reader.getBaseDir(), fileName); } return file; } /** * Replaces any system properties in the given word by their values * (e.g. the substring "<java.home>" is replaced by its value). */ private String replaceSystemProperties(String word) throws ParseException { int fromIndex = 0; while (true) { fromIndex = word.indexOf(ConfigurationConstants.OPEN_SYSTEM_PROPERTY, fromIndex); if (fromIndex < 0) { break; } int toIndex = word.indexOf(ConfigurationConstants.CLOSE_SYSTEM_PROPERTY, fromIndex+1); if (toIndex < 0) { throw new ParseException("Expecting closing '" + ConfigurationConstants.CLOSE_SYSTEM_PROPERTY + "' after opening '" + ConfigurationConstants.OPEN_SYSTEM_PROPERTY + "' in " + reader.locationDescription()); } String propertyName = word.substring(fromIndex+1, toIndex); String propertyValue = System.getProperty(propertyName); if (propertyValue == null) { throw new ParseException("Value of system property '" + propertyName + "' is undefined in " + reader.locationDescription()); } word = word.substring(0, fromIndex) + propertyValue + word.substring(toIndex+1); } return word; } /** * Reads the next word of the configuration in the 'nextWord' field, * throwing an exception if there is no next word. */ private void readNextWord(String expectedDescription) throws ParseException, IOException { readNextWord(expectedDescription, false, false); } /** * Reads the next word of the configuration in the 'nextWord' field, * throwing an exception if there is no next word. */ private void readNextWord(String expectedDescription, boolean isFileName, boolean expectingAtCharacter) throws ParseException, IOException { readNextWord(isFileName); if (configurationEnd(expectingAtCharacter)) { throw new ParseException("Expecting " + expectedDescription + " before " + reader.locationDescription()); } } /** * Reads the next word of the configuration in the 'nextWord' field. */ private void readNextWord() throws IOException { readNextWord(false); } /** * Reads the next word of the configuration in the 'nextWord' field. */ private void readNextWord(boolean isFileName) throws IOException { nextWord = reader.nextWord(isFileName); } /** * Returns whether the end of the configuration has been reached. */ private boolean configurationEnd() { return configurationEnd(false); } /** * Returns whether the end of the configuration has been reached. */ private boolean configurationEnd(boolean expectingAtCharacter) { return nextWord == null || nextWord.startsWith(ConfigurationConstants.OPTION_PREFIX) || (!expectingAtCharacter && nextWord.equals(ConfigurationConstants.AT_DIRECTIVE)); } /** * Checks whether the given word is a valid Java identifier and throws * a ParseException if it isn't. Wildcard characters are accepted. */ private void checkJavaIdentifier(String expectedDescription) throws ParseException { if (!isJavaIdentifier(nextWord)) { throw new ParseException("Expecting " + expectedDescription + " before " + reader.locationDescription()); } } /** * Returns whether the given word is a valid Java identifier. * Wildcard characters are accepted. */ private boolean isJavaIdentifier(String aWord) { for (int index = 0; index < aWord.length(); index++) { char c = aWord.charAt(index); if (!(Character.isJavaIdentifierPart(c) || c == '.' || c == '[' || c == ']' || c == '<' || c == '>' || c == '-' || c == '!' || c == '*' || c == '?' || c == '%')) { return false; } } return true; } /** * Checks whether the given access flags are valid field access flags, * throwing a ParseException if they aren't. */ private void checkFieldAccessFlags(int requiredSetMemberAccessFlags, int requiredUnsetMemberAccessFlags) throws ParseException { if (((requiredSetMemberAccessFlags | requiredUnsetMemberAccessFlags) & ~ClassConstants.VALID_INTERNAL_ACC_FIELD) != 0) { throw new ParseException("Invalid method access modifier for field before " + reader.locationDescription()); } } /** * Checks whether the given access flags are valid method access flags, * throwing a ParseException if they aren't. */ private void checkMethodAccessFlags(int requiredSetMemberAccessFlags, int requiredUnsetMemberAccessFlags) throws ParseException { if (((requiredSetMemberAccessFlags | requiredUnsetMemberAccessFlags) & ~ClassConstants.VALID_INTERNAL_ACC_METHOD) != 0) { throw new ParseException("Invalid field access modifier for method before " + reader.locationDescription()); } } /** * A main method for testing configuration parsing. */ public static void main(String[] args) { try { ConfigurationParser parser = new ConfigurationParser(args); try { parser.parse(new Configuration()); } catch (ParseException ex) { ex.printStackTrace(); } finally { parser.close(); } } catch (IOException ex) { ex.printStackTrace(); } } }