1 /* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk; 27 28 import java.math.BigInteger; 29 import java.util.regex.Matcher; 30 import java.util.regex.Pattern; 31 import java.util.stream.Collectors; 32 import java.util.Arrays; 33 import java.util.Collections; 34 import java.util.List; 35 import java.util.Optional; 36 37 /** 38 * A representation of the JDK version-string which contains a version 39 * number optionally followed by pre-release and build information. 40 * 41 * <h2><a name="verNum">Version numbers</a></h2> 42 * 43 * A <em>version number</em>, {@code $VNUM}, is a non-empty sequence of 44 * non-negative integer numerals, without leading or trailing zeroes, 45 * separated by period characters (U+002E); i.e., it matches the regular 46 * expression {@code ^[1-9][0-9]*(((\.0)*\.[1-9][0-9]*)*)*$}. The sequence may 47 * be of arbitrary length but the first three elements are assigned specific 48 * meanings, as follows: 49 * 50 * <blockquote><pre> 51 * $MAJOR.$MINOR.$SECURITY 52 * </pre></blockquote> 53 * 54 * <ul> 55 * 56 * <li><p> <a name="major">{@code $MAJOR}</a> --- The major version number, 57 * incremented for a major release that contains significant new features as 58 * specified in a new edition of the Java SE Platform Specification, 59 * <em>e.g.</em>, <a href="https://jcp.org/en/jsr/detail?id=337">JSR 337</a> 60 * for Java SE 8. Features may be removed in a major release, given 61 * advance notice at least one major release ahead of time, and incompatible 62 * changes may be made when justified. The {@code $MAJOR} version number of 63 * JDK 8 was {@code 8}; the {@code $MAJOR} version number of JDK 9 64 * is {@code 9}. </p></li> 65 * 66 * <li><p> <a name="minor">{@code $MINOR}</a> --- The minor version number, 67 * incremented for a minor update release that may contain compatible bug 68 * fixes, revisions to standard APIs mandated by a <a 69 * href="https://jcp.org/en/procedures/jcp2#5.3">Maintenance Release</a> of 70 * the relevant Platform Specification, and implementation features outside 71 * the scope of that Specification such as new JDK-specific APIs, additional 72 * service providers, new garbage collectors, and ports to new hardware 73 * architectures. {@code $MINOR} is reset to zero when {@code $MAJOR} is 74 * incremented. </p></li> 75 * 76 * <li><p> <a name="security">{@code $SECURITY}</a> --- The security level, 77 * incremented for a security-update release that contains critical fixes 78 * including those necessary to improve security. {@code $SECURITY} is reset 79 * to zero <strong>only</strong> when {@code $MAJOR} is incremented. A higher 80 * value of {@code $SECURITY} for a given {@code $MAJOR} value, therefore, 81 * always indicates a more secure release, regardless of the value of {@code 82 * $MINOR}. </p></li> 83 * 84 * </ul> 85 * 86 * <p> The fourth and later elements of a version number are free for use by 87 * downstream consumers of the JDK code base. Such a consumer may, 88 * <em>e.g.</em>, use the fourth element to identify patch releases which 89 * contain a small number of critical non-security fixes in addition to the 90 * security fixes in the corresponding security release. </p> 91 * 92 * <p> The version number does not include trailing zero elements; 93 * <em>i.e.</em>, {@code $SECURITY} is omitted if it has the value zero, and 94 * {@code $MINOR} is omitted if both {@code $MINOR} and {@code $SECURITY} have 95 * the value zero. </p> 96 * 97 * <p> The sequence of numerals in a version number is compared to another 98 * such sequence in numerical, pointwise fashion; <em>e.g.</em>, {@code 9.9.1} 99 * is less than {@code 9.10.0}. If one sequence is shorter than another then 100 * the missing elements of the shorter sequence are considered to be zero; 101 * <em>e.g.</em>, {@code 9.1.2} is equal to {@code 9.1.2.0} but less than 102 * {@code 9.1.2.1}. </p> 103 * 104 * <h2><a name="verStr">Version strings</a></h2> 105 * 106 * <p> A <em>version string</em> {@code $VSTR} consists of a version number 107 * {@code $VNUM}, as described above, optionally followed by pre-release and 108 * build information, in the format </p> 109 * 110 * <blockquote><pre> 111 * $VNUM(-$PRE)?(\+($BUILD)?(-$OPT)?)? 112 * </pre></blockquote> 113 * 114 * <p> where: </p> 115 * 116 * <ul> 117 * 118 * <li><p> <a name="pre">{@code $PRE}</a>, matching {@code ([a-zA-Z0-9]+)} --- 119 * A pre-release identifier. Typically {@code ea}, for an early-access 120 * release that's under active development and potentially unstable, or {@code 121 * internal}, for an internal developer build. 122 * 123 * <p> When comparing two version strings, a string with a pre-release 124 * identifier is always less than one with an equal {@code $VNUM} but no such 125 * identifier. Pre-release identifiers are compared numerically when they 126 * consist only of digits, and lexicographically otherwise. Numeric 127 * identifiers are less than than non-numeric identifiers. </p></li> 128 * 129 * <li><p> <a name="build">{@code $BUILD}</a>, matching {@code 130 * (0|[1-9][0-9]*)} --- The build number, incremented for each promoted build. 131 * {@code $BUILD} is reset to {@code 1} when any portion of {@code $VNUM} is 132 * incremented. </p> 133 * 134 * <p> When comparing two version strings with equal {@code $VNUM} and 135 * {@code $PRE} components, a string without a {@code $BUILD} 136 * component is always less than one with a {@code $BUILD} component; 137 * otherwise, {@code $BUILD} numbers are compared numerically. </p></li> 138 * 139 * <li><p> <a name="opt">{@code $OPT}</a>, matching {@code ([-a-zA-Z0-9\.]+)} 140 * --- Additional build information, if desired. In the case of an {@code 141 * internal} build this will often contain the date and time of the 142 * build. </p> 143 * 144 * <p> When comparing two version strings the value of {@code $OPT}, if 145 * present, may or may not be significant depending on the chosen comparsion 146 * method. </p></li> 147 * 148 * </ul> 149 * 150 * <p> A <em>short version string</em> ({@code $SVSTR}), often useful in less 151 * formal contexts, is simply {@code $VNUM} optionally ended with {@code 152 * -$PRE}. </p> 153 * 154 * @see <a href="http://openjdk.java.net/jeps/223">JEP 223: New Version-String Scheme</a> 155 * 156 * @since 9 157 */ 158 public final class Version 159 implements Comparable<Version> 160 { 161 private final List<Integer> version; 162 private final Optional<String> pre; 163 private final Optional<Integer> build; 164 private final Optional<String> optional; 165 166 private static Version current; 167 168 // $VNUM(-$PRE)?(\+($BUILD)?(\-$OPT)?)? 169 // ([1-9][0-9]*(((\.0)*\.[1-9][0-9]*)*)*)(\-([a-zA-Z0-9]+))?((\+)(0|[1-9][0-9]*)?)?(-([\-a-zA-Z0-9\.]+))? 170 171 private static final String VNUM 172 = "([1-9][0-9]*(((\\.0)*\\.[1-9][0-9]*)*)*)"; 173 private static final int VNUM_GROUP = 1; 174 175 private static final String PRE = "(\\-([a-zA-Z0-9]+))?"; 176 private static final int PRE_GROUP = 6; 177 178 private static final String BUILD = "((\\+)(0|[1-9][0-9]*)?)?"; 179 private static final int PLUS_GROUP = 8; 180 private static final int BUILD_GROUP = 9; 181 182 private static final String OPT = "(-([\\-a-zA-Z0-9\\.]+))?"; 183 private static final int OPT_GROUP = 11; 184 185 private static final String VSTR_FORMAT 186 = "^" + VNUM + PRE + BUILD + OPT + "$"; 187 private static final Pattern VSTR_PATTERN = Pattern.compile(VSTR_FORMAT); 188 189 /** 190 * Constructs a valid JDK <a href="verStr">version string</a> containing a 191 * <a href="#verNum">version number</a> followed by pre-release and build 192 * information. 193 * 194 * @param s 195 * A string to be interpreted as a version 196 * 197 * @throws IllegalArgumentException 198 * If the given string cannot be interpreted a valid version 199 * 200 * @throws NullPointerException 201 * If {@code s} is {@code null} 202 * 203 * @throws NumberFormatException 204 * If an element of the version number or the build number cannot 205 * be represented as an {@link Integer} 206 */ 207 private Version(String s) { 208 if (s == null) 209 throw new NullPointerException(); 210 211 Matcher m = VSTR_PATTERN.matcher(s); 212 if (!m.matches()) 213 throw new IllegalArgumentException("Invalid version string: '" 214 + s + "'"); 215 216 // $VNUM is a dot-separated list of integers of arbitrary length 217 version 218 = Collections.unmodifiableList( 219 Arrays.stream(m.group(VNUM_GROUP).split("\\.")) 220 .map(Integer::parseInt) 221 .collect(Collectors.toList())); 222 223 pre = Optional.ofNullable(m.group(PRE_GROUP)); 224 225 String b = m.group(BUILD_GROUP); 226 // $BUILD is an integer 227 build = (b == null) 228 ? Optional.<Integer>empty() 229 : Optional.ofNullable(Integer.parseInt(b)); 230 231 optional = Optional.ofNullable(m.group(OPT_GROUP)); 232 233 if ((m.group(PLUS_GROUP) != null) 234 && !build.isPresent() && !optional.isPresent()) { 235 throw new IllegalArgumentException("'+' found with neither build" 236 + " or optional components: '" 237 + s + "'"); 238 } 239 } 240 241 /** 242 * Parses the given string as a valid JDK <a 243 * href="#verStr">version string</a> containing a <a 244 * href="#verNum">version number</a> followed by pre-release and 245 * build information. 246 * 247 * @param s 248 * A string to interpret as a version 249 * 250 * @throws IllegalArgumentException 251 * If the given string cannot be interpreted a valid version 252 * 253 * @throws NullPointerException 254 * If the given string is {@code null} 255 * 256 * @throws NumberFormatException 257 * If an element of the version number or the build number cannot 258 * be represented as an {@link Integer} 259 * 260 * @return This version 261 */ 262 public static Version parse(String s) { 263 return new Version(s); 264 } 265 266 /** 267 * Returns {@code System.getProperty("java.version")} as a Version. 268 * 269 * @return {@code System.getProperty("java.version")} as a Version 270 */ 271 public static Version current() { 272 if (current == null) 273 current = parse(System.getProperty("java.version")); 274 return current; 275 } 276 277 /** 278 * Returns the <a href="#major">major</a> version number. 279 * 280 * @return The major version number 281 */ 282 public int major() { 283 return version.get(0); 284 } 285 286 /** 287 * Returns the <a href="#minor">minor</a> version number or zero if it was 288 * not set. 289 * 290 * @return The minor version number or zero if it was not set 291 */ 292 public int minor() { 293 return (version.size() > 1 ? version.get(1) : 0); 294 } 295 296 /** 297 * Returns the <a href="#security">security</a> version number or zero if 298 * it was not set. 299 * 300 * @return The security version number or zero if it was not set 301 */ 302 public int security() { 303 return (version.size() > 2 ? version.get(2) : 0); 304 } 305 306 /** 307 * Returns an unmodifiable {@link java.util.List List} of the 308 * integer numerals contained in the <a href="#verNum">version 309 * number</a>. The {@code List} always contains at least one 310 * element corresponding to the <a href="#major">major version 311 * number</a>. 312 * 313 * @return An unmodifiable list of the integer numerals 314 * contained in the version number 315 */ 316 public List<Integer> version() { 317 return version; 318 } 319 320 /** 321 * Returns the optional <a href="#pre">pre-release</a> information. 322 * 323 * @return The optional pre-release information as a String 324 */ 325 public Optional<String> pre() { 326 return pre; 327 } 328 329 /** 330 * Returns the <a href="#build">build number</a>. 331 * 332 * @return The optional build number. 333 */ 334 public Optional<Integer> build() { 335 return build; 336 } 337 338 /** 339 * Returns <a href="#opt">optional</a> additional identifying build 340 * information. 341 * 342 * @return Additional build information as a String 343 */ 344 public Optional<String> optional() { 345 return optional; 346 } 347 348 /** 349 * Compares this version to another. 350 * 351 * <p> Two versions are compared by examining the sequence of version 352 * numbers. If one sequence is shorter than another, then the missing 353 * elements of the shorter sequence are considered to be zero. If the 354 * version numbers are equal, then a version with a pre-release identifier 355 * is always considered to be less than a version without one. 356 * Pre-release identifiers are compared numerically when they consist only 357 * of digits, and lexiographically otherwise. Numeric identifiers are 358 * considered to be less than non-numeric identifiers. A version without 359 * a build value is always less than one with a build value; otherwise 360 * build numbers are compared numerically. The optional build information 361 * is compared lexiographically. During this comparision, a version with 362 * optional build information is considered to be greater than a version 363 * without one. 364 * 365 * <p> A version is not comparable to any other type of object. 366 * 367 * @param ob 368 * The object to be compared 369 * 370 * @return A negative integer, zero, or a positive integer if this 371 * {@code Version} is less than, equal to, or greater than the 372 * given {@code Version} 373 * 374 * @throws NullPointerException 375 * If the given object is {@code null} 376 */ 377 @Override 378 public int compareTo(Version ob) { 379 return compare(ob, false); 380 } 381 382 /** 383 * Compares this version to another disregarding optional build 384 * information. 385 * 386 * <p> Two versions are compared by examining the version string as 387 * described in {@link #compareTo(Version)} with the exception that the 388 * optional build information is always ignored. 389 * 390 * <p> A version is not comparable to any other type of object. 391 * 392 * @param ob 393 * The object to be compared 394 * 395 * @return A negative integer, zero, or a positive integer if this 396 * {@code Version} is less than, equal to, or greater than the 397 * given {@code Version} 398 * 399 * @throws NullPointerException 400 * If the given object is {@code null} 401 */ 402 public int compareToIgnoreOpt(Version ob) { 403 return compare(ob, true); 404 } 405 406 private int compare(Version ob, boolean ignoreOpt) { 407 if (ob == null) 408 throw new NullPointerException("Invalid argument"); 409 410 int ret = compareVersion(ob); 411 if (ret != 0) 412 return ret; 413 414 ret = comparePre(ob); 415 if (ret != 0) 416 return ret; 417 418 ret = compareBuild(ob); 419 if (ret != 0) 420 return ret; 421 422 if (!ignoreOpt) 423 return compareOpt(ob); 424 425 return 0; 426 } 427 428 private int compareVersion(Version ob) { 429 int size = version.size(); 430 int oSize = ob.version().size(); 431 int min = Math.min(size, oSize); 432 for (int i = 0; i < min; i++) { 433 Integer val = version.get(i); 434 Integer oVal = ob.version().get(i); 435 if (val != oVal) 436 return val - oVal; 437 } 438 if (size != oSize) 439 return size - oSize; 440 return 0; 441 } 442 443 private int comparePre(Version ob) { 444 Optional<String> oPre = ob.pre(); 445 if (!pre.isPresent()) { 446 if (oPre.isPresent()) 447 return 1; 448 } else { 449 if (!oPre.isPresent()) 450 return -1; 451 String val = pre.get(); 452 String oVal = oPre.get(); 453 if (val.matches("\\d+")) { 454 return (oVal.matches("\\d+") 455 ? (new BigInteger(val)).compareTo(new BigInteger(oVal)) 456 : -1); 457 } else { 458 return (oVal.matches("\\d+") 459 ? 1 460 : val.compareTo(oVal)); 461 } 462 } 463 return 0; 464 } 465 466 private int compareBuild(Version ob) { 467 Optional<Integer> oBuild = ob.build(); 468 if (oBuild.isPresent()) { 469 return (build.isPresent() 470 ? build.get().compareTo(oBuild.get()) 471 : 1); 472 } else if (build.isPresent()) { 473 return -1; 474 } 475 return 0; 476 } 477 478 private int compareOpt(Version ob) { 479 Optional<String> oOpt = ob.optional(); 480 if (!optional.isPresent()) { 481 if (oOpt.isPresent()) 482 return -1; 483 } else { 484 if (!oOpt.isPresent()) 485 return 1; 486 return optional.get().compareTo(oOpt.get()); 487 } 488 return 0; 489 } 490 491 /** 492 * Returns a string representation of this version. 493 * 494 * @return The version string 495 */ 496 @Override 497 public String toString() { 498 StringBuilder sb 499 = new StringBuilder(version.stream() 500 .map(Object::toString) 501 .collect(Collectors.joining("."))); 502 pre.ifPresent(v -> sb.append("-").append(v)); 503 504 if (build.isPresent()) { 505 sb.append("+").append(build.get()); 506 if (optional.isPresent()) 507 sb.append("-").append(optional.get()); 508 } else { 509 if (optional.isPresent()) 510 sb.append("+-").append(optional.get()); 511 } 512 513 return sb.toString(); 514 } 515 516 /** 517 * Determines whether this {@code Version} is equal to another object. 518 * 519 * <p> Two {@code Version}s are equal if and only if they represent the 520 * same version string. 521 * 522 * <p> This method satisfies the general contract of the {@link 523 * Object#equals(Object) Object.equals} method. </p> 524 * 525 * @param ob 526 * The object to which this {@code Version} is to be compared 527 * 528 * @return {@code true} if, and only if, the given object is a {@code 529 * Version} that is identical to this {@code Version} 530 * 531 */ 532 @Override 533 public boolean equals(Object ob) { 534 boolean ret = equalsIgnoreOpt(ob); 535 if (!ret) 536 return false; 537 538 Version that = (Version)ob; 539 return (this.optional().equals(that.optional())); 540 } 541 542 /** 543 * Determines whether this {@code Version} is equal to another 544 * disregarding optional build information. 545 * 546 * <p> Two {@code Version}s are equal if and only if they represent the 547 * same version string disregarding the optional build information. 548 * 549 * <p> This method satisfies the general contract of the {@link 550 * Object#equals(Object) Object.equals} method. </p> 551 * 552 * @param ob 553 * The object to which this {@code Version} is to be compared 554 * 555 * @return {@code true} if, and only if, the given object is a {@code 556 * Version} that is identical to this {@code Version} 557 * ignoring the optinal build information 558 * 559 */ 560 public boolean equalsIgnoreOpt(Object ob) { 561 if (this == ob) 562 return true; 563 if (!(ob instanceof Version)) 564 return false; 565 566 Version that = (Version)ob; 567 return (this.version().equals(that.version()) 568 && this.pre().equals(that.pre()) 569 && this.build().equals(that.build())); 570 } 571 572 /** 573 * Returns the hash code of this version. 574 * 575 * <p> This method satisfies the general contract of the {@link 576 * Object#hashCode Object.hashCode} method. 577 * 578 * @return The hashcode of this version 579 */ 580 @Override 581 public int hashCode() { 582 int h = 1; 583 int p = 17; 584 585 h = p * h + version.hashCode(); 586 h = p * h + pre.hashCode(); 587 h = p * h + build.hashCode(); 588 h = p * h + optional.hashCode(); 589 590 return h; 591 } 592 }