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.option;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.Comparator;
022import java.util.List;
023import java.util.ListIterator;
024import java.util.Set;
025
026import net.dpml.cli.Argument;
027import net.dpml.cli.DisplaySetting;
028import net.dpml.cli.Group;
029import net.dpml.cli.Option;
030import net.dpml.cli.OptionException;
031import net.dpml.cli.Parent;
032import net.dpml.cli.WriteableCommandLine;
033
034/**
035 * A base implementation of Parent providing limited ground work for further
036 * Parent implementations.
037 * @author <a href="@PUBLISHER-URL@">@PUBLISHER-NAME@</a>
038 * @version @PROJECT-VERSION@
039 */
040public abstract class ParentImpl extends OptionImpl implements Parent
041{
042    private static final char NUL = '\0';
043    private final Group m_children;
044    private final Argument m_argument;
045    private final String m_description;
046
047   /**
048    * Creation of a new ParaentImpl.
049    * @param argument an argument
050    * @param children the children
051    * @param description the description
052    * @param id the id
053    * @param required the required flag
054    */
055    protected ParentImpl(
056      final Argument argument, final Group children, final String description,
057      final int id, final boolean required )
058    {
059        super( id, required );
060        
061        m_children = children;
062        m_argument = argument;
063        m_description = description;
064    }
065
066    /**
067     * Processes String arguments into a CommandLine.
068     * 
069     * The iterator will initially point at the first argument to be processed
070     * and at the end of the method should point to the first argument not
071     * processed. This method MUST process at least one argument from the
072     * ListIterator.
073     * 
074     * @param commandLine the CommandLine object to store results in
075     * @param arguments the arguments to process
076     * @throws OptionException if any problems occur
077     */
078    public void process( final WriteableCommandLine commandLine, final ListIterator arguments )
079        throws OptionException
080    {
081        if( m_argument != null )
082        {
083            handleInitialSeparator( arguments, m_argument.getInitialSeparator() );
084        }
085
086        processParent( commandLine, arguments );
087
088        if( m_argument != null )
089        {
090            m_argument.processValues( commandLine, arguments, this );
091        }
092
093        if( ( m_children != null ) && m_children.canProcess( commandLine, arguments ) ) 
094        {
095            m_children.process( commandLine, arguments );
096        }
097    }
098
099    /**
100     * Indicates whether this Option will be able to process the particular
101     * argument.
102     * 
103     * @param commandLine the CommandLine object to store defaults in
104     * @param arg the argument to be tested
105     * @return true if the argument can be processed by this Option
106     */
107    public boolean canProcess(
108      final WriteableCommandLine commandLine, final String arg )
109    {
110        final Set triggers = getTriggers();
111        if( m_argument != null )
112        {
113            final char separator = m_argument.getInitialSeparator();
114
115            // if there is a valid separator character
116            if( separator != NUL )
117            {
118                final int initialIndex = arg.indexOf( separator );
119                // if there is a separator present
120                if( initialIndex > 0 )
121                {
122                    return triggers.contains( arg.substring( 0, initialIndex ) );
123                }
124            }
125        }
126
127        return triggers.contains( arg );
128    }
129
130    /**
131     * Identifies the argument prefixes that should be considered options. This
132     * is used to identify whether a given string looks like an option or an
133     * argument value. Typically an option would return the set [--,-] while
134     * switches might offer [-,+].
135     * 
136     * The returned Set must not be null.
137     * 
138     * @return The set of prefixes for this Option
139     */
140    public Set getPrefixes()
141    {
142        if( null == m_children )
143        {
144            return Collections.EMPTY_SET;
145        }
146        else
147        {
148            return m_children.getPrefixes();
149        }
150    }
151
152    /**
153     * Checks that the supplied CommandLine is valid with respect to this
154     * option.
155     * 
156     * @param commandLine the CommandLine to check.
157     * @throws OptionException if the CommandLine is not valid.
158     */
159    public void validate( WriteableCommandLine commandLine ) throws OptionException
160    {
161        if( commandLine.hasOption( this ) )
162        {
163            if( m_argument != null )
164            {
165                m_argument.validate( commandLine, this );
166            }
167
168            if( m_children != null )
169            {
170                m_children.validate( commandLine );
171            }
172        }
173    }
174
175    /**
176     * Appends usage information to the specified StringBuffer
177     * 
178     * @param buffer the buffer to append to
179     * @param helpSettings a set of display settings @see DisplaySetting
180     * @param comp a comparator used to sort the Options
181     */
182    public void appendUsage(
183      final StringBuffer buffer, final Set helpSettings, final Comparator comp )
184    {
185        final boolean displayArgument =
186          ( m_argument != null ) 
187          && helpSettings.contains( DisplaySetting.DISPLAY_PARENT_ARGUMENT );
188        final boolean displayChildren =
189          ( m_children != null ) 
190          && helpSettings.contains( DisplaySetting.DISPLAY_PARENT_CHILDREN );
191
192        if( displayArgument )
193        {
194            buffer.append( ' ' );
195            m_argument.appendUsage( buffer, helpSettings, comp );
196        }
197
198        if( displayChildren )
199        {
200            buffer.append( ' ' );
201            m_children.appendUsage( buffer, helpSettings, comp );
202        }
203    }
204
205    /**
206     * Returns a description of the option. This string is used to build help
207     * messages as in the HelpFormatter.
208     * 
209     * @see net.dpml.cli.util.HelpFormatter
210     * @return a description of the option.
211     */
212    public String getDescription()
213    {
214        return m_description;
215    }
216
217    /**
218     * Builds up a list of HelpLineImpl instances to be presented by HelpFormatter.
219     * 
220     * @see net.dpml.cli.HelpLine
221     * @see net.dpml.cli.util.HelpFormatter
222     * @param depth the initial indent depth
223     * @param helpSettings the HelpSettings that should be applied
224     * @param comp a comparator used to sort options when applicable.
225     * @return a List of HelpLineImpl objects
226     */
227    public List helpLines(
228      final int depth, final Set helpSettings, final Comparator comp )
229    {
230        final List helpLines = new ArrayList();
231        helpLines.add( new HelpLineImpl( this, depth ) );
232
233        if( helpSettings.contains( DisplaySetting.DISPLAY_PARENT_ARGUMENT ) && ( m_argument != null ) )
234        {
235            helpLines.addAll( m_argument.helpLines( depth + 1, helpSettings, comp ) );
236        }
237
238        if( helpSettings.contains( DisplaySetting.DISPLAY_PARENT_CHILDREN ) && ( m_children != null ) )
239        {
240            helpLines.addAll( m_children.helpLines( depth + 1, helpSettings, comp ) );
241        }
242
243        return helpLines;
244    }
245
246   /**
247    * Return the argument value if any. 
248    * @return Returns the argument.
249    */
250    public Argument getArgument()
251    {
252        return m_argument;
253    }
254
255    /**
256     * Return any children.
257     * @return Returns the children.
258     */
259    public Group getChildren()
260    {
261        return m_children;
262    }
263
264    /**
265     * Split the token using the specified separator character.
266     * @param arguments the current position in the arguments iterator
267     * @param separator the separator char to split on
268     */
269    private void handleInitialSeparator(
270      final ListIterator arguments, final char separator )
271    {
272        // next token
273        final String newArgument = (String) arguments.next();
274
275        // split the token
276        final int initialIndex = newArgument.indexOf( separator );
277
278        if( initialIndex > 0 )
279        {
280            arguments.remove();
281            arguments.add( newArgument.substring( 0, initialIndex ) );
282            arguments.add( newArgument.substring( initialIndex + 1 ) );
283            arguments.previous();
284        }
285        arguments.previous();
286    }
287    
288   /**
289    * Recursively searches for an option with the supplied trigger.
290    *
291    * @param trigger the trigger to search for.
292    * @return the matching option or null.
293    */
294    public Option findOption( final String trigger )
295    {
296        final Option found = super.findOption( trigger );
297        if( ( found == null ) && ( m_children != null ) )
298        {
299            return m_children.findOption( trigger );
300        } 
301        else 
302        {
303            return found;
304        }
305    }
306
307    /**
308     * Adds defaults to a CommandLine.
309     * 
310     * Any defaults for this option are applied as well as the defaults for 
311     * any contained options
312     * 
313     * @param commandLine the CommandLine object to store defaults in
314     */
315    public void defaults( final WriteableCommandLine commandLine )
316    {
317        super.defaults( commandLine );
318        if( m_argument != null )
319        {
320            m_argument.defaultValues( commandLine, this );
321        }
322        if( m_children != null )
323        {
324            m_children.defaults( commandLine );
325        }
326    }
327}