org.codehaus.mojo.cobertura.integration.shell.PlainJvmCommandLine.java Source code

Java tutorial

Introduction

Here is the source code for org.codehaus.mojo.cobertura.integration.shell.PlainJvmCommandLine.java

Source

/*
 * #%L
 * Mojo's Maven plugin for Cobertura
 * %%
 * Copyright (C) 2005 - 2013 Codehaus
 * %%
 * 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.
 * #L%
 */
package org.codehaus.mojo.cobertura.integration.shell;

import net.sourceforge.cobertura.util.CommandLineBuilder;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.lang.Validate;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.logging.Log;
import org.codehaus.plexus.util.cli.Commandline;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * Plain shell argument line implementation of the JvmArgumentLine specification.
 *
 * @author <a href="mailto:lj@jguru.se">Lennart J&ouml;relid</a>, jGuru Europe AB
 */
public class PlainJvmCommandLine implements JvmArgumentLine {

    // Internal state
    private Log mavenLog;
    private SortedSet<String> options;
    private List<String> arguments;
    private String mainClass;
    private List<Artifact> classpathEntries;
    private boolean useCommandFile;

    /**
     * Convenience constructor, creating a new PlainJvmCommandLine instance using the supplied mavenLog
     * and all other state initialized but empty. The mainClass will be {@code null} after firing this
     * constructor, though, and needs to be set by invoking {@code setMainClass} before executing this
     * PlainJvmCommandLine.
     *
     * @param mavenLog The non-null Log used to display messages as requested.
     */
    public PlainJvmCommandLine(final Log mavenLog) {

        // Delegate
        this(mavenLog, new ArrayList<Artifact>(), new TreeSet<String>(), "completely.irrelevant",
                new ArrayList<String>(), true);

        // Assign internal state
        this.mainClass = null;
    }

    /**
     * Compound constructor creating a new PlainJvmCommandLine wrapping the supplied data.
     *
     * @param mavenLog         The non-null Log used to display messages as requested.
     * @param classpathEntries A List holding all Artifacts to use as classpath entries for the Forked JVM.
     * @param options          The command-line options to inject before the mainClass of this PlainJvmCommandLine.
     * @param mainClass        The fully qualified class name of the class to be executed by this PlainJvmCommandLine.
     * @param arguments        A List holding all arguments
     * @param useCommandsFile  indicates if this PlainJvmCommandLine should use a Cobertura commandsFile to write
     *                         all arguments to.
     */
    public PlainJvmCommandLine(final Log mavenLog, final List<Artifact> classpathEntries,
            final SortedSet<String> options, final String mainClass, final List<String> arguments,
            final boolean useCommandsFile) {

        // Check sanity
        Validate.notNull(mavenLog, "Cannot handle null mavenLog argument.");
        Validate.notNull(classpathEntries, "Cannot handle null classpathEntries argument.");
        Validate.notNull(options, "Cannot handle null options argument.");
        Validate.notNull(arguments, "Cannot handle null arguments argument.");

        // Assign internal state
        this.mavenLog = mavenLog;
        this.options = options;
        this.arguments = arguments;
        this.classpathEntries = classpathEntries;
        this.useCommandFile = useCommandsFile;

        setMainClass(mainClass);
    }

    /**
     * {@inheritDoc}
     */
    public final void addOption(final String option) throws IllegalArgumentException {
        options.add(getTrimmedAndValidated(option, "option", true));
    }

    /**
     * {@inheritDoc}
     */
    public final void addOption(final String key, final String value) throws IllegalArgumentException {
        options.add(getTrimmedAndValidated(key, "option key", true) + "="
                + getTrimmedAndValidated(value, "option value", false));
    }

    /**
     * {@inheritDoc}
     */
    public final void setMainClass(final String fullyQualifiedClassName) {
        mainClass = getTrimmedAndValidated(fullyQualifiedClassName, "mainClass", false);
    }

    /**
     * {@inheritDoc}
     */
    public final String getMainClass() {
        return mainClass;
    }

    /**
     * {@inheritDoc}
     */
    public void addToClasspath(final Artifact classpathArtifact) {

        // Check sanity
        Validate.notNull(classpathArtifact, "Cannot handle null classpathArtifact argument.");

        // Assign internal state, if applicable.
        if (!classpathEntries.contains(classpathArtifact)) {
            classpathEntries.add(classpathArtifact);
        } else {

            final StringBuilder builder = new StringBuilder();
            if (classpathArtifact.getGroupId() != null) {
                builder.append(classpathArtifact.getGroupId());
            }
            if (classpathArtifact.getArtifactId() != null) {
                builder.append(":").append(classpathArtifact.getArtifactId());
            }
            if (classpathArtifact.getVersion() != null) {
                builder.append(":").append(classpathArtifact.getVersion());
            }
            if (classpathArtifact.getType() != null) {
                builder.append(":").append(classpathArtifact.getType());
            }

            mavenLog.warn("Not adding classpath artifact [" + builder.toString() + "] twice.");
        }
    }

    /**
     * {@inheritDoc}
     */
    public void addArgument(final String argument) {
        arguments.add(getTrimmedAndValidated(argument, "argument", false));
    }

