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