Only allow specified command name but still allow additional arguments - java

I am currently using this code to find a command that the user types in:
final Command command = commands.stream()
.filter(cmd -> input.startsWith(cmd.getName()))
.findAny()
.orElseThrow(() -> new InvalidInputException("unknown command"));
Every command class has its own getName() method. This is how the getName() method of ExitCommand() looks like:
#Override
public String getName() {
return "exit";
}
Unfortunately, with the current stream, "exittttttt" is also accepted. I can't use .equals(cmd.getName()) though, because there are commands that have subsequent arguments after the command name.
For example:
#Override
public String getName() {
return "delete track";
}
But the full command is delete track <id>.
Does anyone have an idea how to only allow the command name that is specified in each getName() method but also still allow further arguments?
EDIT:
Each command has its own getArguments() method. The method will return 0 for the exit command and 1 for the delete track command. Maybe this can be used to solve this problem?

If the space is what always separates commands from arguments, then you can use
.filter(cmd -> (cmd.getArguments() == 0 && input.equals(cmd.getName()))
|| input.startsWith(cmd.getName() + " "))
This checks that the input matches the command name exactly if the command supports no arguments, or the input has the command name followed by a space.
If commands supporting arguments may be called without arguments, then maybe this is the right predicate:
.filter(cmd -> (cmd.getArguments() == 0 && input.equals(cmd.getName()))
|| input.equals(cmd.getName()) //no args passed
|| input.startsWith(cmd.getName() + " "))

What about this line:
final Command command = commands.stream()
.filter(cmd -> (input.split(" +").length > 0 && input.split(" +")[0].equals(cmd.getName())))
.findAny()
.orElseThrow(() -> new InvalidInputException("unknown command"));
?

Your design need to be revisited. When we say a command, it will have options and arguments. In your case you are combining the command and option. Just seperate those out and use equals for command comparison.
delete -track <id>
delete- command
track- option can be identified with start of hyphen
optional arguments

Related

ProcessHandle returns ambiguous results

I've two Java 11 methods that check whether a process for a given PID is running:
public static final boolean isProcessRunning( final long pid ) {
Optional< ProcessHandle > optionalProcess = ProcessHandle.of( pid );
return optionalProcess.isPresent() && optionalProcess.get().isAlive();
}
public static boolean isProcessRunning2( final long pid ) {
return ProcessHandle.allProcesses()
.filter( p -> p.pid() == pid )
.anyMatch( p -> p.isAlive() );
}
When checking multiple PIDs on Linux clients, I'm getting sometimes different results for certain PIDs, where the first implementation always returns true and the second one always false.
Checking some "false positives" with shell command ps -ef | grep <pid> shows that first implementation seems to be wrong, the OS doesn't know about these processes neither.
Assumably the second implementation is always right but seems to be very inefficient.
What's wrong with the first implementation and how can I fix it?
Having a look at the implementation of ProcessHandleImpl.isAlive() (Java 11 version) it seems that this method might return true if the native method isAlive0(pid) returns 0 - which it does "if the start time cannot be determined". The same native method is also used to determine whether to return an empty optional (returns -1) or not (returns 0 or actual start time). ProcessHandle.allProcesses() on the other hand gets all children of pid 0 and then calls getProcessPids0(...) (another native method) to return the pids and start times.
So it seems to be a difference in the native code of the JVM you're using - or one in your OS (depends on what the native code is doing).
To "fix" your first snippet, you could eliminate the "0" start times by using info().startInstant().isPresent():
public static final boolean isProcessRunning( final long pid, final boolean excludeUnsure ) {
Optional< ProcessHandle > optionalProcess = ProcessHandle.of( pid );
if( excludeUnsure ) {
return optionalProcess.map(ph -> ph.info())
.flatMap(info -> info.startInstant())
.isPresent();
} else {
return optionalProcess.map(ph -> ph.isAlive()).orElse(Boolean.FALSE);
}
}

How to separate parameters from a command in Java?

