• 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 package com.google.devtools.common.options;
15 
16 import com.google.common.base.Splitter;
17 import com.google.common.collect.ImmutableList;
18 import com.google.common.collect.Maps;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.logging.Level;
23 import java.util.regex.Pattern;
24 import java.util.regex.PatternSyntaxException;
25 
26 /**
27  * Some convenient converters used by blaze. Note: These are specific to
28  * blaze.
29  */
30 public final class Converters {
31 
32   /** Standard converter for booleans. Accepts common shorthands/synonyms. */
33   public static class BooleanConverter implements Converter<Boolean> {
34     @Override
convert(String input)35     public Boolean convert(String input) throws OptionsParsingException {
36       if (input == null) {
37         return false;
38       }
39       input = input.toLowerCase();
40       if (input.equals("true")
41           || input.equals("1")
42           || input.equals("yes")
43           || input.equals("t")
44           || input.equals("y")) {
45         return true;
46       }
47       if (input.equals("false")
48           || input.equals("0")
49           || input.equals("no")
50           || input.equals("f")
51           || input.equals("n")) {
52         return false;
53       }
54       throw new OptionsParsingException("'" + input + "' is not a boolean");
55     }
56 
57     @Override
getTypeDescription()58     public String getTypeDescription() {
59       return "a boolean";
60     }
61   }
62 
63   /** Standard converter for Strings. */
64   public static class StringConverter implements Converter<String> {
65     @Override
convert(String input)66     public String convert(String input) {
67       return input;
68     }
69 
70     @Override
getTypeDescription()71     public String getTypeDescription() {
72       return "a string";
73     }
74   }
75 
76   /** Standard converter for integers. */
77   public static class IntegerConverter implements Converter<Integer> {
78     @Override
convert(String input)79     public Integer convert(String input) throws OptionsParsingException {
80       try {
81         return Integer.decode(input);
82       } catch (NumberFormatException e) {
83         throw new OptionsParsingException("'" + input + "' is not an int");
84       }
85     }
86 
87     @Override
getTypeDescription()88     public String getTypeDescription() {
89       return "an integer";
90     }
91   }
92 
93   /** Standard converter for longs. */
94   public static class LongConverter implements Converter<Long> {
95     @Override
convert(String input)96     public Long convert(String input) throws OptionsParsingException {
97       try {
98         return Long.decode(input);
99       } catch (NumberFormatException e) {
100         throw new OptionsParsingException("'" + input + "' is not a long");
101       }
102     }
103 
104     @Override
getTypeDescription()105     public String getTypeDescription() {
106       return "a long integer";
107     }
108   }
109 
110   /** Standard converter for doubles. */
111   public static class DoubleConverter implements Converter<Double> {
112     @Override
convert(String input)113     public Double convert(String input) throws OptionsParsingException {
114       try {
115         return Double.parseDouble(input);
116       } catch (NumberFormatException e) {
117         throw new OptionsParsingException("'" + input + "' is not a double");
118       }
119     }
120 
121     @Override
getTypeDescription()122     public String getTypeDescription() {
123       return "a double";
124     }
125   }
126 
127   /** Standard converter for TriState values. */
128   public static class TriStateConverter implements Converter<TriState> {
129     @Override
convert(String input)130     public TriState convert(String input) throws OptionsParsingException {
131       if (input == null) {
132         return TriState.AUTO;
133       }
134       input = input.toLowerCase();
135       if (input.equals("auto")) {
136         return TriState.AUTO;
137       }
138       if (input.equals("true")
139           || input.equals("1")
140           || input.equals("yes")
141           || input.equals("t")
142           || input.equals("y")) {
143         return TriState.YES;
144       }
145       if (input.equals("false")
146           || input.equals("0")
147           || input.equals("no")
148           || input.equals("f")
149           || input.equals("n")) {
150         return TriState.NO;
151       }
152       throw new OptionsParsingException("'" + input + "' is not a boolean");
153     }
154 
155     @Override
getTypeDescription()156     public String getTypeDescription() {
157       return "a tri-state (auto, yes, no)";
158     }
159   }
160 
161   /**
162    * Standard "converter" for Void. Should not actually be invoked. For instance, expansion flags
163    * are usually Void-typed and do not invoke the converter.
164    */
165   public static class VoidConverter implements Converter<Void> {
166     @Override
convert(String input)167     public Void convert(String input) throws OptionsParsingException {
168       if (input == null) {
169         return null; // expected input, return is unused so null is fine.
170       }
171       throw new OptionsParsingException("'" + input + "' unexpected");
172     }
173 
174     @Override
getTypeDescription()175     public String getTypeDescription() {
176       return "";
177     }
178   }
179 
180   /**
181    * The converters that are available to the options parser by default. These are used if the
182    * {@code @Option} annotation does not specify its own {@code converter}, and its type is one of
183    * the following.
184    */
185   static final Map<Class<?>, Converter<?>> DEFAULT_CONVERTERS = Maps.newHashMap();
186 
187   static {
DEFAULT_CONVERTERS.put(String.class, new Converters.StringConverter())188     DEFAULT_CONVERTERS.put(String.class, new Converters.StringConverter());
DEFAULT_CONVERTERS.put(int.class, new Converters.IntegerConverter())189     DEFAULT_CONVERTERS.put(int.class, new Converters.IntegerConverter());
DEFAULT_CONVERTERS.put(long.class, new Converters.LongConverter())190     DEFAULT_CONVERTERS.put(long.class, new Converters.LongConverter());
DEFAULT_CONVERTERS.put(double.class, new Converters.DoubleConverter())191     DEFAULT_CONVERTERS.put(double.class, new Converters.DoubleConverter());
DEFAULT_CONVERTERS.put(boolean.class, new Converters.BooleanConverter())192     DEFAULT_CONVERTERS.put(boolean.class, new Converters.BooleanConverter());
DEFAULT_CONVERTERS.put(TriState.class, new Converters.TriStateConverter())193     DEFAULT_CONVERTERS.put(TriState.class, new Converters.TriStateConverter());
DEFAULT_CONVERTERS.put(Void.class, new Converters.VoidConverter())194     DEFAULT_CONVERTERS.put(Void.class, new Converters.VoidConverter());
195   }
196 
197   /**
198    * Join a list of words as in English.  Examples:
199    * "nothing"
200    * "one"
201    * "one or two"
202    * "one and two"
203    * "one, two or three".
204    * "one, two and three".
205    * The toString method of each element is used.
206    */
joinEnglishList(Iterable<?> choices)207   static String joinEnglishList(Iterable<?> choices) {
208     StringBuilder buf = new StringBuilder();
209     for (Iterator<?> ii = choices.iterator(); ii.hasNext(); ) {
210       Object choice = ii.next();
211       if (buf.length() > 0) {
212         buf.append(ii.hasNext() ? ", " : " or ");
213       }
214       buf.append(choice);
215     }
216     return buf.length() == 0 ? "nothing" : buf.toString();
217   }
218 
219   public static class SeparatedOptionListConverter
220       implements Converter<List<String>> {
221 
222     private final String separatorDescription;
223     private final Splitter splitter;
224 
SeparatedOptionListConverter(char separator, String separatorDescription)225     protected SeparatedOptionListConverter(char separator,
226                                            String separatorDescription) {
227       this.separatorDescription = separatorDescription;
228       this.splitter = Splitter.on(separator);
229     }
230 
231     @Override
convert(String input)232     public List<String> convert(String input) {
233       return input.equals("")
234           ? ImmutableList.<String>of()
235           : ImmutableList.copyOf(splitter.split(input));
236     }
237 
238     @Override
getTypeDescription()239     public String getTypeDescription() {
240       return separatorDescription + "-separated list of options";
241     }
242   }
243 
244   public static class CommaSeparatedOptionListConverter
245       extends SeparatedOptionListConverter {
CommaSeparatedOptionListConverter()246     public CommaSeparatedOptionListConverter() {
247       super(',', "comma");
248     }
249   }
250 
251   public static class ColonSeparatedOptionListConverter extends SeparatedOptionListConverter {
ColonSeparatedOptionListConverter()252     public ColonSeparatedOptionListConverter() {
253       super(':', "colon");
254     }
255   }
256 
257   public static class LogLevelConverter implements Converter<Level> {
258 
259     public static final Level[] LEVELS = new Level[] {
260       Level.OFF, Level.SEVERE, Level.WARNING, Level.INFO, Level.FINE,
261       Level.FINER, Level.FINEST
262     };
263 
264     @Override
convert(String input)265     public Level convert(String input) throws OptionsParsingException {
266       try {
267         int level = Integer.parseInt(input);
268         return LEVELS[level];
269       } catch (NumberFormatException e) {
270         throw new OptionsParsingException("Not a log level: " + input);
271       } catch (ArrayIndexOutOfBoundsException e) {
272         throw new OptionsParsingException("Not a log level: " + input);
273       }
274     }
275 
276     @Override
getTypeDescription()277     public String getTypeDescription() {
278       return "0 <= an integer <= " + (LEVELS.length - 1);
279     }
280 
281   }
282 
283   /**
284    * Checks whether a string is part of a set of strings.
285    */
286   public static class StringSetConverter implements Converter<String> {
287 
288     // TODO(bazel-team): if this class never actually contains duplicates, we could s/List/Set/
289     // here.
290     private final List<String> values;
291 
StringSetConverter(String... values)292     public StringSetConverter(String... values) {
293       this.values = ImmutableList.copyOf(values);
294     }
295 
296     @Override
convert(String input)297     public String convert(String input) throws OptionsParsingException {
298       if (values.contains(input)) {
299         return input;
300       }
301 
302       throw new OptionsParsingException("Not one of " + values);
303     }
304 
305     @Override
getTypeDescription()306     public String getTypeDescription() {
307       return joinEnglishList(values);
308     }
309   }
310 
311   /**
312    * Checks whether a string is a valid regex pattern and compiles it.
313    */
314   public static class RegexPatternConverter implements Converter<Pattern> {
315 
316     @Override
convert(String input)317     public Pattern convert(String input) throws OptionsParsingException {
318       try {
319         return Pattern.compile(input);
320       } catch (PatternSyntaxException e) {
321         throw new OptionsParsingException("Not a valid regular expression: " + e.getMessage());
322       }
323     }
324 
325     @Override
getTypeDescription()326     public String getTypeDescription() {
327       return "a valid Java regular expression";
328     }
329   }
330 
331   /**
332    * Limits the length of a string argument.
333    */
334   public static class LengthLimitingConverter implements Converter<String> {
335     private final int maxSize;
336 
LengthLimitingConverter(int maxSize)337     public LengthLimitingConverter(int maxSize) {
338       this.maxSize = maxSize;
339     }
340 
341     @Override
convert(String input)342     public String convert(String input) throws OptionsParsingException {
343       if (input.length() > maxSize) {
344         throw new OptionsParsingException("Input must be " + getTypeDescription());
345       }
346       return input;
347     }
348 
349     @Override
getTypeDescription()350     public String getTypeDescription() {
351       return "a string <= " + maxSize + " characters";
352     }
353   }
354 
355   /**
356    * Checks whether an integer is in the given range.
357    */
358   public static class RangeConverter implements Converter<Integer> {
359     final int minValue;
360     final int maxValue;
361 
RangeConverter(int minValue, int maxValue)362     public RangeConverter(int minValue, int maxValue) {
363       this.minValue = minValue;
364       this.maxValue = maxValue;
365     }
366 
367     @Override
convert(String input)368     public Integer convert(String input) throws OptionsParsingException {
369       try {
370         Integer value = Integer.parseInt(input);
371         if (value < minValue) {
372           throw new OptionsParsingException("'" + input + "' should be >= " + minValue);
373         } else if (value < minValue || value > maxValue) {
374           throw new OptionsParsingException("'" + input + "' should be <= " + maxValue);
375         }
376         return value;
377       } catch (NumberFormatException e) {
378         throw new OptionsParsingException("'" + input + "' is not an int");
379       }
380     }
381 
382     @Override
getTypeDescription()383     public String getTypeDescription() {
384       if (minValue == Integer.MIN_VALUE) {
385         if (maxValue == Integer.MAX_VALUE) {
386           return "an integer";
387         } else {
388           return "an integer, <= " + maxValue;
389         }
390       } else if (maxValue == Integer.MAX_VALUE) {
391         return "an integer, >= " + minValue;
392       } else {
393         return "an integer in "
394             + (minValue < 0 ? "(" + minValue + ")" : minValue) + "-" + maxValue + " range";
395       }
396     }
397   }
398 
399   /**
400    * A converter for variable assignments from the parameter list of a blaze
401    * command invocation. Assignments are expected to have the form "name=value",
402    * where names and values are defined to be as permissive as possible.
403    */
404   public static class AssignmentConverter implements Converter<Map.Entry<String, String>> {
405 
406     @Override
convert(String input)407     public Map.Entry<String, String> convert(String input)
408         throws OptionsParsingException {
409       int pos = input.indexOf("=");
410       if (pos <= 0) {
411         throw new OptionsParsingException("Variable definitions must be in the form of a "
412             + "'name=value' assignment");
413       }
414       String name = input.substring(0, pos);
415       String value = input.substring(pos + 1);
416       return Maps.immutableEntry(name, value);
417     }
418 
419     @Override
getTypeDescription()420     public String getTypeDescription() {
421       return "a 'name=value' assignment";
422     }
423 
424   }
425 
426   /**
427    * A converter for variable assignments from the parameter list of a blaze
428    * command invocation. Assignments are expected to have the form "name[=value]",
429    * where names and values are defined to be as permissive as possible and value
430    * part can be optional (in which case it is considered to be null).
431    */
432   public static class OptionalAssignmentConverter implements Converter<Map.Entry<String, String>> {
433 
434     @Override
convert(String input)435     public Map.Entry<String, String> convert(String input)
436         throws OptionsParsingException {
437       int pos = input.indexOf("=");
438       if (pos == 0 || input.length() == 0) {
439         throw new OptionsParsingException("Variable definitions must be in the form of a "
440             + "'name=value' or 'name' assignment");
441       } else if (pos < 0) {
442         return Maps.immutableEntry(input, null);
443       }
444       String name = input.substring(0, pos);
445       String value = input.substring(pos + 1);
446       return Maps.immutableEntry(name, value);
447     }
448 
449     @Override
getTypeDescription()450     public String getTypeDescription() {
451       return "a 'name=value' assignment with an optional value part";
452     }
453 
454   }
455 
456   public static class HelpVerbosityConverter extends EnumConverter<OptionsParser.HelpVerbosity> {
HelpVerbosityConverter()457     public HelpVerbosityConverter() {
458       super(OptionsParser.HelpVerbosity.class, "--help_verbosity setting");
459     }
460   }
461 
462 }
463