001    package 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    
022    import org.apache.maven.scm.ScmException;
023    import org.apache.maven.scm.ScmFileSet;
024    import org.apache.maven.scm.ScmFileStatus;
025    import org.apache.maven.scm.ScmResult;
026    import org.apache.maven.scm.log.DefaultLog;
027    import org.apache.maven.scm.log.ScmLogger;
028    import org.apache.maven.scm.provider.bazaar.command.BazaarConstants;
029    import org.apache.maven.scm.provider.bazaar.command.BazaarConsumer;
030    import org.codehaus.plexus.util.cli.CommandLineException;
031    import org.codehaus.plexus.util.cli.CommandLineUtils;
032    import org.codehaus.plexus.util.cli.Commandline;
033    
034    import java.io.File;
035    import java.util.ArrayList;
036    import java.util.HashMap;
037    import java.util.List;
038    import java.util.Map;
039    
040    /**
041     * Common code for executing bazaar commands.
042     *
043     * @author <a href="mailto:torbjorn@smorgrav.org">Torbj�rn Eikli Sm�rgrav</a>
044     * @version $Id: BazaarUtils.java 1306858 2012-03-29 13:41:29Z olamy $
045     */
046    public 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    }