aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/common/options/OptionPriority.java
blob: 53f0d75b8d4732aa2c23e8fc8593f382434b0059 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.common.options;

import com.google.common.collect.ImmutableList;
import java.util.Objects;

/**
 * The position of an option in the interpretation order. Options are interpreted using a
 * last-option-wins system for single valued options, and are listed in that order for
 * multiple-valued options.
 *
 * <p>The position of the option is in category order, and within the priority category in index
 * order.
 */
public class OptionPriority implements Comparable<OptionPriority> {
  private final PriorityCategory priorityCategory;
  /**
   * Each option that is passed explicitly has 0 ancestors, so it only has its command line index
   * (or rc index, etc., depending on the category), but expanded options have the command line
   * index of its parent and then its position within the options that were expanded at that point.
   * Since options can expand to expanding options, and --config can expand to expansion options as
   * well, this can technically go arbitrarily deep, but in practice this is very short, of length <
   * 5, most commonly of length 1.
   */
  private final ImmutableList<Integer> priorityIndices;

  private boolean alreadyExpanded = false;

  private OptionPriority(
      PriorityCategory priorityCategory, ImmutableList<Integer> priorityIndices) {
    this.priorityCategory = priorityCategory;
    this.priorityIndices = priorityIndices;
  }

  /** Get the first OptionPriority for that category. */
  static OptionPriority lowestOptionPriorityAtCategory(PriorityCategory category) {
    return new OptionPriority(category, ImmutableList.of(0));
  }

  /**
   * Get the priority for the option following this one. In normal, incremental option parsing, the
   * returned priority would compareTo as after the current one. Does not increment ancestor
   * priorities.
   */
  static OptionPriority nextOptionPriority(OptionPriority priority) {
    int lastElementPosition = priority.priorityIndices.size() - 1;
    return new OptionPriority(
        priority.priorityCategory,
        ImmutableList.<Integer>builder()
            .addAll(priority.priorityIndices.subList(0, lastElementPosition))
            .add(priority.priorityIndices.get(lastElementPosition) + 1)
            .build());
  }

  /**
   * Some options are expanded to other options, and the children options need to have their order
   * preserved while maintaining their position between the options that flank the parent option.
   *
   * @return the priority for the first child of the passed priority. This child's ordering can be
   *     tracked the same way that the parent's was.
   */
  public static OptionPriority getChildPriority(OptionPriority parentPriority)
      throws OptionsParsingException {
    if (parentPriority.alreadyExpanded) {
      throw new OptionsParsingException("Tried to expand option too many times");
    }
    // Prevent this option from being re-expanded.
    parentPriority.alreadyExpanded = true;

    // The child priority has 1 more level of nesting than its parent.
    return new OptionPriority(
        parentPriority.priorityCategory,
        ImmutableList.<Integer>builder().addAll(parentPriority.priorityIndices).add(0).build());
  }

  public PriorityCategory getPriorityCategory() {
    return priorityCategory;
  }

  @Override
  public int compareTo(OptionPriority o) {
    if (priorityCategory.equals(o.priorityCategory)) {
      for (int i = 0; i < priorityIndices.size() && i < o.priorityIndices.size(); ++i) {
        if (!priorityIndices.get(i).equals(o.priorityIndices.get(i))) {
          return priorityIndices.get(i).compareTo(o.priorityIndices.get(i));
        }
      }
      // The values are up to the shorter one's length are the same, so the shorter one is a direct
      // ancestor and comes first.
      return Integer.compare(priorityIndices.size(), o.priorityIndices.size());
    }
    return Integer.compare(priorityCategory.ordinal(), o.priorityCategory.ordinal());
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceof OptionPriority) {
      OptionPriority other = (OptionPriority) o;
      return priorityCategory.equals(other.priorityCategory)
          && priorityIndices.equals(other.priorityIndices);
    }
    return false;
  }

  @Override
  public int hashCode() {
    return Objects.hash(priorityCategory, priorityIndices);
  }

  @Override
  public String toString() {
    return String.format("OptionPriority(%s,%s)", priorityCategory, priorityIndices);
  }

  /**
   * The priority of option values, in order of increasing priority.
   *
   * <p>In general, new values for options can only override values with a lower or equal priority.
   * Option values provided in annotations in an options class are implicitly at the priority {@code
   * DEFAULT}.
   *
   * <p>The ordering of the priorities is the source-code order. This is consistent with the
   * automatically generated {@code compareTo} method as specified by the Java Language
   * Specification. DO NOT change the source-code order of these values, or you will break code that
   * relies on the ordering.
   */
  public enum PriorityCategory {

    /**
     * The priority of values specified in the {@link Option} annotation. This should never be
     * specified in calls to {@link OptionsParser#parse}.
     */
    DEFAULT,

    /**
     * Overrides default options at runtime, while still allowing the values to be overridden
     * manually.
     */
    COMPUTED_DEFAULT,

    /** For options coming from a configuration file or rc file. */
    RC_FILE,

    /** For options coming from the command line. */
    COMMAND_LINE,

    /** For options coming from invocation policy. */
    INVOCATION_POLICY,

    /**
     * This priority can be used to unconditionally override any user-provided options. This should
     * be used rarely and with caution!
     */
    SOFTWARE_REQUIREMENT
  }
}