package japicmp.maven;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import japicmp.cli.JApiCli;
import japicmp.cmp.JApiCmpArchive;
import japicmp.cmp.JarArchiveComparator;
import japicmp.cmp.JarArchiveComparatorOptions;
import japicmp.config.Options;
import japicmp.filter.ClassFilter;
import japicmp.model.*;
import japicmp.output.Filter;
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.versioning.SemanticVersion;
import japicmp.versioning.VersionChange;
import javassist.CtClass;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
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;
@Mojo(name = "cmp", requiresDependencyResolution = ResolutionScope.COMPILE, defaultPhase = LifecyclePhase.VERIFY, threadSafe = true)
public class JApiCmpMojo extends AbstractMojo {
@org.apache.maven.plugins.annotations.Parameter(required = false)
private Version oldVersion;
@org.apache.maven.plugins.annotations.Parameter(required = false)
private List<DependencyDescriptor> oldVersions;
@org.apache.maven.plugins.annotations.Parameter(required = false)
private Version newVersion;
@org.apache.maven.plugins.annotations.Parameter(required = false)
private List<DependencyDescriptor> newVersions;
@org.apache.maven.plugins.annotations.Parameter(required = false)
private Parameter parameter;
@org.apache.maven.plugins.annotations.Parameter(required = false)
private List<Dependency> dependencies;
@org.apache.maven.plugins.annotations.Parameter(required = false)
private List<Dependency> oldClassPathDependencies;
@org.apache.maven.plugins.annotations.Parameter(required = false)
private List<Dependency> newClassPathDependencies;
@org.apache.maven.plugins.annotations.Parameter(property = "japicmp.skip", required = false)
private String skip;
@org.apache.maven.plugins.annotations.Parameter(property = "project.build.directory", required = true)
private File projectBuildDir;
@Component
private ArtifactFactory artifactFactory;
@Component
private ArtifactResolver artifactResolver;
@org.apache.maven.plugins.annotations.Parameter(defaultValue = "${localRepository}")
private ArtifactRepository localRepository;
@org.apache.maven.plugins.annotations.Parameter(defaultValue = "${project.remoteArtifactRepositories}")
private List<ArtifactRepository> artifactRepositories;
@org.apache.maven.plugins.annotations.Parameter(defaultValue = "${project}")
private MavenProject mavenProject;
@org.apache.maven.plugins.annotations.Parameter(defaultValue = "${mojoExecution}", readonly = true)
private MojoExecution mojoExecution;
@org.apache.maven.plugins.annotations.Parameter(defaultValue = "(,${project.version})", readonly = true)
private String versionRangeWithProjectVersion;
@Component
private ArtifactMetadataSource metadataSource;
private Options options;
public void execute() throws MojoExecutionException, MojoFailureException {
MavenParameters mavenParameters = new MavenParameters(artifactRepositories, artifactFactory, localRepository, artifactResolver, mavenProject, mojoExecution, versionRangeWithProjectVersion, metadataSource);
PluginParameters pluginParameters = new PluginParameters(skip, newVersion, oldVersion, parameter, dependencies, Optional.of(projectBuildDir), Optional.<String>absent(), true, oldVersions, newVersions, oldClassPathDependencies, newClassPathDependencies);
executeWithParameters(pluginParameters, mavenParameters);
}
Optional<XmlOutput> executeWithParameters(PluginParameters pluginParameters, MavenParameters mavenParameters) throws MojoFailureException {
if (Boolean.TRUE.toString().equalsIgnoreCase(pluginParameters.getSkipParam())) {
getLog().info("Skipping execution because parameter 'skip' was set to true.");
return Optional.absent();
}
if (skipModule(pluginParameters, mavenParameters)) {
return Optional.absent();
}
Options options = getOptions(pluginParameters, mavenParameters);
JarArchiveComparatorOptions comparatorOptions = JarArchiveComparatorOptions.of(options);
setUpClassPath(comparatorOptions, pluginParameters, mavenParameters);
JarArchiveComparator jarArchiveComparator = new JarArchiveComparator(comparatorOptions);
if (options.getNewArchives().isEmpty()) {
getLog().warn("Skipping execution because no new version could be resolved/found.");
return Optional.absent();
}
List<JApiClass> jApiClasses = jarArchiveComparator.compare(options.getOldArchives(), options.getNewArchives());
try {
jApiClasses = applyPostAnalysisScript(pluginParameters.getParameterParam(), jApiClasses);
File jApiCmpBuildDir = createJapiCmpBaseDir(pluginParameters);
generateDiffOutput(mavenParameters, pluginParameters, options, jApiClasses, jApiCmpBuildDir);
XmlOutput xmlOutput = generateXmlOutput(jApiClasses, jApiCmpBuildDir, options, mavenParameters, pluginParameters);
if (pluginParameters.isWriteToFiles()) {
List<File> filesWritten = XmlOutputGenerator.writeToFiles(options, xmlOutput);
for (File file : filesWritten) {
getLog().info("Written file '" + file.getAbsolutePath() + "'.");
}
}
breakBuildIfNecessary(jApiClasses, pluginParameters.getParameterParam(), options, jarArchiveComparator);
return Optional.of(xmlOutput);
} catch (IOException e) {
throw new MojoFailureException(String.format("Failed to construct output directory: %s", e.getMessage()), e);
}
}
private List<JApiClass> applyPostAnalysisScript(Parameter parameter, List<JApiClass> jApiClasses) throws MojoFailureException {
List<JApiClass> filteredList = jApiClasses;
if (parameter != null) {
String postAnalysisFilterScript = parameter.getPostAnalysisScript();
if (postAnalysisFilterScript != null) {
if (Files.exists(Paths.get(postAnalysisFilterScript))) {
ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("groovy");
Bindings bindings = scriptEngine.createBindings();
bindings.put("jApiClasses", jApiClasses);
try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(postAnalysisFilterScript), Charset.forName("UTF-8"))) {
Object returnValue = scriptEngine.eval(fileReader, bindings);
if (returnValue instanceof List) {
List returnedList = (List) returnValue;
filteredList = new ArrayList<>(returnedList.size());
for (Object obj : returnedList) {
if (obj instanceof JApiClass) {
JApiClass jApiClass = (JApiClass) obj;
filteredList.add(jApiClass);
}
}
} else {
throw new MojoFailureException("Post-analysis script does not return a list.");
}
} catch (ScriptException e) {
throw new MojoFailureException("Execution of post-analysis script failed: " + e.getMessage(), e);
} catch (FileNotFoundException e) {
throw new MojoFailureException("Post-analysis script '" + postAnalysisFilterScript + " does not exist.", e);
} catch (IOException e) {
throw new MojoFailureException("Failed to load post-analysis script '" + postAnalysisFilterScript + ": " + e.getMessage(), e);
}
} else {
throw new MojoFailureException("Post-analysis script '" + postAnalysisFilterScript + " does not exist.");
}
} else {
getLog().debug("No post-analysis script provided.");
}
}
return filteredList;
}
private boolean skipModule(PluginParameters pluginParameters, MavenParameters mavenParameters) {
SkipModuleStrategy skipModuleStrategy = new SkipModuleStrategy(pluginParameters, mavenParameters, getLog());
return skipModuleStrategy.skip();
}
private enum ConfigurationVersion {
OLD, NEW
}
private Artifact getComparisonArtifact(MavenParameters mavenParameters, PluginParameters pluginParameters) throws MojoFailureException, MojoExecutionException {
VersionRange versionRange;
try {
versionRange = VersionRange.createFromVersionSpec(mavenParameters.getVersionRangeWithProjectVersion());
} catch (InvalidVersionSpecificationException e) {
throw new MojoFailureException("Invalid version versionRange: " + e.getMessage(), e);
}
Artifact previousArtifact;
try {
MavenProject project = mavenParameters.getMavenProject();
previousArtifact = mavenParameters.getArtifactFactory().createDependencyArtifact(project.getGroupId(), project.getArtifactId(), versionRange, project.getPackaging(), null, Artifact.SCOPE_COMPILE);
if (!previousArtifact.getVersionRange().isSelectedVersionKnown(previousArtifact)) {
getLog().debug("Searching for versions in versionRange: " + previousArtifact.getVersionRange());
List<ArtifactVersion> availableVersions = mavenParameters.getMetadataSource().retrieveAvailableVersions(previousArtifact, mavenParameters.getLocalRepository(), project.getRemoteArtifactRepositories());
filterSnapshots(availableVersions);
filterVersionPattern(availableVersions, pluginParameters);
ArtifactVersion version = versionRange.matchVersion(availableVersions);
if (version != null) {
previousArtifact.selectVersion(version.toString());
}
}
} catch (OverConstrainedVersionException e) {
throw new MojoFailureException("Invalid comparison version: " + e.getMessage(), e);
} catch (ArtifactMetadataRetrievalException e) {
throw new MojoExecutionException("Error determining previous version: " + e.getMessage(), e);
}
if (previousArtifact.getVersion() == null) {
getLog().info("Unable to find a previous version of the project in the repository.");
}
return previousArtifact;
}
private void filterVersionPattern(List<ArtifactVersion> availableVersions, PluginParameters pluginParameters) throws MojoFailureException {
if (pluginParameters.getParameterParam() != null && pluginParameters.getParameterParam().getOldVersionPattern() != null) {
String versionPattern = pluginParameters.getParameterParam().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 (Iterator<ArtifactVersion> versionIterator = availableVersions.iterator(); versionIterator.hasNext(); ) {
ArtifactVersion version = versionIterator.next();
Matcher matcher = pattern.matcher(version.toString());
if (!matcher.matches()) {
versionIterator.remove();
if (getLog().isDebugEnabled()) {
getLog().debug("Filtering version '" + version.toString() + "' because it does not match configured versionPattern '" + versionPattern + "'.");
}
}
}
} else {
getLog().debug("Parameter <oldVersionPattern> not configured, i.e. no version filtered.");
}
}
private void filterSnapshots(List versions) {
for (Iterator versionIterator = versions.iterator(); versionIterator.hasNext(); ) {
ArtifactVersion version = (ArtifactVersion) versionIterator.next();
String qualifier = version.getQualifier();
if (qualifier != null && qualifier.endsWith("SNAPSHOT")) {
versionIterator.remove();
}
}
}
private void populateArchivesListsFromParameters(PluginParameters pluginParameters, MavenParameters mavenParameters, List<JApiCmpArchive> oldArchives, List<JApiCmpArchive> newArchives) throws MojoFailureException {
if (pluginParameters.getOldVersionParam() != null) {
oldArchives.addAll(retrieveFileFromConfiguration(pluginParameters.getOldVersionParam(), "oldVersion", mavenParameters, pluginParameters, ConfigurationVersion.OLD));
}
if (pluginParameters.getOldVersionsParam() != null) {
for (DependencyDescriptor dependencyDescriptor : pluginParameters.getOldVersionsParam()) {
if (dependencyDescriptor != null) {
oldArchives.addAll(retrieveFileFromConfiguration(dependencyDescriptor, "oldVersions", mavenParameters, pluginParameters, ConfigurationVersion.OLD));
}
}
}
if (pluginParameters.getOldVersionParam() == null && pluginParameters.getOldVersionsParam() == null) {
try {
Artifact comparisonArtifact = getComparisonArtifact(mavenParameters, pluginParameters);
if (comparisonArtifact.getVersion() != null) {
Set<Artifact> artifacts = resolveArtifact(comparisonArtifact, mavenParameters, false, pluginParameters, ConfigurationVersion.OLD);
for (Artifact artifact : artifacts) {
if (!artifact.isOptional()) { //skip optional artifacts because getFile() will return null
File file = artifact.getFile();
if (file != null) {
oldArchives.add(new JApiCmpArchive(file, guessVersion(file)));
} else {
getLog().warn("Artifact '" + artifact + " does not have a file.");
}
}
}
}
} catch (MojoExecutionException e) {
throw new MojoFailureException("Computing and resolving comparison artifact failed: " + e.getMessage(), e);
}
}
if (pluginParameters.getNewVersionParam() != null) {
newArchives.addAll(retrieveFileFromConfiguration(pluginParameters.getNewVersionParam(), "newVersion", mavenParameters, pluginParameters, ConfigurationVersion.NEW));
}
if (pluginParameters.getNewVersionsParam() != null) {
for (DependencyDescriptor dependencyDescriptor : pluginParameters.getNewVersionsParam()) {
if (dependencyDescriptor != null) {
newArchives.addAll(retrieveFileFromConfiguration(dependencyDescriptor, "newVersions", mavenParameters, pluginParameters, ConfigurationVersion.NEW));
}
}
}
if (pluginParameters.getNewVersionParam() == null && pluginParameters.getNewVersionsParam() == null) {
if (mavenParameters.getMavenProject() != null && mavenParameters.getMavenProject().getArtifact() != null) {
Artifact artifact = mavenParameters.getMavenProject().getArtifact();
File file = artifact.getFile();
if (file != null) {
try (JarFile jarFile = new JarFile(file)) {
getLog().debug("Could open file '" + file.getAbsolutePath() + "' of artifact as jar archive: " + jarFile.getName());
newArchives.add(new JApiCmpArchive(file, guessVersion(file)));
} catch (IOException e) {
getLog().warn("No new version specified and file '" + file.getAbsolutePath() + "' of artifact could not be opened as jar archive: " + e.getMessage(), e);
}
} else {
// maven projects with e.g. packaging type ejb don't have a file -> try to resolve artifact through
// ArtifactHandler
if (artifact.getArtifactHandler() != null) {
try {
VersionRange versionSpec = VersionRange.createFromVersionSpec(artifact.getVersion());
Artifact dependencyArtifact = mavenParameters.getArtifactFactory().createDependencyArtifact(artifact.getGroupId(), artifact.getArtifactId(), versionSpec, artifact.getArtifactHandler().getExtension(), null, null);
Set<Artifact> artifacts = resolveArtifact(dependencyArtifact, mavenParameters, false, pluginParameters, ConfigurationVersion.NEW);
for (Artifact a : artifacts) {
file = a.getFile();
if (file != null) {
try (JarFile jarFile = new JarFile(file)) {
getLog().debug("Could open file '" + file.getAbsolutePath() + "' of artifact as jar archive: " + jarFile.getName());
newArchives.add(new JApiCmpArchive(file, guessVersion(file)));
} catch (IOException e) {
getLog().warn("No new version specified and file '" + file.getAbsolutePath() + "' of artifact could not be opened as jar archive: " + e.getMessage(), e);
}
}
}
} catch (InvalidVersionSpecificationException e) {
getLog().error("Failed to obtain file for artifact " + artifact + ": " + e.getMessage(), e);
}
} else {
getLog().warn("Artifact " + artifact + " does not have an ArtifactHandler. Cannot resolve artifact automatically.");
}
}
}
}
if (oldArchives.size() == 0) {
String message = "Please provide at least one resolvable old version using one of the configuration elements <oldVersion/> or <oldVersions/>.";
if (ignoreMissingArtifact(pluginParameters, ConfigurationVersion.OLD)) {
getLog().warn(message);
} else {
throw new MojoFailureException(message);
}
}
if (newArchives.size() == 0) {
String message = "Please provide at least one resolvable new version using one of the configuration elements <newVersion/> or <newVersions/>.";
if (ignoreMissingArtifact(pluginParameters, ConfigurationVersion.NEW)) {
getLog().warn(message);
} else {
throw new MojoFailureException(message);
}
}
}
private void breakBuildIfNecessary(List<JApiClass> jApiClasses, Parameter parameterParam, Options options, JarArchiveComparator jarArchiveComparator) throws MojoFailureException {
if (breakBuildOnModificationsParameter(parameterParam)) {
for (JApiClass jApiClass : jApiClasses) {
if (jApiClass.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
throw new MojoFailureException(String.format("Breaking the build because there is at least one modified class: %s", jApiClass.getFullyQualifiedName()));
}
}
}
breakBuildIfNecessaryByApplyingFilter(jApiClasses, parameterParam, options, jarArchiveComparator);
if (breakBuildBasedOnSemanticVersioning(parameterParam)) {
boolean ignoreMissingOldVersion = "true".equalsIgnoreCase(parameter.getIgnoreMissingOldVersion() == null ? "false" : parameter.getIgnoreMissingOldVersion());
boolean ignoreMissingNewVersion = "true".equalsIgnoreCase(parameter.getIgnoreMissingNewVersion() == null ? "false" : parameter.getIgnoreMissingNewVersion());
List<SemanticVersion> oldVersions = new ArrayList<>();
List<SemanticVersion> newVersions = new ArrayList<>();
for (JApiCmpArchive file : options.getOldArchives()) {
Optional<SemanticVersion> semanticVersion = file.getVersion().getSemanticVersion();
if (semanticVersion.isPresent()) {
oldVersions.add(semanticVersion.get());
}
}
for (JApiCmpArchive file : options.getNewArchives()) {
Optional<SemanticVersion> semanticVersion = file.getVersion().getSemanticVersion();
if (semanticVersion.isPresent()) {
newVersions.add(semanticVersion.get());
}
}
VersionChange versionChange = new VersionChange(oldVersions, newVersions, ignoreMissingOldVersion, ignoreMissingNewVersion);
if (!versionChange.isAllMajorVersionsZero()) {
Optional<SemanticVersion.ChangeType> changeTypeOptional = versionChange.computeChangeType();
if (changeTypeOptional.isPresent()) {
SemanticVersion.ChangeType changeType = changeTypeOptional.get();
SemverOut semverOut = new SemverOut(options, jApiClasses);
String semver = semverOut.generate();
if (changeType == SemanticVersion.ChangeType.MINOR && semver.equals("1.0.0")) {
throw new MojoFailureException("Versions of archives indicate a minor change but binary incompatible changes found.");
}
if (changeType == SemanticVersion.ChangeType.PATCH && semver.equals("1.0.0")) {
throw new MojoFailureException("Versions of archives indicate a patch change but binary incompatible changes found.");
}
if (changeType == SemanticVersion.ChangeType.PATCH && semver.equals("0.1.0")) {
throw new MojoFailureException("Versions of archives indicate a patch change but binary compatible changes found.");
}
if (changeType == SemanticVersion.ChangeType.UNCHANGED && semver.equals("1.0.0")) {
throw new MojoFailureException("Versions of archives indicate no API changes but binary incompatible changes found.");
}
if (changeType == SemanticVersion.ChangeType.UNCHANGED && semver.equals("0.1.0")) {
throw new MojoFailureException("Versions of archives indicate no API changes but binary compatible changes found.");
}
if (changeType == SemanticVersion.ChangeType.UNCHANGED && semver.equals("0.0.1")) {
throw new MojoFailureException("Versions of archives indicate no API changes but found API changes.");
}
} else {
if (getLog().isDebugEnabled()) {
Joiner joiner = Joiner.on(';');
getLog().debug("No change type available for old version(s) " + joiner.join(oldVersions) + " and new version(s) " + joiner.join(newVersions) + ".");
}
}
} else {
getLog().info("Skipping semantic version check because all major versions are zero (see http://semver.org/#semantic-versioning-specification-semver, section 4).");
}
}
}
private static class BreakBuildResult {
private final boolean breakBuildOnBinaryIncompatibleModifications;
private final boolean breakBuildOnSourceIncompatibleModifications;
boolean binaryIncompatibleChanges = false;
boolean sourceIncompatibleChanges = false;
public BreakBuildResult(boolean breakBuildOnBinaryIncompatibleModifications, boolean breakBuildOnSourceIncompatibleModifications) {
this.breakBuildOnBinaryIncompatibleModifications = breakBuildOnBinaryIncompatibleModifications;
this.breakBuildOnSourceIncompatibleModifications = breakBuildOnSourceIncompatibleModifications;
}
public boolean breakTheBuild() {
return binaryIncompatibleChanges && this.breakBuildOnBinaryIncompatibleModifications ||
sourceIncompatibleChanges && this.breakBuildOnSourceIncompatibleModifications;
}
}
void breakBuildIfNecessaryByApplyingFilter(List<JApiClass> jApiClasses, Parameter parameterParam, final Options options,
final JarArchiveComparator jarArchiveComparator) throws MojoFailureException {
final StringBuilder sb = new StringBuilder();
final BreakBuildResult breakBuildResult = new BreakBuildResult(breakBuildOnBinaryIncompatibleModifications(parameterParam),
breakBuildOnSourceIncompatibleModifications(parameterParam));
final boolean breakBuildIfCausedByExclusion = parameterParam.isBreakBuildIfCausedByExclusion();
Filter.filter(jApiClasses, new Filter.FilterVisitor() {
@Override
public void visit(Iterator<JApiClass> iterator, JApiClass jApiClass) {
for (JApiCompatibilityChange jApiCompatibilityChange : jApiClass.getCompatibilityChanges()) {
if (!jApiCompatibilityChange.isBinaryCompatible() || !jApiCompatibilityChange.isSourceCompatible()) {
if (!jApiCompatibilityChange.isBinaryCompatible()) {
breakBuildResult.binaryIncompatibleChanges = true;
}
if (!jApiCompatibilityChange.isSourceCompatible()) {
breakBuildResult.sourceIncompatibleChanges = true;
}
if (sb.length() > 1) {
sb.append(',');
}
sb.append(jApiClass.getFullyQualifiedName()).append(":").append(jApiCompatibilityChange.name());
}
}
}
@Override
public void visit(Iterator<JApiMethod> iterator, JApiMethod jApiMethod) {
for (JApiCompatibilityChange jApiCompatibilityChange : jApiMethod.getCompatibilityChanges()) {
if (!jApiCompatibilityChange.isBinaryCompatible() || !jApiCompatibilityChange.isSourceCompatible()) {
if (!jApiCompatibilityChange.isBinaryCompatible() && breakBuildIfCausedByExclusion(jApiMethod)) {
breakBuildResult.binaryIncompatibleChanges = true;
}
if (!jApiCompatibilityChange.isSourceCompatible() && breakBuildIfCausedByExclusion(jApiMethod)) {
breakBuildResult.sourceIncompatibleChanges = true;
}
if (sb.length() > 1) {
sb.append(',');
}
sb.append(jApiMethod.getjApiClass().getFullyQualifiedName()).append(".").append(jApiMethod.getName()).append("(").append(methodParameterToList(jApiMethod)).append(")").append(":").append(jApiCompatibilityChange.name());
}
}
}
private boolean breakBuildIfCausedByExclusion(JApiMethod jApiMethod) {
if (!breakBuildIfCausedByExclusion) {
JApiReturnType returnType = jApiMethod.getReturnType();
String oldType = returnType.getOldReturnType();
try {
Optional<CtClass> ctClassOptional = jarArchiveComparator.loadClass(JarArchiveComparator.ArchiveType.OLD, oldType);
if (ctClassOptional.isPresent()) {
if (classExcluded(ctClassOptional.get())) {
return false;
}
}
} catch (Exception e) {
getLog().warn("Failed to load class " + oldType + ": " + e.getMessage(), e);
}
String newType = returnType.getNewReturnType();
try {
Optional<CtClass> ctClassOptional = jarArchiveComparator.loadClass(JarArchiveComparator.ArchiveType.NEW, newType);
if (ctClassOptional.isPresent()) {
if (classExcluded(ctClassOptional.get())) {
return false;
}
}
} catch (Exception e) {
getLog().warn("Failed to load class " + newType + ": " + e.getMessage(), e);
}
}
return true;
}
@Override
public void visit(Iterator<JApiConstructor> iterator, JApiConstructor jApiConstructor) {
for (JApiCompatibilityChange jApiCompatibilityChange : jApiConstructor.getCompatibilityChanges()) {
if (!jApiCompatibilityChange.isBinaryCompatible() || !jApiCompatibilityChange.isSourceCompatible()) {
if (!jApiCompatibilityChange.isBinaryCompatible()) {
breakBuildResult.binaryIncompatibleChanges = true;
}
if (!jApiCompatibilityChange.isSourceCompatible()) {
breakBuildResult.sourceIncompatibleChanges = true;
}
if (sb.length() > 1) {
sb.append(',');
}
sb.append(jApiConstructor.getjApiClass().getFullyQualifiedName()).append(".").append(jApiConstructor.getName()).append("(").append(methodParameterToList(jApiConstructor)).append(")").append(":").append(jApiCompatibilityChange.name());
}
}
}
@Override
public void visit(Iterator<JApiImplementedInterface> iterator, JApiImplementedInterface jApiImplementedInterface) {
for (JApiCompatibilityChange jApiCompatibilityChange : jApiImplementedInterface.getCompatibilityChanges()) {
if (!jApiCompatibilityChange.isBinaryCompatible() || !jApiCompatibilityChange.isSourceCompatible()) {
if (!jApiCompatibilityChange.isBinaryCompatible() && breakBuildIfCausedByExclusion(jApiImplementedInterface)) {
breakBuildResult.binaryIncompatibleChanges = true;
}
if (!jApiCompatibilityChange.isSourceCompatible() && breakBuildIfCausedByExclusion(jApiImplementedInterface)) {
breakBuildResult.sourceIncompatibleChanges = true;
}
if (sb.length() > 1) {
sb.append(',');
}
sb.append(jApiImplementedInterface.getFullyQualifiedName()).append("[").append(jApiImplementedInterface.getFullyQualifiedName()).append("]").append(":").append(jApiCompatibilityChange.name());
}
}
}
private boolean breakBuildIfCausedByExclusion(JApiImplementedInterface jApiImplementedInterface) {
if (!breakBuildIfCausedByExclusion) {
CtClass ctClass = jApiImplementedInterface.getCtClass();
if (classExcluded(ctClass)) {
return false;
}
}
return true;
}
@Override
public void visit(Iterator<JApiField> iterator, JApiField jApiField) {
for (JApiCompatibilityChange jApiCompatibilityChange : jApiField.getCompatibilityChanges()) {
if (!jApiCompatibilityChange.isBinaryCompatible() || !jApiCompatibilityChange.isSourceCompatible()) {
if (!jApiCompatibilityChange.isBinaryCompatible() && breakBuildIfCausedByExclusion(jApiField)) {
breakBuildResult.binaryIncompatibleChanges = true;
}
if (!jApiCompatibilityChange.isSourceCompatible() && breakBuildIfCausedByExclusion(jApiField)) {
breakBuildResult.sourceIncompatibleChanges = true;
}
if (sb.length() > 1) {
sb.append(',');
}
sb.append(jApiField.getjApiClass().getFullyQualifiedName()).append(".").append(jApiField.getName()).append(":").append(jApiCompatibilityChange.name());
}
}
}
private boolean breakBuildIfCausedByExclusion(JApiField jApiField) {
if (!breakBuildIfCausedByExclusion) {
JApiType type = jApiField.getType();
Optional<String> oldTypeOptional = type.getOldTypeOptional();
if (oldTypeOptional.isPresent()) {
String oldType = oldTypeOptional.get();
try {
Optional<CtClass> ctClassOptional = jarArchiveComparator.loadClass(JarArchiveComparator.ArchiveType.OLD, oldType);
if (ctClassOptional.isPresent()) {
if (classExcluded(ctClassOptional.get())) {
return false;
}
}
} catch (Exception e) {
getLog().warn("Failed to load class " + oldType + ": " + e.getMessage(), e);
}
}
Optional<String> newTypeOptional = type.getNewTypeOptional();
if (newTypeOptional.isPresent()) {
String newType = newTypeOptional.get();
try {
Optional<CtClass> ctClassOptional = jarArchiveComparator.loadClass(JarArchiveComparator.ArchiveType.NEW, newType);
if (ctClassOptional.isPresent()) {
if (classExcluded(ctClassOptional.get())) {
return false;
}
}
} catch (Exception e) {
getLog().warn("Failed to load class " + newType + ": " + e.getMessage(), e);
}
}
}
return true;
}
@Override
public void visit(Iterator<JApiAnnotation> iterator, JApiAnnotation jApiAnnotation) {
//no incompatible changes
}
@Override
public void visit(JApiSuperclass jApiSuperclass) {
for (JApiCompatibilityChange jApiCompatibilityChange : jApiSuperclass.getCompatibilityChanges()) {
if (!jApiCompatibilityChange.isBinaryCompatible() || !jApiCompatibilityChange.isSourceCompatible()) {
if (!jApiCompatibilityChange.isBinaryCompatible() && breakBuildIfCausedByExclusion(jApiSuperclass)) {
breakBuildResult.binaryIncompatibleChanges = true;
}
if (!jApiCompatibilityChange.isSourceCompatible() && breakBuildIfCausedByExclusion(jApiSuperclass)) {
breakBuildResult.sourceIncompatibleChanges = true;
}
if (sb.length() > 1) {
sb.append(',');
}
sb.append(jApiSuperclass.getJApiClassOwning().getFullyQualifiedName()).append(":").append(jApiCompatibilityChange.name());
}
}
}
private boolean breakBuildIfCausedByExclusion(JApiSuperclass jApiSuperclass) {
if (!breakBuildIfCausedByExclusion) {
Optional<CtClass> oldSuperclassOptional = jApiSuperclass.getOldSuperclass();
if (oldSuperclassOptional.isPresent()) {
CtClass ctClass = oldSuperclassOptional.get();
if (classExcluded(ctClass)) {
return false;
}
}
Optional<CtClass> newSuperclassOptional = jApiSuperclass.getNewSuperclass();
if (newSuperclassOptional.isPresent()) {
CtClass ctClass = newSuperclassOptional.get();
if (classExcluded(ctClass)) {
return false;
}
}
}
return true;
}
private boolean classExcluded(CtClass ctClass) {
List<japicmp.filter.Filter> excludes = options.getExcludes();
for (japicmp.filter.Filter exclude : excludes) {
if (exclude instanceof ClassFilter) {
ClassFilter classFilter = (ClassFilter) exclude;
if (classFilter.matches(ctClass)) {
return true;
}
}
}
return false;
}
});
if (breakBuildResult.breakTheBuild()) {
throw new MojoFailureException(String.format("Breaking the build because there is at least one incompatibility: %s", sb.toString()));
}
}
private String methodParameterToList(JApiBehavior jApiMethod) {
StringBuilder sb = new StringBuilder();
for (JApiParameter jApiParameter : jApiMethod.getParameters()) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(jApiParameter.getType());
}
return sb.toString();
}
Options getOptions(PluginParameters pluginParameters, MavenParameters mavenParameters) throws MojoFailureException {
if (options != null) {
return options;
}
options = Options.newDefault();
populateArchivesListsFromParameters(pluginParameters, mavenParameters, options.getOldArchives(), options.getNewArchives());
Parameter parameterParam = pluginParameters.getParameterParam();
if (parameterParam != null) {
String accessModifierArg = parameterParam.getAccessModifier();
if (accessModifierArg != null) {
try {
AccessModifier accessModifier = AccessModifier.valueOf(accessModifierArg.toUpperCase());
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);
}
}
String onlyBinaryIncompatible = parameterParam.getOnlyBinaryIncompatible();
if (onlyBinaryIncompatible != null) {
Boolean booleanOnlyBinaryIncompatible = Boolean.valueOf(onlyBinaryIncompatible);
options.setOutputOnlyBinaryIncompatibleModifications(booleanOnlyBinaryIncompatible);
}
String onlyModified = parameterParam.getOnlyModified();
if (onlyModified != null) {
Boolean booleanOnlyModified = Boolean.valueOf(onlyModified);
options.setOutputOnlyModifications(booleanOnlyModified);
}
List<String> excludes = parameterParam.getExcludes();
if (excludes != null) {
for (String exclude : excludes) {
options.addExcludeFromArgument(Optional.fromNullable(exclude));
}
}
List<String> includes = parameterParam.getIncludes();
if (includes != null) {
for (String include : includes) {
options.addIncludeFromArgument(Optional.fromNullable(include));
}
}
String includeSyntheticString = parameterParam.getIncludeSynthetic();
if (includeSyntheticString != null) {
Boolean includeSynthetic = Boolean.valueOf(includeSyntheticString);
options.setIncludeSynthetic(includeSynthetic);
}
String ignoreMissingClassesString = parameterParam.getIgnoreMissingClasses();
if (ignoreMissingClassesString != null) {
Boolean ignoreMissingClasses = Boolean.valueOf(ignoreMissingClassesString);
options.setIgnoreMissingClasses(ignoreMissingClasses);
}
List<String> ignoreMissingClassesByRegularExpressions = parameterParam.getIgnoreMissingClassesByRegularExpressions();
if (ignoreMissingClassesByRegularExpressions != null) {
for (String ignoreMissingClassRegularExpression : ignoreMissingClassesByRegularExpressions) {
options.addIgnoreMissingClassRegularExpression(ignoreMissingClassRegularExpression);
}
}
String htmlStylesheet = parameterParam.getHtmlStylesheet();
if (htmlStylesheet != null) {
options.setHtmlStylesheet(Optional.of(htmlStylesheet));
}
String noAnnotationsString = parameterParam.getNoAnnotations();
if (noAnnotationsString != null) {
Boolean noAnnotations = Boolean.valueOf(noAnnotationsString);
options.setNoAnnotations(noAnnotations);
}
options.setReportOnlyFilename(parameterParam.isReportOnlyFilename());
}
return options;
}
private boolean breakBuildOnModificationsParameter(Parameter parameterParam) {
boolean retVal = false;
if (parameterParam != null) {
retVal = Boolean.valueOf(parameterParam.getBreakBuildOnModifications());
}
return retVal;
}
private boolean breakBuildOnBinaryIncompatibleModifications(Parameter parameterParam) {
boolean retVal = false;
if (parameterParam != null) {
retVal = Boolean.valueOf(parameterParam.getBreakBuildOnBinaryIncompatibleModifications());
}
return retVal;
}
private boolean breakBuildOnSourceIncompatibleModifications(Parameter parameter) {
boolean retVal = false;
if (parameter != null) {
retVal = Boolean.valueOf(parameter.getBreakBuildOnSourceIncompatibleModifications());
}
return retVal;
}
private boolean breakBuildBasedOnSemanticVersioning(Parameter parameter) {
boolean retVal = false;
if (parameter != null) {
retVal = Boolean.valueOf(parameter.getBreakBuildBasedOnSemanticVersioning());
}
return retVal;
}
private File createJapiCmpBaseDir(PluginParameters pluginParameters) throws MojoFailureException {
if (pluginParameters.getProjectBuildDirParam().isPresent()) {
try {
File projectBuildDirParam = pluginParameters.getProjectBuildDirParam().get();
if (projectBuildDirParam != null) {
File jApiCmpBuildDir = new File(projectBuildDirParam.getCanonicalPath() + File.separator + "japicmp");
boolean mkdirs = jApiCmpBuildDir.mkdirs();
if (mkdirs || jApiCmpBuildDir.isDirectory() && jApiCmpBuildDir.canWrite()) {
return jApiCmpBuildDir;
}
throw new MojoFailureException(String.format("Failed to create directory '%s'.", jApiCmpBuildDir.getAbsolutePath()));
} else {
throw new MojoFailureException("Maven parameter projectBuildDir is not set.");
}
} catch (IOException e) {
throw new MojoFailureException("Failed to create output directory: " + e.getMessage(), e);
}
} else if (pluginParameters.getOutputDirectory().isPresent()) {
String outputDirectory = pluginParameters.getOutputDirectory().get();
if (outputDirectory != null) {
File outputDirFile = new File(outputDirectory);
boolean mkdirs = outputDirFile.mkdirs();
if (mkdirs || outputDirFile.isDirectory() && outputDirFile.canWrite()) {
return outputDirFile;
}
throw new MojoFailureException(String.format("Failed to create directory '%s'.", outputDirFile.getAbsolutePath()));
} else {
throw new MojoFailureException("Maven parameter outputDirectory is not set.");
}
} else {
throw new MojoFailureException("None of the two parameters projectBuildDir and outputDirectory are present");
}
}
private void generateDiffOutput(MavenParameters mavenParameters, PluginParameters pluginParameters, Options options, List<JApiClass> jApiClasses, File jApiCmpBuildDir) throws IOException, MojoFailureException {
boolean skipDiffReport = false;
if (pluginParameters.getParameterParam() != null) {
skipDiffReport = pluginParameters.getParameterParam().isSkipDiffReport();
}
if (!skipDiffReport) {
StdoutOutputGenerator stdoutOutputGenerator = new StdoutOutputGenerator(options, jApiClasses);
String diffOutput = stdoutOutputGenerator.generate();
File output = new File(jApiCmpBuildDir.getCanonicalPath() + File.separator + createFilename(mavenParameters) + ".diff");
writeToFile(diffOutput, output);
}
}
private XmlOutput generateXmlOutput(List<JApiClass> jApiClasses, File jApiCmpBuildDir, Options options, MavenParameters mavenParameters, PluginParameters pluginParameters) throws IOException, MojoFailureException {
String filename = createFilename(mavenParameters);
if (!skipXmlReport(pluginParameters)) {
options.setXmlOutputFile(Optional.of(jApiCmpBuildDir.getCanonicalPath() + File.separator + filename + ".xml"));
}
if (!skipHtmlReport(pluginParameters)) {
options.setHtmlOutputFile(Optional.of(jApiCmpBuildDir.getCanonicalPath() + File.separator + filename + ".html"));
}
SemverOut semverOut = new SemverOut(options, jApiClasses);
XmlOutputGeneratorOptions xmlOutputGeneratorOptions = new XmlOutputGeneratorOptions();
xmlOutputGeneratorOptions.setCreateSchemaFile(true);
xmlOutputGeneratorOptions.setSemanticVersioningInformation(semverOut.generate());
if (pluginParameters.getParameterParam() != null) {
String optionalTitle = pluginParameters.getParameterParam().getHtmlTitle();
xmlOutputGeneratorOptions.setTitle(optionalTitle!=null ?optionalTitle :options.getDifferenceDescription());
}
XmlOutputGenerator xmlGenerator = new XmlOutputGenerator(jApiClasses, options, xmlOutputGeneratorOptions);
return xmlGenerator.generate();
}
private boolean skipHtmlReport(PluginParameters pluginParameters) {
boolean skipReport = false;
if (pluginParameters.getParameterParam() != null) {
skipReport = Boolean.valueOf(pluginParameters.getParameterParam().getSkipHtmlReport());
}
return skipReport;
}
private boolean skipXmlReport(PluginParameters pluginParameters) {
boolean skipReport = false;
if (pluginParameters.getParameterParam() != null) {
skipReport = Boolean.valueOf(pluginParameters.getParameterParam().getSkipXmlReport());
}
return skipReport;
}
private String createFilename(MavenParameters mavenParameters) {
String filename = "japicmp";
String executionId = mavenParameters.getMojoExecution().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(JarArchiveComparatorOptions comparatorOptions, PluginParameters pluginParameters, MavenParameters mavenParameters) throws MojoFailureException {
if (pluginParameters != null) {
if (pluginParameters.getDependenciesParam() != null) {
if (pluginParameters.getOldClassPathDependencies() != null || pluginParameters.getNewClassPathDependencies() != 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 {
if (getLog().isDebugEnabled()) {
getLog().debug("Element <dependencies/> found. Using " + JApiCli.ClassPathMode.ONE_COMMON_CLASSPATH);
}
for (Dependency dependency : pluginParameters.getDependenciesParam()) {
List<JApiCmpArchive> jApiCmpArchives = resolveDependencyToFile("dependencies", dependency, mavenParameters, true, pluginParameters, ConfigurationVersion.NEW);
for (JApiCmpArchive jApiCmpArchive : jApiCmpArchives) {
comparatorOptions.getClassPathEntries().add(jApiCmpArchive.getFile().getAbsolutePath());
}
comparatorOptions.setClassPathMode(JarArchiveComparatorOptions.ClassPathMode.ONE_COMMON_CLASSPATH);
}
}
} else {
if (pluginParameters.getOldClassPathDependencies() != null || pluginParameters.getNewClassPathDependencies() != null) {
if (getLog().isDebugEnabled()) {
getLog().debug("At least one of the elements <oldClassPathDependencies/> or <newClassPathDependencies/> found. Using " + JApiCli.ClassPathMode.TWO_SEPARATE_CLASSPATHS);
}
if (pluginParameters.getOldClassPathDependencies() != null) {
for (Dependency dependency : pluginParameters.getOldClassPathDependencies()) {
List<JApiCmpArchive> jApiCmpArchives = resolveDependencyToFile("oldClassPathDependencies", dependency, mavenParameters, true, pluginParameters, ConfigurationVersion.OLD);
for (JApiCmpArchive archive : jApiCmpArchives) {
comparatorOptions.getOldClassPath().add(archive.getFile().getAbsolutePath());
}
}
}
if (pluginParameters.getNewClassPathDependencies() != null) {
for (Dependency dependency : pluginParameters.getNewClassPathDependencies()) {
List<JApiCmpArchive> jApiCmpArchives = resolveDependencyToFile("newClassPathDependencies", dependency, mavenParameters, true, pluginParameters, ConfigurationVersion.NEW);
for (JApiCmpArchive archive : jApiCmpArchives) {
comparatorOptions.getNewClassPath().add(archive.getFile().getAbsolutePath());
}
}
}
comparatorOptions.setClassPathMode(JarArchiveComparatorOptions.ClassPathMode.TWO_SEPARATE_CLASSPATHS);
} else {
if (getLog().isDebugEnabled()) {
getLog().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, mavenParameters, pluginParameters, ConfigurationVersion.NEW);
}
private void setUpClassPathUsingMavenProject(JarArchiveComparatorOptions comparatorOptions, MavenParameters mavenParameters, PluginParameters pluginParameters, ConfigurationVersion configurationVersion) throws MojoFailureException {
notNull(mavenParameters.getMavenProject(), "Maven parameter mavenProject should be provided by maven container.");
Set<Artifact> dependencyArtifacts = mavenParameters.getMavenProject().getArtifacts();
Set<String> classPathEntries = new HashSet<>();
for (Artifact artifact : dependencyArtifacts) {
String scope = artifact.getScope();
if (!"test".equals(scope) && !artifact.isOptional()) {
Set<Artifact> artifacts = resolveArtifact(artifact, mavenParameters, false, pluginParameters, configurationVersion);
for (Artifact resolvedArtifact : artifacts) {
File resolvedFile = resolvedArtifact.getFile();
if (resolvedFile != null) {
String absolutePath = resolvedFile.getAbsolutePath();
if (!classPathEntries.contains(absolutePath)) {
if (getLog().isDebugEnabled()) {
getLog().debug("Adding to classpath: " + absolutePath + "; scope: " + scope);
}
classPathEntries.add(absolutePath);
}
}
}
}
}
for (String classPathEntry : classPathEntries) {
comparatorOptions.getClassPathEntries().add(classPathEntry);
}
}
private List<JApiCmpArchive> retrieveFileFromConfiguration(DependencyDescriptor dependencyDescriptor, String parameterName, MavenParameters mavenParameters, PluginParameters pluginParameters, ConfigurationVersion configurationVersion) throws MojoFailureException {
List<JApiCmpArchive> jApiCmpArchives;
if (dependencyDescriptor instanceof Dependency) {
Dependency dependency = (Dependency) dependencyDescriptor;
jApiCmpArchives = resolveDependencyToFile(parameterName, dependency, mavenParameters, false, pluginParameters, configurationVersion);
} else if (dependencyDescriptor instanceof ConfigurationFile) {
ConfigurationFile configurationFile = (ConfigurationFile) dependencyDescriptor;
jApiCmpArchives = resolveConfigurationFileToFile(parameterName, configurationFile, configurationVersion, pluginParameters);
} else {
throw new MojoFailureException("DependencyDescriptor is not of type <dependency/> nor of type <configurationFile/>.");
}
return jApiCmpArchives;
}
private List<JApiCmpArchive> retrieveFileFromConfiguration(Version version, String parameterName, MavenParameters mavenParameters, PluginParameters pluginParameters, ConfigurationVersion configurationVersion) throws MojoFailureException {
if (version != null) {
Dependency dependency = version.getDependency();
if (dependency != null) {
return resolveDependencyToFile(parameterName, dependency, mavenParameters, false, pluginParameters, configurationVersion);
} else if (version.getFile() != null) {
ConfigurationFile configurationFile = version.getFile();
return resolveConfigurationFileToFile(parameterName, configurationFile, configurationVersion, pluginParameters);
} else {
throw new MojoFailureException("Missing configuration parameter 'dependency'.");
}
}
throw new MojoFailureException(String.format("Missing configuration parameter: %s", parameterName));
}
private List<JApiCmpArchive> resolveConfigurationFileToFile(String parameterName, ConfigurationFile configurationFile, ConfigurationVersion configurationVersion, PluginParameters pluginParameters) 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(pluginParameters, configurationVersion)) {
throw new MojoFailureException(String.format("The path '%s' does not point to an existing file.", path));
} else {
getLog().warn("The file given by path '" + file.getAbsolutePath() + "' does not exist.");
}
}
if (!file.isFile() || !file.canRead()) {
if (!ignoreMissingArtifact(pluginParameters, configurationVersion)) {
throw new MojoFailureException(String.format("The file given by path '%s' is either not a file or is not readable.", path));
} else {
getLog().warn("The file given by path '" + file.getAbsolutePath() + "' is either not a file or is not readable.");
}
}
return Collections.singletonList(new JApiCmpArchive(file, guessVersion(file)));
}
private List<JApiCmpArchive> resolveDependencyToFile(String parameterName, Dependency dependency, MavenParameters mavenParameters,
boolean transitively, PluginParameters pluginParameters, ConfigurationVersion configurationVersion) throws MojoFailureException {
List<JApiCmpArchive> jApiCmpArchives = new ArrayList<>();
if (getLog().isDebugEnabled()) {
getLog().debug("Trying to resolve dependency '" + dependency + "' to file.");
}
if (dependency.getSystemPath() == null) {
String descriptor = dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getVersion();
getLog().debug(parameterName + ": " + descriptor);
Set<Artifact> artifacts = resolveArtifact(dependency, mavenParameters, transitively, pluginParameters, configurationVersion);
for (Artifact artifact : artifacts) {
if (!artifact.isOptional()) { //skip optional artifacts because getFile() will return null
File file = artifact.getFile();
if (file != null) {
jApiCmpArchives.add(new JApiCmpArchive(file, artifact.getVersion()));
} else {
throw new MojoFailureException(String.format("Could not resolve dependency with descriptor '%s'.", descriptor));
}
}
}
if (jApiCmpArchives.size() == 0) {
String message = String.format("Could not resolve dependency with descriptor '%s'.", descriptor);
if (ignoreMissingArtifact(pluginParameters, configurationVersion)) {
getLog().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.getMavenProject().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(pluginParameters, configurationVersion)) {
getLog().warn("Could not find file, but ignoreMissingOldVersion is set tot true: " + file.getAbsolutePath());
} else {
throw new MojoFailureException("File '" + file.getAbsolutePath() + "' does not exist.");
}
addFile = false;
}
if (!file.canRead()) {
if (ignoreMissingArtifact(pluginParameters, configurationVersion)) {
getLog().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 = guessVersion(file);
if (addFile) {
jApiCmpArchives.add(new JApiCmpArchive(file, version));
}
}
return jApiCmpArchives;
}
private String guessVersion(File file) {
String name = file.getName();
Optional<SemanticVersion> semanticVersion = japicmp.versioning.Version.getSemanticVersion(name);
String version = semanticVersion.isPresent() ? semanticVersion.get().toString() : "n.a.";
if (name.contains("SNAPSHOT")) {
version += "-SNAPSHOT";
}
return version;
}
private boolean ignoreMissingArtifact(PluginParameters pluginParameters, ConfigurationVersion configurationVersion) {
return ignoreNonResolvableArtifacts(pluginParameters)
|| ignoreMissingOldVersion(pluginParameters, configurationVersion)
|| ignoreMissingNewVersion(pluginParameters, configurationVersion);
}
private boolean ignoreNonResolvableArtifacts(PluginParameters pluginParameters) {
boolean ignoreNonResolvableArtifacts = false;
Parameter parameterParam = pluginParameters.getParameterParam();
if (parameterParam != null) {
String ignoreNonResolvableArtifactsAsString = parameterParam.getIgnoreNonResolvableArtifacts();
if (Boolean.TRUE.toString().equalsIgnoreCase(ignoreNonResolvableArtifactsAsString)) {
ignoreNonResolvableArtifacts = true;
}
}
return ignoreNonResolvableArtifacts;
}
private boolean ignoreMissingOldVersion(PluginParameters pluginParameters, ConfigurationVersion configurationVersion) {
return (configurationVersion == ConfigurationVersion.OLD && ignoreMissingOldVersion(pluginParameters));
}
private boolean ignoreMissingNewVersion(PluginParameters pluginParameters, ConfigurationVersion configurationVersion) {
return (configurationVersion == ConfigurationVersion.NEW && ignoreMissingNewVersion(pluginParameters));
}
private boolean ignoreMissingOldVersion(PluginParameters pluginParameters) {
boolean ignoreMissingOldVersion = false;
if (pluginParameters.getParameterParam() != null) {
ignoreMissingOldVersion = Boolean.valueOf(pluginParameters.getParameterParam().getIgnoreMissingOldVersion());
}
return ignoreMissingOldVersion;
}
private boolean ignoreMissingNewVersion(PluginParameters pluginParameters) {
boolean ignoreMissingNewVersion = false;
if (pluginParameters.getParameterParam() != null) {
ignoreMissingNewVersion = Boolean.valueOf(pluginParameters.getParameterParam().getIgnoreMissingNewVersion());
}
return ignoreMissingNewVersion;
}
private void writeToFile(String output, File outputfile) throws MojoFailureException, IOException {
OutputStreamWriter fileWriter = null;
try {
fileWriter = new OutputStreamWriter(new FileOutputStream(outputfile), Charset.forName("UTF-8"));
fileWriter.write(output);
getLog().info("Written file '" + outputfile.getAbsolutePath() + "'.");
} catch (Exception e) {
throw new MojoFailureException(String.format("Failed to write diff file: %s", e.getMessage()), e);
} finally {
if (fileWriter != null) {
fileWriter.close();
}
}
}
private Set<Artifact> resolveArtifact(Dependency dependency, MavenParameters mavenParameters, boolean transitively, PluginParameters pluginParameters, ConfigurationVersion configurationVersion) throws MojoFailureException {
notNull(mavenParameters.getArtifactRepositories(), "Maven parameter artifactRepositories should be provided by maven container.");
Artifact artifact = mavenParameters.getArtifactFactory().createArtifactWithClassifier(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), dependency.getType(), dependency.getClassifier());
return resolveArtifact(artifact, mavenParameters, transitively, pluginParameters, configurationVersion);
}
private Set<Artifact> resolveArtifact(Artifact artifact, MavenParameters mavenParameters, boolean transitively, PluginParameters pluginParameters, ConfigurationVersion configurationVersion) throws MojoFailureException {
notNull(mavenParameters.getLocalRepository(), "Maven parameter localRepository should be provided by maven container.");
notNull(mavenParameters.getArtifactResolver(), "Maven parameter artifactResolver should be provided by maven container.");
ArtifactResolutionRequest request = new ArtifactResolutionRequest();
request.setArtifact(artifact);
request.setLocalRepository(mavenParameters.getLocalRepository());
request.setRemoteRepositories(mavenParameters.getArtifactRepositories());
request.setResolutionFilter(new ArtifactFilter() {
@Override
public boolean include(Artifact artifact) {
boolean include = true;
if (artifact != null && artifact.isOptional()) {
include = false;
}
return include;
}
});
if (transitively) {
request.setResolveTransitively(true);
}
ArtifactResolutionResult resolutionResult = mavenParameters.getArtifactResolver().resolve(request);
if (resolutionResult.hasExceptions()) {
List<Exception> exceptions = resolutionResult.getExceptions();
String message = "Could not resolve " + artifact;
if (ignoreMissingArtifact(pluginParameters, configurationVersion)) {
getLog().warn(message);
} else {
throw new MojoFailureException(message, exceptions.get(0));
}
}
Set<Artifact> artifacts = resolutionResult.getArtifacts();
if (artifacts.size() == 0) {
String message = "Could not resolve " + artifact;
if (ignoreMissingArtifact(pluginParameters, configurationVersion)) {
getLog().warn(message);
} else {
throw new MojoFailureException(message);
}
}
return artifacts;
}
private static <T> T notNull(T value, String msg) throws MojoFailureException {
if (value == null) {
throw new MojoFailureException(msg);
}
return value;
}
}