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

org.codehaus.mojo.versions.api.AbstractVersionDetails Maven / Gradle / Ivy

There is a newer version: 2.18.0
Show newest version
package org.codehaus.mojo.versions.api;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.Restriction;
import org.apache.maven.artifact.versioning.VersionRange;
import org.codehaus.mojo.versions.ordering.BoundArtifactVersion;
import org.codehaus.mojo.versions.ordering.InvalidSegmentException;
import org.codehaus.mojo.versions.ordering.VersionComparator;
import org.codehaus.mojo.versions.utils.DefaultArtifactVersionCache;

import static java.util.Collections.reverseOrder;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static org.codehaus.mojo.versions.api.Segment.MAJOR;
import static org.codehaus.mojo.versions.api.Segment.SUBINCREMENTAL;

/**
 * Base class for {@link org.codehaus.mojo.versions.api.VersionDetails}.
 *
 * @author Stephen Connolly
 * @since 1.0-beta-1
 */
public abstract class AbstractVersionDetails implements VersionDetails {

    private static final Pattern PREVIEW_PATTERN =
            Pattern.compile("(?i)(?:.*[-.](alpha|a|beta|b|milestone|m|preview|rc)"
                    + "[-.]?(\\d{0,2}[a-z]?|\\d{6}\\.\\d{4})|\\d{8}(?:\\.?\\d{6})?)$");

    /**
     * Current version of the dependency artifact.
     *
     * @since 1.0-beta-1
     */
    private ArtifactVersion currentVersion = null;

    private VersionRange currentVersionRange = null;

    protected boolean verboseDetail = true;

    protected AbstractVersionDetails() {}

    /**
     * If a version is a version range consisting of one or more version ranges, returns the highest lower
     * bound. If a single version range is present, returns its value.
     * @param lowerBoundVersion actual version used
     * @return highest lower bound of the given version range or {@link #getCurrentVersion()} if there's no lower bound
     */
    protected ArtifactVersion getHighestLowerBound(ArtifactVersion lowerBoundVersion) {
        return getCurrentVersionRange().getRestrictions().stream()
                .map(Restriction::getLowerBound)
                .filter(Objects::nonNull)
                .max(getVersionComparator())
                .orElse(lowerBoundVersion);
    }

    /**
     * If the artifact is bound by one or more version ranges, returns the restriction that constitutes
     * the version range containing the selected actual version.
     * If there are no version ranges, returns the provided version.
     * @param selectedVersion actual version used, may not be {@code null}
     * @return restriction containing the version range selected by the given version,
     * or {@link Optional#empty()} if there are no ranges
     */
    protected Optional getSelectedRestriction(ArtifactVersion selectedVersion) {
        assert selectedVersion != null;
        return Optional.ofNullable(getCurrentVersionRange())
                .map(VersionRange::getRestrictions)
                .flatMap(r -> r.stream()
                        .filter(rr -> rr.containsVersion(selectedVersion))
                        .findAny());
    }

    @Override
    public Restriction restrictionForSelectedSegment(ArtifactVersion lowerBound, Optional selectedSegment) {
        ArtifactVersion highestLowerBound = getHighestLowerBound(lowerBound);
        ArtifactVersion nextVersion = selectedSegment
                .filter(s -> s.isMajorTo(SUBINCREMENTAL))
                .map(Segment::minorTo)
                .map(s -> (ArtifactVersion) new BoundArtifactVersion(highestLowerBound, s))
                .orElse(highestLowerBound);
        return new Restriction(
                nextVersion,
                false,
                selectedSegment
                        .filter(MAJOR::isMajorTo)
                        .map(s -> (ArtifactVersion) new BoundArtifactVersion(highestLowerBound, s))
                        .orElse(null),
                false);
    }

    @Override
    public Restriction restrictionForUnchangedSegment(
            ArtifactVersion actualVersion, Optional unchangedSegment, boolean allowDowngrade)
            throws InvalidSegmentException {
        Optional selectedRestriction =
                Optional.ofNullable(actualVersion).flatMap(this::getSelectedRestriction);
        ArtifactVersion selectedRestrictionUpperBound =
                selectedRestriction.map(Restriction::getUpperBound).orElse(actualVersion);
        ArtifactVersion lowerBound = allowDowngrade
                ? getLowerBound(selectedRestrictionUpperBound, unchangedSegment)
                        .map(DefaultArtifactVersionCache::of)
                        .orElse(null)
                : selectedRestrictionUpperBound;
        ArtifactVersion upperBound = unchangedSegment
                .map(s -> (ArtifactVersion) new BoundArtifactVersion(
                        selectedRestrictionUpperBound, s.isMajorTo(SUBINCREMENTAL) ? Segment.minorTo(s) : s))
                .orElse(null);
        return new Restriction(
                lowerBound,
                allowDowngrade
                        || selectedRestriction
                                .map(Restriction::isUpperBoundInclusive)
                                .map(b -> !b)
                                .orElse(false),
                upperBound,
                allowDowngrade);
    }

