| // Copyright 2014 Google Inc. All rights reserved. | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //    http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | package com.google.devtools.common.options; | 
 |  | 
 | import com.google.common.base.Function; | 
 | import com.google.common.base.Functions; | 
 | import com.google.common.base.Joiner; | 
 | import com.google.common.base.Preconditions; | 
 | import com.google.common.base.Predicate; | 
 | import com.google.common.collect.ArrayListMultimap; | 
 | import com.google.common.collect.ImmutableList; | 
 | import com.google.common.collect.Iterables; | 
 | import com.google.common.collect.ListMultimap; | 
 | import com.google.common.collect.Lists; | 
 | import com.google.common.collect.Maps; | 
 | import com.google.devtools.common.options.OptionsParser.OptionValueDescription; | 
 | import com.google.devtools.common.options.OptionsParser.UnparsedOptionValueDescription; | 
 |  | 
 | import java.lang.reflect.Constructor; | 
 | import java.lang.reflect.Field; | 
 | import java.lang.reflect.ParameterizedType; | 
 | import java.lang.reflect.Type; | 
 | import java.util.Arrays; | 
 | import java.util.Collection; | 
 | import java.util.Collections; | 
 | import java.util.Comparator; | 
 | import java.util.LinkedHashMap; | 
 | import java.util.List; | 
 | import java.util.Map; | 
 |  | 
 | /** | 
 |  * The implementation of the options parser. This is intentionally package | 
 |  * private for full flexibility. Use {@link OptionsParser} or {@link Options} | 
 |  * if you're a consumer. | 
 |  */ | 
 | class OptionsParserImpl { | 
 |  | 
 |   /** | 
 |    * A bunch of default converters in case the user doesn't specify a | 
 |    * different one in the field annotation. | 
 |    */ | 
 |   static final Map<Class<?>, Converter<?>> DEFAULT_CONVERTERS = Maps.newHashMap(); | 
 |  | 
 |   static { | 
 |     DEFAULT_CONVERTERS.put(String.class, new Converter<String>() { | 
 |       @Override | 
 |       public String convert(String input) { | 
 |         return input; | 
 |       } | 
 |       @Override | 
 |       public String getTypeDescription() { | 
 |         return "a string"; | 
 |       }}); | 
 |     DEFAULT_CONVERTERS.put(int.class, new Converter<Integer>() { | 
 |       @Override | 
 |       public Integer convert(String input) throws OptionsParsingException { | 
 |         try { | 
 |           return Integer.decode(input); | 
 |         } catch (NumberFormatException e) { | 
 |           throw new OptionsParsingException("'" + input + "' is not an int"); | 
 |         } | 
 |       } | 
 |       @Override | 
 |       public String getTypeDescription() { | 
 |         return "an integer"; | 
 |       }}); | 
 |     DEFAULT_CONVERTERS.put(double.class, new Converter<Double>() { | 
 |       @Override | 
 |       public Double convert(String input) throws OptionsParsingException { | 
 |         try { | 
 |           return Double.parseDouble(input); | 
 |         } catch (NumberFormatException e) { | 
 |           throw new OptionsParsingException("'" + input + "' is not a double"); | 
 |         } | 
 |       } | 
 |       @Override | 
 |       public String getTypeDescription() { | 
 |         return "a double"; | 
 |       }}); | 
 |     DEFAULT_CONVERTERS.put(boolean.class, new Converters.BooleanConverter()); | 
 |     DEFAULT_CONVERTERS.put(TriState.class, new Converter<TriState>() { | 
 |       @Override | 
 |       public TriState convert(String input) throws OptionsParsingException { | 
 |         if (input == null) { | 
 |           return TriState.AUTO; | 
 |         } | 
 |         input = input.toLowerCase(); | 
 |         if (input.equals("auto")) { | 
 |           return TriState.AUTO; | 
 |         } | 
 |         if (input.equals("true") || input.equals("1") || input.equals("yes") || | 
 |             input.equals("t") || input.equals("y")) { | 
 |           return TriState.YES; | 
 |         } | 
 |         if (input.equals("false") || input.equals("0") || input.equals("no") || | 
 |             input.equals("f") || input.equals("n")) { | 
 |           return TriState.NO; | 
 |         } | 
 |         throw new OptionsParsingException("'" + input + "' is not a boolean"); | 
 |       } | 
 |       @Override | 
 |       public String getTypeDescription() { | 
 |         return "a tri-state (auto, yes, no)"; | 
 |       }}); | 
 |     DEFAULT_CONVERTERS.put(Void.class, new Converter<Void>() { | 
 |       @Override | 
 |       public Void convert(String input) throws OptionsParsingException { | 
 |         if (input == null) { | 
 |           return null;  // expected input, return is unused so null is fine. | 
 |         } | 
 |         throw new OptionsParsingException("'" + input + "' unexpected"); | 
 |       } | 
 |       @Override | 
 |       public String getTypeDescription() { | 
 |         return ""; | 
 |       }}); | 
 |     DEFAULT_CONVERTERS.put(long.class, new Converter<Long>() { | 
 |       @Override | 
 |       public Long convert(String input) throws OptionsParsingException { | 
 |         try { | 
 |           return Long.decode(input); | 
 |         } catch (NumberFormatException e) { | 
 |           throw new OptionsParsingException("'" + input + "' is not a long"); | 
 |         } | 
 |       } | 
 |       @Override | 
 |       public String getTypeDescription() { | 
 |         return "a long integer"; | 
 |       }}); | 
 |   } | 
 |  | 
 |   /** | 
 |    * For every value, this class keeps track of its priority, its free-form source | 
 |    * description, whether it was set as an implicit dependency, and the value. | 
 |    */ | 
 |   private static final class ParsedOptionEntry { | 
 |     private final Object value; | 
 |     private final OptionPriority priority; | 
 |     private final String source; | 
 |     private final String implicitDependant; | 
 |     private final String expandedFrom; | 
 |     private final boolean allowMultiple; | 
 |  | 
 |     ParsedOptionEntry(Object value, | 
 |         OptionPriority priority, String source, String implicitDependant, String expandedFrom, | 
 |         boolean allowMultiple) { | 
 |       this.value = value; | 
 |       this.priority = priority; | 
 |       this.source = source; | 
 |       this.implicitDependant = implicitDependant; | 
 |       this.expandedFrom = expandedFrom; | 
 |       this.allowMultiple = allowMultiple; | 
 |     } | 
 |  | 
 |     // Need to suppress unchecked warnings, because the "multiple occurrence" | 
 |     // options use unchecked ListMultimaps due to limitations of Java generics. | 
 |     @SuppressWarnings({"unchecked", "rawtypes"}) | 
 |     Object getValue() { | 
 |       if (allowMultiple) { | 
 |         // Sort the results by option priority and return them in a new list. | 
 |         // The generic type of the list is not known at runtime, so we can't | 
 |         // use it here. It was already checked in the constructor, so this is | 
 |         // type-safe. | 
 |         List result = Lists.newArrayList(); | 
 |         ListMultimap realValue = (ListMultimap) value; | 
 |         for (OptionPriority priority : OptionPriority.values()) { | 
 |           // If there is no mapping for this key, this check avoids object creation (because | 
 |           // ListMultimap has to return a new object on get) and also an unnecessary addAll call. | 
 |           if (realValue.containsKey(priority)) { | 
 |             result.addAll(realValue.get(priority)); | 
 |           } | 
 |         } | 
 |         return result; | 
 |       } | 
 |       return value; | 
 |     } | 
 |  | 
 |     // Need to suppress unchecked warnings, because the "multiple occurrence" | 
 |     // options use unchecked ListMultimaps due to limitations of Java generics. | 
 |     @SuppressWarnings({"unchecked", "rawtypes"}) | 
 |     void addValue(OptionPriority addedPriority, Object addedValue) { | 
 |       Preconditions.checkState(allowMultiple); | 
 |       ListMultimap optionValueList = (ListMultimap) value; | 
 |       if (addedValue instanceof List<?>) { | 
 |         for (Object element : (List<?>) addedValue) { | 
 |           optionValueList.put(addedPriority, element); | 
 |         } | 
 |       } else { | 
 |         optionValueList.put(addedPriority, addedValue); | 
 |       } | 
 |     } | 
 |  | 
 |     OptionValueDescription asOptionValueDescription(String fieldName) { | 
 |       return new OptionValueDescription(fieldName, getValue(), priority, | 
 |           source, implicitDependant, expandedFrom); | 
 |     } | 
 |   } | 
 |  | 
 |   private final OptionsData optionsData; | 
 |  | 
 |   /** | 
 |    * We store the results of parsing the arguments in here. It'll look like | 
 |    * <pre> | 
 |    *   Field("--host") -> "www.google.com" | 
 |    *   Field("--port") -> 80 | 
 |    * </pre> | 
 |    * This map is modified by repeated calls to | 
 |    * {@link #parse(OptionPriority,Function,List)}. | 
 |    */ | 
 |   private final Map<Field, ParsedOptionEntry> parsedValues = Maps.newHashMap(); | 
 |  | 
 |   /** | 
 |    * We store the pre-parsed, explicit options for each priority in here. | 
 |    * We use partially preparsed options, which can be different from the original | 
 |    * representation, e.g. "--nofoo" becomes "--foo=0". | 
 |    */ | 
 |   private final List<UnparsedOptionValueDescription> unparsedValues = | 
 |       Lists.newArrayList(); | 
 |  | 
 |   private final List<String> warnings = Lists.newArrayList(); | 
 |    | 
 |   private boolean allowSingleDashLongOptions = false; | 
 |  | 
 |   /** | 
 |    * Create a new parser object | 
 |    */ | 
 |   OptionsParserImpl(OptionsData optionsData) { | 
 |     this.optionsData = optionsData; | 
 |   } | 
 |  | 
 |   /** | 
 |    * Indicates whether or not the parser will allow long options with a | 
 |    * single-dash, instead of the usual double-dash, too, eg. -example instead of just --example. | 
 |    */ | 
 |   void setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions) { | 
 |     this.allowSingleDashLongOptions = allowSingleDashLongOptions; | 
 |   } | 
 |    | 
 |   /** | 
 |    * The implementation of {@link OptionsBase#asMap}. | 
 |    */ | 
 |   static Map<String, Object> optionsAsMap(OptionsBase optionsInstance) { | 
 |     Map<String, Object> map = Maps.newHashMap(); | 
 |     for (Field field : OptionsParser.getAllAnnotatedFields(optionsInstance.getClass())) { | 
 |       try { | 
 |         String name = field.getAnnotation(Option.class).name(); | 
 |         Object value = field.get(optionsInstance); | 
 |         map.put(name, value); | 
 |       } catch (IllegalAccessException e) { | 
 |         throw new IllegalStateException(e); // unreachable | 
 |       } | 
 |     } | 
 |     return map; | 
 |   } | 
 |  | 
 |   List<Field> getAnnotatedFieldsFor(Class<? extends OptionsBase> clazz) { | 
 |     return optionsData.getFieldsForClass(clazz); | 
 |   } | 
 |  | 
 |   /** | 
 |    * Implements {@link OptionsParser#asListOfUnparsedOptions()}. | 
 |    */ | 
 |   List<UnparsedOptionValueDescription> asListOfUnparsedOptions() { | 
 |     List<UnparsedOptionValueDescription> result = Lists.newArrayList(unparsedValues); | 
 |     // It is vital that this sort is stable so that options on the same priority are not reordered. | 
 |     Collections.sort(result, new Comparator<UnparsedOptionValueDescription>() { | 
 |       @Override | 
 |       public int compare(UnparsedOptionValueDescription o1, | 
 |           UnparsedOptionValueDescription o2) { | 
 |         return o1.getPriority().compareTo(o2.getPriority()); | 
 |       } | 
 |     }); | 
 |     return result; | 
 |   } | 
 |  | 
 |   /** | 
 |    * Implements {@link OptionsParser#asListOfExplicitOptions()}. | 
 |    */ | 
 |   List<UnparsedOptionValueDescription> asListOfExplicitOptions() { | 
 |     List<UnparsedOptionValueDescription> result = Lists.newArrayList(Iterables.filter( | 
 |       unparsedValues, | 
 |       new Predicate<UnparsedOptionValueDescription>() { | 
 |         @Override | 
 |         public boolean apply(UnparsedOptionValueDescription input) { | 
 |           return input.isExplicit(); | 
 |         } | 
 |     })); | 
 |     // It is vital that this sort is stable so that options on the same priority are not reordered. | 
 |     Collections.sort(result, new Comparator<UnparsedOptionValueDescription>() { | 
 |       @Override | 
 |       public int compare(UnparsedOptionValueDescription o1, | 
 |           UnparsedOptionValueDescription o2) { | 
 |         return o1.getPriority().compareTo(o2.getPriority()); | 
 |       } | 
 |     }); | 
 |     return result; | 
 |   } | 
 |  | 
 |   /** | 
 |    * Implements {@link OptionsParser#canonicalize}. | 
 |    */ | 
 |   List<String> asCanonicalizedList() { | 
 |     List<UnparsedOptionValueDescription> processed = Lists.newArrayList(unparsedValues); | 
 |     Collections.sort(processed, new Comparator<UnparsedOptionValueDescription>() { | 
 |       // This Comparator sorts implicit requirement options to the end, keeping their existing | 
 |       // order, and sorts the other options alphabetically. | 
 |       @Override | 
 |       public int compare(UnparsedOptionValueDescription o1, | 
 |           UnparsedOptionValueDescription o2) { | 
 |         if (o1.isImplicitRequirement()) { | 
 |           return o2.isImplicitRequirement() ? 0 : 1; | 
 |         } | 
 |         if (o2.isImplicitRequirement()) { | 
 |           return -1; | 
 |         } | 
 |         return o1.getName().compareTo(o2.getName()); | 
 |       } | 
 |     }); | 
 |  | 
 |     List<String> result = Lists.newArrayList(); | 
 |     for (int i = 0; i < processed.size(); i++) { | 
 |       UnparsedOptionValueDescription value = processed.get(i); | 
 |       // Skip an option if the next option is the same, but only if the option does not allow | 
 |       // multiple values. | 
 |       if (!value.allowMultiple()) { | 
 |         if ((i < processed.size() - 1) && value.getName().equals(processed.get(i + 1).getName())) { | 
 |           continue; | 
 |         } | 
 |       } | 
 |  | 
 |       // Ignore expansion options. | 
 |       if (value.isExpansion()) { | 
 |         continue; | 
 |       } | 
 |  | 
 |       result.add("--" + value.getName() + "=" + value.getUnparsedValue()); | 
 |     } | 
 |     return result; | 
 |   } | 
 |  | 
 |   /** | 
 |    * Implements {@link OptionsParser#asListOfEffectiveOptions()}. | 
 |    */ | 
 |   List<OptionValueDescription> asListOfEffectiveOptions() { | 
 |     List<OptionValueDescription> result = Lists.newArrayList(); | 
 |     for (Map.Entry<String,Field> mapEntry : optionsData.getAllNamedFields()) { | 
 |       String fieldName = mapEntry.getKey(); | 
 |       Field field = mapEntry.getValue(); | 
 |       ParsedOptionEntry entry = parsedValues.get(field); | 
 |       if (entry == null) { | 
 |         Object value = optionsData.getDefaultValue(field); | 
 |         result.add(new OptionValueDescription(fieldName, value, OptionPriority.DEFAULT, | 
 |             null, null, null)); | 
 |       } else { | 
 |         result.add(entry.asOptionValueDescription(fieldName)); | 
 |       } | 
 |     } | 
 |     return result; | 
 |   } | 
 |  | 
 |   Collection<Class<?  extends OptionsBase>> getOptionsClasses() { | 
 |     return optionsData.getOptionsClasses(); | 
 |   } | 
 |  | 
 |   private void maybeAddDeprecationWarning(Field field) { | 
 |     Option option = field.getAnnotation(Option.class); | 
 |     // Continue to support the old behavior for @Deprecated options. | 
 |     String warning = option.deprecationWarning(); | 
 |     if (!warning.equals("") || (field.getAnnotation(Deprecated.class) != null)) { | 
 |       warnings.add("Option '" + option.name() + "' is deprecated" | 
 |           + (warning.equals("") ? "" : ": " + warning)); | 
 |     } | 
 |   } | 
 |  | 
 |   // Warnings should not end with a '.' because the internal reporter adds one automatically. | 
 |   private void setValue(Field field, String name, Object value, | 
 |       OptionPriority priority, String source, String implicitDependant, String expandedFrom) { | 
 |     ParsedOptionEntry entry = parsedValues.get(field); | 
 |     if (entry != null) { | 
 |       // Override existing option if the new value has higher or equal priority. | 
 |       if (priority.compareTo(entry.priority) >= 0) { | 
 |         // Output warnings: | 
 |         if ((implicitDependant != null) && (entry.implicitDependant != null)) { | 
 |           if (!implicitDependant.equals(entry.implicitDependant)) { | 
 |             warnings.add("Option '" + name + "' is implicitly defined by both option '" + | 
 |                 entry.implicitDependant + "' and option '" + implicitDependant + "'"); | 
 |           } | 
 |         } else if ((implicitDependant != null) && priority.equals(entry.priority)) { | 
 |           warnings.add("Option '" + name + "' is implicitly defined by option '" + | 
 |               implicitDependant + "'; the implicitly set value overrides the previous one"); | 
 |         } else if (entry.implicitDependant != null) { | 
 |           warnings.add("A new value for option '" + name + "' overrides a previous " + | 
 |               "implicit setting of that option by option '" + entry.implicitDependant + "'"); | 
 |         } else if ((priority == entry.priority) && | 
 |             ((entry.expandedFrom == null) && (expandedFrom != null))) { | 
 |           // Create a warning if an expansion option overrides an explicit option: | 
 |           warnings.add("The option '" + expandedFrom + "' was expanded and now overrides a " | 
 |               + "previous explicitly specified option '" + name + "'"); | 
 |         } | 
 |  | 
 |         // Record the new value: | 
 |         parsedValues.put(field, | 
 |             new ParsedOptionEntry(value, priority, source, implicitDependant, expandedFrom, false)); | 
 |       } | 
 |     } else { | 
 |       parsedValues.put(field, | 
 |           new ParsedOptionEntry(value, priority, source, implicitDependant, expandedFrom, false)); | 
 |       maybeAddDeprecationWarning(field); | 
 |     } | 
 |   } | 
 |  | 
 |   private void addListValue(Field field, Object value, OptionPriority priority, String source, | 
 |       String implicitDependant, String expandedFrom) { | 
 |     ParsedOptionEntry entry = parsedValues.get(field); | 
 |     if (entry == null) { | 
 |       entry = new ParsedOptionEntry(ArrayListMultimap.create(), priority, source, | 
 |           implicitDependant, expandedFrom, true); | 
 |       parsedValues.put(field, entry); | 
 |       maybeAddDeprecationWarning(field); | 
 |     } | 
 |     entry.addValue(priority, value); | 
 |   } | 
 |  | 
 |   private Object getValue(Field field) { | 
 |     ParsedOptionEntry entry = parsedValues.get(field); | 
 |     return entry == null ? null : entry.getValue(); | 
 |   } | 
 |  | 
 |   OptionValueDescription getOptionValueDescription(String name) { | 
 |     Field field = optionsData.getFieldFromName(name); | 
 |     if (field == null) { | 
 |       throw new IllegalArgumentException("No such option '" + name + "'"); | 
 |     } | 
 |     ParsedOptionEntry entry = parsedValues.get(field); | 
 |     if (entry == null) { | 
 |       return null; | 
 |     } | 
 |     return entry.asOptionValueDescription(name); | 
 |   } | 
 |  | 
 |   boolean containsExplicitOption(String name) { | 
 |     Field field = optionsData.getFieldFromName(name); | 
 |     if (field == null) { | 
 |       throw new IllegalArgumentException("No such option '" + name + "'"); | 
 |     } | 
 |     return parsedValues.get(field) != null; | 
 |   } | 
 |  | 
 |   /** | 
 |    * Parses the args, and returns what it doesn't parse. May be called multiple | 
 |    * times, and may be called recursively. In each call, there may be no | 
 |    * duplicates, but separate calls may contain intersecting sets of options; in | 
 |    * that case, the arg seen last takes precedence. | 
 |    */ | 
 |   List<String> parse(OptionPriority priority, Function<? super String, String> sourceFunction, | 
 |       List<String> args) throws OptionsParsingException { | 
 |     return parse(priority, sourceFunction, null, null, args); | 
 |   } | 
 |  | 
 |   /** | 
 |    * Parses the args, and returns what it doesn't parse. May be called multiple | 
 |    * times, and may be called recursively. Calls may contain intersecting sets | 
 |    * of options; in that case, the arg seen last takes precedence. | 
 |    * | 
 |    * <p>The method uses the invariant that if an option has neither an implicit | 
 |    * dependant nor an expanded from value, then it must have been explicitly | 
 |    * set. | 
 |    */ | 
 |   private List<String> parse(OptionPriority priority, | 
 |       final Function<? super String, String> sourceFunction, String implicitDependant, | 
 |       String expandedFrom, List<String> args) throws OptionsParsingException { | 
 |     List<String> unparsedArgs = Lists.newArrayList(); | 
 |     LinkedHashMap<String,List<String>> implicitRequirements = Maps.newLinkedHashMap(); | 
 |     for (int pos = 0; pos < args.size(); pos++) { | 
 |       String arg = args.get(pos); | 
 |       if (!arg.startsWith("-")) { | 
 |         unparsedArgs.add(arg); | 
 |         continue;  // not an option arg | 
 |       } | 
 |       if (arg.equals("--")) {  // "--" means all remaining args aren't options | 
 |         while (++pos < args.size()) { | 
 |           unparsedArgs.add(args.get(pos)); | 
 |         } | 
 |         break; | 
 |       } | 
 |  | 
 |       String value = null; | 
 |       Field field; | 
 |       boolean booleanValue = true; | 
 |  | 
 |       if (arg.length() == 2) { // -l  (may be nullary or unary) | 
 |         field = optionsData.getFieldForAbbrev(arg.charAt(1)); | 
 |         booleanValue = true; | 
 |  | 
 |       } else if (arg.length() == 3 && arg.charAt(2) == '-') { // -l-  (boolean) | 
 |         field = optionsData.getFieldForAbbrev(arg.charAt(1)); | 
 |         booleanValue = false; | 
 |  | 
 |       } else if (allowSingleDashLongOptions // -long_option | 
 |           || arg.startsWith("--")) { // or --long_option | 
 |         int equalsAt = arg.indexOf('='); | 
 |         int nameStartsAt = arg.startsWith("--") ? 2 : 1; | 
 |         String name = | 
 |             equalsAt == -1 ? arg.substring(nameStartsAt) : arg.substring(nameStartsAt, equalsAt); | 
 |         if (name.trim().equals("")) { | 
 |           throw new OptionsParsingException("Invalid options syntax: " + arg, arg); | 
 |         } | 
 |         value = equalsAt == -1 ? null : arg.substring(equalsAt + 1); | 
 |         field = optionsData.getFieldFromName(name); | 
 |  | 
 |         // look for a "no"-prefixed option name: "no<optionname>"; | 
 |         // (Undocumented: we also allow --no_foo.  We're generous like that.) | 
 |         if (field == null && name.startsWith("no")) { | 
 |           String realname = name.substring(name.startsWith("no_") ? 3 : 2); | 
 |           field = optionsData.getFieldFromName(realname); | 
 |           booleanValue = false; | 
 |           if (field != null) { | 
 |             // TODO(bazel-team): Add tests for these cases. | 
 |             if (!OptionsParserImpl.isBooleanField(field)) { | 
 |               throw new OptionsParsingException( | 
 |                   "Illegal use of 'no' prefix on non-boolean option: " + arg, arg); | 
 |             } | 
 |             if (value != null) { | 
 |               throw new OptionsParsingException( | 
 |                   "Unexpected value after boolean option: " + arg, arg); | 
 |             } | 
 |             // "no<optionname>" signifies a boolean option w/ false value | 
 |             value = "0"; | 
 |           } | 
 |         } | 
 |  | 
 |       } else { | 
 |         throw new OptionsParsingException("Invalid options syntax: " + arg, arg); | 
 |       } | 
 |  | 
 |       if (field == null) { | 
 |         throw new OptionsParsingException("Unrecognized option: " + arg, arg); | 
 |       } | 
 |        | 
 |       if (value == null) { | 
 |         // special case boolean to supply value based on presence of "no" prefix | 
 |         if (OptionsParserImpl.isBooleanField(field)) { | 
 |           value = booleanValue ? "1" : "0"; | 
 |         } else if (field.getType().equals(Void.class)) { | 
 |           // this is expected, Void type options have no args | 
 |         } else if (pos != args.size() - 1) { | 
 |           value = args.get(++pos);  // "--flag value" form | 
 |         } else { | 
 |           throw new OptionsParsingException("Expected value after " + arg); | 
 |         } | 
 |       } | 
 |  | 
 |       Option option = field.getAnnotation(Option.class); | 
 |       final String originalName = option.name(); | 
 |       if (implicitDependant == null) { | 
 |         // Log explicit options and expanded options in the order they are parsed (can be sorted | 
 |         // later). Also remember whether they were expanded or not. This information is needed to | 
 |         // correctly canonicalize flags. | 
 |         unparsedValues.add(new UnparsedOptionValueDescription(originalName, field, value, | 
 |             priority, sourceFunction.apply(originalName), expandedFrom == null)); | 
 |       } | 
 |  | 
 |       // Handle expansion options. | 
 |       if (option.expansion().length > 0) { | 
 |         Function<Object, String> expansionSourceFunction = Functions.<String>constant( | 
 |             "expanded from option --" + originalName + " from " + | 
 |             sourceFunction.apply(originalName)); | 
 |         maybeAddDeprecationWarning(field); | 
 |         List<String> unparsed = parse(priority, expansionSourceFunction, null, originalName, | 
 |             ImmutableList.copyOf(option.expansion())); | 
 |         if (!unparsed.isEmpty()) { | 
 |           // Throw an assertion, because this indicates an error in the code that specified the | 
 |           // expansion for the current option. | 
 |           throw new AssertionError("Unparsed options remain after parsing expansion of " + | 
 |             arg + ":" + Joiner.on(' ').join(unparsed)); | 
 |         } | 
 |       } else { | 
 |         Converter<?> converter = optionsData.getConverter(field); | 
 |         Object convertedValue; | 
 |         try { | 
 |           convertedValue = converter.convert(value); | 
 |         } catch (OptionsParsingException e) { | 
 |           // The converter doesn't know the option name, so we supply it here by | 
 |           // re-throwing: | 
 |           throw new OptionsParsingException("While parsing option " + arg | 
 |                                             + ": " + e.getMessage(), e); | 
 |         } | 
 |  | 
 |         // ...but allow duplicates of single-use options across separate calls to | 
 |         // parse(); latest wins: | 
 |         if (!option.allowMultiple()) { | 
 |           setValue(field, originalName, convertedValue, | 
 |               priority, sourceFunction.apply(originalName), implicitDependant, expandedFrom); | 
 |         } else { | 
 |           // But if it's a multiple-use option, then just accumulate the | 
 |           // values, in the order in which they were seen. | 
 |           // Note: The type of the list member is not known; Java introspection | 
 |           // only makes it available in String form via the signature string | 
 |           // for the field declaration. | 
 |           addListValue(field, convertedValue, priority, sourceFunction.apply(originalName), | 
 |               implicitDependant, expandedFrom); | 
 |         } | 
 |       } | 
 |  | 
 |       // Collect any implicit requirements. | 
 |       if (option.implicitRequirements().length > 0) { | 
 |         implicitRequirements.put(option.name(), Arrays.asList(option.implicitRequirements())); | 
 |       } | 
 |     } | 
 |  | 
 |     // Now parse any implicit requirements that were collected. | 
 |     // TODO(bazel-team): this should happen when the option is encountered. | 
 |     if (!implicitRequirements.isEmpty()) { | 
 |       for (Map.Entry<String,List<String>> entry : implicitRequirements.entrySet()) { | 
 |         Function<Object, String> requirementSourceFunction = Functions.<String>constant( | 
 |             "implicit requirement of option --" + entry.getKey() + " from " + | 
 |             sourceFunction.apply(entry.getKey())); | 
 |  | 
 |         List<String> unparsed = parse(priority, requirementSourceFunction, entry.getKey(), null, | 
 |             entry.getValue()); | 
 |         if (!unparsed.isEmpty()) { | 
 |           // Throw an assertion, because this indicates an error in the code that specified in the | 
 |           // implicit requirements for the option(s). | 
 |           throw new AssertionError("Unparsed options remain after parsing implicit options:" | 
 |               + Joiner.on(' ').join(unparsed)); | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     return unparsedArgs; | 
 |   } | 
 |  | 
 |   /** | 
 |    * Gets the result of parsing the options. | 
 |    */ | 
 |   <O extends OptionsBase> O getParsedOptions(Class<O> optionsClass) { | 
 |     // Create the instance: | 
 |     O optionsInstance; | 
 |     try { | 
 |       Constructor<O> constructor = optionsData.getConstructor(optionsClass); | 
 |       if (constructor == null) { | 
 |         return null; | 
 |       } | 
 |       optionsInstance = constructor.newInstance(new Object[0]); | 
 |     } catch (Exception e) { | 
 |       throw new IllegalStateException(e); | 
 |     } | 
 |  | 
 |     // Set the fields | 
 |     for (Field field : optionsData.getFieldsForClass(optionsClass)) { | 
 |       Object value = getValue(field); | 
 |       if (value == null) { | 
 |         value = optionsData.getDefaultValue(field); | 
 |       } | 
 |       try { | 
 |         field.set(optionsInstance, value); | 
 |       } catch (IllegalAccessException e) { | 
 |         throw new IllegalStateException(e); | 
 |       } | 
 |     } | 
 |     return optionsInstance; | 
 |   } | 
 |  | 
 |   List<String> getWarnings() { | 
 |     return ImmutableList.copyOf(warnings); | 
 |   } | 
 |  | 
 |   static String getDefaultOptionString(Field optionField) { | 
 |     Option annotation = optionField.getAnnotation(Option.class); | 
 |     return annotation.defaultValue(); | 
 |   } | 
 |  | 
 |   static boolean isBooleanField(Field field) { | 
 |     return field.getType().equals(boolean.class) | 
 |         || field.getType().equals(TriState.class) | 
 |         || findConverter(field) instanceof BoolOrEnumConverter; | 
 |   } | 
 |  | 
 |   static boolean isSpecialNullDefault(String defaultValueString, Field optionField) { | 
 |     return defaultValueString.equals("null") && !optionField.getType().isPrimitive(); | 
 |   } | 
 |  | 
 |   static Converter<?> findConverter(Field optionField) { | 
 |     Option annotation = optionField.getAnnotation(Option.class); | 
 |     if (annotation.converter() == Converter.class) { | 
 |       Type type; | 
 |       if (annotation.allowMultiple()) { | 
 |         // The OptionParserImpl already checked that the type is List<T> for some T; | 
 |         // here we extract the type T. | 
 |         type = ((ParameterizedType) optionField.getGenericType()).getActualTypeArguments()[0]; | 
 |       } else { | 
 |         type = optionField.getType(); | 
 |       } | 
 |       Converter<?> converter = DEFAULT_CONVERTERS.get(type); | 
 |       if (converter == null) { | 
 |         throw new AssertionError("No converter found for " | 
 |             + type + "; possible fix: add " | 
 |             + "converter=... to @Option annotation for " | 
 |             + optionField.getName()); | 
 |       } | 
 |       return converter; | 
 |     } | 
 |     try { | 
 |       Class<?> converter = annotation.converter(); | 
 |       Constructor<?> constructor = converter.getConstructor(new Class<?>[0]); | 
 |       return (Converter<?>) constructor.newInstance(new Object[0]); | 
 |     } catch (Exception e) { | 
 |       throw new AssertionError(e); | 
 |     } | 
 |   } | 
 |  | 
 | } |