--- /dev/null Fri Dec 18 12:22:01 2015 +++ new/src/java.base/share/classes/jdk/Version.java Fri Dec 18 12:22:01 2015 @@ -0,0 +1,592 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk; + +import java.math.BigInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * A representation of the JDK version-string which contains a version + * number optionally followed by pre-release and build information. + * + *

Version numbers

+ * + * A version number, {@code $VNUM}, is a non-empty sequence of + * non-negative integer numerals, without leading or trailing zeroes, + * separated by period characters (U+002E); i.e., it matches the regular + * expression {@code ^[1-9][0-9]*(((\.0)*\.[1-9][0-9]*)*)*$}. The sequence may + * be of arbitrary length but the first three elements are assigned specific + * meanings, as follows: + * + *
+ *     $MAJOR.$MINOR.$SECURITY
+ * 
+ * + * + * + *

The fourth and later elements of a version number are free for use by + * downstream consumers of the JDK code base. Such a consumer may, + * e.g., use the fourth element to identify patch releases which + * contain a small number of critical non-security fixes in addition to the + * security fixes in the corresponding security release.

+ * + *

The version number does not include trailing zero elements; + * i.e., {@code $SECURITY} is omitted if it has the value zero, and + * {@code $MINOR} is omitted if both {@code $MINOR} and {@code $SECURITY} have + * the value zero.

+ * + *

The sequence of numerals in a version number is compared to another + * such sequence in numerical, pointwise fashion; e.g., {@code 9.9.1} + * is less than {@code 9.10.0}. If one sequence is shorter than another then + * the missing elements of the shorter sequence are considered to be zero; + * e.g., {@code 9.1.2} is equal to {@code 9.1.2.0} but less than + * {@code 9.1.2.1}.

+ * + *

Version strings

+ * + *

A version string {@code $VSTR} consists of a version number + * {@code $VNUM}, as described above, optionally followed by pre-release and + * build information, in the format

+ * + *
+ *     $VNUM(-$PRE)?(\+($BUILD)?(-$OPT)?)?
+ * 
+ * + *

where:

+ * + * + * + *

A short version string ({@code $SVSTR}), often useful in less + * formal contexts, is simply {@code $VNUM} optionally ended with {@code + * -$PRE}.

