Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openj9
Path: blob/master/debugtools/DDR_VM/src/com/ibm/j9ddr/command/CommandParser.java
6005 views
1
/*******************************************************************************
2
* Copyright (c) 2011, 2019 IBM Corp. and others
3
*
4
* This program and the accompanying materials are made available under
5
* the terms of the Eclipse Public License 2.0 which accompanies this
6
* distribution and is available at https://www.eclipse.org/legal/epl-2.0/
7
* or the Apache License, Version 2.0 which accompanies this distribution and
8
* is available at https://www.apache.org/licenses/LICENSE-2.0.
9
*
10
* This Source Code may also be made available under the following
11
* Secondary Licenses when the conditions for such availability set
12
* forth in the Eclipse Public License, v. 2.0 are satisfied: GNU
13
* General Public License, version 2 with the GNU Classpath
14
* Exception [1] and GNU General Public License, version 2 with the
15
* OpenJDK Assembly Exception [2].
16
*
17
* [1] https://www.gnu.org/software/classpath/license.html
18
* [2] http://openjdk.java.net/legal/assembly-exception.html
19
*
20
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception
21
*******************************************************************************/
22
23
package com.ibm.j9ddr.command;
24
25
import java.io.File;
26
import java.io.FileOutputStream;
27
import java.io.IOException;
28
import java.io.PrintStream;
29
import java.text.ParseException;
30
import java.util.ArrayList;
31
import java.util.Arrays;
32
import java.util.List;
33
34
35
/**
36
* Handles command line entries and does extra interpretation, such as handling quoted strings (whilst discarding
37
* quotes themselves and handling escaped characters), recognising redirection to file etc.
38
*
39
* NOTE: As of Nov 2011, another copy of this class lives in com.ibm.java.diagnostics. Any changes to this file must be copied across.
40
* @author blazejc
41
*
42
*/
43
public class CommandParser {
44
45
private String originalLine; // used in toString(), primarily for debugging
46
47
private List<String> allTokens = new ArrayList<String>();
48
private String command;
49
private List<String> arguments = new ArrayList<String>(); // just the command arguments, ignoring file redirection
50
private String redirectionFilename;
51
52
private boolean isAppendToFile;
53
private boolean isRedirectedToFile;
54
55
public CommandParser(String commandLine) throws ParseException {
56
originalLine = commandLine;
57
tokenise(commandLine);
58
parse(allTokens);
59
}
60
61
public CommandParser(String command, String[] arguments) throws ParseException {
62
List<String> allTokens = new ArrayList<String>();
63
allTokens.add(command);
64
allTokens.addAll(Arrays.asList(arguments));
65
66
originalLine = command;
67
68
for (String arg : arguments) {
69
originalLine += " " + arg;
70
}
71
72
parse(allTokens);
73
}
74
75
@Override
76
public String toString() {
77
return originalLine;
78
}
79
80
public String getOriginalLine() {
81
return originalLine;
82
}
83
84
public String getCommand() {
85
return command;
86
}
87
88
public boolean isRedirectedToFile() {
89
return isRedirectedToFile || isAppendToFile;
90
}
91
92
/**
93
* Just the command arguments, strips the ">" or ">>" and all strings that follow (i.e. file redirection).
94
* @return
95
*/
96
public String[] getArguments() {
97
return arguments.toArray(new String[0]);
98
}
99
100
/**
101
* Returns a PrintStream, which either writes to a new file or overwrites or appends to an existing one,
102
* depending on the command.
103
* @return
104
* @throws IOException
105
* @throws IllegalStateException - thrown if the command is not redirected to file. Use isRedirectedToFile() to check.
106
*/
107
public PrintStream getOutputFile() throws IOException {
108
if (!isRedirectedToFile && !isAppendToFile) {
109
throw new IllegalStateException("Attempt to create output file for a command which does not contain file redirection.");
110
}
111
112
PrintStream outStream = null;
113
File outFile = new File(redirectionFilename);
114
115
if (redirectionFilename.indexOf(File.separator) > -1) {
116
// The last entry is the filename itself (we've checked that during parsing stage)
117
// so everything before it is the directory, some of which may not exist, so need to create it
118
String parentDirectories = redirectionFilename.substring(0, redirectionFilename.lastIndexOf(File.separator));
119
File dir = new File(parentDirectories);
120
if (!dir.exists() && !dir.mkdirs()) {
121
throw new IOException("Could not create some of the requested directories: " + parentDirectories);
122
}
123
}
124
125
if (isAppendToFile && outFile.exists()) {
126
// in this case do not create a new file, but use the existing one
127
outStream = new PrintStream(new FileOutputStream(outFile, true));
128
} else {
129
outStream = new PrintStream(outFile); // this will create a new file
130
}
131
132
return outStream;
133
}
134
135
/*
136
* The tokenise() and parse() work together to interpret the command line.
137
* Initially, tokenise() reads the raw text and applies syntax rules to break it up into tokens
138
* (e.g. by handling quoted strings). Then, parse() reads the tokens and extracts the command
139
* itself, its arguments and possible file redirection options.
140
*/
141
142
private void tokenise(String commandLine) throws ParseException {
143
char[] characters = commandLine.toCharArray();
144
145
int i = 0;
146
147
TokenisingState currentState = new GenericTokenState(this);
148
do {
149
currentState = currentState.process(characters[i++]);
150
151
if (i == characters.length) {
152
currentState.endOfInput();
153
}
154
} while (i < characters.length);
155
}
156
157
private void parse(List<String> allTokens) throws ParseException {
158
if (allTokens.size() == 0) {
159
return;
160
}
161
162
command = allTokens.get(0);
163
int i;
164
for (i = 1; i < allTokens.size(); i++) {
165
if (!allTokens.get(i).equals(">") && !allTokens.get(i).equals(">>")) {
166
arguments.add(allTokens.get(i));
167
} else {
168
break;
169
}
170
}
171
172
// all that follows after and including '>' or '>>' is file redirection
173
if (i < allTokens.size()) {
174
if (allTokens.get(i).equals(">")) {
175
isAppendToFile = false;
176
isRedirectedToFile = true;
177
} else { // isn't '>' so must be '>>', otherwise it would be another argument captured in the previous loop
178
isAppendToFile = true;
179
isRedirectedToFile = false;
180
}
181
182
if (i + 1 < allTokens.size()) {
183
String filename = allTokens.get(i + 1).trim();
184
if (filename.charAt(filename.length() - 1) == File.separatorChar) {
185
throw new ParseException("Invalid redirection path - missing filename", i);
186
}
187
188
redirectionFilename = allTokens.get(i + 1);
189
} else {
190
throw new ParseException("Missing file name for redirection", i);
191
}
192
}
193
}
194
195
// TokenisingState classes
196
197
/**
198
* A mini state machine. Each state receives a character and returns the next state.
199
* So, for example, if a GenericTokenState encounters a quotation mark (either single or double),
200
* it returns a QuotedStringState, which has different rules. If a QuotedStringState encounters
201
* an (unescaped) quotation mark (i.e. end of quotation) it exits and returns a GenericTokenState.
202
*/
203
abstract class TokenisingState {
204
205
protected CommandParser context;
206
protected StringBuilder currentToken = new StringBuilder();
207
abstract protected TokenisingState process(Character chr) throws ParseException;
208
209
protected TokenisingState(CommandParser context) {
210
this.context = context;
211
}
212
213
protected void storeToken() {
214
if (currentToken.length() > 0) {
215
context.allTokens.add(currentToken.toString());
216
currentToken = new StringBuilder();
217
}
218
}
219
220
abstract protected void endOfInput() throws ParseException;
221
}
222
223
/**
224
* Any token written to the command line as-is, without any quotation or escaping
225
* @author blazejc
226
*
227
*/
228
class GenericTokenState extends TokenisingState {
229
230
protected GenericTokenState(CommandParser context) {
231
super(context);
232
}
233
234
@Override
235
protected TokenisingState process(Character chr) throws ParseException {
236
if (Character.isWhitespace(chr)) {
237
if (currentToken.toString().equals(">") || currentToken.toString().equals(">>")) {
238
storeToken();
239
return new FilenameState(context);
240
} else {
241
storeToken();
242
return this;
243
}
244
}
245
246
switch (chr) {
247
case '"':
248
case '\'':
249
if (currentToken.length() > 0) {
250
throw new ParseException("'" + chr + "'" + " invalid inside a string", 0);
251
} else {
252
return new QuotedStringState(context, chr);
253
}
254
default:
255
currentToken.append(chr);
256
return this;
257
}
258
}
259
260
@Override
261
protected void endOfInput() {
262
storeToken();
263
}
264
}
265
266
class QuotedStringState extends TokenisingState {
267
268
private Character quoteType;
269
private boolean escape = false;
270
271
protected QuotedStringState(CommandParser context, Character quoteType) {
272
super(context);
273
this.quoteType = quoteType;
274
}
275
276
@Override
277
protected TokenisingState process(Character chr) throws ParseException {
278
switch (chr) {
279
case '\\':
280
if (escape) { // are we're escaping a '\'?
281
currentToken.append(chr);
282
escape = false;
283
} else {
284
// don't store the escape character itself
285
escape = true;
286
}
287
return this;
288
case '"':
289
case '\'': {
290
if (chr != quoteType) { // a quote different than the surrounding one doesn't need to be escaped, just treat it as any other character
291
currentToken.append(chr);
292
return this;
293
} else { // same quote as the surrounding one
294
if (escape) {
295
currentToken.append(chr);
296
escape = false;
297
return this;
298
} else { // same quote as the surrounding one, so we're closing the quote here
299
storeToken();
300
return new GenericTokenState(context);
301
}
302
}
303
}
304
default:
305
if (escape) {
306
// the previous character was a backslash (hence escaping flag is set), but what follows is just a normal character,
307
// so put back the backslash, we're not escaping anything
308
currentToken.append("\\");
309
escape = false;
310
}
311
currentToken.append(chr); // append any other character, leave QuotedStringState as current state
312
return this;
313
}
314
}
315
316
@Override
317
protected void endOfInput() throws ParseException {
318
if (currentToken.length() > 0) { // we haven't closed the quote, but the input has ended - that's an error
319
throw new ParseException("Unmatched " + quoteType, 0);
320
}
321
}
322
}
323
324
class FilenameState extends TokenisingState {
325
326
protected FilenameState(CommandParser context) {
327
super(context);
328
}
329
330
private Character quoteType = null; // the quote the whole filename is surrounded with, null if it isn't
331
private boolean alreadyStarted = false;
332
private boolean alreadyFinished = false;
333
334
@Override
335
protected TokenisingState process(Character chr) throws ParseException {
336
if (alreadyFinished) {
337
throw new ParseException("Only one file name is permitted", 0);
338
}
339
340
if (!alreadyStarted) {
341
if (chr.equals('"') || chr.equals('\'')) {
342
quoteType = chr;
343
} else {
344
currentToken.append(chr);
345
}
346
alreadyStarted = true;
347
} else {
348
if (chr.equals(quoteType)) {
349
storeToken();
350
alreadyFinished = true;
351
} else if (Character.isWhitespace(chr)) {
352
if (quoteType != null) {
353
currentToken.append(chr); // whitespace within a quoted string is just another character, so store it
354
} else {
355
storeToken(); // whitespace in an unquoted string is the end of it
356
alreadyFinished = true;
357
}
358
} else {
359
currentToken.append(chr);
360
}
361
}
362
363
return this;
364
}
365
366
@Override
367
protected void endOfInput() throws ParseException {
368
if (!alreadyStarted) {
369
throw new ParseException("Missing file name", 0);
370
}
371
if (alreadyStarted && !alreadyFinished && quoteType != null) {
372
throw new ParseException("Unmatched " + quoteType + " in file name", 0);
373
}
374
//started; not finished but no quotes, or finished with quotes
375
storeToken();
376
}
377
}
378
}
379
380