package org.apache.maven;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */


import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;

import org.apache.maven.artifact.manager.DefaultWagonManager;
import org.apache.maven.artifact.manager.WagonManager;
import org.apache.maven.artifact.manager.WagonProviderMapping;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.resolver.DefaultArtifactResolver;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.execution.BuildFailure;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.ReactorManager;
import org.apache.maven.execution.RuntimeInformation;
import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.lifecycle.LifecycleExecutor;
import org.apache.maven.model.Profile;
import org.apache.maven.monitor.event.DefaultEventDispatcher;
import org.apache.maven.monitor.event.EventDispatcher;
import org.apache.maven.monitor.event.MavenEvents;
import org.apache.maven.profiles.ProfileManager;
import org.apache.maven.profiles.activation.ProfileActivationException;
import org.apache.maven.project.DuplicateProjectException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.MissingProjectException;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.reactor.MavenExecutionException;
import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
import org.apache.maven.usability.SystemWarnings;
import org.apache.maven.usability.diagnostics.ErrorDiagnostics;
import org.codehaus.plexus.PlexusConstants;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.context.Context;
import org.codehaus.plexus.context.ContextException;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.Os;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.dag.CycleDetectedException;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException;

/**
 * @author <a href="mailto:jason@maven.org">Jason van Zyl </a>
 * @version $Id: DefaultMaven.java 798706 2009-07-28 20:50:02Z brett $
 */
