This is an automated email from the ASF dual-hosted git repository.

mbeckerle pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git


The following commit(s) were added to refs/heads/main by this push:
     new 48b3f70c8 Changes for Java 21 - a LTS Java release
48b3f70c8 is described below

commit 48b3f70c8d185afde42f37b2deb5e44ad4ee5525
Author: Michael Beckerle <[email protected]>
AuthorDate: Sat Sep 23 14:03:38 2023 -0400

    Changes for Java 21 - a LTS Java release
    
    Required changes due to deprecated objects and methods.
    java.net.URL all constructors were deprecated.
    
    This was harder than one would expect. There is no direct replacement
    in the java.net library for the URL constructors. Nothing tolerates
    jar-file URIs as of Java 20+.
    
    Add xsi:type sensitive comparison for float and double.
    
    Tolerate regex match behavior change: seems lookahead is no longer
    captured into individual groups.
    
    Now uses Java 11 release for Java code compilation with Java 21.
    This eliminates warnings about Java 8 being deprecated.
    
    Removed redundancy of XMLUtils and NodeInfo for converting strings to 
float/double.
    
    DAFFODIL-2757, DAFFODIL-2402
---
 build.sbt                                          |  32 +++--
 .../main/scala/org/apache/daffodil/cli/Main.scala  |   8 +-
 .../apache/daffodil/core/api/TestForHeapDump.scala |   2 -
 .../apache/daffodil/layers/TestJavaIOStreams.scala |  12 +-
 .../scala/org/apache/daffodil/lib/util/Misc.scala  | 118 ++++++++++++-----
 .../org/apache/daffodil/lib/xml/XMLUtils.scala     | 142 ++++++++++++++++-----
 .../daffodil/lib/xml/test/unit/TestXMLUtils.scala  |  52 +++++++-
 .../apache/daffodil/runtime1/dpath/NodeInfo.scala  |  16 +--
 .../test-suite/tresys-contributed/BG.tdml          |  25 ++--
 .../section05/simple_types/SimpleTypes.tdml        |   2 +-
 .../text_number_props/TextNumberProps.tdml         |   4 +-
 11 files changed, 297 insertions(+), 116 deletions(-)

