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