de.thetaphi.forbiddenapis.AbstractCheckMojo.java Source code

Java tutorial

Introduction

Here is the source code for de.thetaphi.forbiddenapis.AbstractCheckMojo.java

Source

package de.thetaphi.forbiddenapis;

/*
 * (C) Copyright Uwe Schindler (Generics Policeman) and others.
 *
 * 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.
 */

import static de.thetaphi.forbiddenapis.Checker.Option.*;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.util.DirectoryScanner;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.annotation.RetentionPolicy;
import java.net.URLClassLoader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;

/**
 * Base class for forbiddenapis Mojos.
 * @since 1.0
 */
public abstract class AbstractCheckMojo extends AbstractMojo {

    /**
     * Lists all files, which contain signatures and comments for forbidden API calls.
     * The signatures are resolved against the compile classpath.
     * @since 1.0
     */
    @Parameter(required = false)
    private File[] signaturesFiles;

    /**
     * Gives a multiline list of signatures, inline in the pom.xml. Use an XML CDATA section to do that!
     * The signatures are resolved against the compile classpath.
     * @since 1.0
     */
    @Parameter(required = false)
    private String signatures;

    /**
     * Specifies <a href="bundled-signatures.html">built-in signatures</a> files (e.g., deprecated APIs for specific Java versions,
     * unsafe method calls using default locale, default charset,...)
     * @since 1.0
     */
    @Parameter(required = false)
    private String[] bundledSignatures;

    /**
     * Forbids calls to classes from the internal java runtime (like sun.misc.Unsafe)
     * @since 1.0
     */
    @Parameter(required = false, defaultValue = "false")
    private boolean internalRuntimeForbidden;

    /**
     * Fail the build, if the bundled ASM library cannot read the class file format
     * of the runtime library or the runtime library cannot be discovered.
     * @since 1.0
     */
    @Parameter(required = false, defaultValue = "false")
    private boolean failOnUnsupportedJava;

    /**
     * Fail the build, if a class referenced in the scanned code is missing. This requires
     * that you pass the whole classpath including all dependencies to this Mojo
     * (Maven does this by default).
     * @since 1.0
     */
    @Parameter(required = false, defaultValue = "true")
    private boolean failOnMissingClasses;

    /**
     * Fail the build if a signature is not resolving. If this parameter is set to
     * to false, then such signatures are silently ignored. This is useful in multi-module Maven
     * projects where only some modules have the dependency to which the signature file(s) apply.
     * @since 1.4
     */
    @Parameter(required = false, defaultValue = "true")
    private boolean failOnUnresolvableSignatures;

    /**
     * Fail the build if violations have been found. Defaults to {@code true}.
     * @since 1.9
     */
    @Parameter(required = false, property = "forbiddenapis.failOnViolation", defaultValue = "true")
    private boolean failOnViolation;

    /**
     * The default compiler target version used to expand references to bundled JDK signatures.
     * E.g., if you use "jdk-deprecated", it will expand to this version.
     * This setting should be identical to the target version used in the compiler plugin.
     * @since 1.0
     */
    @Parameter(required = false, defaultValue = "${maven.compiler.target}")
    private String targetVersion;

    /**
     * List of patterns matching all class files to be parsed from the classesDirectory.
     * Can be changed to e.g. exclude several files (using excludes).
     * The default is a single include with pattern '**&#47;*.class'
     * @see #excludes
     * @since 1.0
     */
    @Parameter(required = false)
    private String[] includes;

    /**
     * List of patterns matching class files to be excluded from checking.
     * @see #includes
     * @since 1.0
     */
    @Parameter(required = false)
    private String[] excludes;

    /**
     * List of a custom Java annotations (full class names) that are used in the checked
     * code to suppress errors. Those annotations must have at least
     * {@link RetentionPolicy#CLASS}. They can be applied to classes, their methods,
     * or fields. By default, {@code @de.thetaphi.forbiddenapis.SuppressForbidden}
     * can always be used, but needs the {@code forbidden-apis.jar} file in classpath
     * of compiled project, which may not be wanted.
     * Instead of a full class name, a glob pattern may be used (e.g.,
     * {@code **.SuppressForbidden}).
     * @since 1.8
     */
    @Parameter(required = false)
    private String[] suppressAnnotations;

    /**
     * Skip entire check. Most useful on the command line via "-Dforbiddenapis.skip=true".
     * @since 1.6
     */
    @Parameter(required = false, property = "forbiddenapis.skip", defaultValue = "false")
    private boolean skip;

    /** The project packaging (pom, jar, etc.). */
    @Parameter(defaultValue = "${project.packaging}", readonly = true, required = true)
    private String packaging;

    /** provided by the concrete Mojos for compile and test classes processing */
    protected abstract List<String> getClassPathElements();

    /** provided by the concrete Mojos for compile and test classes processing */
    protected abstract File getClassesDirectory();

    /** gets overridden for test, because it uses testTargetVersion as optional name to override */
    protected String getTargetVersion() {
        return targetVersion;
    }

    // Not in Java 5: @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        final Log log = getLog();

        if (skip) {
            log.info("Skipping forbidden-apis checks.");
            return;
        }

        // In multi-module projects, one may want to configure the plugin in the parent/root POM.
        // However, it should not be executed for this type of POMs.
        if ("pom".equals(packaging)) {
            log.info("Skipping execution for packaging \"" + packaging + "\"");
            return;
        }

