001package org.apache.maven.scm.provider.bazaar;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.maven.scm.ScmException;
023import org.apache.maven.scm.ScmFileSet;
024import org.apache.maven.scm.ScmFileStatus;
025import org.apache.maven.scm.ScmResult;
026import org.apache.maven.scm.log.DefaultLog;
027import org.apache.maven.scm.log.ScmLogger;
028import org.apache.maven.scm.provider.bazaar.command.BazaarConstants;
029import org.apache.maven.scm.provider.bazaar.command.BazaarConsumer;
030import org.codehaus.plexus.util.cli.CommandLineException;
031import org.codehaus.plexus.util.cli.CommandLineUtils;
032import org.codehaus.plexus.util.cli.Commandline;
033
034import java.io.File;
035import java.util.ArrayList;
036import java.util.HashMap;
037import java.util.List;
038import java.util.Map;
039
040/**
041 * Common code for executing bazaar commands.
042 *
043 * @author <a href="mailto:torbjorn@smorgrav.org">Torbjorn Eikli Smorgrav</a>
044 *
045 */
046public final class BazaarUtils
047{
048
049    private BazaarUtils()
050    {
051    }
052
053    /**
054     * Map between command and its valid exit codes
055     */
056    private static final Map<String,List<Integer>> EXITCODEMAP = new HashMap<String,List<Integer>>();
057
058    /**
059     * Default exit codes for entries not in exitCodeMap
060     */
061    private static final List<Integer> DEFAULTEEXITCODES = new ArrayList<Integer>();
062
063    /** Setup exit codes*/
064    static
065    {
066        DEFAULTEEXITCODES.add( Integer.valueOf( 0 ) );
067
068        //Diff is different
069        List<Integer> diffExitCodes = new ArrayList<Integer>();
070        diffExitCodes.add( Integer.valueOf( 0 ) ); //No difference
071        diffExitCodes.add( Integer.valueOf( 1 ) ); //Conflicts in merge-like or changes in diff-like
072        diffExitCodes.add( Integer.valueOf( 2 ) ); //Unrepresentable diff changes
073        EXITCODEMAP.put( BazaarConstants.DIFF_CMD, diffExitCodes );
074    }
075
076    public static ScmResult execute( BazaarConsumer consumer, ScmLogger logger, File workingDir, String[] cmdAndArgs )
077        throws ScmException
078    {
079        try
080        {
081            //Build commandline
082            Commandline cmd = buildCmd( workingDir, cmdAndArgs );
083            if ( logger.isInfoEnabled() )
084            {
085                logger.info( "EXECUTING: " + cmd );
086            }
087
088            //Execute command
089            int exitCode = executeCmd( consumer, cmd );
090
091            //Return result
092            List<Integer> exitCodes = DEFAULTEEXITCODES;
093            if ( EXITCODEMAP.containsKey( cmdAndArgs[0] ) )
094            {
095                exitCodes = EXITCODEMAP.get( cmdAndArgs[0] );
096            }
097            boolean success = exitCodes.contains( Integer.valueOf( exitCode ) );
098
099            //On failure (and not due to exceptions) - run diagnostics
100            String providerMsg = "Execution of bazaar command succeded";
101            if ( !success )
102            {
103                BazaarConfig config = new BazaarConfig( workingDir );
104                providerMsg = "\nEXECUTION FAILED" + "\n  Execution of cmd : " + cmdAndArgs[0]
105                    + " failed with exit code: " + exitCode + "." + "\n  Working directory was: " + "\n    "
106                    + workingDir.getAbsolutePath() + config.toString( workingDir ) + "\n";
107                if ( logger.isErrorEnabled() )
108                {
109                    logger.error( providerMsg );
110                }
111            }
112
113            return new ScmResult( cmd.toString(), providerMsg, consumer.getStdErr(), success );
114        }
115        catch ( ScmException se )
116        {
117            String msg =
118                "EXECUTION FAILED\n  Execution failed before invoking the Bazaar command. Last exception:"
119                    + "\n    " + se.getMessage();
120
121            //Add nested cause if any
122            if ( se.getCause() != null )
123            {
124                msg += "\n  Nested exception:" + "\n    " + se.getCause().getMessage();
125            }
126
127            //log and return
128            if ( logger.isErrorEnabled() )
129            {
130                logger.error( msg );
131            }
132            throw se;
133        }
134    }
135
136    static Commandline buildCmd( File workingDir, String[] cmdAndArgs )
137        throws ScmException
138    {
139        Commandline cmd = new Commandline();
140        cmd.setExecutable( BazaarConstants.EXEC );
141        cmd.setWorkingDirectory( workingDir.getAbsolutePath() );
142        cmd.addArguments( cmdAndArgs );
143
144        if ( !workingDir.exists() )
145        {
146            boolean success = workingDir.mkdirs();
147            if ( !success )
148            {
149                String msg = "Working directory did not exist" + " and it couldn't be created: " + workingDir;
150                throw new ScmException( msg );
151            }
152        }
153        return cmd;
154    }
155
156    static int executeCmd( BazaarConsumer consumer, Commandline cmd )
157        throws ScmException
158    {
159        final int exitCode;
160        try
161        {
162            exitCode = CommandLineUtils.executeCommandLine( cmd, consumer, consumer );
163        }
164        catch ( CommandLineException ex )
165        {
166            throw new ScmException( "Command could not be executed: " + cmd, ex );
167        }
168        return exitCode;
169    }
170
171    public static ScmResult execute( File workingDir, String[] cmdAndArgs )
172        throws ScmException
173    {
174        ScmLogger logger = new DefaultLog();
175        return execute( new BazaarConsumer( logger ), logger, workingDir, cmdAndArgs );
176    }
177
178    public static String[] expandCommandLine( String[] cmdAndArgs, ScmFileSet additionalFiles )
179    {
180        List<File> files = additionalFiles.getFileList();
181        String[] cmd = new String[files.size() + cmdAndArgs.length];
182
183        // Copy command into array
184        System.arraycopy( cmdAndArgs, 0, cmd, 0, cmdAndArgs.length );
185
186        // Add files as additional parameter into the array
187        for ( int i = 0; i < files.size(); i++ )
188        {
189            String file = files.get( i ).getPath().replace( '\\', File.separatorChar );
190            cmd[i + cmdAndArgs.length] = file;
191        }
192
193        return cmd;
194    }
195
196    public static int getCurrentRevisionNumber( ScmLogger logger, File workingDir )
197        throws ScmException
198    {
199
200        String[] revCmd = new String[]{BazaarConstants.REVNO_CMD};
201        BazaarRevNoConsumer consumer = new BazaarRevNoConsumer( logger );
202        BazaarUtils.execute( consumer, logger, workingDir, revCmd );
203
204        return consumer.getCurrentRevisionNumber();
205    }
206
207    /**
208     * Get current (working) revision.
209     * <p/>
210     * Resolve revision to the last integer found in the command output.
211     */
212    private static class BazaarRevNoConsumer
213        extends BazaarConsumer
214    {
215
216        private int revNo;
217
218        BazaarRevNoConsumer( ScmLogger logger )
219        {
220            super( logger );
221        }
222
223        public void doConsume( ScmFileStatus status, String line )
224        {
225            try
226            {
227                revNo = Integer.valueOf( line ).intValue();
228            }
229            catch ( NumberFormatException e )
230            {
231                // ignore
232            }
233        }
234
235        int getCurrentRevisionNumber()
236        {
237            return revNo;
238        }
239    }
240}