All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.internal.helpers.Args Maven / Gradle / Ivy

There is a newer version: 2025.03.0
Show newest version
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.internal.helpers;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.function.Function;
import org.neo4j.common.Validator;

/**
 * Parses a String[] argument from a main-method. It expects values to be either
 * key/value pairs or just "orphan" values (w/o a key associated).
 * 

* A key is defined with one or more dashes in the beginning, for example: * *

 *   '-path'
 *   '--path'
 * 
* * A key/value pair can be either one single String from the array where there's * a '=' delimiter between the key and value, like so: * *
 *   '--path=/my/path/to/something'
 * 
* ...or consist of two (consecutive) strings from the array, like so: *
 *   '-path' '/my/path/to/something'
 * 
* * Multiple values for an option is supported, however the only means of extracting all values is be * using {@link #interpretOptions(String, Function, Function, Validator...)}, all other methods revolve * around single value, i.e. will fail if there are multiple. * * Options can have metadata which can be extracted using * {@link #interpretOptions(String, Function, Function, Validator...)}. Metadata looks like: *
 *   --my-option:Metadata my-value
 * 
* * where {@code Metadata} would be the metadata of {@code my-value}. */ public class Args { private static final char OPTION_METADATA_DELIMITER = ':'; public static class ArgsParser { private final String[] flags; private ArgsParser(String... flags) { this.flags = Objects.requireNonNull(flags); } public Args parse(String... arguments) { return new Args(flags, arguments); } } public static class Option { private final T value; private final String metadata; private Option(T value, String metadata) { this.value = value; this.metadata = metadata; } public T value() { return value; } public String metadata() { return metadata; } @Override public String toString() { return "Option[" + value + (metadata != null ? ", " + metadata : "") + "]"; } } private static final Function> DEFAULT_OPTION_PARSER = from -> { int metadataStartIndex = from.indexOf(OPTION_METADATA_DELIMITER); return metadataStartIndex == -1 ? new Option<>(from, null) : new Option<>(from.substring(0, metadataStartIndex), from.substring(metadataStartIndex + 1)); }; private final String[] args; private final String[] flags; private final Map>> map = new HashMap<>(); private final List orphans = new ArrayList<>(); public static ArgsParser withFlags(String... flags) { return new ArgsParser(flags); } public static Args parse(String... args) { return withFlags().parse(args); } /** * Suitable for main( String[] args ) * @param args the arguments to parse. */ private Args(String[] flags, String[] args) { this(DEFAULT_OPTION_PARSER, flags, args); } /** * Suitable for main( String[] args ) * @param flags list of possible flags (e.g -v or -skip-bad). A flag differs from an option in that it * has no value after it. This list of flags is used for distinguishing between the two. * @param args the arguments to parse. */ private Args(Function> optionParser, String[] flags, String[] args) { this.flags = flags; this.args = args; parseArgs(optionParser, args); } public Args(Map source) { this(DEFAULT_OPTION_PARSER, source); } public Args(Function> optionParser, Map source) { this.flags = new String[] {}; this.args = null; for (Entry entry : source.entrySet()) { put(optionParser, entry.getKey(), entry.getValue()); } } public String[] source() { return this.args; } public Map asMap() { Map result = new HashMap<>(); for (Map.Entry>> entry : map.entrySet()) { final List> values = entry.getValue(); Option value = values.isEmpty() ? null : values.get(0); result.put(entry.getKey(), value != null ? value.value() : null); } return result; } public boolean has(String key) { return this.map.containsKey(key); } public boolean hasNonNull(String key) { List> values = this.map.get(key); return values != null && !values.isEmpty(); } private String getSingleOptionOrNull(String key) { List> values = this.map.get(key); if (values == null || values.isEmpty()) { return null; } else if (values.size() > 1) { throw new IllegalArgumentException("There are multiple values for '" + key + "'"); } return values.get(0).value(); } /** * Get a config option by name. * @param key name of the option, without any `-` or `--` prefix, eg. "o". * @return the string value of the option, or null if the user has not specified it */ public String get(String key) { return getSingleOptionOrNull(key); } public String get(String key, String defaultValue) { String value = getSingleOptionOrNull(key); return value != null ? value : defaultValue; } public String get(String key, String defaultValueIfNotFound, String defaultValueIfNoValue) { String value = getSingleOptionOrNull(key); if (value != null) { return value; } return this.map.containsKey(key) ? defaultValueIfNoValue : defaultValueIfNotFound; } public Number getNumber(String key, Number defaultValue) { String value = getSingleOptionOrNull(key); return value != null ? Double.valueOf(value) : defaultValue; } public long getDuration(String key, long defaultValueInMillis) { String value = getSingleOptionOrNull(key); return value != null ? TimeUtil.parseTimeMillis.apply(value) : defaultValueInMillis; } /** * Like calling {@link #getBoolean(String, Boolean)} with {@code false} for default value. * This is the most common case, i.e. returns {@code true} if boolean argument was specified as: *
    *
  • --myboolean
  • *
  • --myboolean=true
  • *
* Otherwise {@code false. * } * @param key argument key. * @return {@code true} if argument was specified w/o value, or w/ value {@code true}, otherwise {@code false}. */ public boolean getBoolean(String key) { return getBoolean(key, false); } /** * Like calling {@link #getBoolean(String, Boolean, Boolean)} with {@code true} for * {@code defaultValueIfNoValue}, i.e. specifying {@code --myarg} will interpret it as {@code true}. * * @param key argument key. * @param defaultValueIfNotSpecified used if argument not specified. * @return argument boolean value depending on what was specified, see above. */ public Boolean getBoolean(String key, Boolean defaultValueIfNotSpecified) { return getBoolean(key, defaultValueIfNotSpecified, Boolean.TRUE); } /** * Parses a {@code boolean} argument. There are a couple of cases: *
    *
  • The argument isn't specified. In this case the value of {@code defaultValueIfNotSpecified} * will be returned.
  • *
  • The argument is specified without value, for example
    --myboolean
    . In this case * the value of {@code defaultValueIfNotSpecified} will be returned.
  • *
  • The argument is specified with value, for example
    --myboolean=true
    . * In this case the actual value will be returned.
  • *
* * @param key argument key. * @param defaultValueIfNotSpecified used if argument not specified. * @param defaultValueIfSpecifiedButNoValue used if argument specified w/o value. * @return argument boolean value depending on what was specified, see above. */ public Boolean getBoolean( String key, Boolean defaultValueIfNotSpecified, Boolean defaultValueIfSpecifiedButNoValue) { String value = getSingleOptionOrNull(key); if (value != null) { return Boolean.valueOf(value); } return this.map.containsKey(key) ? defaultValueIfSpecifiedButNoValue : defaultValueIfNotSpecified; } public > T getEnum(Class enumClass, String key, T defaultValue) { String raw = getSingleOptionOrNull(key); if (raw == null) { return defaultValue; } for (T candidate : enumClass.getEnumConstants()) { if (candidate.name().equals(raw)) { return candidate; } } throw new IllegalArgumentException("No enum instance '" + raw + "' in " + enumClass.getName()); } /** * Orphans are arguments specified without options flags, eg: * *
myprogram -o blah orphan1 orphan2
* * Would yield a list here of {@code "orphan1"} and {@code "orphan2"}. * * @return list of orphan arguments */ public List orphans() { return new ArrayList<>(this.orphans); } /** * @see #orphans() * @return list of orphan arguments. */ public String[] orphansAsArray() { return orphans.toArray(new String[0]); } public String[] asArgs() { List list = new ArrayList<>(orphans.size()); for (String orphan : orphans) { String quote = orphan.contains(" ") ? " " : ""; list.add(quote + orphan + quote); } for (Map.Entry>> entry : map.entrySet()) { for (Option option : entry.getValue()) { String key = option.metadata != null ? entry.getKey() + OPTION_METADATA_DELIMITER + option.metadata() : entry.getKey(); String value = option.value(); String quote = key.contains(" ") || (value != null && value.contains(" ")) ? " " : ""; list.add(quote + (key.length() > 1 ? "--" : "-") + key + (value != null ? "=" + value + quote : "")); } } return list.toArray(new String[0]); } @Override public String toString() { StringBuilder builder = new StringBuilder(); for (String arg : asArgs()) { builder.append(builder.length() > 0 ? " " : "").append(arg); } return builder.toString(); } private static boolean isOption(String arg) { return arg.startsWith("-") && arg.length() > 1; } private boolean isFlag(String arg) { return ArrayUtil.contains(flags, arg); } private static boolean isBoolean(String value) { return "true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value); } private static String stripOption(String arg) { while (!arg.isEmpty() && arg.charAt(0) == '-') { arg = arg.substring(1); } return arg; } private void parseArgs(Function> optionParser, String[] args) { for (int i = 0; i < args.length; i++) { String arg = args[i]; if (isOption(arg)) { arg = stripOption(arg); int equalIndex = arg.indexOf('='); if (equalIndex != -1) { String key = arg.substring(0, equalIndex); String value = arg.substring(equalIndex + 1); if (!value.isEmpty()) { put(optionParser, key, value); } } else if (isFlag(arg)) { int nextIndex = i + 1; String value = nextIndex < args.length ? args[nextIndex] : null; if (isBoolean(value)) { i = nextIndex; put(optionParser, arg, Boolean.valueOf(value).toString()); } else { put(optionParser, arg, null); } } else { int nextIndex = i + 1; String value = nextIndex < args.length ? args[nextIndex] : null; value = (value == null || isOption(value)) ? null : value; if (value != null) { i = nextIndex; } put(optionParser, arg, value); } } else { orphans.add(arg); } } } private void put(Function> optionParser, String key, String value) { Option option = optionParser.apply(key); List> values = map.computeIfAbsent(option.value(), k -> new ArrayList<>()); values.add(new Option<>(value, option.metadata())); } public static String jarUsage(Class main, String... params) { StringBuilder usage = new StringBuilder("USAGE: java [-cp ...] "); String jar = main.getProtectionDomain().getCodeSource().getLocation().getPath(); usage.append("-jar ").append(jar); usage.append(' ').append(main.getCanonicalName()); for (String param : params) { usage.append(' ').append(param); } return usage.toString(); } @SafeVarargs public final T interpretOption( String key, Function defaultValue, Function converter, Validator... validators) { T value; if (!has(key)) { value = defaultValue.apply(key); } else { String stringValue = get(key); value = converter.apply(stringValue); } return validated(value, validators); } /** * An option can be specified multiple times; this method will allow interpreting all values for * the given key, returning a {@link Collection}. This is the only means of extracting multiple values * for any given option. All other methods revolve around zero or one value for an option. * * @param key Key of the option * @param defaultValue Default value value of the option * @param converter Converter to use * @param validators Validators to use * @param The type of the option values * @return The option values */ @SafeVarargs public final Collection interpretOptions( String key, Function defaultValue, Function converter, Validator... validators) { Collection> options = interpretOptionsWithMetadata(key, defaultValue, converter, validators); Collection values = new ArrayList<>(options.size()); for (Option option : options) { values.add(option.value()); } return values; } /** * An option can be specified multiple times; this method will allow interpreting all values for * the given key, returning a {@link Collection}. This is the only means of extracting multiple values * for any given option. All other methods revolve around zero or one value for an option. * This is also the only means of extracting metadata about a options. Metadata can be supplied as part * of the option key, like --my-option:Metadata "my value". * * @param key Key of the option * @param defaultValue Default value value of the option * @param converter Converter to use * @param validators Validators to use * @param The type of the option values * @return The option values */ @SafeVarargs public final Collection> interpretOptionsWithMetadata( String key, Function defaultValue, Function converter, Validator... validators) { Collection> values = new ArrayList<>(); if (!hasNonNull(key)) { T defaultItem = defaultValue.apply(key); if (defaultItem != null) { values.add(new Option<>(validated(defaultItem, validators), null)); } } else { for (Option option : map.get(key)) { String stringValue = option.value(); values.add(new Option<>(validated(converter.apply(stringValue), validators), option.metadata())); } } return values; } @SafeVarargs public final T interpretOrphan( int index, Function defaultValue, Function converter, Validator... validators) { assert index >= 0; T value; if (index >= orphans.size()) { value = defaultValue.apply("argument at index " + index); } else { String stringValue = orphans.get(index); value = converter.apply(stringValue); } return validated(value, validators); } @SafeVarargs private static T validated(T value, Validator... validators) { if (value != null) { for (Validator validator : validators) { validator.validate(value); } } return value; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy