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.Collections;
020    import java.util.Comparator;
021    import java.util.List;
022    import java.util.ListIterator;
023    import java.util.Set;
024    
025    import net.dpml.cli.DisplaySetting;
026    import net.dpml.cli.HelpLine;
027    import net.dpml.cli.OptionException;
028    import net.dpml.cli.WriteableCommandLine;
029    import net.dpml.cli.resource.ResourceConstants;
030    
031    /**
032     * Handles the java style "-Dprop=value" opions
033     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
034     * @version 1.0.0
035     */
036    public class PropertyOption extends OptionImpl
037    {
038       /** 
039        * The default property option name.
040        */
041        public static final String DEFAULT_OPTION_STRING = "-D";
042        
043       /** 
044        * The default property option description.
045        */
046        public static final String DEFAULT_DESCRIPTION = "Set property values.";
047    
048        /**
049         * A default PropertyOption instance
050         */
051        public static final PropertyOption INSTANCE = new PropertyOption();
052        
053        private final String m_optionString;
054        private final String m_description;
055        private final Set m_prefixes;
056    
057        /**
058         * Creates a new PropertyOption using the default settings of a "-D" trigger
059         * and an id of 'D'
060         */
061        public PropertyOption() 
062        {
063            this( DEFAULT_OPTION_STRING, DEFAULT_DESCRIPTION, 'D' );
064        }
065    
066        /**
067         * Creates a new PropertyOption using the specified parameters
068         * @param optionString the trigger for the Option
069         * @param description the description of the Option
070         * @param id the id of the Option
071         */
072        public PropertyOption(
073          final String optionString, final String description, final int id )
074        {
075            super( id, false );
076            m_optionString = optionString;
077            m_description = description;
078            m_prefixes = Collections.singleton( optionString );
079        }
080    
081        /**
082         * Indicates whether this Option will be able to process the particular
083         * argument.
084         * 
085         * @param commandLine the CommandLine object to store defaults in
086         * @param argument the argument to be tested
087         * @return true if the argument can be processed by this Option
088         */
089        public boolean canProcess(
090          final WriteableCommandLine commandLine, final String argument )
091        {
092            return ( argument != null ) 
093              && argument.startsWith( m_optionString ) 
094              && ( argument.length() > m_optionString.length() );
095        }
096    
097        /**
098         * Identifies the argument prefixes that should be considered options. This
099         * is used to identify whether a given string looks like an option or an
100         * argument value. Typically an option would return the set [--,-] while
101         * switches might offer [-,+].
102         * 
103         * The returned Set must not be null.
104         * 
105         * @return The set of prefixes for this Option
106         */
107        public Set getPrefixes() 
108        {
109            return m_prefixes;
110        }
111    
112        /**
113         * Processes String arguments into a CommandLine.
114         * 
115         * The iterator will initially point at the first argument to be processed
116         * and at the end of the method should point to the first argument not
117         * processed. This method MUST process at least one argument from the
118         * ListIterator.
119         * 
120         * @param commandLine the CommandLine object to store results in
121         * @param arguments the arguments to process
122         * @throws OptionException if any problems occur
123         */
124        public void process(
125          final WriteableCommandLine commandLine, final ListIterator arguments )
126          throws OptionException 
127        {
128            final String arg = (String) arguments.next();
129    
130            if( !canProcess( commandLine, arg ) )
131            {
132                throw new OptionException(
133                  this,
134                  ResourceConstants.UNEXPECTED_TOKEN, 
135                  arg );
136            }
137            
138            final int propertyStart = m_optionString.length();
139            final int equalsIndex = arg.indexOf( '=', propertyStart );
140            final String property;
141            final String value;
142    
143            if( equalsIndex < 0 )
144            {
145                property = arg.substring( propertyStart );
146                value = "true";
147            }
148            else
149            {
150                property = arg.substring( propertyStart, equalsIndex );
151                value = arg.substring( equalsIndex + 1 );
152            }
153            commandLine.addProperty( property, value );
154        }
155    
156        /**
157         * Identifies the argument prefixes that should trigger this option. This
158         * is used to decide which of many Options should be tried when processing
159         * a given argument string.
160         * 
161         * The returned Set must not be null.
162         * 
163         * @return The set of triggers for this Option
164         */
165        public Set getTriggers()
166        {
167            return Collections.singleton( m_optionString );
168        }
169    
170        /**
171         * Checks that the supplied CommandLine is valid with respect to this
172         * option.
173         * 
174         * @param commandLine the CommandLine to check.
175         * @throws OptionException if the CommandLine is not valid.
176         */
177        public void validate( WriteableCommandLine commandLine ) throws OptionException
178        {
179            // PropertyOption needs no validation
180        }
181    
182        /**
183         * Appends usage information to the specified StringBuffer
184         * 
185         * @param buffer the buffer to append to
186         * @param helpSettings a set of display settings @see DisplaySetting
187         * @param comp a comparator used to sort the Options
188         */
189        public void appendUsage(
190          final StringBuffer buffer, final Set helpSettings, final Comparator comp ) 
191        {
192            final boolean display = helpSettings.contains( DisplaySetting.DISPLAY_PROPERTY_OPTION );
193            final boolean bracketed = helpSettings.contains( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED );
194    
195            if( display )
196            {
197                buffer.append( m_optionString );
198                if( bracketed ) 
199                {
200                    buffer.append( '<' );
201                }
202                buffer.append( "property" );
203                if( bracketed ) 
204                {
205                    buffer.append( '>' );
206                }
207                buffer.append( "=" );
208                if( bracketed )
209                {
210                    buffer.append( '<' );
211                }
212                buffer.append( "value" );
213                if( bracketed )
214                {
215                    buffer.append( '>' );
216                }
217            }
218        }
219    
220        /**
221         * The preferred name of an option is used for generating help and usage
222         * information.
223         * 
224         * @return The preferred name of the option
225         */
226        public String getPreferredName() 
227        {
228            return m_optionString;
229        }
230    
231        /**
232         * Returns a description of the option. This string is used to build help
233         * messages as in the HelpFormatter.
234         * 
235         * @see net.dpml.cli.util.HelpFormatter
236         * @return a description of the option.
237         */
238        public String getDescription()
239        {
240            return m_description;
241        }
242    
243        /**
244         * Builds up a list of HelpLineImpl instances to be presented by HelpFormatter.
245         * 
246         * @see HelpLine
247         * @see net.dpml.cli.util.HelpFormatter
248         * @param depth the initial indent depth
249         * @param helpSettings the HelpSettings that should be applied
250         * @param comp a comparator used to sort options when applicable.
251         * @return a List of HelpLineImpl objects
252         */
253        public List helpLines(
254          final int depth, final Set helpSettings, final Comparator comp )
255        {
256            if( helpSettings.contains( DisplaySetting.DISPLAY_PROPERTY_OPTION ) ) 
257            {
258                final HelpLine helpLine = new HelpLineImpl( this, depth );
259                return Collections.singletonList( helpLine );
260            } 
261            else
262            {
263                return Collections.EMPTY_LIST;
264            }
265        }
266    }