All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.mojo.versions.AbstractVersionsDependencyUpdaterMojo Maven / Gradle / Ivy

Go to download

The Versions Maven Plugin is used when you want to manage the versions of artifacts in a project's POM.

The newest version!
package org.codehaus.mojo.versions;

/*
 * Copyright MojoHaus and Contributors
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

import javax.inject.Inject;
import javax.xml.stream.XMLStreamException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Parent;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.artifact.filter.PatternExcludesArtifactFilter;
import org.apache.maven.shared.artifact.filter.PatternIncludesArtifactFilter;
import org.apache.maven.shared.artifact.filter.ScopeArtifactFilter;
import org.apache.maven.wagon.Wagon;
import org.codehaus.mojo.versions.api.PomHelper;
import org.codehaus.mojo.versions.api.recording.ChangeRecorder;
import org.codehaus.mojo.versions.api.recording.DependencyChangeRecord;
import org.codehaus.mojo.versions.recording.DefaultDependencyChangeRecord;
import org.codehaus.mojo.versions.rewriting.MutableXMLStreamReader;
import org.codehaus.mojo.versions.utils.DependencyBuilder;
import org.codehaus.mojo.versions.utils.DependencyComparator;
import org.eclipse.aether.RepositorySystem;

/**
 * Base class for a mojo that updates dependency versions.
 *
 * @author Paul Gier
 * @author Stephen Connolly
 * @since 1.0-alpha-3
 */
public abstract class AbstractVersionsDependencyUpdaterMojo extends AbstractVersionsUpdaterMojo {
    private static final String END_RANGE_CHARS = "])";

    private static final String START_RANGE_CHARS = "[(";

    /**
     * Pattern to match snapshot versions
     */
    protected static final Pattern SNAPSHOT_REGEX = Pattern.compile("^(.+)-((SNAPSHOT)|(\\d{8}\\.\\d{6}-\\d+))$");

    /**
     * A comma separated list of artifact patterns to include. Follows the pattern
     * "groupId:artifactId:type:classifier:version". Designed to allow specifying the set of includes from the command
     * line. When specifying includes from the pom, use the {@link #includes} configuration instead. If this property is
     * specified then the {@link #includes} configuration is ignored.
     *
     * @since 1.0-beta-1
     */
    @Parameter(property = "includes")
    private String includesList = null;

    /**
     * A comma separated list of artifact patterns to exclude. Follows the pattern
     * "groupId:artifactId:type:classifier:version". Designed to allow specifying the set of excludes from the command
     * line. When specifying excludes from the pom, use the {@link #excludes} configuration instead. If this property is
     * specified then the {@link #excludes} configuration is ignored.
     *
     * @since 1.0-beta-1
     */
    @Parameter(property = "excludes")
    private String excludesList = null;

    /**
     * A list of artifact patterns to include. Follows the pattern "groupId:artifactId:type:classifier:version". This
     * configuration setting is ignored if {@link #includesList} is defined.
     *
     * @since 1.0-beta-1
     */
    @Parameter
    private String[] includes = null;

    /**
     * A list of artifact patterns to exclude. Follows the pattern "groupId:artifactId:type:classifier:version". This
     * configuration setting is ignored if {@link #excludesList} is defined.
     *
     * @since 1.0-beta-1
     */
    @Parameter
    private String[] excludes = null;

    /**
     * a scope to use to filter the artifacts matching the asked scope (as well as the ones implied by maven)
     *
     * @since 2.15
     */
    @Parameter(property = "scope")
    private String scope = null;

    /**
     * Whether to process the dependencies section of the project.
     *
     * @since 1.0-alpha-3
     */
    @Parameter(property = "processDependencies", defaultValue = "true")
    private boolean processDependencies = true;

    /**
     * Whether to process the dependencyManagement section of the project.
     *
     * @since 1.0-alpha-3
     */
    @Parameter(property = "processDependencyManagement", defaultValue = "true")
    private boolean processDependencyManagement = true;

    /**
     * Whether to process the parent section of the project. If not set will default to false.
     *
     * @since 2.3
     */
    @Parameter(property = "processParent", defaultValue = "false")
    private boolean processParent = false;

    /**
     * Artifact filter to determine if artifact should be included
     *
     * @since 1.0-alpha-3
     */
    private PatternIncludesArtifactFilter includesFilter;

    /**
     * Artifact filter to determine if artifact should be excluded
     *
     * @since 1.0-alpha-3
     */
    private PatternExcludesArtifactFilter excludesFilter;

