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.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. */
     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    */
     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    */
     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