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   * 
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      public static final Pattern STANDARD_PATTERN = Pattern.compile( "^((?:\\d+\\.)*\\d+)" // digit(s) and '.' repeated -
80                                                                                            // followed by digit (version
81                                                                                            // digits 1.22.0, etc)
82          + "([-_])?" // optional - or _ (annotation separator)
83          + "([a-zA-Z]*)" // alpha characters (looking for annotation - alpha, beta, RC, etc.)
84          + "([-_])?" // optional - or _ (annotation revision separator)
85          + "(\\d*)" // digits (any digits after rc or beta is an annotation revision)
86          + "(?:([-_])?(.*?))?$" ); // - or _ followed everything else (build specifier)
87  
88      /* *
89       * cmaki 02242009 FIX for non-digit release numbers, e.g. trunk-SNAPSHOT or just SNAPSHOT This alternate pattern
90       * supports version numbers like: trunk-SNAPSHOT branchName-SNAPSHOT SNAPSHOT
91       */
92      // for SNAPSHOT releases only (possible versions include: trunk-SNAPSHOT or SNAPSHOT)
93      public static final Pattern ALTERNATE_PATTERN = Pattern.compile( "^(SNAPSHOT|[a-zA-Z]+[_-]SNAPSHOT)" );
94      
95      private Version( List<String> digits, String annotation, String annotationRevision, String buildSpecifier,
96                                 String annotationSeparator, String annotationRevSeparator, String buildSeparator )
97      {
98          this.digits = digits;
99          this.annotation = annotation;
100         this.annotationRevision = annotationRevision;
101         this.buildSpecifier = buildSpecifier;
102         this.annotationSeparator = annotationSeparator;
103         this.annotationRevSeparator = annotationRevSeparator;
104         this.buildSeparator = buildSeparator;
105         this.strVersion = getVersionString( this, buildSpecifier, buildSeparator );
106 
107         // for now no need to reparse, original version was valid 
108         this.aetherVersion = null;
109         this.mavenArtifactVersion = null;
110     }
111 
112     public Version( String version )
113         throws VersionParseException
114     {
115         this.strVersion = version;
116         this.aetherVersion = new AetherVersion( version );
117         this.mavenArtifactVersion = new MavenArtifactVersion( version );
118 
119         // FIX for non-digit release numbers, e.g. trunk-SNAPSHOT or just SNAPSHOT
120         Matcher matcher = ALTERNATE_PATTERN.matcher( strVersion );
121         // TODO: hack because it didn't support "SNAPSHOT"
122         if ( matcher.matches() )
123         {
124             annotation = null;
125             digits = null;
126             buildSpecifier = version;
127             buildSeparator = null;
128             return;
129         }
130 
131         Matcher m = STANDARD_PATTERN.matcher( strVersion );
132         if ( m.matches() )
133         {
134             digits = parseDigits( m.group( DIGITS_INDEX ) );
135             if ( !SNAPSHOT_IDENTIFIER.equals( m.group( ANNOTATION_INDEX ) ) )
136             {
137                 annotationSeparator = m.group( ANNOTATION_SEPARATOR_INDEX );
138                 annotation = nullIfEmpty( m.group( ANNOTATION_INDEX ) );
139 
140                 if ( StringUtils.isNotEmpty( m.group( ANNOTATION_REV_SEPARATOR_INDEX ) )
141                     && StringUtils.isEmpty( m.group( ANNOTATION_REVISION_INDEX ) ) )
142                 {
143                     // The build separator was picked up as the annotation revision separator
144                     buildSeparator = m.group( ANNOTATION_REV_SEPARATOR_INDEX );
145                     buildSpecifier = nullIfEmpty( m.group( BUILD_SPECIFIER_INDEX ) );
146                 }
147                 else
148                 {
149                     annotationRevSeparator = m.group( ANNOTATION_REV_SEPARATOR_INDEX );
150                     annotationRevision = nullIfEmpty( m.group( ANNOTATION_REVISION_INDEX ) );
151 
152                     buildSeparator = m.group( BUILD_SEPARATOR_INDEX );
153                     buildSpecifier = nullIfEmpty( m.group( BUILD_SPECIFIER_INDEX ) );
154                 }
155             }
156             else
157             {
158                 // Annotation was "SNAPSHOT" so populate the build specifier with that data
159                 buildSeparator = m.group( ANNOTATION_SEPARATOR_INDEX );
160                 buildSpecifier = nullIfEmpty( m.group( ANNOTATION_INDEX ) );
161             }
162         }
163         else
164         {
165             throw new VersionParseException( "Unable to parse the version string: \"" + version + "\"" );
166         }
167     }
168 
169     public boolean isSnapshot()
170     {
171         return ArtifactUtils.isSnapshot( strVersion );
172     }
173 
174     public String toString()
175     {
176         return strVersion;
177     }
178 
179     protected static String getVersionString( Version info, String buildSpecifier, String buildSeparator )
180     {
181         StringBuilder sb = new StringBuilder();
182 
183         if ( info.digits != null )
184         {
185             sb.append( joinDigitString( info.digits ) );
186         }
187 
188         if ( StringUtils.isNotEmpty( info.annotation ) )
189         {
190             sb.append( StringUtils.defaultString( info.annotationSeparator ) );
191             sb.append( info.annotation );
192         }
193 
194         if ( StringUtils.isNotEmpty( info.annotationRevision ) )
195         {
196             if ( StringUtils.isEmpty( info.annotation ) )
197             {
198                 sb.append( StringUtils.defaultString( info.annotationSeparator ) );
199             }
200             else
201             {
202                 sb.append( StringUtils.defaultString( info.annotationRevSeparator ) );
203             }
204             sb.append( info.annotationRevision );
205         }
206 
207         if ( StringUtils.isNotEmpty( buildSpecifier ) )
208         {
209             sb.append( StringUtils.defaultString( buildSeparator ) );
210             sb.append( buildSpecifier );
211         }
212 
213         return sb.toString();
214     }
215 
216     /**
217      * Simply joins the items in the list with "." period
218      * 
219      * @return a {@code String} containing the items in the list joined by "." period
220      * @param digits the list of digits {@code List<String>}
221      */
222     protected static String joinDigitString( List<String> digits )
223     {
224         return digits != null ? StringUtils.join( digits.iterator(), DIGIT_SEPARATOR_STRING ) : null;
225     }
226 
227     /**
228      * Splits the string on "." and returns a list containing each digit.
229      * 
230      * @param strDigits
231      */
232     private List<String> parseDigits( String strDigits )
233     {
234         return Arrays.asList( StringUtils.split( strDigits, DIGIT_SEPARATOR_STRING ) );
235     }
236 
237     private static String nullIfEmpty( String s )
238     {
239         return StringUtils.isEmpty( s ) ? null : s;
240     }
241 
242     public List<String> getDigits()
243     {
244         return digits;
245     }
246     
247     public String getAnnotation()
248     {
249         return annotation;
250     }
251 
252     public String getAnnotationRevSeparator()
253     {
254         return annotationRevSeparator;
255     }
256 
257     public String getAnnotationRevision()
258     {
259         return annotationRevision;
260     }
261 
262     public String getBuildSeparator()
263     {
264         return buildSeparator;
265     }
266 
267     public String getBuildSpecifier()
268     {
269         return buildSpecifier;
270     }
271     
272     /**
273      * 
274      * @param newDigits the new list of digits
275      * @return a new instance of Version
276      */
277     public Version setDigits( List<String> newDigits )
278     {
279         return new Version( newDigits, this.annotation, this.annotationRevision, this.buildSpecifier,
280                             this.annotationSeparator, this.annotationRevSeparator, this.buildSeparator );
281     }
282     
283     /**
284      * 
285      * @param newAnnotationRevision the new annotation revision
286      * @return a new instance of Version
287      */
288     public Version setAnnotationRevision( String newAnnotationRevision )
289     {
290         return new Version( this.digits, this.annotation, newAnnotationRevision, this.buildSpecifier,
291                             this.annotationSeparator,
292                             Objects.toString( this.annotationRevSeparator, DEFAULT_ANNOTATION_REV_SEPARATOR ),
293                             this.buildSeparator );
294     }
295     
296     /**
297      * 
298      * @param newBuildSpecifier the new build specifier
299      * @return a new instance of Version
300      */
301     public Version setBuildSpecifier( String newBuildSpecifier )
302     {
303         return new Version( this.digits, this.annotation, this.annotationRevision, newBuildSpecifier,
304                             this.annotationSeparator, this.annotationRevSeparator,
305                             Objects.toString( this.buildSeparator, DEFAULT_BUILD_SEPARATOR ) );
306     }
307     
308     /**
309      * @throws VersionComparisonConflictException if {@link org.eclipse.aether.version.Version} and
310      *             {@link org.apache.maven.artifact.versioning.ArtifactVersion ArtifactVersion} give different results
311      */
312     public int compareTo( Version other )
313         throws VersionComparisonConflictException
314     {
315         int aetherComparisonResult = this.aetherVersion.compareTo( other.aetherVersion );
316         int mavenComparisonResult = this.mavenArtifactVersion.compareTo( other.mavenArtifactVersion );
317 
318         if ( aetherComparisonResult < 0 && mavenComparisonResult < 0 )
319         {
320             return -1;
321         }
322         else if ( aetherComparisonResult == 0 && mavenComparisonResult == 0 )
323         {
324             return 0;
325         }
326         else if ( aetherComparisonResult > 0 && mavenComparisonResult > 0 )
327         {
328             return 1;
329         }
330         else
331         {
332             throw new VersionComparisonConflictException( this.strVersion, other.strVersion, aetherComparisonResult,
333                                                           mavenComparisonResult );
334         }
335     }
336 
337 }