    /**
     * {@inheritDoc}
     */
    public void useCommandFile(boolean yes) {
        this.useCommandFile = yes;
    }

    /**
     * Default implementation of the CommandLine assigns the classpath as an
     * environment variable (i.e. {@code CLASSPATH}) to reduce the CommandLine
     * length, thereby catering for Windows shells and their problems with
     * long command lines ...
     * <p/>
     * {@inheritDoc}
     */
    public Commandline validateStateAndRetrieveCommandLine() throws IllegalStateException {

        // Check sanity
        if (mainClass == null || mainClass.trim().equals("")) {
            throw new IllegalStateException(
                    "Invalid null or empty MainClass. " + "(Must be set before retrieving a Commandline).");
        }

        // Use Plexus Utils to generate a CommandLine which can be executed as a separate Java process.
        final Commandline cl = new Commandline();
        final File java = new File(SystemUtils.getJavaHome(), "bin/java");
        cl.setExecutable(java.getAbsolutePath());
        cl.addEnvironment("CLASSPATH", synthesizeClasspath());

        // Add all options to the CommandLine
        for (String current : options) {
            createArgWithValue(cl, current);
        }

        createArgWithValue(cl, mainClass);

        // Add all other arguments
        if (useCommandFile) {
            try {

                final File commandsFile = createCommandsFile();

                // Add the commandsfile argument, which is on the form
                //
                //     --commandsfile [path to file]
                //
                createArgWithValue(cl, "--commandsfile");
                createArgWithValue(cl, commandsFile.getAbsolutePath());

            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {

            // Simply add the arguments in order
            for (String arg : arguments) {
                createArgWithValue(cl, arg);
            }
        }

        // All done.
        return cl;
    }

    /**
     * Override this method in subclasses to modify the synthesized Classpath before use.
     *
     * @param synthesizedClasspath The classpath synthesized from all provided classpathArtifacts.
     * @return The final classpath to use in the forked Cobertura process.
     */
    protected String modifyClasspath(final String synthesizedClasspath) {
        return synthesizedClasspath;
    }

    /**
     * Generate a Cobertura-compliant commands file, write all {@code arguments} into it and return its File object.
     *
     * @return the CommandsFile File instance.
     * @throws IOException if the command file could not be created.
     * @see net.sourceforge.cobertura.util.CommandLineBuilder#getCommandLineFile()
     */
    protected File createCommandsFile() throws IOException {

        final CommandLineBuilder builder = new CommandLineBuilder();
        for (String arg : arguments) {
            builder.addArg(arg);
        }

        // Save the command line file and validate that we succeeded.
        builder.saveArgs();
        final File toReturn = new File(builder.getCommandLineFile());

        if (!toReturn.exists()) {
            // Failed to create the commands file. Complain.
            throw new IOException("Failed creating commandsFile [" + toReturn.getCanonicalPath() + "]");
        }

        // All done.
        return toReturn;
    }

    //
    // Private helpers
    //

    /**
     * Joins all classpathEntries into a proper classpath, as required by a forked Cobertura process.
     *
     * @return the Classpath string which could be used for invoking a forked Cobertura process.
     * @throws java.lang.IllegalStateException if any taskClasspathArtifact could not be resolved to a canonical
     *                                         path (i.e. its JAR file could not be located, or equivalent).
     */
    private String synthesizeClasspath() throws IllegalStateException {

        final StringBuilder builder = new StringBuilder();

        for (Artifact current : classpathEntries) {

            try {
                // Get the path to the Artifact
                final String artifactPath = current.getFile().getCanonicalPath();

                // Append the artifact file path to the builder.
                builder.append(File.pathSeparator).append(artifactPath);
            } catch (IOException e) {
                throw new IllegalStateException("Could not find canonical path for '" + current.getFile() + "'.",
                        e);
            }
        }

        // All done.
        return modifyClasspath(builder.toString());
    }

    /**
     * Validates the the supplied 'original' argument is neither null nor empty
     * (following trim()-ming of it). Returns a non-empty {@code trim()}-med
     * version of the original supplied.
     *
     * @param original             The original argument strength.
     * @param type                 The type to describe the original argument in an exception message.
     * @param requireStartWithDash if {@code true}, the key must start with a '-'.
     * @return a non-empty {@code trim()}-med version of the original supplied.
     */
    private String getTrimmedAndValidated(final String original, final String type, boolean requireStartWithDash) {

        // Check sanity
        Validate.notNull(original, "Cannot handle null " + type + ".");

        final String trimmed = original.trim();
        Validate.notEmpty(trimmed, "Cannot handle empty " + type + ".");
        if (requireStartWithDash) {
            Validate.isTrue(trimmed.startsWith("-"),
                    "Expected " + type + " to start with '-', but got: [" + original + "]");
        }

        // All done
        return trimmed;
    }

    /**
     * Convenience method to add a new argument with the supplied value to the given
     * Commandline instance.
     *
     * @param commandline The command line to which we should add an argument.
     * @param value       the value of the new argument to be added to the Commandline.
     */
    private void createArgWithValue(final Commandline commandline, final String value) {

        // Check sanity
        Validate.notEmpty(value, "Cannot handle null or empty value argument.");

        // All done.
        commandline.createArg().setValue(value);
    }
}