    /**
     * Whether to skip processing dependencies that are produced as part of the current reactor.
     *
     * @since 1.0-alpha-3
     */
    @Parameter(property = "excludeReactor", defaultValue = "true")
    private boolean excludeReactor = true;

    @Inject
    protected AbstractVersionsDependencyUpdaterMojo(
            ArtifactHandlerManager artifactHandlerManager,
            RepositorySystem repositorySystem,
            Map wagonMap,
            Map changeRecorders) {
        super(artifactHandlerManager, repositorySystem, wagonMap, changeRecorders);
    }

    /**
     * Should the project/dependencies section of the pom be processed.
     *
     * @return returns true if the project/dependencies section of the pom should be processed.
     * @since 1.0-alpha-3
     */
    public boolean isProcessingDependencies() {
        return processDependencies;
    }

    /**
     * Should the project/dependencyManagement section of the pom be processed.
     *
     * @return returns true if the project/dependencyManagement section of the pom should be processed.
     * @since 1.0-alpha-3
     */
    public boolean isProcessingDependencyManagement() {
        return processDependencyManagement;
    }

    /**
     * Should the project/parent section of the pom be processed.
     *
     * @return returns true if the project/parent section of the pom should be processed.
     * @since 2.3
     */
    public boolean isProcessingParent() {
        return processParent;
    }

    /**
     * Should the artifacts produced in the current reactor be excluded from processing.
     *
     * @return returns true if the artifacts produced in the current reactor
     * should be excluded from processing.
     * @since 1.0-alpha-3
     */
    public boolean isExcludeReactor() {
        return excludeReactor;
    }

    /**
     * Should the dependency be updated itself or is it handled by properties.
     *
     * @param dependency Dependency
     * @return true if the version starts with '${'
     * @since 2.8
     */
    protected boolean isHandledByProperty(Dependency dependency) {
        String version = dependency.getVersion();
        return version != null && version.startsWith("${");
    }

    /**
     * Try to find the dependency artifact that matches the given dependency.
     *
     * @param dependency Dependency
     * @return Artifact
     * @since 1.0-alpha-3
     */
    protected Artifact findArtifact(Dependency dependency) {
        if (getProject().getDependencyArtifacts() == null) {
            return null;
        }
        for (Artifact artifact : getProject().getDependencyArtifacts()) {
            if (compare(artifact, dependency)) {
                return artifact;
            }
        }
        return null;
    }

    /**
     * Try to find the dependency artifact that matches the given dependency.
     *
     * @param dependency Dependency
     * @return Artifact
     * @throws MojoExecutionException Mojo execution exception
     * @since 1.0-alpha-3
     */
    protected Artifact toArtifact(Dependency dependency) throws MojoExecutionException {
        Artifact artifact = findArtifact(dependency);
        if (artifact == null) {
            return getHelper().createDependencyArtifact(dependency);
        }
        return artifact;
    }

    protected Artifact toArtifact(Parent model) throws MojoExecutionException {
        return this.toArtifact(DependencyBuilder.newBuilder()
                .withGroupId(model.getGroupId())
                .withArtifactId(model.getArtifactId())
                .withVersion(model.getVersion())
                .withType("pom")
                .withScope(Artifact.SCOPE_COMPILE)
                .build());
    }

    /**
     * Returns the {@link Dependency} instance for the parent project
     * @return {@link Dependency} object for the parent
     */
    protected Dependency getParentDependency() {
        return DependencyBuilder.newBuilder()
                .withGroupId(getProject().getParent().getGroupId())
                .withArtifactId(getProject().getParent().getArtifactId())
                .withVersion(getProject().getParent().getVersion())
                .withType("pom")
                .build();
    }

    protected String toString(MavenProject project) {
        StringBuilder buf = new StringBuilder();

        buf.append(project.getGroupId());
        buf.append(':');
        buf.append(project.getArtifactId());

        if (project.getVersion() != null && !project.getVersion().isEmpty()) {
            buf.append(":");
            buf.append(project.getVersion());
        }

        return buf.toString();
    }

    protected String toString(Dependency d) {
        StringBuilder buf = new StringBuilder();
        buf.append(d.getGroupId());
        buf.append(':');
        buf.append(d.getArtifactId());
        if (d.getType() != null && !d.getType().isEmpty()) {
            buf.append(':');
            buf.append(d.getType());
        } else {
            buf.append(":jar");
        }
        if (d.getClassifier() != null && !d.getClassifier().isEmpty()) {
            buf.append(':');
            buf.append(d.getClassifier());
        }
        if (d.getVersion() != null && !d.getVersion().isEmpty()) {
            buf.append(":");
            buf.append(d.getVersion());
        }
        return buf.toString();
    }

