1 package org.lsst.ccs.utilities.sa;
2
3 import java.io.PrintStream;
4 import java.util.LinkedList;
5 import java.util.Scanner;
6
7 /**
8 ***************************************************************************
9 **
10 ** Class to implement command processing
11 **
12 ** @author Owen Saxton
13 **
14 ***************************************************************************
15 */
16 public class CmndProc {
17
18 public final static int
19 CMD_AMBIG = -2,
20 CMD_UNKNOWN = -1;
21
22 private final static PrintStream out = System.out;
23
24 private LinkedList<Dispatch> disps = new LinkedList<Dispatch>();
25 private LinkedList<Command> cmnds = new LinkedList<Command>();
26
27
28 /**
29 ***************************************************************************
30 **
31 ** Inner interface to define command dispatching
32 **
33 ***************************************************************************
34 */
35 public interface Dispatch {
36
37 boolean dispatch(int code, Scanner scan);
38
39 }
40
41
42 /**
43 ***************************************************************************
44 **
45 ** Inner class to implement command decoding
46 **
47 ***************************************************************************
48 */
49 public static class Command {
50
51 private int nCmnd = 0;
52 private int[] cmdCode;
53 private String[] cmdName;
54 private String[][] cmdHelp;
55
56
57 /**
58 ***********************************************************************
59 **
60 ** Constructor
61 **
62 ***********************************************************************
63 */
64 public Command(int count)
65 {
66 cmdCode = new int[count];
67 cmdName = new String[count];
68 cmdHelp = new String[count][];
69 }
70
71
72 /**
73 ***********************************************************************
74 **
75 ** Adds a command to the table
76 **
77 ***********************************************************************
78 */
79 public void add(String command, int code, String[] help)
80 {
81 cmdName[nCmnd] = command;
82 cmdCode[nCmnd] = code;
83 cmdHelp[nCmnd] = help;
84 nCmnd++;
85 }
86
87
88 /**
89 ***********************************************************************
90 **
91 ** Gets the number of commands
92 **
93 ***********************************************************************
94 */
95 public int count()
96 {
97 return nCmnd;
98 }
99
100
101 /**
102 ***********************************************************************
103 **
104 ** Gets the index of a command, given its name
105 **
106 ** This routine compares the command string with the first part of
107 ** all the table entries and returns one of the following:
108 **
109 ** index (>= 0) If the command is fully matched
110 ** CMD_UNKNOWN If the command is not in the table
111 ** CMD_AMBIG If the command matches more than one entry
112 ** CMD_AMBIG - 1 - index If the command is partially matched
113 **
114 ***********************************************************************
115 */
116 public int find(String command)
117 {
118 int index = 0, match = 0;
119
120 for (int j = 0; j < nCmnd; j++) {
121 String name = cmdName[j];
122 if (name != null && name.startsWith(command)) {
123 if (name.equals(command)) return j;
124 index = j;
125 match++;
126 }
127 }
128
129 if (match == 0) return CMD_UNKNOWN;
130 if (match > 1) return CMD_AMBIG;
131
132 return CMD_AMBIG - 1 - index;
133 }
134
135
136 /**
137 ***********************************************************************
138 **
139 ** Disables a command, given its index
140 **
141 ***********************************************************************
142 */
143 public void disable(int index)
144 {
145 if (index >= 0) cmdName[index] = null;
146 }
147
148
149 /**
150 ***********************************************************************
151 **
152 ** Gets the name of a command, given its index
153 **
154 ***********************************************************************
155 */
156 public String name(int index)
157 {
158 return (index >= 0) ? cmdName[index] : null;
159 }
160
161
162 /**
163 ***********************************************************************
164 **
165 ** Gets the code for a command, given its index
166 **
167 ***********************************************************************
168 */
169 public int code(int index)
170 {
171 return (index >= 0) ? cmdCode[index] : index;
172 }
173
174
175 /**
176 ***********************************************************************
177 **
178 ** Gets the help for a command, given its index
179 **
180 ***********************************************************************
181 */
182 public String[] help(int index)
183 {
184 return (index >= 0) ? cmdHelp[index] : null;
185 }
186
187
188 /**
189 ***********************************************************************
190 **
191 ** Shows help for a command, given its index
192 **
193 ***********************************************************************
194 */
195 public void showHelp(int index)
196 {
197 String[] text = cmdHelp[index];
198 if (text == null) return;
199 out.println("Description:");
200 out.println(" " + text[0]);
201 out.println("Usage:");
202 out.println(" " + text[1]);
203 if (text.length > 2)
204 out.println("Parameters:");
205 for (int j = 2; j < text.length; j++)
206 out.println(" " + text[j]);
207
208 }
209
210
211 /**
212 ***********************************************************************
213 **
214 ** Gets the maximum length of matching commands
215 **
216 ***********************************************************************
217 */
218 public int getMaxLeng(String command)
219 {
220 int maxLeng = 0;
221 for (int j = 0; j < nCmnd; j++) {
222 String name = cmdName[j];
223 if (name == null) continue;
224 if (command == null || name.startsWith(command))
225 if (name.length() > maxLeng) maxLeng = name.length();
226 }
227
228 return maxLeng;
229 }
230
231
232 /**
233 ***********************************************************************
234 **
235 ** Shows help summary for matching commands
236 **
237 ***********************************************************************
238 */
239 public void showHelpSumm(String command)
240 {
241 showHelpSumm(command, getMaxLeng(command));
242 }
243
244 public void showHelpSumm(String command, int maxLeng)
245 {
246 StringBuilder blanks = new StringBuilder(maxLeng + 2);
247 for (int j = 0; j < blanks.capacity(); j++) blanks.append(" ");
248 for (int j = 0; j < nCmnd; j++) {
249 String name = cmdName[j];
250 if (name == null) continue;
251 if (command == null || name.startsWith(command)) {
252 String[] text = cmdHelp[j];
253 if (text == null) continue;
254 out.format(" %s%s%s\n", name,
255 blanks.substring(name.length()), text[0]);
256 }
257 }
258 }
259
260 }
261
262
263 /**
264 ***************************************************************************
265 **
266 ** Inner class to implement lookup tables
267 **
268 ***************************************************************************
269 */
270 public static class Lookup {
271
272 private int nItem = 0;
273 private String[] names;
274 private int[] codes;
275
276
277 /**
278 ***********************************************************************
279 **
280 ** Constructor
281 **
282 ***********************************************************************
283 */
284 public Lookup(int count)
285 {
286 codes = new int[count];
287 names = new String[count];
288 }
289
290
291 /**
292 ***********************************************************************
293 **
294 ** Adds a new entry
295 **
296 ***********************************************************************
297 */
298 public void add(String name, int code)
299 {
300 names[nItem] = name;
301 codes[nItem] = code;
302 nItem++;
303 }
304
305
306 /**
307 ***********************************************************************
308 **
309 ** Encodes a name to its equivalent code
310 **
311 ***********************************************************************
312 */
313 public int encode(String name, boolean report)
314 {
315 int index = 0, match = 0;
316 String lcName = name.toLowerCase();
317
318 for (int j = 0; j < names.length; j++) {
319 String tName = names[j].toLowerCase();
320 if (tName.startsWith(lcName)) {
321 index = j;
322 match++;
323 if (tName.equals(lcName)) {
324 match = 1;
325 break;
326 }
327 }
328 }
329 if (match == 1) return codes[index];
330 if (report) out.println((match == 0 ? "Unrecognized" : "Ambiguous")
331 + " value: " + name);
332 return (match == 0) ? -1 : -2;
333 }
334
335
336 /**
337 ***********************************************************************
338 **
339 ** Decodes a code to its equivalent name
340 **
341 ***********************************************************************
342 */
343 public String decode(int code)
344 {
345 for (int j = 0; j < codes.length; j++)
346 if (code == codes[j]) return names[j];
347 return "Invalid";
348 }
349
350
351 /**
352 ***********************************************************************
353 **
354 ** Gets the number of items
355 **
356 ***********************************************************************
357 */
358 public int count()
359 {
360 return nItem;
361 }
362
363
364 /**
365 ***********************************************************************
366 **
367 ** Gets the code given its index
368 **
369 ***********************************************************************
370 */
371 public int code(int index)
372 {
373 return codes[index];
374 }
375
376
377 /**
378 ***********************************************************************
379 **
380 ** Gets the name given its index
381 **
382 ***********************************************************************
383 */
384 public String name(int index)
385 {
386 return names[index];
387 }
388 }
389
390
391 /**
392 ***************************************************************************
393 **
394 ** Inner class to implement common command processing
395 **
396 ***************************************************************************
397 */
398 private final static int
399 CMD_HELPSUMM = 0,
400 CMD_HELP = 1,
401 CMD_EXIT = 2;
402
403 private final static String[] helpHelp = {
404 "Display help",
405 "help [<command>]",
406 "command Optional command for which help is wanted",
407 };
408
409 private final static String[] helpExit = {
410 "Exit the program",
411 "exit",
412 };
413
414 private final static String[] helpQuit = {
415 "Exit the program",
416 "quit",
417 };
418
419 private final static Command cmnd = new Command(4);
420 static {
421 cmnd.add("help", CMD_HELP, helpHelp);
422 cmnd.add("?", CMD_HELPSUMM, null);
423 cmnd.add("exit", CMD_EXIT, helpExit);
424 cmnd.add("quit", CMD_EXIT, helpQuit);
425 }
426
427 private class Common implements Dispatch {
428
429 /**
430 ***********************************************************************
431 **
432 ** Constructor
433 **
434 ***********************************************************************
435 */
436 public Common()
437 {
438 add(this, cmnd);
439 }
440
441
442 /**
443 ***********************************************************************
444 **
445 ** Dispatches common commands
446 **
447 ***********************************************************************
448 */
449 @Override
450 public boolean dispatch(int code, Scanner scan)
451 {
452 int found;
453 boolean cont = true;
454 Object[] args = new Object[1];
455
456 switch (code) {
457
458 case CMD_HELP:
459 if ((found = scanArgs(scan, "s", args)) == -1) break;
460 if (found != 0) {
461 String command = ((String)args[0]).toLowerCase();
462 int index = findCmnd(command);
463 if (index >= 0)
464 cmnds.get(index >> 16).showHelp(index & 0xffff);
465 else if (index == CMD_AMBIG) {
466 out.println("Matching commands are:");
467 showHelpSumm(command);
468 }
469 else
470 out.println("No matching commands");
471 break;
472 }
473
474 case CMD_HELPSUMM:
475 out.println("Valid commands are:");
476 showHelpSumm(null);
477 break;
478
479 case CMD_EXIT:
480 cont = false;
481 break;
482 }
483
484 return cont;
485 }
486
487 }
488
489
490 /**
491 ***************************************************************************
492 **
493 ** Main constructor
494 **
495 ***************************************************************************
496 */
497 public CmndProc()
498 {
499 new Common();
500 }
501
502
503 /**
504 ***************************************************************************
505 **
506 ** Adds a command processor
507 **
508 ***************************************************************************
509 */
510 public void add(Dispatch disp, Command cmnd)
511 {
512 for (int j = 0; j < cmnd.count(); j++) {
513 int code = findCmnd(cmnd.name(j));
514 if (code >= 0) cmnd.disable(j);
515 }
516 disps.add(disp);
517 cmnds.add(cmnd);
518 }
519
520
521 /**
522 ***************************************************************************
523 **
524 ** Processes a command line
525 **
526 ***************************************************************************
527 */
528 public boolean process(String line)
529 {
530 Scanner scan = new Scanner(line);
531 if (!scan.hasNext()) return true;
532 String command = scan.next().toLowerCase();
533 int index = findCmnd(command);
534 if (index < 0) {
535 out.println((index == CMD_UNKNOWN ? "Unknown" : "Ambiguous")
536 + " command (" + command + ")");
537 return true;
538 }
539 int code = cmnds.get(index >> 16).code(index & 0xffff);
540 return disps.get(index >> 16).dispatch(code, scan);
541 }
542
543
544 /**
545 ***************************************************************************
546 **
547 ** Scans the arguments in a command line
548 **
549 ***************************************************************************
550 */
551 private final static String[] typeDesc =
552 {"string", "byte", "short", "integer", "keywdint", "keyword", "float",
553 "double"};
554
555 public static int scanArgs(Scanner scan, String types, Object[] args,
556 Lookup ... lookup)
557 {
558 int found = 0, kInc = 0;
559
560 for (int j = 0, k = 0; j < types.length(); j++, k += kInc) {
561 String type = types.substring(j, j + 1);
562 String upType = type.toUpperCase();
563 boolean required = type.equals(upType);
564 int iType = "SBWILKFD".indexOf(upType);
565
566 if (iType < 0) {
567 out.println("Invalid argument type specified (" + type + ")");
568 return -1;
569 }
570
571 if (!scan.hasNext()) {
572 if (required) {
573 out.println("Missing argument (" + (j + 1) + ")");
574 return -1;
575 }
576 continue;
577 }
578
579 String arg = scan.next();
580 kInc = (iType == 4 || iType == 5) ? 1 : 0;
581 if (!required && arg.equals("*")) continue;
582
583 try {
584 if (iType == 0)
585 args[j] = arg;
586 else if (iType < 5) {
587 long value = Long.decode(arg);
588 long sMask = -1L << ((iType == 4) ? 32 : 1 << (iType + 2));
589 long sBits = value & sMask;
590 if ((value >= 0 && sBits != 0) ||
591 (value < 0 && (sBits != sMask || value == sMask)))
592 throw new NumberFormatException();
593 if (iType == 1)
594 args[j] = (byte)value;
595 else if (iType == 2)
596 args[j] = (short)value;
597 else
598 args[j] = (int)value;
599 }
600 else if (iType == 5) {
601 int value = lookup[k].encode(arg, false);
602 if (value < 0) throw new NumberFormatException();
603 args[j] = value;
604 }
605 else if (iType == 6)
606 args[j] = Float.valueOf(arg);
607 else
608 args[j] = Double.valueOf(arg);
609 }
610 catch (NumberFormatException e) {
611 boolean okay = false;
612 if (iType == 4) {
613 int value = lookup[k].encode(arg, false);
614 if (value >= 0) {
615 args[j] = value;
616 okay = true;
617 }
618 }
619 if (!okay) {
620 out.println("Invalid " + typeDesc[iType] + " argument ("
621 + arg + ")");
622 return -1;
623 }
624 }
625 found |= (1 << j);
626 }
627
628 if (scan.hasNext()) {
629 out.println("Too many arguments");
630 return -1;
631 }
632
633 return found;
634 }
635
636
637 /**
638 ***************************************************************************
639 **
640 ** Finds a command in the set of command definitions
641 **
642 ***************************************************************************
643 */
644 private int findCmnd(String command)
645 {
646 int index = CMD_UNKNOWN, cIndx = 0;
647
648 for (int j = 0; j < cmnds.size(); j++) {
649 int tIndex = cmnds.get(j).find(command);
650 if (tIndex >= 0) {
651 index = tIndex;
652 cIndx = j;
653 break;
654 }
655 if (tIndex == CMD_AMBIG) index = CMD_AMBIG;
656 if (tIndex < CMD_AMBIG) {
657 if (index == CMD_UNKNOWN) {
658 index = tIndex;
659 cIndx = j;
660 }
661 else index = CMD_AMBIG;
662 }
663 }
664
665 if (index < CMD_AMBIG) index = CMD_AMBIG - 1 - index;
666 if (index >= 0) index |= (cIndx << 16);
667
668 return index;
669 }
670
671
672 /**
673 ***************************************************************************
674 **
675 ** Displays help summary for matching commands
676 **
677 ***************************************************************************
678 */
679 private void showHelpSumm(String command)
680 {
681 int maxLeng = 0;
682 for (int j = 0; j < cmnds.size(); j++) {
683 int leng = cmnds.get(j).getMaxLeng(command);
684 if (leng > maxLeng) maxLeng = leng;
685 }
686 for (int j = 0; j < cmnds.size(); j++)
687 cmnds.get(j).showHelpSumm(command, maxLeng);
688 }
689
690 }