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