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 }