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