This project has retired. For details please refer to its Attic page.
VersionComparator xref
View Javadoc
1   package org.apache.archiva.common.utils;
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 org.apache.commons.lang3.ArrayUtils;
23  import org.apache.commons.lang3.StringUtils;
24  import org.apache.commons.lang3.math.NumberUtils;
25  
26  import java.util.ArrayList;
27  import java.util.Comparator;
28  import java.util.List;
29  
30  /**
31   * VersionComparator - compare the parts of two version strings.
32   * <p>
33   * Technique.
34   * </p>
35   * <p>
36   * * Split the version strings into parts by splitting on <code>"-._"</code> first, then breaking apart words from numbers.
37   * </p>
38   * <code>
39   * "1.0"         = "1", "0"
40   * "1.0-alpha-1" = "1", "0", "alpha", "1"
41   * "2.0-rc2"     = "2", "0", "rc", "2"
42   * "1.3-m2"      = "1", "3", "m", "3"
43   * </code>
44   * <p>
45   * compare each part individually, and when they do not match, perform the following test.
46   * </p>
47   * <p>
48   * Numbers are calculated per normal comparison rules.
49   * Words that are part of the "special word list" will be treated as their index within that heirarchy.
50   * Words that cannot be identified as special, are treated using normal case-insensitive comparison rules.
51   * </p>
52   *
53   */
54  public class VersionComparator
55      implements Comparator<String>
56  {
57      private static final Comparator<String> INSTANCE = new VersionComparator();
58  
59      private final List<String> specialWords;
60  
61      public VersionComparator()
62      {
63          specialWords = new ArrayList<>( 23 );
64  
65          // ids that refer to LATEST
66          specialWords.add( "final" );
67          specialWords.add( "release" );
68          specialWords.add( "current" );
69          specialWords.add( "latest" );
70          specialWords.add( "g" );
71          specialWords.add( "gold" );
72          specialWords.add( "fcs" );
73  
74          // ids that are for a release cycle.
75          specialWords.add( "a" );
76          specialWords.add( "alpha" );
77          specialWords.add( "b" );
78          specialWords.add( "beta" );
79          specialWords.add( "pre" );
80          specialWords.add( "rc" );
81          specialWords.add( "m" );
82          specialWords.add( "milestone" );
83  
84          // ids that are for dev / debug cycles.
85          specialWords.add( "dev" );
86          specialWords.add( "test" );
87          specialWords.add( "debug" );
88          specialWords.add( "unofficial" );
89          specialWords.add( "nightly" );
90          specialWords.add( "incubating" );
91          specialWords.add( "incubator" );
92          specialWords.add( "snapshot" );
93      }
94  
95      public static Comparator<String> getInstance()
96      {
97          return INSTANCE;
98      }
99  
100     @Override
101     public int compare( String o1, String o2 )
102     {
103         if ( o1 == null && o2 == null )
104         {
105             return 0;
106         }
107 
108         if ( o1 == null )
109         {
110             return 1;
111         }
112 
113         if ( o2 == null )
114         {
115             return -1;
116         }
117 
118         String[] parts1 = toParts( o1 );
119         String[] parts2 = toParts( o2 );
120 
121         int diff;
122         int partLen = Math.max( parts1.length, parts2.length );
123         for ( int i = 0; i < partLen; i++ )
124         {
125             diff = comparePart( safePart( parts1, i ), safePart( parts2, i ) );
126             if ( diff != 0 )
127             {
128                 return diff;
129             }
130         }
131 
132         diff = parts2.length - parts1.length;
133 
134         if ( diff != 0 )
135         {
136             return diff;
137         }
138 
139         return o1.compareToIgnoreCase( o2 );
140     }
141 
142     private String safePart( String[] parts, int idx )
143     {
144         if ( idx < parts.length )
145         {
146             return parts[idx];
147         }
148 
149         return "0";
150     }
151 
152     private int comparePart( String s1, String s2 )
153     {
154         boolean is1Num = NumberUtils.isNumber( s1 );
155         boolean is2Num = NumberUtils.isNumber( s2 );
156 
157         // (Special Case) Test for numbers both first.
158         if ( is1Num && is2Num )
159         {
160             int i1 = NumberUtils.toInt( s1 );
161             int i2 = NumberUtils.toInt( s2 );
162 
163             return i1 - i2;
164         }
165 
166         // Test for text both next.
167         if ( !is1Num && !is2Num )
168         {
169             int idx1 = specialWords.indexOf( s1.toLowerCase() );
170             int idx2 = specialWords.indexOf( s2.toLowerCase() );
171 
172             // Only operate perform index based operation, if both strings
173             // are found in the specialWords index.
174             if ( idx1 >= 0 && idx2 >= 0 )
175             {
176                 return idx1 - idx2;
177             }
178         }
179 
180         // Comparing text to num
181         if ( !is1Num && is2Num )
182         {
183             return -1;
184         }
185 
186         // Comparing num to text
187         if ( is1Num && !is2Num )
188         {
189             return 1;
190         }
191 
192         // Return comparison of strings themselves.
193         return s1.compareToIgnoreCase( s2 );
194     }
195 
196     public static String[] toParts( String version )
197     {
198         if ( StringUtils.isBlank( version ) )
199         {
200             return ArrayUtils.EMPTY_STRING_ARRAY;
201         }
202 
203         int modeOther = 0;
204         int modeDigit = 1;
205         int modeText = 2;
206 
207         List<String> parts = new ArrayList<>();
208         int len = version.length();
209         int i = 0;
210         int start = 0;
211         int mode = modeOther;
212 
213         while ( i < len )
214         {
215             char c = version.charAt( i );
216 
217             if ( Character.isDigit( c ) )
218             {
219                 if ( mode != modeDigit )
220                 {
221                     if ( mode != modeOther )
222                     {
223                         parts.add( version.substring( start, i ) );
224                     }
225                     mode = modeDigit;
226                     start = i;
227                 }
228             }
229             else if ( Character.isLetter( c ) )
230             {
231                 if ( mode != modeText )
232                 {
233                     if ( mode != modeOther )
234                     {
235                         parts.add( version.substring( start, i ) );
236                     }
237                     mode = modeText;
238                     start = i;
239                 }
240             }
241             else
242             {
243                 // Other.
244                 if ( mode != modeOther )
245                 {
246                     parts.add( version.substring( start, i ) );
247                     mode = modeOther;
248                 }
249             }
250 
251             i++;
252         }
253 
254         // Add remainder
255         if ( mode != modeOther )
256         {
257             parts.add( version.substring( start, i ) );
258         }
259 
260         return parts.toArray( new String[parts.size()] );
261     }
262 }