        // set default param:
        if (includes == null)
            includes = new String[] { "**/*.class" };

        final URL[] urls;
        try {
            final List<String> cp = getClassPathElements();
            urls = new URL[cp.size()];
            int i = 0;
            for (final String cpElement : cp) {
                urls[i++] = new File(cpElement).toURI().toURL();
            }
            assert i == urls.length;
            if (log.isDebugEnabled())
                log.debug("Compile Classpath: " + Arrays.toString(urls));
        } catch (MalformedURLException e) {
            throw new MojoExecutionException("Failed to build classpath: " + e);
        }

        URLClassLoader urlLoader = null;
        final ClassLoader loader = (urls.length > 0)
                ? (urlLoader = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader()))
                : ClassLoader.getSystemClassLoader();

        try {
            final EnumSet<Checker.Option> options = EnumSet.noneOf(Checker.Option.class);
            if (internalRuntimeForbidden)
                options.add(INTERNAL_RUNTIME_FORBIDDEN);
            if (failOnMissingClasses)
                options.add(FAIL_ON_MISSING_CLASSES);
            if (failOnViolation)
                options.add(FAIL_ON_VIOLATION);
            if (failOnUnresolvableSignatures)
                options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES);
            final Checker checker = new Checker(loader, options) {
                @Override
                protected void logError(String msg) {
                    log.error(msg);
                }

                @Override
                protected void logWarn(String msg) {
                    log.warn(msg);
                }

                @Override
                protected void logInfo(String msg) {
                    log.info(msg);
                }
            };

            if (!checker.isSupportedJDK) {
                final String msg = String.format(Locale.ENGLISH,
                        "Your Java runtime (%s %s) is not supported by the forbiddenapis MOJO. Please run the checks with a supported JDK!",
                        System.getProperty("java.runtime.name"), System.getProperty("java.runtime.version"));
                if (failOnUnsupportedJava) {
                    throw new MojoExecutionException(msg);
                } else {
                    log.warn(msg);
                    return;
                }
            }

            if (suppressAnnotations != null) {
                for (String a : suppressAnnotations) {
                    checker.addSuppressAnnotation(a);
                }
            }

            log.info("Scanning for classes to check...");
            final File classesDirectory = getClassesDirectory();
            if (!classesDirectory.exists()) {
                log.warn("Classes directory does not exist, forbiddenapis check skipped: " + classesDirectory);
                return;
            }
            final DirectoryScanner ds = new DirectoryScanner();
            ds.setBasedir(classesDirectory);
            ds.setCaseSensitive(true);
            ds.setIncludes(includes);
            ds.setExcludes(excludes);
            ds.addDefaultExcludes();
            ds.scan();
            final String[] files = ds.getIncludedFiles();
            if (files.length == 0) {
                log.warn(String.format(Locale.ENGLISH,
                        "No classes found in '%s' (includes=%s, excludes=%s), forbiddenapis check skipped.",
                        classesDirectory.toString(), Arrays.toString(includes), Arrays.toString(excludes)));
                return;
            }

            try {
                final String sig = (signatures != null) ? signatures.trim() : null;
                if (sig != null && sig.length() != 0) {
                    log.info("Reading inline API signatures...");
                    checker.parseSignaturesString(sig);
                }
                if (bundledSignatures != null) {
                    String targetVersion = getTargetVersion();
                    if ("".equals(targetVersion))
                        targetVersion = null;
                    if (targetVersion == null) {
                        log.warn("The 'targetVersion' parameter or '${maven.compiler.target}' property is missing. "
                                + "Trying to read bundled JDK signatures without compiler target. "
                                + "You have to explicitely specify the version in the resource name.");
                    }
                    for (String bs : bundledSignatures) {
                        log.info("Reading bundled API signatures: " + bs);
                        checker.parseBundledSignatures(bs, targetVersion);
                    }
                }
                if (signaturesFiles != null)
                    for (final File f : signaturesFiles) {
                        log.info("Reading API signatures: " + f);
                        checker.parseSignaturesFile(new FileInputStream(f));
                    }
            } catch (IOException ioe) {
                throw new MojoExecutionException("IO problem while reading files with API signatures: " + ioe);
            } catch (ParseException pe) {
                throw new MojoExecutionException("Parsing signatures failed: " + pe.getMessage());
            }

            if (checker.hasNoSignatures()) {
                if (failOnUnresolvableSignatures) {
                    throw new MojoExecutionException(
                            "No API signatures found; use parameters 'signatures', 'bundledSignatures', and/or 'signaturesFiles' to define those!");
                } else {
                    log.info("Skipping execution because no API signatures are available.");
                    return;
                }
            }

            log.info("Loading classes to check...");
            try {
                for (String f : files) {
                    checker.addClassToCheck(new FileInputStream(new File(classesDirectory, f)));
                }
            } catch (IOException ioe) {
                throw new MojoExecutionException("Failed to load one of the given class files: " + ioe);
            }

            log.info("Scanning for API signatures and dependencies...");
            try {
                checker.run();
            } catch (ForbiddenApiException fae) {
                throw new MojoFailureException(fae.getMessage());
            }
        } finally {
            // Java 7 supports closing URLClassLoader, so check for Closeable interface:
            if (urlLoader instanceof Closeable)
                try {
                    ((Closeable) urlLoader).close();
                } catch (IOException ioe) {
                    // ignore
                }
        }
    }

}