com.simpligility.maven.plugins.android.phase_prebuild.ClasspathModifierLifecycleParticipant.java Source code

Java tutorial

Introduction

Here is the source code for com.simpligility.maven.plugins.android.phase_prebuild.ClasspathModifierLifecycleParticipant.java

Source

/*******************************************************************************
 * 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.
        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;

    }

}