• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
15 package com.google.devtools.common.options;
16 
17 import com.google.common.escape.CharEscaperBuilder;
18 import com.google.common.escape.Escaper;
19 
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Map.Entry;
23 
24 /**
25  * Base class for all options classes.  Extend this class, adding public
26  * instance fields annotated with @Option.  Then you can create instances
27  * either programmatically:
28  *
29  * <pre>
30  *   X x = Options.getDefaults(X.class);
31  *   x.host = "localhost";
32  *   x.port = 80;
33  * </pre>
34  *
35  * or from an array of command-line arguments:
36  *
37  * <pre>
38  *   OptionsParser parser = OptionsParser.newOptionsParser(X.class);
39  *   parser.parse("--host", "localhost", "--port", "80");
40  *   X x = parser.getOptions(X.class);
41  * </pre>
42  *
43  * <p>Subclasses of OptionsBase <b>must</b> be constructed reflectively,
44  * i.e. using not {@code new MyOptions}, but one of the two methods above
45  * instead.  (Direct construction creates an empty instance, not containing
46  * default values.  This leads to surprising behavior and often
47  * NullPointerExceptions, etc.)
48  */
49 public abstract class OptionsBase {
50 
51   private static final Escaper ESCAPER = new CharEscaperBuilder()
52       .addEscape('\\', "\\\\").addEscape('"', "\\\"").toEscaper();
53 
54   /**
55    * Subclasses must provide a default (no argument) constructor.
56    */
OptionsBase()57   protected OptionsBase() {
58     // There used to be a sanity check here that checks the stack trace of this constructor
59     // invocation; unfortunately, that makes the options construction about 10x slower. So be
60     // careful with how you construct options classes.
61   }
62 
63   /**
64    * Returns this options object in the form of a (new) mapping from option
65    * names, including inherited ones, to option values.  If the public fields
66    * are mutated, this will be reflected in subsequent calls to {@code asMap}.
67    * Mutation of this map by the caller does not affect this options object.
68    */
asMap()69   public final Map<String, Object> asMap() {
70     return OptionsParserImpl.optionsAsMap(this);
71   }
72 
73   @Override
toString()74   public final String toString() {
75     return getClass().getName() + asMap();
76   }
77 
78   /**
79    * Returns a string that uniquely identifies the options. This value is
80    * intended for analysis caching.
81    */
cacheKey()82   public final String cacheKey() {
83     StringBuilder result = new StringBuilder(getClass().getName()).append("{");
84 
85     for (Entry<String, Object> entry : asMap().entrySet()) {
86       result.append(entry.getKey()).append("=");
87 
88       Object value = entry.getValue();
89       // This special case is needed because List.toString() prints the same
90       // ("[]") for an empty list and for a list with a single empty string.
91       if (value instanceof List<?> && ((List<?>) value).isEmpty()) {
92         result.append("EMPTY");
93       } else if (value == null) {
94         result.append("NULL");
95       } else {
96         result
97             .append('"')
98             .append(ESCAPER.escape(value.toString()))
99             .append('"');
100       }
101       result.append(", ");
102     }
103 
104     return result.append("}").toString();
105   }
106 
107   @Override
equals(Object that)108   public final boolean equals(Object that) {
109     return that != null &&
110         this.getClass() == that.getClass() &&
111         this.asMap().equals(((OptionsBase) that).asMap());
112   }
113 
114   @Override
hashCode()115   public final int hashCode() {
116     return this.getClass().hashCode() + asMap().hashCode();
117   }
118 }
119