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.ImmutableList;
18 import com.google.common.collect.ImmutableMap;
19 import java.lang.reflect.Constructor;
20 import java.lang.reflect.Modifier;
21 import java.util.Collection;
22 import java.util.Map;
23 import javax.annotation.concurrent.Immutable;
24 
25 /**
26  * This extends IsolatedOptionsData with information that can only be determined once all the {@link
27  * OptionsBase} subclasses for a parser are known. In particular, this includes expansion
28  * information.
29  */
30 @Immutable
31 final class OptionsData extends IsolatedOptionsData {
32 
33   /** Mapping from each option to the (unparsed) options it expands to, if any. */
34   private final ImmutableMap<OptionDefinition, ImmutableList<String>> evaluatedExpansions;
35 
36   /** Construct {@link OptionsData} by extending an {@link IsolatedOptionsData} with new info. */
OptionsData( IsolatedOptionsData base, Map<OptionDefinition, ImmutableList<String>> evaluatedExpansions)37   private OptionsData(
38       IsolatedOptionsData base, Map<OptionDefinition, ImmutableList<String>> evaluatedExpansions) {
39     super(base);
40     this.evaluatedExpansions = ImmutableMap.copyOf(evaluatedExpansions);
41   }
42 
43   private static final ImmutableList<String> EMPTY_EXPANSION = ImmutableList.<String>of();
44 
45   /**
46    * Returns the expansion of an options field, regardless of whether it was defined using {@link
47    * Option#expansion} or {@link Option#expansionFunction}. If the field is not an expansion option,
48    * returns an empty array.
49    */
getEvaluatedExpansion(OptionDefinition optionDefinition)50   public ImmutableList<String> getEvaluatedExpansion(OptionDefinition optionDefinition) {
51     ImmutableList<String> result = evaluatedExpansions.get(optionDefinition);
52     return result != null ? result : EMPTY_EXPANSION;
53   }
54 
55   /**
56    * Constructs an {@link OptionsData} object for a parser that knows about the given {@link
57    * OptionsBase} classes. In addition to the work done to construct the {@link
58    * IsolatedOptionsData}, this also computes expansion information. If an option has static
59    * expansions or uses an expansion function that takes a Void object, try to precalculate the
60    * expansion here.
61    */
from(Collection<Class<? extends OptionsBase>> classes)62   static OptionsData from(Collection<Class<? extends OptionsBase>> classes) {
63     IsolatedOptionsData isolatedData = IsolatedOptionsData.from(classes);
64 
65     // All that's left is to compute expansions.
66     ImmutableMap.Builder<OptionDefinition, ImmutableList<String>> evaluatedExpansionsBuilder =
67         ImmutableMap.builder();
68     for (Map.Entry<String, OptionDefinition> entry : isolatedData.getAllOptionDefinitions()) {
69       OptionDefinition optionDefinition = entry.getValue();
70       // Determine either the hard-coded expansion, or the ExpansionFunction class. The
71       // OptionProcessor checks at compile time that these aren't used together.
72       String[] constExpansion = optionDefinition.getOptionExpansion();
73       Class<? extends ExpansionFunction> expansionFunctionClass =
74           optionDefinition.getExpansionFunction();
75       if (constExpansion.length > 0) {
76         evaluatedExpansionsBuilder.put(optionDefinition, ImmutableList.copyOf(constExpansion));
77       } else if (optionDefinition.usesExpansionFunction()) {
78         if (Modifier.isAbstract(expansionFunctionClass.getModifiers())) {
79           throw new AssertionError(
80               "The expansionFunction type " + expansionFunctionClass + " must be a concrete type");
81         }
82         // Evaluate the ExpansionFunction.
83         ExpansionFunction instance;
84         try {
85           Constructor<?> constructor = expansionFunctionClass.getConstructor();
86           instance = (ExpansionFunction) constructor.newInstance();
87         } catch (Exception e) {
88           // This indicates an error in the ExpansionFunction, and should be discovered the first
89           // time it is used.
90           throw new AssertionError(e);
91         }
92         ImmutableList<String> expansion = instance.getExpansion(isolatedData);
93         evaluatedExpansionsBuilder.put(optionDefinition, expansion);
94       }
95     }
96     return new OptionsData(isolatedData, evaluatedExpansionsBuilder.build());
97   }
98 }
99