KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > cli > avalon > CLArgsParser


1 /*
2  * Copyright 2002-2005 The Apache Software Foundation
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12  * implied.
13  *
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.commons.cli.avalon;
18 //Renamed from org.apache.avalon.excalibur.cli
19

20 import java.text.ParseException JavaDoc;
21 import java.util.Hashtable JavaDoc;
22 import java.util.Vector JavaDoc;
23
24 /**
25  * Parser for command line arguments.
26  *
27  * This parses command lines according to the standard (?) of
28  * GNU utilities.
29  *
30  * Note: This is still used in 1.1 libraries so do not add 1.2+ dependencies.
31  *
32  * Note that CLArgs uses a backing hashtable for the options index and so duplicate
33  * arguments are only returned by getArguments().
34  *
35  * @version $Revision: 1.1.2.7 $ $Date: 2005/03/02 00:37:50 $
36  * @see ParserControl
37  * @see CLOption
38  * @see CLOptionDescriptor
39  */

40 public final class CLArgsParser
41 {
42     //cached character == Integer.MAX_VALUE when invalid
43
private static final int INVALID = Integer.MAX_VALUE;
44
45     private static final int STATE_NORMAL = 0;
46     private static final int STATE_REQUIRE_2ARGS = 1;
47     private static final int STATE_REQUIRE_ARG = 2;
48     private static final int STATE_OPTIONAL_ARG = 3;
49     private static final int STATE_NO_OPTIONS = 4;
50     private static final int STATE_OPTION_MODE = 5;
51
52     // Values for creating tokens
53
private static final int TOKEN_SEPARATOR = 0;
54     private static final int TOKEN_STRING = 1;
55
56     private static final char[] ARG_SEPARATORS =
57             new char[]{(char)0, '='};
58
59     private static final char[] NULL_SEPARATORS =
60             new char[]{(char)0};
61
62     private final CLOptionDescriptor[] m_optionDescriptors;
63     private final Vector JavaDoc m_options;
64     private Hashtable JavaDoc m_optionIndex;
65     private final ParserControl m_control;
66
67     private String JavaDoc m_errorMessage;
68     private String JavaDoc[] m_unparsedArgs = new String JavaDoc[]{};
69
70     //variables used while parsing options.
71
private char m_ch;
72     private String JavaDoc[] m_args;
73     private boolean m_isLong;
74     private int m_argIndex;
75     private int m_stringIndex;
76     private int m_stringLength;
77
78     private int m_lastChar = INVALID;
79
80     private int m_lastOptionId;
81     private CLOption m_option;
82     private int m_state = STATE_NORMAL;
83
84     /**
85      * Retrieve an array of arguments that have not been parsed
86      * due to the parser halting.
87      *
88      * @return an array of unparsed args
89      */

90     public final String JavaDoc[] getUnparsedArgs()
91     {
92         return m_unparsedArgs;
93     }
94
95     /**
96      * Retrieve a list of options that were parsed from command list.
97      *
98      * @return the list of options
99      */

100     public final Vector JavaDoc getArguments()
101     {
102         //System.out.println( "Arguments: " + m_options );
103
return m_options;
104     }
105
106     /**
107      * Retrieve the {@link CLOption} with specified id, or
108      * <code>null</code> if no command line option is found.
109      *
110      * @param id the command line option id
111      * @return the {@link CLOption} with the specified id, or
112      * <code>null</code> if no CLOption is found.
113      * @see CLOption
114      */

115     public final CLOption getArgumentById( final int id )
116     {
117         return (CLOption)m_optionIndex.get( new Integer JavaDoc( id ) );
118     }
119
120     /**
121      * Retrieve the {@link CLOption} with specified name, or
122      * <code>null</code> if no command line option is found.
123      *
124      * @param name the command line option name
125      * @return the {@link CLOption} with the specified name, or
126      * <code>null</code> if no CLOption is found.
127      * @see CLOption
128      */

129     public final CLOption getArgumentByName( final String JavaDoc name )
130     {
131         return (CLOption)m_optionIndex.get( name );
132     }
133
134     /**
135      * Get Descriptor for option id.
136      *
137      * @param id the id
138      * @return the descriptor
139      */

140     private final CLOptionDescriptor getDescriptorFor( final int id )
141     {
142         for( int i = 0; i < m_optionDescriptors.length; i++ )
143         {
144             if( m_optionDescriptors[i].getId() == id )
145             {
146                 return m_optionDescriptors[i];
147             }
148         }
149
150         return null;
151     }
152
153     /**
154      * Retrieve a descriptor by name.
155      *
156      * @param name the name
157      * @return the descriptor
158      */

159     private final CLOptionDescriptor getDescriptorFor( final String JavaDoc name )
160     {
161         for( int i = 0; i < m_optionDescriptors.length; i++ )
162         {
163             if( m_optionDescriptors[i].getName().equals( name ) )
164             {
165                 return m_optionDescriptors[i];
166             }
167         }
168
169         return null;
170     }
171
172     /**
173      * Retrieve an error message that occured during parsing if one existed.
174      *
175      * @return the error string
176      */

177     public final String JavaDoc getErrorString()
178     {
179         //System.out.println( "ErrorString: " + m_errorMessage );
180
return m_errorMessage;
181     }
182
183     /**
184      * Require state to be placed in for option.
185      *
186      * @param descriptor the Option Descriptor
187      * @return the state
188      */

189     private final int getStateFor( final CLOptionDescriptor descriptor )
190     {
191         final int flags = descriptor.getFlags();
192         if( (flags & CLOptionDescriptor.ARGUMENTS_REQUIRED_2) ==
193                 CLOptionDescriptor.ARGUMENTS_REQUIRED_2 )
194         {
195             return STATE_REQUIRE_2ARGS;
196         }
197         else if( (flags & CLOptionDescriptor.ARGUMENT_REQUIRED) ==
198                 CLOptionDescriptor.ARGUMENT_REQUIRED )
199         {
200             return STATE_REQUIRE_ARG;
201         }
202         else if( (flags & CLOptionDescriptor.ARGUMENT_OPTIONAL) ==
203                 CLOptionDescriptor.ARGUMENT_OPTIONAL )
204         {
205             return STATE_OPTIONAL_ARG;
206         }
207         else
208         {
209             return STATE_NORMAL;
210         }
211     }
212
213     /**
214      * Create a parser that can deal with options and parses certain args.
215      *
216      * @param args the args, typically that passed to the
217      * <code>public static void main(String[] args)</code> method.
218      * @param optionDescriptors the option descriptors
219      * @param control the parser control used determine behaviour of parser
220      */

221     public CLArgsParser( final String JavaDoc[] args,
222             final CLOptionDescriptor[] optionDescriptors,
223             final ParserControl control )
224     {
225         m_optionDescriptors = optionDescriptors;
226         m_control = control;
227         m_options = new Vector JavaDoc();
228         m_args = args;
229
230         try
231         {
232             parse();
233             checkIncompatibilities( m_options );
234             buildOptionIndex();
235         }
236         catch( final ParseException JavaDoc pe )
237         {
238             m_errorMessage = pe.getMessage();
239         }
240
241         //System.out.println( "Built : " + m_options );
242
//System.out.println( "From : " + Arrays.asList( args ) );
243
}
244
245     /**
246      * Check for duplicates of an option.
247      * It is an error to have duplicates unless appropriate flags is set in descriptor.
248      *
249      * @param arguments the arguments
250      */

251     private final void checkIncompatibilities( final Vector JavaDoc arguments )
252             throws ParseException JavaDoc
253     {
254         final int size = arguments.size();
255
256         for( int i = 0; i < size; i++ )
257         {
258             final CLOption option = (CLOption)arguments.elementAt( i );
259             final int id = option.getDescriptor().getId();
260             final CLOptionDescriptor descriptor = getDescriptorFor( id );
261
262             //this occurs when id == 0 and user has not supplied a descriptor
263
//for arguments
264
if( null == descriptor )
265             {
266                 continue;
267             }
268
269             final int[] incompatible = descriptor.getIncompatible();
270
271             checkIncompatible( arguments, incompatible, i );
272         }
273     }
274
275     private final void checkIncompatible( final Vector JavaDoc arguments,
276             final int[] incompatible,
277             final int original )
278             throws ParseException JavaDoc
279     {
280         final int size = arguments.size();
281
282         for( int i = 0; i < size; i++ )
283         {
284             if( original == i )
285             {
286                 continue;
287             }
288
289             final CLOption option = (CLOption)arguments.elementAt( i );
290             final int id = option.getDescriptor().getId();
291
292             for( int j = 0; j < incompatible.length; j++ )
293             {
294                 if( id == incompatible[j] )
295                 {
296                     final CLOption originalOption = (CLOption)arguments.elementAt( original );
297                     final int originalId = originalOption.getDescriptor().getId();
298
299                     String JavaDoc message = null;
300
301                     if( id == originalId )
302                     {
303                         message =
304                                 "Duplicate options for " + describeDualOption( originalId ) +
305                                 " found.";
306                     }
307                     else
308                     {
309                         message = "Incompatible options -" +
310                                 describeDualOption( id ) + " and " +
311                                 describeDualOption( originalId ) + " found.";
312                     }
313                     throw new ParseException JavaDoc( message, 0 );
314                 }
315             }
316         }
317     }
318
319     private final String JavaDoc describeDualOption( final int id )
320     {
321         final CLOptionDescriptor descriptor = getDescriptorFor( id );
322         if( null == descriptor )
323         {
324             return "<parameter>";
325         }
326         else
327         {
328             final StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
329             boolean hasCharOption = false;
330
331             if( Character.isLetter( (char)id ) )
332             {
333                 sb.append( '-' );
334                 sb.append( (char)id );
335                 hasCharOption = true;
336             }
337
338             final String JavaDoc longOption = descriptor.getName();
339             if( null != longOption )
340             {
341                 if( hasCharOption )
342                 {
343                     sb.append( '/' );
344                 }
345                 sb.append( "--" );
346                 sb.append( longOption );
347             }
348
349             return sb.toString();
350         }
351     }
352
353     /**
354      * Create a parser that deals with options and parses certain args.
355      *
356      * @param args the args
357      * @param optionDescriptors the option descriptors
358      */

359     public CLArgsParser( final String JavaDoc[] args,
360             final CLOptionDescriptor[] optionDescriptors )
361     {
362         this( args, optionDescriptors, null );
363     }
364
365     /**
366      * Create a string array that is subset of input array.
367      * The sub-array should start at array entry indicated by index. That array element
368      * should only include characters from charIndex onwards.
369      *
370      * @param array the original array
371      * @param index the cut-point in array
372      * @param charIndex the cut-point in element of array
373      * @return the result array
374      */

375     private final String JavaDoc[] subArray( final String JavaDoc[] array,
376             final int index,
377             final int charIndex )
378     {
379         final int remaining = array.length - index;
380         final String JavaDoc[] result = new String JavaDoc[remaining];
381
382         if( remaining > 1 )
383         {
384             System.arraycopy( array, index + 1, result, 1, remaining - 1 );
385         }
386
387         result[0] = array[index].substring( charIndex - 1 );
388
389         return result;
390     }
391
392     /**
393      * Actually parse arguments
394      */

395     private final void parse()
396             throws ParseException JavaDoc
397     {
398         if( 0 == m_args.length )
399         {
400             return;
401         }
402
403         m_stringLength = m_args[m_argIndex].length();
404
405         //ch = peekAtChar();
406

407         while( true )
408         {
409             m_ch = peekAtChar();
410
411             //System.out.println( "Pre State=" + m_state );
412
//System.out.println( "Pre Char=" + (char)ch + "/" + (int)ch );
413

414             if( m_argIndex >= m_args.length )
415             {
416                 break;
417             }
418
419             if( null != m_control && m_control.isFinished( m_lastOptionId ) )
420             {
421                 //this may need mangling due to peeks
422
m_unparsedArgs = subArray( m_args, m_argIndex, m_stringIndex );
423                 return;
424             }
425
426             //System.out.println( "State=" + m_state );
427
//System.out.println( "Char=" + (char)ch + "/" + (int)ch );
428

429             if( STATE_OPTION_MODE == m_state )
430             {
431                 //if get to an arg barrier then return to normal mode
432
//else continue accumulating options
433
if( 0 == m_ch )
434                 {
435                     getChar(); //strip the null
436
m_state = STATE_NORMAL;
437                 }
438                 else
439                 {
440                     parseShortOption();
441                 }
442             }
443             else if( STATE_NORMAL == m_state )
444             {
445                 parseNormal();
446             }
447             else if( STATE_NO_OPTIONS == m_state )
448             {
449                 //should never get to here when stringIndex != 0
450
addOption( new CLOption( m_args[m_argIndex++] ) );
451             }
452             else if( STATE_OPTIONAL_ARG == m_state && m_isLong && m_ch != 0)
453             {
454                 m_state = STATE_NORMAL;
455                 addOption( m_option );
456             }
457             else
458             {
459                 parseArguments();
460             }
461         }
462
463         if( m_option != null )
464         {
465             if( STATE_OPTIONAL_ARG == m_state )
466             {
467                 m_options.addElement( m_option );
468             }
469             else if( STATE_REQUIRE_ARG == m_state )
470             {
471                 final CLOptionDescriptor descriptor = getDescriptorFor(
472                         m_option.getDescriptor().getId() );
473                 final String JavaDoc message =
474                         "Missing argument to option " + getOptionDescription( descriptor );
475                 throw new ParseException JavaDoc( message, 0 );
476             }
477             else if( STATE_REQUIRE_2ARGS == m_state )
478             {
479                 if( 1 == m_option.getArgumentCount() )
480                 {
481                     m_option.addArgument( "" );
482                     m_options.addElement( m_option );
483                 }
484                 else
485                 {
486                     final CLOptionDescriptor descriptor = getDescriptorFor(
487                             m_option.getDescriptor().getId() );
488                     final String JavaDoc message =
489                             "Missing argument to option " + getOptionDescription( descriptor );
490                     throw new ParseException JavaDoc( message, 0 );
491                 }
492             }
493             else
494             {
495                 throw new ParseException JavaDoc( "IllegalState " + m_state + ": " + m_option, 0 );
496             }
497         }
498     }
499
500     private final String JavaDoc getOptionDescription( final CLOptionDescriptor descriptor )
501     {
502         if( m_isLong )
503         {
504             return "--" + descriptor.getName();
505         }
506         else
507         {
508             return "-" + (char)descriptor.getId();
509         }
510     }
511
512     private final char peekAtChar()
513     {
514         if( INVALID == m_lastChar )
515         {
516             m_lastChar = readChar();
517         }
518         return (char)m_lastChar;
519     }
520
521     private final char getChar()
522     {
523         if( INVALID != m_lastChar )
524         {
525             final char result = (char)m_lastChar;
526             m_lastChar = INVALID;
527             return result;
528         }
529         else
530         {
531             return readChar();
532         }
533     }
534
535     private final char readChar()
536     {
537         if( m_stringIndex >= m_stringLength )
538         {
539             m_argIndex++;
540             m_stringIndex = 0;
541
542             if( m_argIndex < m_args.length )
543             {
544                 m_stringLength = m_args[m_argIndex].length();
545             }
546             else
547             {
548                 m_stringLength = 0;
549             }
550
551             return 0;
552         }
553
554         if( m_argIndex >= m_args.length )
555         {
556             return 0;
557         }
558
559         return m_args[m_argIndex].charAt( m_stringIndex++ );
560     }
561
562     private final Token nextToken( final char[] separators )
563     {
564         m_ch = getChar();
565
566         if( isSeparator( m_ch, separators ) )
567         {
568             m_ch = getChar();
569             return new Token( TOKEN_SEPARATOR, null );
570         }
571
572         final StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
573
574         do
575         {
576             sb.append( m_ch );
577             m_ch = getChar();
578         }
579         while( !isSeparator( m_ch, separators ) );
580
581         return new Token( TOKEN_STRING, sb.toString() );
582     }
583
584     private final boolean isSeparator( final char ch, final char[] separators )
585     {
586         for( int i = 0; i < separators.length; i++ )
587         {
588             if( ch == separators[i] )
589             {
590                 return true;
591             }
592         }
593
594         return false;
595     }
596
597     private final void addOption( final CLOption option )
598     {
599         m_options.addElement( option );
600         m_lastOptionId = option.getDescriptor().getId();
601         m_option = null;
602     }
603
604     private final void parseOption( final CLOptionDescriptor descriptor,
605             final String JavaDoc optionString )
606             throws ParseException JavaDoc
607     {
608         if( null == descriptor )
609         {
610             throw new ParseException JavaDoc( "Unknown option " + optionString, 0 );
611         }
612
613         m_state = getStateFor( descriptor );
614         m_option = new CLOption( descriptor );
615
616         if( STATE_NORMAL == m_state )
617         {
618             addOption( m_option );
619         }
620     }
621
622     private final void parseShortOption()
623             throws ParseException JavaDoc
624     {
625         m_ch = getChar();
626         final CLOptionDescriptor descriptor = getDescriptorFor( m_ch );
627         m_isLong = false;
628         parseOption( descriptor, "-" + m_ch );
629
630         if( STATE_NORMAL == m_state )
631         {
632             m_state = STATE_OPTION_MODE;
633         }
634     }
635
636     private final void parseArguments()
637             throws ParseException JavaDoc
638     {
639         if( STATE_REQUIRE_ARG == m_state )
640         {
641             if( '=' == m_ch || 0 == m_ch )
642             {
643                 getChar();
644             }
645
646             final Token token = nextToken( NULL_SEPARATORS );
647             m_option.addArgument( token.getValue() );
648
649             addOption( m_option );
650             m_state = STATE_NORMAL;
651         }
652         else if( STATE_OPTIONAL_ARG == m_state )
653         {
654             if( '-' == m_ch || 0 == m_ch )
655             {
656                 getChar(); //consume stray character
657
addOption( m_option );
658                 m_state = STATE_NORMAL;
659                 return;
660             }
661
662             if( '=' == m_ch )
663             {
664                 getChar();
665             }
666
667             final Token token = nextToken( NULL_SEPARATORS );
668             m_option.addArgument( token.getValue() );
669
670             addOption( m_option );
671             m_state = STATE_NORMAL;
672         }
673         else if( STATE_REQUIRE_2ARGS == m_state )
674         {
675             if( 0 == m_option.getArgumentCount() )
676             {
677                 /*
678                  * Fix bug: -D arg1=arg2 was causing parse error; however --define arg1=arg2 is OK
679                  * This seems to be because the parser skips the terminator for the long options,
680                  * but was not doing so for the short options.
681                  */

682                 if (!m_isLong){
683                     if (0 == peekAtChar()){
684                         getChar();
685                     }
686                 }
687                 final Token token = nextToken( ARG_SEPARATORS );
688
689                 if( TOKEN_SEPARATOR == token.getType() )
690                 {
691                     final CLOptionDescriptor descriptor = getDescriptorFor(
692                             m_option.getDescriptor().getId() );
693                     final String JavaDoc message =
694                             "Unable to parse first argument for option " +
695                             getOptionDescription( descriptor );
696                     throw new ParseException JavaDoc( message, 0 );
697                 }
698                 else
699                 {
700                     m_option.addArgument( token.getValue() );
701                 }
702                 // Are we about to start a new option?
703
if (0 == m_ch && '-' == peekAtChar()){
704                     // Yes, so the second argument is missing
705
m_option.addArgument( "" );
706                     m_options.addElement( m_option );
707                     m_state = STATE_NORMAL;
708                 }
709             }
710             else //2nd argument
711
{
712                 final StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
713
714                 m_ch = getChar();
715                 while( !isSeparator( m_ch, NULL_SEPARATORS ) )
716                 {
717                     sb.append( m_ch );
718                     m_ch = getChar();
719                 }
720
721                 final String JavaDoc argument = sb.toString();
722
723                 //System.out.println( "Arguement:" + argument );
724

725                 m_option.addArgument( argument );
726                 addOption( m_option );
727                 m_option = null;
728                 m_state = STATE_NORMAL;
729             }
730         }
731     }
732
733     /**
734      * Parse Options from Normal mode.
735      */

736     private final void parseNormal()
737             throws ParseException JavaDoc
738     {
739         if( '-' != m_ch )
740         {
741             //Parse the arguments that are not options
742
final String JavaDoc argument = nextToken( NULL_SEPARATORS ).getValue();
743             addOption( new CLOption( argument ) );
744             m_state = STATE_NORMAL;
745         }
746         else
747         {
748             getChar(); // strip the -
749

750             if( 0 == peekAtChar() )
751             {
752                 throw new ParseException JavaDoc( "Malformed option -", 0 );
753             }
754             else
755             {
756                 m_ch = peekAtChar();
757
758                 //if it is a short option then parse it else ...
759
if( '-' != m_ch )
760                 {
761                     parseShortOption();
762                 }
763                 else
764                 {
765                     getChar(); // strip the -
766
//-- sequence .. it can either mean a change of state
767
//to STATE_NO_OPTIONS or else a long option
768

769                     if( 0 == peekAtChar() )
770                     {
771                         getChar();
772                         m_state = STATE_NO_OPTIONS;
773                     }
774                     else
775                     {
776                         //its a long option
777
final String JavaDoc optionName = nextToken( ARG_SEPARATORS ).getValue();
778                         final CLOptionDescriptor descriptor = getDescriptorFor( optionName );
779                         m_isLong = true;
780                         parseOption( descriptor, "--" + optionName );
781                     }
782                 }
783             }
784         }
785     }
786
787     /**
788      * Build the m_optionIndex lookup map for the parsed options.
789      */

790     private final void buildOptionIndex()
791     {
792         final int size = m_options.size();
793         m_optionIndex = new Hashtable JavaDoc( size * 2 );
794
795         for( int i = 0; i < size; i++ )
796         {
797             final CLOption option = (CLOption)m_options.get( i );
798             final CLOptionDescriptor optionDescriptor =
799                     getDescriptorFor( option.getDescriptor().getId() );
800
801             m_optionIndex.put( new Integer JavaDoc( option.getDescriptor().getId() ), option );
802
803             if( null != optionDescriptor &&
804                     null != optionDescriptor.getName() )
805             {
806                 m_optionIndex.put( optionDescriptor.getName(), option );
807             }
808         }
809     }
810 }
811
Popular Tags