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.Comparator;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Set;
024
025import net.dpml.cli.Argument;
026import net.dpml.cli.Option;
027import net.dpml.cli.OptionException;
028import net.dpml.cli.WriteableCommandLine;
029import net.dpml.cli.resource.ResourceConstants;
030import 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="@PUBLISHER-URL@">@PUBLISHER-NAME@</a>
038 * @version @PROJECT-VERSION@
039 */
040public 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}