Home | History | Annotate | Download | only in options
      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. */
     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    */
     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    */
     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