1 // Copyright 2017 The Bazel Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.devtools.common.options; 16 17 import com.google.common.collect.ImmutableMap; 18 import com.google.common.collect.Maps; 19 import java.lang.reflect.Constructor; 20 import java.lang.reflect.Field; 21 import java.lang.reflect.Modifier; 22 import java.util.Collection; 23 import java.util.Map; 24 import javax.annotation.concurrent.Immutable; 25 26 /** 27 * This extends IsolatedOptionsData with information that can only be determined once all the {@link 28 * OptionsBase} subclasses for a parser are known. In particular, this includes expansion 29 * information. 30 */ 31 @Immutable 32 final class OptionsData extends IsolatedOptionsData { 33 34 /** 35 * Mapping from each Option-annotated field with a {@code String[]} expansion to that expansion. 36 */ 37 // TODO(brandjon): This is technically not necessarily immutable due to String[], and should use 38 // ImmutableList. Either fix this or remove @Immutable. 39 private final ImmutableMap<Field, String[]> evaluatedExpansions; 40 41 /** Construct {@link OptionsData} by extending an {@link IsolatedOptionsData} with new info. */ OptionsData(IsolatedOptionsData base, Map<Field, String[]> evaluatedExpansions)42 private OptionsData(IsolatedOptionsData base, Map<Field, String[]> evaluatedExpansions) { 43 super(base); 44 this.evaluatedExpansions = ImmutableMap.copyOf(evaluatedExpansions); 45 } 46 47 private static final String[] EMPTY_EXPANSION = new String[] {}; 48 49 /** 50 * Returns the expansion of an options field, regardless of whether it was defined using {@link 51 * Option#expansion} or {@link Option#expansionFunction}. If the field is not an expansion option, 52 * returns an empty array. 53 */ getEvaluatedExpansion(Field field)54 public String[] getEvaluatedExpansion(Field field) { 55 String[] result = evaluatedExpansions.get(field); 56 return result != null ? result : EMPTY_EXPANSION; 57 } 58 59 /** 60 * Constructs an {@link OptionsData} object for a parser that knows about the given {@link 61 * OptionsBase} classes. In addition to the work done to construct the {@link 62 * IsolatedOptionsData}, this also computes expansion information. 63 */ from(Collection<Class<? extends OptionsBase>> classes)64 public static OptionsData from(Collection<Class<? extends OptionsBase>> classes) { 65 IsolatedOptionsData isolatedData = IsolatedOptionsData.from(classes); 66 67 // All that's left is to compute expansions. 68 Map<Field, String[]> evaluatedExpansionsBuilder = Maps.newHashMap(); 69 for (Map.Entry<String, Field> entry : isolatedData.getAllNamedFields()) { 70 Field field = entry.getValue(); 71 Option annotation = field.getAnnotation(Option.class); 72 // Determine either the hard-coded expansion, or the ExpansionFunction class. 73 String[] constExpansion = annotation.expansion(); 74 Class<? extends ExpansionFunction> expansionFunctionClass = annotation.expansionFunction(); 75 if (constExpansion.length > 0 && usesExpansionFunction(annotation)) { 76 throw new AssertionError( 77 "Cannot set both expansion and expansionFunction for option --" + annotation.name()); 78 } else if (constExpansion.length > 0) { 79 evaluatedExpansionsBuilder.put(field, constExpansion); 80 } else if (usesExpansionFunction(annotation)) { 81 if (Modifier.isAbstract(expansionFunctionClass.getModifiers())) { 82 throw new AssertionError( 83 "The expansionFunction type " + expansionFunctionClass + " must be a concrete type"); 84 } 85 // Evaluate the ExpansionFunction. 86 ExpansionFunction instance; 87 try { 88 Constructor<?> constructor = expansionFunctionClass.getConstructor(); 89 instance = (ExpansionFunction) constructor.newInstance(); 90 } catch (Exception e) { 91 // This indicates an error in the ExpansionFunction, and should be discovered the first 92 // time it is used. 93 throw new AssertionError(e); 94 } 95 String[] expansion = instance.getExpansion(isolatedData); 96 evaluatedExpansionsBuilder.put(field, expansion); 97 } 98 } 99 100 return new OptionsData(isolatedData, evaluatedExpansionsBuilder); 101 } 102 } 103