+ * + * @see JEP 223: New Version-String Scheme + * + * @since 9 + */ +public final class Version + implements Comparable +{ + private final List version; + private final Optional pre; + private final Optional build; + private final Optional optional; + + private static Version current; + + // $VNUM(-$PRE)?(\+($BUILD)?(\-$OPT)?)? + // ([1-9][0-9]*(((\.0)*\.[1-9][0-9]*)*)*)(\-([a-zA-Z0-9]+))?((\+)(0|[1-9][0-9]*)?)?(-([\-a-zA-Z0-9\.]+))? + + private static final String VNUM + = "([1-9][0-9]*(((\\.0)*\\.[1-9][0-9]*)*)*)"; + private static final int VNUM_GROUP = 1; + + private static final String PRE = "(\\-([a-zA-Z0-9]+))?"; + private static final int PRE_GROUP = 6; + + private static final String BUILD = "((\\+)(0|[1-9][0-9]*)?)?"; + private static final int PLUS_GROUP = 8; + private static final int BUILD_GROUP = 9; + + private static final String OPT = "(-([\\-a-zA-Z0-9\\.]+))?"; + private static final int OPT_GROUP = 11; + + private static final String VSTR_FORMAT + = "^" + VNUM + PRE + BUILD + OPT + "$"; + private static final Pattern VSTR_PATTERN = Pattern.compile(VSTR_FORMAT); + + /** + * Constructs a valid JDK version string containing a + * version number followed by pre-release and build + * information. + * + * @param s + * A string to be interpreted as a version + * + * @throws IllegalArgumentException + * If the given string cannot be interpreted a valid version + * + * @throws NullPointerException + * If {@code s} is {@code null} + * + * @throws NumberFormatException + * If an element of the version number or the build number cannot + * be represented as an {@link Integer} + */ + private Version(String s) { + if (s == null) + throw new NullPointerException(); + + Matcher m = VSTR_PATTERN.matcher(s); + if (!m.matches()) + throw new IllegalArgumentException("Invalid version string: '" + + s + "'"); + + // $VNUM is a dot-separated list of integers of arbitrary length + version + = Collections.unmodifiableList( + Arrays.stream(m.group(VNUM_GROUP).split("\\.")) + .map(Integer::parseInt) + .collect(Collectors.toList())); + + pre = Optional.ofNullable(m.group(PRE_GROUP)); + + String b = m.group(BUILD_GROUP); + // $BUILD is an integer + build = (b == null) + ? Optional.empty() + : Optional.ofNullable(Integer.parseInt(b)); + + optional = Optional.ofNullable(m.group(OPT_GROUP)); + + if ((m.group(PLUS_GROUP) != null) + && !build.isPresent() && !optional.isPresent()) { + throw new IllegalArgumentException("'+' found with neither build" + + " or optional components: '" + + s + "'"); + } + } + + /** + * Parses the given string as a valid JDK version string containing a version number followed by pre-release and + * build information. + * + * @param s + * A string to interpret as a version + * + * @throws IllegalArgumentException + * If the given string cannot be interpreted a valid version + * + * @throws NullPointerException + * If the given string is {@code null} + * + * @throws NumberFormatException + * If an element of the version number or the build number cannot + * be represented as an {@link Integer} + * + * @return This version + */ + public static Version parse(String s) { + return new Version(s); + } + + /** + * Returns {@code System.getProperty("java.version")} as a Version. + * + * @return {@code System.getProperty("java.version")} as a Version + */ + public static Version current() { + if (current == null) + current = parse(System.getProperty("java.version")); + return current; + } + + /** + * Returns the major version number. + * + * @return The major version number + */ + public int major() { + return version.get(0); + } + + /** + * Returns the minor version number or zero if it was + * not set. + * + * @return The minor version number or zero if it was not set + */ + public int minor() { + return (version.size() > 1 ? version.get(1) : 0); + } + + /** + * Returns the security version number or zero if + * it was not set. + * + * @return The security version number or zero if it was not set + */ + public int security() { + return (version.size() > 2 ? version.get(2) : 0); + } + + /** + * Returns an unmodifiable {@link java.util.List List} of the + * integer numerals contained in the version + * number. The {@code List} always contains at least one + * element corresponding to the major version + * number. + * + * @return An unmodifiable list of the integer numerals + * contained in the version number + */ + public List version() { + return version; + } + + /** + * Returns the optional pre-release information. + * + * @return The optional pre-release information as a String + */ + public Optional pre() { + return pre; + } + + /** + * Returns the build number. + * + * @return The optional build number. + */ + public Optional build() { + return build; + } + + /** + * Returns optional additional identifying build + * information. + * + * @return Additional build information as a String + */ + public Optional optional() { + return optional; + } + + /** + * Compares this version to another. + * + *

Two versions are compared by examining the sequence of version + * numbers. If one sequence is shorter than another, then the missing + * elements of the shorter sequence are considered to be zero. If the + * version numbers are equal, then a version with a pre-release identifier + * is always considered to be less than a version without one. + * Pre-release identifiers are compared numerically when they consist only + * of digits, and lexiographically otherwise. Numeric identifiers are + * considered to be less than non-numeric identifiers. A version without + * a build value is always less than one with a build value; otherwise + * build numbers are compared numerically. The optional build information + * is compared lexiographically. During this comparision, a version with + * optional build information is considered to be greater than a version + * without one. + * + *

A version is not comparable to any other type of object. + * + * @param ob + * The object to be compared + * + * @return A negative integer, zero, or a positive integer if this + * {@code Version} is less than, equal to, or greater than the + * given {@code Version} + * + * @throws NullPointerException + * If the given object is {@code null} + */ + @Override + public int compareTo(Version ob) { + return compare(ob, false); + } + + /** + * Compares this version to another disregarding optional build + * information. + * + *

Two versions are compared by examining the version string as + * described in {@link #compareTo(Version)} with the exception that the + * optional build information is always ignored. + * + *

A version is not comparable to any other type of object. + * + * @param ob + * The object to be compared + * + * @return A negative integer, zero, or a positive integer if this + * {@code Version} is less than, equal to, or greater than the + * given {@code Version} + * + * @throws NullPointerException + * If the given object is {@code null} + */ + public int compareToIgnoreOpt(Version ob) { + return compare(ob, true); + } + + private int compare(Version ob, boolean ignoreOpt) { + if (ob == null) + throw new NullPointerException("Invalid argument"); + + int ret = compareVersion(ob); + if (ret != 0) + return ret; + + ret = comparePre(ob); + if (ret != 0) + return ret; + + ret = compareBuild(ob); + if (ret != 0) + return ret; + + if (!ignoreOpt) + return compareOpt(ob); + + return 0; + } + + private int compareVersion(Version ob) { + int size = version.size(); + int oSize = ob.version().size(); + int min = Math.min(size, oSize); + for (int i = 0; i < min; i++) { + Integer val = version.get(i); + Integer oVal = ob.version().get(i); + if (val != oVal) + return val - oVal; + } + if (size != oSize) + return size - oSize; + return 0; + } + + private int comparePre(Version ob) { + Optional oPre = ob.pre(); + if (!pre.isPresent()) { + if (oPre.isPresent()) + return 1; + } else { + if (!oPre.isPresent()) + return -1; + String val = pre.get(); + String oVal = oPre.get(); + if (val.matches("\\d+")) { + return (oVal.matches("\\d+") + ? (new BigInteger(val)).compareTo(new BigInteger(oVal)) + : -1); + } else { + return (oVal.matches("\\d+") + ? 1 + : val.compareTo(oVal)); + } + } + return 0; + } + + private int compareBuild(Version ob) { + Optional oBuild = ob.build(); + if (oBuild.isPresent()) { + return (build.isPresent() + ? build.get().compareTo(oBuild.get()) + : 1); + } else if (build.isPresent()) { + return -1; + } + return 0; + } + + private int compareOpt(Version ob) { + Optional oOpt = ob.optional(); + if (!optional.isPresent()) { + if (oOpt.isPresent()) + return -1; + } else { + if (!oOpt.isPresent()) + return 1; + return optional.get().compareTo(oOpt.get()); + } + return 0; + } + + /** + * Returns a string representation of this version. + * + * @return The version string + */ + @Override + public String toString() { + StringBuilder sb + = new StringBuilder(version.stream() + .map(Object::toString) + .collect(Collectors.joining("."))); + pre.ifPresent(v -> sb.append("-").append(v)); + + if (build.isPresent()) { + sb.append("+").append(build.get()); + if (optional.isPresent()) + sb.append("-").append(optional.get()); + } else { + if (optional.isPresent()) + sb.append("+-").append(optional.get()); + } + + return sb.toString(); + } + + /** + * Determines whether this {@code Version} is equal to another object. + * + *

Two {@code Version}s are equal if and only if they represent the + * same version string. + * + *

This method satisfies the general contract of the {@link + * Object#equals(Object) Object.equals} method.

+ * + * @param ob + * The object to which this {@code Version} is to be compared + * + * @return {@code true} if, and only if, the given object is a {@code + * Version} that is identical to this {@code Version} + * + */ + @Override + public boolean equals(Object ob) { + boolean ret = equalsIgnoreOpt(ob); + if (!ret) + return false; + + Version that = (Version)ob; + return (this.optional().equals(that.optional())); + } + + /** + * Determines whether this {@code Version} is equal to another + * disregarding optional build information. + * + *

Two {@code Version}s are equal if and only if they represent the + * same version string disregarding the optional build information. + * + *

This method satisfies the general contract of the {@link + * Object#equals(Object) Object.equals} method.

+ * + * @param ob + * The object to which this {@code Version} is to be compared + * + * @return {@code true} if, and only if, the given object is a {@code + * Version} that is identical to this {@code Version} + * ignoring the optinal build information + * + */ + public boolean equalsIgnoreOpt(Object ob) { + if (this == ob) + return true; + if (!(ob instanceof Version)) + return false; + + Version that = (Version)ob; + return (this.version().equals(that.version()) + && this.pre().equals(that.pre()) + && this.build().equals(that.build())); + } + + /** + * Returns the hash code of this version. + * + *

This method satisfies the general contract of the {@link + * Object#hashCode Object.hashCode} method. + * + * @return The hashcode of this version + */ + @Override + public int hashCode() { + int h = 1; + int p = 17; + + h = p * h + version.hashCode(); + h = p * h + pre.hashCode(); + h = p * h + build.hashCode(); + h = p * h + optional.hashCode(); + + return h; + } +} --- /dev/null Fri Dec 18 12:22:02 2015 +++ new/test/jdk/Version/Basic.java Fri Dec 18 12:22:02 2015 @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Unit test for jdk.Version. + * @bug 8072379 + */ + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigInteger; +import java.util.stream.Collectors; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import jdk.Version; +import static java.lang.System.out; + +public class Basic { + private static final Class IAE + = IllegalArgumentException.class; + private static final Class NPE + = NullPointerException.class; + private static final Class NFE + = NumberFormatException.class; + private static final Class VERSION = Version.class; + + private static final BigInteger TOO_BIG + = (BigInteger.valueOf(Integer.MAX_VALUE)).add(BigInteger.ONE); + private static final String TOO_BIG_STR = TOO_BIG.toString(); + + public static void main(String ... args) { + + //// Tests for parse(), major(), minor(), security(), pre(), + //// build(), opt(), version(), toString() + // v M m sec pre bld opt + + // $VNUM + test("9", 9, 0, 0, "", 0, ""); + test("9.1", 9, 1, 0, "", 0, ""); + test("9.0.1", 9, 0, 1, "", 0, ""); + test("404.1.2", 404, 1, 2, "", 0, ""); + test("9.1.2.3", 9, 1, 2, "", 0, ""); + test("1000.0.0.0.0.0.99999999", 1000, 0, 0, "", 0, ""); + + tryCatch(null, NPE); + tryCatch("", IAE); + tryCatch("foo", IAE); + tryCatch("7a", IAE); + tryCatch("0", IAE); + tryCatch("09", IAE); + tryCatch("9.0", IAE); + tryCatch("9.0.", IAE); + tryCatch("1.9,1", IAE); + tryCatch(TOO_BIG_STR, NFE); + + // $PRE + test("9-ea", 9, 0, 0, "ea", 0, ""); + test("9-internal", 9, 0, 0, "internal", 0, ""); + test("9-0", 9, 0, 0, "0", 0, ""); + test("9.2.7-8", 9, 2, 7, "8", 0, ""); + test("1-ALL", 1, 0, 0, "ALL", 0, ""); + test("2.3.4.5-1a", 2, 3, 4, "1a", 0, ""); + test("1-" + TOO_BIG_STR, 1, 0, 0, TOO_BIG_STR, 0, ""); + + tryCatch("3.14159-", IAE); + tryCatch("3.14159-%", IAE); + + // $BUILD + test("9+0", 9, 0, 0, "", 0, ""); + test("3.14+9999900", 3, 14, 0, "", 9999900, ""); + test("9-pre+105", 9, 0, 0, "pre", 105, ""); + test("6.0.42-8beta+4", 6, 0, 42, "8beta", 4, ""); + + tryCatch("9+", IAE); + tryCatch("7+a", IAE); + tryCatch("9+00", IAE); + tryCatch("4.2+01", IAE); + tryCatch("4.2+1a", IAE); + tryCatch("1+" + TOO_BIG_STR, NFE); + + // $OPT + test("9+-foo", 9, 0, 0, "", 0, "foo"); + test("42+---bar", 42, 0, 0, "", 0, "--bar"); + test("2.91+-8061493-", 2, 91, 0, "", 0, "8061493-"); + test("24+-foo.bar", 24, 0, 0, "", 0, "foo.bar"); + test("9-ribbit+17-...", 9, 0, 0, "ribbit", 17, "..."); + test("7+1-" + TOO_BIG_STR, 7,0, 0, "", 1, TOO_BIG_STR); + + tryCatch("1.4142+-", IAE); + tryCatch("2.9979+-%", IAE); + + //// Test for current() + testCurrent(); + + //// Test for equals{IgnoreOpt}?(), hashCode(), compareTo{IgnoreOpt}?() + // compare: after "<" == -1, equal == 0, before ">" == 1 + // v0 v1 eq eqNO cmp cmpNO + testEHC("9", "9", true, true, 0, 0); + + testEHC("8", "9", false, false, -1, -1); + testEHC("9", "10", false, false, -1, -1); + testEHC("9", "8", false, false, 1, 1); + + // $OPT comparison + testEHC("9", "9+-oink", false, true, -1, 0); + testEHC("9+-ribbit", "9+-moo", false, true, 1, 0); + testEHC("9-quack+-ribbit", + "9-quack+-moo", false, true, 1, 0); + testEHC("9.1+7", "9.1+7-moo-baa-la", false, true, -1, 0); + + // numeric vs. non-numeric $PRE + testEHC("9.1.1.2-2a", "9.1.1.2-12", false, false, 1, 1); + testEHC("9.1.1.2-12", "9.1.1.2-4", false, false, 1, 1); + + testEHC("27.16", "27.16+120", false, false, 1, 1); + testEHC("10", "10-ea", false, false, 1, 1); + testEHC("10.1+1", "10.1-ea+1", false, false, 1, 1); + testEHC("10.0.1+22", "10.0.1+21", false, false, 1, 1); + + // numeric vs. non-numeric $PRE + testEHC("9.1.1.2-12", "9.1.1.2-a2", false, false, -1, -1); + testEHC("9.1.1.2-1", "9.1.1.2-4", false, false, -1, -1); + + testEHC("9-internal", "9", false, false, -1, -1); + testEHC("9-ea+120", "9+120", false, false, -1, -1); + testEHC("9-ea+120", "9+120", false, false, -1, -1); + testEHC("9+101", "9", false, false, -1, -1); + testEHC("9+101", "9+102", false, false, -1, -1); + testEHC("1.9-ea", "9-ea", false, false, -1, -1); + + if (fail != 0) + throw new RuntimeException((fail + pass) + " tests: " + + fail + " failure(s), first", first); + else + out.println("all " + (fail + pass) + " tests passed"); + + } + + private static void test(String s, Integer major, Integer minor, + Integer sec, String pre, Integer build, + String opt) + { + Version v = testParse(s); + + testStr(v.toString(), s); + + testInt(v.major(), major); + testInt(v.minor(), minor); + testInt(v.security(), sec); + testStr((v.pre().isPresent() ? v.pre().get() : ""), pre); + testInt((v.build().isPresent() ? v.build().get() : 0), build); + testStr((v.optional().isPresent() ? v.optional().get() : ""), opt); + + testVersion(v.version(), s); + } + + private static Version testParse(String s) { + Version v = Version.parse(s); + pass(); + return v; + } + + private static void testInt(int got, int exp) { + if (got != exp) { + fail("testInt()", Integer.toString(exp), Integer.toString(got)); + } else { + pass(); + } + } + + private static void testStr(String got, String exp) { + if (!got.equals(exp)) { + fail("testStr()", exp, got); + } else { + pass(); + } + } + + private static void tryCatch(String s, Class ex) { + Throwable t = null; + try { + Version.parse(s); + } catch (Throwable x) { + if (ex.isAssignableFrom(x.getClass())) { + t = x; + } else + x.printStackTrace(); + } + if ((t == null) && (ex != null)) + fail(s, ex); + else + pass(); + } + + private static void testCurrent() { + Version current = Version.current(); + String javaVer = System.getProperty("java.version"); + + // java.version == $VNUM(\-$PRE) + String [] ver = javaVer.split("-"); + List javaVerVNum + = Arrays.stream(ver[0].split("\\.")) + .map(v -> Integer.parseInt(v)) + .collect(Collectors.toList()); + if (!javaVerVNum.equals(current.version())) { + fail("testCurrent() version()", javaVerVNum.toString(), + current.version().toString()); + } else { + pass(); + } + + Optional javaVerPre + = (ver.length == 2) + ? Optional.ofNullable(ver[1]) + : Optional.empty(); + if (!javaVerPre.equals(current.pre())) { + fail("testCurrent() pre()", javaVerPre.toString(), + current.pre().toString()); + } else { + pass(); + } + + testEHC(current.toString(), javaVer, true, true, 0, 0); + } + + private static void testVersion(List vnum, String s) { + List svnum = new ArrayList(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < s.length(); i++) { + Character c = s.charAt(i); + if (Character.isDigit(c)) { + sb.append(c); + } else { + svnum.add(Integer.parseInt(sb.toString())); + sb = new StringBuilder(); + if (c == '+' || c == '-') { + break; + } + } + } + if (sb.length() > 0) { + svnum.add(Integer.parseInt(sb.toString())); + } + + if (!svnum.equals(vnum)) { + fail("testVersion() equals()", svnum.toString(), vnum.toString()); + } else { + pass(); + } + } + + private static void testEHC(String s0, String s1, boolean eq, boolean eqNO, + int cmp, int cmpNO) + { + Version v0 = Version.parse(s0); + Version v1 = Version.parse(s1); + + testEquals(v0, v1, eq); + testEqualsNO(v0, v1, eqNO); + + testHashCode(v0, v1, eq); + + testCompare(v0, v1, cmp); + testCompareNO(v0, v1, cmpNO); + } + + private static void testEqualsNO(Version v0, Version v1, boolean eq) { + if ((eq && !v0.equalsIgnoreOpt(v1)) + || (!eq && v0.equalsIgnoreOpt(v1))) { + fail("equalsIgnoreOpt() " + Boolean.toString(eq), + v0.toString(), v1.toString()); + } else { + pass(); + } + } + + private static void testEquals(Version v0, Version v1, boolean eq) { + if ((eq && !v0.equals(v1)) || (!eq && v0.equals(v1))) { + fail("equals() " + Boolean.toString(eq), + v0.toString(), v1.toString()); + } else { + pass(); + } + } + + private static void testHashCode(Version v0, Version v1, boolean eq) { + int h0 = v0.hashCode(); + int h1 = v1.hashCode(); + if (eq) { + testInt(h0, h1); + } else if (h0 == h1) { + fail(String.format("hashCode() %s", h0), + Integer.toString(h0), + Integer.toString(h1)); + } else { // !eq && (h0 != h1) + pass(); + } + } + + private static void testCompareNO(Version v0, Version v1, int compare) + { + try { + Method m = VERSION.getMethod("compareToIgnoreOpt", VERSION); + int cmp = (int) m.invoke(v0, v1); + checkCompare(v0, v1, compare, cmp); + } catch (IllegalAccessException | InvocationTargetException | + NoSuchMethodException ex) { + fail(String.format("compareToIgnoreOpt() invocation: %s", + ex.getClass()), + null); + } + } + + private static void testCompare(Version v0, Version v1, int compare) { + try { + Method m = VERSION.getMethod("compareTo", VERSION); + int cmp = (int) m.invoke(v0, v1); + checkCompare(v0, v1, compare, cmp); + } catch (IllegalAccessException | InvocationTargetException | + NoSuchMethodException ex) { + fail(String.format("compareTo() invocation: %s", ex.getClass()), + null); + } + } + + private static void checkCompare(Version v0, Version v1, + int compare, int cmp) + { + if (((cmp == 0) && (compare == 0)) + || (compare == (cmp / Math.abs(cmp == 0 ? 1 : cmp)))) { + pass(); + } else { + fail(String.format("compare() (cmp = %s) (compare = %s)", + cmp, compare), + v0.toString(), v1.toString()); + } + } + + private static int fail = 0; + private static int pass = 0; + + private static Throwable first; + + static void pass() { + pass++; + } + + static void fail(String fs, Class ex) { + String s = "'" + fs + "'"; + if (ex != null) + s += ": " + ex.getName() + " not thrown"; + if (first == null) + setFirst(s); + System.err.println("FAILED: " + s); + fail++; + } + + static void fail(String t, String exp, String got) { + String s = t + ": Expected '" + exp + "', got '" + got + "'"; + if (first == null) + setFirst(s); + System.err.println("FAILED: " + s); + fail++; + } + + private static void setFirst(String s) { + try { + throw new RuntimeException(s); + } catch (RuntimeException x) { + first = x; + } + } +}