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 */
017package net.dpml.cli.option;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.Comparator;
022import java.util.HashSet;
023import java.util.Iterator;
024import java.util.List;
025import java.util.ListIterator;
026import java.util.Set;
027
028import net.dpml.cli.Argument;
029import net.dpml.cli.DisplaySetting;
030import net.dpml.cli.Group;
031import net.dpml.cli.OptionException;
032import net.dpml.cli.WriteableCommandLine;
033import net.dpml.cli.resource.ResourceConstants;
034import 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="@PUBLISHER-URL@">@PUBLISHER-NAME@</a>
043 * @version @PROJECT-VERSION@
044 */
045public 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}