/*
 * Copyright (C) 2007-2008 JVending Masa
 * Copyright (C) 2011 Jayway AB
 *
 * Licensed 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.
 */
package com.simpligility.maven.plugins.android.standalonemojos;

import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.FileListingService;
import com.android.ddmlib.FileListingService.FileEntry;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.SyncException;
import com.android.ddmlib.SyncService;
import com.android.ddmlib.TimeoutException;
import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
import com.simpligility.maven.plugins.android.DeviceCallback;
import com.simpligility.maven.plugins.android.common.DeviceHelper;
import com.simpligility.maven.plugins.android.common.LogSyncProgressMonitor;
import com.simpligility.maven.plugins.android.config.ConfigHandler;
import com.simpligility.maven.plugins.android.config.ConfigPojo;
import com.simpligility.maven.plugins.android.config.PullParameter;
import com.simpligility.maven.plugins.android.configuration.Pull;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import java.io.File;
import java.io.IOException;

/**
 * Copy file or directory from all the attached (or specified)
 * devices/emulators.
 *
 * @author Manfred Moser - manfred@simpligility.com
 */
@Mojo( name = "pull", requiresProject = false )
public class PullMojo extends AbstractAndroidMojo
{

    /**
     *
The configuration for the pull goal can be set up in the plugin configuration in the pom file as:

     *

     * <pull>
     *     <source>path</source>
     *     <destination>path</destination>
     * </pull>
     *

     *
The parameters can also be configured as property in the pom or settings file
     *

     * <properties>
     *     <android.pull.source>pathondevice</android.pull.source>
     *     <android.pull.destination>path</android.pull.destination>
     * </properties>
     *

     * or from command-line with parameter -Dandroid.pull.source=path
     * and -Dandroid.pull.destination=path.

     */
    @Parameter
    @ConfigPojo
    private Pull pull;

    /**
     * The path of the source file or directory on the emulator/device.
     */
    @Parameter( property = "android.pull.source" )
    private String pullSource;

    @PullParameter( required = true )
    private String parsedSource;

    /**
     * The path of the destination to copy the file to.
     *
     * If destination ends with {@link File#separator}, it is supposed to be a
     * directory. Therefore the source - whether it refers to a file or
     * directory - will be copied into the destination directory.
     *
     * If destination does not end with {@link File#separator}, the last path
     * segment will be assumed as the new file or directory name (depending on
     * the type of source).
     *
     * Any missing directories will be created.
     */
    @Parameter( property = "android.pull.destination" )
    private String pullDestination;

    @PullParameter( required = true )
    private String parsedDestination;

    public void execute() throws MojoExecutionException, MojoFailureException
    {

        ConfigHandler configHandler = new ConfigHandler( this, this.session, this.execution );
        configHandler.parseConfiguration();

        doWithDevices( new DeviceCallback()
        {
            public void doWithDevice( final IDevice device ) throws MojoExecutionException
            {
                String deviceLogLinePrefix = DeviceHelper.getDeviceLogLinePrefix( device );

                // message will be set later according to the processed files
                String message = "";
                try
                {
                    SyncService syncService = device.getSyncService();
                    FileListingService fileListingService = device.getFileListingService();

                    FileEntry sourceFileEntry = getFileEntry( parsedSource, fileListingService );

                    if ( sourceFileEntry.isDirectory() )
                    {
                        // pulling directory
                        File destinationDir = new File( parsedDestination );
                        if ( ! destinationDir.exists() )
                        {
                            getLog().info( "Creating destination directory " + destinationDir );
                            destinationDir.mkdirs();
                            destinationDir.mkdir();
                        }
                        String destinationDirPath = destinationDir.getAbsolutePath();

                        FileEntry[] fileEntries;
                        if ( parsedDestination.endsWith( File.separator ) )
                        {
                            // pull source directory directly
                            fileEntries = new FileEntry[]{ sourceFileEntry };
                        }
                        else
                        {
                            // pull the children of source directory only
                            fileEntries = fileListingService.getChildren( sourceFileEntry, true, null );
                        }

                        message = deviceLogLinePrefix + "Pull of " + parsedSource + " to " + destinationDirPath
                                + " from ";

                        syncService.pull( fileEntries, destinationDirPath, new LogSyncProgressMonitor( getLog() ) );
                    }
                    else
                    {
                        // pulling file
                        File parentDir = new File( FilenameUtils.getFullPath( parsedDestination ) );
                        if ( ! parentDir.exists() )
                        {
                            getLog().info( deviceLogLinePrefix + "Creating destination directory " + parentDir );
                            parentDir.mkdirs();
                        }

                        String destinationFileName;
                        if ( parsedDestination.endsWith( File.separator ) )
                        {
                            // keep original filename
                            destinationFileName = FilenameUtils.getName( parsedSource );
                        }
                        else
                        {
                            // rename filename
                            destinationFileName = FilenameUtils.getName( parsedDestination );
                        }

                        File destinationFile = new File( parentDir, destinationFileName );
                        String destinationFilePath = destinationFile.getAbsolutePath();
                        message = deviceLogLinePrefix + "Pull of " + parsedSource + " to " + destinationFilePath
                                + " from " + DeviceHelper.getDescriptiveName( device );

                        syncService.pullFile( sourceFileEntry, destinationFilePath,
                                new LogSyncProgressMonitor( getLog() ) );
                    }

                    getLog().info( message + " successful." );
                }
                catch ( SyncException e )
                {
                    throw new MojoExecutionException( message + " failed.", e );
                }
                catch ( IOException e )
                {
                    throw new MojoExecutionException( message + " failed.", e );
                }
                catch ( TimeoutException e )
                {
                    throw new MojoExecutionException( message + " failed.", e );
                }
                catch ( AdbCommandRejectedException e )
                {
                    throw new MojoExecutionException( message + " failed.", e );
                }
            }
        } );
    }

    /**
     * Retrieves the corresponding {@link FileEntry} on the emulator/device for
     * a given file path.
     *
     * If the file path starts with the symlink "sdcard", it will be resolved
     * statically to "/mnt/sdcard".
     *
     * @param filePath           path to file or directory on device or emulator
     * @param fileListingService {@link FileListingService} for retrieving the
     *                           {@link FileEntry}
     * @return a {@link FileEntry} object for the given file path
     * @throws MojoExecutionException if the file path could not be found on the device
     */
    private FileEntry getFileEntry( String filePath, FileListingService fileListingService )
            throws MojoExecutionException
    {
        // static resolution of symlink
        if ( filePath.startsWith( "/sdcard" ) )
        {
            filePath = "/mnt" + filePath;
        }

        String[] destinationPathSegments = StringUtils.split( filePath, "/" );

        FileEntry fileEntry = fileListingService.getRoot();
        for ( String destinationPathSegment : destinationPathSegments )
        {
            // build up file listing cache
            fileListingService.getChildren( fileEntry, true, null );

            fileEntry = fileEntry.findChild( destinationPathSegment );
            if ( fileEntry == null )
            {
                throw new MojoExecutionException( "Cannot execute goal: " + filePath + " does not exist on device." );
            }
        }
        return fileEntry;
    }
}


/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * Licensed 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.
 */

package com.simpligility.maven.plugins.android.common;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.repackaged.com.google.common.base.Preconditions;
import com.google.api.client.repackaged.com.google.common.base.Strings;
import com.google.api.services.androidpublisher.AndroidPublisher;
import com.google.api.services.androidpublisher.AndroidPublisherScopes;

import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

