001/*
002 * Copyright 2003-2005 The Apache Software Foundation
003 * Copyright 2005 Stephen McConnell
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package net.dpml.cli.commandline;
018
019import java.io.IOException;
020
021import java.util.LinkedList;
022import java.util.List;
023import java.util.ListIterator;
024
025import net.dpml.cli.CommandLine;
026import net.dpml.cli.Group;
027import net.dpml.cli.Option;
028import net.dpml.cli.OptionException;
029import net.dpml.cli.WriteableCommandLine;
030import net.dpml.cli.resource.ResourceConstants;
031import net.dpml.cli.util.HelpFormatter;
032
033/**
034 * A class that implements the <code>Parser</code> interface can parse a
035 * String array according to the {@link Group}specified and return a
036 * {@link CommandLine}.
037 *
038 * @author <a href="@PUBLISHER-URL@">@PUBLISHER-NAME@</a>
039 * @version @PROJECT-VERSION@
040 */
041public class Parser
042{
043    private HelpFormatter m_helpFormatter = new HelpFormatter();
044    private Option m_helpOption = null;
045    private String m_helpTrigger = null;
046    private Group m_group = null;
047
048    /**
049     * Parse the arguments according to the specified options and properties.
050     *
051     * @param arguments the command line arguments
052     * @return the list of atomic option and value tokens
053     * @throws OptionException if there are any problems encountered while parsing the
054     *   command line tokens.
055     */
056    public CommandLine parse( final String[] arguments ) throws OptionException
057    {
058        // build a mutable list for the arguments
059        final List argumentList = new LinkedList();
060
061        // copy the arguments into the new list
062        for( int i = 0; i < arguments.length; i++ )
063        {
064            final String argument = arguments[i];
065
066            // ensure non intern'd strings are used 
067            // so that == comparisons work as expected
068            argumentList.add( new String( argument ) );
069        }
070
071        // wet up a command line for this group
072        final WriteableCommandLine commandLine = 
073          new WriteableCommandLineImpl( m_group, argumentList );
074
075        // pick up any defaults from the model
076        m_group.defaults( commandLine );
077
078        // process the options as far as possible
079        final ListIterator iterator = argumentList.listIterator();
080        Object previous = null;
081        
082        while( m_group.canProcess( commandLine, iterator ) )
083        {
084            // peek at the next item and backtrack
085            final Object next = iterator.next();
086            iterator.previous();
087            // if we have just tried to process this instance
088            if( next == previous )
089            {
090                // abort
091                break;
092            }
093            // remember previous
094            previous = next;
095            m_group.process( commandLine, iterator );
096        }
097        
098        // if there are more arguments we have a problem
099        if( iterator.hasNext() )
100        {
101            final String arg = (String) iterator.next();
102            throw new OptionException(
103              m_group, 
104              ResourceConstants.UNEXPECTED_TOKEN, 
105              arg );
106        }
107        
108        // no need to validate if the help option is present
109        if( !commandLine.hasOption( m_helpOption ) && !commandLine.hasOption( m_helpTrigger ) )
110        {
111            m_group.validate( commandLine );
112        }
113        return commandLine;
114    }
115
116    /**
117     * Parse the arguments according to the specified options and properties and
118     * displays the usage screen if the CommandLine is not valid or the help
119     * option was specified.
120     *
121     * @param arguments the command line arguments
122     * @return a valid CommandLine or null if the parse was unsuccessful
123     * @throws IOException if an error occurs while formatting help
124     */
125    public CommandLine parseAndHelp( final String[] arguments ) throws IOException
126    {
127        m_helpFormatter.setGroup( m_group );
128
129        try
130        {
131            // attempt to parse the command line
132            final CommandLine commandLine = parse( arguments );
133            if( !commandLine.hasOption( m_helpOption ) && !commandLine.hasOption( m_helpTrigger ) )
134            {
135                return commandLine;
136            }
137        } 
138        catch( final OptionException oe )
139        {
140            // display help regarding the exception
141            m_helpFormatter.setException( oe );
142        }
143
144        // print help
145        m_helpFormatter.print();
146        return null;
147    }
148
149    /**
150     * Sets the Group of options to parse against
151     * @param group the group of options to parse against
152     */
153    public void setGroup( final Group group )
154    {
155        m_group = group;
156    }
157
158    /**
159     * Sets the HelpFormatter to use with the simplified parsing.
160     * @see #parseAndHelp(String[])
161     * @param helpFormatter the HelpFormatter to use with the simplified parsing
162     */
163    public void setHelpFormatter( final HelpFormatter helpFormatter )
164    {
165        m_helpFormatter = helpFormatter;
166    }
167
168    /**
169     * Sets the help option to use with the simplified parsing.  For example
170     * <code>--help</code>, <code>-h</code> and <code>-?</code> are often used.
171     * @see #parseAndHelp(String[])
172     * @param helpOption the help Option
173     */
174    public void setHelpOption( final Option helpOption )
175    {
176        m_helpOption = helpOption;
177    }
178
179    /**
180     * Sets the help option to use with the simplified parsing.  For example
181     * <code>--help</code>, <code>-h</code> and <code>-?</code> are often used.
182     * @see #parseAndHelp(String[])
183     * @param helpTrigger the trigger of the help Option
184     */
185    public void setHelpTrigger( final String helpTrigger )
186    {
187        m_helpTrigger = helpTrigger;
188    }
189}