View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.shared.release.versions;
20  
21  import java.util.Arrays;
22  import java.util.List;
23  import java.util.Objects;
24  import java.util.regex.Matcher;
25  import java.util.regex.Pattern;
26  
27  import org.apache.maven.artifact.ArtifactUtils;
28  import org.codehaus.plexus.util.StringUtils;
29  
30  /**
31   * <p>Version class.</p>
32   */
33  public class Version implements Comparable<Version>, Cloneable {
34      private final AetherVersion aetherVersion;
35  
36      private final MavenArtifactVersion mavenArtifactVersion;
37  
38      private final String strVersion;
39  
40      private final List<String> digits;
41  
42      private String annotation;
43  
44      private String annotationRevision;
45  
46      private final String buildSpecifier;
47  
48      private String annotationSeparator;
49  
50      private String annotationRevSeparator;
51  
52      private String buildSeparator;
53  
54      private static final int DIGITS_INDEX = 1;
55  
56      private static final int ANNOTATION_SEPARATOR_INDEX = 2;
57  
58      private static final int ANNOTATION_INDEX = 3;
59  
60      private static final int ANNOTATION_REV_SEPARATOR_INDEX = 4;
61  
62      private static final int ANNOTATION_REVISION_INDEX = 5;
63  
64      private static final int BUILD_SEPARATOR_INDEX = 6;
65  
66      private static final int BUILD_SPECIFIER_INDEX = 7;
67  
68      private static final String SNAPSHOT_IDENTIFIER = "SNAPSHOT";
69  
70      private static final String DIGIT_SEPARATOR_STRING = ".";
71  
72      private static final String DEFAULT_ANNOTATION_REV_SEPARATOR = "-";
73  
74      private static final String DEFAULT_BUILD_SEPARATOR = "-";
75  
76      /** Constant <code>STANDARD_PATTERN</code> */
77      public static final Pattern STANDARD_PATTERN = Pattern.compile(
78              "^((?:\\d+\\.)*\\d+)" // digit(s) and '.' repeated -
79                      // followed by digit (version
80                      // digits 1.22.0, etc)
81                      + "([-_])?" // optional - or _ (annotation separator)
82                      + "([a-zA-Z]*)" // alpha characters (looking for annotation - alpha, beta, RC, etc.)
83                      + "([-_])?" // optional - or _ (annotation revision separator)
84                      + "(\\d*)" // digits (any digits after rc or beta is an annotation revision)
85                      + "(?:([-_])?(.*?))?$"); // - or _ followed everything else (build specifier)
86  
87      /* *
88       * cmaki 02242009 FIX for non-digit release numbers, e.g. trunk-SNAPSHOT or just SNAPSHOT This alternate pattern
89       * supports version numbers like: trunk-SNAPSHOT branchName-SNAPSHOT SNAPSHOT
90       */
91      // for SNAPSHOT releases only (possible versions include: trunk-SNAPSHOT or SNAPSHOT)
92      /** Constant <code>ALTERNATE_PATTERN</code> */
93      public static final Pattern ALTERNATE_PATTERN = Pattern.compile("^(SNAPSHOT|[a-zA-Z]+[_-]SNAPSHOT)");
94  
95      private Version(
96              List<String> digits,
97              String annotation,
98              String annotationRevision,
99              String buildSpecifier,
100             String annotationSeparator,
101             String annotationRevSeparator,
102             String buildSeparator) {
103         this.digits = digits;
104         this.annotation = annotation;
105         this.annotationRevision = annotationRevision;
106         this.buildSpecifier = buildSpecifier;
107         this.annotationSeparator = annotationSeparator;
108         this.annotationRevSeparator = annotationRevSeparator;
109         this.buildSeparator = buildSeparator;
110         this.strVersion = getVersionString(this, buildSpecifier, buildSeparator);
111 
112         // for now no need to reparse, original version was valid
113         this.aetherVersion = null;
114         this.mavenArtifactVersion = null;
115     }
116 
117     /**
118      * <p>Constructor for Version.</p>
119      *
120      * @param version a {@link java.lang.String} object
121      * @throws org.apache.maven.shared.release.versions.VersionParseException if any.
122      */
123     public Version(String version) throws VersionParseException {
124         this.strVersion = version;
125         this.aetherVersion = new AetherVersion(version);
126         this.mavenArtifactVersion = new MavenArtifactVersion(version);
127 
128         // FIX for non-digit release numbers, e.g. trunk-SNAPSHOT or just SNAPSHOT
129         Matcher matcher = ALTERNATE_PATTERN.matcher(strVersion);
130         // TODO: hack because it didn't support "SNAPSHOT"
131         if (matcher.matches()) {
132             annotation = null;
133             digits = null;
134             buildSpecifier = version;
135             buildSeparator = null;
136             return;
137         }
138 
139         Matcher m = STANDARD_PATTERN.matcher(strVersion);
140         if (m.matches()) {
141             digits = parseDigits(m.group(DIGITS_INDEX));
142             if (!SNAPSHOT_IDENTIFIER.equals(m.group(ANNOTATION_INDEX))) {
143                 annotationSeparator = m.group(ANNOTATION_SEPARATOR_INDEX);
144                 annotation = nullIfEmpty(m.group(ANNOTATION_INDEX));
145 
146                 if (StringUtils.isNotEmpty(m.group(ANNOTATION_REV_SEPARATOR_INDEX))
147                         && StringUtils.isEmpty(m.group(ANNOTATION_REVISION_INDEX))) {
148                     // The build separator was picked up as the annotation revision separator
149                     buildSeparator = m.group(ANNOTATION_REV_SEPARATOR_INDEX);
150                     buildSpecifier = nullIfEmpty(m.group(BUILD_SPECIFIER_INDEX));
151                 } else {
152                     annotationRevSeparator = m.group(ANNOTATION_REV_SEPARATOR_INDEX);
153                     annotationRevision = nullIfEmpty(m.group(ANNOTATION_REVISION_INDEX));
154 
155                     buildSeparator = m.group(BUILD_SEPARATOR_INDEX);
156                     buildSpecifier = nullIfEmpty(m.group(BUILD_SPECIFIER_INDEX));
157                 }
158             } else {
159                 // Annotation was "SNAPSHOT" so populate the build specifier with that data
160                 buildSeparator = m.group(ANNOTATION_SEPARATOR_INDEX);
161                 buildSpecifier = nullIfEmpty(m.group(ANNOTATION_INDEX));
162             }
163         } else {
164             throw new VersionParseException("Unable to parse the version string: \"" + version + "\"");
165         }
166     }
167 
168     /**
169      * <p>isSnapshot.</p>
170      *
171      * @return a boolean
172      */
173     public boolean isSnapshot() {
174         return ArtifactUtils.isSnapshot(strVersion);
175     }
176 
177     /**
178      * <p>toString.</p>
179      *
180      * @return a {@link java.lang.String} object
181      */
182     public String toString() {
183         return strVersion;
184     }
185 
186     /**
187      * <p>getVersionString.</p>
188      *
189      * @param info a {@link org.apache.maven.shared.release.versions.Version} object
190      * @param buildSpecifier a {@link java.lang.String} object
191      * @param buildSeparator a {@link java.lang.String} object
192      * @return a {@link java.lang.String} object
193      */
194     protected static String getVersionString(Version info, String buildSpecifier, String buildSeparator) {
195         StringBuilder sb = new StringBuilder();
196 
197         if (info.digits != null) {
198             sb.append(joinDigitString(info.digits));
199         }
200 
201         if (info.annotation != null && !info.annotation.isEmpty()) {
202             sb.append(StringUtils.defaultString(info.annotationSeparator));
203             sb.append(info.annotation);
204         }
205 
206         if (info.annotationRevision != null && !info.annotationRevision.isEmpty()) {
207             if (info.annotation == null || info.annotation.isEmpty()) {
208                 sb.append(StringUtils.defaultString(info.annotationSeparator));
209             } else {
210                 sb.append(StringUtils.defaultString(info.annotationRevSeparator));
211             }
212             sb.append(info.annotationRevision);
213         }
214 
215         if (buildSpecifier != null && !buildSpecifier.isEmpty()) {
216             sb.append(StringUtils.defaultString(buildSeparator));
217             sb.append(buildSpecifier);
218         }
219 
220         return sb.toString();
221     }
222 
223     /**
224      * Simply joins the items in the list with "." period
225      *
226      * @return a {@code String} containing the items in the list joined by "." period
227      * @param digits the list of digits {@code List<String>}
228      */
229     protected static String joinDigitString(List<String> digits) {
230         return digits != null ? StringUtils.join(digits.iterator(), DIGIT_SEPARATOR_STRING) : null;
231     }
232 
233     /**
234      * Splits the string on "." and returns a list containing each digit.
235      *
236      * @param strDigits
237      */
238     private List<String> parseDigits(String strDigits) {
239         return Arrays.asList(StringUtils.split(strDigits, DIGIT_SEPARATOR_STRING));
240     }
241 
242     private static String nullIfEmpty(String s) {
243         return (s == null || s.isEmpty()) ? null : s;
244     }
245 
246     /**
247      * <p>Getter for the field <code>digits</code>.</p>
248      *
249      * @return a {@link java.util.List} object
250      */
251     public List<String> getDigits() {
252         return digits;
253     }
254 
255     /**
256      * <p>Getter for the field <code>annotation</code>.</p>
257      *
258      * @return a {@link java.lang.String} object
259      */
260     public String getAnnotation() {
261         return annotation;
262     }
263 
264     /**
265      * <p>Getter for the field <code>annotationRevSeparator</code>.</p>
266      *
267      * @return a {@link java.lang.String} object
268      */
269     public String getAnnotationRevSeparator() {
270         return annotationRevSeparator;
271     }
272 
273     /**
274      * <p>Getter for the field <code>annotationRevision</code>.</p>
275      *
276      * @return a {@link java.lang.String} object
277      */
278     public String getAnnotationRevision() {
279         return annotationRevision;
280     }
281 
282     /**
283      * <p>Getter for the field <code>buildSeparator</code>.</p>
284      *
285      * @return a {@link java.lang.String} object
286      */
287     public String getBuildSeparator() {
288         return buildSeparator;
289     }
290 
291     /**
292      * <p>Getter for the field <code>buildSpecifier</code>.</p>
293      *
294      * @return a {@link java.lang.String} object
295      */
296     public String getBuildSpecifier() {
297         return buildSpecifier;
298     }
299 
300     /**
301      * <p>Setter for the field <code>digits</code>.</p>
302      *
303      * @param newDigits the new list of digits
304      * @return a new instance of Version
305      */
306     public Version setDigits(List<String> newDigits) {
307         return new Version(
308                 newDigits,
309                 this.annotation,
310                 this.annotationRevision,
311                 this.buildSpecifier,
312                 this.annotationSeparator,
313                 this.annotationRevSeparator,
314                 this.buildSeparator);
315     }
316 
317     /**
318      * <p>Setter for the field <code>annotationRevision</code>.</p>
319      *
320      * @param newAnnotationRevision the new annotation revision
321      * @return a new instance of Version
322      */
323     public Version setAnnotationRevision(String newAnnotationRevision) {
324         return new Version(
325                 this.digits,
326                 this.annotation,
327                 newAnnotationRevision,
328                 this.buildSpecifier,
329                 this.annotationSeparator,
330                 Objects.toString(this.annotationRevSeparator, DEFAULT_ANNOTATION_REV_SEPARATOR),
331                 this.buildSeparator);
332     }
333 
334     /**
335      * <p>Setter for the field <code>buildSpecifier</code>.</p>
336      *
337      * @param newBuildSpecifier the new build specifier
338      * @return a new instance of Version
339      */
340     public Version setBuildSpecifier(String newBuildSpecifier) {
341         return new Version(
342                 this.digits,
343                 this.annotation,
344                 this.annotationRevision,
345                 newBuildSpecifier,
346                 this.annotationSeparator,
347                 this.annotationRevSeparator,
348                 Objects.toString(this.buildSeparator, DEFAULT_BUILD_SEPARATOR));
349     }
350 
351     /**
352      * <p>compareTo.</p>
353      *
354      * @throws org.apache.maven.shared.release.versions.VersionComparisonConflictException
355      *              if {@link org.eclipse.aether.version.Version} and
356      *             {@link org.apache.maven.artifact.versioning.ArtifactVersion ArtifactVersion} give different results
357      * @param other a {@link org.apache.maven.shared.release.versions.Version} object
358      * @return a int
359      */
360     public int compareTo(Version other) throws VersionComparisonConflictException {
361         int aetherComparisonResult = this.aetherVersion.compareTo(other.aetherVersion);
362         int mavenComparisonResult = this.mavenArtifactVersion.compareTo(other.mavenArtifactVersion);
363 
364         if (aetherComparisonResult < 0 && mavenComparisonResult < 0) {
365             return -1;
366         } else if (aetherComparisonResult == 0 && mavenComparisonResult == 0) {
367             return 0;
368         } else if (aetherComparisonResult > 0 && mavenComparisonResult > 0) {
369             return 1;
370         } else {
371             throw new VersionComparisonConflictException(
372                     this.strVersion, other.strVersion, aetherComparisonResult, mavenComparisonResult);
373         }
374     }
375 }