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(
 277                     GetPropertyAction.privilegedGetProperty("java.version"));
 278         }
 279         return current;
 280     }
 281 
 282     /**
 283      * Returns the <a href="#major">major</a> version number.
 284      *
 285      * @return  The major version number
 286      */
 287     public int major() {
 288         return version.get(0);
 289     }
 290 
 291     /**
 292      * Returns the <a href="#minor">minor</a> version number or zero if it was
 293      * not set.
 294      *
 295      * @return  The minor version number or zero if it was not set
 296      */
 297     public int minor() {
 298         return (version.size() > 1 ? version.get(1) : 0);
 299     }
 300 
 301     /**
 302      * Returns the <a href="#security">security</a> version number or zero if
 303      * it was not set.
 304      *
 305      * @return  The security version number or zero if it was not set
 306      */
 307     public int security() {
 308         return (version.size() > 2 ? version.get(2) : 0);
 309     }
 310 
 311     /**
 312      * Returns an unmodifiable {@link java.util.List List} of the
 313      * integer numerals contained in the <a href="#verNum">version
 314      * number</a>.  The {@code List} always contains at least one
 315      * element corresponding to the <a href="#major">major version
 316      * number</a>.
 317      *
 318      * @return  An unmodifiable list of the integer numerals
 319      *          contained in the version number
 320      */
 321     public List<Integer> version() {
 322         return version;
 323     }
 324 
 325     /**
 326      * Returns the optional <a href="#pre">pre-release</a> information.
 327      *
 328      * @return  The optional pre-release information as a String
 329      */
 330     public Optional<String> pre() {
 331         return pre;
 332     }
 333 
 334     /**
 335      * Returns the <a href="#build">build number</a>.
 336      *
 337      * @return The optional build number.
 338      */
 339     public Optional<Integer> build() {
 340         return build;
 341     }
 342 
 343     /**
 344      * Returns <a href="#opt">optional</a> additional identifying build
 345      * information.
 346      *
 347      * @return  Additional build information as a String
 348      */
 349     public Optional<String> optional() {
 350         return optional;
 351     }
 352 
 353     /**
 354      * Compares this version to another.
 355      *
 356      * <p> Each of the components in the <a href="#verStr">version</a> is
 357      * compared in the follow order of precedence: version numbers,
 358      * pre-release identifiers, build numbers, optional build information. </p>
 359      *
 360      * <p> Comparison begins by examining the sequence of version numbers.  If
 361      * one sequence is shorter than another, then the missing elements of the
 362      * shorter sequence are considered to be zero. </p>
 363      *
 364      * <p> A version with a pre-release identifier is always considered to be
 365      * less than a version without one.  Pre-release identifiers are compared
 366      * numerically when they consist only of digits, and lexicographically
 367      * otherwise.  Numeric identifiers are considered to be less than
 368      * non-numeric identifiers.  </p>
 369      *
 370      * <p> A version without a build number is always less than one with a
 371      * build number; otherwise build numbers are compared numerically. </p>
 372      *
 373      * <p> The optional build information is compared lexicographically.
 374      * During this comparison, a version with optional build information is
 375      * considered to be greater than a version without one. </p>
 376      *
 377      * <p> A version is not comparable to any other type of object.
 378      *
 379      * @param  ob
 380      *         The object to be compared
 381      *
 382      * @return  A negative integer, zero, or a positive integer if this
 383      *          {@code Version} is less than, equal to, or greater than the
 384      *          given {@code Version}
 385      *
 386      * @throws  NullPointerException
 387      *          If the given object is {@code null}
 388      */
 389     @Override
 390     public int compareTo(Version ob) {
 391         return compare(ob, false);
 392     }
 393 
 394     /**
 395      * Compares this version to another disregarding optional build
 396      * information.
 397      *
 398      * <p> Two versions are compared by examining the version string as
 399      * described in {@link #compareTo(Version)} with the exception that the
 400      * optional build information is always ignored. </p>
 401      *
 402      * <p> A version is not comparable to any other type of object.
 403      *
 404      * @param  ob
 405      *         The object to be compared
 406      *
 407      * @return  A negative integer, zero, or a positive integer if this
 408      *          {@code Version} is less than, equal to, or greater than the
 409      *          given {@code Version}
 410      *
 411      * @throws  NullPointerException
 412      *          If the given object is {@code null}
 413      */
 414     public int compareToIgnoreOpt(Version ob) {
 415         return compare(ob, true);
 416     }
 417 
 418     private int compare(Version ob, boolean ignoreOpt) {
 419         if (ob == null)
 420             throw new NullPointerException("Invalid argument");
 421 
 422         int ret = compareVersion(ob);
 423         if (ret != 0)
 424             return ret;
 425 
 426         ret = comparePre(ob);
 427         if (ret != 0)
 428             return ret;
 429 
 430         ret = compareBuild(ob);
 431         if (ret != 0)
 432             return ret;
 433 
 434         if (!ignoreOpt)
 435             return compareOpt(ob);
 436 
 437         return 0;
 438     }
 439 
 440     private int compareVersion(Version ob) {
 441         int size = version.size();
 442         int oSize = ob.version().size();
 443         int min = Math.min(size, oSize);
 444         for (int i = 0; i < min; i++) {
 445             Integer val = version.get(i);
 446             Integer oVal = ob.version().get(i);
 447             if (val != oVal)
 448                 return val - oVal;
 449         }
 450         if (size != oSize)
 451             return size - oSize;
 452         return 0;
 453     }
 454 
 455     private int comparePre(Version ob) {
 456         Optional<String> oPre = ob.pre();
 457         if (!pre.isPresent()) {
 458             if (oPre.isPresent())
 459                 return 1;
 460         } else {
 461             if (!oPre.isPresent())
 462                 return -1;
 463             String val = pre.get();
 464             String oVal = oPre.get();
 465             if (val.matches("\\d+")) {
 466                 return (oVal.matches("\\d+")
 467                         ? (new BigInteger(val)).compareTo(new BigInteger(oVal))
 468                         : -1);
 469             } else {
 470                 return (oVal.matches("\\d+")
 471                         ? 1
 472                         : val.compareTo(oVal));
 473             }
 474         }
 475         return 0;
 476     }
 477 
 478     private int compareBuild(Version ob) {
 479         Optional<Integer> oBuild = ob.build();
 480         if (oBuild.isPresent()) {
 481             return (build.isPresent()
 482                    ? build.get().compareTo(oBuild.get())
 483                    : 1);
 484         } else if (build.isPresent()) {
 485             return -1;
 486         }
 487         return 0;
 488     }
 489 
 490     private int compareOpt(Version ob) {
 491         Optional<String> oOpt = ob.optional();
 492         if (!optional.isPresent()) {
 493             if (oOpt.isPresent())
 494                 return -1;
 495         } else {
 496             if (!oOpt.isPresent())
 497                 return 1;
 498             return optional.get().compareTo(oOpt.get());
 499         }
 500         return 0;
 501     }
 502 
 503     /**
 504      * Returns a string representation of this version.
 505      *
 506      * @return  The version string
 507      */
 508     @Override
 509     public String toString() {
 510         StringBuilder sb
 511             = new StringBuilder(version.stream()
 512                                 .map(Object::toString)
 513                                 .collect(Collectors.joining(".")));
 514         pre.ifPresent(v -> sb.append("-").append(v));
 515 
 516         if (build.isPresent()) {
 517             sb.append("+").append(build.get());
 518             if (optional.isPresent())
 519                 sb.append("-").append(optional.get());
 520         } else {
 521             if (optional.isPresent()) {
 522                 sb.append(pre.isPresent() ? "-" : "+-");
 523                 sb.append(optional.get());
 524             }
 525         }
 526 
 527         return sb.toString();
 528     }
 529 
 530     /**
 531      * Determines whether this {@code Version} is equal to another object.
 532      *
 533      * <p> Two {@code Version}s are equal if and only if they represent the
 534      * same version string.
 535      *
 536      * <p> This method satisfies the general contract of the {@link
 537      * Object#equals(Object) Object.equals} method. </p>
 538      *
 539      * @param  ob
 540      *         The object to which this {@code Version} is to be compared
 541      *
 542      * @return  {@code true} if, and only if, the given object is a {@code
 543      *          Version} that is identical to this {@code Version}
 544      *
 545      */
 546     @Override
 547     public boolean equals(Object ob) {
 548         boolean ret = equalsIgnoreOpt(ob);
 549         if (!ret)
 550             return false;
 551 
 552         Version that = (Version)ob;
 553         return (this.optional().equals(that.optional()));
 554     }
 555 
 556     /**
 557      * Determines whether this {@code Version} is equal to another
 558      * disregarding optional build information.
 559      *
 560      * <p> Two {@code Version}s are equal if and only if they represent the
 561      * same version string disregarding the optional build information.
 562      *
 563      * @param  ob
 564      *         The object to which this {@code Version} is to be compared
 565      *
 566      * @return  {@code true} if, and only if, the given object is a {@code
 567      *          Version} that is identical to this {@code Version}
 568      *          ignoring the optinal build information
 569      *
 570      */
 571     public boolean equalsIgnoreOpt(Object ob) {
 572         if (this == ob)
 573             return true;
 574         if (!(ob instanceof Version))
 575             return false;
 576 
 577         Version that = (Version)ob;
 578         return (this.version().equals(that.version())
 579                 && this.pre().equals(that.pre())
 580                 && this.build().equals(that.build()));
 581     }
 582 
 583     /**
 584      * Returns the hash code of this version.
 585      *
 586      * <p> This method satisfies the general contract of the {@link
 587      * Object#hashCode Object.hashCode} method.
 588      *
 589      * @return  The hashcode of this version
 590      */
 591     @Override
 592     public int hashCode() {
 593         int h = 1;
 594         int p = 17;
 595 
 596         h = p * h + version.hashCode();
 597         h = p * h + pre.hashCode();
 598         h = p * h + build.hashCode();
 599         h = p * h + optional.hashCode();
 600 
 601         return h;
 602     }
 603 }