001 package org.apache.archiva.common.utils; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 import org.apache.commons.lang.ArrayUtils; 023 import org.apache.commons.lang.StringUtils; 024 import org.apache.commons.lang.math.NumberUtils; 025 026 import java.util.ArrayList; 027 import java.util.Comparator; 028 import java.util.List; 029 030 /** 031 * VersionComparator - compare the parts of two version strings. 032 * <p/> 033 * Technique. 034 * <p/> 035 * * Split the version strings into parts by splitting on <code>"-._"</code> first, then breaking apart words from numbers. 036 * <p/> 037 * <code> 038 * "1.0" = "1", "0" 039 * "1.0-alpha-1" = "1", "0", "alpha", "1" 040 * "2.0-rc2" = "2", "0", "rc", "2" 041 * "1.3-m2" = "1", "3", "m", "3" 042 * </code> 043 * <p/> 044 * compare each part individually, and when they do not match, perform the following test. 045 * <p/> 046 * Numbers are calculated per normal comparison rules. 047 * Words that are part of the "special word list" will be treated as their index within that heirarchy. 048 * Words that cannot be identified as special, are treated using normal case-insensitive comparison rules. 049 * 050 * 051 */ 052 public class VersionComparator 053 implements Comparator<String> 054 { 055 private static Comparator<String> INSTANCE = new VersionComparator(); 056 057 private List<String> specialWords; 058 059 public VersionComparator() 060 { 061 specialWords = new ArrayList<String>( 23 ); 062 063 // ids that refer to LATEST 064 specialWords.add( "final" ); 065 specialWords.add( "release" ); 066 specialWords.add( "current" ); 067 specialWords.add( "latest" ); 068 specialWords.add( "g" ); 069 specialWords.add( "gold" ); 070 specialWords.add( "fcs" ); 071 072 // ids that are for a release cycle. 073 specialWords.add( "a" ); 074 specialWords.add( "alpha" ); 075 specialWords.add( "b" ); 076 specialWords.add( "beta" ); 077 specialWords.add( "pre" ); 078 specialWords.add( "rc" ); 079 specialWords.add( "m" ); 080 specialWords.add( "milestone" ); 081 082 // ids that are for dev / debug cycles. 083 specialWords.add( "dev" ); 084 specialWords.add( "test" ); 085 specialWords.add( "debug" ); 086 specialWords.add( "unofficial" ); 087 specialWords.add( "nightly" ); 088 specialWords.add( "incubating" ); 089 specialWords.add( "incubator" ); 090 specialWords.add( "snapshot" ); 091 } 092 093 public static Comparator<String> getInstance() 094 { 095 return INSTANCE; 096 } 097 098 public int compare( String o1, String o2 ) 099 { 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 }