import javax.annotation.Nullable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Helper class to initialize the publisher APIs client library.
 * <p>
 * Before making any calls to the API through the client library you need to
 * call the {@link AndroidPublisherHelper#init(String)} method. This will run
 * all precondition checks for for client id and secret setup properly in
 * resources/client_secrets.json and authorize this client against the API.
 * </p>
 */
public class AndroidPublisherHelper
{

    private static final Log LOG = LogFactory.getLog( AndroidPublisherHelper.class );

    public static final String MIME_TYPE_APK = "application/vnd.android.package-archive";

    public static final String MIME_TYPE_IMAGE = "image/*";

    /** Path to the private key file (only used for Service Account auth). */
    private static final String SRC_RESOURCES_KEY_P12 = "resources/key.p12";

    /**
     * Path to the client secrets file (only used for Installed Application
     * auth).
     */
    private static final String RESOURCES_CLIENT_SECRETS_JSON = "/resources/client_secrets.json";

    /**
     * Directory to store user credentials (only for Installed Application
     * auth).
     */
    private static final String DATA_STORE_SYSTEM_PROPERTY = "user.home";
    private static final String DATA_STORE_FILE = ".store/android_publisher_api";
    private static final File DATA_STORE_DIR =
            new File( System.getProperty( DATA_STORE_SYSTEM_PROPERTY ), DATA_STORE_FILE );

    /** Global instance of the JSON factory. */
    private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();

    /** Global instance of the HTTP transport. */
    private static HttpTransport httpTransport;

    /** Installed application user ID. */
    private static final String INST_APP_USER_ID = "user";

    private static Credential authorizeWithServiceAccount( String serviceAccountEmail )
            throws GeneralSecurityException, IOException
    {
        LOG.info( String.format( "Authorizing using Service Account: %s", serviceAccountEmail ) );

        return authorizeWithServiceAccount( serviceAccountEmail, null );
    }

    private static Credential authorizeWithServiceAccount( String serviceAccountEmail, File pk12File )
            throws GeneralSecurityException, IOException
    {
        LOG.info( String.format( "Authorizing using Service Account: %s", serviceAccountEmail ) );

        // Build service account credential.
        GoogleCredential credential = new GoogleCredential.Builder()
                .setTransport( httpTransport )
                .setJsonFactory( JSON_FACTORY )
                .setServiceAccountId( serviceAccountEmail )
                .setServiceAccountScopes( Collections.singleton( AndroidPublisherScopes.ANDROIDPUBLISHER ) )
                .setServiceAccountPrivateKeyFromP12File( pk12File == null ? new File( SRC_RESOURCES_KEY_P12 )
                        : pk12File )
                .build();
        return credential;
    }

    /**
     * Ensure the client secrets file has been filled out.
     *
     * @param clientSecrets the GoogleClientSecrets containing data from the
     *            file
     */
    private static void checkClientSecretsFile( GoogleClientSecrets clientSecrets )
    {
        if ( clientSecrets.getDetails().getClientId().startsWith( "[[INSERT" )
                || clientSecrets.getDetails().getClientSecret().startsWith( "[[INSERT" ) )
        {
            LOG.error( "Enter Client ID and Secret from "
                    + "APIs console into resources/client_secrets.json." );
            System.exit( 1 );
        }
    }

    /**
     * Performs all necessary setup steps for running requests against the API
     * using the Installed Application auth method.
     *
     * @param applicationName the name of the application: com.example.app
     * @return the {@Link AndroidPublisher} service
     */
    public static AndroidPublisher init( String applicationName ) throws Exception
    {
        return init( applicationName, null );
    }

    /**
     * Performs all necessary setup steps for running requests against the API.
     *
     * @param applicationName the name of the application: com.example.app
     * @param serviceAccountEmail the Service Account Email (empty if using
     *            installed application)
     * @return the {@Link AndroidPublisher} service
     * @throws GeneralSecurityException
     * @throws IOException
     */
    public static AndroidPublisher init( String applicationName,
            @Nullable String serviceAccountEmail ) throws IOException, GeneralSecurityException
    {
        Preconditions.checkArgument( !Strings.isNullOrEmpty( applicationName ),
                "applicationName cannot be null or empty!" );

        // Authorization.
        newTrustedTransport();
        Credential credential;
        credential = authorizeWithServiceAccount( serviceAccountEmail );

        // Set up and return API client.
        return new AndroidPublisher.Builder(
                httpTransport, JSON_FACTORY, credential ).setApplicationName( applicationName )
                .build();
    }

    /**
     * Performs all necessary setup steps for running requests against the API.
     *
     * @param serviceAccountEmail the Service Account Email (empty if using
     *            installed application)
     * @return the {@Link AndroidPublisher} service
     * @throws GeneralSecurityException
     * @throws IOException
     */
    public static AndroidPublisher init( String applicationName, String serviceAccountEmail, File pk12File )
            throws IOException, GeneralSecurityException
    {

        // Authorization.
        newTrustedTransport();
        Credential credential = authorizeWithServiceAccount( serviceAccountEmail, pk12File );

        // Set up and return API client.
        return new AndroidPublisher.Builder( httpTransport, JSON_FACTORY, credential )
                .setApplicationName( applicationName )
                .build();
    }

    private static void newTrustedTransport() throws GeneralSecurityException,
            IOException
    {
        if ( null == httpTransport )
        {
            httpTransport = GoogleNetHttpTransport.newTrustedTransport();
        }
    }

}


/*******************************************************************************
 * Copyright (c) 2008, 2011 Sonatype Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Sonatype Inc. - initial API and implementation
 *******************************************************************************/
package com.simpligility.maven.plugins.android.phase_prebuild;

import com.simpligility.maven.plugins.android.common.AndroidExtension;
import com.simpligility.maven.plugins.android.common.ArtifactResolverHelper;
import com.simpligility.maven.plugins.android.common.DependencyResolver;
import com.simpligility.maven.plugins.android.common.PomConfigurationHelper;
import com.simpligility.maven.plugins.android.common.UnpackedLibHelper;
import org.apache.maven.AbstractMavenLifecycleParticipant;
import org.apache.maven.MavenExecutionException;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * Adds classes from AAR and APK dependencies to the project compile classpath.
 *
 * @author William Ferguson
 * @author Benoit Billington
 * @author Manfred Moser
 */
@Component( role = AbstractMavenLifecycleParticipant.class, hint = "default" )
public final class ClasspathModifierLifecycleParticipant extends AbstractMavenLifecycleParticipant
{
    /**
     * Mojo configuration parameter to determine if jar files found inside an apklib are
     * pulled onto the classpath and into the resulting apk, defaults to false
     * @see INCLUDE_FROM_APKLIB_DEFAULT
     */
    private static final String INCLUDE_FROM_APKLIB_PARAM = "includeLibsJarsFromApklib";
    /**
     * Mojo configuration parameter to determine if jar files found inside an aar are
     * pulled onto the classpath and into the resulting apk, defaults to false
     * @see INCLUDE_FROM_AAR_DEFAULT
     */
    private static final String INCLUDE_FROM_AAR_PARAM = "includeLibsJarsFromAar";
    /**
     * Mojo configuration parameter to determine if we should warn about dependency conflicts with the provided
     * dependencies.
     *
     * @see DISABLE_CONFLICTING_DEPENDENCIES_WARNING_DEFAULT
     */
    private static final String DISABLE_CONFLICTING_DEPENDENCIES_WARNING_PARAM
        = "disableConflictingDependenciesWarning";
    private static final boolean INCLUDE_FROM_APKLIB_DEFAULT = false;
    private static final boolean INCLUDE_FROM_AAR_DEFAULT = true;
    private static final boolean DISABLE_CONFLICTING_DEPENDENCIES_WARNING_DEFAULT = false;

    /**
     * Mojo configuration parameter that defines where AAR files should be unpacked.
     * Default is /target/unpacked-libs
     */
    private static final String UNPACKED_LIBS_FOLDER_PARAM = "unpackedLibsFolder";

    @Requirement
    private ArtifactResolver artifactResolver;

    @Requirement( hint = "default" )
    private DependencyGraphBuilder dependencyGraphBuilder;

    @Requirement
    private Logger log;

    private boolean addedJarFromLibs = false;

    @Override
    public void afterProjectsRead( MavenSession session ) throws MavenExecutionException
    {
        log.debug( "" );
        log.debug( "ClasspathModifierLifecycleParticipant#afterProjectsRead - start" );
        log.debug( "" );

        log.debug( "CurrentProject=" + session.getCurrentProject() );
        final List<MavenProject> projects = session.getProjects();
        final DependencyResolver dependencyResolver = new DependencyResolver( log, dependencyGraphBuilder );
        final ArtifactResolverHelper artifactResolverHelper = new ArtifactResolverHelper( artifactResolver, log );

        for ( MavenProject project : projects )
        {
            log.debug( "" );
            log.debug( "project=" + project.getArtifact() );

            if ( ! AndroidExtension.isAndroidPackaging( project.getPackaging() ) )
            {
                continue; // do not modify classpath if not an android project.
            }

            final String unpackedLibsFolder
                = getMojoConfigurationParameter( project, UNPACKED_LIBS_FOLDER_PARAM, null );
            final UnpackedLibHelper helper = new UnpackedLibHelper( artifactResolverHelper, project, log,
                    unpackedLibsFolder == null ? null : new File( unpackedLibsFolder )
            );

            final Set<Artifact> artifacts;

            // If there is an extension ClassRealm loaded for this project then use that
            // as the ContextClassLoader so that Wagon extensions can be used to resolves dependencies.
            final ClassLoader projectClassLoader = ( project.getClassRealm() != null )
                    ? project.getClassRealm()
                    : Thread.currentThread().getContextClassLoader();

            final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
            try
            {
                Thread.currentThread().setContextClassLoader( projectClassLoader );
                artifacts = dependencyResolver.getProjectDependenciesFor( project, session );
            }
            catch ( DependencyGraphBuilderException e )
            {
                // Nothing to do. The resolution failure will be displayed by the standard resolution mechanism.
                continue;
            }
            finally
            {
                Thread.currentThread().setContextClassLoader( originalClassLoader );
            }

            boolean includeFromAar = getMojoConfigurationParameter( project, INCLUDE_FROM_AAR_PARAM,
                    INCLUDE_FROM_AAR_DEFAULT );
            boolean includeFromApklib = getMojoConfigurationParameter( project, INCLUDE_FROM_APKLIB_PARAM,
                    INCLUDE_FROM_APKLIB_DEFAULT );
            boolean disableConflictingDependenciesWarning = getMojoConfigurationParameter( project,
                    DISABLE_CONFLICTING_DEPENDENCIES_WARNING_PARAM, DISABLE_CONFLICTING_DEPENDENCIES_WARNING_DEFAULT );

            log.debug( "projects deps: : " + artifacts );

            if ( !disableConflictingDependenciesWarning )
            {
                ProvidedDependencyChecker checker = new ProvidedDependencyChecker();
                checker.checkProvidedDependencies( artifacts, log );
            }

            for ( Artifact artifact : artifacts )
            {
                final String type = artifact.getType();
                if ( type.equals( AndroidExtension.AAR ) )
                {
                    // An AAR lib contains a classes jar that needs to be added to the classpath.
                    // Create a placeholder classes.jar and add it to the compile classpath.
                    // It will replaced with the real classes.jar by GenerateSourcesMojo.
                    addClassesToClasspath( helper, project, artifact );

                    // An AAR may also contain zero or more internal libs in the libs folder.
                    // If 'includeLibsJarsFromAar' config param is true then include them too.
                    if ( includeFromAar )
                    {
                        // Add jar files in 'libs' into classpath.
                        addLibsJarsToClassPath( helper, project, artifact );
                    }
                }
                else if ( type.equals( AndroidExtension.APK ) )
                {
                    // The only time that an APK will likely be a dependency is when this an an APK test project.
                    // So add a placeholder (we cannot resolve the actual dep pre build) to the compile classpath.
                    // The placeholder will be replaced with the real APK jar later.
                    addClassesToClasspath( helper, project, artifact );
                }
                else if ( type.equals( AndroidExtension.APKLIB ) )
                {
                    if ( includeFromApklib )
                    {
                      // Add jar files in 'libs' into classpath.
                      addLibsJarsToClassPath( helper, project, artifact );
                    }
                }
            }
        }

        if ( addedJarFromLibs )
        {
            log.warn(
                    "Transitive dependencies should really be provided by Maven dependency management.\n"
        + "          We suggest you to ask the above providers to package their component properly.\n"
        + "          Things may break at compile and/or runtime due to multiple copies of incompatible libraries." );
        }
        log.debug( "" );
        log.debug( "ClasspathModifierLifecycleParticipant#afterProjectsRead - finish" );
    }

    private String getMojoConfigurationParameter( MavenProject project, String name, String defaultValue )
    {
        String value = PomConfigurationHelper.getPluginConfigParameter( project,
                name, defaultValue );
        log.debug( name + " set to " + value );
        return value;
    }

    private boolean getMojoConfigurationParameter( MavenProject project, String name, boolean defaultValue )
    {
        return Boolean.valueOf( getMojoConfigurationParameter( project, name, Boolean.toString( defaultValue ) ) );
    }

    /**
     * Add jar files in libs into the project classpath.
     */
    private void addLibsJarsToClassPath( UnpackedLibHelper helper, MavenProject project, Artifact artifact )
        throws MavenExecutionException
    {
         try
         {
             final File unpackLibFolder = helper.getUnpackedLibFolder( artifact );
             final File artifactFile = helper.getArtifactToFile( artifact );
             ZipFile zipFile = new ZipFile( artifactFile );
             Enumeration enumeration = zipFile.entries();
             while ( enumeration.hasMoreElements() )
             {
                 ZipEntry entry = ( ZipEntry )  enumeration.nextElement();
                 String entryName = entry.getName();

                 // Only jar files under 'libs' directory to be processed.
                 if ( Pattern.matches( "^libs/.+\\.jar$", entryName ) )
                 {
                     final File libsJarFile = new File( unpackLibFolder, entryName );
                     log.warn( "Adding jar from libs folder to classpath: " + libsJarFile );

                     // In order to satisfy the LifecycleDependencyResolver on execution up to a phase that
                     // has a Mojo requiring dependency resolution I need to create a dummy classesJar here.
                     if ( !libsJarFile.getParentFile().exists() )
                     {
                         libsJarFile.getParentFile().mkdirs();
                     }
                     libsJarFile.createNewFile();

                     // Add the jar to the classpath.
                     final Dependency dependency =
                            createSystemScopeDependency( artifact, libsJarFile, libsJarFile.getName() );

                     project.getModel().addDependency( dependency );
                     addedJarFromLibs = true;
                 }
             }
         }
         catch ( MojoExecutionException e )
         {
             log.debug( "Error extract jars" );
         }
         catch ( ZipException e )
         {
             log.debug( "Error" );
         }
         catch ( IOException e )
         {
             log.debug( "Error" );
         }
    }

    /**
     * Add the dependent library classes to the project classpath.
     */
    private void addClassesToClasspath( UnpackedLibHelper helper, MavenProject project, Artifact artifact )
        throws MavenExecutionException
    {
        // Work out where the dep will be extracted and calculate the file path to the classes jar.
        // This is location where the GenerateSourcesMojo will extract the classes.
        final File classesJar = helper.getUnpackedClassesJar( artifact );
        log.debug( "Adding to classpath : " + classesJar );

        // In order to satisfy the LifecycleDependencyResolver on execution up to a phase that
        // has a Mojo requiring dependency resolution I need to create a dummy classesJar here.
        if ( !classesJar.exists() )
        {
            classesJar.getParentFile().mkdirs();
            try
            {
                final ZipOutputStream zipOutputStream = new ZipOutputStream( new FileOutputStream( classesJar ) );
                zipOutputStream.putNextEntry( new ZipEntry( "dummy" ) );
                zipOutputStream.close();
                log.debug( "Created dummy " + classesJar.getName() + " exist=" + classesJar.exists() );
            }
            catch ( IOException e )
            {
                throw new MavenExecutionException( "Could not add " + classesJar.getName() + " as dependency", e );
            }
        }

        // Modify the classpath to use an extracted dex file.  This will overwrite
        // any exisiting dependencies with the same information.
        final Dependency dependency = createSystemScopeDependency( artifact, classesJar, null );
        final Dependency providedJar = findProvidedDependencies( dependency, project );
        if ( providedJar != null )
        {
            project.getModel().removeDependency( providedJar );
        }
        project.getModel().addDependency( dependency );
    }

    private Dependency createSystemScopeDependency( Artifact artifact, File location, String suffix )
    {
        String artifactId = artifact.getArtifactId();
        if ( suffix != null )
        {
            artifactId += "_" + suffix;
        }
        final Dependency dependency = new Dependency();
        dependency.setGroupId( artifact.getGroupId() );
        dependency.setArtifactId( artifactId );
        dependency.setVersion( artifact.getBaseVersion() );
        dependency.setScope( Artifact.SCOPE_SYSTEM );
        dependency.setSystemPath( location.getAbsolutePath() );
        return dependency;
    }

    private Dependency findProvidedDependencies( Dependency dexDependency, MavenProject project )
    {
        for ( Dependency dependency : project.getDependencies() )
        {
            if ( dependency.getScope().equals( Artifact.SCOPE_PROVIDED ) )
            {
                if ( dependency.getArtifactId().equals( dexDependency.getArtifactId() )
                        && dependency.getGroupId().equals( dexDependency.getGroupId() )
                        && dependency.getType().equals( dexDependency.getType() )
                        && dependency.getVersion().equals( dexDependency.getVersion() ) )
                {
                    return dependency;
                }
            }
        }
        return null;

    }

}


package com.simpligility.maven.plugins.android.standalonemojos;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import com.android.tools.lint.LintCliClient;
import com.android.tools.lint.LintCliFlags;
import com.android.tools.lint.MultiProjectHtmlReporter;
import com.android.tools.lint.XmlReporter;
import com.android.tools.lint.checks.BuiltinIssueRegistry;
import com.android.tools.lint.client.api.IssueRegistry;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;

import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
import com.simpligility.maven.plugins.android.CommandExecutor;
import com.simpligility.maven.plugins.android.ExecutionException;
import com.simpligility.maven.plugins.android.config.ConfigHandler;
import com.simpligility.maven.plugins.android.config.ConfigPojo;
import com.simpligility.maven.plugins.android.config.PullParameter;
import com.simpligility.maven.plugins.android.configuration.Lint;

import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

/**
 * LintMojo can run the lint command against the project. Implements parsing parameters from pom or command line
 * arguments and sets useful defaults as well. Warning, if you use android.lint.enableClasspath and/or
 * android.lint.enableLibraries the behavior of this goal will vary depending on the phase where this goal is executed.
 * See android.lint.classpath/lintClassPath and android.lint.libraries/lintLibraries for more details.
 *
 * @author Stéphane Nicolas - snicolas@octo.com
 * @author Manfred Moser - manfred@simpligility.com
 */
@SuppressWarnings( "unused" )
@Mojo( name = "lint", requiresProject = false )
public class LintMojo extends AbstractAndroidMojo
{

    /**
     * The configuration for the lint goal. As soon as a lint goal is invoked the command will be executed unless the
     * skip parameter is set. A minimal configuration that will run lint and produce a XML report in
     * ${project.build.directory}/lint/lint-results.xml is
     *
     *

     * <lint>
     *     <skip>false</skip>
     * </lint>
     *

     *
     * Full configuration can use these parameters.
     *
     *

     * <lint>
     *     <failOnError>true|false</failOnError>
     *     <skip>true|false</skip>
     *     <ignoreWarnings>true|false</ignoreWarnings>
     *     <warnAll>true|false</warnAll>
     *     <warningsAsErrors>true|false</warningsAsErrors>
     *     <config></config>
     *     <fullPath>true|false</fullPath>
     *     <showAll>true|false</showAll>
     *     <disableSourceLines>true|false</disableSourceLines>
     *     <url>none|a=b</url>
     *     <enableHtml>true|false</enableHtml>
     *     <htmlOutputPath>${project.build.directory}/lint-results/lint-results-html/</htmlOutputPath>
     *     <enableSimpleHtml>true|false</enableSimpleHtml>
     *     <simpleHtmlOutputPath>${project.build.directory}/lint-results/lint-results-simple-html
     *     </simpleHtmlOutputPath>
     *     <enableXml>true|false</enableXml>
     *     <xmlOutputPath>${project.build.directory}/lint-results/lint-results.xml</xmlOutputPath>
     *     <enableSources>true|false</enableSources>
     *     <sources>${project.build.sourceDirecory}}</sources>
     *     <enableClasspath>true|false</enableClasspath>
     *     <classpath>${project.build.outputDirectory}</classpath>
     *     <enableLibraries>true|false</enableLibraries>
     *     <libraries></libraries>
     * </lint>
     *

     *
     *
     * Alternatively to the plugin configuration values can also be configured as properties on the command line as
     * android.lint.* or in pom or settings file as properties like lint*.
     */
    @Parameter
    @ConfigPojo
    private Lint lint;

    /**
     * Fail build on lint errors. Defaults to "false".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#failOnError
     */
    @Parameter( property = "android.lint.failOnError" )
    private Boolean lintFailOnError;

    @PullParameter( defaultValue = "false" )
    private Boolean parsedFailOnError;

    /**
     * Skip the lint goal execution. Defaults to "true".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#skip
     */
    @Parameter( property = "android.lint.skip" )
    private Boolean lintSkip;

    @PullParameter( defaultValue = "true" )
    private Boolean parsedSkip;

    // ---------------
    // Enabled Checks
    // ---------------

    /**
     * Only check for errors and ignore warnings. Defaults to "false".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#ignoreWarnings
     */
    @Parameter( property = "android.lint.ignoreWarning" )
    private Boolean lintIgnoreWarnings;

    @PullParameter( defaultValue = "false" )
    private Boolean parsedIgnoreWarnings;

    /**
     * Check all warnings, including those off by default. Defaults to "false".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#warnAll
     */
    @Parameter( property = "android.lint.warnAll" )
    private Boolean lintWarnAll;

    @PullParameter( defaultValue = "false" )
    private Boolean parsedWarnAll;

    /**
     * Report all warnings as errors. Defaults to "false".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#warningsAsErrors
     */
    @Parameter( property = "android.lint.warningsAsErrors" )
    private Boolean lintWarningsAsErrors;

    @PullParameter( defaultValue = "false" )
    private Boolean parsedWarningsAsErrors;

    /**
     * Use the given configuration file to determine whether issues are enabled or disabled. Defaults is "null" so no
     * config file will be used. To use the commonly used lint.xml in the project root set the parameter to
     * "${project.basedir}/lint.xml".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#config
     */
    @Parameter( property = "android.lint.config" )
    private String lintConfig;

    @PullParameter( defaultValue = "null" )
    private String parsedConfig;

    // ---------------
    // Enabled Checks
    // ---------------

    /**
     * Use full paths in the error output. Defaults to "false".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#fullPath
     */
    @Parameter( property = "android.lint.fullPath" )
    private Boolean lintFullPath;

    @PullParameter( defaultValue = "false" )
    private Boolean parsedFullPath;

    /**
     * Do not truncate long messages, lists of alternate locations, etc. Defaults to "true".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#showAll
     */
    @Parameter( property = "android.lint.showAll" )
    private Boolean lintShowAll;

    @PullParameter( defaultValue = "true" )
    private Boolean parsedShowAll;

    /**
     * Do not include the source file lines with errors in the output. By default, the error output includes snippets of
     * source code on the line containing the error, but this flag turns it off. Defaults to "false".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#disableSourceLines
     */
    @Parameter( property = "android.lint.disableSourceLines" )
    private Boolean lintDisableSourceLines;

    @PullParameter( defaultValue = "false" )
    private Boolean parsedDisableSourceLines;

    /**
     * Add links to HTML report, replacing local path prefixes with url prefix. The mapping can be a comma-separated
     * list of path prefixes to corresponding URL prefixes, such as C:\temp\Proj1=http://buildserver/sources/temp/Proj1.
     * To turn off linking to files, use --url none. Defaults to "none".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#url
     */
    @Parameter( property = "android.lint.url" )
    private String lintUrl;

    @PullParameter( defaultValue = "none" )
    private String parsedUrl;

    /**
     * Enable the creation of a HTML report. Defaults to "false".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#enableHtml
     */
    @Parameter( property = "android.lint.enableHtml" )
    private Boolean lintEnableHtml;

    @PullParameter( defaultValue = "false" )
    private Boolean parsedEnableHtml;

    /**
     * Path for the HTML report. If the filename is a directory (or a new filename without an extension), lint will
     * create a separate report for each scanned project. Defaults to ${project.build.directory}/lint/lint-html/.
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#htmlOutputPath
     */
    @Parameter( property = "android.lint.htmlOutputPath" )
    private String lintHtmlOutputPath;

    @PullParameter( defaultValueGetterMethod = "getHtmlOutputPath" )
    private String parsedHtmlOutputPath;

    /**
     * Enable the creation of a simple HTML report. Defaults to "false".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#enableSimpleHtml
     */
    @Parameter( property = "android.lint.enableSimpleHtml" )
    private Boolean lintEnableSimpleHtml;

    @PullParameter( defaultValue = "false" )
    private Boolean parsedEnableSimpleHtml;

    /**
     * Create a simple HTML report. If the filename is a directory (or a new filename without an extension), lint will
     * create a separate report for each scanned project. Defaults to ${project.build.directory}/lint/lint-simple-html/.
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#simpleHtmlOutputPath
     */
    @Parameter( property = "android.lint.simpleHtmlOutputPath" )
    private String lintSimpleHtmlOutputPath;

    @PullParameter( defaultValueGetterMethod = "getSimpleHtmlOutputPath" )
    private String parsedSimpleHtmlOutputPath;

    /**
     * Enable the creation of a XML report. Defaults to "true".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#enableXml
     */
    @Parameter( property = "android.lint.enableXml" )
    private Boolean lintEnableXml;

    @PullParameter( defaultValue = "true" )
    private Boolean parsedEnableXml;

    /**
     * Create an XML report. If the filename is a directory (or a new filename without an extension), lint will create a
     * separate report for each scanned project. Defaults to ${project.build.directory}/lint/lint-results.xml.
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#xmlOutputPath
     */
    @Parameter( property = "android.lint.xmlOutputPath" )
    private String lintXmlOutputPath;

    @PullParameter( defaultValueGetterMethod = "getXmlOutputPath" )
    private String parsedXmlOutputPath;

    // ---------------
    // Project Options
    // ---------------

    /**
     * Enable including sources into lint analysis. Defaults to "true".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#enableSources
     */
    @Parameter( property = "android.lint.enableSources" )
    private Boolean lintEnableSource;

    @PullParameter( defaultValue = "true" )
    private Boolean parsedEnableSources;

    /**
     * Add the given folder (or path) as a source directory for the project. Only valid when running lint on a single
     * project. Defaults to ${project.build.sourceDirectory}.
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#sources
     */
    @Parameter( property = "android.lint.sources" )
    private String lintSources;

    @PullParameter( defaultValueGetterMethod = "getSources" )
    private String parsedSources;

    /**
     * Enable including classpath into lint analysis. Defaults to "false".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#enableClasspath
     * @see com.simpligility.maven.plugins.android.configuration.Lint#classpath
     */
    @Parameter( property = "android.lint.enableSources" )
    private Boolean lintEnableClasspath;

    @PullParameter( defaultValue = "false" )
    private Boolean parsedEnableClasspath;

    /**
     * Add the given folder (or jar file, or path) as a class directory for the project. Only valid when running lint on
     * a single project. Defaults to ${project.build.outputDirectory}. Consequently, the lint output depends on the
     * phase during which this goal is executed, whether project has been compiled or not.
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#classpath
     */
    @Parameter( property = "android.lint.classpath" )
    private String lintClasspath;

    @PullParameter( defaultValueGetterMethod = "getClasspath" )
    private String parsedClasspath;

    /**
     * Enable including libraries into lint analysis. Defaults to "false".
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#enableLibraries
     * @see com.simpligility.maven.plugins.android.configuration.Lint#libraries
     */
    @Parameter( property = "android.lint.enableSources" )
    private Boolean lintEnableLibraries;

    @PullParameter( defaultValue = "false" )
    private Boolean parsedEnableLibraries;

    /**
     * Add the given folder (or jar file, or path) as a class library for the project. Only valid when running lint on a
     * single project. Defaults to all non provided resolved artifacts. Consequently, the lint output depends on the
     * phase during which this goal is executed, whether project's dependencies have been resolved or not.
     *
     * @see com.simpligility.maven.plugins.android.configuration.Lint#libraries
     */
    @Parameter( property = "android.lint.libraries" )
    private String lintLibraries;

    @PullParameter( defaultValueGetterMethod = "getLibraries" )
    private String parsedLibraries;

    @Parameter( property = "android.lint.legacy" )
    private Boolean legacy;

    @PullParameter( defaultValue = "true" )
    private Boolean parsedLegacy;

    @Parameter( property = "android.lint.quiet" )
    private Boolean quiet;

    @PullParameter( defaultValue = "true" )
    private Boolean parsedQuiet;

    /**
     * Execute the mojo by parsing the config and actually invoking the lint command from the Android SDK.
     *
     * @throws MojoExecutionException
     */
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException
    {
        ConfigHandler configHandler = new ConfigHandler( this, this.session, this.execution );
        configHandler.parseConfiguration();
        getLog().debug( "Parsed values for Android Lint invocation: " );
        getLog().debug( "failOnError:" + parsedFailOnError );
        getLog().debug( "skip:" + parsedSkip );
        getLog().debug( "legacy:" + parsedLegacy );
        getLog().debug( "quiet:" + parsedQuiet );
        getLog().debug( "ignoreWarnings:" + parsedIgnoreWarnings );
        getLog().debug( "warnAll:" + parsedWarnAll );
        getLog().debug( "warningsAsErrors:" + parsedWarningsAsErrors );
        getLog().debug( "config2:" + parsedConfig );

        getLog().debug( "fullPath:" + parsedFullPath );
        getLog().debug( "showAll:" + parsedShowAll );
        getLog().debug( "disableSourceLines:" + parsedDisableSourceLines );

        getLog().debug( "enablehtml: " + parsedEnableHtml );
        getLog().debug( "htmlOutputPath:" + parsedHtmlOutputPath );

        getLog().debug( "enableSimpleHtml: " + parsedEnableSimpleHtml );
        getLog().debug( "simpleHtmlOutputPath:" + parsedSimpleHtmlOutputPath );

        getLog().debug( "enableXml: " + parsedEnableXml );
        getLog().debug( "xmlOutputPath:" + parsedXmlOutputPath );

        getLog().debug( "sources:" + parsedSources );
        getLog().debug( "classpath:" + parsedClasspath );
        getLog().debug( "libraries:" + parsedLibraries );

        if ( parsedSkip )
        {
            getLog().info( "Skipping lint analysis." );
        }
        else
        {
            getLog().info( "Performing lint analysis." );

            if ( parsedLegacy )
            {
                getLog().info( "Using Lint from the Android SDK." );
                executeWhenConfigured();
            }
            else
            {
                getLog().info( "Using Lint dependency library." );
                runLint();
            }
        }
    }

    private void executeWhenConfigured() throws MojoExecutionException
    {
        CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
        executor.setLogger( this.getLog() );

        String command = getAndroidSdk().getLintPath();

        List< String > parameters = new ArrayList< String >();

        if ( isNotNullAndTrue( parsedIgnoreWarnings ) )
        {
            parameters.add( "-w" );
        }
        if ( isNotNullAndTrue( parsedWarnAll ) )
        {
            parameters.add( "-Wall" );
        }
        if ( isNotNullAndTrue( parsedWarningsAsErrors ) )
        {
            parameters.add( "-Werror" );
        }

        if ( isNotNullAndNotEquals( parsedConfig, "null" ) )
        {
            parameters.add( "--config" );
            parameters.add( parsedConfig );
        }

        if ( isNotNullAndTrue( parsedFullPath ) )
        {
            parameters.add( "--fullpath" );
        }
        if ( isNotNullAndTrue( parsedShowAll ) )
        {
            parameters.add( "--showall" );
        }
        if ( isNotNullAndTrue( parsedDisableSourceLines ) )
        {
            parameters.add( "--nolines" );
        }
        if ( isNotNullAndTrue( parsedEnableHtml ) )
        {
            parameters.add( "--html" );
            parameters.add( parsedHtmlOutputPath );
            getLog().info( "Writing Lint HTML report in " + parsedHtmlOutputPath );
        }
        if ( isNotNullAndNotEquals( parsedUrl, "none" ) )
        {
            parameters.add( "--url" );
            parameters.add( parsedUrl );
        }
        if ( isNotNullAndTrue( parsedEnableSimpleHtml ) )
        {
            parameters.add( "--simplehtml" );
            parameters.add( parsedSimpleHtmlOutputPath );
            getLog().info( "Writing Lint simple HTML report in " + parsedSimpleHtmlOutputPath );
        }
        if ( isNotNullAndTrue( parsedEnableXml ) )
        {
            parameters.add( "--xml" );
            parameters.add( parsedXmlOutputPath );
            getLog().info( "Writing Lint XML report in " + parsedXmlOutputPath );
        }
        if ( isNotNullAndTrue( parsedEnableSources ) )
        {
            parameters.add( "--sources" );
            parameters.add( parsedSources );
        }
        if ( isNotNullAndTrue( parsedEnableClasspath ) )
        {
            parameters.add( "--classpath" );
            parameters.add( parsedClasspath );
        }
        if ( isNotNullAndTrue( parsedEnableLibraries ) )
        {
            parameters.add( "--libraries" );
            parameters.add( parsedLibraries );
        }

        parameters.add( project.getBasedir().getAbsolutePath() );

        // change return code if errors
        // see http://developer.android.com/tools/help/lint.html
        // option not provided by lint --help
        parameters.add( "--exitcode" );
        try
        {
            getLog().info( "Running command: " + command );
            getLog().info( "with parameters: " + parameters );
            executor.executeCommand( command, parameters, false );
        }
        catch ( ExecutionException e )
        {
            if ( isNotNullAndTrue( parsedFailOnError ) )
            {
                getLog().info( "Lint analysis produced errors and project is configured to fail on error." );
                getLog().info( "Inspect lint reports or re-run with -X to see lint errors in log" );
                getLog().info( "Failing build as configured. Ignore following error message." );
                throw new MojoExecutionException( "", e );
            }
        }
        getLog().info( "Lint analysis completed successfully." );
    }

    private void runLint() throws MojoExecutionException
    {
        IssueRegistry registry = new BuiltinIssueRegistry();

        LintCliFlags flags = new LintCliFlags();

        LintCliClient client = new LintCliClient( flags, "AndroidMaven" );

        try
        {
            if ( isNotNull( parsedQuiet ) )
            {
                flags.setQuiet( parsedQuiet );
            }
            if ( isNotNull( parsedIgnoreWarnings ) )
            {
                flags.setIgnoreWarnings( parsedIgnoreWarnings );
            }
            if ( isNotNull( parsedWarnAll ) )
            {
                flags.setCheckAllWarnings( parsedWarnAll );
            }
            if ( isNotNull( parsedWarningsAsErrors ) )
            {
                flags.setWarningsAsErrors( parsedWarningsAsErrors );
            }

            if ( isNotNullAndNotEquals( parsedConfig, "null" ) )
            {
                flags.setDefaultConfiguration( new File( parsedConfig ) );
            }

            if ( isNotNull( parsedFullPath ) )
            {
                flags.setFullPath( parsedFullPath );
            }
            if ( isNotNull( parsedShowAll ) )
            {
                flags.setShowEverything( parsedShowAll );
            }
            if ( isNotNull( parsedDisableSourceLines ) )
            {
                flags.setShowSourceLines( !parsedDisableSourceLines );
            }
            if ( isNotNullAndTrue( parsedEnableHtml ) )
            {
                File outHtml = new File( parsedHtmlOutputPath );
                flags.getReporters().add( new MultiProjectHtmlReporter( client, outHtml, flags ) );

                getLog().info( "Writing Lint HTML report in " + parsedHtmlOutputPath );
            }
            if ( isNotNullAndNotEquals( parsedUrl, "none" ) )
            {
//                TODO what is this?
//                parameters.add( "--url" );
//                parameters.add( parsedUrl );
            }
            if ( isNotNullAndTrue( parsedEnableSimpleHtml ) )
            {
                File outSimpleHtml = new File( parsedSimpleHtmlOutputPath );
                flags.getReporters().add( new MultiProjectHtmlReporter( client, outSimpleHtml, flags ) );

                getLog().info( "Writing Lint simple HTML report in " + parsedSimpleHtmlOutputPath );
            }
            if ( isNotNullAndTrue( parsedEnableXml ) )
            {
                flags.getReporters().add( new XmlReporter( client, new File( parsedXmlOutputPath ) ) );

                getLog().info( "Writing Lint XML report in " + parsedXmlOutputPath );
            }
            if ( isNotNullAndTrue( parsedEnableSources ) )
            {
//                TODO what is this?
//                parameters.add( "--sources" );
//                parameters.add( parsedSources );
            }
            if ( isNotNullAndTrue( parsedEnableClasspath ) )
            {
//                TODO what is this?
//                parameters.add( "--classpath" );
//                parameters.add( parsedClasspath );
            }
            if ( isNotNullAndTrue( parsedEnableLibraries ) )
            {
//              TODO libraries
//                parameters.add( "--libraries" );
//                parameters.add( parsedLibraries );
            }

            List< File > files = new ArrayList< File >();
            files.add( resourceDirectory );
            files.add( destinationManifestFile );
            files.add( sourceDirectory );
            files.add( assetsDirectory );

            client.run( registry, files );
        }
        catch ( IOException ex )
        {
            throw new MojoExecutionException( ex.getMessage(), ex );
        }
    }

    private boolean isNotNull( Boolean b )
    {
        return b != null;
    }

    private boolean isNotNullAndTrue( Boolean b )
    {
        return b != null && b;
    }

    private boolean isNotNullAndNotEquals( String underTest, String compared )
    {
        return underTest != null && !underTest.equals( compared );
    }

    // used via PullParameter annotation - do not remove
    private String getHtmlOutputPath()
    {
        if ( parsedHtmlOutputPath == null )
        {
            File reportPath = new File( targetDirectory, "lint-results/lint-results-html" );
            createReportDirIfNeeded( reportPath );
            return reportPath.getAbsolutePath();
        }
        return parsedHtmlOutputPath;
    }

    // used via PullParameter annotation - do not remove
    private String getSimpleHtmlOutputPath()
    {
        if ( parsedSimpleHtmlOutputPath == null )
        {
            File reportPath = new File( targetDirectory, "lint-results/lint-results-simple-html" );
            createReportDirIfNeeded( reportPath );
            return reportPath.getAbsolutePath();
        }
        return parsedSimpleHtmlOutputPath;
    }

    // used via PullParameter annotation - do not remove
    private String getXmlOutputPath()
    {
        getLog().debug( "get parsed xml output path:" + parsedXmlOutputPath );

        if ( parsedXmlOutputPath == null )
        {
            File reportPath = new File( targetDirectory, "lint-results/lint-results.xml" );
            createReportDirIfNeeded( reportPath );
            return reportPath.getAbsolutePath();
        }
        return parsedXmlOutputPath;
    }

    private void createReportDirIfNeeded( File reportPath )
    {
        if ( !reportPath.getParentFile().exists() )
        {
            reportPath.getParentFile().mkdirs();
        }
    }

    // used via PullParameter annotation - do not remove
    private String getSources()
    {
        if ( parsedSources == null )
        {
            parsedSources = sourceDirectory.getAbsolutePath();
        }
        return parsedSources;
    }

    // used via PullParameter annotation - do not remove
    private String getClasspath()
    {
        if ( parsedClasspath == null )
        {
            parsedClasspath = projectOutputDirectory.getAbsolutePath();
        }
        return parsedClasspath;
    }

    private String getLibraries()
    {
        if ( parsedLibraries == null )
        {
            StringBuilder defaultClasspathBuilder = new StringBuilder();
            Set< Artifact > artifacts = project.getDependencyArtifacts();
            if ( artifacts != null )
            {
                for ( Artifact artifact : artifacts )
                {
                    if ( !Artifact.SCOPE_PROVIDED.equals( artifact.getScope() ) && artifact.isResolved() )
                    {
                        defaultClasspathBuilder.append( artifact.getFile().getPath() );
                        defaultClasspathBuilder.append( File.pathSeparator );
                    }
                }
                if ( defaultClasspathBuilder.length() > 0 )
                {
                    defaultClasspathBuilder.deleteCharAt( defaultClasspathBuilder.length() - 1 );
                    parsedLibraries = defaultClasspathBuilder.toString();
                }
            }
        }
        return parsedLibraries;
    }
}


package com.simpligility.maven.plugins.android;

import java.util.Properties;
import java.io.InputStream;
import java.io.IOException;

/**
 * PluginInfo reads plugin.properties which contains filtered
 * values from the build like the GAV coordinates of the plugin itself
 * and provides convenience methods for accessing these properties
 * and related things about the plugin.
 *
 * @author Manfred Moser
 */
public class PluginInfo
{

  static
  {
    loadProperties();
  }

  private static final String COLON = ":";
  private static Properties prop;
  private static String groupId;
  private static String artifactId;
  private static String version;

  private static void loadProperties()
  {
    prop = new Properties();
    InputStream in = PluginInfo.class.getResourceAsStream( "plugin.properties" );
    try
    {
      prop.load( in );
      groupId = prop.getProperty( "groupId" );
      artifactId = prop.getProperty( "artifactId" );
      version = prop.getProperty( "version" );
      in.close();
    }
    catch ( IOException e )
    {
      e.printStackTrace();
    }
  }

  /**
   * Get the Maven GAV string of the plugin.
   * @return
   */
  public static String getGAV()
  {
    StringBuilder builder = new StringBuilder()
      .append( groupId )
      .append( COLON )
      .append( artifactId )
      .append( COLON )
      .append( version );
      return builder.toString();
  }

  public static String getGroupId()
  {
    return groupId;
  }

  public static String getArtifactId()
  {
    return artifactId;
  }

  public static String getVersion()
  {
    return version;
  }

  public static String getQualifiedGoal( String goal )
  {
    StringBuilder builder = new StringBuilder()
      .append( groupId )
      .append( COLON )
      .append( artifactId )
      .append( COLON )
      .append( version )
      .append( COLON )
      .append( goal );
    return builder.toString();
  }
}


package com.simpligility.maven.plugins.android.common;

/**
 * The file system extension for the Android artifact also used for the packaging type of an Android Maven Project.
 */
public final class AndroidExtension
{
    /**
     * Android application.
     */
    public static final String APK = "apk";

    /**
     * Android library project as created by Android Maven Plugin.
     * @deprecated Use {@link AAR} instead.
     */
    @Deprecated
    public static final String APKLIB = "apklib";

    /**
     * Android archive as introduced by the Gradle Android build system (modelled after apklib with extensions and some
     * differences).
     */
    public static final String AAR = "aar";

    /**
     * @deprecated Use {@link APKLIB} instead.
     */
    public static final String APKSOURCES = "apksources";

    //No instances
    private AndroidExtension()
    {
    }

    /**
     * Determine whether or not a {@link MavenProject}'s packaging is an
     * Android project.
     *
     * @param packaging Project packaging.
     * @return True if an Android project.
     */
    public static boolean isAndroidPackaging( String packaging )
    {
        return APK.equals( packaging ) || APKLIB.equals( packaging ) || APKSOURCES.equals( packaging )
                || AAR.equalsIgnoreCase( packaging );
    }
}


package com.simpligility.maven.plugins.android.phase04processclasses;

import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
import com.simpligility.maven.plugins.android.CommandExecutor;
import com.simpligility.maven.plugins.android.ExecutionException;
import com.simpligility.maven.plugins.android.IncludeExcludeSet;
import com.simpligility.maven.plugins.android.config.ConfigHandler;
import com.simpligility.maven.plugins.android.config.ConfigPojo;
import com.simpligility.maven.plugins.android.config.PullParameter;
import com.simpligility.maven.plugins.android.configuration.Proguard;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.codehaus.plexus.interpolation.os.Os;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import static com.simpligility.maven.plugins.android.InclusionExclusionResolver.filterArtifacts;
import static com.simpligility.maven.plugins.android.common.AndroidExtension.AAR;

/**
 * Processes both application and dependency classes using the ProGuard byte code obfuscator,
 * minimzer, and optimizer. For more information, see https://proguard.sourceforge.net.
 *
 * @author Jonson
 * @author Matthias Kaeppler
 * @author Manfred Moser
 * @author Michal Harakal
 */
@Mojo(
        name = "proguard",
        defaultPhase = LifecyclePhase.PROCESS_CLASSES,
        requiresDependencyResolution = ResolutionScope.COMPILE
)
public class ProguardMojo extends AbstractAndroidMojo
{

    /**
     *

     * ProGuard configuration. ProGuard is disabled by default. Set the skip parameter to false to activate proguard.
     * A complete configuration can include any of the following:
     *

     *
     *

     * <proguard>
     *    <skip>true|false</skip>
     *    <config>proguard.cfg</config>
     *    <configs>
     *      <config>${env.ANDROID_HOME}/tools/proguard/proguard-android.txt</config>
     *    </configs>
     *    <proguardJarPath>someAbsolutePathToProguardJar</proguardJarPath>
     *    <filterMavenDescriptor>true|false</filterMavenDescriptor>
     *    <filterManifest>true|false</filterManifest>
     *    <customFilter>filter1,filter2</customFilter>
     *    <jvmArguments>
     *     <jvmArgument>-Xms256m</jvmArgument>
     *     <jvmArgument>-Xmx512m</jvmArgument>
     *   </jvmArguments>
     * </proguard>
     *

     *

     * A good practice is to create a release profile in your POM, in which you enable ProGuard.
     * ProGuard should be disabled for development builds, since it obfuscates class and field
     * names, and it may interfere with test projects that rely on your application classes.
     * All parameters can be overridden in profiles or the the proguard* properties. Default values apply and are
     * documented with these properties.
     *

     */
    @ConfigPojo
    @Parameter
    protected Proguard proguard;

    /**
     * Whether ProGuard is enabled or not. Defaults to true.
     */
    @Parameter( property = "android.proguard.skip" )
    private Boolean proguardSkip;

    @PullParameter( defaultValue = "true" )
    private Boolean parsedSkip;

    @PullParameter( defaultValue = "${project.basedir}/proguard.cfg" )
    private File parsedConfig;

    @PullParameter( defaultValueGetterMethod = "getDefaultProguardConfigs" )
    private String[] parsedConfigs;

    /**
     * Additional ProGuard options
     */
    @Parameter( property = "android.proguard.options" )
    private String[] proguardOptions;

    @PullParameter( defaultValueGetterMethod = "getDefaultProguardOptions" )
    private String[] parsedOptions;

    /**
     * Path to the proguard jar and therefore version of proguard to be used. By default this will use Proguard
     * from the Central Repository. Overriding it with an absolute path allows you to use a newer or custom proguard
     * version..
     *
     * You can also reference an external Proguard version as a plugin dependency like this:
     *

     * <plugin>
     *   <groupId>com.jayway.maven.plugins.android.generation2</groupId>
     *   <artifactId>android-maven-plugin</artifactId>
     *     <dependencies>
     *       <dependency>
     *         <groupId>net.sf.proguard</groupId>
     *         <artifactId>proguard-base</artifactId>
     *         <version>4.7</version>
     *       </dependency>
     *     </dependencies>
     *

     *
     * which will download and use Proguard 4.7 as deployed to the Central Repository.
     */
    @Parameter( property = "android.proguard.proguardJarPath" )
    private String proguardProguardJarPath;

    @PullParameter( defaultValueGetterMethod = "getProguardJarPath" )
    private String parsedProguardJarPath;

    /**
     * Path relative to the project's build directory (target) where proguard puts folowing files:
     *
     *

     *
dump.txt

     *
seeds.txt

     *
usage.txt

     *
mapping.txt

     *

     *
     * You can define the directory like this:
     *

     * <proguard>
     *   <skip>false</skip>
     *   <config>proguard.cfg</config>
     *   <outputDirectory>my_proguard</outputDirectory>
     * </proguard>
     *

     *
     * Output directory is defined relatively so it could be also outside of the target directory.
     *
     */
    @Parameter( property = "android.proguard.outputDirectory" )
    private File outputDirectory;

    @PullParameter( defaultValue = "${project.build.directory}/proguard" )
    private File parsedOutputDirectory;

    @Parameter(
            property = "android.proguard.obfuscatedJar",
            defaultValue = "${project.build.directory}/${project.build.finalName}_obfuscated.jar"
    )
    private String obfuscatedJar;

    /**
     * Extra JVM Arguments. Using these you can e.g. increase memory for the jvm running the build.
     * Defaults to "-Xmx512M".
     */
    @Parameter( property = "android.proguard.jvmArguments" )
    private String[] proguardJvmArguments;

    @PullParameter( defaultValueGetterMethod = "getDefaultJvmArguments" )
    private String[] parsedJvmArguments;

    /**
     * If set to true will add a filter to remove META-INF/maven/* files. Defaults to false.
     */
    @Parameter( property = "android.proguard.filterMavenDescriptor" )
    private Boolean proguardFilterMavenDescriptor;

    @PullParameter( defaultValue = "true" )
    private Boolean parsedFilterMavenDescriptor;

    /**
     * If set to true will add a filter to remove META-INF/MANIFEST.MF files.  Defaults to false.
     */
    @Parameter( property = "android.proguard.filterManifest" )
    private Boolean proguardFilterManifest;

    @PullParameter( defaultValue = "true" )
    private Boolean parsedFilterManifest;

    /**
     * You can specify a custom filter which will be used to filter out unnecessary files from ProGuard input.
     *
     * @see http://proguard.sourceforge.net/manual/usage.html#filefilters
     */
    @Parameter( property = "android.proguard.customfilter" )
    private String proguardCustomFilter;

    @PullParameter
    private String parsedCustomFilter;

    /**
     * If set to true JDK jars will be included as library jars and corresponding filters
     * will be applied to android.jar.
     */
    @Parameter( property = "android.proguard.includeJdkLibs" )
    private Boolean includeJdkLibs;

    @PullParameter( defaultValue = "true" )
    private Boolean parsedIncludeJdkLibs;

    /**
     * If set to true the mapping.txt file will be attached as artifact of type map
     */
    @Parameter( property = "android.proguard.attachMap" )
    private Boolean attachMap;

    @PullParameter( defaultValue = "false" )
    private Boolean parsedAttachMap;

    /**
     * The plugin dependencies.
     */
    @Parameter( defaultValue = "${plugin.artifacts}", required = true, readonly = true )
    protected List< Artifact > pluginDependencies;

    /**
     * Skips transitive dependencies. May be useful if the target classes directory is populated with the
     * {@code maven-dependency-plugin} and already contains all dependency classes.
     */
    @Parameter( property = "skipDependencies", defaultValue = "false" )
    private boolean skipDependencies;

    /**
     * Allows to include or exclude artifacts by type. The {@code include} parameter has higher priority than the
     * {@code exclude} parameter. These two parameters can be overridden by the {@code artifactSet} parameter. Empty
     * strings are ignored. Example:
     *

     *     <artifactTypeSet>
     *         <includes>
     *             <include>aar</include>
     *         <includes>
     *         <excludes>
     *             <exclude>jar</exclude>
     *         <excludes>
     *     </artifactTypeSet>
     *

     */
    @Parameter( property = "artifactTypeSet" )
    private IncludeExcludeSet artifactTypeSet;

    /**
     * Allows to include or exclude artifacts by {@code groupId}, {@code artifactId}, and {@code versionId}. The
     * {@code include} parameter has higher priority than the {@code exclude} parameter. These two parameters can
     * override the {@code artifactTypeSet} and {@code skipDependencies} parameters. Artifact {@code groupId},
     * {@code artifactId}, and {@code versionId} are specified by a string with the respective values separated using
     * a colon character {@code :}. {@code artifactId} and {@code versionId} can be optional covering an artifact
     * range. Empty strings are ignored. Example:
     *

     *     <artifactTypeSet>
     *         <includes>
     *             <include>foo-group:foo-artifact:1.0-SNAPSHOT</include>
     *             <include>bar-group:bar-artifact:1.0-SNAPSHOT</include>
     *             <include>baz-group:*</include>
     *         <includes>
     *         <excludes>
     *             <exclude>qux-group:qux-artifact:*</exclude>
     *         <excludes>
     *     </artifactTypeSet>
     *

     */
    @Parameter( property = "artifactSet" )
    private IncludeExcludeSet artifactSet;

    private static final Collection< String > ANDROID_LIBRARY_EXCLUDED_FILTER = Arrays
        .asList( "org/xml/**", "org/w3c/**", "java/**", "javax/**" );

    private static final Collection< String > MAVEN_DESCRIPTOR = Arrays.asList( "META-INF/maven/**" );

    private static final Collection< String > META_INF_MANIFEST = Arrays.asList( "META-INF/MANIFEST.MF" );

    /**
     * For Proguard is required only jar type dependencies, all other like .so or .apklib can be skipped.
     */
    private static final String JAR_DEPENDENCY_TYPE = "jar";

    private static class ArtifactPrototype
    {
        private final String groupId;
        private final String artifactId;

        private ArtifactPrototype( String groupId, String artifactId )
        {
            this.groupId = groupId;
            this.artifactId = artifactId;
        }
    }

    private List< ArtifactPrototype > artifactBlacklist = new LinkedList< ArtifactPrototype>();

    private List< ArtifactPrototype > artifactsToShift = new LinkedList< ArtifactPrototype>();

    private File javaHomeDir;

    private File javaLibDir;

    private File altJavaLibDir;

    private static class ProGuardInput
    {

        private String path;

        private Collection< String > excludedFilter;

        ProGuardInput( String path, Collection< String > excludedFilter )
        {
            this.path = path;
            this.excludedFilter = excludedFilter;
        }

        public String toPath()
        {
            if ( excludedFilter != null && !excludedFilter.isEmpty() )
            {
                String middleQuote, endQuote;

                if ( !Os.isFamily( Os.FAMILY_WINDOWS ) )
                {
                    middleQuote = "(";
                    endQuote = ")";
                }
                else
                {
                    middleQuote = "(";
                    endQuote = ")";
                }

                StringBuilder sb = new StringBuilder();
                sb.append( path );
                sb.append( middleQuote );
                for ( Iterator< String > it = excludedFilter.iterator(); it.hasNext(); )
                {
                    sb.append( '!' ).append( it.next() );
                    if ( it.hasNext() )
                    {
                        sb.append( ',' );
                    }
                }
                sb.append( endQuote );
                return sb.toString();
            }
            else
            {
                return path;
            }
        }

        @Override
        public String toString()
        {
            return "ProGuardInput{"
                    + "path='" + path + '\''
                    + ", excludedFilter=" + excludedFilter
                    + '}';
        }
    }

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException
    {
        if ( getJack().isEnabled() )
        {
            //proguard is handled by Jack
            return;
        }

        ConfigHandler configHandler = new ConfigHandler( this, this.session, this.execution );
        configHandler.parseConfiguration();

        if ( !parsedSkip )
        {
            if ( parsedConfig.exists() )
            {
                // TODO: make the property name a constant sometime after switching to @Mojo
                project.getProperties().setProperty( "android.proguard.obfuscatedJar", obfuscatedJar );

                executeProguard();
            }
            else
            {
                getLog().info( String
                    .format( "Proguard skipped because the configuration file doesn't exist: %s", parsedConfig ) );
            }
        }
    }

    private void executeProguard() throws MojoExecutionException
    {
        final File proguardDir = this.parsedOutputDirectory;

        if ( !proguardDir.exists() && !proguardDir.mkdir() )
        {
            throw new MojoExecutionException( "Cannot create proguard output directory" );
        }
        else
        {
            if ( proguardDir.exists() && !proguardDir.isDirectory() )
            {
                throw new MojoExecutionException( "Non-directory exists at " + proguardDir.getAbsolutePath() );
            }
        }

        getLog().info( "Proguarding output" );
        CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
        executor.setLogger( this.getLog() );
        List< String > commands = new ArrayList< String >();

        collectJvmArguments( commands );

        commands.add( "-jar" );
        commands.add( parsedProguardJarPath );

        List<String> proguardCommands = new ArrayList<String>();

        proguardCommands.add( "@" + parsedConfig + "" );

        for ( String config : parsedConfigs )
        {
            proguardCommands.add( "@" + config );
        }

        if ( proguardFile != null )
        {
            proguardCommands.add( "@" + proguardFile.getAbsolutePath() );
        }

        for ( Artifact artifact : getTransitiveDependencyArtifacts( AAR ) )
        {
            File unpackedLibFolder = getUnpackedLibFolder( artifact );
            File proguardFile = new File( unpackedLibFolder, "proguard.txt" );
            if ( proguardFile.exists() )
            {
                proguardCommands.add( "@" + proguardFile.getAbsolutePath() );
            }
        }

        collectInputFiles( proguardCommands );

        proguardCommands.add( "-outjars" );
        proguardCommands.add( obfuscatedJar );

        proguardCommands.add( "-dump" );
        proguardCommands.add( proguardDir + File.separator + "dump.txt" );
        proguardCommands.add( "-printseeds" );
        proguardCommands.add( proguardDir + File.separator + "seeds.txt" );
        proguardCommands.add( "-printusage" );
        proguardCommands.add( proguardDir + File.separator + "usage.txt" );

        File mapFile = new File( proguardDir, "mapping.txt" );

        proguardCommands.add( "-printmapping" );
        proguardCommands.add( mapFile.toString() );

        proguardCommands.addAll( Arrays.asList( parsedOptions ) );

        final String javaExecutable = getJavaExecutable().getAbsolutePath();

        getLog().debug( javaExecutable + " " + commands.toString() + proguardCommands.toString() );

        FileOutputStream tempConfigFileOutputStream = null;
        try
        {
            File tempConfigFile = new File ( proguardDir , "temp_config.cfg" );

            StringBuilder commandStringBuilder = new StringBuilder();
            for ( String command : proguardCommands )
            {
                commandStringBuilder.append( command );
                commandStringBuilder.append( SystemUtils.LINE_SEPARATOR );
            }
            tempConfigFileOutputStream = new FileOutputStream( tempConfigFile );
            IOUtils.write( commandStringBuilder, tempConfigFileOutputStream );

            executor.setCaptureStdOut( true );
            commands.add( "@" + tempConfigFile.getAbsolutePath() + "" );
            executor.executeCommand( javaExecutable, commands, project.getBasedir(), false );
        }
        catch ( ExecutionException e )
        {
            throw new MojoExecutionException( "", e );
        }
        catch ( IOException e )
        {
            throw new MojoExecutionException( "Error writing proguard commands to temporary file", e );
        }
        finally
        {
            IOUtils.closeQuietly( tempConfigFileOutputStream );
        }

        if ( parsedAttachMap )
        {
            projectHelper.attachArtifact( project, "map", mapFile );
        }
    }

    /**
     * Convert the jvm arguments in parsedJvmArguments as populated by the config in format as needed by the java
     * command. Also preserve backwards compatibility in terms of dashes required or not..
     */
    private void collectJvmArguments( List< String > commands )
    {
        if ( parsedJvmArguments != null )
        {
            for ( String jvmArgument : parsedJvmArguments )
            {
                // preserve backward compatibility allowing argument with or without dash (e.g.
                // Xmx512m as well as -Xmx512m should work) (see
                // http://code.google.com/p/maven-android-plugin/issues/detail?id=153)
                if ( !jvmArgument.startsWith( "-" ) )
                {
                    jvmArgument = "-" + jvmArgument;
                }
                commands.add( jvmArgument );
            }
        }
    }

    private void collectInputFiles( List< String > commands ) throws MojoExecutionException
    {
        // commons-logging breaks everything horribly, so we skip it from the program
        // dependencies and declare it to be a library dependency instead
        skipArtifact( "commons-logging", "commons-logging", true );

        final List< ProGuardInput > inJars = getProgramInputFiles();
        if ( isAPKBuild() )
        {
            inJars.addAll( getProjectDependencyFiles() );
        }

        for ( final ProGuardInput injar : inJars )
        {
            getLog().debug( "Added injar : " + injar );
            commands.add( "-injars" );
            commands.add( injar.toPath() );
        }

        final List< ProGuardInput > libraryJars = getLibraryInputFiles();
        if ( !isAPKBuild() )
        {
            getLog().info( "Library project - not adding project dependencies to the obfuscated JAR" );
            libraryJars.addAll( getProjectDependencyFiles() );
        }

        for ( final ProGuardInput libraryjar : libraryJars )
        {
            getLog().debug( "Added libraryJar : " + libraryjar );
            commands.add( "-libraryjars" );
            commands.add( libraryjar.toPath() );
        }
    }

    /**
     * Figure out the full path to the current java executable.
     *
     * @return the full path to the current java executable.
     */
    private static File getJavaExecutable()
    {
        final String javaHome = System.getProperty( "java.home" );
        final String slash = File.separator;
        return new File( javaHome + slash + "bin" + slash + "java" );
    }

    private void skipArtifact( String groupId, String artifactId, boolean shiftToLibraries )
            throws MojoExecutionException
    {
        final ArtifactPrototype artifact = new ArtifactPrototype( groupId, artifactId );
        artifactBlacklist.add( artifact );
        if ( shiftToLibraries )
        {
            artifactsToShift.add( artifact );
        }
    }

    private boolean isBlacklistedArtifact( Artifact artifact )
    {
        for ( ArtifactPrototype artifactToSkip : artifactBlacklist )
        {
            if ( artifactToSkip.groupId.equals( artifact.getGroupId() )
                    && artifactToSkip.artifactId.equals( artifact.getArtifactId() ) )
            {
                return true;
            }
        }
        return false;
    }

    private boolean isShiftedArtifact( Artifact artifact )
    {
        for ( ArtifactPrototype artifactToShift : artifactsToShift )
        {
            if ( artifactToShift.groupId.equals( artifact.getGroupId() )
                    && artifactToShift.artifactId.equals( artifact.getArtifactId() ) )
            {
                return true;
            }
        }
        return false;
    }

    private List< ProGuardInput > getProgramInputFiles()
    {
        final List< ProGuardInput > inJars = new LinkedList< ProguardMojo.ProGuardInput >();
        inJars.add( createProguardInput( projectOutputDirectory.getAbsolutePath() ) );
        return inJars;
    }

    private List< ProGuardInput > getProjectDependencyFiles()
    {
        final Collection< String > globalInJarExcludes = new HashSet< String >();
        final List< ProGuardInput > inJars = new LinkedList< ProguardMojo.ProGuardInput >();

        if ( parsedFilterManifest )
        {
            globalInJarExcludes.addAll( META_INF_MANIFEST );
        }
        if ( parsedFilterMavenDescriptor )
        {
            globalInJarExcludes.addAll( MAVEN_DESCRIPTOR );
        }
        if ( parsedCustomFilter != null )
        {
            globalInJarExcludes.addAll( Arrays.asList( parsedCustomFilter.split( "," ) ) );
        }

        // we then add all its dependencies (incl. transitive ones), unless they're blacklisted
        for ( Artifact artifact : filterArtifacts( getTransitiveDependencyArtifacts(), skipDependencies,
                    artifactTypeSet.getIncludes(), artifactTypeSet.getExcludes(), artifactSet.getIncludes(),
                    artifactSet.getExcludes() ) )
        {
            if ( isBlacklistedArtifact( artifact ) )
            {
                getLog().debug( "Excluding (blacklisted) dependency as input jar : " + artifact );
                continue;
            }

            if ( JAR_DEPENDENCY_TYPE.equals( artifact.getType() ) )
            {
                getLog().debug( "Including dependency as input jar : " + artifact );
                inJars.add( createProguardInput( artifact.getFile().getAbsolutePath(), globalInJarExcludes ) );
            }
            else if ( AAR.equals( artifact.getType() ) )
            {
                // The Aar classes.jar should now be automatically included
                // because it will be a transitive dependency. As should any jars in the libs folder.
            }
            else
            {
                getLog().debug( "Excluding dependency as input jar : " + artifact );
            }
        }

        return inJars;
    }

    private ProGuardInput createProguardInput( String path, Collection< String > filterExpression )
    {
        return new ProGuardInput( path, filterExpression );
    }

    private ProGuardInput createProguardInput( String path )
    {
        return createProguardInput( path, null );
    }

    private List< ProGuardInput > getLibraryInputFiles()
    {
        final List< ProGuardInput > libraryJars = new LinkedList< ProguardMojo.ProGuardInput >();

        if ( parsedIncludeJdkLibs )
        {
            // we have to add the Java framework classes to the library JARs, since they are not
            // distributed with the JAR on Central, and since we'll strip them out of the android.jar
            // that is shipped with the SDK (since that is not a complete Java distribution)
            File rtJar = getJVMLibrary( "rt.jar" );
            if ( rtJar == null )
            {
                rtJar = getJVMLibrary( "classes.jar" );
            }
            if ( rtJar != null )
            {
                libraryJars.add( createProguardInput( rtJar.getPath() ) );
            }

            // we also need to add the JAR containing e.g. javax.servlet
            File jsseJar = getJVMLibrary( "jsse.jar" );
            if ( jsseJar != null )
            {
                libraryJars.add( createProguardInput( jsseJar.getPath() ) );
            }

            // and the javax.crypto stuff
            File jceJar = getJVMLibrary( "jce.jar" );
            if ( jceJar != null )
            {
                libraryJars.add( createProguardInput( jceJar.getPath() ) );
            }
        }

        // we treat any dependencies with provided scope as library JARs
        for ( Artifact artifact : project.getArtifacts() )
        {
            if ( artifact.getScope().equals( Artifact.SCOPE_PROVIDED ) )
            {
                if ( artifact.getArtifactId().equals( "android" ) && parsedIncludeJdkLibs )
                {
                    getLog().debug( "Including dependency as (android) library jar : " + artifact );
                    libraryJars.add(
                            createProguardInput( artifact.getFile().getAbsolutePath(), ANDROID_LIBRARY_EXCLUDED_FILTER )
                    );
                }
                else
                {
                    getLog().debug( "Including dependency as (provided) library jar : " + artifact );
                    libraryJars.add( createProguardInput( artifact.getFile().getAbsolutePath() ) );
                }
            }
            else
            {
                if ( isShiftedArtifact( artifact ) )
                {
                    // this is a blacklisted artifact that should be processed as a library instead
                    getLog().debug( "Including dependency as (shifted) library jar : " + artifact );
                    libraryJars.add( createProguardInput( artifact.getFile().getAbsolutePath() ) );
                }
                else
                {
                    getLog().debug( "Excluding dependency as library jar : " + artifact );
                }
            }
        }

        return libraryJars;
    }

    /**
     * Get the path to the proguard jar.
     */
    @SuppressWarnings( "unused" ) // NB Used to populate the parsedProguardJarPath attribute via reflection.
    private String getProguardJarPath() throws MojoExecutionException
    {
        return getProguardJarPathFromDependencies();
    }

    private String getProguardJarPathFromDependencies() throws MojoExecutionException
    {
        Artifact proguardArtifact = null;
        int proguardArtifactDistance = -1;
        for ( Artifact artifact : pluginDependencies )
        {
            getLog().debug( "pluginArtifact: " + artifact.getFile() );
            if ( ( "proguard".equals( artifact.getArtifactId() ) ) || ( "proguard-base"
                .equals( artifact.getArtifactId() ) ) )
            {
                int distance = artifact.getDependencyTrail().size();
                getLog().debug( "proguard DependencyTrail: " + distance );
                if ( proguardArtifactDistance == -1 )
                {
                    proguardArtifact = artifact;
                    proguardArtifactDistance = distance;
                }
                else
                {
                    if ( distance < proguardArtifactDistance )
                    {
                        proguardArtifact = artifact;
                        proguardArtifactDistance = distance;
                    }
                }
            }
        }
        if ( proguardArtifact != null )
        {
            getLog().debug( "proguardArtifact: " + proguardArtifact.getFile() );
            return proguardArtifact.getFile().getAbsoluteFile().toString();
        }
        else
        {
            return null;
        }

    }

    /**
     * Get the default JVM arguments for the proguard invocation.
     *
     * @see #parsedJvmArguments
     */
    @SuppressWarnings( "unused" ) // Provides default value for parsedJvmArguments attribute
    private String[] getDefaultJvmArguments()
    {
        return new String[] { "-Xmx512M" };
    }

    /**
     * Get the default ProGuard config files.
     *
     * @see #parsedConfigs
     */
    @SuppressWarnings( "unused" ) // Provides default value for parsedConfigs attribute
    private String[] getDefaultProguardConfigs()
    {
        return new String[0];
    }

    /**
     * Get the default ProGuard options.
     *
     * @see #parsedOptions
     */
    @SuppressWarnings( "unused" ) // Provides default value for parsedOptions attribute
    private String[] getDefaultProguardOptions()
    {
        return new String[0];
    }

    /**
     * Finds a library file in either the primary or alternate lib directory.
     * @param fileName The base name of the file.
     * @return Either a canonical filename, or {@code null} if not found.
     */
    private File getJVMLibrary( String fileName )
    {
        File libFile = new File( getJavaLibDir(), fileName );
        if ( !libFile.exists() )
        {
            libFile = new File( getAltJavaLibDir(), fileName );
            if ( !libFile.exists() )
            {
                libFile = null;
            }
        }
        return libFile;
    }

    /**
     * Determines the java.home directory.
     * @return The java.home directory, as a File.
     */
    private File getJavaHomeDir()
    {
        if ( javaHomeDir == null )
        {
            javaHomeDir = new File( System.getProperty( "java.home" ) );
        }
        return javaHomeDir;
    }

    /**
     * Determines the primary JVM library location.
     * @return The primary library directory, as a File.
     */
    private File getJavaLibDir()
    {
        if ( javaLibDir == null )
        {
            javaLibDir = new File( getJavaHomeDir(), "lib" );
        }
        return javaLibDir;
    }

    /**
     * Determines the alternate JVM library location (applies with older
     * MacOSX JVMs).
     * @return The alternate JVM library location, as a File.
     */
    private File getAltJavaLibDir()
    {
        if ( altJavaLibDir == null )
        {
            altJavaLibDir = new File( getJavaHomeDir().getParent(), "Classes" );
        }
        return altJavaLibDir;
    }
}