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      * @param digits
220      */
221     protected static String joinDigitString( List<String> digits )
222     {
223         return digits != null ? StringUtils.join( digits.iterator(), DIGIT_SEPARATOR_STRING ) : null;
224     }
225 
226     /**
227      * Splits the string on "." and returns a list containing each digit.
228      * 
229      * @param strDigits
230      */
231     private List<String> parseDigits( String strDigits )
232     {
233         return Arrays.asList( StringUtils.split( strDigits, DIGIT_SEPARATOR_STRING ) );
234     }
235 
236     private static String nullIfEmpty( String s )
237     {
238         return StringUtils.isEmpty( s ) ? null : s;
239     }
240 
241     public List<String> getDigits()
242     {
243         return digits;
244     }
245     
246     public String getAnnotation()
247     {
248         return annotation;
249     }
250 
251     public String getAnnotationRevSeparator()
252     {
253         return annotationRevSeparator;
254     }
255 
256     public String getAnnotationRevision()
257     {
258         return annotationRevision;
259     }
260 
261     public String getBuildSeparator()
262     {
263         return buildSeparator;
264     }
265 
266     public String getBuildSpecifier()
267     {
268         return buildSpecifier;
269     }
270     
271     /**
272      * 
273      * @param newDigits the new list of digits
274      * @return a new instance of Version
275      */
276     public Version setDigits( List<String> newDigits )
277     {
278         return new Version( newDigits, this.annotation, this.annotationRevision, this.buildSpecifier,
279                             this.annotationSeparator, this.annotationRevSeparator, this.buildSeparator );
280     }
281     
282     /**
283      * 
284      * @param newAnnotationRevision the new annotation revision
285      * @return a new instance of Version
286      */
287     public Version setAnnotationRevision( String newAnnotationRevision )
288     {
289         return new Version( this.digits, this.annotation, newAnnotationRevision, this.buildSpecifier,
290                             this.annotationSeparator,
291                             Objects.toString( this.annotationRevSeparator, DEFAULT_ANNOTATION_REV_SEPARATOR ),
292                             this.buildSeparator );
293     }
294     
295     /**
296      * 
297      * @param newBuildSpecifier the new build specifier
298      * @return a new instance of Version
299      */
300     public Version setBuildSpecifier( String newBuildSpecifier )
301     {
302         return new Version( this.digits, this.annotation, this.annotationRevision, newBuildSpecifier,
303                             this.annotationSeparator, this.annotationRevSeparator,
304                             Objects.toString( this.buildSeparator, DEFAULT_BUILD_SEPARATOR ) );
305     }
306     
307     /**
308      * @throws VersionComparisonConflictException if {@link org.eclipse.aether.version.Version} and
309      *             {@link org.apache.maven.artifact.versioning.ArtifactVersion ArtifactVersion} give different results
310      */
311     public int compareTo( Version other )
312         throws VersionComparisonConflictException
313     {
314         int aetherComparisonResult = this.aetherVersion.compareTo( other.aetherVersion );
315         int mavenComparisonResult = this.mavenArtifactVersion.compareTo( other.mavenArtifactVersion );
316 
317         if ( aetherComparisonResult < 0 && mavenComparisonResult < 0 )
318         {
319             return -1;
320         }
321         else if ( aetherComparisonResult == 0 && mavenComparisonResult == 0 )
322         {
323             return 0;
324         }
325         else if ( aetherComparisonResult > 0 && mavenComparisonResult > 0 )
326         {
327             return 1;
328         }
329         else
330         {
331             throw new VersionComparisonConflictException( this.strVersion, other.strVersion, aetherComparisonResult,
332                                                           mavenComparisonResult );
333         }
334     }
335 
336 }