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     */
017    package net.dpml.cli.option;
018    
019    import java.util.ArrayList;
020    import java.util.Collections;
021    import java.util.Comparator;
022    import java.util.List;
023    import java.util.ListIterator;
024    import java.util.Set;
025    
026    import net.dpml.cli.Argument;
027    import net.dpml.cli.DisplaySetting;
028    import net.dpml.cli.Group;
029    import net.dpml.cli.Option;
030    import net.dpml.cli.OptionException;
031    import net.dpml.cli.Parent;
032    import 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="http://www.dpml.net">Digital Product Meta Library</a>
038     * @version 1.0.0
039     */
040    public 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    }