summaryrefslogtreecommitdiff
path: root/java/com/google/devtools/common/options/OptionsData.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/devtools/common/options/OptionsData.java')
-rw-r--r--java/com/google/devtools/common/options/OptionsData.java307
1 files changed, 58 insertions, 249 deletions
diff --git a/java/com/google/devtools/common/options/OptionsData.java b/java/com/google/devtools/common/options/OptionsData.java
index ac23d63..e71321c 100644
--- a/java/com/google/devtools/common/options/OptionsData.java
+++ b/java/com/google/devtools/common/options/OptionsData.java
@@ -1,4 +1,4 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
+// Copyright 2017 The Bazel Authors. 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.
@@ -14,280 +14,89 @@
package com.google.devtools.common.options;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
-import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
import java.util.Map;
-
import javax.annotation.concurrent.Immutable;
/**
- * An immutable selection of options data corresponding to a set of options
- * classes. The data is collected using reflection, which can be expensive.
- * Therefore this class can be used internally to cache the results.
+ * This extends IsolatedOptionsData with information that can only be determined once all the {@link
+ * OptionsBase} subclasses for a parser are known. In particular, this includes expansion
+ * information.
*/
@Immutable
-final class OptionsData extends OpaqueOptionsData {
-
- /**
- * These are the options-declaring classes which are annotated with
- * {@link Option} annotations.
- */
- private final Map<Class<? extends OptionsBase>, Constructor<?>> optionsClasses;
-
- /** Maps option name to Option-annotated Field. */
- private final Map<String, Field> nameToField;
-
- /** Maps option abbreviation to Option-annotated Field. */
- private final Map<Character, Field> abbrevToField;
+final class OptionsData extends IsolatedOptionsData {
/**
- * For each options class, contains a list of all Option-annotated fields in
- * that class.
+ * Mapping from each Option-annotated field with a {@code String[]} expansion to that expansion.
*/
- private final Map<Class<? extends OptionsBase>, List<Field>> allOptionsFields;
+ // TODO(brandjon): This is technically not necessarily immutable due to String[], and should use
+ // ImmutableList. Either fix this or remove @Immutable.
+ private final ImmutableMap<Field, String[]> evaluatedExpansions;
+
+ /** Construct {@link OptionsData} by extending an {@link IsolatedOptionsData} with new info. */
+ private OptionsData(IsolatedOptionsData base, Map<Field, String[]> evaluatedExpansions) {
+ super(base);
+ this.evaluatedExpansions = ImmutableMap.copyOf(evaluatedExpansions);
+ }
- /**
- * Mapping from each Option-annotated field to the default value for that
- * field.
- */
- private final Map<Field, Object> optionDefaults;
+ private static final String[] EMPTY_EXPANSION = new String[] {};
/**
- * Mapping from each Option-annotated field to the proper converter.
- *
- * @see OptionsParserImpl#findConverter
+ * Returns the expansion of an options field, regardless of whether it was defined using {@link
+ * Option#expansion} or {@link Option#expansionFunction}. If the field is not an expansion option,
+ * returns an empty array.
*/
- private final Map<Field, Converter<?>> converters;
+ public String[] getEvaluatedExpansion(Field field) {
+ String[] result = evaluatedExpansions.get(field);
+ return result != null ? result : EMPTY_EXPANSION;
+ }
/**
- * Mapping from each Option-annotated field to a boolean for whether that field allows multiple
- * values.
+ * Constructs an {@link OptionsData} object for a parser that knows about the given {@link
+ * OptionsBase} classes. In addition to the work done to construct the {@link
+ * IsolatedOptionsData}, this also computes expansion information.
*/
- private final Map<Field, Boolean> allowMultiple;
-
- private OptionsData(Map<Class<? extends OptionsBase>, Constructor<?>> optionsClasses,
- Map<String, Field> nameToField,
- Map<Character, Field> abbrevToField,
- Map<Class<? extends OptionsBase>, List<Field>> allOptionsFields,
- Map<Field, Object> optionDefaults,
- Map<Field, Converter<?>> converters,
- Map<Field, Boolean> allowMultiple) {
- this.optionsClasses = ImmutableMap.copyOf(optionsClasses);
- this.allOptionsFields = ImmutableMap.copyOf(allOptionsFields);
- this.nameToField = ImmutableMap.copyOf(nameToField);
- this.abbrevToField = ImmutableMap.copyOf(abbrevToField);
- // Can't use an ImmutableMap here because of null values.
- this.optionDefaults = Collections.unmodifiableMap(optionDefaults);
- this.converters = ImmutableMap.copyOf(converters);
- this.allowMultiple = ImmutableMap.copyOf(allowMultiple);
- }
-
- public Collection<Class<? extends OptionsBase>> getOptionsClasses() {
- return optionsClasses.keySet();
- }
-
- @SuppressWarnings("unchecked") // The construction ensures that the case is always valid.
- public <T extends OptionsBase> Constructor<T> getConstructor(Class<T> clazz) {
- return (Constructor<T>) optionsClasses.get(clazz);
- }
-
- public Field getFieldFromName(String name) {
- return nameToField.get(name);
- }
-
- public Iterable<Map.Entry<String, Field>> getAllNamedFields() {
- return nameToField.entrySet();
- }
-
- public Field getFieldForAbbrev(char abbrev) {
- return abbrevToField.get(abbrev);
- }
-
- public List<Field> getFieldsForClass(Class<? extends OptionsBase> optionsClass) {
- return allOptionsFields.get(optionsClass);
- }
-
- public Object getDefaultValue(Field field) {
- return optionDefaults.get(field);
- }
-
- public Converter<?> getConverter(Field field) {
- return converters.get(field);
- }
-
- public boolean getAllowMultiple(Field field) {
- return allowMultiple.get(field);
- }
-
- private static List<Field> getAllAnnotatedFields(Class<? extends OptionsBase> optionsClass) {
- List<Field> allFields = Lists.newArrayList();
- for (Field field : optionsClass.getFields()) {
- if (field.isAnnotationPresent(Option.class)) {
- allFields.add(field);
- }
- }
- if (allFields.isEmpty()) {
- throw new IllegalStateException(optionsClass + " has no public @Option-annotated fields");
- }
- return ImmutableList.copyOf(allFields);
- }
-
- private static Object retrieveDefaultFromAnnotation(Field optionField) {
- Converter<?> converter = OptionsParserImpl.findConverter(optionField);
- String defaultValueAsString = OptionsParserImpl.getDefaultOptionString(optionField);
- // Special case for "null"
- if (OptionsParserImpl.isSpecialNullDefault(defaultValueAsString, optionField)) {
- return null;
- }
- boolean allowsMultiple = optionField.getAnnotation(Option.class).allowMultiple();
- // If the option allows multiple values then we intentionally return the empty list as
- // the default value of this option since it is not always the case that an option
- // that allows multiple values will have a converter that returns a list value.
- if (allowsMultiple) {
- return Collections.emptyList();
- }
- // Otherwise try to convert the default value using the converter
- Object convertedValue;
- try {
- convertedValue = converter.convert(defaultValueAsString);
- } catch (OptionsParsingException e) {
- throw new IllegalStateException("OptionsParsingException while "
- + "retrieving default for " + optionField.getName() + ": "
- + e.getMessage());
- }
- return convertedValue;
- }
-
- static OptionsData of(Collection<Class<? extends OptionsBase>> classes) {
- Map<Class<? extends OptionsBase>, Constructor<?>> constructorBuilder = Maps.newHashMap();
- Map<Class<? extends OptionsBase>, List<Field>> allOptionsFieldsBuilder = Maps.newHashMap();
- Map<String, Field> nameToFieldBuilder = Maps.newHashMap();
- Map<Character, Field> abbrevToFieldBuilder = Maps.newHashMap();
- Map<Field, Object> optionDefaultsBuilder = Maps.newHashMap();
- Map<Field, Converter<?>> convertersBuilder = Maps.newHashMap();
- Map<Field, Boolean> allowMultipleBuilder = Maps.newHashMap();
-
- // Read all Option annotations:
- for (Class<? extends OptionsBase> parsedOptionsClass : classes) {
- try {
- Constructor<? extends OptionsBase> constructor =
- parsedOptionsClass.getConstructor(new Class[0]);
- constructorBuilder.put(parsedOptionsClass, constructor);
- } catch (NoSuchMethodException e) {
- throw new IllegalArgumentException(parsedOptionsClass
- + " lacks an accessible default constructor");
- }
- List<Field> fields = getAllAnnotatedFields(parsedOptionsClass);
- allOptionsFieldsBuilder.put(parsedOptionsClass, fields);
-
- for (Field field : fields) {
- Option annotation = field.getAnnotation(Option.class);
-
- // Check that the field type is a List, and that the converter
- // type matches the element type of the list.
- Type fieldType = field.getGenericType();
- if (annotation.allowMultiple()) {
- if (!(fieldType instanceof ParameterizedType)) {
- throw new AssertionError("Type of multiple occurrence option must be a List<...>");
- }
- ParameterizedType pfieldType = (ParameterizedType) fieldType;
- if (pfieldType.getRawType() != List.class) {
- // Throw an assertion, because this indicates an undetected type
- // error in the code.
- throw new AssertionError("Type of multiple occurrence option must be a List<...>");
- }
- fieldType = pfieldType.getActualTypeArguments()[0];
- }
-
- // Get the converter return type.
- @SuppressWarnings("rawtypes")
- Class<? extends Converter> converter = annotation.converter();
- if (converter == Converter.class) {
- Converter<?> actualConverter = OptionsParserImpl.DEFAULT_CONVERTERS.get(fieldType);
- if (actualConverter == null) {
- throw new AssertionError("Cannot find converter for field of type "
- + field.getType() + " named " + field.getName()
- + " in class " + field.getDeclaringClass().getName());
- }
- converter = actualConverter.getClass();
- }
- if (Modifier.isAbstract(converter.getModifiers())) {
- throw new AssertionError("The converter type (" + converter
- + ") must be a concrete type");
- }
- Type converterResultType;
- try {
- Method convertMethod = converter.getMethod("convert", String.class);
- converterResultType = GenericTypeHelper.getActualReturnType(converter, convertMethod);
- } catch (NoSuchMethodException e) {
- throw new AssertionError("A known converter object doesn't implement the convert"
- + " method");
- }
-
- if (annotation.allowMultiple()) {
- if (GenericTypeHelper.getRawType(converterResultType) == List.class) {
- Type elementType =
- ((ParameterizedType) converterResultType).getActualTypeArguments()[0];
- if (!GenericTypeHelper.isAssignableFrom(fieldType, elementType)) {
- throw new AssertionError("If the converter return type of a multiple occurance " +
- "option is a list, then the type of list elements (" + fieldType + ") must be " +
- "assignable from the converter list element type (" + elementType + ")");
- }
- } else {
- if (!GenericTypeHelper.isAssignableFrom(fieldType, converterResultType)) {
- throw new AssertionError("Type of list elements (" + fieldType +
- ") for multiple occurrence option must be assignable from the converter " +
- "return type (" + converterResultType + ")");
- }
- }
- } else {
- if (!GenericTypeHelper.isAssignableFrom(fieldType, converterResultType)) {
- throw new AssertionError("Type of field (" + fieldType +
- ") must be assignable from the converter " +
- "return type (" + converterResultType + ")");
- }
- }
-
- if (annotation.name() == null) {
+ public static OptionsData from(Collection<Class<? extends OptionsBase>> classes) {
+ IsolatedOptionsData isolatedData = IsolatedOptionsData.from(classes);
+
+ // All that's left is to compute expansions.
+ Map<Field, String[]> evaluatedExpansionsBuilder = Maps.newHashMap();
+ for (Map.Entry<String, Field> entry : isolatedData.getAllNamedFields()) {
+ Field field = entry.getValue();
+ Option annotation = field.getAnnotation(Option.class);
+ // Determine either the hard-coded expansion, or the ExpansionFunction class.
+ String[] constExpansion = annotation.expansion();
+ Class<? extends ExpansionFunction> expansionFunctionClass = annotation.expansionFunction();
+ if (constExpansion.length > 0 && usesExpansionFunction(annotation)) {
+ throw new AssertionError(
+ "Cannot set both expansion and expansionFunction for option --" + annotation.name());
+ } else if (constExpansion.length > 0) {
+ evaluatedExpansionsBuilder.put(field, constExpansion);
+ } else if (usesExpansionFunction(annotation)) {
+ if (Modifier.isAbstract(expansionFunctionClass.getModifiers())) {
throw new AssertionError(
- "Option cannot have a null name");
+ "The expansionFunction type " + expansionFunctionClass + " must be a concrete type");
}
- if (nameToFieldBuilder.put(annotation.name(), field) != null) {
- throw new DuplicateOptionDeclarationException(
- "Duplicate option name: --" + annotation.name());
- }
- if (!annotation.oldName().isEmpty()) {
- if (nameToFieldBuilder.put(annotation.oldName(), field) != null) {
- throw new DuplicateOptionDeclarationException(
- "Old option name duplicates option name: --" + annotation.oldName());
- }
- }
- if (annotation.abbrev() != '\0') {
- if (abbrevToFieldBuilder.put(annotation.abbrev(), field) != null) {
- throw new DuplicateOptionDeclarationException(
- "Duplicate option abbrev: -" + annotation.abbrev());
- }
+ // Evaluate the ExpansionFunction.
+ ExpansionFunction instance;
+ try {
+ Constructor<?> constructor = expansionFunctionClass.getConstructor();
+ instance = (ExpansionFunction) constructor.newInstance();
+ } catch (Exception e) {
+ // This indicates an error in the ExpansionFunction, and should be discovered the first
+ // time it is used.
+ throw new AssertionError(e);
}
-
- optionDefaultsBuilder.put(field, retrieveDefaultFromAnnotation(field));
-
- convertersBuilder.put(field, OptionsParserImpl.findConverter(field));
-
- allowMultipleBuilder.put(field, annotation.allowMultiple());
+ String[] expansion = instance.getExpansion(isolatedData);
+ evaluatedExpansionsBuilder.put(field, expansion);
}
}
- return new OptionsData(constructorBuilder, nameToFieldBuilder, abbrevToFieldBuilder,
- allOptionsFieldsBuilder, optionDefaultsBuilder, convertersBuilder, allowMultipleBuilder);
+
+ return new OptionsData(isolatedData, evaluatedExpansionsBuilder);
}
}