MATSIM
CommandLine.java
Go to the documentation of this file.
1 
2 /* *********************************************************************** *
3  * project: org.matsim.*
4  * CommandLine.java
5  * *
6  * *********************************************************************** *
7  * *
8  * copyright : (C) 2019 by the members listed in the COPYING, *
9  * LICENSE and WARRANTY file. *
10  * email : info at matsim dot org *
11  * *
12  * *********************************************************************** *
13  * *
14  * This program is free software; you can redistribute it and/or modify *
15  * it under the terms of the GNU General Public License as published by *
16  * the Free Software Foundation; either version 2 of the License, or *
17  * (at your option) any later version. *
18  * See also COPYING, LICENSE and WARRANTY file *
19  * *
20  * *********************************************************************** */
21 
22  package org.matsim.core.config;
23 
24 import java.util.ArrayDeque;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.LinkedList;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Optional;
34 import java.util.Set;
35 import java.util.stream.Collectors;
36 
37 import org.apache.logging.log4j.LogManager;
38 import org.apache.logging.log4j.Logger;
40 
128 public class CommandLine {
129  final private static Logger logger = LogManager.getLogger(CommandLine.class);
130 
131  final private static String CONFIG_PREFIX = "config";
132  final private static String FLAG_VALUE = "true";
133 
134  final private Set<String> allowedPrefixes;
135  final private Set<String> allowedOptions;
136  final private Set<String> requiredOptions;
137 
138  final private Map<String, String> options = new HashMap<>();
139  final private List<String> positionalArguments = new LinkedList<>();
140 
141  final private boolean positionalArgumentsAllowed;
142  final private boolean allowAnyOption;
143 
145 
146  // Configuration part
147 
151  static public class Builder {
152  final private Set<String> allowedPrefixes = new HashSet<>(Collections.singleton(CONFIG_PREFIX));
153  final private Set<String> allowedOptions = new HashSet<>();
154  final private Set<String> requiredOptions = new HashSet<>();
155 
156  private boolean positionalArgumentsAllowed = true;
157  private boolean allowAnyOption = false;
158 
159  final private List<String> arguments;
160 
166  public Builder(String[] args) {
167  this.arguments = Arrays.asList(args);
168  for( String argument : this.arguments ){
169  if ( argument==null) {
170  throw new RuntimeException( "one of the entries in args is null; this will not work ..." ) ;
171  }
172  }
173  }
174 
179  public Builder allowPositionalArguments(boolean allow) {
180  positionalArgumentsAllowed = allow;
181  return this;
182  }
183 
189  public Builder allowAnyOption(boolean allow) {
190  allowAnyOption = allow;
191  return this;
192  }
193 
199  public Builder allowOptions(Collection<String> options) {
200  allowedOptions.addAll(options);
201  return this;
202  }
203 
207  public Builder allowOptions(String... options) {
208  allowOptions(Arrays.asList(options));
209  return this;
210  }
211 
216  public Builder requireOptions(Collection<String> options) {
217  allowedOptions.addAll(options);
218  requiredOptions.addAll(options);
219  return this;
220  }
221 
225  public Builder requireOptions(String... options) {
226  requireOptions(Arrays.asList(options));
227  return this;
228  }
229 
236  public Builder allowPrefixes(Collection<String> prefixes) {
237  allowedPrefixes.addAll(prefixes);
238  return this;
239  }
240 
244  public Builder allowPrefixes(String... prefixes) {
245  allowPrefixes(Arrays.asList(prefixes));
246  return this;
247  }
248 
270  CommandLine commandLine = new CommandLine(allowedOptions, requiredOptions, allowedPrefixes,
271  positionalArgumentsAllowed, allowAnyOption);
272  commandLine.process(arguments);
273  return commandLine;
274  }
275  }
276 
277  private CommandLine(Set<String> allowedOptions, Set<String> requiredOptions, Set<String> allowedPrefixes,
278  boolean positionalArgumentsAllowed, boolean allowAnyOption) {
279  this.allowedOptions = allowedOptions;
280  this.requiredOptions = requiredOptions;
281  this.allowedPrefixes = allowedPrefixes;
282  this.positionalArgumentsAllowed = positionalArgumentsAllowed;
283  this.allowAnyOption = allowAnyOption;
284  }
285 
286  // Getters for positional arguments
287 
293  return positionalArguments.size();
294  }
295 
300  public List<String> getPositionalArguments() {
301  return Collections.unmodifiableList(positionalArguments);
302  }
303 
308  public Optional<String> getPositionalArgument(int index) {
309  return index < positionalArguments.size() ? Optional.of(positionalArguments.get(index)) : Optional.empty();
310  }
311 
318  public String getPositionalArgumentStrict(int index) throws ConfigurationException {
319  if (index < positionalArguments.size()) {
320  return positionalArguments.get(index);
321  } else {
322  throw new ConfigurationException(String.format(
323  "Requested positional command line argument with index %d, but only %d arguments are available",
324  index, positionalArguments.size()));
325  }
326  }
327 
328  // Getters for options
329 
333  public Collection<String> getAvailableOptions() {
334  return Collections.unmodifiableCollection(options.keySet());
335  }
336 
340  public boolean hasOption(String option) {
341  return options.containsKey(option);
342  }
343 
347  public Optional<String> getOption(String option) {
348  return options.containsKey(option) ? Optional.of(options.get(option)) : Optional.empty();
349  }
350 
357  public String getOptionStrict(String option) throws ConfigurationException {
358  if (options.containsKey(option)) {
359  return options.get(option);
360  } else {
361  throw new ConfigurationException(
362  String.format("Requested command line option '%s' is not available", option));
363  }
364  }
365 
366  // Processing
367 
373  private void process(List<String> args) throws ConfigurationException {
374  List<String> arguments = flattenArguments(args);
375  positionalArguments.clear();
376 
377  String currentOption = null;
378 
379  for (String token : arguments) {
380  if (token.startsWith("--")) {
381  if (currentOption != null) {
382  addOption(currentOption, FLAG_VALUE);
383  }
384 
385  currentOption = token.substring(2);
386  } else {
387  if (currentOption != null) {
388  addOption(currentOption, token);
389  } else {
390  addPositionalArgument(token);
391  }
392 
393  currentOption = null;
394  }
395  }
396 
397  if (currentOption != null) {
398  addOption(currentOption, FLAG_VALUE);
399  }
400 
402  reportOptions();
403  }
404 
417  private List<String> flattenArguments(List<String> args) {
418  List<String> flatArguments = new LinkedList<>();
419 
420  for (String argument : args) {
421  int index = argument.lastIndexOf('=');
422  int bracketIndex = argument.lastIndexOf(']');
423 
424  if (bracketIndex > index) {
425  index = argument.indexOf('=', bracketIndex);
426  }
427 
428  if (index > -1) {
429  flatArguments.add(argument.substring(0, index));
430  flatArguments.add(argument.substring(index + 1));
431  } else {
432  flatArguments.add(argument);
433  }
434  }
435 
436  return flatArguments;
437  }
438 
444  private void addPositionalArgument(String value) throws ConfigurationException {
445  if (!positionalArgumentsAllowed) {
446  throw new ConfigurationException(String.format("Positional argument '%s' is not allowed.", value));
447  }
448 
449  positionalArguments.add(value);
450  }
451 
457  private void addOption(String option, String value) throws ConfigurationException {
458  if (!allowAnyOption && !allowedOptions.contains(option)) {
459  String[] parts = option.split(":");
460 
461  if (!allowedPrefixes.contains(parts[0])) {
462  throw new ConfigurationException(String.format("Option '%s' is not allowed.", option));
463  }
464  }
465 
466  options.put(option, value);
467  }
468 
475  List<String> missingOptions = new LinkedList<>();
476 
477  for (String option : requiredOptions) {
478  if (!options.containsKey(option)) {
479  missingOptions.add(option);
480  }
481  }
482 
483  if (missingOptions.size() > 0) {
484  throw new ConfigurationException(
485  String.format("The following options are missing: %s", missingOptions.toString()));
486  }
487  }
488 
489  // MATSim Configuration
490 
497  public void applyConfiguration(Config config) throws ConfigurationException {
498  List<String> configOptions = options.keySet().stream().filter(o -> o.startsWith(CONFIG_PREFIX + ":"))
499  .collect(Collectors.toList());
500 
501  for (String option : configOptions) {
502  processConfigOption(config, option, option.substring(CONFIG_PREFIX.length() + 1));
503  }
504  }
505 
510  private void reportOptions() {
511  logger.info(String.format("Received %d positional command line arguments:", positionalArguments.size()));
512  logger.info(" " + String.join(" , ", positionalArguments));
513 
514  Map<String, List<String>> prefixedOptions = new HashMap<>();
515  List<String> nonPrefixedOptions = new LinkedList<>();
516 
517  for (String option : options.keySet()) {
518  int separatorIndex = option.indexOf(':');
519 
520  if (separatorIndex > -1) {
521  String prefix = option.substring(0, separatorIndex);
522  option = option.substring(separatorIndex + 1);
523 
524  if (!prefixedOptions.containsKey(prefix)) {
525  prefixedOptions.put(prefix, new LinkedList<>());
526  }
527 
528  prefixedOptions.get(prefix).add(option);
529  } else {
530  nonPrefixedOptions.add(option);
531  }
532  }
533 
534  logger.info(String.format("Received %d command line options with %d prefixes:", options.size(),
535  prefixedOptions.size()));
536 
537  Collections.sort(nonPrefixedOptions);
538  for (String option : nonPrefixedOptions) {
539  logger.info(String.format(" %s = %s", option, options.get(option)));
540  }
541 
542  List<String> orderedPrefixes = new LinkedList<>(prefixedOptions.keySet());
543  Collections.sort(orderedPrefixes);
544 
545  for (String prefix : orderedPrefixes) {
546  logger.info(String.format(" Prefix %s:", prefix));
547 
548  for (String option : prefixedOptions.get(prefix)) {
549  logger.info(String.format(" %s = %s", option, options.get(prefix + ":" + option)));
550  }
551  }
552  }
553 
554  private void processConfigOption(Config config, String option, String remainder) throws ConfigurationException {
555  int separatorIndex = remainder.indexOf('.');
556 
557  if (separatorIndex > -1) {
558  String module = remainder.substring(0, separatorIndex);
559  String newRemainder = remainder.substring(separatorIndex + 1);
560 
561  module = this.configAliases.resolveAlias(module, new ArrayDeque<>());
562 
563  if (config.getModules().containsKey(module)) {
564  processParameter(option, module, config.getModules().get(module), newRemainder);
565  } else {
566  throw new ConfigurationException(
567  String.format("Invalid MATSim option: '%s'. Module '%s' does not exist.", remainder, module));
568  }
569  } else {
570  throw new ConfigurationException(
571  String.format("Malformatted MATSim option: '%s'. Expected MODULE.*", remainder));
572  }
573  }
574 
575  private void processParameter(String option, String path, ConfigGroup configGroup, String remainder)
576  throws ConfigurationException {
577  if (remainder.contains("[")) {
578  int selectorStartIndex = remainder.indexOf('[');
579  int selectorEndIndex = remainder.indexOf(']');
580  int equalIndex = remainder.indexOf('=');
581 
582  if (selectorStartIndex > -1 && selectorEndIndex > -1 && equalIndex > -1) {
583  if (selectorStartIndex < equalIndex && equalIndex < selectorEndIndex) {
584  String parameterSetType = remainder.substring(0, selectorStartIndex);
585  String selectionParameter = remainder.substring(selectorStartIndex + 1, equalIndex);
586  String selectionValue = remainder.substring(equalIndex + 1, selectorEndIndex);
587 
588  String newRemainder = remainder.substring(selectorEndIndex + 1);
589 
590  if (newRemainder.startsWith(".")) {
591  newRemainder = newRemainder.substring(1);
592 
593  String newPath = String.format("%s.%s[%s=%s]", path, parameterSetType, selectionParameter,
594  selectionValue);
595 
596  Collection<? extends ConfigGroup> parameterSets = configGroup
597  .getParameterSets(parameterSetType);
598 
599  // change values in *all* parameter sets of parameterSetType
600  final boolean changeValueInAllSets = selectionParameter.equals("*") && selectionValue.equals("*");
601 
602  if (!parameterSets.isEmpty()) {
603  for (ConfigGroup parameterSet : parameterSets) {
604  if (changeValueInAllSets || parameterSet.getParams().containsKey(selectionParameter)) {
605  String comparisonValue = parameterSet.getParams().get(selectionParameter);
606 
607  if (changeValueInAllSets || comparisonValue.equals(selectionValue)) {
608  processParameter(option, newPath, parameterSet, newRemainder);
609  if (!changeValueInAllSets) {
610  // retain current behavior to only change value in first matching parameter set
611  return;
612  }
613  }
614 
615  // allow for the case subpopulation = 'null' in the scoring parameters
616  if (parameterSetType.equals(ScoringParameterSet.SET_TYPE) && selectionParameter.equals("subpopulation") && selectionValue.equals("null")) {
617  processParameter(option, newPath, parameterSet, newRemainder);
618  return;
619  }
620 
621  }
622  }
623 
624  if (changeValueInAllSets) {
625  return;
626  } else {
627  throw new ConfigurationException(
628  String.format("Parameter set '%s' with %s=%s for %s is not available in %s",
629  parameterSetType, selectionParameter, selectionValue, path, option));
630  }
631  } else {
632  throw new ConfigurationException(
633  String.format("Parameter set of type '%s' for %s is not available in %s",
634  parameterSetType, path, option));
635  }
636  }
637  }
638  }
639 
640  throw new ConfigurationException(String.format(
641  "Malformatted parameter set selector: '%s' in %s. Expected %s.SET_TYPE[PARAM=VALUE].*", remainder,
642  option, path));
643  } else {
644  if (configGroup.getParams().containsKey(remainder)) {
645  String value = options.get(option);
646  configGroup.addParam(remainder, value);
647  logger.info(String.format("Setting %s to %s", option, value));
648  } else {
649  throw new ConfigurationException(String.format("Parameter %s in %s is not available", remainder, path));
650  }
651  }
652  }
653 
654  // Exception
655 
656  static public class ConfigurationException extends Exception {
657  public ConfigurationException(String message) {
658  super(message);
659  }
660 
661  private static final long serialVersionUID = 8427111111975754721L;
662  }
663 }
String resolveAlias(String oldName, Deque< String > pathStack)
String getPositionalArgumentStrict(int index)
void processParameter(String option, String path, ConfigGroup configGroup, String remainder)
final Set< String > requiredOptions
String getOptionStrict(String option)
Optional< String > getOption(String option)
void applyConfiguration(Config config)
Builder allowOptions(Collection< String > options)
Optional< String > getPositionalArgument(int index)
Builder allowPrefixes(String... prefixes)
final Map< String, String > options
Builder requireOptions(String... options)
CommandLine(Set< String > allowedOptions, Set< String > requiredOptions, Set< String > allowedPrefixes, boolean positionalArgumentsAllowed, boolean allowAnyOption)
boolean hasOption(String option)
void processConfigOption(Config config, String option, String remainder)
Builder allowPositionalArguments(boolean allow)
Builder requireOptions(Collection< String > options)
List< String > flattenArguments(List< String > args)
Builder allowOptions(String... options)
void addOption(String option, String value)
Collection< String > getAvailableOptions()
final Set< String > allowedOptions
void process(List< String > args)
Builder allowPrefixes(Collection< String > prefixes)
void addPositionalArgument(String value)
final Set< String > allowedPrefixes
final List< String > positionalArguments