1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.hbtop.terminal.impl;
19
20 import com.google.common.util.concurrent.ThreadFactoryBuilder;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.io.Reader;
25 import java.nio.charset.StandardCharsets;
26 import java.util.Queue;
27 import java.util.concurrent.BlockingQueue;
28 import java.util.concurrent.ExecutorService;
29 import java.util.concurrent.Executors;
30 import java.util.concurrent.LinkedBlockingQueue;
31 import java.util.concurrent.TimeUnit;
32 import java.util.concurrent.atomic.AtomicBoolean;
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.apache.hadoop.hbase.classification.InterfaceAudience;
36 import org.apache.hadoop.hbase.hbtop.terminal.KeyPress;
37 import org.apache.hadoop.hbase.util.Threads;
38
39
40
41
42
43 @InterfaceAudience.Private
44 public class KeyPressGenerator {
45
46 private static final Log LOG = LogFactory.getLog(KeyPressGenerator.class);
47
48 private enum ParseState {
49 START, ESCAPE, ESCAPE_SEQUENCE_PARAM1, ESCAPE_SEQUENCE_PARAM2
50 }
51
52 private final Queue<KeyPress> keyPressQueue;
53 private final BlockingQueue<Character> inputCharacterQueue = new LinkedBlockingQueue<>();
54 private final Reader input;
55 private final InputStream inputStream;
56 private final AtomicBoolean stopThreads = new AtomicBoolean();
57 private final ExecutorService executorService;
58
59 private ParseState parseState;
60 private int param1;
61 private int param2;
62
63 public KeyPressGenerator(InputStream inputStream, Queue<KeyPress> keyPressQueue) {
64 this.inputStream = inputStream;
65 input = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
66 this.keyPressQueue = keyPressQueue;
67
68 executorService = Executors.newFixedThreadPool(2, new ThreadFactoryBuilder()
69 .setNameFormat("KeyPressGenerator-%d").setDaemon(true)
70 .setUncaughtExceptionHandler(Threads.LOGGING_EXCEPTION_HANDLER).build());
71
72 initState();
73 }
74
75 public void start() {
76 executorService.execute(new Runnable() {
77 @Override
78 public void run() {
79 readerThread();
80 }
81 });
82 executorService.execute(new Runnable() {
83 @Override
84 public void run() {
85 generatorThread();
86 }
87 });
88 }
89
90 private void initState() {
91 parseState = ParseState.START;
92 param1 = 0;
93 param2 = 0;
94 }
95
96 private void readerThread() {
97 boolean done = false;
98 char[] readBuffer = new char[128];
99
100 while (!done && !stopThreads.get()) {
101 try {
102 int n = inputStream.available();
103 if (n > 0) {
104 if (readBuffer.length < n) {
105 readBuffer = new char[readBuffer.length * 2];
106 }
107
108 int rc = input.read(readBuffer, 0, readBuffer.length);
109 if (rc == -1) {
110
111 done = true;
112 } else {
113 for (int i = 0; i < rc; i++) {
114 int ch = readBuffer[i];
115 inputCharacterQueue.offer((char) ch);
116 }
117 }
118 } else {
119 Thread.sleep(20);
120 }
121 } catch (InterruptedException ignored) {
122 } catch (IOException e) {
123 LOG.error("Caught an exception", e);
124 done = true;
125 }
126 }
127 }
128
129 private void generatorThread() {
130 while (!stopThreads.get()) {
131 Character ch;
132 try {
133 ch = inputCharacterQueue.poll(100, TimeUnit.MILLISECONDS);
134 } catch (InterruptedException ignored) {
135 continue;
136 }
137
138 if (ch == null) {
139 if (parseState == ParseState.ESCAPE) {
140 offer(new KeyPress(KeyPress.Type.Escape, null, false, false, false));
141 initState();
142 } else if (parseState != ParseState.START) {
143 offer(new KeyPress(KeyPress.Type.Unknown, null, false, false, false));
144 initState();
145 }
146 continue;
147 }
148
149 if (parseState == ParseState.START) {
150 if (ch == 0x1B) {
151 parseState = ParseState.ESCAPE;
152 continue;
153 }
154
155 switch (ch) {
156 case '\n':
157 case '\r':
158 offer(new KeyPress(KeyPress.Type.Enter, '\n', false, false, false));
159 continue;
160
161 case 0x08:
162 case 0x7F:
163 offer(new KeyPress(KeyPress.Type.Backspace, '\b', false, false, false));
164 continue;
165
166 case '\t':
167 offer(new KeyPress(KeyPress.Type.Tab, '\t', false, false, false));
168 continue;
169
170 default:
171
172 break;
173 }
174
175 if (ch < 32) {
176 ctrlAndCharacter(ch);
177 continue;
178 }
179
180 if (isPrintableChar(ch)) {
181
182 offer(new KeyPress(KeyPress.Type.Character, ch, false, false, false));
183 continue;
184 }
185
186 offer(new KeyPress(KeyPress.Type.Unknown, null, false, false, false));
187 continue;
188 }
189
190 if (parseState == ParseState.ESCAPE) {
191 if (ch == 0x1B) {
192 offer(new KeyPress(KeyPress.Type.Escape, null, false, false, false));
193 continue;
194 }
195
196 if (ch < 32 && ch != 0x08) {
197 ctrlAltAndCharacter(ch);
198 initState();
199 continue;
200 } else if (ch == 0x7F || ch == 0x08) {
201 offer(new KeyPress(KeyPress.Type.Backspace, '\b', false, false, false));
202 initState();
203 continue;
204 }
205
206 if (ch == '[' || ch == 'O') {
207 parseState = ParseState.ESCAPE_SEQUENCE_PARAM1;
208 continue;
209 }
210
211 if (isPrintableChar(ch)) {
212
213 offer(new KeyPress(KeyPress.Type.Character, ch, true, false, false));
214 initState();
215 continue;
216 }
217
218 offer(new KeyPress(KeyPress.Type.Escape, null, false, false, false));
219 offer(new KeyPress(KeyPress.Type.Unknown, null, false, false, false));
220 initState();
221 continue;
222 }
223
224 escapeSequenceCharacter(ch);
225 }
226 }
227
228 private void ctrlAndCharacter(char ch) {
229 char ctrlCode;
230 switch (ch) {
231 case 0:
232 ctrlCode = ' ';
233 break;
234
235 case 28:
236 ctrlCode = '\\';
237 break;
238
239 case 29:
240 ctrlCode = ']';
241 break;
242
243 case 30:
244 ctrlCode = '^';
245 break;
246
247 case 31:
248 ctrlCode = '_';
249 break;
250
251 default:
252 ctrlCode = (char) ('a' - 1 + ch);
253 break;
254 }
255 offer(new KeyPress(KeyPress.Type.Character, ctrlCode, false, true, false));
256 }
257
258 private boolean isPrintableChar(char ch) {
259 if (Character.isISOControl(ch)) {
260 return false;
261 }
262 Character.UnicodeBlock block = Character.UnicodeBlock.of(ch);
263 return block != null && !block.equals(Character.UnicodeBlock.SPECIALS);
264 }
265
266 private void ctrlAltAndCharacter(char ch) {
267 char ctrlCode;
268 switch (ch) {
269 case 0:
270 ctrlCode = ' ';
271 break;
272
273 case 28:
274 ctrlCode = '\\';
275 break;
276
277 case 29:
278 ctrlCode = ']';
279 break;
280
281 case 30:
282 ctrlCode = '^';
283 break;
284
285 case 31:
286 ctrlCode = '_';
287 break;
288
289 default:
290 ctrlCode = (char) ('a' - 1 + ch);
291 break;
292 }
293 offer(new KeyPress(KeyPress.Type.Character, ctrlCode, true, true, false));
294 }
295
296 private void escapeSequenceCharacter(char ch) {
297 switch (parseState) {
298 case ESCAPE_SEQUENCE_PARAM1:
299 if (ch == ';') {
300 parseState = ParseState.ESCAPE_SEQUENCE_PARAM2;
301 } else if (Character.isDigit(ch)) {
302 param1 = param1 * 10 + Character.digit(ch, 10);
303 } else {
304 doneEscapeSequenceCharacter(ch);
305 }
306 break;
307
308 case ESCAPE_SEQUENCE_PARAM2:
309 if (Character.isDigit(ch)) {
310 param2 = param2 * 10 + Character.digit(ch, 10);
311 } else {
312 doneEscapeSequenceCharacter(ch);
313 }
314 break;
315
316 default:
317 throw new AssertionError();
318 }
319 }
320
321 private void doneEscapeSequenceCharacter(char last) {
322 boolean alt = false;
323 boolean ctrl = false;
324 boolean shift = false;
325 if (param2 != 0) {
326 alt = isAlt(param2);
327 ctrl = isCtrl(param2);
328 shift = isShift(param2);
329 }
330
331 if (last != '~') {
332 switch (last) {
333 case 'A':
334 offer(new KeyPress(KeyPress.Type.ArrowUp, null, alt, ctrl, shift));
335 break;
336
337 case 'B':
338 offer(new KeyPress(KeyPress.Type.ArrowDown, null, alt, ctrl, shift));
339 break;
340
341 case 'C':
342 offer(new KeyPress(KeyPress.Type.ArrowRight, null, alt, ctrl, shift));
343 break;
344
345 case 'D':
346 offer(new KeyPress(KeyPress.Type.ArrowLeft, null, alt, ctrl, shift));
347 break;
348
349 case 'H':
350 offer(new KeyPress(KeyPress.Type.Home, null, alt, ctrl, shift));
351 break;
352
353 case 'F':
354 offer(new KeyPress(KeyPress.Type.End, null, alt, ctrl, shift));
355 break;
356
357 case 'P':
358 offer(new KeyPress(KeyPress.Type.F1, null, alt, ctrl, shift));
359 break;
360
361 case 'Q':
362 offer(new KeyPress(KeyPress.Type.F2, null, alt, ctrl, shift));
363 break;
364
365 case 'R':
366 offer(new KeyPress(KeyPress.Type.F3, null, alt, ctrl, shift));
367 break;
368
369 case 'S':
370 offer(new KeyPress(KeyPress.Type.F4, null, alt, ctrl, shift));
371 break;
372
373 case 'Z':
374 offer(new KeyPress(KeyPress.Type.ReverseTab, null, alt, ctrl, shift));
375 break;
376
377 default:
378 offer(new KeyPress(KeyPress.Type.Unknown, null, alt, ctrl, shift));
379 break;
380 }
381 initState();
382 return;
383 }
384
385 switch (param1) {
386 case 1:
387 offer(new KeyPress(KeyPress.Type.Home, null, alt, ctrl, shift));
388 break;
389
390 case 2:
391 offer(new KeyPress(KeyPress.Type.Insert, null, alt, ctrl, shift));
392 break;
393
394 case 3:
395 offer(new KeyPress(KeyPress.Type.Delete, null, alt, ctrl, shift));
396 break;
397
398 case 4:
399 offer(new KeyPress(KeyPress.Type.End, null, alt, ctrl, shift));
400 break;
401
402 case 5:
403 offer(new KeyPress(KeyPress.Type.PageUp, null, alt, ctrl, shift));
404 break;
405
406 case 6:
407 offer(new KeyPress(KeyPress.Type.PageDown, null, alt, ctrl, shift));
408 break;
409
410 case 11:
411 offer(new KeyPress(KeyPress.Type.F1, null, alt, ctrl, shift));
412 break;
413
414 case 12:
415 offer(new KeyPress(KeyPress.Type.F2, null, alt, ctrl, shift));
416 break;
417
418 case 13:
419 offer(new KeyPress(KeyPress.Type.F3, null, alt, ctrl, shift));
420 break;
421
422 case 14:
423 offer(new KeyPress(KeyPress.Type.F4, null, alt, ctrl, shift));
424 break;
425
426 case 15:
427 offer(new KeyPress(KeyPress.Type.F5, null, alt, ctrl, shift));
428 break;
429
430 case 17:
431 offer(new KeyPress(KeyPress.Type.F6, null, alt, ctrl, shift));
432 break;
433
434 case 18:
435 offer(new KeyPress(KeyPress.Type.F7, null, alt, ctrl, shift));
436 break;
437
438 case 19:
439 offer(new KeyPress(KeyPress.Type.F8, null, alt, ctrl, shift));
440 break;
441
442 case 20:
443 offer(new KeyPress(KeyPress.Type.F9, null, alt, ctrl, shift));
444 break;
445
446 case 21:
447 offer(new KeyPress(KeyPress.Type.F10, null, alt, ctrl, shift));
448 break;
449
450 case 23:
451 offer(new KeyPress(KeyPress.Type.F11, null, alt, ctrl, shift));
452 break;
453
454 case 24:
455 offer(new KeyPress(KeyPress.Type.F12, null, alt, ctrl, shift));
456 break;
457
458 default:
459 offer(new KeyPress(KeyPress.Type.Unknown, null, false, false, false));
460 break;
461 }
462
463 initState();
464 }
465
466 private boolean isShift(int param) {
467 return (param & 1) != 0;
468 }
469
470 private boolean isAlt(int param) {
471 return (param & 2) != 0;
472 }
473
474 private boolean isCtrl(int param) {
475 return (param & 4) != 0;
476 }
477
478 private void offer(KeyPress keyPress) {
479
480 if (keyPress.isCtrl() && keyPress.getType() == KeyPress.Type.Character &&
481 keyPress.getCharacter() == 'c') {
482 System.exit(0);
483 }
484
485 keyPressQueue.offer(keyPress);
486 }
487
488 public void stop() {
489 stopThreads.set(true);
490
491 executorService.shutdown();
492 try {
493 while (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
494 LOG.warn("Waiting for thread-pool to terminate");
495 }
496 } catch (InterruptedException e) {
497 LOG.warn("Interrupted while waiting for thread-pool termination", e);
498 }
499 }
500 }