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 }