-
Notifications
You must be signed in to change notification settings - Fork 20
/
CommandParser.java
144 lines (120 loc) · 3.8 KB
/
CommandParser.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - [email protected]
*/
package sirius.kernel.commons;
import sirius.kernel.tokenizer.LookaheadReader;
import javax.annotation.Nullable;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Parses command invocations as expected by {@link Runtime#exec(String[])}.
* <p>
* A command is a verb, followed by additional parameters (0..N). Parameters are separated by one or more
* whitespaces. If a parameter value itself contains whitespaces, it can be put into quotes:
* <tt>command arg1 arg2 "argument 3"</tt>.
*/
public class CommandParser {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private final String input;
private String command;
private List<String> args;
/**
* Creates a new command parser for the given input.
*
* @param input the command string to parse
*/
public CommandParser(String input) {
this.input = input;
}
/**
* Extracts the command (the first token) of the given input.
*
* @return the actual name of the command or <tt>null</tt> if the input was empty
*/
@Nullable
public String parseCommand() {
if (command == null) {
parse();
}
return command;
}
/**
* Obtains the list of arguments in the given string.
*
* @return the list of arguments in the input
*/
public List<String> getArgs() {
if (args == null) {
parse();
}
return Collections.unmodifiableList(args);
}
/**
* Returns the arguments as array.
*
* @return the arguments as returned by {@link #getArgs()} readily converted into an array.
*/
public String[] getArgArray() {
return getArgs().toArray(EMPTY_STRING_ARRAY);
}
private void parse() {
command = null;
args = new ArrayList<>();
if (Strings.isEmpty(input)) {
return;
}
LookaheadReader reader = new LookaheadReader(new StringReader(input));
skipWhitespace(reader);
while (!reader.current().isEndOfInput()) {
String token = parseToken(reader);
if (Strings.isFilled(token)) {
if (command == null) {
command = token;
} else {
args.add(token);
}
}
skipWhitespace(reader);
}
}
private void skipWhitespace(LookaheadReader reader) {
while (reader.current().isWhitespace()) {
reader.consume();
}
}
private String parseToken(LookaheadReader reader) {
if (reader.current().is('\"')) {
return parseEscapedToken(reader);
} else {
return parseNormalToken(reader);
}
}
private String parseNormalToken(LookaheadReader reader) {
StringBuilder current = new StringBuilder();
while (!reader.current().isEndOfInput() && !reader.current().isWhitespace()) {
current.append(reader.consume().getValue());
}
return current.toString();
}
private String parseEscapedToken(LookaheadReader reader) {
StringBuilder current = new StringBuilder();
reader.consume();
while (!reader.current().isEndOfInput() && !reader.current().is('\"')) {
if (reader.current().is('\\')) {
reader.consume();
if (!reader.current().isEndOfInput()) {
current.append(reader.consume().getValue());
}
} else {
current.append(reader.consume().getValue());
}
}
return current.toString();
}
}