View Javadoc

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 }