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     * Represents a cvs "update" style command line option.
038     *
039     * Like all Parents, Commands can have child options and can be part of
040     * Arguments.
041     *
042     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
043     * @version 1.0.0
044     */
045    public class Command extends ParentImpl
046    {
047        /** The display name for the command */
048        private final String m_preferredName;
049    
050        /** The aliases for this command */
051        private final Set m_aliases;
052    
053        /** All the names for this command */
054        private final Set m_triggers;
055    
056        /**
057         * Creates a new Command instance.
058         *
059         * @param preferredName the name normally used to refer to the Command
060         * @param description a description of the Command
061         * @param aliases alternative names for the Command
062         * @param required true if the Command is required
063         * @param argument an Argument that the command takes
064         * @param children the Group of child options for this Command
065         * @param id a unique id for the Command
066         * @see ParentImpl#ParentImpl(Argument, Group, String, int, boolean)
067         */
068        public Command(
069          final String preferredName, final String description, final Set aliases, 
070          final boolean required, final Argument argument, final Group children, final int id )
071        {
072            super( argument, children, description, id, required );
073    
074            // check the preferred name is valid
075            if( ( preferredName == null ) || ( preferredName.length() < 1 ) )
076            {
077                throw new IllegalArgumentException(
078                  ResourceHelper.getResourceHelper().getMessage(
079                    ResourceConstants.COMMAND_PREFERRED_NAME_TOO_SHORT ) );
080            }
081    
082            m_preferredName = preferredName;
083    
084            // gracefully and defensively handle aliases
085            
086            if( null == aliases )
087            {
088                m_aliases = Collections.EMPTY_SET;
089            }
090            else
091            {
092                m_aliases = Collections.unmodifiableSet( new HashSet( aliases ) );
093            }
094            
095            // populate the triggers Set
096            final Set newTriggers = new HashSet();
097            newTriggers.add( preferredName );
098            newTriggers.addAll( m_aliases );
099            m_triggers = Collections.unmodifiableSet( newTriggers );
100        }
101    
102       /**
103        * Process the parent.
104        * @param commandLine the commandline
105        * @param arguments an iterator of arguments
106        * @exception OptionException if an error occurs
107        */
108        public void processParent(
109          final WriteableCommandLine commandLine, final ListIterator arguments )
110          throws OptionException
111        {
112            // grab the argument to process
113            final String arg = (String) arguments.next();
114    
115            // if we can process it
116            if( canProcess( commandLine, arg ) )
117            {
118                // then note the option
119                commandLine.addOption( this );
120    
121                // normalise the argument list
122                arguments.set( m_preferredName );
123            }
124            else
125            {
126                throw new OptionException(
127                  this,
128                  ResourceConstants.UNEXPECTED_TOKEN, 
129                  arg );
130            }
131        }
132    
133        /**
134         * Identifies the argument prefixes that should trigger this option. This
135         * is used to decide which of many Options should be tried when processing
136         * a given argument string.
137         * 
138         * The returned Set must not be null.
139         * 
140         * @return The set of triggers for this Option
141         */
142        public Set getTriggers()
143        {
144            return m_triggers;
145        }
146    
147        /**
148         * Checks that the supplied CommandLine is valid with respect to this
149         * option.
150         * 
151         * @param commandLine the CommandLine to check.
152         * @throws OptionException if the CommandLine is not valid.
153         */
154        public void validate( WriteableCommandLine commandLine ) throws OptionException
155        {
156            if( isRequired() && !commandLine.hasOption( this ) )
157            {
158                throw new OptionException(
159                  this,
160                  ResourceConstants.OPTION_MISSING_REQUIRED,
161                  getPreferredName() );
162            }
163            super.validate( commandLine );
164        }
165    
166        /**
167         * Appends usage information to the specified StringBuffer
168         * 
169         * @param buffer the buffer to append to
170         * @param helpSettings a set of display settings @see DisplaySetting
171         * @param comp a comparator used to sort the Options
172         */
173        public void appendUsage(
174          final StringBuffer buffer, final Set helpSettings, final Comparator comp )
175          {
176            // do we display optionality
177            final boolean optional =
178              !isRequired() && helpSettings.contains( DisplaySetting.DISPLAY_OPTIONAL );
179            final boolean displayAliases = 
180              helpSettings.contains( DisplaySetting.DISPLAY_ALIASES );
181    
182            if( optional )
183            {
184                buffer.append( '[' );
185            }
186    
187            buffer.append( m_preferredName );
188    
189            if( displayAliases && !m_aliases.isEmpty() )
190            {
191                buffer.append( " (" );
192                final List list = new ArrayList( m_aliases );
193                Collections.sort( list );
194                for( final Iterator i = list.iterator(); i.hasNext();)
195                {
196                    final String alias = (String) i.next();
197                    buffer.append( alias );
198                    if( i.hasNext() )
199                    {
200                        buffer.append( ',' );
201                    }
202                }
203                buffer.append( ')' );
204            }
205    
206            super.appendUsage( buffer, helpSettings, comp );
207            if( optional )
208            {
209                buffer.append( ']' );
210            }
211        }
212    
213        /**
214         * The preferred name of an option is used for generating help and usage
215         * information.
216         * 
217         * @return The preferred name of the option
218         */
219        public String getPreferredName()
220        {
221            return m_preferredName;
222        }
223    }