    @Override
    public Restriction restrictionForIgnoreScope(ArtifactVersion lowerBound, Optional ignored) {
        ArtifactVersion highestLowerBound = getHighestLowerBound(lowerBound);
        ArtifactVersion nextVersion = ignored.map(s -> (ArtifactVersion) new BoundArtifactVersion(highestLowerBound, s))
                .orElse(highestLowerBound);
        return new Restriction(nextVersion, false, null, false);
    }

    @Override
    public final ArtifactVersion getCurrentVersion() {
        return currentVersion;
    }

    @Override
    public final void setCurrentVersion(ArtifactVersion currentVersion) {
        this.currentVersion = currentVersion;
    }

    @Override
    public final VersionRange getCurrentVersionRange() {
        return currentVersionRange;
    }

    @Override
    public final void setCurrentVersionRange(VersionRange versionRange) {
        currentVersionRange = versionRange;
    }

    @Override
    public final void setCurrentVersion(String currentVersion) {
        setCurrentVersion(currentVersion == null ? null : DefaultArtifactVersionCache.of(currentVersion));
    }

    @Override
    public final ArtifactVersion[] getVersions(VersionRange versionRange, boolean includeSnapshots) {
        return getVersions(versionRange, null, includeSnapshots);
    }

    @Override
    public final ArtifactVersion getNewestVersion(
            VersionRange versionRange, Restriction restriction, boolean includeSnapshots) {
        return getNewestVersion(versionRange, restriction, includeSnapshots, false);
    }

    @Override
    public final ArtifactVersion getNewestVersion(
            VersionRange versionRange, Restriction restriction, boolean includeSnapshots, boolean allowDowngrade) {
        // reverseOrder( getVersions( ... ) ) will contain versions sorted from latest to oldest,
        // so we only need to find the first candidate fulfilling the criteria
        return Arrays.stream(getVersions(includeSnapshots))
                .sorted(reverseOrder())
                .filter(candidate -> allowDowngrade
                        || versionRange == null
                        || ArtifactVersions.isVersionInRange(candidate, versionRange))
                .filter(candidate -> restriction == null || isVersionInRestriction(restriction, candidate))
                .filter(candidate -> includeSnapshots || !ArtifactUtils.isSnapshot(candidate.toString()))
                .findAny()
                .orElse(null);
    }

    @Override
    public final ArtifactVersion getNewestVersion(Restriction restriction, boolean includeSnapshots) {
        return getNewestVersion(null, restriction, includeSnapshots);
    }

    @Override
    public final ArtifactVersion getNewestVersion(VersionRange versionRange, boolean includeSnapshots) {
        return getNewestVersion(versionRange, null, includeSnapshots);
    }

    @Override
    public final boolean containsVersion(String version) {
        for (ArtifactVersion candidate : getVersions(true)) {
            if (version.equals(candidate.toString())) {
                return true;
            }
        }
        return false;
    }

    @Override
    public final ArtifactVersion[] getNewerVersions(
            String versionString, Optional unchangedSegment, boolean includeSnapshots, boolean allowDowngrade)
            throws InvalidSegmentException {
        ArtifactVersion currentVersion = DefaultArtifactVersionCache.of(versionString);
        ArtifactVersion lowerBound = allowDowngrade
                ? getLowerBound(currentVersion, unchangedSegment)
                        .map(DefaultArtifactVersionCache::of)
                        .orElse(null)
                : currentVersion;
        ArtifactVersion upperBound = unchangedSegment
                .map(s -> (ArtifactVersion)
                        new BoundArtifactVersion(currentVersion, s.isMajorTo(SUBINCREMENTAL) ? Segment.minorTo(s) : s))
                .orElse(null);

        Restriction restriction = new Restriction(lowerBound, allowDowngrade, upperBound, allowDowngrade);
        return getVersions(restriction, includeSnapshots);
    }

