blob: d1e2e9bbb289d6e5e166cbed34b5fc2857a4185f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.internal.commandline.argument.parser;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
import org.apache.ignite.IgniteException;
import org.apache.ignite.internal.util.GridStringBuilder;
import static java.util.stream.Collectors.toSet;
/**
* Parser for command line arguments.
*/
public class CLIArgumentParser {
/** */
private final Map<String, CLIArgument> argConfiguration = new LinkedHashMap<>();
/** */
private final Map<String, Object> parsedArgs = new HashMap<>();
/** */
public CLIArgumentParser(List<CLIArgument> argConfiguration) {
for (CLIArgument cliArgument : argConfiguration)
this.argConfiguration.put(cliArgument.name(), cliArgument);
}
/**
* Parses arguments using iterator. Parsed argument value are available through {@link #get(CLIArgument)}
* and {@link #get(String)}.
*
* @param argsIter Iterator.
*/
public void parse(Iterator<String> argsIter) {
Set<String> obligatoryArgs =
argConfiguration.values().stream().filter(a -> !a.optional()).map(CLIArgument::name).collect(toSet());
while (argsIter.hasNext()) {
String arg = argsIter.next();
CLIArgument cliArg = argConfiguration.get(arg);
if (cliArg == null)
throw new IgniteException("Unexpected argument: " + arg);
if (cliArg.type().equals(Boolean.class))
parsedArgs.put(cliArg.name(), true);
else {
if (!argsIter.hasNext())
throw new IgniteException("Please specify a value for argument: " + arg);
String strVal = argsIter.next();
parsedArgs.put(cliArg.name(), parseVal(strVal, cliArg.type()));
}
obligatoryArgs.remove(cliArg.name());
}
if (!obligatoryArgs.isEmpty())
throw new IgniteException("Mandatory argument(s) missing: " + obligatoryArgs);
}
/** */
private Object parseVal(String val, Class type) {
switch (type.getSimpleName()) {
case "String": return val;
case "String[]": return val.split(",");
case "Integer": return wrapNumberFormatException(() -> Integer.parseInt(val), val, Integer.class);
case "Long": return wrapNumberFormatException(() -> Long.parseLong(val), val, Long.class);
case "UUID": return UUID.fromString(val);
default: throw new IgniteException("Unsupported argument type: " + type.getName());
}
}
/**
* Wrap {@link NumberFormatException} to get more user friendly message.
*
* @param closure Closure that parses number.
* @param val String value.
* @param expectedType Expected type.
* @return Parsed result, if parse had success.
*/
private Object wrapNumberFormatException(Supplier<Object> closure, String val, Class<? extends Number> expectedType) {
try {
return closure.get();
}
catch (NumberFormatException e) {
throw new NumberFormatException("Can't parse number '" + val + "', expected type: " + expectedType.getName());
}
}
/**
* Get parsed argument value.
*
* @param arg Argument configuration.
* @param <T> Value type.
* @return Value.
*/
public <T> T get(CLIArgument arg) {
Object val = parsedArgs.get(arg.name());
if (val == null)
return (T)arg.defaultValueSupplier().apply(this);
else
return (T)val;
}
/**
* Get parsed argument value.
*
* @param name Argument name.
* @param <T> Value type.
* @return Value.
*/
public <T> T get(String name) {
CLIArgument arg = argConfiguration.get(name);
if (arg == null)
throw new IgniteException("No such argument: " + name);
return get(arg);
}
/**
* Returns usage description.
*
* @return Usage.
*/
public String usage() {
GridStringBuilder sb = new GridStringBuilder("Usage: ");
for (CLIArgument arg : argConfiguration.values())
sb.a(argNameForUsage(arg)).a(" ");
for (CLIArgument arg : argConfiguration.values()) {
Object dfltVal = null;
try {
dfltVal = arg.defaultValueSupplier().apply(this);
}
catch (Exception ignored) {
/* No op. */
}
sb.a("\n\n").a(arg.name()).a(": ").a(arg.usage());
if (arg.optional())
sb.a(" Default value: ").a(dfltVal);
}
return sb.toString();
}
/** */
private String argNameForUsage(CLIArgument arg) {
if (arg.optional())
return "[" + arg.name() + "]";
else
return arg.name();
}
}