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
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 }