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.Iterator;
020    import java.util.ListIterator;
021    import java.util.Set;
022    
023    import net.dpml.cli.DisplaySetting;
024    import net.dpml.cli.Option;
025    import net.dpml.cli.WriteableCommandLine;
026    import net.dpml.cli.resource.ResourceConstants;
027    import net.dpml.cli.resource.ResourceHelper;
028    
029    /**
030     * A base implementation of Option providing limited ground work for further
031     * Option implementations.
032     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
033     * @version 1.0.0
034     */
035    public abstract class OptionImpl implements Option 
036    {
037        private final int m_id;
038        private final boolean m_required;
039    
040        /**
041         * Creates an OptionImpl with the specified id
042         * @param id the unique id of this Option
043         * @param required true iff this Option must be present
044         */
045        public OptionImpl( final int id, final boolean required ) 
046        {
047            m_id = id;
048            m_required = required;
049        }
050    
051        /**
052         * Indicates whether this Option will be able to process the particular
053         * argument. The ListIterator must be restored to the initial state before
054         * returning the boolean.
055         * 
056         * @see #canProcess(WriteableCommandLine,String)
057         * @param commandLine the CommandLine object to store defaults in
058         * @param arguments the ListIterator over String arguments
059         * @return true if the argument can be processed by this Option
060         */
061        public boolean canProcess( 
062          final WriteableCommandLine commandLine, final ListIterator arguments )
063        {
064            if( arguments.hasNext() )
065            {
066                final String argument = (String) arguments.next();
067                arguments.previous();
068                return canProcess( commandLine, argument );
069            } 
070            else 
071            {
072                return false;
073            }
074        }
075    
076       /**
077        * Returns a string representation of the option.
078        * @return the string value
079        */
080        public String toString() 
081        {
082            final StringBuffer buffer = new StringBuffer();
083            appendUsage( buffer, DisplaySetting.ALL, null );
084            return buffer.toString();
085        }
086    
087        /**
088         * Returns the id of the option.  This can be used in a loop and switch 
089         * construct:
090         * 
091         * <code>
092         * for(Option o : cmd.getOptions()){
093         *     switch(o.getId()){
094         *         case POTENTIAL_OPTION:
095         *             ...
096         *     }
097         * }
098         * </code> 
099         * 
100         * The returned value is not guarenteed to be unique.
101         * 
102         * @return the id of the option.
103         */
104        public int getId() 
105        {
106            return m_id;
107        }
108    
109       /**
110        * Evaluate this instance against the supplied instance for equality.
111        * @param thatObj the other object
112        * @return true if the supplied instance is equal to this instance
113        */
114        public boolean equals( final Object thatObj )
115        {
116            if( thatObj instanceof OptionImpl )
117            {
118                final OptionImpl that = (OptionImpl) thatObj;
119                return ( getId() == that.getId() ) 
120                  && equals( getPreferredName(), that.getPreferredName() ) 
121                  && equals( getDescription(), that.getDescription() ) 
122                  && equals( getPrefixes(), that.getPrefixes() ) 
123                  && equals( getTriggers(), that.getTriggers() );
124            }
125            else
126            {
127                return false;
128            }
129        }
130    
131        private boolean equals( Object left, Object right )
132        {
133            if( ( left == null ) && ( right == null ) )
134            {
135                return true;
136            }
137            else if( ( left == null ) || ( right == null ) )
138            {
139                return false;
140            } 
141            else
142            {
143                return left.equals( right );
144            }
145        }
146    
147       /**
148        * Return the hashcode value for this instance.
149        * @return the hash value
150        */
151        public int hashCode()
152        {
153            int hashCode = getId();
154            hashCode = ( hashCode * 37 ) + getPreferredName().hashCode();
155            if( getDescription() != null )
156            {
157                hashCode = ( hashCode * 37 ) + getDescription().hashCode();
158            }
159            hashCode = ( hashCode * 37 ) + getPrefixes().hashCode();
160            hashCode = ( hashCode * 37 ) + getTriggers().hashCode();
161            return hashCode;
162        }
163    
164       /**
165        * Recursively searches for an option with the supplied trigger.
166        *
167        * @param trigger the trigger to search for.
168        * @return the matching option or null.
169        */
170        public Option findOption( String trigger )
171        {
172            if( getTriggers().contains( trigger ) )
173            {
174                return this;
175            } 
176            else 
177            {
178                return null;
179            }
180        }
181    
182        /**
183         * Indicates whether this option is required to be present.
184         * @return true if the CommandLine will be invalid without this Option
185         */
186        public boolean isRequired() 
187        {
188            return m_required;
189        }
190    
191        /**
192         * Adds defaults to a CommandLine.
193         * 
194         * Any defaults for this option are applied as well as the defaults for 
195         * any contained options
196         * 
197         * @param commandLine the CommandLine object to store defaults in
198         */
199        public void defaults( final WriteableCommandLine commandLine ) 
200        {
201            // nothing to do normally
202        }
203    
204       /**
205        * Check prefixes.
206        * @param prefixes the prefixes set
207        */
208        protected void checkPrefixes( final Set prefixes ) 
209        {
210            // nothing to do if empty prefix list
211            if( prefixes.isEmpty() )
212            {
213                return;
214            }
215    
216            // check preferred name
217            checkPrefix( prefixes, getPreferredName() );
218    
219            // check triggers
220            getTriggers();
221    
222            for( final Iterator i = getTriggers().iterator(); i.hasNext();)
223            {
224                checkPrefix( prefixes, (String) i.next() );
225            }
226        }
227    
228       /**
229        * Check prefixes.
230        * @param prefixes the prefixes set
231        * @param trigger the trigger
232        */
233        private void checkPrefix( final Set prefixes, final String trigger )
234        {
235            for( final Iterator i = prefixes.iterator(); i.hasNext();) 
236            {
237                String prefix = (String) i.next();
238                if( trigger.startsWith( prefix ) ) 
239                {
240                    return;
241                }
242            }
243    
244            final ResourceHelper helper = ResourceHelper.getResourceHelper();
245            final String message =
246              helper.getMessage( 
247                ResourceConstants.OPTION_TRIGGER_NEEDS_PREFIX, 
248                trigger, 
249                prefixes.toString() );
250            throw new IllegalArgumentException( message );
251        }
252    }