1 /*
   2  * Copyright (c) 2015, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package jdk.test.lib.jittester.utils;
  25 
  26 import java.io.FileReader;
  27 import java.io.IOException;
  28 import java.util.*;
  29 
  30 public class OptionResolver {
  31     private final Map<String, Option<?>> options = new LinkedHashMap<>(20);
  32     private Map<Option<?>, Object> values = Collections.emptyMap();
  33 
  34     public OptionResolver() {
  35     }
  36 
  37     public final void parse(String[] argv) {
  38         parse(argv, null);
  39     }
  40 
  41     public final void parse(String[] argv, Option<String> propertyFileOption) {
  42         int position = 0;
  43         this.values = new HashMap<>(argv.length / 2);
  44 
  45         while (position < argv.length) {
  46             String curArg = argv[position];
  47             if (curArg.startsWith("-")) {
  48                 String valueArg = null;
  49                 int opt;
  50                 if (curArg.startsWith("--")) {
  51                     opt = curArg.indexOf("=");
  52                     if (opt != -1) {
  53                         valueArg = curArg.substring(opt + 1);
  54                         curArg = curArg.substring(0, opt);
  55                     }
  56                 } else if (curArg.length() > 2) {
  57                     for (opt = 1; opt < curArg.length(); ++opt) {
  58                         final char key = curArg.charAt(opt);
  59                         Option<?> flagOption = this.options.get("-" + key);
  60 
  61                         if (!flagOption.isFlag()) {
  62                             throw new IllegalArgumentException("Unknown flag option " + key);
  63                         }
  64 
  65                         values.put(flagOption, Boolean.TRUE);
  66                     }
  67 
  68                     ++position;
  69                     continue;
  70                 }
  71 
  72                 Option<?> currentOption = this.options.get(curArg);
  73                 if (currentOption == null) {
  74                     throw new IllegalArgumentException("Unknown option " + curArg);
  75                 }
  76 
  77                 Object value;
  78                 if (!currentOption.isFlag()) {
  79                     if (valueArg == null) {
  80                         ++position;
  81                         if (position < argv.length) {
  82                             valueArg = argv[position];
  83                         }
  84                     }
  85                 }
  86                 try {
  87                     value = currentOption.parseFromString(valueArg);
  88                 } catch (Exception ex) {
  89                     throw new IllegalArgumentException("Error parsing " + valueArg + ", option " + curArg, ex);
  90                 }
  91                 values.put(currentOption, value);
  92             }
  93             ++position;
  94         }
  95 
  96         if (propertyFileOption != null && values.containsKey(propertyFileOption)) {
  97             parseProperties(propertyFileOption.value());
  98         }
  99     }
 100 
 101     private void parseProperties(String fileName) {
 102         Properties properties = new Properties();
 103         try {
 104             properties.load(new FileReader(fileName));
 105         } catch (IOException e) {
 106             throw new RuntimeException(e);
 107         }
 108         for (String optionName : properties.stringPropertyNames()) {
 109             Option<?> currentOption = this.options.get("--" + optionName);
 110             if (currentOption == null) {
 111                 throw new IllegalArgumentException("Unknown option in property file" + optionName);
 112             }
 113 
 114             final String propertyValue = properties.getProperty(optionName);
 115             try {
 116                 values.putIfAbsent(currentOption, currentOption.parseFromString(propertyValue));
 117             } catch (Exception ex) {
 118                 throw new IllegalArgumentException("Error parsing " + propertyValue + ", property " + optionName, ex);
 119             }
 120         }
 121     }
 122 
 123     public Option<Integer> addIntegerOption(Character key, String name, int defaultValue, String description) {
 124         final Option<Integer> option = new IntOption(key, name, defaultValue, description);
 125         register(option);
 126         return option;
 127     }
 128 
 129     public Option<Long> addLongOption(Character key, String name, long defaultValue, String description) {
 130         final Option<Long> option = new LongOption(key, name, defaultValue, description);
 131         register(option);
 132         return option;
 133     }
 134 
 135     public Option<String> addStringOption(Character key, String name, String defaultValue, String description) {
 136         final Option<String> option = new StringOption(key, name, defaultValue, description);
 137         register(option);
 138         return option;
 139     }
 140 
 141     public Option<Boolean> addBooleanOption(Character key, String name, boolean defaultValue, String description) {
 142         final Option<Boolean> option = new BooleanOption(key, name, defaultValue, description);
 143         register(option);
 144         return option;
 145     }
 146 
 147     public Option<Integer> addIntegerOption(String name, int defaultValue, String description) {
 148         return addIntegerOption(null, name, defaultValue, description);
 149     }
 150 
 151     public Option<String> addStringOption(String name, String defaultValue, String description) {
 152         return addStringOption(null, name, defaultValue, description);
 153     }
 154 
 155     public Option<Boolean> addBooleanOption(String name, String description) {
 156         return addBooleanOption(null, name, false, description);
 157     }
 158 
 159     private void register(Option<?> option) {
 160         if (options.put("--" + option.longName, option) != null) {
 161             throw new RuntimeException("Option is already registered for key " + option.longName);
 162         }
 163         if (option.shortName != null && options.put("-" + option.shortName, option) != null) {
 164             throw new RuntimeException("Option is already registered for key " + option.shortName);
 165         }
 166     }
 167 
 168     public abstract class Option<T> {
 169         protected final Character shortName;
 170         protected final String longName;
 171         protected final T defaultValue;
 172         protected final String description;
 173 
 174         public Option(Character shortName, String longName, T defaultValue, String description) {
 175             this.shortName = shortName;
 176             this.longName = longName;
 177             this.defaultValue = defaultValue;
 178             this.description = description;
 179         }
 180 
 181         public Character getShortName() {
 182             return shortName;
 183         }
 184 
 185         public String getLongName() {
 186             return longName;
 187         }
 188 
 189         public T getDefaultValue() {
 190             return defaultValue;
 191         }
 192 
 193         public String getDescription() {
 194             return description;
 195         }
 196 
 197         @SuppressWarnings("unchecked")
 198         public T value() {
 199             return (T) values.getOrDefault(this, defaultValue);
 200         }
 201 
 202         public boolean isSet() {
 203             return values.containsKey(this);
 204         }
 205 
 206         public boolean isFlag() {
 207             return false;
 208         }
 209 
 210         public abstract T parseFromString(String arg);
 211     }
 212 
 213     private class StringOption extends Option<String> {
 214 
 215         StringOption(Character s, String l, String v, String d) {
 216             super(s, l, v, d);
 217         }
 218 
 219         @Override
 220         public String parseFromString(String arg) {
 221             return arg;
 222         }
 223     }
 224 
 225     private class LongOption extends Option<Long> {
 226 
 227         LongOption(Character s, String l, long v, String d) {
 228             super(s, l, v, d);
 229         }
 230 
 231         @Override
 232         public Long parseFromString(String arg) {
 233             return Long.valueOf(arg);
 234         }
 235     }
 236 
 237     private class IntOption extends Option<Integer> {
 238 
 239         IntOption(Character s, String l, int v, String d) {
 240             super(s, l, v, d);
 241         }
 242 
 243         @Override
 244         public Integer parseFromString(String arg) {
 245             return Integer.valueOf(arg);
 246         }
 247     }
 248 
 249     private class BooleanOption extends Option<Boolean> {
 250 
 251         BooleanOption(Character s, String l, boolean v, String d) {
 252             super(s, l, v, d);
 253         }
 254 
 255         @Override
 256         public boolean isFlag() {
 257             return true;
 258         }
 259 
 260         @Override
 261         public Boolean parseFromString(String arg) {
 262             //null and empty value is considered true, as option is flag and value could be absent
 263             return arg == null || "".equals(arg) || "1".equalsIgnoreCase(arg) || "true".equalsIgnoreCase(arg);
 264         }
 265     }
 266 
 267     public Collection<Option<?>> getRegisteredOptions() {
 268         return Collections.unmodifiableSet(new LinkedHashSet<>(options.values()));
 269     }
 270 }