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