I'm writing a program for an address book in Java. The user will enter commands such as add-addressbook . The main problem I'm having is knowing which command is entered and taking the parameters from the command and putting them into methods. Not all commands have the same number of parameters. There is a space between the command and the parameters. The parameters are separated by exactly one comma. This code works when the user enters everything properly but returns an error otherwise. Is there a better way to do this?
while (!input.equals("quit")) {
String[] data = input.split(" ", 2);
ArrayList<String> listofdata = new ArrayList<String>(Arrays.asList(data[1].split(",")));
switch (data[0]) {
case "add-addressbook":
String nameofBook = listofdata.get(0);
c.addBook(nameofBook);
break;
case "add-contact":
String nameofBook2 = listofdata.get(0);
String nameofContact = listofdata.get(1);
String email = listofdata.get(2);
String phone = listofdata.get(3);
c.addPerson(nameofBook2, nameofContact, email, phone);
break;
//rest of code
}
You'd better create a common interface Command with method that accepts either String or List<String>. And several separate implementations for each command.
And then each concrete implementation will parse its parameter and either process it or throw exception (or return false or String with error message).

How to make ObservableBooleanValue with two arguments in Java?

I want to add second argument to when() condition for ObservableBooleanValue. In case when there is only one argument
it`s working correctly. Problem in that line:
game.winnerProperty().isEqualTo(Square.State.EMPTY) || (GameTimer::isTimeOver==true)
This is ok:
game.winnerProperty().isEqualTo(Square.State.EMPTY) //This is BooleanBinding
Code:
playerLabel.textProperty().bind(
Bindings.when(
game.gameOverProperty().not()
)
.then("Actual Player: ")
.otherwise(
Bindings.when(
game.winnerProperty().isEqualTo(Square.State.EMPTY) || (GameTimer::isTimeOver==true)
)
.then("Draw")
.otherwise("Winner: ")
)
);
How to add second argument which type is boolean?
Sometimes it's convenient to combine multiple bindings.
However this can lead to complex code that is hard to understand/maintain. It would be easier to use Bindings.createStringBinding and add the proper dependencies:
playerLabel.textProperty().bind(
Bindings.createStringBinding(() -> {
if (game.isGameOver()) {
return Square.State.EMPTY.equals(game.getWinner()) || gameTimer.isTimeOver()
? "Draw"
: "Winner: ";
} else {
return "Actual Player: ";
}
},
game.gameOverProperty(),
game.winnerProperty(),
gameTimer.timeOverProperty()));
You can do
game.winnerProperty().isEqualTo(Square.State.EMPTY).or(/* whatever you actually mean here */);
The argument to or here needs to be another ObservableBooleanValue (e.g. a BooleanProperty): I don't really know what is intended by the method reference you currently have.

DefaultParser in Commons CLI doesn't behave like the deprecated parsers

Problem
I'm trying to parse some command line arguments in any order. Two of them are single-value and mandatory and the other one is an optional comma-separated list:
usage:
-mo <value1,value2,...,valueN>
-sm1 <value>
-sm2 <value>
Using any of the old parsers (BasicParser, PosixParser and GnuParser) the code works fine but if I use DefaultParser instead, a MissingOptionException is thrown.
Code
import java.util.Arrays;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
public class Foo {
public static void main(String[] args) throws Exception {
Option singleMandatory1 = Option.builder("sm1")
.argName("value")
.hasArg()
.required()
.build();
Option singleMandatory2 = Option.builder("sm2")
.argName("value")
.hasArg()
.required()
.build();
Option multipleOptional = Option.builder("mo")
.argName("value1,value2,...,valueN")
.hasArgs()
.valueSeparator(',')
.build();
Options options = new Options();
options.addOption(singleMandatory1);
options.addOption(singleMandatory2);
options.addOption(multipleOptional);
CommandLineParser parser = new DefaultParser();
CommandLine line = parser.parse(options, args);
for (Option o : line.getOptions()) {
System.out.println(o.getOpt() + '\t'
+ Arrays.toString(o.getValues()));
}
}
}
Command line arguments
-sm1 Alice -sm2 Bob -mo Charles,David works
-sm1 Alice -mo Charles,David -sm2 Bob works only using the old (and now deprecated) parsers
Am I missing something? I'm using commons-cli-1.4-SNAPSHOT.
Thanks for any help.
I think this is a bug in DefaultParser. Ultimately it boils down to this method:
/**
* Tells if the token looks like a short option.
*
* #param token
*/
private boolean isShortOption(String token)
{
// short options (-S, -SV, -S=V, -SV1=V2, -S1S2)
return token.startsWith("-") && token.length() >= 2 &&
options.hasShortOption(token.substring(1, 2));
}
(Line broken to make it easier to read on SO).
Unfortunately this will always return false for "short options" that are more than a single character, because of the final clause options.hasShortOption(token.substring(1, 2)). It will certainly fail on items 2, 3 and 4 in the comment immediately preceding the return statement, which leads me to believe it is a bug. I may be misinterpreting the intention behind the comment, so please ignore previous statement.
A fix might look something like this:
/**
* Tells if the token looks like a short option.
*
* #param token
*/
private boolean isShortOption(String token)
{
// short options (-S, -SV, -S=V, -SV1=V2, -S1S2)
// extended to handle short options of more than one character
if (token.startsWith("-") && token.length() >= 2)
{
return options.hasShortOption(token.substring(1, 2)) ||
options.hasShortOption(extractShortOption(token));
}
return false;
}
/**
* Extract option from token. Assume the token starts with '-'.
*/
private String extractShortOption(String token)
{
int index = token.indexOf('=');
return (index == -1) ? token.substring(1) : token.substring(1, index);
}
Unfortunately there is no nice way to get this into DefaultParser as the methods are private, the calling methods are private (isOption, isArgument and handleToken) and DefaultParser relies on package local methods in Options.
The way I tested a fix was to copy/plaster DefaultParser into my local project, move into org.apache.commons.cli package and make the changes above.
As a dodgy work around for the specific case in the question, you could add a dummy short option "s", which would trick isShortOption(...) into returning true for sm1 and/or sm2 options. Something like this:
Option singleMandatory1 = Option.builder("sm1")
.argName("value")
.hasArg()
.required()
.build();
Option singleMandatory2 = Option.builder("sm2")
.argName("value")
.hasArg()
.required()
.build();
Option multipleOptional = Option.builder("mo")
.argName("value1,value2,...,valueN")
.hasArgs()
.valueSeparator(',')
.build();
Option dummyOptional = Option.builder("s")
.build();
Options options = new Options();
options.addOption(singleMandatory1);
options.addOption(singleMandatory2);
options.addOption(multipleOptional);
options.addOption(dummyOptional);
CommandLineParser parser = new DefaultParser();
CommandLine line = parser.parse(options, args);
This issue on ASF JIRA appears to capture the issue, albeit with a slightly different trigger case: https://issues.apache.org/jira/browse/CLI-265
With the "mo" option you're using the hasArgs() method instead of hasArg(). As a result in the latter case -sm2 and Bob will be parsed as additional arguments for the "mo" option. When using hasArg() instead, the example works fine (you can still pass multiple values to the "mo" option)

Returns java.lang.IllegalArgumentException in ProcessBuilder.start()

ProcessBuilder pb = new ProcessBuilder(commandInformation);
Process process = pb.start();
Above code is returning error:
java.lang.IllegalArgumentException
at java.lang.ProcessImpl.<init>(ProcessImpl.java:69)
at java.lang.ProcessImpl.start(ProcessImpl.java:30)
at java.lang.ProcessBuilder.start(ProcessBuilder.java:452)
at com.ConvertsImages.SystemCommandExecutor.executeCommand(SystemCommandExecutor.java:51)
at com.ConvertsImages.ImageConversion.runConvertCommand(ImageConversion.java:115)
at com.ConvertsImages.ImageConversion.runConvert(ImageConversion.java:80)
at com.ConvertsImages.ImageConversion.main(ImageConversion.java:26)
List commandInformation has only one entry as below:
["D:\Program Files\ImageMagick-6.8.6-Q16\convert.exe" "D:\ConvertFiles\ReImport_2507_1.jpg" -resize 40x40 "D:\ConvertFiles\proxy-40\ReImport_2507_1.jpg.jpg" ]
Please suggest.
Looking at the source code for ProcessBuilder, there is a method isQuoted that checks if a String argument is quoted and throws IllegalArgumentException if it is and a flag is checked.
private static boolean isQuoted(boolean noQuotesInside, String arg,
String errorMessage) {
int lastPos = arg.length() - 1;
if (lastPos >=1 && arg.charAt(0) == '"' && arg.charAt(lastPos) == '"') {
// The argument has already been quoted.
if (noQuotesInside) {
if (arg.indexOf('"', 1) != lastPos) {
// There is ["] inside.
throw new IllegalArgumentException(errorMessage);
}
}
return true;
}
if (noQuotesInside) {
if (arg.indexOf('"') >= 0) {
// There is ["] inside.
throw new IllegalArgumentException(errorMessage);
}
}
return false;
}
The above is called from getExecutablePath, note the true flag
boolean pathIsQuoted = isQuoted(true, path,
"Executable name has embedded quote, split the arguments");
which is called inside the ProcessImpl constructor
String executablePath = getExecutablePath(cmd[0]);
where cmd is the array created from your list. Index 0 matches the executable (in your case the whole String). In the String you showed us, your executable is quoted (or at least starts with one), so the method will throw an IllegalArgumentException.
This is confirmed by your stack trace
at java.lang.ProcessImpl.<init>(ProcessImpl.java:69)
That means inside your constructor.
Split each argument in your command list as a separate String element in the list. Don't put quotes around the executable.
I suspect you need a list of 'n' arguments.
It's currently interpreting your first list entry as the executable, and that's clearly not right since it incorporates your arguments too.
Please make sure, that the parameter passed in ProcessBuilder, commandInformation is a String array or a List<String>.
Parameters: command A string array containing the program and its
arguments
Parameters:
command The list containing the program and its arguments
Source
Oracle Docs. which states one of the reasons for IllegalArgumetnException.
Also I just noticed the file name that you have is included with .jpg.jpg .
Shouldn't it be fileName.jpg

Categories

Resources