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.HashSet;
023 import java.util.Iterator;
024 import java.util.List;
025 import java.util.ListIterator;
026 import java.util.Set;
027
028 import net.dpml.cli.Argument;
029 import net.dpml.cli.DisplaySetting;
030 import net.dpml.cli.Group;
031 import net.dpml.cli.OptionException;
032 import net.dpml.cli.WriteableCommandLine;
033 import net.dpml.cli.resource.ResourceConstants;
034 import net.dpml.cli.resource.ResourceHelper;
035
036 /**
037 * A Parent implementation representing normal switch options.
038 * For example: <code>+d|-d</code> or <code>--enable-x|--disable-x</code>.
039 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
040 * @version 1.0.0
041 */
042 public class Switch extends ParentImpl
043 {
044 /** i18n */
045 public static final ResourceHelper RESOURCES =
046 ResourceHelper.getResourceHelper();
047
048 /**
049 * The default prefix for enabled switches
050 */
051 public static final String DEFAULT_ENABLED_PREFIX = "+";
052
053 /**
054 * The default prefix for disabled switches
055 */
056 public static final String DEFAULT_DISABLED_PREFIX = "-";
057
058 private final String m_enabledPrefix;
059 private final String m_disabledPrefix;
060 private final Set m_triggers;
061 private final String m_preferredName;
062 private final Set m_aliases;
063 private final Set m_prefixes;
064 private final Boolean m_defaultSwitch;
065
066 /**
067 * Creates a new Switch with the specified parameters
068 * @param enabledPrefix the prefix used for enabled switches
069 * @param disabledPrefix the prefix used for disabled switches
070 * @param preferredName the preferred name of the switch
071 * @param aliases the aliases by which the Switch is known
072 * @param description a description of the Switch
073 * @param required whether the Option is strictly required
074 * @param argument the Argument belonging to this Parent, or null
075 * @param children the Group children belonging to this Parent, ot null
076 * @param id the unique identifier for this Option
077 * @param switchDefault the switch default value
078 * @throws IllegalArgumentException if the preferredName or an alias isn't
079 * prefixed with enabledPrefix or disabledPrefix
080 */
081 public Switch(
082 final String enabledPrefix, final String disabledPrefix, final String preferredName,
083 final Set aliases, final String description, final boolean required,
084 final Argument argument, final Group children, final int id,
085 final Boolean switchDefault )
086 throws IllegalArgumentException
087 {
088 super( argument, children, description, id, required );
089
090 if( enabledPrefix == null )
091 {
092 throw new IllegalArgumentException(
093 RESOURCES.getMessage(
094 ResourceConstants.SWITCH_NO_ENABLED_PREFIX ) );
095 }
096
097 if( disabledPrefix == null )
098 {
099 throw new IllegalArgumentException(
100 RESOURCES.getMessage(
101 ResourceConstants.SWITCH_NO_DISABLED_PREFIX ) );
102 }
103
104 if( enabledPrefix.startsWith( disabledPrefix ) )
105 {
106 throw new IllegalArgumentException(
107 RESOURCES.getMessage(
108 ResourceConstants.SWITCH_ENABLED_STARTS_WITH_DISABLED ) );
109 }
110
111 if( disabledPrefix.startsWith( enabledPrefix ) )
112 {
113 throw new IllegalArgumentException(
114 RESOURCES.getMessage(
115 ResourceConstants.SWITCH_DISABLED_STARTWS_WITH_ENABLED ) );
116 }
117
118 m_enabledPrefix = enabledPrefix;
119 m_disabledPrefix = disabledPrefix;
120 m_preferredName = preferredName;
121
122 if( ( preferredName == null ) || ( preferredName.length() < 1 ) )
123 {
124 throw new IllegalArgumentException(
125 RESOURCES.getMessage(
126 ResourceConstants.SWITCH_PREFERRED_NAME_TOO_SHORT ) );
127 }
128
129 final Set newTriggers = new HashSet();
130 newTriggers.add( enabledPrefix + preferredName );
131 newTriggers.add( disabledPrefix + preferredName );
132 m_triggers = Collections.unmodifiableSet( newTriggers );
133
134 if( aliases == null )
135 {
136 m_aliases = Collections.EMPTY_SET;
137 }
138 else
139 {
140 m_aliases = Collections.unmodifiableSet( new HashSet( aliases ) );
141
142 for( final Iterator i = aliases.iterator(); i.hasNext();)
143 {
144 final String alias = (String) i.next();
145 newTriggers.add( enabledPrefix + alias );
146 newTriggers.add( disabledPrefix + alias );
147 }
148 }
149
150 final Set newPrefixes = new HashSet( super.getPrefixes() );
151 newPrefixes.add( enabledPrefix );
152 newPrefixes.add( disabledPrefix );
153 m_prefixes = Collections.unmodifiableSet( newPrefixes );
154 m_defaultSwitch = switchDefault;
155 checkPrefixes( newPrefixes );
156 }
157
158 /**
159 * Processes the parent part of the Option. The combination of parent,
160 * argument and children is handled by the process method.
161 * @see net.dpml.cli.Option#process(WriteableCommandLine, ListIterator)
162 *
163 * @param commandLine the CommandLine to write results to
164 * @param arguments a ListIterator over argument strings positioned at the next
165 * argument to process
166 * @throws OptionException if an error occurs while processing
167 */
168 public void processParent(
169 final WriteableCommandLine commandLine, final ListIterator arguments )
170 throws OptionException
171 {
172 final String arg = (String) arguments.next();
173
174 if( canProcess( commandLine, arg ) )
175 {
176 if( arg.startsWith( m_enabledPrefix ) )
177 {
178 commandLine.addSwitch( this, true );
179 arguments.set( m_enabledPrefix + m_preferredName );
180 }
181 if( arg.startsWith( m_disabledPrefix ) )
182 {
183 commandLine.addSwitch( this, false );
184 arguments.set( m_disabledPrefix + m_preferredName );
185 }
186 }
187 else
188 {
189 throw new OptionException(
190 this,
191 ResourceConstants.UNEXPECTED_TOKEN,
192 arg );
193 }
194 }
195
196 /**
197 * Identifies the argument prefixes that should trigger this option. This
198 * is used to decide which of many Options should be tried when processing
199 * a given argument string.
200 *
201 * The returned Set must not be null.
202 *
203 * @return The set of triggers for this Option
204 */
205 public Set getTriggers()
206 {
207 return m_triggers;
208 }
209
210 /**
211 * Identifies the argument prefixes that should be considered options. This
212 * is used to identify whether a given string looks like an option or an
213 * argument value. Typically an option would return the set [--,-] while
214 * switches might offer [-,+].
215 *
216 * The returned Set must not be null.
217 *
218 * @return The set of prefixes for this Option
219 */
220 public Set getPrefixes()
221 {
222 return m_prefixes;
223 }
224
225 /**
226 * Checks that the supplied CommandLine is valid with respect to this
227 * option.
228 *
229 * @param commandLine the CommandLine to check.
230 * @throws OptionException if the CommandLine is not valid.
231 */
232 public void validate( WriteableCommandLine commandLine )
233 throws OptionException
234 {
235 if( isRequired() && !commandLine.hasOption( this ) )
236 {
237 throw new OptionException(
238 this,
239 ResourceConstants.OPTION_MISSING_REQUIRED,
240 getPreferredName() );
241 }
242 super.validate( commandLine );
243 }
244
245 /**
246 * Appends usage information to the specified StringBuffer
247 *
248 * @param buffer the buffer to append to
249 * @param helpSettings a set of display settings @see DisplaySetting
250 * @param comp a comparator used to sort the Options
251 */
252 public void appendUsage(
253 final StringBuffer buffer, final Set helpSettings, final Comparator comp )
254 {
255 // do we display optionality
256 final boolean optional =
257 !isRequired()
258 && helpSettings.contains( DisplaySetting.DISPLAY_OPTIONAL );
259
260 final boolean displayAliases =
261 helpSettings.contains( DisplaySetting.DISPLAY_ALIASES );
262 final boolean disabled =
263 helpSettings.contains( DisplaySetting.DISPLAY_SWITCH_DISABLED );
264 final boolean enabled =
265 !disabled || helpSettings.contains( DisplaySetting.DISPLAY_SWITCH_ENABLED );
266 final boolean both = disabled && enabled;
267
268 if( optional )
269 {
270 buffer.append( '[' );
271 }
272
273 if( enabled )
274 {
275 buffer.append( m_enabledPrefix ).append( m_preferredName );
276 }
277
278 if( both )
279 {
280 buffer.append( '|' );
281 }
282
283 if( disabled )
284 {
285 buffer.append( m_disabledPrefix ).append( m_preferredName );
286 }
287
288 if( displayAliases && !m_aliases.isEmpty() )
289 {
290 buffer.append( " (" );
291
292 final List list = new ArrayList( m_aliases );
293 Collections.sort( list );
294 for( final Iterator i = list.iterator(); i.hasNext();)
295 {
296 final String alias = (String) i.next();
297
298 if( enabled )
299 {
300 buffer.append( m_enabledPrefix ).append( alias );
301 }
302
303 if( both )
304 {
305 buffer.append( '|' );
306 }
307
308 if( disabled )
309 {
310 buffer.append( m_disabledPrefix ).append( alias );
311 }
312
313 if( i.hasNext() )
314 {
315 buffer.append( ',' );
316 }
317 }
318
319 buffer.append( ')' );
320 }
321
322 super.appendUsage( buffer, helpSettings, comp );
323
324 if( optional )
325 {
326 buffer.append( ']' );
327 }
328 }
329
330 /**
331 * The preferred name of an option is used for generating help and usage
332 * information.
333 *
334 * @return The preferred name of the option
335 */
336 public String getPreferredName()
337 {
338 return m_enabledPrefix + m_preferredName;
339 }
340
341 /**
342 * Adds defaults to a CommandLine.
343 *
344 * Any defaults for this option are applied as well as the defaults for
345 * any contained options
346 *
347 * @param commandLine the CommandLine object to store defaults in
348 */
349 public void defaults( final WriteableCommandLine commandLine )
350 {
351 commandLine.setDefaultSwitch( this, m_defaultSwitch );
352 }
353 }