JApiCmpProcessor.java
package japicmp.maven;
import com.google.common.base.Joiner;
import japicmp.cli.JApiCli;
import japicmp.cmp.JApiCmpArchive;
import japicmp.cmp.JarArchiveComparator;
import japicmp.cmp.JarArchiveComparatorOptions;
import japicmp.config.Options;
import japicmp.exception.JApiCmpException;
import japicmp.model.AccessModifier;
import japicmp.model.JApiClass;
import japicmp.model.JApiCompatibilityChangeType;
import japicmp.model.JApiSemanticVersionLevel;
import japicmp.output.html.HtmlOutput;
import japicmp.output.html.HtmlOutputGenerator;
import japicmp.output.html.HtmlOutputGeneratorOptions;
import japicmp.output.incompatible.IncompatibleErrorOutput;
import japicmp.output.markdown.MarkdownOutputGenerator;
import japicmp.output.markdown.config.MarkdownOptions;
import japicmp.output.semver.SemverOut;
import japicmp.output.stdout.StdoutOutputGenerator;
import japicmp.output.xml.XmlOutput;
import japicmp.output.xml.XmlOutputGenerator;
import japicmp.output.xml.XmlOutputGeneratorOptions;
import japicmp.util.FileHelper;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.resolution.*;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class JApiCmpProcessor {
final MavenParameters mavenParameters;
final PluginParameters pluginParameters;
final Log log;
private Options options;
public JApiCmpProcessor(final PluginParameters pluginParameters,
final MavenParameters mavenParameters,
final Log log) {
this.pluginParameters = pluginParameters;
this.mavenParameters = mavenParameters;
this.log = log;
}
public Optional<HtmlOutput> execute() throws MojoExecutionException, MojoFailureException {
if (pluginParameters.skip()) {
log.info("japicmp skipped.");
return Optional.empty();
}
if (skipModule()) {
return Optional.empty();
}
Options options = getOptions();
JarArchiveComparatorOptions comparatorOptions = JarArchiveComparatorOptions.of(options);
setUpClassPath(comparatorOptions);
setUpOverrideCompatibilityChanges(comparatorOptions);
JarArchiveComparator jarArchiveComparator = new JarArchiveComparator(comparatorOptions);
if (options.getNewArchives().isEmpty()) {
log.warn("Skipping execution because no new version could be resolved/found.");
return Optional.empty();
}
List<JApiClass> jApiClasses = jarArchiveComparator.compare(options.getOldArchives(),
options.getNewArchives());
try {
PostAnalysisScriptExecutor postAnalysisScriptExecutor = new PostAnalysisScriptExecutor();
postAnalysisScriptExecutor.apply(pluginParameters.parameter(), jApiClasses, log);
File jApiCmpBuildDir = createJApiCmpBaseDir();
SemverOut semverOut = new SemverOut(options, jApiClasses);
String semanticVersioningInformation = semverOut.generate();
if (!skipDiffReport()) {
generateDiffOutput(options, jApiClasses, jApiCmpBuildDir, semanticVersioningInformation);
}
if (!skipMarkdownReport()) {
generateMarkdownOutput(jApiClasses, jApiCmpBuildDir);
}
if (!skipXmlReport()) {
generateXmlOutput(jApiClasses, jApiCmpBuildDir, semanticVersioningInformation);
}
Optional<HtmlOutput> retVal = Optional.empty();
if (!skipHtmlReport()) {
retVal = Optional.of(generateHtmlOutput(jApiClasses, jApiCmpBuildDir,
semanticVersioningInformation));
}
breakBuildIfNecessary(jApiClasses, pluginParameters.parameter(), options,
jarArchiveComparator);
return retVal;
} catch (IOException e) {
throw new MojoFailureException(
String.format("Failed to construct output directory: %s", e.getMessage()), e);
}
}
private void setUpOverrideCompatibilityChanges(
final JarArchiveComparatorOptions comparatorOptions)
throws MojoFailureException {
if (pluginParameters.parameter().getOverrideCompatibilityChangeParameters() != null) {
final List<ConfigParameters.OverrideCompatibilityChangeParameter> overrideCompatibilityChangeParameters =
pluginParameters.parameter().getOverrideCompatibilityChangeParameters();
for (final ConfigParameters.OverrideCompatibilityChangeParameter configChange : overrideCompatibilityChangeParameters) {
final String compatibilityChange = configChange.getCompatibilityChange();
JApiCompatibilityChangeType foundChange = null;
for (JApiCompatibilityChangeType change : JApiCompatibilityChangeType.values()) {
if (change.name().equalsIgnoreCase(compatibilityChange)) {
foundChange = change;
break;
}
}
if (foundChange == null) {
throw new MojoFailureException("Unknown compatibility change '"
+ compatibilityChange
+ "'. Supported values: "
+ Joiner.on(',')
.join(JApiCompatibilityChangeType.values()));
}
final String semanticVersionLevel = configChange.getSemanticVersionLevel();
JApiSemanticVersionLevel foundSemanticVersionLevel = foundChange.getSemanticVersionLevel();
for (final JApiSemanticVersionLevel level : JApiSemanticVersionLevel.values()) {
if (level.name().equalsIgnoreCase(semanticVersionLevel)) {
foundSemanticVersionLevel = level;
break;
}
}
if (foundSemanticVersionLevel == null) {
throw new MojoFailureException("Unknown semantic version level '"
+ semanticVersionLevel
+ "'. Supported values: "
+ Joiner.on(',').join(
JApiSemanticVersionLevel.values()));
}
comparatorOptions.addOverrideCompatibilityChange(
new JarArchiveComparatorOptions.OverrideCompatibilityChange(foundChange,
configChange.isBinaryCompatible(),
configChange.isSourceCompatible(),
foundSemanticVersionLevel));
}
}
}
enum ConfigurationVersion {
OLD, NEW
}
private DefaultArtifact createDefaultArtifact(final MavenProject mavenProject,
final String version) {
final org.apache.maven.artifact.Artifact artifact = mavenProject.getArtifact();
return createDefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(),
artifact.getClassifier(), artifact.getType(), version);
}
private DefaultArtifact createDefaultArtifact(final String groupId,
final String artifactId,
final String classifier,
final String type,
final String version) {
String mappedType = type;
if ("bundle".equals(type) || "ejb".equals(type)) {
mappedType = "jar";
}
return new DefaultArtifact(groupId, artifactId, classifier, mappedType, version);
}
private Artifact getComparisonArtifact()
throws MojoFailureException {
final MavenProject mavenProject = mavenParameters.mavenProject();
// create a version range of the form (,<current-version-of-the-project>), that includes all
// versions up to this version
final DefaultArtifact artifactVersionRange = createDefaultArtifact(mavenProject,
mavenParameters.versionRangeWithProjectVersion());
final VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifactVersionRange,
mavenParameters.remoteRepos(),
null);
try {
log.debug("Trying version range: " + versionRangeRequest);
final VersionRangeResult versionRangeResult = mavenParameters.repoSystem().resolveVersionRange(
mavenParameters.repoSession(), versionRangeRequest);
log.debug("Version range result: " + versionRangeRequest);
final List<org.eclipse.aether.version.Version> versions = versionRangeResult.getVersions();
filterSnapshots(versions);
filterVersionPattern(versions);
log.debug("Version range after filtering: " + versions);
if (!versions.isEmpty()) {
final DefaultArtifact artifactVersion = createDefaultArtifact(mavenProject,
versions.get(versions.size() - 1)
.toString());
final ArtifactRequest artifactRequest = new ArtifactRequest(artifactVersion,
mavenParameters.remoteRepos(), null);
final ArtifactResult artifactResult = mavenParameters.repoSystem().resolveArtifact(
mavenParameters.repoSession(), artifactRequest);
processArtifactResult(artifactVersion, artifactResult);
return artifactResult.getArtifact();
} else {
if (ignoreMissingOldVersion(ConfigurationVersion.OLD)) {
log.warn("Ignoring missing old artifact version: "
+ artifactVersionRange.getGroupId()
+ ":"
+ artifactVersionRange.getArtifactId());
return null;
} else {
throw new MojoFailureException("Could not find previous version for artifact: "
+ artifactVersionRange.getGroupId()
+ ":"
+ artifactVersionRange.getArtifactId());
}
}
} catch (final VersionRangeResolutionException | ArtifactResolutionException e) {
log.error("Failed to retrieve comparison artifact: " + e.getMessage(), e);
throw new MojoFailureException(e.getMessage(), e);
}
}
private void processArtifactResult(final DefaultArtifact artifactVersion,
final ArtifactResult artifactResult)
throws MojoFailureException {
if (artifactResult.getExceptions() != null && !artifactResult.getExceptions().isEmpty()) {
final List<Exception> exceptions = artifactResult.getExceptions();
for (Exception exception : exceptions) {
log.debug(exception.getMessage(), exception);
}
}
if (artifactResult.isMissing()) {
if (ignoreMissingArtifact(ConfigurationVersion.OLD)) {
log.warn("Ignoring missing artifact: " + artifactResult.getArtifact());
} else {
throw new MojoFailureException("Could not resolve artifact: " + artifactVersion);
}
}
}
private void filterVersionPattern(
final List<org.eclipse.aether.version.Version> availableVersions)
throws MojoFailureException {
if (pluginParameters.parameter().getOldVersionPattern() != null) {
final String versionPattern = pluginParameters.parameter().getOldVersionPattern();
Pattern pattern;
try {
pattern = Pattern.compile(versionPattern);
} catch (PatternSyntaxException e) {
throw new MojoFailureException("Could not compile provided versionPattern '"
+ versionPattern
+ "' as regular expression: "
+ e.getMessage(), e);
}
for (final Iterator<org.eclipse.aether.version.Version> versionIterator =
availableVersions.iterator(); versionIterator.hasNext(); ) {
final org.eclipse.aether.version.Version version = versionIterator.next();
final Matcher matcher = pattern.matcher(version.toString());
if (!matcher.matches()) {
versionIterator.remove();
log.debug("Filtering version '"
+ version
+ "' because it does not match configured versionPattern '"
+ versionPattern
+ "'.");
}
}
} else {
log.debug("Parameter <oldVersionPattern> not configured, i.e. no version filtered.");
}
}
private void filterSnapshots(final List<org.eclipse.aether.version.Version> versions) {
if (!pluginParameters.parameter().isIncludeSnapshots()) {
versions.removeIf(
version -> version.toString() != null && version.toString().endsWith("SNAPSHOT"));
}
}
private void populateArchivesListsFromParameters(final List<JApiCmpArchive> oldArchives,
final List<JApiCmpArchive> newArchives)
throws MojoFailureException {
if (pluginParameters.oldVersion() != null) {
oldArchives.addAll(retrieveFileFromConfiguration(pluginParameters.oldVersion(), "oldVersion",
ConfigurationVersion.OLD));
}
if (pluginParameters.oldVersions() != null) {
for (final DependencyDescriptor dependencyDescriptor : pluginParameters.oldVersions()) {
if (dependencyDescriptor != null) {
oldArchives.addAll(retrieveFileFromConfiguration(dependencyDescriptor, "oldVersions",
ConfigurationVersion.OLD));
}
}
}
if (pluginParameters.oldVersion() == null && pluginParameters.oldVersions() == null) {
final Artifact comparisonArtifact = getComparisonArtifact();
if (comparisonArtifact != null && comparisonArtifact.getVersion() != null) {
final Artifact artifact = resolveArtifact(comparisonArtifact, ConfigurationVersion.OLD);
if (artifact != null) {
final File file = artifact.getFile();
if (file != null) {
oldArchives.add(new JApiCmpArchive(file, FileHelper.guessVersion(file)));
} else {
handleMissingArtifactFile(artifact);
}
}
}
}
if (pluginParameters.newVersion() != null) {
newArchives.addAll(retrieveFileFromConfiguration(pluginParameters.newVersion(), "newVersion",
ConfigurationVersion.NEW));
}
if (pluginParameters.newVersions() != null) {
for (final DependencyDescriptor dependencyDescriptor : pluginParameters.newVersions()) {
if (dependencyDescriptor != null) {
newArchives.addAll(retrieveFileFromConfiguration(dependencyDescriptor, "newVersions",
ConfigurationVersion.NEW));
}
}
}
if (pluginParameters.newVersion() == null && pluginParameters.newVersions() == null) {
final MavenProject mavenProject = mavenParameters.mavenProject();
if (mavenProject != null && mavenProject.getArtifact() != null) {
final DefaultArtifact defaultArtifact = createDefaultArtifact(mavenProject,
mavenProject.getVersion());
final Artifact artifact = resolveArtifact(defaultArtifact, ConfigurationVersion.NEW);
if (artifact != null) {
final File file = artifact.getFile();
if (file != null) {
try (JarFile jarFile = new JarFile(file)) {
log.debug("Could open file '"
+ file.getAbsolutePath()
+ "' of artifact as jar archive: "
+ jarFile.getName());
newArchives.add(new JApiCmpArchive(file, FileHelper.guessVersion(file)));
} catch (IOException e) {
log.warn("No new version specified and file '"
+ file.getAbsolutePath()
+ "' of artifact could not be opened as jar archive: "
+ e.getMessage(), e);
}
} else {
log.warn("Artifact "
+ artifact
+ " does not have a file. Cannot resolve artifact automatically.");
}
}
}
}
if (oldArchives.isEmpty()) {
String message =
"Please provide at least one resolvable old version using one of the configuration "
+ "elements <oldVersion/> or <oldVersions/>.";
if (ignoreMissingArtifact(ConfigurationVersion.OLD)) {
log.warn(message);
} else {
throw new MojoFailureException(message);
}
}
if (newArchives.isEmpty()) {
String message =
"Please provide at least one resolvable new version using one of the configuration "
+ "elements <newVersion/> or <newVersions/>.";
if (ignoreMissingArtifact(ConfigurationVersion.NEW)) {
log.warn(message);
} else {
throw new MojoFailureException(message);
}
}
}
void breakBuildIfNecessary(final List<JApiClass> jApiClasses,
final ConfigParameters parameterParam,
final Options options,
final JarArchiveComparator jarArchiveComparator)
throws MojoFailureException, MojoExecutionException {
if (breakBuildBasedOnSemanticVersioning(parameterParam)) {
options.setErrorOnSemanticIncompatibility(true);
}
if (breakBuildBasedOnSemanticVersioningForMajorVersionZero(parameterParam)) {
options.setErrorOnSemanticIncompatibilityForMajorVersionZero(true);
}
if (breakBuildOnBinaryIncompatibleModifications(parameterParam)) {
options.setErrorOnBinaryIncompatibility(true);
}
if (breakBuildOnSourceIncompatibleModifications(parameterParam)) {
options.setErrorOnSourceIncompatibility(true);
}
if (breakBuildOnModifications(parameterParam)) {
options.setErrorOnModifications(true);
}
if (!parameterParam.isBreakBuildIfCausedByExclusion()) {
options.setErrorOnExclusionIncompatibility(false);
}
if (pluginParameters.parameter().getIgnoreMissingOldVersion()) {
options.setIgnoreMissingOldVersion(true);
}
if (pluginParameters.parameter().getIgnoreMissingNewVersion()) {
options.setIgnoreMissingNewVersion(true);
}
IncompatibleErrorOutput errorOutput = new IncompatibleErrorOutput(options, jApiClasses,
jarArchiveComparator) {
@Override
protected void warn(String msg, Throwable error) {
log.warn(msg, error);
}
};
try {
errorOutput.generate();
} catch (JApiCmpException e) {
if (e.getReason() == JApiCmpException.Reason.IncompatibleChange) {
throw new MojoFailureException(e.getMessage());
} else {
throw new MojoExecutionException("Error while checking for incompatible changes", e);
}
}
}
Options getOptions() throws MojoFailureException {
if (this.options != null) {
return this.options;
}
this.options = Options.newDefault();
populateArchivesListsFromParameters(this.options.getOldArchives(),
this.options.getNewArchives());
ConfigParameters parameterParam = pluginParameters.parameter();
if (parameterParam != null) {
String accessModifierArg = parameterParam.getAccessModifier();
if (accessModifierArg != null) {
try {
AccessModifier accessModifier = AccessModifier.valueOf(accessModifierArg.toUpperCase());
this.options.setAccessModifier(accessModifier);
} catch (IllegalArgumentException e) {
throw new MojoFailureException(
String.format(
"Invalid value for option accessModifier: %s. Possible values are: %s.",
accessModifierArg, AccessModifier.listOfAccessModifier()), e);
}
}
this.options.setOutputOnlyBinaryIncompatibleModifications(
parameterParam.getOnlyBinaryIncompatible());
this.options.setOutputOnlyModifications(parameterParam.getOnlyModified());
List<String> excludes = parameterParam.getExcludes();
if (excludes != null) {
for (String exclude : excludes) {
this.options.addExcludeFromArgument(Optional.ofNullable(exclude),
parameterParam.isExcludeExclusively());
}
}
List<String> includes = parameterParam.getIncludes();
if (includes != null) {
for (String include : includes) {
this.options.addIncludeFromArgument(Optional.ofNullable(include),
parameterParam.isIncludeExlusively());
}
}
this.options.setIncludeSynthetic(parameterParam.getIncludeSynthetic());
this.options.setIgnoreMissingClasses(parameterParam.getIgnoreMissingClasses());
List<String> ignoreMissingClassesByRegularExpressions =
parameterParam.getIgnoreMissingClassesByRegularExpressions();
if (ignoreMissingClassesByRegularExpressions != null) {
for (String ignoreMissingClassRegularExpression :
ignoreMissingClassesByRegularExpressions) {
this.options.addIgnoreMissingClassRegularExpression(ignoreMissingClassRegularExpression);
}
}
String htmlStylesheet = parameterParam.getHtmlStylesheet();
if (htmlStylesheet != null) {
this.options.setHtmlStylesheet(Optional.of(htmlStylesheet));
}
this.options.setNoAnnotations(parameterParam.getNoAnnotations());
this.options.setReportOnlyFilename(parameterParam.isReportOnlyFilename());
this.options.setReportOnlySummary(parameterParam.isReportOnlySummary());
}
return this.options;
}
boolean breakBuildOnModifications(final ConfigParameters params) {
return pluginParameters.breakBuild().onModifications()
| params.getBreakBuildOnModifications();
}
boolean breakBuildOnBinaryIncompatibleModifications(final ConfigParameters params) {
return pluginParameters.breakBuild().onBinaryIncompatibleModifications()
| params.getBreakBuildOnBinaryIncompatibleModifications();
}
boolean breakBuildOnSourceIncompatibleModifications(final ConfigParameters params) {
return pluginParameters.breakBuild().onSourceIncompatibleModifications()
| params.getBreakBuildOnSourceIncompatibleModifications();
}
boolean breakBuildBasedOnSemanticVersioning(final ConfigParameters params) {
return pluginParameters.breakBuild().onSemanticVersioning()
| params.getBreakBuildBasedOnSemanticVersioning();
}
boolean breakBuildBasedOnSemanticVersioningForMajorVersionZero(final ConfigParameters params) {
return pluginParameters.breakBuild().onSemanticVersioningForMajorVersionZero()
| params.isBreakBuildBasedOnSemanticVersioningForMajorVersionZero();
}
File createJApiCmpBaseDir() throws MojoFailureException {
File baseDir;
try {
if (pluginParameters.outputDirectory() != null) {
baseDir = pluginParameters.outputDirectory();
} else {
final File projectBuildDirParam = pluginParameters.projectBuildDir();
baseDir = new File(
projectBuildDirParam.getCanonicalPath() + File.separator + "japicmp");
}
boolean madeDir = true;
if (!baseDir.exists()) {
madeDir = baseDir.mkdirs();
}
if ((!madeDir || !baseDir.isDirectory()) || !baseDir.canWrite()) {
throw new IOException("mkDirs failed");
}
} catch (IOException e) {
throw new MojoFailureException("Failed to create output directory: " + e.getMessage(), e);
}
return baseDir;
}
private void generateDiffOutput(final Options options,
final List<JApiClass> jApiClasses,
final File jApiCmpBuildDir,
final String semanticVersioningInformation)
throws IOException, MojoFailureException {
StdoutOutputGenerator stdoutOutputGenerator = new StdoutOutputGenerator(options, jApiClasses);
String diffOutput = stdoutOutputGenerator.generate();
diffOutput += "\nSemantic versioning suggestion: " + semanticVersioningInformation;
File output = new File(
jApiCmpBuildDir.getCanonicalPath() + File.separator + createFilename() + ".diff");
writeToFile(diffOutput, output);
}
private void generateMarkdownOutput(final List<JApiClass> jApiClasses, final File jApiCmpBuildDir)
throws IOException, MojoFailureException {
if (pluginParameters.isWriteToFiles()) {
MarkdownOptions mdOptions = MarkdownOptions.newDefault(options);
if (pluginParameters.parameter().getMarkdownTitle() != null) {
mdOptions.title.report = pluginParameters.parameter().getMarkdownTitle();
}
MarkdownOutputGenerator mdOut = new MarkdownOutputGenerator(mdOptions, jApiClasses);
String markdown = mdOut.generate();
File output = new File(
jApiCmpBuildDir.getCanonicalPath() + File.separator + createFilename() + ".md");
writeToFile(markdown, output);
}
}
private void generateXmlOutput(final List<JApiClass> jApiClasses,
final File jApiCmpBuildDir,
final String semanticVersioningInformation)
throws IOException {
if (pluginParameters.isWriteToFiles()) {
XmlOutput xmlOutput = createXmlOutput(jApiClasses, jApiCmpBuildDir,
semanticVersioningInformation);
List<File> filesWritten = XmlOutputGenerator.writeToFiles(options, xmlOutput);
for (File file : filesWritten) {
log.info("Created file '" + file.getAbsolutePath() + "'.");
}
}
}
private XmlOutput createXmlOutput(final List<JApiClass> jApiClasses,
final File jApiCmpBuildDir,
final String semanticVersioningInformation)
throws IOException {
String filename = createFilename();
options.setXmlOutputFile(
Optional.of(jApiCmpBuildDir.getCanonicalPath() + File.separator + filename + ".xml"));
XmlOutputGeneratorOptions xmlOutputGeneratorOptions = new XmlOutputGeneratorOptions();
xmlOutputGeneratorOptions.setCreateSchemaFile(true);
xmlOutputGeneratorOptions.setSemanticVersioningInformation(semanticVersioningInformation);
String optionalTitle = pluginParameters.parameter().getHtmlTitle();
xmlOutputGeneratorOptions.setTitle(
optionalTitle != null ? optionalTitle : options.getDifferenceDescription());
XmlOutputGenerator xmlGenerator = new XmlOutputGenerator(jApiClasses, options,
xmlOutputGeneratorOptions);
return xmlGenerator.generate();
}
private HtmlOutput generateHtmlOutput(final List<JApiClass> jApiClasses,
final File jApiCmpBuildDir,
final String semanticVersioningInformation)
throws IOException {
HtmlOutput htmlOutput = createHtmlOutput(jApiClasses, jApiCmpBuildDir,
semanticVersioningInformation);
if (pluginParameters.isWriteToFiles() && options.getHtmlOutputFile().isPresent()) {
Path path = Paths.get(options.getHtmlOutputFile().get());
Files.write(path, htmlOutput.getHtml().getBytes(StandardCharsets.UTF_8));
log.info("Written file '" + path + "'.");
}
return htmlOutput;
}
private HtmlOutput createHtmlOutput(final List<JApiClass> jApiClasses,
final File jApiCmpBuildDir,
final String semanticVersioningInformation)
throws IOException {
final String filename = createFilename();
options.setHtmlOutputFile(
Optional.of(jApiCmpBuildDir.getCanonicalPath() + File.separator + filename + ".html"));
HtmlOutputGeneratorOptions htmlOutputGeneratorOptions = new HtmlOutputGeneratorOptions();
htmlOutputGeneratorOptions.setSemanticVersioningInformation(semanticVersioningInformation);
final String title = pluginParameters.parameter().getHtmlTitle();
htmlOutputGeneratorOptions.setTitle(title != null ? title : options.getDifferenceDescription());
final HtmlOutputGenerator htmlOutputGenerator = new HtmlOutputGenerator(jApiClasses, options,
htmlOutputGeneratorOptions);
return htmlOutputGenerator.generate();
}
boolean skipModule() {
SkipModuleStrategy skipModuleStrategy = new SkipModuleStrategy(pluginParameters,
mavenParameters, log);
return skipModuleStrategy.skip();
}
boolean skipDiffReport() {
return pluginParameters.skipReport().diffReport() |
pluginParameters.parameter().skipDiffReport();
}
boolean skipHtmlReport() {
return pluginParameters.skipReport().htmlReport() |
pluginParameters.parameter().skipHtmlReport();
}
boolean skipMarkdownReport() {
return pluginParameters.skipReport().markdownReport() |
pluginParameters.parameter().skipMarkdownReport();
}
boolean skipXmlReport() {
return pluginParameters.skipReport().xmlReport() |
pluginParameters.parameter().skipXmlReport();
}
private String createFilename() {
String filename = "japicmp";
String executionId = mavenParameters.mojoExecution().getExecutionId();
if (executionId != null && !"default".equals(executionId)) {
filename = executionId;
}
StringBuilder sb = new StringBuilder();
for (char c : filename.toCharArray()) {
if (c == '.' || Character.isJavaIdentifierPart(c) || c == '-') {
sb.append(c);
}
}
return sb.toString();
}
private void setUpClassPath(final JarArchiveComparatorOptions comparatorOptions)
throws MojoFailureException {
if (pluginParameters != null) {
if (pluginParameters.dependencies() != null) {
if (pluginParameters.oldClassPathDependencies() != null
|| pluginParameters.newClassPathDependencies() != null) {
throw new MojoFailureException(
"Please specify either a <dependencies/> element or the two elements "
+ "<oldClassPathDependencies/> and <newClassPathDependencies/>. "
+
"With <dependencies/> you can specify one common classpath for both versions and "
+ "with <oldClassPathDependencies/> and <newClassPathDependencies/> a "
+ "separate classpath for the new and old version.");
} else {
log.debug("Element <dependencies/> found. Using "
+ JApiCli.ClassPathMode.ONE_COMMON_CLASSPATH);
for (Dependency dependency : pluginParameters.dependencies()) {
List<JApiCmpArchive> jApiCmpArchives = resolveDependencyToFile("dependencies",
dependency,
ConfigurationVersion.NEW);
for (JApiCmpArchive jApiCmpArchive : jApiCmpArchives) {
comparatorOptions.getClassPathEntries().add(
jApiCmpArchive.getFile().getAbsolutePath());
}
comparatorOptions.setClassPathMode(
JarArchiveComparatorOptions.ClassPathMode.ONE_COMMON_CLASSPATH);
}
}
} else {
if (pluginParameters.oldClassPathDependencies() != null
|| pluginParameters.newClassPathDependencies() != null) {
log.debug("At least one of the elements <oldClassPathDependencies/> or "
+ "<newClassPathDependencies/> found. Using "
+ JApiCli.ClassPathMode.TWO_SEPARATE_CLASSPATHS);
if (pluginParameters.oldClassPathDependencies() != null) {
for (Dependency dependency : pluginParameters.oldClassPathDependencies()) {
List<JApiCmpArchive> jApiCmpArchives = resolveDependencyToFile(
"oldClassPathDependencies", dependency, ConfigurationVersion.OLD);
for (JApiCmpArchive archive : jApiCmpArchives) {
comparatorOptions.getOldClassPath().add(archive.getFile().getAbsolutePath());
}
}
}
if (pluginParameters.newClassPathDependencies() != null) {
for (Dependency dependency : pluginParameters.newClassPathDependencies()) {
List<JApiCmpArchive> jApiCmpArchives = resolveDependencyToFile(
"newClassPathDependencies", dependency, ConfigurationVersion.NEW);
for (JApiCmpArchive archive : jApiCmpArchives) {
comparatorOptions.getNewClassPath().add(archive.getFile().getAbsolutePath());
}
}
}
comparatorOptions.setClassPathMode(
JarArchiveComparatorOptions.ClassPathMode.TWO_SEPARATE_CLASSPATHS);
} else {
log.debug(
"None of the elements <oldClassPathDependencies/>, <newClassPathDependencies/> or"
+ " <dependencies/> found. Using "
+ JApiCli.ClassPathMode.ONE_COMMON_CLASSPATH);
comparatorOptions.setClassPathMode(
JarArchiveComparatorOptions.ClassPathMode.ONE_COMMON_CLASSPATH);
}
}
}
setUpClassPathUsingMavenProject(comparatorOptions);
}
private void setUpClassPathUsingMavenProject(final JarArchiveComparatorOptions comparatorOptions)
throws MojoFailureException {
MavenProject mavenProject = mavenParameters.mavenProject();
notNull(mavenProject, "Maven parameter mavenProject should be provided by maven container.");
Set<String> classPathEntries = new HashSet<>();
for (Artifact artifact : getCompileArtifacts(mavenProject)) {
File resolvedFile = artifact.getFile();
if (resolvedFile != null) {
String absolutePath = resolvedFile.getAbsolutePath();
if (classPathEntries.add(absolutePath)) {
log.debug("Adding to classpath: " + absolutePath);
}
} else {
handleMissingArtifactFile(artifact);
}
}
comparatorOptions.getClassPathEntries().addAll(classPathEntries);
}
private Set<Artifact> getCompileArtifacts(final MavenProject mavenProject) throws MojoFailureException {
// dependencies that this project has, including transitive ones
Set<org.apache.maven.artifact.Artifact> projectDependencies =
mavenProject.getArtifacts();
HashSet<Artifact> result = new HashSet<>(1+projectDependencies.size());
// Include the project artifact; use the reactor to resolve the project artifact in case it's not being built
Artifact project = RepositoryUtils.toArtifact(mavenProject.getArtifact());
result.add(resolveArtifact(project, ConfigurationVersion.NEW));
for (org.apache.maven.artifact.Artifact dep : projectDependencies) {
if (dep.getArtifactHandler().isAddedToClasspath()) {
if (org.apache.maven.artifact.Artifact.SCOPE_COMPILE.equals(dep.getScope())
|| org.apache.maven.artifact.Artifact.SCOPE_PROVIDED.equals(dep.getScope())
|| org.apache.maven.artifact.Artifact.SCOPE_SYSTEM.equals(dep.getScope())) {
result.add(RepositoryUtils.toArtifact(dep));
}
}
}
return result;
}
private void handleMissingArtifactFile(final Artifact artifact) {
if (pluginParameters.parameter().isIgnoreMissingOptionalDependency()) {
log.info("Ignoring missing optional dependency: " + toDescriptor(artifact));
} else {
log.warn("Could not resolve optional artifact: " + toDescriptor(artifact));
}
}
private String toDescriptor(final Artifact artifact) {
return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion();
}
private List<JApiCmpArchive> retrieveFileFromConfiguration(
final DependencyDescriptor dependencyDescriptor,
final String parameterName,
final ConfigurationVersion configurationVersion)
throws MojoFailureException {
List<JApiCmpArchive> jApiCmpArchives;
if (dependencyDescriptor instanceof Dependency) {
Dependency dependency = (Dependency) dependencyDescriptor;
jApiCmpArchives = resolveDependencyToFile(parameterName, dependency, configurationVersion);
} else if (dependencyDescriptor instanceof ConfigurationFile) {
ConfigurationFile configurationFile = (ConfigurationFile) dependencyDescriptor;
jApiCmpArchives = resolveConfigurationFileToFile(parameterName, configurationFile,
configurationVersion);
} else {
throw new MojoFailureException(
"DependencyDescriptor is not of type <dependency/> nor of type <configurationFile/>.");
}
return jApiCmpArchives;
}
private List<JApiCmpArchive> retrieveFileFromConfiguration(final Version version,
final String parameterName,
final ConfigurationVersion configurationVersion)
throws MojoFailureException {
if (version != null) {
final Dependency dependency = version.getDependency();
if (dependency != null) {
return resolveDependencyToFile(parameterName, dependency, configurationVersion);
} else if (version.getFile() != null) {
final ConfigurationFile configurationFile = version.getFile();
return resolveConfigurationFileToFile(parameterName, configurationFile,
configurationVersion);
} else {
throw new MojoFailureException("Missing configuration parameter 'dependency'.");
}
}
throw new MojoFailureException(
String.format("Missing configuration parameter: %s", parameterName));
}
private List<JApiCmpArchive> resolveConfigurationFileToFile(final String parameterName,
final ConfigurationFile configurationFile,
final ConfigurationVersion configurationVersion)
throws MojoFailureException {
String path = configurationFile.getPath();
if (path == null) {
throw new MojoFailureException(
String.format(
"The path element in the configuration of the plugin is missing for %s.",
parameterName));
}
File file = new File(path);
if (!file.exists()) {
if (!ignoreMissingArtifact(configurationVersion)) {
throw new MojoFailureException(
String.format("The path '%s' does not point to an existing file.", path));
} else {
log.warn("The file given by path '" + file.getAbsolutePath() + "' does not exist.");
}
}
if (!file.isFile() || !file.canRead()) {
if (!ignoreMissingArtifact(configurationVersion)) {
throw new MojoFailureException(
String.format(
"The file given by path '%s' is either not a file or is not readable.",
path));
} else {
log.warn("The file given by path '"
+ file.getAbsolutePath()
+ "' is either not a file or is not readable.");
}
}
return Collections.singletonList(new JApiCmpArchive(file, FileHelper.guessVersion(file)));
}
List<JApiCmpArchive> resolveDependencyToFile(final String parameterName,
final Dependency dependency,
final ConfigurationVersion configurationVersion)
throws MojoFailureException {
List<JApiCmpArchive> jApiCmpArchives = new ArrayList<>();
log.debug("Trying to resolve dependency '" + dependency + "' to file.");
if (dependency.getSystemPath() == null) {
String descriptor = dependency.getGroupId()
+ ":"
+ dependency.getArtifactId()
+ ":"
+ dependency.getVersion();
log.debug(parameterName + ": " + descriptor);
// Use the reactor to resolve all artifacts, even the project artifact. This
// way the project artifact can be pulled from the repository in cases where
// it's not being explicitly built; i.e., mvn clean site
final Artifact artifact = resolveArtifact(dependency, configurationVersion);
if (artifact != null) {
final File file = artifact.getFile();
if (file != null) {
jApiCmpArchives.add(new JApiCmpArchive(file, artifact.getVersion()));
} else {
handleMissingArtifactFile(artifact);
}
}
if (jApiCmpArchives.isEmpty()) {
String message = String.format("Could not resolve dependency with descriptor '%s'.",
descriptor);
if (ignoreMissingArtifact(configurationVersion)) {
log.warn(message);
} else {
throw new MojoFailureException(message);
}
}
} else {
String systemPath = dependency.getSystemPath();
Pattern pattern = Pattern.compile("\\$\\{([^}])");
Matcher matcher = pattern.matcher(systemPath);
if (matcher.matches()) {
for (int i = 1; i <= matcher.groupCount(); i++) {
String property = matcher.group(i);
String propertyResolved = mavenParameters.mavenProject().getProperties().getProperty(
property);
if (propertyResolved != null) {
systemPath = systemPath.replaceAll("${" + property + "}", propertyResolved);
} else {
throw new MojoFailureException("Could not resolve property '" + property + "'.");
}
}
}
File file = new File(systemPath);
boolean addFile = true;
if (!file.exists()) {
if (ignoreMissingArtifact(configurationVersion)) {
log.warn("Could not find file, but ignoreMissingOldVersion is set to true: "
+ file.getAbsolutePath());
} else {
throw new MojoFailureException("File '" + file.getAbsolutePath() + "' does not exist.");
}
addFile = false;
}
if (!file.canRead()) {
if (ignoreMissingArtifact(configurationVersion)) {
log.warn("File is not readable, but ignoreMissingOldVersion is set tot true: "
+ file.getAbsolutePath());
} else {
throw new MojoFailureException("File '" + file.getAbsolutePath() + "' is not readable.");
}
addFile = false;
}
String version = FileHelper.guessVersion(file);
if (addFile) {
jApiCmpArchives.add(new JApiCmpArchive(file, version));
}
}
return jApiCmpArchives;
}
private boolean ignoreMissingArtifact(final ConfigurationVersion configurationVersion) {
return ignoreNonResolvableArtifacts()
|| ignoreMissingOldVersion(configurationVersion)
|| ignoreMissingNewVersion(configurationVersion);
}
private boolean ignoreNonResolvableArtifacts() {
boolean ignoreNonResolvableArtifacts = false;
ConfigParameters parameterParam = pluginParameters.parameter();
if (parameterParam != null) {
String ignoreNonResolvableArtifactsAsString =
parameterParam.getIgnoreNonResolvableArtifacts();
if (Boolean.TRUE.toString().equalsIgnoreCase(ignoreNonResolvableArtifactsAsString)) {
ignoreNonResolvableArtifacts = true;
}
}
return ignoreNonResolvableArtifacts;
}
private boolean ignoreMissingOldVersion(final ConfigurationVersion configurationVersion) {
return (configurationVersion == ConfigurationVersion.OLD &&
pluginParameters.parameter().getIgnoreMissingOldVersion());
}
private boolean ignoreMissingNewVersion(final ConfigurationVersion configurationVersion) {
return (configurationVersion == ConfigurationVersion.NEW &&
pluginParameters.parameter().getIgnoreMissingNewVersion());
}
private void writeToFile(final String output, final File outputFile) throws MojoFailureException {
try (OutputStreamWriter fileWriter = new OutputStreamWriter(
Files.newOutputStream(outputFile.toPath()), StandardCharsets.UTF_8)) {
fileWriter.write(output);
log.info("Written file '" + outputFile.getAbsolutePath() + "'.");
} catch (IOException e) {
throw new MojoFailureException(String.format("Failed to write file: %s", e.getMessage()), e);
}
}
private Artifact resolveArtifact(final Dependency dependency,
final ConfigurationVersion configurationVersion)
throws MojoFailureException {
notNull(mavenParameters.artifactRepositories(),
"Maven parameter artifactRepositories should be provided by maven container.");
Artifact artifact = createDefaultArtifact(dependency.getGroupId(), dependency.getArtifactId(),
dependency.getClassifier(), dependency.getType(),
dependency.getVersion());
return resolveArtifact(artifact, configurationVersion);
}
private Artifact resolveArtifact(final Artifact artifact,
final ConfigurationVersion configurationVersion)
throws MojoFailureException {
notNull(mavenParameters.repoSystem(),
"Maven parameter repoSystem should be provided by maven container.");
notNull(mavenParameters.repoSession(),
"Maven parameter repoSession should be provided by maven container.");
ArtifactRequest request = new ArtifactRequest();
request.setArtifact(artifact);
request.setRepositories(mavenParameters.remoteRepos());
ArtifactResult resolutionResult;
try {
log.debug("Trying to resolve artifact: " + request);
resolutionResult = mavenParameters.repoSystem().resolveArtifact(mavenParameters.repoSession(),
request);
log.debug("Resolved artifact: " + resolutionResult);
if (resolutionResult != null) {
if (resolutionResult.getExceptions() != null && !resolutionResult.getExceptions()
.isEmpty()) {
List<Exception> exceptions = resolutionResult.getExceptions();
for (Exception exception : exceptions) {
log.debug(exception.getMessage(), exception);
}
}
if (resolutionResult.isMissing()) {
if (ignoreMissingArtifact(configurationVersion)) {
log.warn("Ignoring missing artifact " + request.getArtifact());
} else {
throw new MojoFailureException("Could not resolve artifact " + request.getArtifact());
}
return null;
}
return resolutionResult.getArtifact();
}
} catch (final ArtifactResolutionException e) {
if (ignoreMissingArtifact(configurationVersion)) {
log.warn(e.getMessage());
} else {
throw new MojoFailureException(e.getMessage(), e);
}
}
return null;
}
private <T> void notNull(final T value, final String msg) throws MojoFailureException {
if (value == null) {
throw new MojoFailureException(msg);
}
}
}