1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.runtime.options;
  27 
  28 import java.util.Locale;
  29 import java.util.TimeZone;
  30 import jdk.nashorn.internal.runtime.QuotedStringTokenizer;
  31 
  32 /**
  33  * This describes the valid input for an option, as read from the resource
  34  * bundle file. Metainfo such as parameters and description is here as well
  35  * for context sensitive help generation.
  36  */
  37 public final class OptionTemplate implements Comparable<OptionTemplate> {
  38     /** Resource, e.g. "nashorn" for this option */
  39     private final String resource;
  40 
  41     /** Key in the resource bundle */
  42     private final String key;
  43 
  44     /** Is this option a help option? */
  45     private final boolean isHelp;
  46 
  47     /** Is this option a extended help option? */
  48     private final boolean isXHelp;
  49 
  50     /** Name - for example --dump-on-error (usually prefixed with --) */
  51     private String name;
  52 
  53     /** Short name - for example -doe (usually prefixed with -) */
  54     private String shortName;
  55 
  56     /** Params - a parameter template string */
  57     private String params;
  58 
  59     /** Type - e.g. "boolean". */
  60     private String type;
  61 
  62     /** Does this option have a default value? */
  63     private String defaultValue;
  64 
  65     /** Does this option activate another option when set? */
  66     private String dependency;
  67 
  68     /** Does this option conflict with another? */
  69     private String conflict;
  70 
  71     /** Is this a documented option that should show up in help? */
  72     private boolean isUndocumented;
  73 
  74     /** A longer description of what this option does */
  75     private String description;
  76 
  77     /** is the option value specified as next argument? */
  78     private boolean valueNextArg;
  79 
  80     /**
  81      * Can this option be repeated in command line?
  82      *
  83      * For a repeatable option, multiple values will be merged as comma
  84      * separated values rather than the last value overriding previous ones.
  85      */
  86     private boolean repeated;
  87 
  88     OptionTemplate(final String resource, final String key, final String value, final boolean isHelp, final boolean isXHelp) {
  89         this.resource = resource;
  90         this.key = key;
  91         this.isHelp = isHelp;
  92         this.isXHelp = isXHelp;
  93         parse(value);
  94     }
  95 
  96     /**
  97      * Is this the special help option, used to generate help for
  98      * all the others
  99      *
 100      * @return true if this is the help option
 101      */
 102     public boolean isHelp() {
 103         return this.isHelp;
 104     }
 105 
 106     /**
 107      * Is this the special extended help option, used to generate extended help for
 108      * all the others
 109      *
 110      * @return true if this is the extended help option
 111      */
 112     public boolean isXHelp() {
 113         return this.isXHelp;
 114     }
 115 
 116     /**
 117      * Get the resource name used to prefix this option set, e.g. "nashorn"
 118      *
 119      * @return the name of the resource
 120      */
 121     public String getResource() {
 122         return this.resource;
 123     }
 124 
 125     /**
 126      * Get the type of this option
 127      *
 128      * @return the type of the option
 129      */
 130     public String getType() {
 131         return this.type;
 132     }
 133 
 134     /**
 135      * Get the key of this option
 136      *
 137      * @return the key
 138      */
 139     public String getKey() {
 140         return this.key;
 141     }
 142 
 143     /**
 144      * Get the default value for this option
 145      *
 146      * @return the default value as a string
 147      */
 148     public String getDefaultValue() {
 149         switch (getType()) {
 150         case "boolean":
 151             if (this.defaultValue == null) {
 152                 this.defaultValue = "false";
 153             }
 154             break;
 155         case "integer":
 156             if (this.defaultValue == null) {
 157                 this.defaultValue = "0";
 158             }
 159             break;
 160         case "timezone":
 161             this.defaultValue = TimeZone.getDefault().getID();
 162             break;
 163         case "locale":
 164             this.defaultValue = Locale.getDefault().toLanguageTag();
 165             break;
 166         default:
 167             break;
 168         }
 169         return this.defaultValue;
 170     }
 171 
 172     /**
 173      * Does this option automatically enable another option, i.e. a dependency.
 174      * @return the dependency or null if none exists
 175      */
 176     public String getDependency() {
 177         return this.dependency;
 178     }
 179 
 180     /**
 181      * Is this option in conflict with another option so that both can't be enabled
 182      * at the same time
 183      *
 184      * @return the conflicting option or null if none exists
 185      */
 186     public String getConflict() {
 187         return this.conflict;
 188     }
 189 
 190     /**
 191      * Is this option undocumented, i.e. should not show up in the standard help output
 192      *
 193      * @return true if option is undocumented
 194      */
 195     public boolean isUndocumented() {
 196         return this.isUndocumented;
 197     }
 198 
 199     /**
 200      * Get the short version of this option name if one exists, e.g. "-co" for "--compile-only"
 201      *
 202      * @return the short name
 203      */
 204     public String getShortName() {
 205         return this.shortName;
 206     }
 207 
 208     /**
 209      * Get the name of this option, e.g. "--compile-only". A name always exists
 210      *
 211      * @return the name of the option
 212      */
 213     public String getName() {
 214         return this.name;
 215     }
 216 
 217     /**
 218      * Get the description of this option.
 219      *
 220      * @return the description
 221      */
 222     public String getDescription() {
 223         return this.description;
 224     }
 225 
 226     /**
 227      * Is value of this option passed as next argument?
 228      * @return boolean
 229      */
 230     public boolean isValueNextArg() {
 231         return valueNextArg;
 232     }
 233 
 234     /**
 235      * Can this option be repeated?
 236      * @return boolean
 237      */
 238     public boolean isRepeated() {
 239         return repeated;
 240     }
 241 
 242     private static String strip(final String value, final char start, final char end) {
 243         final int len = value.length();
 244         if (len > 1 && value.charAt(0) == start && value.charAt(len - 1) == end) {
 245             return value.substring(1, len - 1);
 246         }
 247         return null;
 248     }
 249 
 250     private void parse(final String origValue) {
 251         String value = origValue.trim();
 252 
 253         try {
 254             value = OptionTemplate.strip(value, '{', '}');
 255             final QuotedStringTokenizer keyValuePairs = new QuotedStringTokenizer(value, ",");
 256 
 257             while (keyValuePairs.hasMoreTokens()) {
 258                 final String                keyValue = keyValuePairs.nextToken();
 259                 final QuotedStringTokenizer st       = new QuotedStringTokenizer(keyValue, "=");
 260                 final String                keyToken = st.nextToken();
 261                 final String                arg      = st.nextToken();
 262 
 263                 switch (keyToken) {
 264                 case "is_undocumented":
 265                     this.isUndocumented = Boolean.parseBoolean(arg);
 266                     break;
 267                 case "name":
 268                     if (!arg.startsWith("-")) {
 269                         throw new IllegalArgumentException(arg);
 270                     }
 271                     this.name = arg;
 272                     break;
 273                 case "short_name":
 274                     if (!arg.startsWith("-")) {
 275                         throw new IllegalArgumentException(arg);
 276                     }
 277                     this.shortName = arg;
 278                     break;
 279                 case "desc":
 280                     this.description = arg;
 281                     break;
 282                 case "params":
 283                     this.params = arg;
 284                     break;
 285                 case "type":
 286                     this.type = arg.toLowerCase(Locale.ENGLISH);
 287                     break;
 288                 case "default":
 289                     this.defaultValue = arg;
 290                     break;
 291                 case "dependency":
 292                     this.dependency = arg;
 293                     break;
 294                 case "conflict":
 295                     this.conflict = arg;
 296                     break;
 297                 case "value_next_arg":
 298                     this.valueNextArg = Boolean.parseBoolean(arg);
 299                     break;
 300                 case "repeated":
 301                     this.repeated = true;
 302                     break;
 303                 default:
 304                     throw new IllegalArgumentException(keyToken);
 305                 }
 306             }
 307 
 308             // default to boolean if no type is given
 309             if (this.type == null) {
 310                 this.type = "boolean";
 311             }
 312 
 313             if (this.params == null && "boolean".equals(this.type)) {
 314                 this.params = "[true|false]";
 315             }
 316 
 317         } catch (final Exception e) {
 318             throw new IllegalArgumentException(origValue);
 319         }
 320 
 321         if (name == null && shortName == null) {
 322             throw new IllegalArgumentException(origValue);
 323         }
 324 
 325         if (this.repeated && !"string".equals(this.type)) {
 326             throw new IllegalArgumentException("repeated option should be of type string: " + this.name);
 327         }
 328     }
 329 
 330     boolean nameMatches(final String aName) {
 331         return aName.equals(this.shortName) || aName.equals(this.name);
 332     }
 333 
 334     private static final int LINE_BREAK = 64;
 335 
 336     @Override
 337     public String toString() {
 338         final StringBuilder sb = new StringBuilder();
 339 
 340         sb.append('\t');
 341 
 342         if (shortName != null) {
 343             sb.append(shortName);
 344             if (name != null) {
 345                 sb.append(", ");
 346             }
 347         }
 348 
 349         if (name != null) {
 350             sb.append(name);
 351         }
 352 
 353         if (description != null) {
 354             final int indent = sb.length();
 355             sb.append(' ');
 356             sb.append('(');
 357             int pos = 0;
 358             for (final char c : description.toCharArray()) {
 359                 sb.append(c);
 360                 pos++;
 361                 if (pos >= LINE_BREAK && Character.isWhitespace(c)) {
 362                     pos = 0;
 363                     sb.append("\n\t");
 364                     for (int i = 0; i < indent; i++) {
 365                         sb.append(' ');
 366                     }
 367                 }
 368             }
 369             sb.append(')');
 370         }
 371 
 372         if (params != null) {
 373             sb.append('\n');
 374             sb.append('\t');
 375             sb.append('\t');
 376             sb.append(Options.getMsg("nashorn.options.param")).append(": ");
 377             sb.append(params);
 378             sb.append("   ");
 379             final Object def = this.getDefaultValue();
 380             if (def != null) {
 381                 sb.append(Options.getMsg("nashorn.options.default")).append(": ");
 382                 sb.append(this.getDefaultValue());
 383             }
 384         }
 385 
 386 
 387         return sb.toString();
 388     }
 389 
 390     @Override
 391     public int compareTo(final OptionTemplate o) {
 392         return this.getKey().compareTo(o.getKey());
 393     }
 394 }