CommandLine.java
/*
* Copyright (c) 2001-2024, Jean Tessier
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Jean Tessier nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jeantessier.commandline;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* Command-line parser.
*/
public class CommandLine implements Visitable {
private static final boolean DEFAULT_STRICT = true;
private final boolean strict;
private final ParameterStrategy parameterStrategy;
private final Map<String, CommandLineSwitch> map = new TreeMap<>();
public CommandLine() {
this(DEFAULT_STRICT, new CollectingParameterStrategy());
}
public CommandLine(boolean strict) {
this(strict, new CollectingParameterStrategy());
}
public CommandLine(ParameterStrategy parameterStrategy) {
this(DEFAULT_STRICT, parameterStrategy);
}
public CommandLine(boolean strict, ParameterStrategy parameterStrategy) {
this.strict = strict;
this.parameterStrategy = parameterStrategy;
}
public boolean isStrict() {
return strict;
}
public ParameterStrategy getParameterStrategy() {
return parameterStrategy;
}
public ToggleSwitch addToggleSwitch(String name) {
return addSwitch(new ToggleSwitch(name));
}
public ToggleSwitch addToggleSwitch(String name, boolean defaultValue) {
return addSwitch(new ToggleSwitch(name, defaultValue));
}
public SingleValueSwitch addSingleValueSwitch(String name) {
return addSwitch(new SingleValueSwitch(name));
}
public SingleValueSwitch addSingleValueSwitch(String name, boolean mandatory) {
return addSwitch(new SingleValueSwitch(name, mandatory));
}
public SingleValueSwitch addSingleValueSwitch(String name, String defaultValue) {
return addSwitch(new SingleValueSwitch(name, defaultValue));
}
public SingleValueSwitch addSingleValueSwitch(String name, String defaultValue, boolean mandatory) {
return addSwitch(new SingleValueSwitch(name, defaultValue, mandatory));
}
public OptionalValueSwitch addOptionalValueSwitch(String name) {
return addSwitch(new OptionalValueSwitch(name));
}
public OptionalValueSwitch addOptionalValueSwitch(String name, boolean mandatory) {
return addSwitch(new OptionalValueSwitch(name, mandatory));
}
public OptionalValueSwitch addOptionalValueSwitch(String name, String defaultValue) {
return addSwitch(new OptionalValueSwitch(name, defaultValue));
}
public OptionalValueSwitch addOptionalValueSwitch(String name, String defaultValue, boolean mandatory) {
return addSwitch(new OptionalValueSwitch(name, defaultValue, mandatory));
}
public MultipleValuesSwitch addMultipleValuesSwitch(String name) {
return addSwitch(new MultipleValuesSwitch(name));
}
public MultipleValuesSwitch addMultipleValuesSwitch(String name, boolean mandatory) {
return addSwitch(new MultipleValuesSwitch(name, mandatory));
}
public MultipleValuesSwitch addMultipleValuesSwitch(String name, String defaultValue) {
return addSwitch(new MultipleValuesSwitch(name, defaultValue));
}
public MultipleValuesSwitch addMultipleValuesSwitch(String name, String defaultValue, boolean mandatory) {
return addSwitch(new MultipleValuesSwitch(name, defaultValue, mandatory));
}
/**
* Returns an {@link AliasSwitch} mapping name to switchNames.
*
* @param name the name of the new alias.
* @param switchNames the switches that the alias maps to.
* @return an AliasSwitch for the new alias.
* @throws IllegalArgumentException if any switch name is unknown.
*
* @see AliasSwitch
*/
public AliasSwitch addAliasSwitch(String name, String ... switchNames) {
CommandLineSwitch[] switches = new CommandLineSwitch[switchNames.length];
IntStream.range(0, switchNames.length)
.forEach(i -> switches[i] = getSwitch(switchNames[i], true));
return addSwitch(new AliasSwitch(name, switches));
}
private <T extends CommandLineSwitch> T addSwitch(T cls) {
map.put(cls.getName(), cls);
return cls;
}
/**
* Returns a {@link CommandLineSwitch} matching name, if any.
*
* @param name the name of the switch to lookup.
* @return a switch matching name.
* @throws IllegalArgumentException if this CommandLine is strict and name is unknown.
*
* @see CommandLineSwitch
*/
public CommandLineSwitch getSwitch(String name) {
return getSwitch(name, isStrict());
}
/**
* Returns a {@link CommandLineSwitch} matching name, if any.
*
* @param name the name of the CommandLineSwitch to lookup.
* @param strict if true, will throw an exception if name is unknown.
* @return a CommandLineSwitch matching name.
* @throws IllegalArgumentException if strict is true and name is unknown.
*/
public CommandLineSwitch getSwitch(String name, boolean strict) {
CommandLineSwitch cls = map.get(name);
if (cls == null) {
if (strict) {
throw new IllegalArgumentException("Unknown switch \"" + name + "\"");
} else {
cls = new OptionalValueSwitch(name);
addSwitch(cls);
}
}
return cls;
}
public boolean getToggleSwitch(String name) {
boolean result = false;
CommandLineSwitch cls = map.get(name);
if (cls != null) {
result = (Boolean) cls.getValue();
}
return result;
}
public String getSingleSwitch(String name) {
return getStringSwitch(name);
}
public String getOptionalSwitch(String name) {
return getStringSwitch(name);
}
public List<String> getMultipleSwitch(String name) {
return getListSwitch(name);
}
private String getStringSwitch(String name) {
String result = null;
CommandLineSwitch cls = map.get(name);
if (cls != null) {
result = cls.getValue().toString();
}
return result;
}
private List<String> getListSwitch(String name) {
List<String> result = null;
CommandLineSwitch cls = map.get(name);
if (cls != null && cls.getValue() instanceof List) {
result = (List<String>) cls.getValue();
}
return result;
}
public boolean isPresent(String name) {
boolean result = false;
CommandLineSwitch cls = map.get(name);
if (cls != null) {
result = cls.isPresent();
}
return result;
}
public Set<String> getKnownSwitches() {
return map.keySet();
}
public Collection<CommandLineSwitch> getSwitches() {
return map.values();
}
public Set<String> getPresentSwitches() {
return getKnownSwitches().stream()
.map(map::get)
.filter(Objects::nonNull)
.filter(CommandLineSwitch::isPresent)
.map(CommandLineSwitch::getName)
.collect(Collectors.toCollection(TreeSet::new));
}
public List<String> getParameters() {
return parameterStrategy.getParameters();
}
public Collection<CommandLineException> parse(String[] args) {
Collection<CommandLineException> exceptions = new ArrayList<>();
int i=0;
while (i < args.length) {
try {
if (args[i].startsWith("-")) {
String name = args[i].substring(1);
String value = null;
if (i+1 < args.length && !map.containsKey(args[i+1].substring(1))) {
value = args[i+1];
}
i += getSwitch(name).parse(value);
} else {
i += parameterStrategy.accept(args[i]);
}
} catch (CommandLineException e) {
exceptions.add(e);
i++;
}
}
// Checking that all manadatory switches are present
map.values().forEach(cls -> {
try {
cls.validate();
} catch (CommandLineException e) {
exceptions.add(e);
}
});
// Checking that all mandatory parameters are present
try {
parameterStrategy.validate();
} catch (CommandLineException e) {
exceptions.add(e);
}
return exceptions;
}
public void accept(Visitor visitor) {
visitor.visitCommandLine(this);
}
}