Home | History | Annotate | Download | only in options
      1 // Copyright 2014 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 package com.google.devtools.common.options;
     15 
     16 import com.google.common.collect.ImmutableList;
     17 import java.util.Objects;
     18 
     19 /**
     20  * The position of an option in the interpretation order. Options are interpreted using a
     21  * last-option-wins system for single valued options, and are listed in that order for
     22  * multiple-valued options.
     23  *
     24  * <p>The position of the option is in category order, and within the priority category in index
     25  * order.
     26  */
     27 public class OptionPriority implements Comparable<OptionPriority> {
     28   private final PriorityCategory priorityCategory;
     29   /**
     30    * Each option that is passed explicitly has 0 ancestors, so it only has its command line index
     31    * (or rc index, etc., depending on the category), but expanded options have the command line
     32    * index of its parent and then its position within the options that were expanded at that point.
     33    * Since options can expand to expanding options, and --config can expand to expansion options as
     34    * well, this can technically go arbitrarily deep, but in practice this is very short, of length <
     35    * 5, most commonly of length 1.
     36    */
     37   private final ImmutableList<Integer> priorityIndices;
     38 
     39   private boolean alreadyExpanded = false;
     40 
     41   private OptionPriority(
     42       PriorityCategory priorityCategory, ImmutableList<Integer> priorityIndices) {
     43     this.priorityCategory = priorityCategory;
     44     this.priorityIndices = priorityIndices;
     45   }
     46 
     47   /** Get the first OptionPriority for that category. */
     48   static OptionPriority lowestOptionPriorityAtCategory(PriorityCategory category) {
     49     return new OptionPriority(category, ImmutableList.of(0));
     50   }
     51 
     52   /**
     53    * Get the priority for the option following this one. In normal, incremental option parsing, the
     54    * returned priority would compareTo as after the current one. Does not increment ancestor
     55    * priorities.
     56    */
     57   static OptionPriority nextOptionPriority(OptionPriority priority) {
     58     int lastElementPosition = priority.priorityIndices.size() - 1;
     59     return new OptionPriority(
     60         priority.priorityCategory,
     61         ImmutableList.<Integer>builder()
     62             .addAll(priority.priorityIndices.subList(0, lastElementPosition))
     63             .add(priority.priorityIndices.get(lastElementPosition) + 1)
     64             .build());
     65   }
     66 
     67   /**
     68    * Some options are expanded to other options, and the children options need to have their order
     69    * preserved while maintaining their position between the options that flank the parent option.
     70    *
     71    * @return the priority for the first child of the passed priority. This child's ordering can be
     72    *     tracked the same way that the parent's was.
     73    */
     74   public static OptionPriority getChildPriority(OptionPriority parentPriority)
     75       throws OptionsParsingException {
     76     if (parentPriority.alreadyExpanded) {
     77       throw new OptionsParsingException("Tried to expand option too many times");
     78     }
     79     // Prevent this option from being re-expanded.
     80     parentPriority.alreadyExpanded = true;
     81 
     82     // The child priority has 1 more level of nesting than its parent.
     83     return new OptionPriority(
     84         parentPriority.priorityCategory,
     85         ImmutableList.<Integer>builder().addAll(parentPriority.priorityIndices).add(0).build());
     86   }
     87 
     88   public PriorityCategory getPriorityCategory() {
     89     return priorityCategory;
     90   }
     91 
     92   @Override
     93   public int compareTo(OptionPriority o) {
     94     if (priorityCategory.equals(o.priorityCategory)) {
     95       for (int i = 0; i < priorityIndices.size() && i < o.priorityIndices.size(); ++i) {
     96         if (!priorityIndices.get(i).equals(o.priorityIndices.get(i))) {
     97           return priorityIndices.get(i).compareTo(o.priorityIndices.get(i));
     98         }
     99       }
    100       // The values are up to the shorter one's length are the same, so the shorter one is a direct
    101       // ancestor and comes first.
    102       return Integer.compare(priorityIndices.size(), o.priorityIndices.size());
    103     }
    104     return Integer.compare(priorityCategory.ordinal(), o.priorityCategory.ordinal());
    105   }
    106 
    107   @Override
    108   public boolean equals(Object o) {
    109     if (o instanceof OptionPriority) {
    110       OptionPriority other = (OptionPriority) o;
    111       return priorityCategory.equals(other.priorityCategory)
    112           && priorityIndices.equals(other.priorityIndices);
    113     }
    114     return false;
    115   }
    116 
    117   @Override
    118   public int hashCode() {
    119     return Objects.hash(priorityCategory, priorityIndices);
    120   }
    121 
    122   @Override
    123   public String toString() {
    124     return String.format("OptionPriority(%s,%s)", priorityCategory, priorityIndices);
    125   }
    126 
    127   /**
    128    * The priority of option values, in order of increasing priority.
    129    *
    130    * <p>In general, new values for options can only override values with a lower or equal priority.
    131    * Option values provided in annotations in an options class are implicitly at the priority {@code
    132    * DEFAULT}.
    133    *
    134    * <p>The ordering of the priorities is the source-code order. This is consistent with the
    135    * automatically generated {@code compareTo} method as specified by the Java Language
    136    * Specification. DO NOT change the source-code order of these values, or you will break code that
    137    * relies on the ordering.
    138    */
    139   public enum PriorityCategory {
    140 
    141     /**
    142      * The priority of values specified in the {@link Option} annotation. This should never be
    143      * specified in calls to {@link OptionsParser#parse}.
    144      */
    145     DEFAULT,
    146 
    147     /**
    148      * Overrides default options at runtime, while still allowing the values to be overridden
    149      * manually.
    150      */
    151     COMPUTED_DEFAULT,
    152 
    153     /** For options coming from a configuration file or rc file. */
    154     RC_FILE,
    155 
    156     /** For options coming from the command line. */
    157     COMMAND_LINE,
    158 
    159     /** For options coming from invocation policy. */
    160     INVOCATION_POLICY,
    161 
    162     /**
    163      * This priority can be used to unconditionally override any user-provided options. This should
    164      * be used rarely and with caution!
    165      */
    166     SOFTWARE_REQUIREMENT
    167   }
    168 }
    169