ClasspathWriter.java

package org.andromda.maven.plugin.andromdapp.eclipse;

import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.andromda.core.common.ResourceUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
import org.codehaus.plexus.util.xml.XMLWriter;

/**
 * Writes the Eclipse .classpath files.
 *
 * @author Chad Brandon
 */
public class ClasspathWriter
    extends EclipseWriter
{
    /**
     * @param project
     * @param logger
     */
    public ClasspathWriter(
        final MavenProject project,
        final Log logger)
    {
        super(project, logger);
    }

    /**
     * Writes the .classpath file for eclipse.
     *
     * @param projects the list of projects from which the .classpath will get its dependencies.
     * @param repositoryVariableName the name of the maven repository variable.
     * @param artifactFactory the factory for constructing artifacts.
     * @param artifactResolver the artifact resolver.
     * @param localRepository the local repository instance.
     * @param artifactMetadataSource
     * @param classpathArtifactTypes the artifacts types that are allowed in the classpath file.
     * @param remoteRepositories the list of remote repository instances.
     * @param resolveTransitiveDependencies whether or not dependencies shall be transitively resolved.
     * @param variables Variables replaced within the path
     * @param merge anything extra (not auto-generated), that should be "merged" into the generated .classpath
     * @throws Exception
     */
    public void write(
        final List<MavenProject> projects,
        final String repositoryVariableName,
        final ArtifactFactory artifactFactory,
        final ArtifactResolver artifactResolver,
        final ArtifactRepository localRepository,
        final ArtifactMetadataSource artifactMetadataSource,
        final Set<String> classpathArtifactTypes,
        final List<ArtifactRepository> remoteRepositories,
        final boolean resolveTransitiveDependencies,
        final Variable[] variables,
        final String merge)
        throws Exception
    {
        final String rootDirectory = ResourceUtils.normalizePath(this.project.getBasedir().toString());
        final File classpathFile = new File(rootDirectory, ".classpath");
        final FileWriter fileWriter = new FileWriter(classpathFile);
        final XMLWriter writer = new PrettyPrintXMLWriter(fileWriter);

        writer.startElement("classpath");

        final Set<String> projectArtifactIds = new LinkedHashSet<String>();
        for (final MavenProject project : projects)
        {
            final Artifact projectArtifact =
                artifactFactory.createArtifact(
                    project.getGroupId(),
                    project.getArtifactId(),
                    project.getVersion(),
                    null,
                    project.getPackaging());
            projectArtifactIds.add(projectArtifact.getId());
        }

        // - write the source roots for the root project (if they are any)
        this.writeSourceRoots(this.project, rootDirectory, writer);

        final Set<Artifact> allArtifacts = new LinkedHashSet<Artifact>(this.project.createArtifacts(
            artifactFactory,
            null,
            null));
        for (final MavenProject project : projects)
        {
            this.writeSourceRoots(project, rootDirectory, writer);
            final Set<Artifact> artifacts = project.createArtifacts(
                    artifactFactory,
                    null,
                    null);

            // - get the direct dependencies
            for (final Artifact artifact : artifacts)
            {
                // - don't attempt to resolve the artifact if its part of the project (we
                //   infer this if it has the same id has one of the projects or is in
                //   the same groupId).
                if (!projectArtifactIds.contains(artifact.getId()) &&
                    !project.getGroupId().equals(artifact.getGroupId()))
                {
                    artifactResolver.resolve(
                        artifact,
                        project.getRemoteArtifactRepositories(),
                        localRepository);
                    allArtifacts.add(artifact);
                }
                else
                {
                    allArtifacts.add(artifact);
                }
            }
        }

        // - remove the project artifacts
        for (final MavenProject project : projects)
        {
            final Artifact projectArtifact = project.getArtifact();
            if (projectArtifact != null)
            {
                for (final Iterator<Artifact> artifactIterator = allArtifacts.iterator(); artifactIterator.hasNext();)
                {
                    final Artifact artifact = artifactIterator.next();
                    final String projectId = projectArtifact.getArtifactId();
                    final String projectGroupId = projectArtifact.getGroupId();
                    final String artifactId = artifact.getArtifactId();
                    final String groupId = artifact.getGroupId();
                    if (artifactId.equals(projectId) && groupId.equals(projectGroupId))
                    {
                        artifactIterator.remove();
                    }
                }
            }
        }

        // - now we resolve transitively, if we have the flag on
        if (resolveTransitiveDependencies)
        {
            final Artifact rootProjectArtifact =
                artifactFactory.createArtifact(
                    this.project.getGroupId(),
                    this.project.getArtifactId(),
                    this.project.getVersion(),
                    null,
                    this.project.getPackaging());

            final OrArtifactFilter filter = new OrArtifactFilter();
            filter.add(new ScopeArtifactFilter(Artifact.SCOPE_COMPILE));
            filter.add(new ScopeArtifactFilter(Artifact.SCOPE_PROVIDED));
            filter.add(new ScopeArtifactFilter(Artifact.SCOPE_TEST));
            final ArtifactResolutionResult result =
                artifactResolver.resolveTransitively(
                    allArtifacts,
                    rootProjectArtifact,
                    localRepository,
                    remoteRepositories,
                    artifactMetadataSource,
                    filter);

            allArtifacts.clear();
            allArtifacts.addAll(result.getArtifacts());
        }

        final List<String> artifactPathList = new ArrayList<String>();
        for (final Artifact artifact : allArtifacts)
        {
            if (classpathArtifactTypes.contains(artifact.getType()))
            {
                final File artifactFile = artifact.getFile();
                final String artifactPath = ResourceUtils.normalizePath(artifactFile.toString());
                String path =
                    StringUtils.replace(
                        artifactPath,
                        ResourceUtils.normalizePath(localRepository.getBasedir()),
                        VAR_PREFIX + repositoryVariableName);
                if (path.equals(artifactPath))
                {
                    // - replace any variables if present
                    if (variables != null)
                    {
                        for (final Variable variable : variables)
                        {
                            final String name = StringUtils.trimToEmpty(variable.getName());
                            final String value = StringUtils.trimToEmpty(variable.getValue());
                            path = StringUtils.replace(path, value, VAR_PREFIX + name);
                        }
                    }
                }
                artifactPathList.add(path);
            }
        }

        // - sort the paths
        Collections.sort(artifactPathList);

        // - get rid of any duplicates
        final Set<String> artifactPaths = new LinkedHashSet<String>(artifactPathList);

        for (String path : artifactPaths)
        {
            if (path.startsWith(VAR_PREFIX))
            {
                this.writeClasspathEntry(
                    writer,
                    "var",
                    path.split(VAR_PREFIX)[1]);
            }
            else
            {
                if (path.startsWith(rootDirectory))
                {
                    path = StringUtils.replace(path, rootDirectory + '/', "");
                }
                this.writeClasspathEntry(
                    writer,
                    "lib",
                    path);
            }
        }

        this.writeClasspathEntry(
            writer,
            "con",
            "org.eclipse.jdt.launching.JRE_CONTAINER");

        String outputPath =
            StringUtils.replace(
                ResourceUtils.normalizePath(this.project.getBuild().getOutputDirectory()),
                rootDirectory,
                "");
        if (outputPath.startsWith("/"))
        {
            outputPath = outputPath.substring(
                    1,
                    outputPath.length());
        }
        this.writeClasspathEntry(
            writer,
            "output",
            outputPath);

        if (StringUtils.isNotBlank(merge))
        {
            writer.writeMarkup(merge);
        }
        writer.endElement();

        logger.info("Classpath file written --> '" + classpathFile + '\'');
        IOUtil.close(fileWriter);
    }

    private static final String VAR_PREFIX = "var:";

    private static final String DRIVE_PATTERN = ".*:";

    /**
     * Writes the source roots for the given project.
     *
     * @param project the project for which to write the source roots.
     * @param rootDirectory the root project's base directory
     * @param writer the XMLWriter used to write the source roots.
     */
    private void writeSourceRoots(final MavenProject project, String rootDirectory, final XMLWriter writer)
    {
        // - strip the drive prefix (so we don't have to worry about replacement dependent on case)
        rootDirectory = rootDirectory.replaceFirst(DRIVE_PATTERN, "");
        for (final Iterator<String> sourceIterator = project.getCompileSourceRoots().iterator(); sourceIterator.hasNext();)
        {
            final String sourceRoot = ResourceUtils.normalizePath(sourceIterator.next()).replaceFirst(DRIVE_PATTERN, "");
            if (new File(sourceRoot).isDirectory())
            {
                String sourceRootPath = StringUtils.replace(
                        sourceRoot,
                        rootDirectory,
                        "");
                if (sourceRootPath.startsWith("/"))
                {
                    sourceRootPath = sourceRootPath.substring(
                            1,
                            sourceRootPath.length());
                    this.writeClasspathEntry(
                        writer,
                        "src",
                        sourceRootPath);
                }
            }
        }
    }

    /**
     * Writes a classpathentry with the given <code>kind</code> and <code>path</code> values.
     *
     * @param writer the XML writer with which to write.
     * @param kind the kind of the classpath entry.
     * @param path the path of the classpath entry.
     */
    private void writeClasspathEntry(
        final XMLWriter writer,
        final String kind,
        final String path)
    {
        writer.startElement("classpathentry");
        writer.addAttribute(
            "kind",
            kind);
        writer.addAttribute(
            "path",
            path);
        writer.endElement();
    }
}