public class DefaultMaven
    extends AbstractLogEnabled
    implements Maven, Contextualizable
{
    // ----------------------------------------------------------------------
    // Components
    // ----------------------------------------------------------------------

    protected MavenProjectBuilder projectBuilder;

    protected LifecycleExecutor lifecycleExecutor;

    protected PlexusContainer container;

    protected ErrorDiagnostics errorDiagnostics;

    protected RuntimeInformation runtimeInformation;

    private static final long MB = 1024 * 1024;

    private static final int MS_PER_SEC = 1000;

    private static final int SEC_PER_MIN = 60;

    // ----------------------------------------------------------------------
    // Project execution
    // ----------------------------------------------------------------------

    public void execute( MavenExecutionRequest request )
        throws MavenExecutionException
    {
//        if ( request.getLocalRepository() == null )
//        {
//            request.setLocalRepository( mavenTools.createLocalRepository( request.getLocalRepositoryPath() ) );
//        }

        EventDispatcher dispatcher = request.getEventDispatcher();

        String event = MavenEvents.REACTOR_EXECUTION;

        dispatcher.dispatchStart( event, request.getBaseDirectory() );

        ReactorManager rm;
        try
        {
            rm = doExecute( request, dispatcher );
        }
        catch ( LifecycleExecutionException e )
        {
            dispatcher.dispatchError( event, request.getBaseDirectory(), e );

            logError( e, request.isShowErrors() );

            stats( request.getStartTime() );

            line();

            throw new MavenExecutionException( e.getMessage(), e );
        }
        catch ( BuildFailureException e )
        {
            dispatcher.dispatchError( event, request.getBaseDirectory(), e );

            logFailure( e, request.isShowErrors() );

            stats( request.getStartTime() );

            line();

            throw new MavenExecutionException( e.getMessage(), e );
        }
        catch ( Throwable t )
        {
            dispatcher.dispatchError( event, request.getBaseDirectory(), t );

            logFatal( t );

            stats( request.getStartTime() );

            line();

            throw new MavenExecutionException( "Error executing project within the reactor", t );
        }

        // Either the build was successful, or it was a fail_at_end/fail_never reactor build

        // TODO: should all the logging be left to the CLI?
        logReactorSummary( rm );

        if ( rm.hasBuildFailures() )
        {
            logErrors( rm, request.isShowErrors() );

            if ( !ReactorManager.FAIL_NEVER.equals( rm.getFailureBehavior() ) )
            {
                dispatcher.dispatchError( event, request.getBaseDirectory(), null );

                getLogger().info( "BUILD ERRORS" );

                line();

                stats( request.getStartTime() );

                line();

                throw new MavenExecutionException( "Some builds failed" );
            }
            else
            {
                getLogger().info( " + Ignoring failures" );
            }
        }

        logSuccess( rm );

        stats( request.getStartTime() );

        line();

        dispatcher.dispatchEnd( event, request.getBaseDirectory() );
    }

    private void logErrors( ReactorManager rm, boolean showErrors )
    {
        for ( Iterator it = rm.getSortedProjects().iterator(); it.hasNext(); )
        {
            MavenProject project = (MavenProject) it.next();

            if ( rm.hasBuildFailure( project ) )
            {
                BuildFailure buildFailure = rm.getBuildFailure( project );

                getLogger().info(
                    "Error for project: " + project.getName() + " (during " + buildFailure.getTask() + ")" );

                line();

                logDiagnostics( buildFailure.getCause() );

                logTrace( buildFailure.getCause(), showErrors );
            }
        }

        if ( !showErrors )
        {
            getLogger().info( "For more information, run Maven with the -e switch" );

            line();
        }

    }

    private ReactorManager doExecute( MavenExecutionRequest request, EventDispatcher dispatcher )
        throws MavenExecutionException, BuildFailureException, LifecycleExecutionException
    {
        try
        {
            resolveParameters( request.getSettings(), request.getExecutionProperties() );
        }
        catch ( ComponentLookupException e )
        {
            throw new MavenExecutionException( "Unable to configure Maven for execution", e );
        }
        catch ( ComponentLifecycleException e )
        {
            throw new MavenExecutionException( "Unable to configure Maven for execution", e );
        }
        catch ( SettingsConfigurationException e )
        {
            throw new MavenExecutionException( "Unable to configure Maven for execution", e );
        }

        ProfileManager globalProfileManager = request.getGlobalProfileManager();

        globalProfileManager.loadSettingsProfiles( request.getSettings() );

        getLogger().info( "Scanning for projects..." );

        boolean foundProjects = true;
        List projects = getProjects( request );
        if ( projects.isEmpty() )
        {
            projects.add( getSuperProject( request ) );
            foundProjects = false;
        }

        ReactorManager rm;
        try
        {
            String resumeFrom = request.getResumeFrom();
            
            List projectList = request.getSelectedProjects();
            
            String makeBehavior = request.getMakeBehavior();
            
            rm = new ReactorManager( projects, projectList, resumeFrom, makeBehavior );

            rm.setFailureBehavior( request.getFailureBehavior() );
            
        }
        catch ( CycleDetectedException e )
        {
            throw new BuildFailureException(
                "The projects in the reactor contain a cyclic reference: " + e.getMessage(), e );
        }
        catch ( DuplicateProjectException e )
        {
            throw new BuildFailureException( e.getMessage(), e );
        }
        catch ( MissingProjectException e )
        {
            throw new BuildFailureException( e.getMessage(), e );
        }

        // --------------------------------------------------------------------------------
        // MNG-3641: print a warning if one of the profiles to be activated explicitly
        // was not activated

        validateActivatedProfiles( globalProfileManager, projects );

        if ( rm.hasMultipleProjects() )
        {
            getLogger().info( "Reactor build order: " );

            for ( Iterator i = rm.getSortedProjects().iterator(); i.hasNext(); )
            {
                MavenProject project = (MavenProject) i.next();
                getLogger().info( "  " + project.getName() );
            }
        }

        MavenSession session = createSession( request, rm );

        session.setUsingPOMsFromFilesystem( foundProjects );

        lifecycleExecutor.execute( session, rm, dispatcher );

        return rm;
    }

    private void validateActivatedProfiles( ProfileManager globalProfileManager, List projects )
    {
        if ( globalProfileManager != null )
        {
            // get all activated profile ids
            Set activeProfileIds = new HashSet();

            for ( Iterator i = projects.iterator(); i.hasNext(); )
            {
                MavenProject project = (MavenProject) i.next();

                do
                {
                    for ( Iterator j = project.getActiveProfiles().iterator(); j.hasNext(); )
                    {
                        activeProfileIds.add( ( (Profile) j.next() ).getId() );
                    }
                    project = project.getParent();
                }
                while ( project != null );
            }

            for ( Iterator i = globalProfileManager.getExplicitlyActivatedIds().iterator(); i.hasNext(); )
            {
                String explicitProfileId = (String) i.next();

                if ( !activeProfileIds.contains( explicitProfileId ) )
                {
                    getLogger().warn( "\n\tProfile with id: \'" + explicitProfileId + "\' has not been activated.\n" );
                }
            }
        }
    }

    private MavenProject getSuperProject( MavenExecutionRequest request )
        throws MavenExecutionException
    {
        MavenProject superProject;
        try
        {
            superProject = projectBuilder.buildStandaloneSuperProject( request.getLocalRepository(), request.getGlobalProfileManager() );

        }
        catch ( ProjectBuildingException e )
        {
            throw new MavenExecutionException( e.getMessage(), e );
        }
        return superProject;
    }

    private List getProjects( MavenExecutionRequest request )
        throws MavenExecutionException, BuildFailureException
    {
        List projects;
        try
        {
            List files = getProjectFiles( request );

            projects = collectProjects( files, request, !request.isReactorActive() );

        }
        catch ( IOException e )
        {
            throw new MavenExecutionException( "Error processing projects for the reactor: " + e.getMessage(), e );
        }
        catch ( ArtifactResolutionException e )
        {
            throw new MavenExecutionException( e.getMessage(), e );
        }
        catch ( ProjectBuildingException e )
        {
            throw new MavenExecutionException( e.getMessage(), e );
        }
        catch ( ProfileActivationException e )
        {
            throw new MavenExecutionException( e.getMessage(), e );
        }
        return projects;
    }

    private void logReactorSummaryLine( String name, String status )
    {
        logReactorSummaryLine( name, status, -1 );
    }

    private void logReactorSummaryLine( String name, String status, long time )
    {
        StringBuffer messageBuffer = new StringBuffer();

        messageBuffer.append( name );

        int dotCount = 54;

        dotCount -= name.length();

        messageBuffer.append( " " );

        for ( int i = 0; i < dotCount; i++ )
        {
            messageBuffer.append( '.' );
        }

        messageBuffer.append( " " );

        messageBuffer.append( status );

        if ( time >= 0 )
        {
            messageBuffer.append( " [" );

            messageBuffer.append( getFormattedTime( time ) );

            messageBuffer.append( "]" );
        }

        getLogger().info( messageBuffer.toString() );
    }

    private static String getFormattedTime( long time )
    {
        String pattern = "s.SSS's'";
        if ( time / 60000L > 0 )
        {
            pattern = "m:s" + pattern;
            if ( time / 3600000L > 0 )
            {
                pattern = "H:m" + pattern;
            }
        }
        DateFormat fmt = new SimpleDateFormat( pattern );
        fmt.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
        return fmt.format( new Date( time ) );
    }

    private List collectProjects( List files, MavenExecutionRequest request, boolean isRoot )
        throws ArtifactResolutionException, ProjectBuildingException, ProfileActivationException,
        MavenExecutionException, BuildFailureException
    {
//        .getLocalRepository(), request.isRecursive(),
//        request.getSettings(), request.getUserProperties(), requ, !request.isReactorActive()
        List projects = new ArrayList( files.size() );

        for ( Iterator iterator = files.iterator(); iterator.hasNext(); )
        {
            File file = (File) iterator.next();

            boolean usingReleasePom = false;

            if ( RELEASE_POMv4.equals( file.getName() ) )
            {
                getLogger().info( "NOTE: Using release-pom: " + file + " in reactor build." );
                usingReleasePom = true;
            }

            MavenProject project = getProject( file, request );

            if ( isRoot )
            {
                project.setExecutionRoot( true );
            }

            if ( ( project.getPrerequisites() != null ) && ( project.getPrerequisites().getMaven() != null ) )
            {
                DefaultArtifactVersion version = new DefaultArtifactVersion( project.getPrerequisites().getMaven() );
                if ( runtimeInformation.getApplicationVersion().compareTo( version ) < 0 )
                {
                    throw new BuildFailureException( "Unable to build project '" + project.getFile() +
                        "; it requires Maven version " + version.toString() );
                }
            }

            if ( ( project.getModules() != null ) && !project.getModules().isEmpty() && request.isRecursive() )
            {
                // TODO: Really should fail if it was not? What if it is aggregating - eg "ear"?
                project.setPackaging( "pom" );

                File basedir = file.getParentFile();

                // Initial ordering is as declared in the modules section
                List moduleFiles = new ArrayList( project.getModules().size() );
                for ( Iterator i = project.getModules().iterator(); i.hasNext(); )
                {
                    String name = (String) i.next();

                    if ( StringUtils.isEmpty( StringUtils.trim( name ) ) )
                    {
                        getLogger().warn(
                            "Empty module detected. Please check you don't have any empty module definitions in your POM." );

                        continue;
                    }

                    File moduleFile = new File( basedir, name );

                    if ( moduleFile.exists() && moduleFile.isDirectory() )
                    {
                        if ( usingReleasePom )
                        {
                            moduleFile = new File( basedir, name + "/" + Maven.RELEASE_POMv4 );
                        }
                        else
                        {
                            moduleFile = new File( basedir, name + "/" + Maven.POMv4 );
                        }
                    }

                    if ( Os.isFamily( "windows" ) )
                    {
                        // we don't canonicalize on unix to avoid interfering with symlinks

                        try
                        {
                            moduleFile = moduleFile.getCanonicalFile();
                        }
                        catch ( IOException e )
                        {
                            throw new MavenExecutionException( "Unable to canonicalize file name " + moduleFile, e );
                        }
                    }
                    else
                    {
                        moduleFile = new File( moduleFile.toURI().normalize() );
                    }

                    moduleFiles.add( moduleFile );
                }

                List collectedProjects =
                    collectProjects( moduleFiles, request, false );
                projects.addAll( collectedProjects );
                project.setCollectedProjects( collectedProjects );
            }
            projects.add( project );
        }

        return projects;
    }

    /**
     * @deprecated Use {@link DefaultMaven#getProject(File, MavenExecutionRequest)} instead.
     */
    public MavenProject getProject( File pom, ArtifactRepository localRepository, Settings settings,
                                    Properties userProperties, ProfileManager globalProfileManager )
        throws ProjectBuildingException, ArtifactResolutionException, ProfileActivationException
    {
        MavenExecutionRequest request = new DefaultMavenExecutionRequest(
                                                                      localRepository,
                                                                      settings,
                                                                      new DefaultEventDispatcher(),
                                                                      Collections.EMPTY_LIST,
                                                                      pom.getParentFile()
                                                                         .getAbsolutePath(),
                                                                      globalProfileManager,
                                                                      globalProfileManager.getRequestProperties(),
                                                                      new Properties(), false );

        return getProject( pom, request );
    }

    public MavenProject getProject( File pom, MavenExecutionRequest request )
        throws ProjectBuildingException, ArtifactResolutionException, ProfileActivationException
    {
        if ( pom.exists() )
        {
            if ( pom.length() == 0 )
            {
                throw new ProjectBuildingException( "unknown", "The file " + pom.getAbsolutePath() +
                    " you specified has zero length." );
            }
        }

        return projectBuilder.build( pom, request.getProjectBuilderConfiguration() );
    }

    // ----------------------------------------------------------------------
    // Methods used by all execution request handlers
    // ----------------------------------------------------------------------

    //!! We should probably have the execution request handler create the
    // session as
    // the session type would be specific to the request i.e. having a project
    // or not.

    protected MavenSession createSession( MavenExecutionRequest request,
                                          ReactorManager rpm )
    {
        return new MavenSession( container, request.getSettings(), request.getLocalRepository(),
                                 request.getEventDispatcher(), rpm, request.getGoals(), request.getBaseDirectory(),
                                 request.getExecutionProperties(), request.getUserProperties(), request.getStartTime() );
    }

    /**
     * @todo [BP] this might not be required if there is a better way to pass
     * them in. It doesn't feel quite right.
     * @todo [JC] we should at least provide a mapping of protocol-to-proxy for
     * the wagons, shouldn't we?
     */
    private void resolveParameters( Settings settings, Properties executionProperties )
        throws ComponentLookupException, ComponentLifecycleException, SettingsConfigurationException
    {
        WagonManager wagonManager = (WagonManager) container.lookup( WagonManager.ROLE );

        try
        {
            if ( settings.isOffline() )
            {
                getLogger().info( SystemWarnings.getOfflineWarning() );
    
                wagonManager.setOnline( false );
            }

            try
            {
                DefaultWagonManager wm = (DefaultWagonManager) wagonManager;
            
                String oldUserAgent = wm.getHttpUserAgent();
                int firstSpace = oldUserAgent == null ? -1 : oldUserAgent.indexOf( " " );
            
                StringBuffer buffer = new StringBuffer();
            
                buffer.append( "Apache-Maven/" );
            
                ArtifactVersion version = runtimeInformation.getApplicationVersion();
                if ( version != null )
                {
                    buffer.append( version.getMajorVersion() );
                    buffer.append( '.' );
                    buffer.append( version.getMinorVersion() );
                }
                else
                {
                    buffer.append( "unknown" );
                }
                
                buffer.append( ' ' );
                if ( firstSpace > -1 )
                {
                    buffer.append( oldUserAgent.substring( firstSpace + 1 ) );
                    buffer.append( ' ' );
                    buffer.append( oldUserAgent.substring( 0, firstSpace ) );
                }
                else
                {
                    buffer.append( oldUserAgent );
                }
                
                wm.setHttpUserAgent( buffer.toString() );
            }
            catch ( ClassCastException e )
            {
                // ignore
            }

            SecDispatcher sd = null;
        
            try
            {
                Proxy proxy = settings.getActiveProxy();
            
                try
                {
                    sd = (SecDispatcher) container.lookup( SecDispatcher.ROLE, "maven" );
                }
                catch (Exception e)
                {
                    getLogger().warn( "Security features are disabled. Cannot find plexus component "+SecDispatcher.ROLE + ":maven" );
                    
                    line();
                }
    
                if ( proxy != null )
                {
                    if ( proxy.getHost() == null )
                    {
                        throw new SettingsConfigurationException( "Proxy in settings.xml has no host" );
                    }
                    
                    String pass = proxy.getPassword();
                    
                    if ( sd != null )
                    {
                        try
                        {
                            pass = sd.decrypt( pass );
                        }
                        catch ( SecDispatcherException e )
                        {
                            reportSecurityConfigurationError( "password for proxy '" + proxy.getId() + "'", e );
                        }
                    }
    
                    wagonManager.addProxy( proxy.getProtocol(), proxy.getHost(), proxy.getPort(), proxy.getUsername(),
                                           pass, proxy.getNonProxyHosts() );
                }
                
                for ( Iterator i = settings.getServers().iterator(); i.hasNext(); )
                {
                    Server server = (Server) i.next();
                    
                    String passWord = server.getPassword();
    
                    if ( sd != null )
                    {
                        try
                        {
                            passWord = sd.decrypt( passWord );
                        }
                        catch ( SecDispatcherException e )
                        {
                            reportSecurityConfigurationError( "password for server '" + server.getId() + "'", e );
                        }
                    }
                    
                    String passPhrase = server.getPassphrase();
    
                    if ( sd != null )
                    {
                        try
                        {
                            passPhrase = sd.decrypt( passPhrase );
                        }
                        catch ( SecDispatcherException e )
                        {
                            reportSecurityConfigurationError( "passphrase for server '" + server.getId() + "'", e );
                        }
                    }
    
                    wagonManager.addAuthenticationInfo( server.getId(), server.getUsername(), passWord,
                                                        server.getPrivateKey(), passPhrase );
    
                    wagonManager.addPermissionInfo( server.getId(), server.getFilePermissions(),
                                                    server.getDirectoryPermissions() );
    
                    if ( server.getConfiguration() != null )
                    {
                        wagonManager.addConfiguration( server.getId(), (Xpp3Dom) server.getConfiguration() );
                    }
                }
    
                for ( Iterator i = settings.getMirrors().iterator(); i.hasNext(); )
                {
                    Mirror mirror = (Mirror) i.next();
    
                    wagonManager.addMirror( mirror.getId(), mirror.getMirrorOf(), mirror.getUrl() );
                }
            }
            finally
            {
                if ( sd != null )
                {
                    container.release( sd );
                }
            }
        }
        finally
        {
            container.release( wagonManager );
        }
        
        WagonProviderMapping mapping = (WagonProviderMapping) container.lookup( WagonProviderMapping.ROLE );
        try
        {
            // set defaults
            mapping.setWagonProvider( "http", "lightweight" );
            mapping.setWagonProvider( "https", "lightweight" );

            for ( Object k: executionProperties.keySet() )
            {
                String key = (String) k;
                if ( key.startsWith( "maven.wagon.provider." ) )
                {
                    String provider = executionProperties.getProperty( key );
                    key = key.substring( "maven.wagon.provider.".length() );
                    
                    mapping.setWagonProvider( key, provider );
                }
            }
        }
        finally
        {
            container.release( mapping );
        }

        // Would be better in settings.xml, but it is not extensible yet
        String numThreads = System.getProperty( "maven.artifact.threads" );
        if ( numThreads != null )
        {
            int threads = 0;
            try
            {
                threads = Integer.valueOf( numThreads ).intValue();

                if ( threads < 1 )
                {
                    getLogger().warn( "Invalid number of threads '" + threads + "' will be ignored" );
                }
            }
            catch ( NumberFormatException e )
            {
                getLogger().warn( "Invalid number of threads '" + numThreads + "' will be ignored: " + e.getMessage() );
            }
            
            if ( threads > 0 )
            {
                DefaultArtifactResolver artifactResolver = (DefaultArtifactResolver) container.lookup( ArtifactResolver.ROLE );
                try
                {
                    artifactResolver.configureNumberOfThreads( threads );
                    getLogger().debug( "Resolution thread pool size set to: " + threads );
                }
                finally
                {
                    container.release( artifactResolver );
                }
            }
        }
    }

    private void reportSecurityConfigurationError( String affectedConfiguration, SecDispatcherException e )
    {
        Throwable cause = e;

        String msg = "Not decrypting " + affectedConfiguration + " due to exception in security handler.";

        // Drop to the actual cause, it wraps multiple times
        while ( cause.getCause() != null )
        {
            cause = cause.getCause();
        }

        // common cause is missing settings-security.xml
        if ( cause instanceof FileNotFoundException )
        {
            msg += "\nEnsure that you have configured your master password file (and relocation if appropriate)\nSee the installation instructions for details.";
        }

        getLogger().warn( msg + "\nCause: " + cause.getMessage() );
        getLogger().debug( "Full trace follows", e );
    }

    // ----------------------------------------------------------------------
    // Lifecylce Management
    // ----------------------------------------------------------------------

    public void contextualize( Context context )
        throws ContextException
    {
        container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
    }

    // ----------------------------------------------------------------------
    // Reporting / Logging
    // ----------------------------------------------------------------------

    protected void logFatal( Throwable error )
    {
        line();

        getLogger().error( "FATAL ERROR" );

        line();

        logDiagnostics( error );

        logTrace( error, true );
    }

    protected void logError( Exception e, boolean showErrors )
    {
        line();

        getLogger().error( "BUILD ERROR" );

        line();

        logDiagnostics( e );

        logTrace( e, showErrors );

        if ( !showErrors )
        {
            getLogger().info( "For more information, run Maven with the -e switch" );

            line();
        }
    }

    protected void logFailure( BuildFailureException e, boolean showErrors )
    {
        line();

        getLogger().error( "BUILD FAILURE" );

        line();

        logDiagnostics( e );

        logTrace( e, showErrors );

        if ( !showErrors )
        {
            getLogger().info( "For more information, run Maven with the -e switch" );

            line();
        }
    }

    private void logTrace( Throwable t, boolean showErrors )
    {
        if ( getLogger().isDebugEnabled() )
        {
            getLogger().debug( "Trace", t );

            line();
        }
        else if ( showErrors )
        {
            getLogger().info( "Trace", t );

            line();
        }
    }

    private void logDiagnostics( Throwable t )
    {
        String message = null;
        if ( errorDiagnostics != null )
        {
            message = errorDiagnostics.diagnose( t );
        }

        if ( message == null )
        {
            message = t.getMessage();
        }

        getLogger().info( message );

        line();
    }

    protected void logSuccess( ReactorManager rm )
    {
        line();

        getLogger().info( "BUILD SUCCESSFUL" );

        line();
    }

    private void logReactorSummary( ReactorManager rm )
    {
        if ( rm.hasMultipleProjects() && rm.executedMultipleProjects() )
        {
            getLogger().info( "" );
            getLogger().info( "" );

            // -------------------------
            // Reactor Summary:
            // -------------------------
            // o project-name...........FAILED
            // o project2-name..........SKIPPED (dependency build failed or was skipped)
            // o project-3-name.........SUCCESS

            line();
            getLogger().info( "Reactor Summary:" );
            line();

            for ( Iterator it = rm.getSortedProjects().iterator(); it.hasNext(); )
            {
                MavenProject project = (MavenProject) it.next();

                if ( rm.hasBuildFailure( project ) )
                {
                    logReactorSummaryLine( project.getName(), "FAILED", rm.getBuildFailure( project ).getTime() );
                }
                else if ( rm.isBlackListed( project ) )
                {
                    logReactorSummaryLine( project.getName(), "SKIPPED (dependency build failed or was skipped)" );
                }
                else if ( rm.hasBuildSuccess( project ) )
                {
                    logReactorSummaryLine( project.getName(), "SUCCESS", rm.getBuildSuccess( project ).getTime() );
                }
                else
                {
                    logReactorSummaryLine( project.getName(), "NOT BUILT" );
                }
            }
            line();
        }
    }

    protected void stats( Date start )
    {
        Date finish = new Date();

        long time = finish.getTime() - start.getTime();

        getLogger().info( "Total time: " + formatTime( time ) );

        getLogger().info( "Finished at: " + finish );

        //noinspection CallToSystemGC
        System.gc();

        Runtime r = Runtime.getRuntime();

        getLogger().info(
            "Final Memory: " + ( r.totalMemory() - r.freeMemory() ) / MB + "M/" + r.totalMemory() / MB + "M" );
    }

    protected void line()
    {
        getLogger().info( "------------------------------------------------------------------------" );
    }

    protected static String formatTime( long ms )
    {
        long secs = ms / MS_PER_SEC;

        long min = secs / SEC_PER_MIN;

        secs = secs % SEC_PER_MIN;

        String msg = "";

        if ( min > 1 )
        {
            msg = min + " minutes ";
        }
        else if ( min == 1 )
        {
            msg = "1 minute ";
        }

        if ( secs > 1 )
        {
            msg += secs + " seconds";
        }
        else if ( secs == 1 )
        {
            msg += "1 second";
        }
        else if ( min == 0 )
        {
            msg += "< 1 second";
        }
        return msg;
    }

    private List getProjectFiles( MavenExecutionRequest request )
        throws IOException
    {
        List files = Collections.EMPTY_LIST;

        File userDir = new File( System.getProperty( "user.dir" ) );
        if ( request.isReactorActive() )
        {
            // TODO: should we now include the pom.xml in the current directory?
//            String includes = System.getProperty( "maven.reactor.includes", "**/" + POMv4 );
//            String excludes = System.getProperty( "maven.reactor.excludes", POMv4 );

            String includes = System.getProperty( "maven.reactor.includes", "**/" + POMv4 + ",**/" + RELEASE_POMv4 );
            String excludes = System.getProperty( "maven.reactor.excludes", POMv4 + "," + RELEASE_POMv4 );

            files = FileUtils.getFiles( userDir, includes, excludes );

            filterOneProjectFilePerDirectory( files );

            // make sure there is consistent ordering on all platforms, rather than using the filesystem ordering
            Collections.sort( files );
        }
        else if ( request.getPomFile() != null )
        {
            File projectFile = new File( request.getPomFile() ).getAbsoluteFile();

            if ( projectFile.exists() )
            {
                files = Collections.singletonList( projectFile );
            }
        }
        else
        {
            File projectFile = new File( userDir, RELEASE_POMv4 );

            if ( !projectFile.exists() )
            {
                projectFile = new File( userDir, POMv4 );
            }

            if ( projectFile.exists() )
            {
                files = Collections.singletonList( projectFile );
            }
        }

        return files;
    }

    private void filterOneProjectFilePerDirectory( List files )
    {
        List releaseDirs = new ArrayList();

        for ( Iterator it = files.iterator(); it.hasNext(); )
        {
            File projectFile = (File) it.next();

            if ( RELEASE_POMv4.equals( projectFile.getName() ) )
            {
                releaseDirs.add( projectFile.getParentFile() );
            }
        }

        for ( Iterator it = files.iterator(); it.hasNext(); )
        {
            File projectFile = (File) it.next();

            // remove pom.xml files where there is a sibling release-pom.xml file...
            if ( !RELEASE_POMv4.equals( projectFile.getName() ) && releaseDirs.contains( projectFile.getParentFile() ) )
            {
                it.remove();
            }
        }
    }
}
