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    
035    /**
036     * A Parent implementation representing normal options.
037     *
038     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
039     * @version 1.0.0
040     */
041    public class DefaultOption extends ParentImpl
042    {
043        /**
044         * The default token used to prefix a short option
045         */
046        public static final String DEFAULT_SHORT_PREFIX = "-";
047    
048        /**
049         * The default token used to prefix a long option
050         */
051        public static final String DEFAULT_LONG_PREFIX = "--";
052    
053        /**
054         * The default value for the burstEnabled constructor parameter
055         */
056        public static final boolean DEFAULT_BURST_ENABLED = true;
057        
058        private final String m_preferredName;
059        private final Set m_aliases;
060        private final Set m_burstAliases;
061        private final Set m_triggers;
062        private final Set m_prefixes;
063        private final String m_shortPrefix;
064        private final boolean m_burstEnabled;
065        private final int m_burstLength;
066    
067        /**
068         * Creates a new DefaultOption
069         *
070         * @param shortPrefix the prefix used for short options
071         * @param longPrefix the prefix used for long options
072         * @param burstEnabled should option bursting be enabled
073         * @param preferredName the preferred name for this Option, this should 
074         *   begin with either shortPrefix or longPrefix
075         * @param description a description of this Option
076         * @param aliases the alternative names for this Option
077         * @param burstAliases the aliases that can be burst
078         * @param required whether the Option is strictly required
079         * @param argument the Argument belonging to this Parent, or null
080         * @param children the Group children belonging to this Parent, ot null
081         * @param id the unique identifier for this Option
082         * @throws IllegalArgumentException if the preferredName or an alias isn't
083         *     prefixed with shortPrefix or longPrefix
084         */
085        public DefaultOption(
086          final String shortPrefix, final String longPrefix, final boolean burstEnabled,
087          final String preferredName, final String description, final Set aliases,
088          final Set burstAliases, final boolean required, final Argument argument,
089          final Group children, final int id ) 
090          throws IllegalArgumentException
091        {
092            super( argument, children, description, id, required );
093    
094            m_shortPrefix = shortPrefix;
095            m_burstEnabled = burstEnabled;
096            m_burstLength = shortPrefix.length() + 1;
097            m_preferredName = preferredName;
098            
099            if( aliases == null )
100            {
101                m_aliases = Collections.EMPTY_SET;
102            }
103            else
104            {
105                m_aliases = Collections.unmodifiableSet( new HashSet( aliases ) );
106            }
107            
108            if( burstAliases == null )
109            {
110                m_burstAliases = Collections.EMPTY_SET;
111            }
112            else
113            {
114                m_burstAliases = Collections.unmodifiableSet( new HashSet( burstAliases ) );
115            }
116            
117            final Set newTriggers = new HashSet();
118            newTriggers.add( m_preferredName );
119            newTriggers.addAll( m_aliases );
120            newTriggers.addAll( m_burstAliases );
121            m_triggers = Collections.unmodifiableSet( newTriggers );
122    
123            final Set newPrefixes = new HashSet( super.getPrefixes() );
124            newPrefixes.add( m_shortPrefix );
125            newPrefixes.add( longPrefix );
126            m_prefixes = Collections.unmodifiableSet( newPrefixes );
127    
128            checkPrefixes( newPrefixes );
129        }
130    
131        /**
132         * Indicates whether this Option will be able to process the particular
133         * argument.
134         * 
135         * @param commandLine the CommandLine object to store defaults in
136         * @param argument the argument to be tested
137         * @return true if the argument can be processed by this Option
138         */
139        public boolean canProcess(
140          final WriteableCommandLine commandLine, final String argument )
141        {
142            return 
143              ( argument != null ) 
144              && ( 
145                super.canProcess( commandLine, argument ) 
146                || ( 
147                  ( argument.length() >= m_burstLength ) 
148                  && m_burstAliases.contains( 
149                    argument.substring( 0, m_burstLength ) ) 
150                ) 
151              );
152        }
153    
154        /**
155         * Process the parent.
156         * @param commandLine the CommandLine object to store defaults in
157         * @param arguments the ListIterator over String arguments
158         * @exception OptionException if an error occurs
159         */
160        public void processParent( WriteableCommandLine commandLine, ListIterator arguments )
161          throws OptionException 
162        {
163            final String argument = (String) arguments.next();
164    
165            if( m_triggers.contains( argument ) )
166            {
167                commandLine.addOption( this );
168                arguments.set( m_preferredName );
169            } 
170            else if( m_burstEnabled && ( argument.length() >= m_burstLength ) )
171            {
172                final String burst = argument.substring( 0, m_burstLength );
173                if( m_burstAliases.contains( burst ) )
174                {
175                    commandLine.addOption( this );
176                    //HMM test bursting all vs bursting one by one.
177                    arguments.set( m_preferredName );
178    
179                    if( getArgument() == null )
180                    {
181                        arguments.add( m_shortPrefix + argument.substring( m_burstLength ) );
182                    }
183                    else
184                    {
185                        arguments.add( argument.substring( m_burstLength ) );
186                    }
187                    arguments.previous();
188                } 
189                else
190                {
191                    throw new OptionException(
192                      this, ResourceConstants.CANNOT_BURST, argument );
193                }
194            }
195            else
196            {
197                throw new OptionException(
198                  this, 
199                  ResourceConstants.UNEXPECTED_TOKEN,
200                  argument );
201            }
202        }
203    
204        /**
205         * Identifies the argument prefixes that should trigger this option. This
206         * is used to decide which of many Options should be tried when processing
207         * a given argument string.
208         * 
209         * The returned Set must not be null.
210         * 
211         * @return The set of triggers for this Option
212         */
213        public Set getTriggers()
214        {
215            return m_triggers;
216        }
217    
218        /**
219         * Identifies the argument prefixes that should be considered options. This
220         * is used to identify whether a given string looks like an option or an
221         * argument value. Typically an option would return the set [--,-] while
222         * switches might offer [-,+].
223         * 
224         * The returned Set must not be null.
225         * 
226         * @return The set of prefixes for this Option
227         */
228        public Set getPrefixes() 
229        {
230            return m_prefixes;
231        }
232    
233        /**
234         * Checks that the supplied CommandLine is valid with respect to this
235         * option.
236         * 
237         * @param commandLine the CommandLine to check.
238         * @throws OptionException if the CommandLine is not valid.
239         */
240        public void validate( WriteableCommandLine commandLine )
241          throws OptionException
242        {
243            if( isRequired() && !commandLine.hasOption( this ) )
244            {
245                throw new OptionException(
246                  this,
247                  ResourceConstants.OPTION_MISSING_REQUIRED,
248                  getPreferredName() );
249            }
250            super.validate( commandLine );
251        }
252    
253        /**
254         * Appends usage information to the specified StringBuffer
255         * 
256         * @param buffer the buffer to append to
257         * @param helpSettings a set of display settings @see DisplaySetting
258         * @param comp a comparator used to sort the Options
259         */
260        public void appendUsage(
261          final StringBuffer buffer, final Set helpSettings, final Comparator comp )
262        {
263            // do we display optionality
264            final boolean optional =
265              !isRequired() 
266              && helpSettings.contains( DisplaySetting.DISPLAY_OPTIONAL );
267              
268            final boolean displayAliases = 
269              helpSettings.contains( DisplaySetting.DISPLAY_ALIASES );
270    
271            if( optional )
272            {
273                buffer.append( '[' );
274            }
275    
276            buffer.append( m_preferredName );
277    
278            if( displayAliases && !m_aliases.isEmpty() ) 
279            {
280                buffer.append( " (" );
281    
282                final List list = new ArrayList( m_aliases );
283                Collections.sort( list );
284                for( final Iterator i = list.iterator(); i.hasNext();)
285                {
286                    final String alias = (String) i.next();
287                    buffer.append( alias );
288                    if( i.hasNext() )
289                    {
290                        buffer.append( ',' );
291                    }
292                }
293                buffer.append( ')' );
294            }
295    
296            super.appendUsage( buffer, helpSettings, comp );
297    
298            if( optional )
299            {
300                buffer.append( ']' );
301            }
302        }
303    
304        /**
305         * The preferred name of an option is used for generating help and usage
306         * information.
307         * 
308         * @return The preferred name of the option
309         */
310        public String getPreferredName()
311        {
312            return m_preferredName;
313        }
314    }