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, is always ignored. </p></li> 146 * 147 * </ul> 148 * 149 * <p> A <em>short version string</em> ({@code $SVSTR}), often useful in less 150 * formal contexts, is simply {@code $VNUM} optionally ended with {@code 151 * -$PRE}. </p> 152 * 153 * @see <a href="http://openjdk.java.net/jeps/223">JEP 223: New Version-String Scheme</a> 154 * 155 * @since 9 156 */ 157 public class Version 158 implements Comparable<Version> 159 { 160 private final List<Integer> version; 161 private final Optional<String> pre; 162 private final Optional<Integer> build; 163 private final Optional<String> optional; 164 165 // $VNUM(-$PRE)?(\+$BUILD)?(\-$OPT)? 166 // ([1-9][0-9]*(((\.0)*\.[1-9][0-9]*)*)*)(\-([a-zA-Z0-9]+))?((\+)(0|[1-9][0-9]*)?)?(-([\-a-zA-Z0-9\.]+))? 167 168 private static final String VNUM 169 = "([1-9][0-9]*(((\\.0)*\\.[1-9][0-9]*)*)*)"; 170 private static final int VNUM_GROUP = 1; 171 172 private static final String PRE = "(\\-([a-zA-Z0-9]+))?"; 173 private static final int PRE_GROUP = 6; 174 175 private static final String BUILD = "((\\+)(0|[1-9][0-9]*)?)?"; 176 private static final int PLUS_GROUP = 8; 177 private static final int BUILD_GROUP = 9; 178 179 private static final String OPT = "(-([\\-a-zA-Z0-9\\.]+))?"; 180 private static final int OPT_GROUP = 11; 181 182 private static final String VSTR_FORMAT 183 = "^" + VNUM + PRE + BUILD + OPT + "$"; 184 private static final Pattern VSTR_PATTERN = Pattern.compile(VSTR_FORMAT); 185 186 /** 187 * Constructs a valid JDK <a href="verStr">version string</a> containing a 188 * <a href="#verNum">version number</a> followed by pre-release and build 189 * information. 190 * 191 * @param s 192 * A string to be interpreted as a version 193 * 194 * @throws IllegalArgumentException 195 * If the given string cannot be interpreted a valid version 196 * 197 * @throws NullPointerException 198 * If {@code s} is {@code null} 199 * 200 * @throws NumberFormatException 201 * If an element of the version number or the build number cannot 202 * be represented as an {@link Integer} 203 */ 204 Version(String s) { 205 if (s == null) 206 throw new NullPointerException(); 207 208 Matcher m = VSTR_PATTERN.matcher(s); 209 if (!m.matches()) 210 throw new IllegalArgumentException("Invalid version string: '" 211 + s + "'"); 212 213 version 214 = Collections.unmodifiableList( 215 Arrays.stream(m.group(VNUM_GROUP).split("\\.")) 216 .map(Integer::parseInt) 217 .collect(Collectors.toList())); 218 219 pre = Optional.ofNullable(m.group(PRE_GROUP)); 220 221 String b = m.group(BUILD_GROUP); 222 build = (b == null) 223 ? Optional.<Integer>empty() 224 : Optional.ofNullable(Integer.parseInt(b)); 225 optional = Optional.ofNullable(m.group(OPT_GROUP)); 226 227 if ((m.group(PLUS_GROUP) != null) 228 && !build.isPresent() && !optional.isPresent()) { 229 throw new IllegalArgumentException("Terminal '+': '" + s + "'"); 230 } 231 } 232 233 /** 234 * Parses the given string as a valid JDK <a 235 * href="#verStr">version string</a> containing a <a 236 * href="#verNum">version number</a> followed by pre-release and 237 * build information. 238 * 239 * @param s 240 * A string to interpret as a version 241 * 242 * @throws IllegalArgumentException 243 * If the given string cannot be interpreted a valid version 244 * 245 * @throws NullPointerException 246 * If the given string is {@code null} 247 * 248 * @throws NumberFormatException 249 * If an element of the version number or the build number cannot 250 * be represented as an {@link Integer} 251 * 252 * @return This version 253 */ 254 public static Version parse(String s) { 255 return new Version(s); 256 } 257 258 /** 259 * Returns {@code System.getProperty("java.version")} as a Version. 260 * 261 * @return {@code System.getProperty("java.version")} as a Version 262 */ 263 public static Version current() { 264 return parse(System.getProperty("java.version")); 265 } 266 267 /** 268 * Returns the <a href="#major">major</a> version number. 269 * 270 * @return The major version number 271 */ 272 public int major() { 273 return version.get(0); 274 } 275 276 /** 277 * Returns the <a href="#minor">minor</a> version number or zero if it was 278 * not set. 279 * 280 * @return The minor version number or zero if it was not set 281 */ 282 public int minor() { 283 return (version.size() > 1 ? version.get(1) : 0); 284 } 285 286 /** 287 * Returns the <a href="#security">security</a> version number or zero if 288 * it was not set. 289 * 290 * @return The security version number or zero if it was not set 291 */ 292 public int security() { 293 return (version.size() > 2 ? version.get(2) : 0); 294 } 295 296 /** 297 * Returns an unmodifiable {@link java.util.List List} of the 298 * integer numerals contained in the <a href="#verNum">version 299 * number</a>. The {@code List} always contains at least one 300 * element corresponding to the <a href="#major">major version 301 * number</a>. 302 * 303 * @return An unmodifiable list of the integer numerals 304 * contained in the version number 305 */ 306 public List<Integer> version() { 307 return version; 308 } 309 310 /** 311 * Returns the optional <a href="#pre">pre-release</a> information. 312 * 313 * @return The optional pre-release information as a String 314 */ 315 public Optional<String> pre() { 316 return pre; 317 } 318 319 /** 320 * Returns the <a href="#build">build number</a>. 321 * 322 * @return The optional build number. 323 */ 324 public Optional<Integer> build() { 325 return build; 326 } 327 328 /** 329 * Returns <a href="#opt">optional</a> additional identifying build 330 * information. 331 * 332 * @return Additional build information as a String 333 */ 334 public Optional<String> optional() { 335 return optional; 336 } 337 338 /** 339 * Compares this version to another. 340 * 341 * <p> Two versions are compared by examining the sequence of version 342 * numbers. If one sequence is shorter than another, then the missing 343 * elements of the shorter sequence are considered to be zero. If the 344 * version numbers are equal, then a version with a pre-release identifier 345 * is always considered to be less than a version without one. 346 * Pre-release identifiers are compared numerically when they consist only 347 * of digits, and lexiographically otherwise. Numeric identifiers are 348 * considered to be less than non-numeric identifiers. A version without 349 * a build value is always less than one with a build value; otherwise 350 * build numbers are compared numerically. The optional build information 351 * is always ignored. 352 * 353 * <p> A version is not comparable to any other type of object. 354 * 355 * @param ob 356 * The object to be compared 357 * 358 * @return A negative integer, zero, or a positive integer if this 359 * {@code Version} is less than, equal to, or greater than the 360 * given {@code Version} 361 * 362 * @throws NullPointerException 363 * If the given object is {@code null} 364 */ 365 @Override 366 public int compareTo(Version ob) { 367 if (ob == null) 368 throw new NullPointerException("Invalid argument"); 369 370 // $VNUM 371 int size = version.size(); 372 int oSize = ob.version().size(); 373 int min = Math.min(size, oSize); 374 for (int i = 0; i < min; i++) { 375 Integer val = version.get(i); 376 Integer oVal = ob.version().get(i); 377 if (val != oVal) 378 return val - oVal; 379 } 380 if (size != oSize) 381 return size - oSize; 382 383 // $PRE 384 Optional<String> oPre = ob.pre(); 385 if (!pre.isPresent()) { 386 if (oPre.isPresent()) 387 return 1; 388 } else { 389 if (!oPre.isPresent()) 390 return -1; 391 String val = pre.get(); 392 String oVal = oPre.get(); 393 if (val.matches("\\d+")) { 394 if (oVal.matches("\\d+")) { 395 return 396 (new BigInteger(val)).compareTo(new BigInteger(oVal)); 397 } else { 398 return -1; 399 } 400 } else { 401 if (oVal.matches("\\d+")) { 402 return 1; 403 } else { 404 return val.compareTo(oVal); 405 } 406 } 407 } 408 409 // $BUILD 410 Optional<Integer> oBuild = ob.build(); 411 if (oBuild.isPresent()) { 412 return (build.isPresent() 413 ? build.get().compareTo(oBuild.get()) 414 : 1); 415 } else if (build.isPresent()) { 416 return -1; 417 } 418 419 return 0; 420 } 421 422 /** 423 * Returns a string representation of this version. 424 * 425 * @return The version string 426 */ 427 @Override 428 public String toString() { 429 StringBuilder sb 430 = new StringBuilder(version.stream() 431 .map(Object::toString) 432 .collect(Collectors.joining("."))); 433 if (pre.isPresent()) 434 sb.append("-").append(pre.get()); 435 436 if (build.isPresent()) { 437 sb.append("+").append(build.get()); 438 if (optional.isPresent()) 439 sb.append("-").append(optional.get()); 440 } else { 441 if (optional.isPresent()) 442 sb.append("+-").append(optional.get()); 443 } 444 445 return sb.toString(); 446 } 447 448 /** 449 * Determines whether this {@code Version} is equal to another object. 450 * 451 * <p> Two {@code Version}s are equal if and only if they represent the 452 * same version string. 453 * 454 * <p> This method satisfies the general contract of the {@link 455 * Object#equals(Object) Object.equals} method. </p> 456 * 457 * @param ob 458 * The object to which this {@code Version} is to be compared 459 * 460 * @return {@code true} if, and only if, the given object is a {@code 461 * Version} that is identical to this {@code Version} 462 * 463 */ 464 @Override 465 public boolean equals(Object ob) { 466 if (this == ob) 467 return true; 468 if (!(ob instanceof Version)) 469 return false; 470 471 Version that = (Version)ob; 472 return (this.version().equals(that.version()) 473 && this.pre().equals(that.pre()) 474 && this.build().equals(that.build()) 475 && this.optional().equals(that.optional())); 476 } 477 478 /** 479 * Returns the hash code of this version. 480 * 481 * <p> This method satisfies the general contract of the {@link 482 * Object#hashCode Object.hashCode} method. 483 * 484 * @return The hashcode of this version 485 */ 486 @Override 487 public int hashCode() { 488 int h = 1; 489 int p = 17; 490 491 h = p * h + version.hashCode(); 492 h = p * h + pre.hashCode(); 493 h = p * h + build.hashCode(); 494 h = p * h + optional.hashCode(); 495 496 return h; 497 } 498 }