diff --git a/build.sbt b/build.sbt
index 9809bd5a5..1c01d8b7f 100644
--- a/build.sbt
+++ b/build.sbt
@@ -211,6 +211,12 @@ lazy val testStdLayout = 
Project("daffodil-test-stdLayout", file("test-stdLayout
   .dependsOn(tdmlProc % "test")
   .settings(commonSettings, nopublish)
 
+// Choices here are Java LTS versions, 8, 11, 17, 21,...
+// However 8 is deprecated as of Java 21, so will be phased out.
+val minSupportedJavaVersion: String =
+  if (scala.util.Properties.isJavaAtLeast("21")) "11"
+  else "8"
+
 lazy val commonSettings = Seq(
   organization := "org.apache.daffodil",
   version := "3.6.0-SNAPSHOT",
@@ -245,7 +251,7 @@ lazy val commonSettings = Seq(
 
 def buildScalacOptions(scalaVersion: String) = {
   val commonOptions = Seq(
-    "-target:jvm-1.8",
+    s"-release:$minSupportedJavaVersion", // scala 2.12 can only do Java 8, 
regardless of this setting.
     "-feature",
     "-deprecation",
     "-language:experimental.macros",
@@ -268,13 +274,19 @@ def buildScalacOptions(scalaVersion: String) = {
     case _ => Seq.empty
   }
 
-  val javaVersionSpecificOptions =
-    if (scala.util.Properties.isJavaAtLeast("9"))
-      Seq("-release", "8") // ensure Java backwards compatibility 
(DAFFODIL-2579)
-    else
-      Seq.empty
+  commonOptions ++ scalaVersionSpecificOptions
+}
+
+val javaVersionSpecificOptions = {
+  val releaseOption = // as of Java 11, they no longer accept "-release". Must 
use "--release".
+    if (scala.util.Properties.isJavaAtLeast("11")) "--release" else "-release"
 
-  commonOptions ++ scalaVersionSpecificOptions ++ javaVersionSpecificOptions
+  // Java 21 deprecates Java 8 and warns about it.
+  // So if you are using Java 21, Java code compilation will specify a newer 
Java version
+  // to avoid warnings.
+  if (scala.util.Properties.isJavaAtLeast("11")) Seq(releaseOption, 
minSupportedJavaVersion)
+  else if (scala.util.Properties.isJavaAtLeast("9")) Seq(releaseOption, "8")
+  else Nil // for Java 8 compilation
 }
 
 // Workaround issue that some options are valid for javac, not javadoc.
@@ -285,12 +297,6 @@ def buildJavacOptions() = {
     "-Xlint:deprecation",
   )
 
-  val javaVersionSpecificOptions =
-    if (scala.util.Properties.isJavaAtLeast("9"))
-      Seq("--release", "8") // ensure Java backwards compatibility 
(DAFFODIL-2579)
-    else
-      Seq.empty
-
   commonOptions ++ javaVersionSpecificOptions
 }
 
diff --git a/daffodil-cli/src/main/scala/org/apache/daffodil/cli/Main.scala 
b/daffodil-cli/src/main/scala/org/apache/daffodil/cli/Main.scala
index 80409ac30..5ecc7c3d9 100644
--- a/daffodil-cli/src/main/scala/org/apache/daffodil/cli/Main.scala
+++ b/daffodil-cli/src/main/scala/org/apache/daffodil/cli/Main.scala
@@ -28,6 +28,7 @@ import java.nio.channels.Channels
 import java.nio.file.Paths
 import java.util.Scanner
 import java.util.concurrent.Executors
+import javax.xml.parsers.SAXParserFactory
 import javax.xml.transform.TransformerException
 import javax.xml.transform.TransformerFactory
 import javax.xml.transform.stream.StreamResult
@@ -92,7 +93,6 @@ import org.rogach.scallop.exceptions.GenericScallopException
 import org.slf4j.event.Level
 import org.xml.sax.InputSource
 import org.xml.sax.SAXParseException
-import org.xml.sax.helpers.XMLReaderFactory
 
 class ScallopExitException(val exitCode: Int) extends Exception
 
@@ -1813,8 +1813,10 @@ class Main(
           case (false, true) => { // Encoding
             val exiResult = new EXIResult(exiFactory.get)
             exiResult.setOutputStream(output)
-
-            val reader = XMLReaderFactory.createXMLReader()
+            val factory = SAXParserFactory.newInstance()
+            factory.setNamespaceAware(true)
+            val saxParser = factory.newSAXParser()
+            val reader = saxParser.getXMLReader
             reader.setContentHandler(exiResult.getHandler)
             reader.setErrorHandler(new EXIErrorHandler)
             try {
diff --git 
a/daffodil-core/src/test/scala/org/apache/daffodil/core/api/TestForHeapDump.scala
 
b/daffodil-core/src/test/scala/org/apache/daffodil/core/api/TestForHeapDump.scala
index e0ddf4f59..c9868234b 100644
--- 
a/daffodil-core/src/test/scala/org/apache/daffodil/core/api/TestForHeapDump.scala
+++ 
b/daffodil-core/src/test/scala/org/apache/daffodil/core/api/TestForHeapDump.scala
@@ -150,8 +150,6 @@ class TestForHeapDump {
   }
 
   def gcAndAllowHeapDump(): Unit = {
-    System.gc()
-    System.runFinalization()
     System.gc()
     System.out.println("Take a Heap Dump Now! (You have 10 seconds)")
     Thread.sleep(10000)
diff --git 
a/daffodil-io/src/test/scala/org/apache/daffodil/layers/TestJavaIOStreams.scala 
b/daffodil-io/src/test/scala/org/apache/daffodil/layers/TestJavaIOStreams.scala
index 0b9a277b7..eba14b049 100644
--- 
a/daffodil-io/src/test/scala/org/apache/daffodil/layers/TestJavaIOStreams.scala
+++ 
b/daffodil-io/src/test/scala/org/apache/daffodil/layers/TestJavaIOStreams.scala
@@ -129,10 +129,16 @@ ZyBzb2x1dGlvbnMuCg=="""
     val scanner = new Scanner(is, StandardCharsets.ISO_8859_1.name())
     is.skip(3)
     is.mark(2)
-    val matchString = scanner.findWithinHorizon("(.*?)(?=(\\Q;\\E))", 2)
+    // Prior to changes for Java21, this test used non-capturing lookahead for 
the ";"
+    // and group(2) was containing the match of the lookahead.
+    // as of Java21 testing, the lookahead match is no longer made into a 
group it seems.
+    // The lookahead is included in the "whole match" aka group(0)
+    val matchString = scanner.findWithinHorizon("(.*?)(\\Q;\\E)", 2)
     is.reset()
-    assertEquals("l", matchString)
-    assertEquals(";", scanner.`match`().group(2))
+    val m = scanner.`match`()
+    assertEquals("l;", m.group(0))
+    assertEquals("l", m.group(1))
+    assertEquals(";", m.group(2))
   }
 
   /**
diff --git 
a/daffodil-lib/src/main/scala/org/apache/daffodil/lib/util/Misc.scala 
b/daffodil-lib/src/main/scala/org/apache/daffodil/lib/util/Misc.scala
index 11ef999d5..70103e6f0 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/lib/util/Misc.scala
+++ b/daffodil-lib/src/main/scala/org/apache/daffodil/lib/util/Misc.scala
@@ -20,9 +20,8 @@ package org.apache.daffodil.lib.util
 import java.io.ByteArrayInputStream
 import java.io.ByteArrayOutputStream
 import java.io.File
-import java.io.InputStream
+import java.io.IOException
 import java.net.URI
-import java.net.URL
 import java.net.URLClassLoader
 import java.nio.ByteBuffer
 import java.nio.CharBuffer
@@ -103,38 +102,102 @@ object Misc {
     // more time is wasted by people forgetting that the initial "/" is needed
     // to get classpath relative behavior... Let's make sure there is a 
leading "/"
     val resPath = if (resourcePath.startsWith("/")) resourcePath else "/" + 
resourcePath
-    val res = this.getClass().getResource(resPath)
-    if (res == null) {
-      (None, resPath)
-    } else (Some(res.toURI), resPath)
+    val res = Option(this.getClass().getResource(resPath))
+    (res.map(_.toURI), resPath)
   }
 
   /**
    * Gets a resource on the classpath, or relative to another URI
    */
-  def getResourceRelativeOption(rawResName: String, optContextURI: 
Option[URI]): Option[URI] = {
+  private def getResourceAbsoluteOrRelativeOption(
+    rawResName: String,
+    optContextURI: Option[URI],
+  ): Option[URI] = {
     val resName = rawResName.replaceAll("""\s""", "%20")
     val (maybeRes, _) = Misc.getResourceOption(resName)
     if (maybeRes.isDefined) {
       maybeRes // found directly on the classpath.
     } else {
       optContextURI.flatMap { contextURI =>
-        //
-        // try relative to enclosing context uri
-        //
-        // Done using URL constructor because the URI.resolve(uri) method
-        // doesn't work against so called opaque URIs, and jar URIs of the
-        // sort we get here if the resource is in a jar, are opaque.
-        // Some discussion of this issue is 
https://issues.apache.org/jira/browse/XMLSCHEMA-3
-        //
-        val contextURL = contextURI.toURL
-        val completeURL = new URL(contextURL, resName)
-        val res = tryURL(completeURL)
-        res
+        getResourceRelativeOnlyOption(resName, contextURI)
       }
     }
   }
 
+  /**
+   * Get resource relative to the context URI.
+   *
+   * Does NOT try the string as an absolute location first
+   * or anything like that.
+   *
+   * @param relPath
+   * @param contextURI
+   * @return Some uri if the relative resource exists.
+   */
+  def getResourceRelativeOnlyOption(relPath: String, contextURI: URI): 
Option[URI] = {
+    Assert.usage(relPath ne null)
+    Assert.usage(contextURI ne null)
+    if (contextURI.isOpaque) {
+      //
+      // We used to call new URL(jarURI, relativePathString)
+      // but that is deprecated now (as of Java 20)
+      //
+      optRelativeJarFileURI(contextURI, relPath)
+    } else {
+      // context URI is not opaque. It's probably a file URI
+      if (contextURI.getScheme == "file") {
+        val relURI = contextURI.resolve(relPath)
+        if (Paths.get(relURI).toFile.exists())
+          Some(relURI)
+        else None
+      } else {
+        // not a file nor an opaque resource URI. What is it?
+        throw new IllegalArgumentException(s"Unrecognized URI type: 
$contextURI")
+      }
+    }
+  }
+
+  /**
+   * Java 20 deprecated the 2-arg URL constructor which worked to create 
relative URIs
+   * within the same Jar file.
+   *
+   * This is a bit harder to achieve now. You are not allowed to resolve 
relative to a jar file URI.
+   * That is URI.resolve(relPath) doesn't work if the URI is a jar file URI.
+   *
+   * Now we have to hack the jar:file: URI as a string because URI.resolve 
won't work
+   *
+   * jar file URIs look like this:
+   *
+   *    `jar:file:/..absolute path to jar file.jar!/absolute path from root 
inside jar to file``
+   *
+   * We split at the !/, make a relative path on just the inside-jar-file 
part, then glue
+   * back together.
+   *
+   *
+   * @param contextURI
+   * @param relPath
+   * @return Some(uri) for an existing relative path within the same jar file, 
or None if it does not exist.
+   */
+  def optRelativeJarFileURI(contextURI: URI, relPath: String): Option[URI] = {
+    val parts = contextURI.toString.split("\\!\\/")
+    Assert.invariant(parts.length == 2)
+    val jarPart = parts(0)
+    val pathPart = parts(1)
+    Assert.invariant(pathPart ne null)
+    val contextURIPathOnly = URI.create(pathPart)
+    val resolvedURIPathOnly = contextURIPathOnly.resolve(relPath)
+    val newJarPathURI = URI.create(jarPart + "!/" + 
resolvedURIPathOnly.toString)
+    try {
+      newJarPathURI.toURL.openStream().close()
+      // that worked, so we can open it so it exists.
+      Some(newJarPathURI)
+    } catch {
+      case io: IOException =>
+        // failed. So that jar file doesn't exist
+        None
+    }
+  }
+
   /**
    * Search for a resource name, trying a handful of heuristics.
    *
@@ -155,7 +218,7 @@ object Misc {
       if (resAsURI.getScheme != null) Paths.get(resAsURI) else 
Paths.get(resName)
     val resolvedURI =
       if (Files.exists(resPath)) Some(resPath.toFile().toURI())
-      else Misc.getResourceRelativeOption(resName, relativeTo)
+      else Misc.getResourceAbsoluteOrRelativeOption(resName, relativeTo)
     val res = resolvedURI.orElse {
       // try ignoring the directory part
       val parts = resName.split("/")
@@ -170,21 +233,6 @@ object Misc {
     res
   }
 
-  private def tryURL(url: URL): Option[URI] = {
-    var is: InputStream = null
-    val res =
-      try {
-        is = url.openStream()
-        // worked! We found it.
-        Some(url.toURI)
-      } catch {
-        case e: java.io.IOException => None
-      } finally {
-        if (is != null) is.close()
-      }
-    res
-  }
-
   lazy val classPath = {
     val cl = this.getClass().getClassLoader()
     val urls = cl match {
diff --git 
a/daffodil-lib/src/main/scala/org/apache/daffodil/lib/xml/XMLUtils.scala 
b/daffodil-lib/src/main/scala/org/apache/daffodil/lib/xml/XMLUtils.scala
index 0a1d88b61..176bbda56 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/lib/xml/XMLUtils.scala
+++ b/daffodil-lib/src/main/scala/org/apache/daffodil/lib/xml/XMLUtils.scala
@@ -21,7 +21,6 @@ import java.io.File
 import java.io.IOException
 import java.net.URI
 import java.net.URISyntaxException
-import java.net.URL
 import java.nio.charset.StandardCharsets
 import java.nio.file.Files
 import java.nio.file.Paths
@@ -30,6 +29,7 @@ import javax.xml.XMLConstants
 import scala.annotation.tailrec
 import scala.collection.mutable
 import scala.collection.mutable.ArrayBuilder
+import scala.math.abs
 import scala.util.matching.Regex
 import scala.xml.NamespaceBinding
 import scala.xml._
@@ -70,6 +70,34 @@ object XMLUtils {
   val NegativeInfinityString = "-INF"
   val NaNString = "NaN"
 
+  /**
+   * Converts a string to a float, including handling our INF, -INF, and NaN 
notations.
+   * @param s
+   * @return
+   */
+  def strToFloat(s: String): Float = {
+    s match {
+      case PositiveInfinityString => Float.PositiveInfinity
+      case NegativeInfinityString => Float.NegativeInfinity
+      case NaNString => Float.NaN
+      case _ => s.toFloat
+    }
+  }
+
+  /**
+   * Converts a string to a double, including handling our INF, -INF, and NaN 
notations.
+   * @param s
+   * @return
+   */
+  def strToDouble(s: String): Double = {
+    s match {
+      case PositiveInfinityString => Double.PositiveInfinity
+      case NegativeInfinityString => Double.NegativeInfinity
+      case NaNString => Double.NaN
+      case _ => s.toDouble
+    }
+  }
+
   /**
    * Length where a surrogate pair counts as 1 character, not two.
    */
@@ -778,6 +806,8 @@ object XMLUtils {
     ignoreProcInstr: Boolean = true,
     checkPrefixes: Boolean = false,
     checkNamespaces: Boolean = false,
+    maybeFloatEpsilon: Option[Float] = None,
+    maybeDoubleEpsilon: Option[Double] = None,
   ): Unit = {
     val expectedMinimized = normalize(expected)
     val actualMinimized = normalize(actual)
@@ -787,6 +817,8 @@ object XMLUtils {
       ignoreProcInstr,
       checkPrefixes,
       checkNamespaces,
+      maybeFloatEpsilon,
+      maybeDoubleEpsilon,
     )
     if (diffs.length > 0) {
       throw new XMLDifferenceException(
@@ -821,6 +853,8 @@ Differences were (path, expected, actual):
     ignoreProcInstr: Boolean = true,
     checkPrefixes: Boolean = false,
     checkNamespaces: Boolean = false,
+    maybeFloatEpsilon: Option[Float] = None,
+    maybeDoubleEpsilon: Option[Double] = None,
   ) = {
     computeDiffOne(
       a,
@@ -833,6 +867,8 @@ Differences were (path, expected, actual):
       checkPrefixes,
       checkNamespaces,
       None,
+      maybeFloatEpsilon,
+      maybeDoubleEpsilon,
     )
   }
 
@@ -850,6 +886,11 @@ Differences were (path, expected, actual):
     arrayCounters
   }
 
+  private def getXSIType(a: Elem) = {
+    val res = a.attribute(XSI_NAMESPACE.toString, "type").map(_.head.text)
+    res
+  }
+
   def computeDiffOne(
     an: Node,
     bn: Node,
@@ -861,14 +902,16 @@ Differences were (path, expected, actual):
     checkPrefixes: Boolean,
     checkNamespaces: Boolean,
     maybeType: Option[String],
+    maybeFloatEpsilon: Option[Float],
+    maybeDoubleEpsilon: Option[Double],
   ): Seq[(String, String, String)] = {
     lazy val zPath = parentPathSteps.reverse.mkString("/")
     (an, bn) match {
       case (a: Elem, b: Elem) => {
         val Elem(prefixA, labelA, attribsA, nsbA, childrenA @ _*) = a
         val Elem(prefixB, labelB, attribsB, nsbB, childrenB @ _*) = b
-        val typeA: Option[String] = a.attribute(XSI_NAMESPACE.toString, 
"type").map(_.head.text)
-        val typeB: Option[String] = b.attribute(XSI_NAMESPACE.toString, 
"type").map(_.head.text)
+        val typeA: Option[String] = getXSIType(a)
+        val typeB: Option[String] = getXSIType(b)
         val maybeType: Option[String] = 
Option(typeA.getOrElse(typeB.getOrElse(null)))
         val nilledA = a.attribute(XSI_NAMESPACE.toString, "nil")
         val nilledB = b.attribute(XSI_NAMESPACE.toString, "nil")
@@ -941,6 +984,8 @@ Differences were (path, expected, actual):
               checkPrefixes,
               checkNamespaces,
               maybeType,
+              maybeFloatEpsilon,
+              maybeDoubleEpsilon,
             )
           }
 
@@ -963,13 +1008,14 @@ Differences were (path, expected, actual):
         }
       }
       case (tA: Text, tB: Text) => {
-        val thisDiff = computeTextDiff(zPath, tA, tB, maybeType)
+        val thisDiff =
+          computeTextDiff(zPath, tA, tB, maybeType, maybeFloatEpsilon, 
maybeDoubleEpsilon)
         thisDiff
       }
       case (pA: ProcInstr, pB: ProcInstr) => {
         val ProcInstr(tA1label, tA1content) = pA
         val ProcInstr(tB1label, tB1content) = pB
-        val labelDiff = computeTextDiff(zPath, tA1label, tB1label, None)
+        val labelDiff = computeTextDiff(zPath, tA1label, tB1label, None, None, 
None)
         //
         // The content of a ProcInstr is technically a big string
         // But our usage of them the content is XML-like so could be loaded 
and then compared
@@ -981,7 +1027,7 @@ Differences were (path, expected, actual):
         //
         // TODO: implement XML-comparison for our data format info PIs.
         //
-        val contentDiff = computeTextDiff(zPath, tA1content, tB1content, 
maybeType)
+        val contentDiff = computeTextDiff(zPath, tA1content, tB1content, 
maybeType, None, None)
         labelDiff ++ contentDiff
       }
       case _ => {
@@ -995,11 +1041,13 @@ Differences were (path, expected, actual):
     tA: Text,
     tB: Text,
     maybeType: Option[String],
+    maybeFloatEpsilon: Option[Float],
+    maybeDoubleEpsilon: Option[Double],
   ): Seq[(String, String, String)] = {
 
     val dataA = tA.toString
     val dataB = tB.toString
-    computeTextDiff(zPath, dataA, dataB, maybeType)
+    computeTextDiff(zPath, dataA, dataB, maybeType, maybeFloatEpsilon, 
maybeDoubleEpsilon)
   }
 
   def computeBlobDiff(zPath: String, dataA: String, dataB: String) = {
@@ -1061,12 +1109,14 @@ Differences were (path, expected, actual):
     dataA: String,
     dataB: String,
     maybeType: Option[String],
+    maybeFloatEpsilon: Option[Float],
+    maybeDoubleEpsilon: Option[Double],
   ): Seq[(String, String, String)] = {
 
     val hasBlobType = maybeType.isDefined && maybeType.get == "xs:anyURI"
     val dataLooksLikeBlobURI = Seq(dataA, 
dataB).forall(_.startsWith("file://"))
     if (hasBlobType || dataLooksLikeBlobURI) computeBlobDiff(zPath, dataA, 
dataB)
-    else if (textIsSame(dataA, dataB, maybeType)) Nil
+    else if (textIsSame(dataA, dataB, maybeType, maybeFloatEpsilon, 
maybeDoubleEpsilon)) Nil
     else {
       // There must be some difference, so let's find just the first index of
       // difference and we'll include that and some following characters for
@@ -1095,7 +1145,30 @@ Differences were (path, expected, actual):
     }
   }
 
-  def textIsSame(dataA: String, dataB: String, maybeType: Option[String]): 
Boolean = {
+  /**
+   * Compares two strings of xml text, optionally using type information to 
tolerate insignificant differences, and
+   * optionally using a tolerance amount for floating point comparison.
+   *
+   * @param dataA string for first value in comparison
+   * @param dataB string for second value in comparison
+   * @param maybeType - type as a "xs:" prefixed QName string of an XSD type 
(Ex: as in the value of
+   *                  the xsi:type="xs:date" attribute.) Any non "xs:" 
prefixed type is ignored.
+   * @param maybeFloatEpsilon - for floating point comparison, a float (single 
precision) expressing the acceptable delta
+   *                     amount to proclaim equality.
+   * @param maybeDoubleEpsilon - for floating point comparison, a double 
(double precision) expressing the acceptable delta
+   *                          amount to proclaim equality.
+   * @return
+   */
+  def textIsSame(
+    dataA: String,
+    dataB: String,
+    maybeType: Option[String],
+    maybeFloatEpsilon: Option[Float],
+    maybeDoubleEpsilon: Option[Double],
+  ): Boolean = {
+    maybeFloatEpsilon.foreach { eps => Assert.usage(eps > 0.0) }
+    maybeDoubleEpsilon.foreach { eps => Assert.usage(eps > 0.0) }
+
     maybeType match {
       case Some("xs:hexBinary") => dataA.equalsIgnoreCase(dataB)
       case Some("xs:date") => {
@@ -1113,6 +1186,29 @@ Differences were (path, expected, actual):
         val b = DFDLDateTimeConversion.fromXMLString(dataB)
         a == b
       }
+      case Some("xs:double") => {
+        val a = strToDouble(dataA)
+        val b = strToDouble(dataB)
+        if (a.isNaN && b.isNaN) true // two NaNs are not normally considered 
equal
+        else {
+          maybeDoubleEpsilon match {
+            case None => a == b
+            case Some(epsilon) => abs(a - b) < epsilon
+          }
+        }
+      }
+      case Some("xs:float") => {
+        val a = strToFloat(dataA)
+        val b = strToFloat(dataB)
+        if (a.isNaN && b.isNaN) true // two NaNs are not normally considered 
equal
+        else {
+          maybeFloatEpsilon match {
+            case None => a == b
+            case Some(epsilon) => abs(a - b) < epsilon
+          }
+        }
+      }
+
       case _ => dataA == dataB
     }
   }
@@ -1347,7 +1443,7 @@ Differences were (path, expected, actual):
         uri.getFragment == null &&
         uri.getPath != null
 
-    val optResolved =
+    val optResolved: Option[(URI, Boolean)] =
       if (uri.isAbsolute) {
         // an absolute URI is one with a scheme. In this case, we expect to be 
able to resolve
         // the URI and do not try anything else (e.g. filesystem, classpath). 
Since this function
@@ -1392,20 +1488,8 @@ Differences were (path, expected, actual):
         val contextURI = optContextURI.get
         val optResolvedRelative = None
           .orElse {
-            // This is a relative path, so look up the schemaLocation path 
relative to the
-            // context. Note that URI.resolve does not support opaque URIs, 
and the context
-            // parameter is often an opaque "jar" URI if the context resolved 
to a file inside a
-            // jar on the classpath. Instead we can use the URL constructor to 
resolve the
-            // relative path, which does support opaque URIs for supported 
schemes (jar and
-            // file). Unfortunately, the only way to test for URL existence is 
to open a stream
-            // to that resulting URL and see if an exception is thrown or not
-            val resolvedURL = new URL(contextURI.toURL, uri.getPath)
-            try {
-              resolvedURL.openStream.close
-              Some((resolvedURL.toURI, false))
-            } catch {
-              case e: IOException => None
-            }
+            // This is a relative path, so look up the schemaLocation path 
relative to the context
+            Misc.getResourceRelativeOnlyOption(uri.getPath, contextURI).map { 
(_, false) }
           }
           .orElse {
             // The user might have meant an absolute schemaLocation but left 
off the leading
@@ -1413,12 +1497,10 @@ Differences were (path, expected, actual):
             // but return a boolean if a relative path was found absolutely so 
callers can warn
             // if needed. Future versions of Daffodil may want to remove this 
orElse block so we
             // are strict about how absolute vs relative schemaLocations are 
resolved.
-            val resource = this.getClass.getResource("/" + uri.getPath)
-            if (resource != null) {
-              Some((resource.toURI, true))
-            } else {
-              None
-            }
+            val pair = Option(this.getClass.getResource("/" + uri.getPath))
+              .map { _.toURI }
+              .map { (_, true) }
+            pair
           }
         optResolvedRelative
       }
diff --git 
a/daffodil-lib/src/test/scala/org/apache/daffodil/lib/xml/test/unit/TestXMLUtils.scala
 
b/daffodil-lib/src/test/scala/org/apache/daffodil/lib/xml/test/unit/TestXMLUtils.scala
index c833b52b4..f74f672f8 100644
--- 
a/daffodil-lib/src/test/scala/org/apache/daffodil/lib/xml/test/unit/TestXMLUtils.scala
+++ 
b/daffodil-lib/src/test/scala/org/apache/daffodil/lib/xml/test/unit/TestXMLUtils.scala
@@ -36,7 +36,7 @@ class TestXMLUtils {
   @Test def testDiff0(): Unit = {
     val d1 = new Text("a")
     val d2 = new Text("b")
-    val diffs = XMLUtils.computeTextDiff("", d1, d2, None)
+    val diffs = XMLUtils.computeTextDiff("", d1, d2, None, None, None)
     val Seq((p, a, b)) = diffs
     assertEquals(".charAt(1)", p)
     assertEquals("a", a)
@@ -74,7 +74,6 @@ class TestXMLUtils {
     assertEquals("a/d[2].charAt(3)", p2)
     assertEquals("x", x)
     assertEquals("y", y)
-
   }
 
   @Test def testNilDiff1(): Unit = {
@@ -111,6 +110,55 @@ class TestXMLUtils {
     assertEquals("xmlns:ns1=\"someprefixB\"", b)
   }
 
+  val xsiNS = "http://www.w3.org/2001/XMLSchema-instance";
+  val xsNS = "http://www.w3.org/2001/XMLSchema";
+
+  @Test def testDoubleDiffNoEpsilon(): Unit = {
+    val d1 = <tnp05 xmlns:xsi={xsiNS} xmlns:xs={xsNS} xsi:type="xs:double">
+      9.8765432109876544E16
+    </tnp05>
+    val d2 = <tnp05 xmlns:xsi={xsiNS} xmlns:xs={xsNS} xsi:type="xs:double">
+      9.876543210987654E16
+    </tnp05>
+    val diffs = XMLUtils.computeDiff(d1, d2, false).toList
+    assertTrue(diffs.isEmpty)
+  }
+
+  @Test def testDoubleDiffWithEpsilon(): Unit = {
+    val d1 = "9.09"
+    val d2 = "9.11"
+    val isSame = XMLUtils.textIsSame(d1, d2, Some("xs:double"), None, 
Some(0.1))
+    assertTrue(isSame)
+  }
+
+  @Test def testDoubleDiffWithEpsilonNeg(): Unit = {
+    val d1 = "9.09"
+    val d2 = "9.11"
+    val isSame = XMLUtils.textIsSame(d1, d2, Some("xs:double"), None, 
Some(0.01))
+    assertFalse(isSame)
+  }
+
+  @Test def testFloatDiffNoEpsilon(): Unit = {
+    val d1 = <tnp05 xmlns:xsi={xsiNS} xmlns:xs={xsNS} 
xsi:type="xs:float">6.5400003E9</tnp05>
+    val d2 = <tnp05 xmlns:xsi={xsiNS} xmlns:xs={xsNS} 
xsi:type="xs:float">6.54E9</tnp05>
+    val diffs = XMLUtils.computeDiff(d1, d2, false).toList
+    assertTrue(diffs.isEmpty)
+  }
+
+  @Test def testFloatDiffWithEpsilon(): Unit = {
+    val d1 = "9.09"
+    val d2 = "9.11"
+    val isSame = XMLUtils.textIsSame(d1, d2, Some("xs:float"), 
Some(0.1.toFloat), None)
+    assertTrue(isSame)
+  }
+
+  @Test def testFloatDiffWithEpsilonNeg(): Unit = {
+    val d1 = "9.09"
+    val d2 = "9.11"
+    val isSame = XMLUtils.textIsSame(d1, d2, Some("xs:float"), 
Some(0.01.toFloat), None)
+    assertFalse(isSame)
+  }
+
   @Test def testIsNil(): Unit = {
     val d1 = JDOMUtils.elem2Element(<a xmlns:xsi={XMLUtils.XSI_NAMESPACE} 
xsi:nil="true"/>)
     val d2 = JDOMUtils.elem2Element(<a 
xmlns:xsi={XMLUtils.XSI_NAMESPACE}>foo</a>)
diff --git 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/NodeInfo.scala
 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/NodeInfo.scala
index a844a063a..fe669c21a 100644
--- 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/NodeInfo.scala
+++ 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/NodeInfo.scala
@@ -658,13 +658,8 @@ object NodeInfo extends Enum {
       with PrimNumericFloat
       with FloatView {
       type Kind = FloatKind
-      protected override def fromString(s: String) = {
-        val f: JFloat = s match {
-          case XMLUtils.PositiveInfinityString => JFloat.POSITIVE_INFINITY
-          case XMLUtils.NegativeInfinityString => JFloat.NEGATIVE_INFINITY
-          case XMLUtils.NaNString => JFloat.NaN
-          case _ => s.toFloat
-        }
+      protected override def fromString(s: String): DataValueFloat = {
+        val f: JFloat = XMLUtils.strToFloat(s)
         f
       }
       protected override def fromNumberNoCheck(n: Number): DataValueFloat = 
n.floatValue
@@ -681,12 +676,7 @@ object NodeInfo extends Enum {
       with DoubleView {
       type Kind = DoubleKind
       protected override def fromString(s: String): DataValueDouble = {
-        val d: JDouble = s match {
-          case XMLUtils.PositiveInfinityString => JDouble.POSITIVE_INFINITY
-          case XMLUtils.NegativeInfinityString => JDouble.NEGATIVE_INFINITY
-          case XMLUtils.NaNString => JDouble.NaN
-          case _ => s.toDouble
-        }
+        val d: JDouble = XMLUtils.strToDouble(s)
         d
       }
       protected override def fromNumberNoCheck(n: Number): DataValueDouble = 
n.doubleValue
diff --git 
a/daffodil-test-ibm1/src/test/resources/test-suite/tresys-contributed/BG.tdml 
b/daffodil-test-ibm1/src/test/resources/test-suite/tresys-contributed/BG.tdml
index 256c1c29a..2b869d5c7 100644
--- 
a/daffodil-test-ibm1/src/test/resources/test-suite/tresys-contributed/BG.tdml
+++ 
b/daffodil-test-ibm1/src/test/resources/test-suite/tresys-contributed/BG.tdml
@@ -17,7 +17,8 @@
 -->
 
 <testSuite suiteName="BG" xmlns="http://www.ibm.com/xmlns/dfdl/testData";
-  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+  xmlns:xs="http://www.w3.org/2001/XMLSchema";
   xmlns:ex="http://example.com";
   defaultImplementations="ibm daffodil">
 
@@ -30,20 +31,20 @@
     <infoset>
       <dfdlInfoset>
         <ex:list>
-          <x>9.87654321001E9</x>
-          <x>12345.6</x>
-          <x>1.23456789123456784E17</x>
-          <x>INF</x>
-          <x>NaN</x>
-          <x>0.0</x>
-          <x>0.0</x>
-          <x>0.0</x>
+          <x xsi:type="xs:double">9.87654321001E9</x>
+          <x xsi:type="xs:double">12345.6</x>
+          <x xsi:type="xs:double">1.23456789123456784E17</x>
+          <x xsi:type="xs:double">INF</x>
+          <x xsi:type="xs:double">NaN</x>
+          <x xsi:type="xs:double">0.0</x>
+          <x xsi:type="xs:double">0.0</x>
+          <x xsi:type="xs:double">0.0</x>
           <y>187723572702975</y>
           <y>986895</y>
           <y>4886718345</y>
-          <z>10.1</z>
-          <z>20.3</z>
-          <z>-9.12E-11</z>
+          <z xsi:type="xs:float">10.1</z>
+          <z xsi:type="xs:float">20.3</z>
+          <z xsi:type="xs:float">-9.12E-11</z>
         </ex:list>
       </dfdlInfoset>
     </infoset>
diff --git 
a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/SimpleTypes.tdml
 
b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/SimpleTypes.tdml
index e652f9812..6c49131d3 100644
--- 
a/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/SimpleTypes.tdml
+++ 
b/daffodil-test/src/test/resources/org/apache/daffodil/section05/simple_types/SimpleTypes.tdml
@@ -3426,7 +3426,7 @@
     </tdml:document>
     <tdml:infoset>
       <tdml:dfdlInfoset>
-        <d_02>9.8765432109876544E16</d_02>
+        <d_02 xsi:type="xs:double">9.8765432109876544E16</d_02>
       </tdml:dfdlInfoset>
     </tdml:infoset>
   </tdml:parserTestCase>
diff --git 
a/daffodil-test/src/test/resources/org/apache/daffodil/section13/text_number_props/TextNumberProps.tdml
 
b/daffodil-test/src/test/resources/org/apache/daffodil/section13/text_number_props/TextNumberProps.tdml
index abf3c3259..272d3f623 100644
--- 
a/daffodil-test/src/test/resources/org/apache/daffodil/section13/text_number_props/TextNumberProps.tdml
+++ 
b/daffodil-test/src/test/resources/org/apache/daffodil/section13/text_number_props/TextNumberProps.tdml
@@ -22,7 +22,7 @@
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
   xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/";
   xmlns:xs="http://www.w3.org/2001/XMLSchema";
-  xmlns:ex="http://example.com"; 
+  xmlns:ex="http://example.com";
   xmlns="http://example.com"; 
   xmlns:tns="http://example.com"; 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema";
@@ -609,7 +609,7 @@
     </tdml:document>
     <tdml:infoset>
       <tdml:dfdlInfoset>
-        <tnp05>6.5400003E9</tnp05>
+        <tnp05 xsi:type="xs:float">6.5400003E9</tnp05>
       </tdml:dfdlInfoset>
     </tdml:infoset>
 

Reply via email to