    /**
     * Returns true if the dependency is produced by the current reactor.
     *
     * @param dependency the dependency to heck.
     * @return true if the dependency is produced by the current reactor.
     * @since 1.0-alpha-3
     */
    protected boolean isProducedByReactor(Dependency dependency) {
        return reactorProjects.stream().anyMatch(reactorProject -> compare(reactorProject, dependency));
    }

    /**
     * Compare a project to a dependency. Returns true only if the groupId and artifactId are all equal.
     *
     * @param project the project
     * @param dep     the dependency
     * @return true if project and dep refer to the same artifact
     */
    private boolean compare(MavenProject project, Dependency dep) {
        if (!StringUtils.equals(project.getGroupId(), dep.getGroupId())) {
            return false;
        }
        return project.getArtifactId().equals(dep.getArtifactId());
    }

    /**
     * Compare and artifact to a dependency. Returns true only if the groupId, artifactId, type, and classifier are all
     * equal.
     *
     * @param artifact Artifact
     * @param dep      Dependency
     * @return true if artifact and dep refer to the same artifact
     */
    private boolean compare(Artifact artifact, Dependency dep) {
        if (!StringUtils.equals(artifact.getGroupId(), dep.getGroupId())) {
            return false;
        }
        if (!StringUtils.equals(artifact.getArtifactId(), dep.getArtifactId())) {
            return false;
        }
        if (!StringUtils.equals(artifact.getType(), dep.getType())) {
            return false;
        }
        return StringUtils.equals(artifact.getClassifier(), dep.getClassifier());
    }

    /**
     * Determine if the artifact is included in the list of artifacts to be processed.
     *
     * @param artifact The artifact we want to check.
     * @return true if the artifact should be processed, false otherwise.
     */
    protected boolean isIncluded(Artifact artifact) {
        boolean result = true;

        ArtifactFilter includesFilter = this.getIncludesArtifactFilter();

        if (includesFilter != null) {
            result = includesFilter.include(artifact);
        }

        ArtifactFilter excludesFilter = this.getExcludesArtifactFilter();

        if (excludesFilter != null) {
            result = result && excludesFilter.include(artifact);
        }

        ArtifactFilter scopeFilter = this.getScopeArtifactFilter();

        if (scopeFilter != null) {
            result = result && scopeFilter.include(artifact);
        }

        return result;
    }

    /**
     * Indicates whether any includes were specified via the 'includes' or 'includesList' options.
     *
     * @return true if includes were specified, false otherwise.
     */
    protected boolean hasIncludes() {
        return includes != null || includesList != null;
    }

    private ArtifactFilter getIncludesArtifactFilter() {
        if (includesFilter == null && (includes != null || includesList != null)) {
            List patterns = new ArrayList<>();
            if (this.includesList != null) {
                patterns.addAll(separatePatterns(includesList));
            } else {
                patterns.addAll(Arrays.asList(includes));
            }
            includesFilter = new PatternIncludesArtifactFilter(patterns);
        }
        return includesFilter;
    }

    private ArtifactFilter getExcludesArtifactFilter() {
        if (excludesFilter == null && (excludes != null || excludesList != null)) {
            List patterns = new ArrayList<>();
            if (excludesList != null) {
                patterns.addAll(separatePatterns(excludesList));
            } else {
                patterns.addAll(Arrays.asList(excludes));
            }
            excludesFilter = new PatternExcludesArtifactFilter(patterns);
        }
        return excludesFilter;
    }

    private ArtifactFilter getScopeArtifactFilter() {
        if (scope == null) {
            return null;
        }
        return new ScopeArtifactFilter(scope);
    }

    /**
     * To handle multiple includes with version range like "group:artifact:jar:[1.0.0,2.2)", we have to use a parsing a
     * little bit more complex than split().
     *
     * @param includeString the string to parse
     * @return list of patterns
     */
    protected List separatePatterns(String includeString) {
        if (includeString == null) {
            return Collections.emptyList();
        }

        List patterns = new ArrayList<>();
        int indexOf = nextCommaIndex(includeString);
        while (indexOf >= 0) {
            patterns.add(includeString.substring(0, indexOf));
            includeString = includeString.substring(indexOf + 1);
            indexOf = nextCommaIndex(includeString);
        }
        patterns.add(includeString);

        return patterns;
    }

