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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package edu.caltech.nanodb.client;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import edu.caltech.nanodb.server.CommandResult;
/**
* This abstract class implements the basic functionality necessary for
* providing an interactive SQL client.
*/
public abstract class InteractiveClient {
private static Logger logger = LogManager.getLogger(InteractiveClient.class);
/** A string constant specifying the "first-line" command-prompt. */
private static final String CMDPROMPT_FIRST = "CMD> ";
/** A string constant specifying the "subsequent-lines" command-prompt. */
private static final String CMDPROMPT_NEXT = " > ";
/** The buffer that accumulates each command's text. */
private StringBuilder enteredText;
public abstract void startup() throws Exception;
public void mainloop() {
// We don't use the console directly, since we can't read/write it
// if someone redirects a file onto the client's input-stream.
boolean hasConsole = (System.console() != null);
if (hasConsole) {
System.out.println(
"Welcome to NanoDB. Exit with EXIT or QUIT command.\n");
}
boolean exiting = false;
BufferedReader bufReader = new BufferedReader(new InputStreamReader(System.in));
while (!exiting) {
enteredText = new StringBuilder();
boolean firstLine = true;
getcmd:
while (true) {
try {
if (hasConsole) {
if (firstLine) {
System.out.print(CMDPROMPT_FIRST);
System.out.flush();
firstLine = false;
}
else {
System.out.print(CMDPROMPT_NEXT);
System.out.flush();
}
}
String line = bufReader.readLine();
if (line == null) {
// Hit EOF.
exiting = true;
break;
}
enteredText.append(line).append('\n');
// Process any commands in the entered text.
while (true) {
String command = getCommandString();
if (command == null)
break; // No more complete commands.
// if (logger.isDebugEnabled())
// logger.debug("Command string:\n" + command);
CommandResult result = handleCommand(command);
if (result.isExit()) {
exiting = true;
break getcmd;
}
}
if (enteredText.length() == 0)
firstLine = true;
}
catch (Throwable e) {
System.out.println("Unexpected error: " + e.getClass() +
": " + e.getMessage());
logger.error("Unexpected error", e);
}
}
}
}
/**
* This helper method goes through the {@link #enteredText} buffer, trying
* to identify the extent of the next command string. This is done using
* semicolons (that are not enclosed with single or double quotes). If a
* command is identified, it is removed from the internal buffer and
* returned. If no complete command is identified, {@code null} is
* returned.
*
* @return the first semicolon-terminated command in the internal data
* buffer, or {@code null} if the buffer contains no complete
* commands.
*/
private String getCommandString() {
int i = 0;
String command = null;
while (i < enteredText.length()) {
char ch = enteredText.charAt(i);
if (ch == ';') {
// Found the end of the command. Extract the string, and
// make sure the semicolon is also included.
command = enteredText.substring(0, i + 1);
enteredText.delete(0, i + 1);
// Consume any leading whitespace at the start of the entered
// text.
while (enteredText.length() > 0 &&
Character.isWhitespace(enteredText.charAt(0))) {
enteredText.deleteCharAt(0);
}
break;
}
else if (ch == '\'' || ch == '"') {
// Need to ignore all subsequent characters until we find
// the end of this quoted string.
i++;
while (i < enteredText.length() &&
enteredText.charAt(i) != ch) {
i++;
}
}
i++; // Go on to the next character.
}
return command;
}
/**
* Subclasses can implement this method to handle each command entered
* by the user. For example, a subclass may send the command over a
* socket to the server, wait for a response, then output the response
* to the console.
*
* @param command the command to handle.
*
* @return the command-result from executing the command
*/
public abstract CommandResult handleCommand(String command);
/**
* Shut down the interactive client. The specific way the client
* interacts with the server dictates how this shutdown mechanism
* will work.
*
* @throws Exception if any error occurs during shutdown
*/
public abstract void shutdown() throws Exception;
}