001// Code source of this file: 002// http://grepcode.com/file/repo1.maven.org/maven2/ 003// org.apache.maven/maven-artifact/3.1.1/ 004// org/apache/maven/artifact/versioning/ComparableVersion.java/ 005// 006// Modifications made on top of the source: 007// 1. Changed 008// package org.apache.maven.artifact.versioning; 009// to 010// package org.apache.hadoop.util; 011// 2. Removed author tags to clear hadoop author tag warning 012// 013package org.apache.hadoop.util; 014 015/* 016 * Licensed to the Apache Software Foundation (ASF) under one 017 * or more contributor license agreements. See the NOTICE file 018 * distributed with this work for additional information 019 * regarding copyright ownership. The ASF licenses this file 020 * to you under the Apache License, Version 2.0 (the 021 * "License"); you may not use this file except in compliance 022 * with the License. You may obtain a copy of the License at 023 * 024 * http://www.apache.org/licenses/LICENSE-2.0 025 * 026 * Unless required by applicable law or agreed to in writing, 027 * software distributed under the License is distributed on an 028 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 029 * KIND, either express or implied. See the License for the 030 * specific language governing permissions and limitations 031 * under the License. 032 */ 033 034import java.math.BigInteger; 035import java.util.ArrayList; 036import java.util.Arrays; 037import java.util.Iterator; 038import java.util.List; 039import java.util.ListIterator; 040import java.util.Properties; 041import java.util.Stack; 042 043/** 044 * Generic implementation of version comparison. 045 * 046 * <p>Features: 047 * <ul> 048 * <li>mixing of '<code>-</code>' (dash) and '<code>.</code>' (dot) separators,</li> 049 * <li>transition between characters and digits also constitutes a separator: 050 * <code>1.0alpha1 => [1, 0, alpha, 1]</code></li> 051 * <li>unlimited number of version components,</li> 052 * <li>version components in the text can be digits or strings,</li> 053 * <li>strings are checked for well-known qualifiers and the qualifier ordering is used for version ordering. 054 * Well-known qualifiers (case insensitive) are:<ul> 055 * <li><code>alpha</code> or <code>a</code></li> 056 * <li><code>beta</code> or <code>b</code></li> 057 * <li><code>milestone</code> or <code>m</code></li> 058 * <li><code>rc</code> or <code>cr</code></li> 059 * <li><code>snapshot</code></li> 060 * <li><code>(the empty string)</code> or <code>ga</code> or <code>final</code></li> 061 * <li><code>sp</code></li> 062 * </ul> 063 * Unknown qualifiers are considered after known qualifiers, with lexical order (always case insensitive), 064 * </li> 065 * <li>a dash usually precedes a qualifier, and is always less important than something preceded with a dot.</li> 066 * </ul></p> 067 * 068 * @see <a href="https://cwiki.apache.org/confluence/display/MAVENOLD/Versioning">"Versioning" on Maven Wiki</a> 069 */ 070public class ComparableVersion 071 implements Comparable<ComparableVersion> 072{ 073 private String value; 074 075 private String canonical; 076 077 private ListItem items; 078 079 private interface Item 080 { 081 int INTEGER_ITEM = 0; 082 int STRING_ITEM = 1; 083 int LIST_ITEM = 2; 084 085 int compareTo( Item item ); 086 087 int getType(); 088 089 boolean isNull(); 090 } 091 092 /** 093 * Represents a numeric item in the version item list. 094 */ 095 private static class IntegerItem 096 implements Item 097 { 098 private static final BigInteger BIG_INTEGER_ZERO = new BigInteger( "0" ); 099 100 private final BigInteger value; 101 102 public static final IntegerItem ZERO = new IntegerItem(); 103 104 private IntegerItem() 105 { 106 this.value = BIG_INTEGER_ZERO; 107 } 108 109 public IntegerItem( String str ) 110 { 111 this.value = new BigInteger( str ); 112 } 113 114 public int getType() 115 { 116 return INTEGER_ITEM; 117 } 118 119 public boolean isNull() 120 { 121 return BIG_INTEGER_ZERO.equals( value ); 122 } 123 124 public int compareTo( Item item ) 125 { 126 if ( item == null ) 127 { 128 return BIG_INTEGER_ZERO.equals( value ) ? 0 : 1; // 1.0 == 1, 1.1 > 1 129 } 130 131 switch ( item.getType() ) 132 { 133 case INTEGER_ITEM: 134 return value.compareTo( ( (IntegerItem) item ).value ); 135 136 case STRING_ITEM: 137 return 1; // 1.1 > 1-sp 138 139 case LIST_ITEM: 140 return 1; // 1.1 > 1-1 141 142 default: 143 throw new RuntimeException( "invalid item: " + item.getClass() ); 144 } 145 } 146 147 public String toString() 148 { 149 return value.toString(); 150 } 151 } 152 153 /** 154 * Represents a string in the version item list, usually a qualifier. 155 */ 156 private static class StringItem 157 implements Item 158 { 159 private static final String[] QUALIFIERS = { "alpha", "beta", "milestone", "rc", "snapshot", "", "sp" }; 160 161 private static final List<String> _QUALIFIERS = Arrays.asList( QUALIFIERS ); 162 163 private static final Properties ALIASES = new Properties(); 164 static 165 { 166 ALIASES.put( "ga", "" ); 167 ALIASES.put( "final", "" ); 168 ALIASES.put( "cr", "rc" ); 169 } 170 171 /** 172 * A comparable value for the empty-string qualifier. This one is used to determine if a given qualifier makes 173 * the version older than one without a qualifier, or more recent. 174 */ 175 private static final String RELEASE_VERSION_INDEX = String.valueOf( _QUALIFIERS.indexOf( "" ) ); 176 177 private String value; 178 179 public StringItem( String value, boolean followedByDigit ) 180 { 181 if ( followedByDigit && value.length() == 1 ) 182 { 183 // a1 = alpha-1, b1 = beta-1, m1 = milestone-1 184 switch ( value.charAt( 0 ) ) 185 { 186 case 'a': 187 value = "alpha"; 188 break; 189 case 'b': 190 value = "beta"; 191 break; 192 case 'm': 193 value = "milestone"; 194 break; 195 default: 196 break; 197 } 198 } 199 this.value = ALIASES.getProperty( value , value ); 200 } 201 202 public int getType() 203 { 204 return STRING_ITEM; 205 } 206 207 public boolean isNull() 208 { 209 return ( comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX ) == 0 ); 210 } 211 212 /** 213 * Returns a comparable value for a qualifier. 214 * 215 * This method takes into account the ordering of known qualifiers then unknown qualifiers with lexical ordering. 216 * 217 * just returning an Integer with the index here is faster, but requires a lot of if/then/else to check for -1 218 * or QUALIFIERS.size and then resort to lexical ordering. Most comparisons are decided by the first character, 219 * so this is still fast. If more characters are needed then it requires a lexical sort anyway. 220 * 221 * @param qualifier 222 * @return an equivalent value that can be used with lexical comparison 223 */ 224 public static String comparableQualifier( String qualifier ) 225 { 226 int i = _QUALIFIERS.indexOf( qualifier ); 227 228 return i == -1 ? ( _QUALIFIERS.size() + "-" + qualifier ) : String.valueOf( i ); 229 } 230 231 public int compareTo( Item item ) 232 { 233 if ( item == null ) 234 { 235 // 1-rc < 1, 1-ga > 1 236 return comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX ); 237 } 238 switch ( item.getType() ) 239 { 240 case INTEGER_ITEM: 241 return -1; // 1.any < 1.1 ? 242 243 case STRING_ITEM: 244 return comparableQualifier( value ).compareTo( comparableQualifier( ( (StringItem) item ).value ) ); 245 246 case LIST_ITEM: 247 return -1; // 1.any < 1-1 248 249 default: 250 throw new RuntimeException( "invalid item: " + item.getClass() ); 251 } 252 } 253 254 public String toString() 255 { 256 return value; 257 } 258 } 259 260 /** 261 * Represents a version list item. This class is used both for the global item list and for sub-lists (which start 262 * with '-(number)' in the version specification). 263 */ 264 private static class ListItem 265 extends ArrayList<Item> 266 implements Item 267 { 268 public int getType() 269 { 270 return LIST_ITEM; 271 } 272 273 public boolean isNull() 274 { 275 return ( size() == 0 ); 276 } 277 278 void normalize() 279 { 280 for ( ListIterator<Item> iterator = listIterator( size() ); iterator.hasPrevious(); ) 281 { 282 Item item = iterator.previous(); 283 if ( item.isNull() ) 284 { 285 iterator.remove(); // remove null trailing items: 0, "", empty list 286 } 287 else 288 { 289 break; 290 } 291 } 292 } 293 294 public int compareTo( Item item ) 295 { 296 if ( item == null ) 297 { 298 if ( size() == 0 ) 299 { 300 return 0; // 1-0 = 1- (normalize) = 1 301 } 302 Item first = get( 0 ); 303 return first.compareTo( null ); 304 } 305 switch ( item.getType() ) 306 { 307 case INTEGER_ITEM: 308 return -1; // 1-1 < 1.0.x 309 310 case STRING_ITEM: 311 return 1; // 1-1 > 1-sp 312 313 case LIST_ITEM: 314 Iterator<Item> left = iterator(); 315 Iterator<Item> right = ( (ListItem) item ).iterator(); 316 317 while ( left.hasNext() || right.hasNext() ) 318 { 319 Item l = left.hasNext() ? left.next() : null; 320 Item r = right.hasNext() ? right.next() : null; 321 322 // if this is shorter, then invert the compare and mul with -1 323 int result = l == null ? -1 * r.compareTo( l ) : l.compareTo( r ); 324 325 if ( result != 0 ) 326 { 327 return result; 328 } 329 } 330 331 return 0; 332 333 default: 334 throw new RuntimeException( "invalid item: " + item.getClass() ); 335 } 336 } 337 338 public String toString() 339 { 340 StringBuilder buffer = new StringBuilder( "(" ); 341 for ( Iterator<Item> iter = iterator(); iter.hasNext(); ) 342 { 343 buffer.append( iter.next() ); 344 if ( iter.hasNext() ) 345 { 346 buffer.append( ',' ); 347 } 348 } 349 buffer.append( ')' ); 350 return buffer.toString(); 351 } 352 } 353 354 public ComparableVersion( String version ) 355 { 356 parseVersion( version ); 357 } 358 359 public final void parseVersion( String version ) 360 { 361 this.value = version; 362 363 items = new ListItem(); 364 365 version = StringUtils.toLowerCase(version); 366 367 ListItem list = items; 368 369 Stack<Item> stack = new Stack<Item>(); 370 stack.push( list ); 371 372 boolean isDigit = false; 373 374 int startIndex = 0; 375 376 for ( int i = 0; i < version.length(); i++ ) 377 { 378 char c = version.charAt( i ); 379 380 if ( c == '.' ) 381 { 382 if ( i == startIndex ) 383 { 384 list.add( IntegerItem.ZERO ); 385 } 386 else 387 { 388 list.add( parseItem( isDigit, version.substring( startIndex, i ) ) ); 389 } 390 startIndex = i + 1; 391 } 392 else if ( c == '-' ) 393 { 394 if ( i == startIndex ) 395 { 396 list.add( IntegerItem.ZERO ); 397 } 398 else 399 { 400 list.add( parseItem( isDigit, version.substring( startIndex, i ) ) ); 401 } 402 startIndex = i + 1; 403 404 if ( isDigit ) 405 { 406 list.normalize(); // 1.0-* = 1-* 407 408 if ( ( i + 1 < version.length() ) && Character.isDigit( version.charAt( i + 1 ) ) ) 409 { 410 // new ListItem only if previous were digits and new char is a digit, 411 // ie need to differentiate only 1.1 from 1-1 412 list.add( list = new ListItem() ); 413 414 stack.push( list ); 415 } 416 } 417 } 418 else if ( Character.isDigit( c ) ) 419 { 420 if ( !isDigit && i > startIndex ) 421 { 422 list.add( new StringItem( version.substring( startIndex, i ), true ) ); 423 startIndex = i; 424 } 425 426 isDigit = true; 427 } 428 else 429 { 430 if ( isDigit && i > startIndex ) 431 { 432 list.add( parseItem( true, version.substring( startIndex, i ) ) ); 433 startIndex = i; 434 } 435 436 isDigit = false; 437 } 438 } 439 440 if ( version.length() > startIndex ) 441 { 442 list.add( parseItem( isDigit, version.substring( startIndex ) ) ); 443 } 444 445 while ( !stack.isEmpty() ) 446 { 447 list = (ListItem) stack.pop(); 448 list.normalize(); 449 } 450 451 canonical = items.toString(); 452 } 453 454 private static Item parseItem( boolean isDigit, String buf ) 455 { 456 return isDigit ? new IntegerItem( buf ) : new StringItem( buf, false ); 457 } 458 459 public int compareTo( ComparableVersion o ) 460 { 461 return items.compareTo( o.items ); 462 } 463 464 public String toString() 465 { 466 return value; 467 } 468 469 public boolean equals( Object o ) 470 { 471 return ( o instanceof ComparableVersion ) && canonical.equals( ( (ComparableVersion) o ).canonical ); 472 } 473 474 public int hashCode() 475 { 476 return canonical.hashCode(); 477 } 478}