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="@PUBLISHER-URL@">@PUBLISHER-NAME@</a>
038 * @version @PROJECT-VERSION@
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 }