io.takari.maven.plugins.jar.Jar.java Source code

Java tutorial

Introduction

Here is the source code for io.takari.maven.plugins.jar.Jar.java

Source

/**
 * Copyright (c) 2014 Takari, Inc.
 * 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
 */
package io.takari.maven.plugins.jar;

import static com.google.common.collect.Iterables.size;
import static java.util.Arrays.asList;
import static java.util.Collections.singleton;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import javax.inject.Inject;

import org.apache.maven.plugin.MojoExecutionException;
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.project.MavenProject;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;

import io.takari.incrementalbuild.Output;
import io.takari.incrementalbuild.aggregator.AggregatorBuildContext;
import io.takari.incrementalbuild.aggregator.InputAggregator;
import io.takari.incrementalbuild.aggregator.InputSet;
import io.takari.maven.plugins.TakariLifecycleMojo;
import io.takari.maven.plugins.util.PropertiesWriter;
import io.tesla.proviso.archive.Archiver;
import io.tesla.proviso.archive.Entry;
import io.tesla.proviso.archive.source.FileEntry;

@Mojo(name = "jar", defaultPhase = LifecyclePhase.PACKAGE, configurator = "takari", threadSafe = true)
public class Jar extends TakariLifecycleMojo {

    @Parameter(defaultValue = "${project.build.outputDirectory}")
    protected File classesDirectory;

    @Parameter(defaultValue = "${project.build.finalName}")
    private String finalName;

    @Parameter(defaultValue = "${project.build.directory}")
    private File outputDirectory;

    @Parameter(defaultValue = "true", property = "mainJar")
    private boolean mainJar;

    @Parameter(defaultValue = "false", property = "sourceJar")
    private boolean sourceJar;

    @Parameter(defaultValue = "false", property = "testJar")
    private boolean testJar;

    @Parameter(defaultValue = "${project.build.testOutputDirectory}")
    private File testClassesDirectory;

    @Parameter
    private ArchiveConfiguration archive;

    @Inject
    private AggregatorBuildContext buildContext;

    private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";

    @Override
    protected void executeMojo() throws MojoExecutionException {

        if (!outputDirectory.exists()) {
            outputDirectory.mkdir();
        }

        if (mainJar) {
            File jar = new File(outputDirectory, String.format("%s.jar", finalName));
            InputSet registeredOutput = buildContext.newInputSet();
            try {
                if (classesDirectory.isDirectory()) {
                    classesDirectory = classesDirectory.getCanonicalFile();
                    Iterable<File> inputs = registeredOutput.addInputs(classesDirectory, null, null);
                    logger.debug("Analyzing main classes directory {} with {} entries", classesDirectory,
                            size(inputs));
                } else {
                    logger.warn("Main classes directory {} does not exist", classesDirectory);
                }
                // XXX this does not detect changes in archive#manifestFile.
                boolean processingRequired = registeredOutput.aggregateIfNecessary(jar, new InputAggregator() {
                    @Override
                    public void aggregate(Output<File> output, Iterable<File> inputs) throws IOException {
                        logger.info("Building main JAR.");

                        List<Iterable<Entry>> sources = new ArrayList<>();
                        if (archive != null && archive.getManifestFile() != null) {
                            sources.add(jarManifestSource(archive.getManifestFile()));
                        }
                        sources.add(inputsSource(classesDirectory, inputs));
                        sources.add(singleton(pomPropertiesSource(project)));
                        sources.add(jarManifestSource(project));
                        archive(output.getResource(), sources);
                    }
                });
                if (!processingRequired) {
                    logger.info("Main JAR is up-to-date");
                }
                project.getArtifact().setFile(jar);
            } catch (IOException e) {
                throw new MojoExecutionException(e.getMessage(), e);
            }
        }

        if (sourceJar) {
            final Multimap<File, File> sources = HashMultimap.create();
            File sourceJar = new File(outputDirectory, String.format("%s-%s.jar", finalName, "sources"));
            InputSet registeredOutput = buildContext.newInputSet();
            try {
                for (String sourceRoot : project.getCompileSourceRoots()) {
                    File dir = new File(sourceRoot);
                    if (dir.isDirectory()) {
                        dir = dir.getCanonicalFile();
                        Iterable<File> inputs = registeredOutput.addInputs(dir, null, null);
                        logger.debug("Analyzing sources directory {} with {} entries", dir, size(inputs));
                        sources.putAll(dir, inputs);
                    } else {
                        logger.debug("Sources directory {} does not exist", dir);
                    }
                }
                boolean processingRequired = registeredOutput.aggregateIfNecessary(sourceJar,
                        new InputAggregator() {
                            @Override
                            public void aggregate(Output<File> output, Iterable<File> inputs) throws IOException {
                                logger.info("Building source JAR.");

                                archive(output.getResource(),
                                        asList(inputsSource(sources), jarManifestSource(project)));
                            }
                        });
                if (!processingRequired) {
                    logger.info("Source JAR is up-to-date");
                }
                projectHelper.attachArtifact(project, "jar", "sources", sourceJar);
            } catch (IOException e) {
                throw new MojoExecutionException(e.getMessage(), e);
            }
        }

        if (testJar && testClassesDirectory.isDirectory()) {
            File testJar = new File(outputDirectory, String.format("%s-%s.jar", finalName, "tests"));
            InputSet registeredOutput = buildContext.newInputSet();
            try {
                testClassesDirectory = testClassesDirectory.getCanonicalFile();
                Iterable<File> inputs = registeredOutput.addInputs(testClassesDirectory, null, null);
                logger.debug("Analyzing test classes directory {} with {} entries", testClassesDirectory,
                        size(inputs));
                boolean processingRequired = registeredOutput.aggregateIfNecessary(testJar, new InputAggregator() {
                    @Override
                    public void aggregate(Output<File> output, Iterable<File> inputs) throws IOException {
                        logger.info("Building test JAR.");

                        archive(output.getResource(),
                                asList(inputsSource(testClassesDirectory, inputs), jarManifestSource(project)));
                    }
                });
                if (!processingRequired) {
                    logger.info("Test JAR is up-to-date");
                }
            } catch (IOException e) {
                throw new MojoExecutionException(e.getMessage(), e);
            }
            projectHelper.attachArtifact(project, "jar", "tests", testJar);
        }
    }