    @Override
    public Optional getNewestVersion(
            String actualVersion, Optional unchangedSegment, boolean includeSnapshots, boolean allowDowngrade)
            throws InvalidSegmentException {
        Restriction segmentRestriction = restrictionForUnchangedSegment(
                DefaultArtifactVersionCache.of(actualVersion), unchangedSegment, allowDowngrade);
        Restriction lookupRestriction;
        if (!allowDowngrade
                && Optional.ofNullable(currentVersion)
                        .map(v -> v.compareTo(segmentRestriction.getLowerBound()) > 0)
                        .orElse(false)) {
            lookupRestriction = new Restriction(currentVersion, false, null, false);
        } else {
            lookupRestriction = segmentRestriction;
        }
        return Arrays.stream(getVersions(includeSnapshots))
                .filter(candidate -> isVersionInRestriction(lookupRestriction, candidate))
                .filter(candidate -> includeSnapshots || !ArtifactUtils.isSnapshot(candidate.toString()))
                .max(getVersionComparator());
    }

    @Override
    public final ArtifactVersion[] getVersions(Restriction restriction, boolean includeSnapshots) {
        return getVersions(null, restriction, includeSnapshots);
    }

    @Override
    public final ArtifactVersion[] getVersions(
            VersionRange versionRange, Restriction restriction, boolean includeSnapshots) {
        return Arrays.stream(getVersions(includeSnapshots))
                .filter(candidate -> versionRange == null || ArtifactVersions.isVersionInRange(candidate, versionRange))
                .filter(candidate -> restriction == null || isVersionInRestriction(restriction, candidate))
                .filter(candidate -> includeSnapshots || !ArtifactUtils.isSnapshot(candidate.toString()))
                .sorted(getVersionComparator())
                .distinct()
                .toArray(ArtifactVersion[]::new);
    }

    @Override
    public final ArtifactVersion getNewestUpdateWithinSegment(
            ArtifactVersion currentVersion, Optional updateScope, boolean includeSnapshots) {
        return getNewestVersion(restrictionForSelectedSegment(currentVersion, updateScope), includeSnapshots);
    }

    @Override
    public final ArtifactVersion[] getAllUpdates(
            ArtifactVersion currentVersion, Optional updateScope, boolean includeSnapshots) {
        return getVersions(restrictionForSelectedSegment(currentVersion, updateScope), includeSnapshots);
    }

    @Override
    public final ArtifactVersion getNewestUpdateWithinSegment(Optional updateScope, boolean includeSnapshots) {
        if (getCurrentVersion() != null) {
            return getNewestUpdateWithinSegment(getCurrentVersion(), updateScope, includeSnapshots);
        }
        return null;
    }

    @Override
    public final ArtifactVersion[] getAllUpdates(Optional updateScope, boolean includeSnapshots) {
        if (getCurrentVersion() != null) {
            return getAllUpdates(getCurrentVersion(), updateScope, includeSnapshots);
        }
        return null;
    }

    @Override
    public final ArtifactVersion[] getAllUpdates(boolean includeSnapshots) {
        return getAllUpdates((VersionRange) null, includeSnapshots);
    }

    @Override
    public ArtifactVersion[] getAllUpdates(VersionRange versionRange, boolean includeSnapshots) {
        Restriction restriction = new Restriction(getCurrentVersion(), false, null, false);
        return getVersions(versionRange, restriction, includeSnapshots);
    }

    /**
     * Returns the string designation of the lower bound version based on the given artifact version
     * and the lowest unchanged segment index (0-based); -1 means that the whole version string can be changed,
     * implying that there is also no string designation of the lower bound version.
     *
     * @param version {@link ArtifactVersion} object specifying the version for which the lower bound is being computed
     * @param unchangedSegment first segment not to be changed; empty() means anything can change
     * @return {@link Optional} string containing the lowest artifact version with the given segment held
     * @throws InvalidSegmentException if the requested segment is outside of the bounds (less than 1 or greater than
     * the segment count)
     */
    protected Optional getLowerBound(ArtifactVersion version, Optional unchangedSegment)
            throws InvalidSegmentException {
        if (!unchangedSegment.isPresent()) {
            return empty();
        }

        int segmentCount = getVersionComparator().getSegmentCount(version);
        if (unchangedSegment.get().value() > segmentCount) {
            throw new InvalidSegmentException(unchangedSegment.get(), segmentCount, version);
        }

        StringBuilder newVersion = new StringBuilder();
        newVersion.append(version.getMajorVersion());

        if (segmentCount > 0) {
            newVersion.append(".").append(unchangedSegment.get().value() >= 1 ? version.getMinorVersion() : 0);
        }
        if (segmentCount > 1) {
            newVersion.append(".").append(unchangedSegment.get().value() >= 2 ? version.getIncrementalVersion() : 0);
        }
        if (segmentCount > 2) {
            if (version.getQualifier() != null) {
                newVersion.append("-").append(unchangedSegment.get().value() >= 3 ? version.getQualifier() : "0");
            } else {
                newVersion.append("-").append(unchangedSegment.get().value() >= 3 ? version.getBuildNumber() : "0");
            }
        }
        return of(newVersion.toString());
    }

