aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/common/options/IsolatedOptionsData.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/common/options/IsolatedOptionsData.java')
-rw-r--r--src/main/java/com/google/devtools/common/options/IsolatedOptionsData.java279
1 files changed, 134 insertions, 145 deletions
diff --git a/src/main/java/com/google/devtools/common/options/IsolatedOptionsData.java b/src/main/java/com/google/devtools/common/options/IsolatedOptionsData.java
index bd74b67990..e087dbbb56 100644
--- a/src/main/java/com/google/devtools/common/options/IsolatedOptionsData.java
+++ b/src/main/java/com/google/devtools/common/options/IsolatedOptionsData.java
@@ -16,7 +16,6 @@ package com.google.devtools.common.options;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Ordering;
import com.google.devtools.common.options.OptionsParser.ConstructionException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -24,13 +23,14 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
-import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import javax.annotation.concurrent.Immutable;
/**
@@ -57,20 +57,21 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
private final ImmutableMap<Class<? extends OptionsBase>, Constructor<?>> optionsClasses;
/**
- * Mapping from option name to {@code @Option}-annotated field. Entries appear ordered first by
- * their options class (the order in which they were passed to {@link #from(Collection)}, and then
- * in alphabetic order within each options class.
+ * Mapping from option name to {@code OptionDefinition}. Entries appear ordered first by their
+ * options class (the order in which they were passed to {@link #from(Collection)}, and then in
+ * alphabetic order within each options class.
*/
- private final ImmutableMap<String, Field> nameToField;
+ private final ImmutableMap<String, OptionDefinition> nameToField;
- /** Mapping from option abbreviation to {@code Option}-annotated field (unordered). */
- private final ImmutableMap<Character, Field> abbrevToField;
+ /** Mapping from option abbreviation to {@code OptionDefinition} (unordered). */
+ private final ImmutableMap<Character, OptionDefinition> abbrevToField;
/**
- * Mapping from options class to a list of all {@code Option}-annotated fields in that class. The
- * map entries are unordered, but the fields in the lists are ordered alphabetically.
+ * Mapping from options class to a list of all {@code OptionFields} in that class. The map entries
+ * are unordered, but the fields in the lists are ordered alphabetically.
*/
- private final ImmutableMap<Class<? extends OptionsBase>, ImmutableList<Field>> allOptionsFields;
+ private final ImmutableMap<Class<? extends OptionsBase>, ImmutableList<OptionDefinition>>
+ allOptionsFields;
/**
* Mapping from each {@code Option}-annotated field to the default value for that field
@@ -79,20 +80,20 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
* <p>(This is immutable like the others, but uses {@code Collections.unmodifiableMap} to support
* null values.)
*/
- private final Map<Field, Object> optionDefaults;
+ private final Map<OptionDefinition, Object> optionDefaults;
/**
* Mapping from each {@code Option}-annotated field to the proper converter (unordered).
*
* @see #findConverter
*/
- private final ImmutableMap<Field, Converter<?>> converters;
+ private final ImmutableMap<OptionDefinition, Converter<?>> converters;
/**
* Mapping from each {@code Option}-annotated field to a boolean for whether that field allows
* multiple values (unordered).
*/
- private final ImmutableMap<Field, Boolean> allowMultiple;
+ private final ImmutableMap<OptionDefinition, Boolean> allowMultiple;
/**
* Mapping from each options class to whether or not it has the {@link UsesOnlyCoreTypes}
@@ -105,14 +106,13 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
"undocumented", "hidden", "internal");
private IsolatedOptionsData(
- Map<Class<? extends OptionsBase>,
- Constructor<?>> optionsClasses,
- Map<String, Field> nameToField,
- Map<Character, Field> abbrevToField,
- Map<Class<? extends OptionsBase>, ImmutableList<Field>> allOptionsFields,
- Map<Field, Object> optionDefaults,
- Map<Field, Converter<?>> converters,
- Map<Field, Boolean> allowMultiple,
+ Map<Class<? extends OptionsBase>, Constructor<?>> optionsClasses,
+ Map<String, OptionDefinition> nameToField,
+ Map<Character, OptionDefinition> abbrevToField,
+ Map<Class<? extends OptionsBase>, ImmutableList<OptionDefinition>> allOptionsFields,
+ Map<OptionDefinition, Object> optionDefaults,
+ Map<OptionDefinition, Converter<?>> converters,
+ Map<OptionDefinition, Boolean> allowMultiple,
Map<Class<? extends OptionsBase>, Boolean> usesOnlyCoreTypes) {
this.optionsClasses = ImmutableMap.copyOf(optionsClasses);
this.nameToField = ImmutableMap.copyOf(nameToField);
@@ -150,7 +150,7 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
return (Constructor<T>) optionsClasses.get(clazz);
}
- public Field getFieldFromName(String name) {
+ public OptionDefinition getFieldFromName(String name) {
return nameToField.get(name);
}
@@ -159,11 +159,11 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
* objects. Entries appear ordered first by their options class (the order in which they were
* passed to {@link #from(Collection)}, and then in alphabetic order within each options class.
*/
- public Iterable<Map.Entry<String, Field>> getAllNamedFields() {
+ public Iterable<Map.Entry<String, OptionDefinition>> getAllNamedFields() {
return nameToField.entrySet();
}
- public Field getFieldForAbbrev(char abbrev) {
+ public OptionDefinition getFieldForAbbrev(char abbrev) {
return abbrevToField.get(abbrev);
}
@@ -171,20 +171,21 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
* Returns a list of all {@link Field} objects for options in the given options class, ordered
* alphabetically by option name.
*/
- public ImmutableList<Field> getFieldsForClass(Class<? extends OptionsBase> optionsClass) {
+ public ImmutableList<OptionDefinition> getOptionDefinitionsFromClass(
+ Class<? extends OptionsBase> optionsClass) {
return allOptionsFields.get(optionsClass);
}
- public Object getDefaultValue(Field field) {
- return optionDefaults.get(field);
+ public Object getDefaultValue(OptionDefinition optionDefinition) {
+ return optionDefaults.get(optionDefinition);
}
- public Converter<?> getConverter(Field field) {
- return converters.get(field);
+ public Converter<?> getConverter(OptionDefinition optionDefinition) {
+ return converters.get(optionDefinition);
}
- public boolean getAllowMultiple(Field field) {
- return allowMultiple.get(field);
+ public boolean getAllowMultiple(OptionDefinition optionDefinition) {
+ return allowMultiple.get(optionDefinition);
}
public boolean getUsesOnlyCoreTypes(Class<? extends OptionsBase> optionsClass) {
@@ -196,9 +197,9 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
* that does use it, asserts that the type is a {@code List<T>} and returns its element type
* {@code T}.
*/
- private static Type getFieldSingularType(Field field, Option annotation) {
- Type fieldType = field.getGenericType();
- if (annotation.allowMultiple()) {
+ private static Type getFieldSingularType(OptionDefinition optionDefinition) {
+ Type fieldType = optionDefinition.getField().getGenericType();
+ if (optionDefinition.allowsMultiple()) {
// If the type isn't a List<T>, this is an error in the option's declaration.
if (!(fieldType instanceof ParameterizedType)) {
throw new ConstructionException("Type of multiple occurrence option must be a List<...>");
@@ -217,43 +218,24 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
*
* <p>Can be used for usage help and controlling whether the "no" prefix is allowed.
*/
- boolean isBooleanField(Field field) {
- return isBooleanField(field, getConverter(field));
+ boolean isBooleanField(OptionDefinition optionDefinition) {
+ return isBooleanField(optionDefinition, getConverter(optionDefinition));
}
- private static boolean isBooleanField(Field field, Converter<?> converter) {
- return field.getType().equals(boolean.class)
- || field.getType().equals(TriState.class)
+ private static boolean isBooleanField(OptionDefinition optionDefinition, Converter<?> converter) {
+ return optionDefinition.getType().equals(boolean.class)
+ || optionDefinition.getType().equals(TriState.class)
|| converter instanceof BoolOrEnumConverter;
}
- /** Returns whether a field has Void type. */
- static boolean isVoidField(Field field) {
- return field.getType().equals(Void.class);
- }
-
- /** Returns whether the arg is an expansion option. */
- public static boolean isExpansionOption(Option annotation) {
- return (annotation.expansion().length > 0 || OptionsData.usesExpansionFunction(annotation));
- }
-
- /**
- * Returns whether the arg is an expansion option defined by an expansion function (and not a
- * constant expansion value).
- */
- static boolean usesExpansionFunction(Option annotation) {
- return annotation.expansionFunction() != ExpansionFunction.class;
- }
-
/**
* Given an {@code @Option}-annotated field, retrieves the {@link Converter} that will be used,
* taking into account the default converters if an explicit one is not specified.
*/
- private static Converter<?> findConverter(Field optionField) {
- Option annotation = optionField.getAnnotation(Option.class);
- if (annotation.converter() == Converter.class) {
+ private static Converter<?> findConverter(OptionDefinition optionDefinition) {
+ if (optionDefinition.getProvidedConverter() == Converter.class) {
// No converter provided, use the default one.
- Type type = getFieldSingularType(optionField, annotation);
+ Type type = getFieldSingularType(optionDefinition);
Converter<?> converter = Converters.DEFAULT_CONVERTERS.get(type);
if (converter == null) {
throw new ConstructionException(
@@ -261,13 +243,13 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
+ type
+ "; possible fix: add "
+ "converter=... to @Option annotation for "
- + optionField.getName());
+ + optionDefinition.getField().getName());
}
return converter;
}
try {
// Instantiate the given Converter class.
- Class<?> converter = annotation.converter();
+ Class<?> converter = optionDefinition.getProvidedConverter();
Constructor<?> constructor = converter.getConstructor();
return (Converter<?>) constructor.newInstance();
} catch (Exception e) {
@@ -276,40 +258,32 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
throw new ConstructionException(e);
}
}
-
- private static final Ordering<Field> fieldOrdering =
- new Ordering<Field>() {
- @Override
- public int compare(Field f1, Field f2) {
- String n1 = f1.getAnnotation(Option.class).name();
- String n2 = f2.getAnnotation(Option.class).name();
- return n1.compareTo(n2);
- }
- };
-
- /**
- * Return all {@code @Option}-annotated fields, alphabetically ordered by their option name (not
- * their field name).
- */
- private static ImmutableList<Field> getAllAnnotatedFieldsSorted(
+ /** Returns all {@code optionDefinitions}, ordered by their option name (not their field name). */
+ private static ImmutableList<OptionDefinition> getAllOptionDefinitionsSorted(
Class<? extends OptionsBase> optionsClass) {
- List<Field> unsortedFields = new ArrayList<>();
- for (Field field : optionsClass.getFields()) {
- if (field.isAnnotationPresent(Option.class)) {
- unsortedFields.add(field);
- }
- }
- return fieldOrdering.immutableSortedCopy(unsortedFields);
+ return Arrays.stream(optionsClass.getFields())
+ .map(field -> {
+ try {
+ return OptionDefinition.extractOptionDefinition(field);
+ } catch (ConstructionException e) {
+ // Ignore non-@Option annotated fields. Requiring all fields in the OptionsBase to be
+ // @Option-annotated requires a depot cleanup.
+ return null;
+ }
+ })
+ .filter(Objects::nonNull)
+ .sorted(OptionDefinition.BY_OPTION_NAME)
+ .collect(ImmutableList.toImmutableList());
}
- private static Object retrieveDefaultFromAnnotation(Field optionField) {
- Converter<?> converter = findConverter(optionField);
- String defaultValueAsString = OptionsParserImpl.getDefaultOptionString(optionField);
+ private static Object retrieveDefaultValue(OptionDefinition optionDefinition) {
+ Converter<?> converter = findConverter(optionDefinition);
+ String defaultValueAsString = optionDefinition.getUnparsedDefaultValue();
// Special case for "null"
- if (OptionsParserImpl.isSpecialNullDefault(defaultValueAsString, optionField)) {
+ if (optionDefinition.isSpecialNullDefault()) {
return null;
}
- boolean allowsMultiple = optionField.getAnnotation(Option.class).allowMultiple();
+ boolean allowsMultiple = optionDefinition.allowsMultiple();
// 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.
@@ -321,17 +295,18 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
try {
convertedValue = converter.convert(defaultValueAsString);
} catch (OptionsParsingException e) {
- throw new IllegalStateException("OptionsParsingException while "
- + "retrieving default for " + optionField.getName() + ": "
- + e.getMessage());
+ throw new IllegalStateException(
+ "OptionsParsingException while "
+ + "retrieving default for "
+ + optionDefinition.getField().getName()
+ + ": "
+ + e.getMessage());
}
return convertedValue;
}
private static <A> void checkForCollisions(
- Map<A, Field> aFieldMap,
- A optionName,
- String description) {
+ Map<A, OptionDefinition> aFieldMap, A optionName, String description) {
if (aFieldMap.containsKey(optionName)) {
throw new DuplicateOptionDeclarationException(
"Duplicate option name, due to " + description + ": --" + optionName);
@@ -354,7 +329,7 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
}
private static void checkAndUpdateBooleanAliases(
- Map<String, Field> nameToFieldMap,
+ Map<String, OptionDefinition> nameToFieldMap,
Map<String, String> booleanAliasMap,
String optionName) {
// Check that the negating alias does not conflict with existing flags.
@@ -418,13 +393,13 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
static IsolatedOptionsData from(Collection<Class<? extends OptionsBase>> classes) {
// Mind which fields have to preserve order.
Map<Class<? extends OptionsBase>, Constructor<?>> constructorBuilder = new LinkedHashMap<>();
- Map<Class<? extends OptionsBase>, ImmutableList<Field>> allOptionsFieldsBuilder =
+ Map<Class<? extends OptionsBase>, ImmutableList<OptionDefinition>> allOptionsFieldsBuilder =
new HashMap<>();
- Map<String, Field> nameToFieldBuilder = new LinkedHashMap<>();
- Map<Character, Field> abbrevToFieldBuilder = new HashMap<>();
- Map<Field, Object> optionDefaultsBuilder = new HashMap<>();
- Map<Field, Converter<?>> convertersBuilder = new HashMap<>();
- Map<Field, Boolean> allowMultipleBuilder = new HashMap<>();
+ Map<String, OptionDefinition> nameToFieldBuilder = new LinkedHashMap<>();
+ Map<Character, OptionDefinition> abbrevToFieldBuilder = new HashMap<>();
+ Map<OptionDefinition, Object> optionDefaultsBuilder = new HashMap<>();
+ Map<OptionDefinition, Converter<?>> convertersBuilder = new HashMap<>();
+ Map<OptionDefinition, Boolean> allowMultipleBuilder = new HashMap<>();
// Maps the negated boolean flag aliases to the original option name.
Map<String, String> booleanAliasMap = new HashMap<>();
@@ -441,44 +416,54 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
throw new IllegalArgumentException(parsedOptionsClass
+ " lacks an accessible default constructor");
}
- ImmutableList<Field> fields = getAllAnnotatedFieldsSorted(parsedOptionsClass);
- allOptionsFieldsBuilder.put(parsedOptionsClass, fields);
+ ImmutableList<OptionDefinition> optionDefinitions =
+ getAllOptionDefinitionsSorted(parsedOptionsClass);
+ allOptionsFieldsBuilder.put(parsedOptionsClass, optionDefinitions);
- for (Field field : fields) {
- Option annotation = field.getAnnotation(Option.class);
- String optionName = annotation.name();
+ for (OptionDefinition optionDefinition : optionDefinitions) {
+ String optionName = optionDefinition.getOptionName();
+
+ // Check that the option makes sense on its own, as defined.
if (optionName == null) {
throw new ConstructionException("Option cannot have a null name");
}
- if (DEPRECATED_CATEGORIES.contains(annotation.category())) {
+ if (DEPRECATED_CATEGORIES.contains(optionDefinition.getOptionCategory())) {
throw new ConstructionException(
"Documentation level is no longer read from the option category. Category \""
- + annotation.category() + "\" in option \"" + optionName + "\" is disallowed.");
+ + optionDefinition.getOptionCategory()
+ + "\" in option \""
+ + optionName
+ + "\" is disallowed.");
}
- checkEffectTagRationality(optionName, annotation.effectTags());
+ checkEffectTagRationality(optionName, optionDefinition.getOptionEffectTags());
checkMetadataTagAndCategoryRationality(
- optionName, annotation.metadataTags(), annotation.documentationCategory());
-
- Type fieldType = getFieldSingularType(field, annotation);
+ optionName,
+ optionDefinition.getOptionMetadataTags(),
+ optionDefinition.getDocumentationCategory());
+ Type fieldType = getFieldSingularType(optionDefinition);
// For simple, static expansions, don't accept non-Void types.
- if (annotation.expansion().length != 0 && !isVoidField(field)) {
+ if (optionDefinition.getOptionExpansion().length != 0 && !optionDefinition.isVoidField()) {
throw new ConstructionException(
"Option "
- + optionName
+ + optionDefinition.getOptionName()
+ " is an expansion flag with a static expansion, but does not have Void type.");
}
- // Get the converter return type.
+ // Get the converter's return type to check that it matches the option type.
@SuppressWarnings("rawtypes")
- Class<? extends Converter> converterClass = annotation.converter();
+ Class<? extends Converter> converterClass = optionDefinition.getProvidedConverter();
if (converterClass == Converter.class) {
Converter<?> actualConverter = Converters.DEFAULT_CONVERTERS.get(fieldType);
if (actualConverter == null) {
- throw new ConstructionException("Cannot find converter for field of type "
- + field.getType() + " named " + field.getName()
- + " in class " + field.getDeclaringClass().getName());
+ throw new ConstructionException(
+ "Cannot find converter for field of type "
+ + optionDefinition.getType()
+ + " named "
+ + optionDefinition.getField().getName()
+ + " in class "
+ + optionDefinition.getField().getDeclaringClass().getName());
}
converterClass = actualConverter.getClass();
}
@@ -495,10 +480,10 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
throw new ConstructionException(
"A known converter object doesn't implement the convert method");
}
- Converter<?> converter = findConverter(field);
- convertersBuilder.put(field, converter);
+ Converter<?> converter = findConverter(optionDefinition);
+ convertersBuilder.put(optionDefinition, converter);
- if (annotation.allowMultiple()) {
+ if (optionDefinition.allowsMultiple()) {
if (GenericTypeHelper.getRawType(converterResultType) == List.class) {
Type elementType =
((ParameterizedType) converterResultType).getActualTypeArguments()[0];
@@ -533,47 +518,51 @@ public class IsolatedOptionsData extends OpaqueOptionsData {
}
}
- if (isBooleanField(field, converter)) {
+ if (isBooleanField(optionDefinition, converter)) {
checkAndUpdateBooleanAliases(nameToFieldBuilder, booleanAliasMap, optionName);
}
checkForCollisions(nameToFieldBuilder, optionName, "option");
checkForBooleanAliasCollisions(booleanAliasMap, optionName, "option");
- nameToFieldBuilder.put(optionName, field);
+ nameToFieldBuilder.put(optionName, optionDefinition);
- if (!annotation.oldName().isEmpty()) {
- String oldName = annotation.oldName();
+ if (!optionDefinition.getOldOptionName().isEmpty()) {
+ String oldName = optionDefinition.getOldOptionName();
checkForCollisions(nameToFieldBuilder, oldName, "old option name");
checkForBooleanAliasCollisions(booleanAliasMap, oldName, "old option name");
- nameToFieldBuilder.put(annotation.oldName(), field);
+ nameToFieldBuilder.put(optionDefinition.getOldOptionName(), optionDefinition);
// If boolean, repeat the alias dance for the old name.
- if (isBooleanField(field, converter)) {
+ if (isBooleanField(optionDefinition, converter)) {
checkAndUpdateBooleanAliases(nameToFieldBuilder, booleanAliasMap, oldName);
}
}
- if (annotation.abbrev() != '\0') {
- checkForCollisions(abbrevToFieldBuilder, annotation.abbrev(), "option abbreviation");
- abbrevToFieldBuilder.put(annotation.abbrev(), field);
+ if (optionDefinition.getAbbreviation() != '\0') {
+ checkForCollisions(
+ abbrevToFieldBuilder, optionDefinition.getAbbreviation(), "option abbreviation");
+ abbrevToFieldBuilder.put(optionDefinition.getAbbreviation(), optionDefinition);
}
- optionDefaultsBuilder.put(field, retrieveDefaultFromAnnotation(field));
-
- allowMultipleBuilder.put(field, annotation.allowMultiple());
-
- }
+ optionDefaultsBuilder.put(optionDefinition, retrieveDefaultValue(optionDefinition));
+ allowMultipleBuilder.put(optionDefinition, optionDefinition.allowsMultiple());
+ }
boolean usesOnlyCoreTypes = parsedOptionsClass.isAnnotationPresent(UsesOnlyCoreTypes.class);
if (usesOnlyCoreTypes) {
// Validate that @UsesOnlyCoreTypes was used correctly.
- for (Field field : fields) {
+ for (OptionDefinition optionDefinition : optionDefinitions) {
// The classes in coreTypes are all final. But even if they weren't, we only want to check
// for exact matches; subclasses would not be considered core types.
- if (!UsesOnlyCoreTypes.CORE_TYPES.contains(field.getType())) {
+ if (!UsesOnlyCoreTypes.CORE_TYPES.contains(optionDefinition.getType())) {
throw new ConstructionException(
- "Options class '" + parsedOptionsClass.getName() + "' is marked as "
- + "@UsesOnlyCoreTypes, but field '" + field.getName()
- + "' has type '" + field.getType().getName() + "'");
+ "Options class '"
+ + parsedOptionsClass.getName()
+ + "' is marked as "
+ + "@UsesOnlyCoreTypes, but field '"
+ + optionDefinition.getField().getName()
+ + "' has type '"
+ + optionDefinition.getType().getName()
+ + "'");
}
}
}