    private void archive(File jar, List<Iterable<Entry>> sources) throws IOException {
        Archiver archiver = Archiver.builder() //
                .useRoot(false) //
                .normalize(true) //
                .build();
        archiver.archive(jar, new AggregateSource(sources));
        if (logger.isDebugEnabled()) {
            int size = 0;
            for (Iterable<?> source : sources) {
                size += size(source);
            }
            logger.debug("Created archive {} with {} entries", jar, size);
        }
    }

    static String getRelativePath(File basedir, File resource) {
        return basedir.toPath().relativize(resource.toPath()).toString().replace('\\', '/'); // always use forward slash for path separator
    }

    private List<Entry> inputsSource(Multimap<File, File> inputs) {
        final List<Entry> entries = new ArrayList<>();
        for (File basedir : inputs.keySet()) {
            entries.addAll(inputsSource(basedir, inputs.get(basedir)));
        }
        return entries;
    }

    private List<Entry> inputsSource(File basedir, Iterable<File> inputs) {
        final List<Entry> entries = new ArrayList<>();
        for (File input : inputs) {
            String entryName = getRelativePath(basedir, input);
            entries.add(new FileEntry(entryName, input));
        }
        return entries;
    }

    public static Iterable<Entry> jarManifestSource(File file) {
        return singleton((Entry) new FileEntry(MANIFEST_PATH, file));
    }

    private Iterable<Entry> jarManifestSource(MavenProject project) throws IOException {
        Manifest manifest = new Manifest();
        Attributes main = manifest.getMainAttributes();
        main.putValue("Manifest-Version", "1.0");
        main.putValue("Archiver-Version", "Provisio Archiver");
        main.putValue("Created-By", "Takari Inc.");
        main.putValue("Built-By", System.getProperty("user.name"));
        main.putValue("Build-Jdk", System.getProperty("java.version"));
        main.putValue("Specification-Title", project.getArtifactId());
        main.putValue("Specification-Version", project.getVersion());
        main.putValue("Implementation-Title", project.getArtifactId());
        main.putValue("Implementation-Version", project.getVersion());
        main.putValue("Implementation-Vendor-Id", project.getGroupId());
        File manifestFile = new File(project.getBuild().getDirectory(), "MANIFEST.MF");
        if (!manifestFile.getParentFile().exists()) {
            manifestFile.getParentFile().mkdirs();
        }

        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        manifest.write(buf);

        return singleton((Entry) new BytesEntry(MANIFEST_PATH, buf.toByteArray()));
    }

    protected Entry pomPropertiesSource(MavenProject project) throws IOException {
        String entryName = String.format("META-INF/maven/%s/%s/pom.properties", project.getGroupId(),
                project.getArtifactId());

        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        Properties properties = new Properties();
        properties.setProperty("groupId", project.getGroupId());
        properties.setProperty("artifactId", project.getArtifactId());
        properties.setProperty("version", project.getVersion());
        PropertiesWriter.write(properties, null, buf);

        return new BytesEntry(entryName, buf.toByteArray());
    }

}