    /**
     * Checks if the candidate version is in the range of the restriction.
     * a custom comparator is/can be used to have milestones and rcs before final releases,
     * which is not yet possible with {@link Restriction#containsVersion(ArtifactVersion)}.
     * @param restriction the range to check against.
     * @param candidate the version to check.
     * @return true if the candidate version is within the range of the restriction parameter.
     */
    public boolean isVersionInRestriction(Restriction restriction, ArtifactVersion candidate) {
        ArtifactVersion lowerBound = restriction.getLowerBound();
        ArtifactVersion upperBound = restriction.getUpperBound();
        boolean includeLower = restriction.isLowerBoundInclusive();
        boolean includeUpper = restriction.isUpperBoundInclusive();
        final VersionComparator versionComparator = getVersionComparator();
        int lower = lowerBound == null ? -1 : versionComparator.compare(lowerBound, candidate);
        int upper = upperBound == null ? +1 : versionComparator.compare(upperBound, candidate);
        if (lower > 0 || upper < 0) {
            return false;
        }
        return (includeLower || lower != 0) && (includeUpper || upper != 0);
    }

    /**
     * Returns the latest version newer than the specified current version, and within the specified update scope,
     * or {@code null} if no such version exists.
     * @param updateScope the scope of updates to include.
     * @param includeSnapshots whether snapshots should be included
     * @return the newest version after currentVersion within the specified update scope,
     *         or null if no version is available.
     */
    public final ArtifactVersion getReportNewestUpdate(Optional updateScope, boolean includeSnapshots) {
        return getArtifactVersionStream(updateScope, includeSnapshots)
                .min(Collections.reverseOrder(getVersionComparator()))
                .orElse(null);
    }

    /**
     * Returns all versions newer than the specified current version, and within the specified update scope.
     * @param updateScope the scope of updates to include.
     * @param includeSnapshots whether snapshots should be included
     * @return all versions after currentVersion within the specified update scope.
     */
    public final ArtifactVersion[] getReportUpdates(Optional updateScope, boolean includeSnapshots) {
        TreeSet versions = getArtifactVersionStream(updateScope, includeSnapshots)
                .collect(Collectors.toCollection(() -> new TreeSet<>(getVersionComparator())));
        // filter out intermediate minor versions.
        if (!verboseDetail) {
            int major = 0;
            int minor = 0;
            boolean needOneMore = false;
            for (Iterator it = versions.descendingIterator(); it.hasNext(); ) {
                ArtifactVersion version = it.next();
                boolean isPreview = PREVIEW_PATTERN.matcher(version.toString()).matches();

                // encountered a version in same Major.Minor version, remove it.
                if (version.getMajorVersion() == major && version.getMinorVersion() == minor) {
                    if (needOneMore && !isPreview) {
                        needOneMore = false;
                        continue;
                    }
                    it.remove();
                    continue;
                }

                // encountered a new Major.Minor version, keep it.
                major = version.getMajorVersion();
                minor = version.getMinorVersion();

                // if version is a pre-release, also search for the last release.
                needOneMore = isPreview;
            }
        }
        return versions.toArray(new ArtifactVersion[0]);
    }

    /**
     * Returns all versions newer than the specified current version, and within the specified update scope.
     * @param updateScope the scope of updates to include.
     * @param includeSnapshots whether snapshots should be included
     * @return all versions after currentVersion within the specified update scope.
     */
    private Stream getArtifactVersionStream(Optional updateScope, boolean includeSnapshots) {
        if (getCurrentVersion() != null) {
            Restriction restriction = restrictionForSelectedSegment(getCurrentVersion(), updateScope);

            return Arrays.stream(getVersions(includeSnapshots))
                    .filter(candidate -> isVersionInRestriction(restriction, candidate));
        }
        return Stream.empty();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy