From 72076e59257da72f962d4101d87ff5507da28e4f Mon Sep 17 00:00:00 2001 From: Pavel Petlinsky Date: Fri, 8 Jul 2016 15:37:21 +0300 Subject: SI-9750 scala.util.Properties.isJavaAtLeast works with JDK9 The utility method compares javaSpecVersion, which has the form "1.8" previously and "9" going forward. The method accepts "1.n" for n < 9. More correctly, the string argument should be a single number. Supports JEP-223. --- src/library/scala/util/Properties.scala | 26 ++++++++------ test/junit/scala/util/SpecVersionTest.scala | 56 ++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/src/library/scala/util/Properties.scala b/src/library/scala/util/Properties.scala index a176748cd6..6995f452fa 100644 --- a/src/library/scala/util/Properties.scala +++ b/src/library/scala/util/Properties.scala @@ -168,27 +168,31 @@ private[scala] trait PropertiesTrait { /** Compares the given specification version to the specification version of the platform. * - * @param version a specification version of the form "major.minor" + * @param version a specification version number (legacy forms acceptable) * @return `true` iff the specification version of the current runtime * is equal to or higher than the version denoted by the given string. * @throws NumberFormatException if the given string is not a version string * * @example {{{ - * // In this example, the runtime's Java specification is assumed to be at version 1.7. + * // In this example, the runtime's Java specification is assumed to be at version 8. * isJavaAtLeast("1.6") // true - * isJavaAtLeast("1.7") // true - * isJavaAtLeast("1.8") // false + * isJavaAtLeast("1.8") // true + * isJavaAtLeast("8") // true + * isJavaAtLeast("9") // false + * isJavaAtLeast("1.9") // throws * }}} */ def isJavaAtLeast(version: String): Boolean = { - def parts(x: String) = { - val i = x.indexOf('.') - if (i < 0) throw new NumberFormatException("Not a version: " + x) - (x.substring(0, i), x.substring(i+1, x.length)) + def versionOf(s: String): Int = s.indexOf('.') match { + case 1 if s.charAt(0) == '1' => + val v = versionOf(s.substring(2)) + if (v < 9) v else -1 + case -1 => s.toInt + case _ => -1 } - val (v, _v) = parts(version) - val (s, _s) = parts(javaSpecVersion) - s.toInt >= v.toInt && _s.toInt >= _v.toInt + val v = versionOf(version) + if (v < 0) throw new NumberFormatException(s"Not a version: $version") + versionOf(javaSpecVersion) >= v } // provide a main method so version info can be obtained by running this diff --git a/test/junit/scala/util/SpecVersionTest.scala b/test/junit/scala/util/SpecVersionTest.scala index e3e7a978f2..2b69f288fa 100644 --- a/test/junit/scala/util/SpecVersionTest.scala +++ b/test/junit/scala/util/SpecVersionTest.scala @@ -6,13 +6,16 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import scala.tools.testing.AssertUtil._ + /** The java version property uses the spec version - * and must work for all "major.minor" and fail otherwise. + * and must work for legacy "major.minor" and plain version_number, + * and fail otherwise. */ @RunWith(classOf[JUnit4]) class SpecVersionTest { - val sut = new PropertiesTrait { - override def javaSpecVersion = "1.7" + class TestProperties(versionAt: String) extends PropertiesTrait { + override def javaSpecVersion = versionAt override protected def pickJarBasedOn: Class[_] = ??? override protected def propCategory: String = "test" @@ -20,38 +23,57 @@ class SpecVersionTest { // override because of vals like releaseVersion override lazy val scalaProps = new java.util.Properties } + val sut7 = new TestProperties("1.7") + val sut9 = new TestProperties("9") + + @Test + def comparesJDK9Correctly(): Unit = { + assert(sut9 isJavaAtLeast "1") + assert(sut9 isJavaAtLeast "1.5") + assert(sut9 isJavaAtLeast "5") + assert(sut9 isJavaAtLeast "1.8") + assert(sut9 isJavaAtLeast "8") + assert(sut9 isJavaAtLeast "9") + } // SI-7265 @Test def comparesCorrectly(): Unit = { - assert(sut isJavaAtLeast "1.5") - assert(sut isJavaAtLeast "1.6") - assert(sut isJavaAtLeast "1.7") - assert(!(sut isJavaAtLeast "1.8")) - assert(!(sut isJavaAtLeast "1.71")) + assert(sut7 isJavaAtLeast "1") + assert(sut7 isJavaAtLeast "1.5") + assert(sut7 isJavaAtLeast "5") + assert(sut7 isJavaAtLeast "1.6") + assert(sut7 isJavaAtLeast "1.7") + assertFalse(sut7 isJavaAtLeast "1.8") + assertFalse(sut7 isJavaAtLeast "9") + assertFalse(sut7 isJavaAtLeast "10") } - @Test(expected = classOf[NumberFormatException]) - def badVersion(): Unit = { - sut isJavaAtLeast "1.a" + + @Test def variousBadVersionStrings(): Unit = { + assertThrows[NumberFormatException] { sut7 isJavaAtLeast "1.9" } + assertThrows[NumberFormatException] { sut9 isJavaAtLeast "1.9" } + assertThrows[NumberFormatException] { sut7 isJavaAtLeast "9.1" } + assertThrows[NumberFormatException] { sut9 isJavaAtLeast "9.1" } } + @Test(expected = classOf[NumberFormatException]) - def missingVersion(): Unit = { - sut isJavaAtLeast "1" + def badVersion(): Unit = { + sut7 isJavaAtLeast "1.a" } @Test(expected = classOf[NumberFormatException]) def noVersion(): Unit = { - sut isJavaAtLeast "" + sut7 isJavaAtLeast "" } @Test(expected = classOf[NumberFormatException]) def dotOnly(): Unit = { - sut isJavaAtLeast "." + sut7 isJavaAtLeast "." } @Test(expected = classOf[NumberFormatException]) def leadingDot(): Unit = { - sut isJavaAtLeast ".5" + sut7 isJavaAtLeast ".5" } @Test(expected = classOf[NumberFormatException]) def notASpec(): Unit = { - sut isJavaAtLeast "1.7.1" + sut7 isJavaAtLeast "1.7.1" } } -- cgit v1.2.3 From 9ac26c4626f906f4b561ec1fe9c308a0cf905608 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 15 Jul 2016 19:52:57 -0700 Subject: SI-9750 Tweak tests for what is a number Leaves the error string as is, but adds test to show how it looks. Java calls it a version number. `Not a version: 1.9`. Don't strip `1.` prefix recursively. (That was Snytt's fault.) --- src/library/scala/util/Properties.scala | 2 +- test/junit/scala/util/SpecVersionTest.scala | 25 ++++--------------------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/library/scala/util/Properties.scala b/src/library/scala/util/Properties.scala index 6995f452fa..fb28132dfe 100644 --- a/src/library/scala/util/Properties.scala +++ b/src/library/scala/util/Properties.scala @@ -185,7 +185,7 @@ private[scala] trait PropertiesTrait { def isJavaAtLeast(version: String): Boolean = { def versionOf(s: String): Int = s.indexOf('.') match { case 1 if s.charAt(0) == '1' => - val v = versionOf(s.substring(2)) + val v = s.substring(2).toInt if (v < 9) v else -1 case -1 => s.toInt case _ => -1 diff --git a/test/junit/scala/util/SpecVersionTest.scala b/test/junit/scala/util/SpecVersionTest.scala index 2b69f288fa..4c16ff08fb 100644 --- a/test/junit/scala/util/SpecVersionTest.scala +++ b/test/junit/scala/util/SpecVersionTest.scala @@ -50,30 +50,13 @@ class SpecVersionTest { } @Test def variousBadVersionStrings(): Unit = { - assertThrows[NumberFormatException] { sut7 isJavaAtLeast "1.9" } + assertThrows[NumberFormatException](sut7.isJavaAtLeast("1.9"), _ == "Not a version: 1.9") assertThrows[NumberFormatException] { sut9 isJavaAtLeast "1.9" } assertThrows[NumberFormatException] { sut7 isJavaAtLeast "9.1" } assertThrows[NumberFormatException] { sut9 isJavaAtLeast "9.1" } - } - @Test(expected = classOf[NumberFormatException]) - def badVersion(): Unit = { - sut7 isJavaAtLeast "1.a" - } - @Test(expected = classOf[NumberFormatException]) - def noVersion(): Unit = { - sut7 isJavaAtLeast "" - } - @Test(expected = classOf[NumberFormatException]) - def dotOnly(): Unit = { - sut7 isJavaAtLeast "." - } - @Test(expected = classOf[NumberFormatException]) - def leadingDot(): Unit = { - sut7 isJavaAtLeast ".5" - } - @Test(expected = classOf[NumberFormatException]) - def notASpec(): Unit = { - sut7 isJavaAtLeast "1.7.1" + val badvs = List("1.1.8", "1.", "1.a", "", ".", ".5", "1.7.1") + + for (v <- badvs) assertThrows[NumberFormatException](sut7.isJavaAtLeast(v)) } } -- cgit v1.2.3 From 656162bb48fbbd703790a2c94d4563e40ddfdfc2 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 15 Jul 2016 21:47:53 -0700 Subject: SI-9750 isJavaAtLeast(Int) A good opportunity to simplify the API. Versions are strings, but a spec version is just a number. --- src/library/scala/util/Properties.scala | 2 ++ test/junit/scala/util/SpecVersionTest.scala | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/library/scala/util/Properties.scala b/src/library/scala/util/Properties.scala index fb28132dfe..1bdf50bac2 100644 --- a/src/library/scala/util/Properties.scala +++ b/src/library/scala/util/Properties.scala @@ -195,6 +195,8 @@ private[scala] trait PropertiesTrait { versionOf(javaSpecVersion) >= v } + def isJavaAtLeast(version: Int): Boolean = isJavaAtLeast(version.toString) + // provide a main method so version info can be obtained by running this def main(args: Array[String]) { val writer = new PrintWriter(Console.err, true) diff --git a/test/junit/scala/util/SpecVersionTest.scala b/test/junit/scala/util/SpecVersionTest.scala index 4c16ff08fb..9232c4721b 100644 --- a/test/junit/scala/util/SpecVersionTest.scala +++ b/test/junit/scala/util/SpecVersionTest.scala @@ -23,33 +23,38 @@ class SpecVersionTest { // override because of vals like releaseVersion override lazy val scalaProps = new java.util.Properties } - val sut7 = new TestProperties("1.7") - val sut9 = new TestProperties("9") @Test def comparesJDK9Correctly(): Unit = { + val sut9 = new TestProperties("9") assert(sut9 isJavaAtLeast "1") assert(sut9 isJavaAtLeast "1.5") assert(sut9 isJavaAtLeast "5") assert(sut9 isJavaAtLeast "1.8") assert(sut9 isJavaAtLeast "8") assert(sut9 isJavaAtLeast "9") + assert(sut9.isJavaAtLeast(9)) } // SI-7265 @Test def comparesCorrectly(): Unit = { + val sut7 = new TestProperties("1.7") assert(sut7 isJavaAtLeast "1") assert(sut7 isJavaAtLeast "1.5") assert(sut7 isJavaAtLeast "5") assert(sut7 isJavaAtLeast "1.6") assert(sut7 isJavaAtLeast "1.7") + assert(sut7.isJavaAtLeast(7)) + assertFalse(sut7.isJavaAtLeast(9)) assertFalse(sut7 isJavaAtLeast "1.8") assertFalse(sut7 isJavaAtLeast "9") assertFalse(sut7 isJavaAtLeast "10") } @Test def variousBadVersionStrings(): Unit = { + val sut7 = new TestProperties("1.7") + val sut9 = new TestProperties("9") assertThrows[NumberFormatException](sut7.isJavaAtLeast("1.9"), _ == "Not a version: 1.9") assertThrows[NumberFormatException] { sut9 isJavaAtLeast "1.9" } assertThrows[NumberFormatException] { sut7 isJavaAtLeast "9.1" } -- cgit v1.2.3 From 06f8b6244ae8e80152f25a81cc2b92afd14c62f4 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 21 Jul 2016 06:22:48 -0700 Subject: SI-9750 Spec check major.minor.security Don't assume spec is just major, but allow arbitrary version number for both spec value and user value to check. Only the first three dot-separated fields are considered, after skipping optional leading value "1" in legacy format. Minimal validity checks of user arg are applied. Leading three fields, if present, must be number values, but subsequent fields are ignored. Note that a version number is not a version string, which optionally includes pre and build info, `9-ea+109`. --- src/library/scala/util/Properties.scala | 68 ++++++++++++++++++++--------- test/junit/scala/util/SpecVersionTest.scala | 52 ++++++++++++++++++---- 2 files changed, 91 insertions(+), 29 deletions(-) diff --git a/src/library/scala/util/Properties.scala b/src/library/scala/util/Properties.scala index 1bdf50bac2..8722294dde 100644 --- a/src/library/scala/util/Properties.scala +++ b/src/library/scala/util/Properties.scala @@ -168,33 +168,59 @@ private[scala] trait PropertiesTrait { /** Compares the given specification version to the specification version of the platform. * - * @param version a specification version number (legacy forms acceptable) - * @return `true` iff the specification version of the current runtime - * is equal to or higher than the version denoted by the given string. - * @throws NumberFormatException if the given string is not a version string + * @param version a specification version number (legacy forms acceptable) + * @return `true` if the specification version of the current runtime + * is equal to or higher than the version denoted by the given string. + * @throws NumberFormatException if the given string is not a version string * - * @example {{{ - * // In this example, the runtime's Java specification is assumed to be at version 8. - * isJavaAtLeast("1.6") // true - * isJavaAtLeast("1.8") // true - * isJavaAtLeast("8") // true - * isJavaAtLeast("9") // false - * isJavaAtLeast("1.9") // throws - * }}} + * @example {{{ + * // In this example, the runtime's Java specification is assumed to be at version 8. + * isJavaAtLeast("1.8") // true + * isJavaAtLeast("8") // true + * isJavaAtLeast("9") // false + * isJavaAtLeast("9.1") // false + * isJavaAtLeast("1.9") // throws + * }}} */ def isJavaAtLeast(version: String): Boolean = { - def versionOf(s: String): Int = s.indexOf('.') match { - case 1 if s.charAt(0) == '1' => - val v = s.substring(2).toInt - if (v < 9) v else -1 - case -1 => s.toInt - case _ => -1 + def versionOf(s: String, depth: Int): (Int, String) = + s.indexOf('.') match { + case 0 => + (-2, s.substring(1)) + case 1 if depth == 0 && s.charAt(0) == '1' => + val r0 = s.substring(2) + val (v, r) = versionOf(r0, 1) + val n = if (v > 8 || r0.isEmpty) -2 else v // accept 1.8, not 1.9 or 1. + (n, r) + case -1 => + val n = if (!s.isEmpty) s.toInt else if (depth == 0) -2 else 0 + (n, "") + case i => + val r = s.substring(i + 1) + val n = if (depth < 2 && r.isEmpty) -2 else s.substring(0, i).toInt + (n, r) + } + def compareVersions(s: String, v: String, depth: Int): Int = { + if (depth >= 3) 0 + else { + val (sn, srest) = versionOf(s, depth) + val (vn, vrest) = versionOf(v, depth) + if (vn < 0) -2 + else if (sn < vn) -1 + else if (sn > vn) 1 + else compareVersions(srest, vrest, depth + 1) + } + } + compareVersions(javaSpecVersion, version, 0) match { + case -2 => throw new NumberFormatException(s"Not a version: $version") + case i => i >= 0 } - val v = versionOf(version) - if (v < 0) throw new NumberFormatException(s"Not a version: $version") - versionOf(javaSpecVersion) >= v } + /** Tests whether the major version of the platform specification is at least the given value. + * + * @param version a major version number + */ def isJavaAtLeast(version: Int): Boolean = isJavaAtLeast(version.toString) // provide a main method so version info can be obtained by running this diff --git a/test/junit/scala/util/SpecVersionTest.scala b/test/junit/scala/util/SpecVersionTest.scala index 9232c4721b..4639389dd9 100644 --- a/test/junit/scala/util/SpecVersionTest.scala +++ b/test/junit/scala/util/SpecVersionTest.scala @@ -34,6 +34,8 @@ class SpecVersionTest { assert(sut9 isJavaAtLeast "8") assert(sut9 isJavaAtLeast "9") assert(sut9.isJavaAtLeast(9)) + assertFalse(sut9.isJavaAtLeast(10)) + assertFalse(sut9.isJavaAtLeast("10")) } // SI-7265 @@ -53,15 +55,49 @@ class SpecVersionTest { } @Test def variousBadVersionStrings(): Unit = { - val sut7 = new TestProperties("1.7") - val sut9 = new TestProperties("9") - assertThrows[NumberFormatException](sut7.isJavaAtLeast("1.9"), _ == "Not a version: 1.9") - assertThrows[NumberFormatException] { sut9 isJavaAtLeast "1.9" } - assertThrows[NumberFormatException] { sut7 isJavaAtLeast "9.1" } - assertThrows[NumberFormatException] { sut9 isJavaAtLeast "9.1" } + val sut = new TestProperties("9") + assertThrows[NumberFormatException](sut.isJavaAtLeast("1.9"), _ == "Not a version: 1.9") + assertThrows[NumberFormatException](sut.isJavaAtLeast("1.")) + assertThrows[NumberFormatException](sut.isJavaAtLeast("1.8.")) + assertThrows[NumberFormatException](sut.isJavaAtLeast("1.a")) + assertThrows[NumberFormatException](sut.isJavaAtLeast("")) + assertThrows[NumberFormatException](sut.isJavaAtLeast(".")) + assertThrows[NumberFormatException](sut.isJavaAtLeast("..")) + assertThrows[NumberFormatException](sut.isJavaAtLeast(".5")) + assertThrows[NumberFormatException](sut.isJavaAtLeast("9-ea")) //version number, not version string + } - val badvs = List("1.1.8", "1.", "1.a", "", ".", ".5", "1.7.1") + @Test def `spec has minor or more`(): Unit = { + val sut = new TestProperties("9.2.5") + assert(sut.isJavaAtLeast(9)) + assert(sut.isJavaAtLeast("9")) + assert(sut.isJavaAtLeast("9.0.1")) + assert(sut.isJavaAtLeast("9.2.1")) + assert(sut.isJavaAtLeast("8.3.1")) + assert(sut.isJavaAtLeast("8.3.1.1.1")) + assertFalse(sut.isJavaAtLeast("9.3.1")) + assertFalse(sut.isJavaAtLeast("10.3.1")) + } + + @Test def `compares only major minor security`(): Unit = { + val sut = new TestProperties("9.2.5.1.2.3") + assert(sut.isJavaAtLeast(9)) + assert(sut.isJavaAtLeast("9")) + assert(sut.isJavaAtLeast("9.0.1")) + assert(sut.isJavaAtLeast("9.2.5.9.9.9")) + assertFalse(sut.isJavaAtLeast("9.2.6")) + } - for (v <- badvs) assertThrows[NumberFormatException](sut7.isJavaAtLeast(v)) + @Test def `futurely proofed`(): Unit = { + val sut = new TestProperties("10.2.5") + assert(sut.isJavaAtLeast(9)) + assert(sut.isJavaAtLeast(10)) + assert(sut.isJavaAtLeast("9")) + assert(sut.isJavaAtLeast("9.0.1")) + assert(sut.isJavaAtLeast("9.2.1")) + assert(sut.isJavaAtLeast("8.3.1")) + assert(sut.isJavaAtLeast("8.3.1.1.1")) + assert(sut.isJavaAtLeast("9.3.1")) + assertFalse(sut.isJavaAtLeast("10.3.1")) } } -- cgit v1.2.3 From 10336958aba9b8af5f9127a4dc21c0899836ff8d Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 29 Jul 2016 23:51:27 -0700 Subject: SI-9750 Remove isJavaAtLeast from util.StackTracing Formatting suppressed exceptions required reflection for platform compatibility. No longer, since Java 8 is assumed. Minor tidying. --- .../scala/tools/nsc/util/StackTracing.scala | 44 +++++++++------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/src/compiler/scala/tools/nsc/util/StackTracing.scala b/src/compiler/scala/tools/nsc/util/StackTracing.scala index 0765bb923f..c6749a13f3 100644 --- a/src/compiler/scala/tools/nsc/util/StackTracing.scala +++ b/src/compiler/scala/tools/nsc/util/StackTracing.scala @@ -8,7 +8,7 @@ private[util] trait StackTracing extends Any { /** Format a stack trace, returning the prefix consisting of frames that satisfy * a given predicate. - * The format is similar to the typical case described in the JavaDoc + * The format is similar to the typical case described in the Javadoc * for [[java.lang.Throwable#printStackTrace]]. * If a stack trace is truncated, it will be followed by a line of the form * `... 3 elided`, by analogy to the lines `... 3 more` which indicate @@ -19,25 +19,18 @@ private[util] trait StackTracing extends Any { def stackTracePrefixString(e: Throwable)(p: StackTraceElement => Boolean): String = { import collection.mutable.{ ArrayBuffer, ListBuffer } import compat.Platform.EOL - import scala.util.Properties.isJavaAtLeast - - val sb = ListBuffer.empty[String] type TraceRelation = String val Self = new TraceRelation("") val CausedBy = new TraceRelation("Caused by: ") val Suppressed = new TraceRelation("Suppressed: ") - val suppressable = isJavaAtLeast("1.7") - - def clazz(e: Throwable) = e.getClass.getName + def clazz(e: Throwable): String = e.getClass.getName def because(e: Throwable): String = e.getCause match { case null => null ; case c => header(c) } def msg(e: Throwable): String = e.getMessage match { case null => because(e) ; case s => s } def txt(e: Throwable): String = msg(e) match { case null => "" ; case s => s": $s" } def header(e: Throwable): String = s"${clazz(e)}${txt(e)}" - val indent = "\u0020\u0020" - val seen = new ArrayBuffer[Throwable](16) def unseen(t: Throwable) = { def inSeen = seen exists (_ eq t) @@ -46,28 +39,25 @@ private[util] trait StackTracing extends Any { interesting } + val sb = ListBuffer.empty[String] + + // format the stack trace, skipping the shared trace def print(e: Throwable, r: TraceRelation, share: Array[StackTraceElement], indents: Int): Unit = if (unseen(e)) { val trace = e.getStackTrace - val frames = ( - if (share.nonEmpty) { - val spare = share.reverseIterator - val trimmed = trace.reverse dropWhile (spare.hasNext && spare.next == _) - trimmed.reverse - } else trace - ) - val prefix = frames takeWhile p - val margin = indent * indents - val indented = margin + indent + val frames = if (share.isEmpty) trace else { + val spare = share.reverseIterator + val trimmed = trace.reverse dropWhile (spare.hasNext && spare.next == _) + trimmed.reverse + } + val prefix = frames takeWhile p + val margin = " " * indents + val indent = margin + " " sb append s"${margin}${r}${header(e)}" - prefix foreach (f => sb append s"${indented}at $f") - if (frames.size < trace.size) sb append s"$indented... ${trace.size - frames.size} more" - if (r == Self && prefix.size < frames.size) sb append s"$indented... ${frames.size - prefix.size} elided" + prefix foreach (f => sb append s"${margin} at $f") + if (frames.size < trace.size) sb append s"${margin} ... ${trace.size - frames.size} more" + if (r == Self && prefix.size < frames.size) sb append s"${margin} ... ${frames.size - prefix.size} elided" print(e.getCause, CausedBy, trace, indents) - if (suppressable) { - import scala.language.reflectiveCalls - type Suppressing = { def getSuppressed(): Array[Throwable] } - for (s <- e.asInstanceOf[Suppressing].getSuppressed) print(s, Suppressed, frames, indents + 1) - } + e.getSuppressed foreach (t => print(t, Suppressed, frames, indents + 1)) } print(e, Self, share = Array.empty, indents = 0) -- cgit v1.2.3