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