    private int nextCommaIndex(final String includeString) {

        int indexOfComma = includeString.indexOf(',');
        int nextRangeStartDelimiterIndex = findFirstChar(includeString, START_RANGE_CHARS);
        if (nextRangeStartDelimiterIndex >= 0) {
            if (!(indexOfComma >= 0 && indexOfComma < nextRangeStartDelimiterIndex)) {
                int nextStopDelimiterIndex = findFirstChar(includeString, END_RANGE_CHARS);

                // recursive call
                int tmp = nextCommaIndex(includeString.substring(nextStopDelimiterIndex + 1));
                indexOfComma = (tmp >= 0) ? nextStopDelimiterIndex + 1 + tmp : -1;
            }
        }
        return indexOfComma;
    }

    private int findFirstChar(final String includeString, final String chars) {
        int nextRangeStartDelimiterIndex = -1;

        char[] delimiters = chars.toCharArray();
        for (char delimiter : delimiters) {
            int index = includeString.indexOf(delimiter);
            if (index >= 0 && nextRangeStartDelimiterIndex >= 0) {
                nextRangeStartDelimiterIndex = Math.min(index, nextRangeStartDelimiterIndex);
            } else {
                if (index >= 0) {
                    nextRangeStartDelimiterIndex = index;
                }
            }
        }
        return nextRangeStartDelimiterIndex;
    }

    /**
     * Attempts to update the dependency {@code dep} to the given {@code newVersion}. The dependency can either
     * be the parent project or any given dependency.
     *
     * @param pom {@link MutableXMLStreamReader} instance to update the POM XML document
     * @param dep dependency to be updated (can also be a dependency made from the parent)
     * @param newVersion new version to update the dependency to
     * @param changeKind title for the {@link ChangeRecorder} log
     * @return {@code true} if an update has been made, {@code false} otherwise
     * @throws XMLStreamException thrown if updating the XML doesn't succeed
     */
    protected boolean updateDependencyVersion(
            MutableXMLStreamReader pom, Dependency dep, String newVersion, DependencyChangeRecord.ChangeKind changeKind)
            throws XMLStreamException, MojoExecutionException {
        boolean updated = false;
        if (isProcessingParent()
                && getProject().getParent() != null
                && (DependencyComparator.INSTANCE.compare(
                                        dep,
                                        DependencyBuilder.newBuilder()
                                                .withGroupId(getProject()
                                                        .getParentArtifact()
                                                        .getGroupId())
                                                .withArtifactId(getProject()
                                                        .getParentArtifact()
                                                        .getArtifactId())
                                                .withVersion(getProject()
                                                        .getParentArtifact()
                                                        .getVersion())
                                                .build())
                                == 0
                        || getProject().getParentArtifact().getBaseVersion() != null
                                && DependencyComparator.INSTANCE.compare(
                                                dep,
                                                DependencyBuilder.newBuilder()
                                                        .withGroupId(getProject()
                                                                .getParentArtifact()
                                                                .getGroupId())
                                                        .withArtifactId(getProject()
                                                                .getParentArtifact()
                                                                .getArtifactId())
                                                        .withVersion(getProject()
                                                                .getParentArtifact()
                                                                .getBaseVersion())
                                                        .build())
                                        == 0)) {
            if (PomHelper.setProjectParentVersion(pom, newVersion)) {
                if (getLog().isDebugEnabled()) {
                    getLog().debug("Made parent update from " + dep.getVersion() + " to " + newVersion);
                }
                getChangeRecorder()
                        .recordChange(DefaultDependencyChangeRecord.builder()
                                .withKind(changeKind)
                                .withDependency(dep)
                                .withNewVersion(newVersion)
                                .build());
                updated = true;
            } else {
                getLog().warn("Could not update parent: " + dep.toString() + " to " + newVersion);
            }
        }

        if (PomHelper.setDependencyVersion(
                pom,
                dep.getGroupId(),
                dep.getArtifactId(),
                dep.getVersion(),
                newVersion,
                getProject().getModel(),
                getLog())) {
            if (getLog().isInfoEnabled()) {
                getLog().info("Updated " + toString(dep) + " to version " + newVersion);
            }
            getChangeRecorder()
                    .recordChange(DefaultDependencyChangeRecord.builder()
                            .withKind(changeKind)
                            .withDependency(dep)
                            .withNewVersion(newVersion)
                            .build());
            updated = true;
        }

        return updated;
    }

    // TODO: add an updatePropertyVersion as well??? (like in CompareDependenciesMojo)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy