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;
023
024import org.apache.maven.artifact.versioning.ArtifactVersion;
025import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
026import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
027import org.apache.maven.artifact.versioning.Restriction;
028import org.apache.maven.artifact.versioning.VersionRange;
029
030/**
031 * Provides a representation of a maven version range. The implementation 
032 * is a small wrapper around
033 * {@link org.apache.maven.artifact.versioning.VersionRange}.
034 * <P>
035 * Because {@link org.apache.maven.artifact.versioning.VersionRange} has 
036 * only a private constructor and cannot be extended, the wrapper
037 * delegates to an instance of 
038 * {@link org.apache.maven.artifact.versioning.VersionRange}.
039 */
040@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
041public class MavenVersionRange extends MavenVersionSpecification {
042
043    public static final MavenVersionRange ALL;
044    public static final MavenVersionRange NONE;
045    private static final ArtifactVersion ZERO = new DefaultArtifactVersion("0");
046    private VersionRange range;
047
048    static {
049        VersionRange range;
050        try {
051            range = VersionRange.createFromVersionSpec("[0,)");
052        } catch (InvalidVersionSpecificationException e) {
053            // Won't happen (checked).
054            range = null;
055        }
056        ALL = new MavenVersionRange(range);
057        try {
058            range = VersionRange.createFromVersionSpec("[,0)");
059        } catch (InvalidVersionSpecificationException e) {
060            // Won't happen (checked).
061            range = null;
062        }
063        NONE = new MavenVersionRange(range);
064    }
065
066    /**
067     * Instantiates a new maven version range from the given range.
068     *
069     * @param range the range
070     */
071    public MavenVersionRange(VersionRange range) {
072        this.range = range;
073    }
074
075    /**
076     * Instantiates a new maven version range from the given representation.
077     * If {@code version} is {@code} null it is considered to be
078     * the "all inclusive range" ("[0,)").
079     *
080     * @param range the range
081     */
082    public MavenVersionRange(String range) {
083        if (range == null) {
084            this.range = ALL.range;
085            return;
086        }
087        try {
088            this.range = VersionRange.createFromVersionSpec(range);
089        } catch (InvalidVersionSpecificationException e) {
090            throw new IllegalArgumentException(e);
091        }
092    }
093
094    /**
095     * Returns the version range that this instance delegates to.
096     *
097     * @return the org.apache.maven.artifact.versioning. version range
098     */
099    public VersionRange versionRange() {
100        return range;
101    }
102
103    /**
104     * Returns the complementary version rang.
105     *
106     * @return the maven version range
107     */
108    @SuppressWarnings("PMD.CyclomaticComplexity")
109    public MavenVersionRange complement() {
110        List<Restriction> restrictions = range.getRestrictions();
111        restrictions.sort(Comparator.comparing(Restriction::getLowerBound));
112        ArtifactVersion lastVersion = new DefaultArtifactVersion("0");
113        boolean lastUpperInclusive = false;
114        StringBuilder cmpl = new StringBuilder();
115        for (Restriction rstrct : restrictions) {
116            ArtifactVersion rstrctLower = rstrct.getLowerBound();
117            if (rstrctLower == null) {
118                rstrctLower = ZERO;
119            }
120            int cmp = lastVersion.compareTo(rstrctLower);
121            if (cmp < 0 || cmp == 0
122                && !(lastUpperInclusive || rstrct.isLowerBoundInclusive())) {
123                // Not overlap or continuation.
124                if (cmpl.length() > 0) {
125                    cmpl.append(',');
126                }
127                cmpl.append(lastUpperInclusive ? '(' : '[');
128                cmpl.append(lastVersion.toString());
129                cmpl.append(',');
130                cmpl.append(rstrct.getLowerBound().toString());
131                cmpl.append(rstrct.isLowerBoundInclusive() ? ')' : ']');
132            }
133            lastVersion = rstrct.getUpperBound();
134            lastUpperInclusive = rstrct.isUpperBoundInclusive();
135            if (lastVersion == null) {
136                // Any restriction with open upper end is final
137                // (cannot add range to maximum range).
138                break;
139            }
140        }
141        if (lastVersion == null) {
142            // Open ended, check if it was "all" ("[0,)")
143            if (cmpl.length() == 0) {
144                cmpl.append("[,0)");
145            }
146        } else {
147            // Not open ended, so we must provide the last restriction.
148            if (cmpl.length() > 0) {
149                cmpl.append(',');
150            }
151            cmpl.append(lastUpperInclusive ? '(' : '[');
152            cmpl.append(lastVersion.toString());
153            cmpl.append(",)");
154        }
155        return new MavenVersionRange(cmpl.toString());
156    }
157
158    /**
159     * Checks if this version range includes the specified version
160     * or range. A range is included if it is fully included.
161     *
162     * @param mavenVersion the maven version
163     * @return the result
164     */
165    public boolean includes(MavenVersionSpecification mavenVersion) {
166        if (mavenVersion instanceof MavenVersion) {
167            return range.containsVersion((MavenVersion) mavenVersion);
168        }
169        return restrict((MavenVersionRange) mavenVersion).range
170            .getRestrictions().isEmpty();
171    }
172
173    /**
174     * Creates and returns a new VersionRange that is a restriction 
175     * of this version range and the specified version range.
176     *
177     * @see VersionRange#restrict
178     *
179     * @param restriction the restriction
180     * @return the maven version range
181     */
182    public MavenVersionRange restrict(MavenVersionRange restriction) {
183        return new MavenVersionRange(range.restrict(restriction.range));
184    }
185
186    /**
187     * Creates a new maven version range from the given representation.
188     *
189     * @param version the string representation
190     * @return the maven version range
191     */
192    public static MavenVersionRange parseRange(String version) {
193        return new MavenVersionRange(version);
194    }
195
196    /**
197     * Checks if is the provided version representation is a range.
198     * If {@code version} is {@code} null it is considered to be
199     * the "all inclusive range" ("[0,)").
200     *
201     * @param version the version
202     * @return true, if is range
203     * @deprecated Use {@link MavenVersionSpecification#isRange(String)} instead
204     */
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}