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.Comparator;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.util.Set;
024    
025    import net.dpml.cli.Argument;
026    import net.dpml.cli.Option;
027    import net.dpml.cli.OptionException;
028    import net.dpml.cli.WriteableCommandLine;
029    import net.dpml.cli.resource.ResourceConstants;
030    import net.dpml.cli.resource.ResourceHelper;
031    
032    /**
033     * An Argument implementation that allows a variable size Argument to precede a
034     * fixed size argument.  The canonical example of it's use is in the unix
035     * <code>cp</code> command where a number of source can be specified with
036     * exactly one destination specfied at the end.
037     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
038     * @version 1.0.0
039     */
040    public class SourceDestArgument extends ArgumentImpl
041    {
042        private final Argument m_source;
043        private final Argument m_dest;
044    
045        /**
046         * Creates a SourceDestArgument using defaults where possible.
047         *
048         * @param source the variable size Argument
049         * @param dest the fixed size Argument
050         */
051        public SourceDestArgument(
052          final Argument source, final Argument dest )
053        {
054            this( 
055              source, 
056              dest, 
057              DEFAULT_INITIAL_SEPARATOR, 
058              DEFAULT_SUBSEQUENT_SEPARATOR,
059              DEFAULT_CONSUME_REMAINING, 
060              null );
061        }
062    
063        /**
064         * Creates a SourceDestArgument using the specified parameters.
065         *
066         * @param source the variable size Argument
067         * @param dest the fixed size Argument
068         * @param initialSeparator the inistial separator to use
069         * @param subsequentSeparator the subsequent separator to use
070         * @param consumeRemaining the token triggering consume remaining behaviour
071         * @param defaultValues the default values for the SourceDestArgument
072         */
073        public SourceDestArgument(
074          final Argument source, final Argument dest, final char initialSeparator,
075          final char subsequentSeparator, final String consumeRemaining,
076          final List defaultValues )
077        {
078            super( 
079              "SourceDestArgument", null, sum( source.getMinimum(), dest.getMinimum() ),
080              sum( source.getMaximum(), dest.getMaximum() ), initialSeparator, 
081              subsequentSeparator, null, consumeRemaining, defaultValues, 0 );
082    
083            m_source = source;
084            m_dest = dest;
085    
086            if( dest.getMinimum() != dest.getMaximum() )
087            {
088                throw new IllegalArgumentException(
089                  ResourceHelper.getResourceHelper().getMessage(
090                    ResourceConstants.SOURCE_DEST_MUST_ENFORCE_VALUES ) );
091            }
092        }
093    
094        private static int sum( final int a, final int b )
095        {
096            return Math.max( a, Math.max( b, a + b ) );
097        }
098    
099        /**
100         * Appends usage information to the specified StringBuffer
101         * 
102         * @param buffer the buffer to append to
103         * @param helpSettings a set of display settings @see DisplaySetting
104         * @param comp a comparator used to sort the Options
105         */
106        public void appendUsage(
107          final StringBuffer buffer, final Set helpSettings, final Comparator comp )
108        {
109            final int length = buffer.length();
110    
111            m_source.appendUsage( buffer, helpSettings, comp );
112    
113            if( buffer.length() != length )
114            {
115                buffer.append( ' ' );
116            }
117    
118            m_dest.appendUsage( buffer, helpSettings, comp );
119        }
120    
121        /**
122         * Builds up a list of HelpLineImpl instances to be presented by HelpFormatter.
123         * 
124         * @see net.dpml.cli.HelpLine
125         * @see net.dpml.cli.util.HelpFormatter
126         * @param depth the initial indent depth
127         * @param helpSettings the HelpSettings that should be applied
128         * @param comp a comparator used to sort options when applicable.
129         * @return a List of HelpLineImpl objects
130         */
131        public List helpLines(
132          int depth, Set helpSettings, Comparator comp )
133        {
134            final List helpLines = new ArrayList();
135            helpLines.addAll( m_source.helpLines( depth, helpSettings, comp ) );
136            helpLines.addAll( m_dest.helpLines( depth, helpSettings, comp ) );
137            return helpLines;
138        }
139    
140        /**
141         * Checks that the supplied CommandLine is valid with respect to the
142         * suppled option.
143         * 
144         * @param commandLine the CommandLine to check.
145         * @param option the option to evaluate
146         * @throws OptionException if the CommandLine is not valid.
147         */
148        public void validate( WriteableCommandLine commandLine, Option option )
149          throws OptionException
150        {
151            final List values = commandLine.getValues( option );
152    
153            final int limit = values.size() - m_dest.getMinimum();
154            int count = 0;
155    
156            final Iterator i = values.iterator();
157    
158            while( count++ < limit )
159            {
160                commandLine.addValue( m_source, i.next() );
161            }
162            
163            while( i.hasNext() )
164            {
165                commandLine.addValue( m_dest, i.next() );
166            }
167            
168            m_source.validate( commandLine, m_source );
169            m_dest.validate( commandLine, m_dest );
170        }
171    
172        /**
173         * Indicates whether this Option will be able to process the particular
174         * argument.
175         * 
176         * @param commandLine the CommandLine object to store defaults in
177         * @param arg the argument to be tested
178         * @return true if the argument can be processed by this Option
179         */
180        public boolean canProcess(
181          final WriteableCommandLine commandLine, final String arg )
182        {
183            return m_source.canProcess( commandLine, arg ) || m_dest.canProcess( commandLine, arg );
184        }
185    }