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 
OptionPriority( PriorityCategory priorityCategory, ImmutableList<Integer> priorityIndices)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. */
lowestOptionPriorityAtCategory(PriorityCategory 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    */
nextOptionPriority(OptionPriority priority)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    */
getChildPriority(OptionPriority parentPriority)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 
getPriorityCategory()88   public PriorityCategory getPriorityCategory() {
89     return priorityCategory;
90   }
91 
92   @Override
compareTo(OptionPriority o)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
equals(Object o)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
hashCode()118   public int hashCode() {
119     return Objects.hash(priorityCategory, priorityIndices);
120   }
121 
122   @Override
toString()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