001/*
002 * Extra Bnd Repository Plugins
003 * Copyright (C) 2017  Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package de.mnl.osgi.bnd.maven;
020
021import java.util.Comparator;
022import java.util.List;
023import org.apache.maven.artifact.versioning.ArtifactVersion;
024import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
025import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
026import org.apache.maven.artifact.versioning.Restriction;
027import org.apache.maven.artifact.versioning.VersionRange;
028
029/**
030 * Provides a representation of a maven version range. The implementation 
031 * is a small wrapper around
032 * {@link org.apache.maven.artifact.versioning.VersionRange}.
033 * <P>
034 * Because {@link org.apache.maven.artifact.versioning.VersionRange} has 
035 * only a private constructor and cannot be extended, the wrapper
036 * delegates to an instance of 
037 * {@link org.apache.maven.artifact.versioning.VersionRange}.
038 */
039@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
040public class MavenVersionRange extends MavenVersionSpecification {
041
042    public static final MavenVersionRange ALL;
043    public static final MavenVersionRange NONE;
044    private static final ArtifactVersion ZERO = new DefaultArtifactVersion("0");
045    private VersionRange range;
046
047    static {
048        VersionRange range;
049        try {
050            range = VersionRange.createFromVersionSpec("[0,)");
051        } catch (InvalidVersionSpecificationException e) {
052            // Won't happen (checked).
053            range = null;
054        }
055        ALL = new MavenVersionRange(range);
056        try {
057            range = VersionRange.createFromVersionSpec("[,0)");
058        } catch (InvalidVersionSpecificationException e) {
059            // Won't happen (checked).
060            range = null;
061        }
062        NONE = new MavenVersionRange(range);
063    }
064
065    /**
066     * Instantiates a new maven version range from the given range.
067     *
068     * @param range the range
069     */
070    public MavenVersionRange(VersionRange range) {
071        this.range = range;
072    }
073
074    /**
075     * Instantiates a new maven version range from the given representation.
076     * If {@code version} is {@code} null it is considered to be
077     * the "all inclusive range" ("[0,)").
078     *
079     * @param range the range
080     */
081    public MavenVersionRange(String range) {
082        if (range == null) {
083            this.range = ALL.range;
084            return;
085        }
086        try {
087            this.range = VersionRange.createFromVersionSpec(range);
088        } catch (InvalidVersionSpecificationException e) {
089            throw new IllegalArgumentException(e);
090        }
091    }
092
093    /**
094     * Returns the version range that this instance delegates to.
095     *
096     * @return the org.apache.maven.artifact.versioning. version range
097     */
098    public VersionRange versionRange() {
099        return range;
100    }
101
102    /**
103     * Returns the complementary version rang.
104     *
105     * @return the maven version range
106     */
107    @SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.CognitiveComplexity" })
108    public MavenVersionRange complement() {
109        List<Restriction> restrictions = range.getRestrictions();
110        restrictions.sort(Comparator.comparing(Restriction::getLowerBound));
111        ArtifactVersion lastVersion = new DefaultArtifactVersion("0");
112        boolean lastUpperInclusive = false;
113        StringBuilder cmpl = new StringBuilder();
114        for (Restriction rstrct : restrictions) {
115            ArtifactVersion rstrctLower = rstrct.getLowerBound();
116            if (rstrctLower == null) {
117                rstrctLower = ZERO;
118            }
119            int cmp = lastVersion.compareTo(rstrctLower);
120            if (cmp < 0 || cmp == 0
121                && !(lastUpperInclusive || rstrct.isLowerBoundInclusive())) {
122                // Not overlap or continuation.
123                if (cmpl.length() > 0) {
124                    cmpl.append(',');
125                }
126                cmpl.append(lastUpperInclusive ? '(' : '[');
127                cmpl.append(lastVersion.toString());
128                cmpl.append(',');
129                cmpl.append(rstrct.getLowerBound().toString());
130                cmpl.append(rstrct.isLowerBoundInclusive() ? ')' : ']');
131            }
132            lastVersion = rstrct.getUpperBound();
133            lastUpperInclusive = rstrct.isUpperBoundInclusive();
134            if (lastVersion == null) {
135                // Any restriction with open upper end is final
136                // (cannot add range to maximum range).
137                break;
138            }
139        }
140        if (lastVersion == null) {
141            // Open ended, check if it was "all" ("[0,)")
142            if (cmpl.length() == 0) {
143                cmpl.append("[,0)");
144            }
145        } else {
146            // Not open ended, so we must provide the last restriction.
147            if (cmpl.length() > 0) {
148                cmpl.append(',');
149            }
150            cmpl.append(lastUpperInclusive ? '(' : '[');
151            cmpl.append(lastVersion.toString());
152            cmpl.append(",)");
153        }
154        return new MavenVersionRange(cmpl.toString());
155    }
156
157    /**
158     * Checks if this version range includes the specified version
159     * or range. A range is included if it is fully included.
160     *
161     * @param mavenVersion the maven version
162     * @return the result
163     */
164    public boolean includes(MavenVersionSpecification mavenVersion) {
165        if (mavenVersion instanceof MavenVersion) {
166            return range.containsVersion((MavenVersion) mavenVersion);
167        }
168        return restrict((MavenVersionRange) mavenVersion).range
169            .getRestrictions().isEmpty();
170    }
171
172    /**
173     * Creates and returns a new VersionRange that is a restriction 
174     * of this version range and the specified version range.
175     *
176     * @see VersionRange#restrict
177     *
178     * @param restriction the restriction
179     * @return the maven version range
180     */
181    public MavenVersionRange restrict(MavenVersionRange restriction) {
182        return new MavenVersionRange(range.restrict(restriction.range));
183    }
184
185    /**
186     * Creates a new maven version range from the given representation.
187     *
188     * @param version the string representation
189     * @return the maven version range
190     */
191    public static MavenVersionRange parseRange(String version) {
192        return new MavenVersionRange(version);
193    }
194
195    /**
196     * Checks if is the provided version representation is a range.
197     * If {@code version} is {@code} null it is considered to be
198     * the "all inclusive range" ("[0,)").
199     *
200     * @param version the version
201     * @return true, if is range
202     * @deprecated Use {@link MavenVersionSpecification#isRange(String)} instead
203     */
204    @Deprecated
205    public static boolean isRange(String version) {
206        return MavenVersionSpecification.isRange(version);
207    }
208
209    @Override
210    public String toString() {
211        return range.toString();
212    }
213
214    /*
215     * (non-Javadoc)
216     * 
217     * @see java.lang.Object#hashCode()
218     */
219    @Override
220    public int hashCode() {
221        return range.hashCode();
222    }
223
224    /*
225     * (non-Javadoc)
226     * 
227     * @see java.lang.Object#equals(java.lang.Object)
228     */
229    @Override
230    public boolean equals(Object obj) {
231        if (obj instanceof MavenVersionRange) {
232            return range.equals(((MavenVersionRange) obj).range);
233        }
234        if (obj instanceof VersionRange) {
235            return range.equals((VersionRange) obj);
236        }
237        return false;
238    }
239
240}