You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by ja...@apache.org on 2018/07/09 14:52:41 UTC
[incubator-daffodil] 04/13: Almost done with parser
This is an automated email from the ASF dual-hosted git repository.
jadams pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-daffodil.git
commit fa0b330b8d9d98fb893f33a5229610bba73d9ead
Author: Josh Adams <ja...@tresys.com>
AuthorDate: Wed Apr 18 10:13:34 2018 -0400
Almost done with parser
---
.../grammar/primitives/PrimitivesZoned.scala | 159 ++++++-
.../org/apache/daffodil/util/DecimalUtils.scala | 28 +-
.../apache/daffodil/util/TestDecimalUtils.scala | 180 ++++----
.../unparsers/ConvertTextNumberUnparser.scala | 42 +-
.../processors/parsers/ZonedTextParsers.scala | 493 ++++++++++++++++++++-
5 files changed, 757 insertions(+), 145 deletions(-)
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesZoned.scala b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesZoned.scala
index ecad042..3d795d0 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesZoned.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesZoned.scala
@@ -17,13 +17,158 @@
package org.apache.daffodil.grammar.primitives
-import org.apache.daffodil.dsom.ElementBase
+import org.apache.daffodil.dsom._
+import org.apache.daffodil.grammar.Gram
+import org.apache.daffodil.grammar.Terminal
+import com.ibm.icu.text.DecimalFormat
+import org.apache.daffodil.processors.unparsers.Unparser
+import org.apache.daffodil.processors.unparsers.ConvertZonedNumberUnparser
+import org.apache.daffodil.processors.unparsers.ConvertZonedCombinatorUnparser
+import java.math.{ BigDecimal => JBigDecimal, BigInteger => JBigInt }
+import org.apache.daffodil.processors.parsers.ConvertZonedByteParserUnparserHelper
+import org.apache.daffodil.processors.parsers.ConvertZonedCombinatorParser
+import org.apache.daffodil.processors.parsers.ConvertZonedDecimalParserUnparserHelper
+import org.apache.daffodil.processors.parsers.ConvertZonedIntParserUnparserHelper
+import org.apache.daffodil.processors.parsers.ConvertZonedIntegerParserUnparserHelper
+import org.apache.daffodil.processors.parsers.ConvertZonedLongParserUnparserHelper
+import org.apache.daffodil.processors.parsers.ConvertZonedNonNegativeIntegerParserUnparserHelper
+import org.apache.daffodil.processors.parsers.ConvertZonedNumberParser
+import org.apache.daffodil.processors.parsers.ConvertZonedNumberParserUnparserHelperBase
+import org.apache.daffodil.processors.parsers.ConvertZonedShortParserUnparserHelper
+import org.apache.daffodil.processors.parsers.ConvertZonedUnsignedByteParserUnparserHelper
+import org.apache.daffodil.processors.parsers.ConvertZonedUnsignedLongParserUnparserHelper
+import org.apache.daffodil.processors.parsers.ConvertZonedUnsignedShortParserUnparserHelper
+import org.apache.daffodil.processors.parsers.ZonedFormatFactoryBase
+import org.apache.daffodil.processors.parsers.ZonedFormatFactoryDynamic
+import org.apache.daffodil.processors.parsers.ZonedFormatFactoryStatic
+import org.apache.daffodil.processors.parsers.ConvertZonedUnsignedIntParserUnparserHelper
+import org.apache.daffodil.processors.parsers.Parser
-abstract class ZonedTextNumberPrim(e: ElementBase, guard: Boolean) extends UnimplementedPrimitive(e, guard) {
- // lazy val parser: DaffodilParser = new ZonedTextNumberParser(e.elementRuntimeData)
+case class ConvertZonedCombinator(e: ElementBase, value: Gram, converter: Gram)
+ extends Terminal(e, !(value.isEmpty || converter.isEmpty)) {
+
+ lazy val parser = new ConvertZonedCombinatorParser(e.termRuntimeData, value.parser, converter.parser)
+
+ override lazy val unparser = new ConvertZonedCombinatorUnparser(e.termRuntimeData, value.unparser, converter.unparser)
+}
+
+abstract class ConvertZonedNumberPrim[S](e: ElementBase)
+ extends Terminal(e, true) {
+
+ def helper: ConvertZonedNumberParserUnparserHelperBase[S]
+
+ def numFormatFactory: ZonedFormatFactoryBase[S] = {
+ val h = helper
+
+ val (pattern, patternStripped) = {
+ val p = e.textNumberPattern
+
+ val noEscapedTicksRegex = """''""".r
+ val patternNoEscapedTicks = noEscapedTicksRegex.replaceAllIn(p, "")
+ val noQuotedRegex = """'[^']+'""".r
+ val patternNoQuoted = noQuotedRegex.replaceAllIn(patternNoEscapedTicks, "")
+
+ if (patternNoQuoted.contains("V")) {
+ e.notYetImplemented("textNumberPattern with V symbol")
+ }
+
+ if (patternNoQuoted.contains("P")) {
+ e.notYetImplemented("textNumberPattern with P symbol")
+ }
+
+ if (patternNoQuoted.contains("@")) {
+ e.SDE("The '@' symbol may not be used in textNumberPattern for textNumberRep='zoned'")
+ }
+
+ if (patternNoQuoted.contains(";")) {
+ e.SDE("Negative patterns may not be used in textNumberPattern for textNumberRep='zoned'")
+ }
+
+ e.primType match {
+ case PrimType.Double | PrimType.Float => e.SDE("textZonedFormat='zoned' does not support Doubles/Floats")
+ case PrimType.UnsignedLong | PrimType.UnsignedInt | PrimType.UnsignedShort | PrimType.UnsignedByte if (e.textNumberCheckPolicy == TextNumberCheckPolicy.Lax) => {
+ if ((patternNoQuoted.charAt(0) != '+') || (patternNoQuoted.charAt(patternNoQuoted.length) != '+'))
+ e.SDE("textNumberPattern must have '+' at the beginning or the end of the pattern when textZonedFormat='zoned' and textNumberPolicy='lax' for unsigned numbers")
+ }
+ case _ => {
+ if ((patternNoQuoted.charAt(0) != '+') || (patternNoQuoted.charAt(patternNoQuoted.length) != '+'))
+ e.SDE("textNumberPattern must have '+' at the beginning or the end of the pattern when textZonedFormat='zoned' for signed numbers")
+ }
+ }
+
+ // Load the pattern to make sure it is valid
+ try {
+ new DecimalFormat(p)
+ } catch {
+ case ex: IllegalArgumentException => e.SDE("Invalid textNumberPattern: " + ex.getMessage())
+ }
+
+ (p, patternNoQuoted)
+ }
+
+ val nff = if (isConstant) {
+ new ZonedFormatFactoryStatic[S](e.termRuntimeData, h,
+ e.textNumberCheckPolicy,
+ pattern,
+ e.textNumberRounding,
+ roundingMode,
+ roundingIncrement)
+ } else {
+ new ZonedFormatFactoryDynamic[S](e.termRuntimeData, h,
+ e.textNumberCheckPolicy,
+ pattern,
+ e.textNumberRounding,
+ roundingMode,
+ roundingIncrement)
+ }
+ nff
+ }
+
+ lazy val parser: Parser = new ConvertZonedNumberParser[S](helper, numFormatFactory, e.textZonedSignStyle, e.elementRuntimeData)
+
+ override lazy val unparser: Unparser = new ConvertZonedNumberUnparser[S](helper, e.textZonedSignStyle, e.elementRuntimeData)
+}
+
+case class ConvertZonedIntegerPrim(e: ElementBase) extends ConvertZonedNumberPrim[JBigInt](e) {
+ val helper = new ConvertZonedIntegerParserUnparserHelper[JBigInt]()
}
-case class ZonedTextBytePrim(el: ElementBase) extends ZonedTextNumberPrim(el, false)
-case class ZonedTextShortPrim(el: ElementBase) extends ZonedTextNumberPrim(el, false)
-case class ZonedTextIntPrim(el: ElementBase) extends ZonedTextNumberPrim(el, false)
-case class ZonedTextLongPrim(el: ElementBase) extends ZonedTextNumberPrim(el, false)
+case class ConvertZonedDecimalPrim(e: ElementBase) extends ConvertZonedNumberPrim[JBigDecimal](e) {
+ val helper = new ConvertZonedDecimalParserUnparserHelper[JBigDecimal]()
+}
+
+case class ConvertZonedNonNegativeIntegerPrim(e: ElementBase) extends ConvertZonedNumberPrim[JBigInt](e) {
+ val helper = new ConvertZonedNonNegativeIntegerParserUnparserHelper[JBigDecimal]()
+}
+
+case class ConvertZonedLongPrim(e: ElementBase) extends ConvertZonedNumberPrim[Long](e) {
+ val helper = new ConvertZonedLongParserUnparserHelper[Long]()
+}
+
+case class ConvertZonedIntPrim(e: ElementBase) extends ConvertZonedNumberPrim[Int](e) {
+ val helper = new ConvertZonedIntParserUnparserHelper[Int]()
+}
+
+case class ConvertZonedShortPrim(e: ElementBase) extends ConvertZonedNumberPrim[Short](e) {
+ val helper = new ConvertZonedShortParserUnparserHelper[Short]()
+}
+
+case class ConvertZonedBytePrim(e: ElementBase) extends ConvertZonedNumberPrim[Byte](e) {
+ val helper = new ConvertZonedByteParserUnparserHelper[Byte]()
+}
+
+case class ConvertZonedUnsignedLongPrim(e: ElementBase) extends ConvertZonedNumberPrim[JBigInt](e) {
+ val helper = new ConvertZonedUnsignedLongParserUnparserHelper[JBigInt]()
+}
+
+case class ConvertZonedUnsignedIntPrim(e: ElementBase) extends ConvertZonedNumberPrim[Long](e) {
+ val helper = ConvertZonedUnsignedIntParserUnparserHelper[Long]()
+}
+
+case class ConvertZonedUnsignedShortPrim(e: ElementBase) extends ConvertZonedNumberPrim[Int](e) {
+ val helper = new ConvertZonedUnsignedShortParserUnparserHelper[Int]()
+}
+
+case class ConvertZonedUnsignedBytePrim(e: ElementBase) extends ConvertZonedNumberPrim[Short](e) {
+ val helper = new ConvertZonedUnsignedByteParserUnparserHelper[Short]()
+}
diff --git a/daffodil-lib/src/main/scala/org/apache/daffodil/util/DecimalUtils.scala b/daffodil-lib/src/main/scala/org/apache/daffodil/util/DecimalUtils.scala
index 5851369..9a733e8 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/util/DecimalUtils.scala
+++ b/daffodil-lib/src/main/scala/org/apache/daffodil/util/DecimalUtils.scala
@@ -412,7 +412,7 @@ object DecimalUtils {
return (digit + 80).asInstanceOf[Char]
}
- def zonedToInt(num: String, zonedStyle: TextZonedSignStyle): Int = {
+ def zonedToNumber(num: String, zonedStyle: TextZonedSignStyle): String = {
val decodedValue = new StringBuilder()
var negative = false
@@ -424,29 +424,31 @@ object DecimalUtils {
case TextZonedSignStyle.AsciiTandemModified => convertFromAsciiTandemModified(char)
}
- if ((digit < 0) && !negative)
+ if ((digit < 0) && !negative) {
negative = true
+ decodedValue.append('-')
+ }
decodedValue.append(java.lang.Math.abs(digit))
}
- return negative match {
- case true => -Integer.parseInt(decodedValue.toString)
- case false => Integer.parseInt(decodedValue.toString)
- }
+ return decodedValue.toString
}
- def zonedFromInt(num: Int, zonedStyle: TextZonedSignStyle): String = {
- val positive = (num >= 0)
- val inChars = java.lang.Math.abs(num).toString.toCharArray
+ def zonedFromNumber(num: String, zonedStyle: TextZonedSignStyle): String = {
+ val negative = (num.charAt(0) == '-')
+ val inChars = negative match {
+ case true => num.substring(0).toCharArray
+ case false => num.toCharArray
+ }
val encodedValue = new StringBuilder()
for (char <- inChars) {
val digit = zonedStyle match {
- case TextZonedSignStyle.AsciiStandard => convertToAsciiStandard(char, positive)
- case TextZonedSignStyle.AsciiTranslatedEBCDIC => convertToAsciiTranslatedEBCDIC(char, positive)
- case TextZonedSignStyle.AsciiCARealiaModified => convertToAsciiCARealiaModified(char, positive)
- case TextZonedSignStyle.AsciiTandemModified => convertToAsciiTandemModified(char, positive)
+ case TextZonedSignStyle.AsciiStandard => convertToAsciiStandard(char, negative)
+ case TextZonedSignStyle.AsciiTranslatedEBCDIC => convertToAsciiTranslatedEBCDIC(char, negative)
+ case TextZonedSignStyle.AsciiCARealiaModified => convertToAsciiCARealiaModified(char, negative)
+ case TextZonedSignStyle.AsciiTandemModified => convertToAsciiTandemModified(char, negative)
}
encodedValue.append(digit)
diff --git a/daffodil-lib/src/test/scala/org/apache/daffodil/util/TestDecimalUtils.scala b/daffodil-lib/src/test/scala/org/apache/daffodil/util/TestDecimalUtils.scala
index fc1c950..20674b1 100644
--- a/daffodil-lib/src/test/scala/org/apache/daffodil/util/TestDecimalUtils.scala
+++ b/daffodil-lib/src/test/scala/org/apache/daffodil/util/TestDecimalUtils.scala
@@ -1252,212 +1252,212 @@ class TestDecimalUtils {
@Test def zonedIntAsciiStandardPos1() {
val num = "1"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiStandard)
- assertEquals(result, 1)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiStandard), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiStandard)
+ assertEquals(result, "1")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiStandard), num)
}
@Test def zonedIntAsciiStandardPos2() {
val num = "12"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiStandard)
- assertEquals(result, 12)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiStandard), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiStandard)
+ assertEquals(result, "12")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiStandard), num)
}
@Test def zonedIntAsciiStandardPos3() {
val num = "123"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiStandard)
- assertEquals(result, 123)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiStandard), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiStandard)
+ assertEquals(result, "123")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiStandard), num)
}
@Test def zonedIntAsciiStandardPos4() {
val num = "1234567890"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiStandard)
- assertEquals(result, 1234567890)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiStandard), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiStandard)
+ assertEquals(result, "1234567890")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiStandard), num)
}
@Test def zonedIntAsciiStandardPos5() {
val num = "000000000001234567890"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiStandard)
- assertEquals(result, 1234567890)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiStandard), "1234567890")
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiStandard)
+ assertEquals(result, "1234567890")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiStandard), "1234567890")
}
@Test def zonedIntAsciiStandardNeg1() {
val num = "q"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiStandard)
- assertEquals(result, -1)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiStandard), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiStandard)
+ assertEquals(result, "-1")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiStandard), num)
}
@Test def zonedIntAsciiStandardNeg2() {
val num = "qr"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiStandard)
- assertEquals(result, -12)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiStandard), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiStandard)
+ assertEquals(result, "-12")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiStandard), num)
}
@Test def zonedIntAsciiStandardNeg3() {
val num = "qrs"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiStandard)
- assertEquals(result, -123)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiStandard), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiStandard)
+ assertEquals(result, "-123")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiStandard), num)
}
@Test def zonedIntAsciiStandardNeg4() {
val num = "qrstuvwxyp"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiStandard)
- assertEquals(result, -1234567890)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiStandard), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiStandard)
+ assertEquals(result, "-1234567890")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiStandard), num)
}
@Test def zonedIntAsciiStandardNeg5() {
val num = "pppppppppppqrstuvwxyp"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiStandard)
- assertEquals(result, -1234567890)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiStandard), "qrstuvwxyp")
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiStandard)
+ assertEquals(result, "-1234567890")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiStandard), "qrstuvwxyp")
}
@Test def zonedIntAsciiCARealiaModifiedPos1() {
val num = "1"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiCARealiaModified)
- assertEquals(result, 1)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiCARealiaModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiCARealiaModified)
+ assertEquals(result, "1")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiCARealiaModified), num)
}
@Test def zonedIntAsciiCARealiaModifiedPos2() {
val num = "12"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiCARealiaModified)
- assertEquals(result, 12)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiCARealiaModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiCARealiaModified)
+ assertEquals(result, "12")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiCARealiaModified), num)
}
@Test def zonedIntAsciiCARealiaModifiedPos3() {
val num = "123"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiCARealiaModified)
- assertEquals(result, 123)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiCARealiaModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiCARealiaModified)
+ assertEquals(result, "123")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiCARealiaModified), num)
}
@Test def zonedIntAsciiCARealiaModifiedPos4() {
val num = "1234567890"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiCARealiaModified)
- assertEquals(result, 1234567890)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiCARealiaModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiCARealiaModified)
+ assertEquals(result, "1234567890")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiCARealiaModified), num)
}
@Test def zonedIntAsciiCARealiaModifiedPos5() {
val num = "000000000001234567890"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiCARealiaModified)
- assertEquals(result, 1234567890)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiCARealiaModified), "1234567890")
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiCARealiaModified)
+ assertEquals(result, "1234567890")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiCARealiaModified), "1234567890")
}
@Test def zonedIntAsciiCARealiaModifiedNeg1() {
val num = "!"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiCARealiaModified)
- assertEquals(result, -1)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiCARealiaModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiCARealiaModified)
+ assertEquals(result, "-1")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiCARealiaModified), num)
}
@Test def zonedIntAsciiCARealiaModifiedNeg2() {
val num = "!\""
- val result = zonedToInt(num, TextZonedSignStyle.AsciiCARealiaModified)
- assertEquals(result, -12)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiCARealiaModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiCARealiaModified)
+ assertEquals(result, "-12")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiCARealiaModified), num)
}
@Test def zonedIntAsciiCARealiaModifiedNeg3() {
val num = "!\"#"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiCARealiaModified)
- assertEquals(result, -123)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiCARealiaModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiCARealiaModified)
+ assertEquals(result, "-123")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiCARealiaModified), num)
}
@Test def zonedIntAsciiCARealiaModifiedNeg4() {
val num = "!\"#$%&'() "
- val result = zonedToInt(num, TextZonedSignStyle.AsciiCARealiaModified)
- assertEquals(result, -1234567890)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiCARealiaModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiCARealiaModified)
+ assertEquals(result, "-1234567890")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiCARealiaModified), num)
}
@Test def zonedIntAsciiCARealiaModifiedNeg5() {
val num = " !\"#$%&'() "
- val result = zonedToInt(num, TextZonedSignStyle.AsciiCARealiaModified)
- assertEquals(result, -1234567890)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiCARealiaModified), "!\"#$%&'() ")
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiCARealiaModified)
+ assertEquals(result, "-1234567890")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiCARealiaModified), "!\"#$%&'() ")
}
@Test def zonedIntAsciiTandemModifiedPos1() {
val num = "1"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiTandemModified)
- assertEquals(result, 1)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiTandemModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiTandemModified)
+ assertEquals(result, "1")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiTandemModified), num)
}
@Test def zonedIntAsciiTandemModifiedPos2() {
val num = "12"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiTandemModified)
- assertEquals(result, 12)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiTandemModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiTandemModified)
+ assertEquals(result, "12")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiTandemModified), num)
}
@Test def zonedIntAsciiTandemModifiedPos3() {
val num = "123"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiTandemModified)
- assertEquals(result, 123)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiTandemModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiTandemModified)
+ assertEquals(result, "123")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiTandemModified), num)
}
@Test def zonedIntAsciiTandemModifiedPos4() {
val num = "1234567890"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiTandemModified)
- assertEquals(result, 1234567890)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiTandemModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiTandemModified)
+ assertEquals(result, "1234567890")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiTandemModified), num)
}
@Test def zonedIntAsciiTandemModifiedPos5() {
val num = "000000000001234567890"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiTandemModified)
- assertEquals(result, 1234567890)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiTandemModified), "1234567890")
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiTandemModified)
+ assertEquals(result, "1234567890")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiTandemModified), "1234567890")
}
@Test def zonedIntAsciiTandemModifiedNeg1() {
val num = ""
- val result = zonedToInt(num, TextZonedSignStyle.AsciiTandemModified)
- assertEquals(result, -1)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiTandemModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiTandemModified)
+ assertEquals(result, "-1")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiTandemModified), num)
}
@Test def zonedIntAsciiTandemModifiedNeg2() {
val num = ""
- val result = zonedToInt(num, TextZonedSignStyle.AsciiTandemModified)
- assertEquals(result, -12)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiTandemModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiTandemModified)
+ assertEquals(result, "-12")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiTandemModified), num)
}
@Test def zonedIntAsciiTandemModifiedNeg3() {
val num = ""
- val result = zonedToInt(num, TextZonedSignStyle.AsciiTandemModified)
- assertEquals(result, -123)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiTandemModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiTandemModified)
+ assertEquals(result, "-123")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiTandemModified), num)
}
@Test def zonedIntAsciiTandemModifiedNeg4() {
val num = "
"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiTandemModified)
- assertEquals(result, -1234567890)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiTandemModified), num)
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiTandemModified)
+ assertEquals(result, "-1234567890")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiTandemModified), num)
}
@Test def zonedIntAsciiTandemModifiedNeg5() {
val num = "
"
- val result = zonedToInt(num, TextZonedSignStyle.AsciiTandemModified)
- assertEquals(result, -1234567890)
- assertEquals(zonedFromInt(result, TextZonedSignStyle.AsciiTandemModified), "
")
+ val result = zonedToNumber(num, TextZonedSignStyle.AsciiTandemModified)
+ assertEquals(result, "-1234567890")
+ assertEquals(zonedFromNumber(result, TextZonedSignStyle.AsciiTandemModified), "
")
}
}
diff --git a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ConvertTextNumberUnparser.scala b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ConvertTextNumberUnparser.scala
index 2efa11a..bfa31d4 100644
--- a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ConvertTextNumberUnparser.scala
+++ b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ConvertTextNumberUnparser.scala
@@ -17,15 +17,12 @@
package org.apache.daffodil.processors.unparsers
+import org.apache.daffodil.schema.annotation.props.gen.TextZonedSignStyle
import org.apache.daffodil.processors._
-import org.apache.daffodil.util.Maybe
-import org.apache.daffodil.util.Maybe._
-import org.apache.daffodil.cookers.EntityReplacer
-import org.apache.daffodil.exceptions.Assert
-import org.apache.daffodil.processors.parsers.NumberFormatFactoryBase
-import org.apache.daffodil.processors.parsers.ConvertTextNumberParserUnparserHelperBase
-
-case class ConvertTextCombinatorUnparser(
+import org.apache.daffodil.util.DecimalUtils
+import org.apache.daffodil.processors.parsers.ConvertZonedNumberParserUnparserHelperBase
+
+case class ConvertZonedCombinatorUnparser(
rd: TermRuntimeData,
valueUnparser: Unparser,
converterUnparser: Unparser)
@@ -44,9 +41,9 @@ case class ConvertTextCombinatorUnparser(
}
}
-case class ConvertTextNumberUnparser[S](
- helper: ConvertTextNumberParserUnparserHelperBase[S],
- nff: NumberFormatFactoryBase[S],
+case class ConvertZonedNumberUnparser[S](
+ helper: ConvertZonedNumberParserUnparserHelperBase[S],
+ zonedSignStyle: TextZonedSignStyle,
override val context: ElementRuntimeData)
extends PrimUnparser
with ToBriefXMLImpl {
@@ -56,36 +53,17 @@ case class ConvertTextNumberUnparser[S](
override def toString = "to(xs:" + helper.xsdType + ")"
override lazy val childProcessors = Nil
- lazy val zeroRep: Maybe[String] = helper.zeroRepListRaw.headOption.map { zr =>
- EntityReplacer { _.replaceForUnparse(zr) }
- }
-
override def unparse(state: UState): Unit = {
val node = state.currentInfosetNode.asSimple
- val value = node.dataValue
+ val value = node.dataValueAsString
// The type of value should have the type of S, but type erasure makes this
// difficult to assert. Could probably check this with TypeTags or Manifest
// if we find this is not the case. Want something akin to:
// Assert.invariant(value.isInstanceOf[S])
- val strRep =
- if (value == 0 && zeroRep.isDefined) {
- zeroRep.get
- } else {
- // Needed because the DecimalFormat class of ICU will call
- // doubleValue on scala's BigInt and BigDecimal because it
- // doesn't recognize it as Java's BigInteger and BigDecimal.
- // This caused large numbers to be truncated silently.
- value match {
- case bd: scala.math.BigDecimal => Assert.usageError("Received scala.math.BigDecimal, expected java.math.BigDecimal.")
- case bi: scala.math.BigInt => Assert.usageError("Received scala.math.BigInt, expected java.math.BigInteger.")
- case _ => // OK
- }
- val df = nff.getNumFormat(state)
- df.get.format(value)
- }
+ val strRep = DecimalUtils.zonedFromNumber(value, zonedSignStyle)
node.overwriteDataValue(strRep)
}
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ZonedTextParsers.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ZonedTextParsers.scala
index d300cd8..ceaac9a 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ZonedTextParsers.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ZonedTextParsers.scala
@@ -17,14 +17,501 @@
package org.apache.daffodil.processors.parsers
+import org.apache.daffodil.schema.annotation.props.gen. { TextNumberCheckPolicy, TextNumberRounding, TextNumberRoundingMode, TextZonedSignStyle }
+import org.apache.daffodil.exceptions.ThrowsSDE
+import org.apache.daffodil.exceptions.Assert
+import org.apache.daffodil.exceptions.UnsuppressableException
+import org.apache.daffodil.util.Maybe
+import org.apache.daffodil.util.DecimalUtils
+import java.text.ParsePosition
+import com.ibm.icu.text.DecimalFormat
+import com.ibm.icu.text.DecimalFormatSymbols
+import org.apache.daffodil.util.MaybeDouble
+import org.apache.daffodil.util.MaybeDouble
+import java.lang.{ Number => JNumber }
+import java.math.{ BigDecimal => JBigDecimal, BigInteger => JBigInt }
+import org.apache.daffodil.infoset.DISimple
+import org.apache.daffodil.processors.Dynamic
import org.apache.daffodil.processors.ElementRuntimeData
+import org.apache.daffodil.processors.ParseOrUnparseState
+import org.apache.daffodil.processors.Success
+import java.lang.{ Number => JNumber }
+import java.math.{ BigDecimal => JBigDecimal }
+import java.math.{ BigInteger => JBigInt }
+import org.apache.daffodil.processors.TermRuntimeData
-class ZonedTextNumberParser(override val context: ElementRuntimeData)
- extends TextPrimParser {
+case class ConvertZonedCombinatorParser(
+ rd: TermRuntimeData,
+ valueParser: Parser,
+ converterParser: Parser)
+ extends CombinatorParser(rd) {
override lazy val runtimeDependencies = Nil
+ override lazy val childProcessors = Seq(valueParser, converterParser)
+
+ def parse(start: PState): Unit = {
+ valueParser.parse1(start)
+ if (start.processorStatus ne Success) {
+ return
+ }
+ converterParser.parse1(start)
+ }
+}
+
+case class ConvertZonedNumberParser[S](
+ helper: ConvertZonedNumberParserUnparserHelperBase[S],
+ nff: ZonedFormatFactoryBase[S],
+ zonedSignStyle: TextZonedSignStyle,
+ override val context: ElementRuntimeData) extends TextPrimParser {
+ override lazy val runtimeDependencies = Nil
+
+ override def toString = "to(xs:" + helper.xsdType + ")"
+
def parse(start: PState): Unit = {
- context.notYetImplemented("Zoned Numbers")
+ val node: DISimple = start.simpleElement
+ val str = node.dataValueAsString
+
+ Assert.invariant(str != null) // worst case it should be empty string. But not null.
+ if (str == "") {
+ PE(start, "Convert to %s (for xs:%s): Cannot parse number from empty string", helper.prettyType, helper.xsdType)
+ return
+ }
+
+ // because of the way the zero rep regular expressions are generated, they
+ // will match either all or none of 'str', never part of it. Thus,
+ // findFirstIn() either matches and it's a zero rep, or it doesn't and it's
+ // not a zero
+ val numValue = {
+ val df = nff.getNumFormat(start)
+ val pos = new ParsePosition(0)
+ val num = try {
+ val decodedNum = DecimalUtils.zonedToNumber(str, zonedSignStyle)
+ df.get.parse(decodedNum, pos)
+ } catch {
+ case s: scala.util.control.ControlThrowable => throw s
+ case u: UnsuppressableException => throw u
+ case e: Exception => {
+ PE(start, "Convert to %s (for xs:%s): Parse of '%s' threw exception %s",
+ helper.prettyType, helper.xsdType, str, e)
+ return
+ }
+ }
+
+ // Verify that what was parsed was what was passed exactly in byte count.
+ // Use pos to verify all characters consumed & check for errors!
+ if (num == null || pos.getIndex != str.length) {
+ PE(start, "Convert to %s (for xs:%s): Unable to parse '%s' (using up all characters).",
+ helper.prettyType, helper.xsdType, str)
+ return
+ }
+
+ val numValue = {
+ if (helper.isInvalidRange(num)) {
+ PE(start, "Convert to %s (for xs:%s): Out of Range: '%s' converted to %s, is not in range for the type.",
+ helper.prettyType, helper.xsdType, str, num)
+ return
+ }
+
+ // convert to proper type
+ val asNumber = helper.getNum(num)
+ Assert.invariant(!asNumber.isInstanceOf[String])
+
+ // The following change was made because of the issues with the float
+ // adding a position of precision to the Number object. At some point we
+ // will want to revert this back to the actual type but this is a quick fix
+ // for the issues we were having with the 0.003 vs 0.0030 error in test_DelimProp_05
+ //
+ //helper.getStringFormat(asNumber)
+ //
+ // Above was changed back into the actual number object.
+ asNumber
+ }
+ numValue
+ }
+
+ Assert.invariant(!numValue.isInstanceOf[String])
+ node.overwriteDataValue(numValue.asInstanceOf[JNumber])
+
+ }
+}
+
+abstract class ConvertZonedNumberParserUnparserHelperBase[S]() extends Serializable {
+ val xsdType: String
+ val prettyType: String
+
+ def getNum(s: Number): S
+ def isInt: Boolean
+ def isInvalidRange(n: java.lang.Number): Boolean
+ def getStringFormat(n: S): String
+}
+
+abstract class ConvertZonedIntegerNumberParserUnparserHelper[S]()
+ extends ConvertZonedNumberParserUnparserHelperBase[S]() {
+ override def isInt = true
+
+ override def getStringFormat(n: S): String = n.toString()
+
+ def isInvalidRange(n: java.lang.Number): Boolean = {
+ //
+ // Note: Scala has no class analogous to java.lang.Number. There's no common
+ // base class above its number types (as there isn't above the Java *primitive* number types.)
+ //
+ // We're being handed here a java 'boxed' number type, and those have common parent Number.
+ //
+ // println("number's actual type is: " + n.getClass.getName)
+ //
+ // This method only for things that fit in range of a Long. (i.e., not unbounded size Integer, and not unsignedLong
+ // Nevertheless, if invalid data much too long for the real numeric type is what is found in the data
+ // then a java BigInteger (or maybe even BigDecimal might get passed here.
+ //
+ // The only thing we can check is whether there is conversion to a long available.
+ // e.g., like this: Assert.invariant(n.isInstanceOf[{ def longValue : Long}])
+ // But that's eliminated by erasure, so we'll just do without.
+ //
+ val l = n.longValue
+
+ // check for overflow/underflow.
+ val orig = new JBigDecimal(n.toString)
+ val newl = new JBigDecimal(l)
+ if (orig.compareTo(newl) != 0) {
+ true
+ } else {
+ l < min || l > max
+ }
+ }
+ def min: Long
+ def max: Long
+}
+
+abstract class ConvertZonedFloatingPointNumberParserUnparserHelper[S]()
+ extends ConvertZonedNumberParserUnparserHelperBase[S]() {
+ override def isInt = false
+ override def getStringFormat(n: S): String = {
+
+ //val trailingZeroes = """0*(?!<[1-9])$"""
+ val trailingZeroes = """(?<=[1-9])(0*)$""".r
+ val trailingZeroesBeforeExponent = """(?<=[1-9])(0*?)(?=E.*)""".r
+
+ val nAsStr = n.toString()
+
+ if (nAsStr.contains("E") || nAsStr.contains("e")) {
+ // Exponent
+ trailingZeroesBeforeExponent.replaceAllIn(nAsStr, "")
+ } else {
+ trailingZeroes.replaceAllIn(nAsStr, "")
+ }
+
+ nAsStr
+ }
+
+}
+
+case class ConvertZonedIntegerParserUnparserHelper[S]()
+ extends ConvertZonedIntegerNumberParserUnparserHelper[JBigInt]() {
+
+ override def getNum(num: Number) = new JBigInt(num.toString)
+ override val xsdType = "integer"
+ override val prettyType = "Unlimited Size Integer"
+ override def isInvalidRange(n: java.lang.Number): Boolean = false
+ def min = -1 // ignored
+ def max = -1 // ignored
+}
+
+case class ConvertZonedNonNegativeIntegerParserUnparserHelper[S]()
+ extends ConvertZonedIntegerNumberParserUnparserHelper[JBigInt]() {
+
+ override def getNum(num: Number) = new JBigInt(num.toString)
+ override val xsdType = "nonNegativeInteger"
+ override val prettyType = "Unlimited Size Non Negative Integer"
+ override def isInvalidRange(n: java.lang.Number): Boolean = {
+ val value = new JBigDecimal(n.toString())
+ val isNegative = value.signum == -1
+ if (isNegative) return true
+ false
+ }
+ def min = -1 // ignored
+ def max = -1 // ignored
+}
+
+case class ConvertZonedLongParserUnparserHelper[S]()
+ extends ConvertZonedIntegerNumberParserUnparserHelper[Long]() {
+
+ override def getNum(num: Number) = num.longValue
+ override val xsdType = "long"
+ override val prettyType = "Long Integer"
+ val min = Long.MinValue
+ val max = Long.MaxValue
+}
+
+case class ConvertZonedIntParserUnparserHelper[S]()
+ extends ConvertZonedIntegerNumberParserUnparserHelper[Int]() {
+
+ override def getNum(num: Number) = num.intValue
+ override val xsdType = "int"
+ override val prettyType = "Integer"
+ val min = Int.MinValue.toLong
+ val max = Int.MaxValue.toLong
+}
+
+case class ConvertZonedShortParserUnparserHelper[S]()
+ extends ConvertZonedIntegerNumberParserUnparserHelper[Short]() {
+
+ override def getNum(num: Number) = num.shortValue
+ override val xsdType = "short"
+ override val prettyType = "Short Integer"
+ val min = Short.MinValue.toLong
+ val max = Short.MaxValue.toLong
+}
+
+case class ConvertZonedByteParserUnparserHelper[S]()
+ extends ConvertZonedIntegerNumberParserUnparserHelper[Byte]() {
+
+ override def getNum(num: Number) = num.byteValue
+ override val xsdType = "byte"
+ override val prettyType = "Byte"
+ val min = Byte.MinValue.toLong
+ val max = Byte.MaxValue.toLong
+}
+
+case class ConvertZonedUnsignedLongParserUnparserHelper[S]()
+ extends ConvertZonedIntegerNumberParserUnparserHelper[JBigInt]() {
+
+ override def getNum(num: Number) = new JBigInt(num.toString)
+ override val xsdType = "unsignedLong"
+ override val prettyType = "Unsigned Long"
+ override def isInvalidRange(jn: java.lang.Number) = {
+ jn match {
+ case n: JBigInt => {
+ n.compareTo(JBigInt.ZERO) < 0 || n.compareTo(JBigInt.ONE.shiftLeft(64)) >= 0
+ }
+ case _ => {
+ val n = jn.longValue()
+ n < 0 // note: the other side of the check is inherently ok since a Long must be smaller than an unsignedLong.
+ }
+ }
}
+ val min = 0.toLong
+ val max = -1.toLong // unused.
+}
+
+case class ConvertZonedUnsignedIntParserUnparserHelper[S]()
+ extends ConvertZonedIntegerNumberParserUnparserHelper[Long]() {
+
+ override def getNum(num: Number) = num.longValue
+ override val xsdType = "unsignedInt"
+ override val prettyType = "Unsigned Integer"
+ val min = 0L
+ val max = (1L << 32) - 1L
+}
+
+case class ConvertZonedUnsignedShortParserUnparserHelper[S]()
+ extends ConvertZonedIntegerNumberParserUnparserHelper[Int]() {
+
+ override def getNum(num: Number) = num.intValue
+ override val xsdType = "unsignedShort"
+ override val prettyType = "Unsigned Short"
+ val min = 0L
+ val max = (1L << 16) - 1L
+}
+
+case class ConvertZonedUnsignedByteParserUnparserHelper[S]()
+ extends ConvertZonedIntegerNumberParserUnparserHelper[Short]() {
+
+ override def getNum(num: Number) = num.shortValue
+ override val xsdType = "unsignedByte"
+ override val prettyType = "Unsigned Byte"
+ val min = 0L
+ val max = (1L << 8) - 1L
+}
+
+case class ConvertZonedDecimalParserUnparserHelper[S]()
+ extends ConvertZonedFloatingPointNumberParserUnparserHelper[JBigDecimal]() {
+
+ override def getNum(num: Number) = new JBigDecimal(num.toString)
+ override val xsdType = "decimal"
+ override val prettyType = "Unlimited Size Decimal"
+ override def isInvalidRange(n: java.lang.Number): Boolean = false
+
+ override def getStringFormat(n: JBigDecimal): String = {
+ n.toPlainString()
+ }
+}
+
+case class ConvertZonedDoubleParserUnparserHelper[S]()
+ extends ConvertZonedFloatingPointNumberParserUnparserHelper[Double]() {
+
+ val MAX_VALUE = new JBigDecimal(Double.MaxValue)
+ val MIN_VALUE = new JBigDecimal(Double.MinValue)
+
+ override def getNum(num: Number) = num.doubleValue
+ override val xsdType = "double"
+ override val prettyType = "Double"
+ def isInvalidRange(n: java.lang.Number): Boolean = {
+ val d = n.doubleValue() // This can truncate the number and void range checking
+ val bd = new JBigDecimal(n.toString)
+ (d.isNaN || bd.compareTo(MIN_VALUE) < 0 || bd.compareTo(MAX_VALUE) > 0)
+ }
+}
+
+case class ConvertZonedFloatParserUnparserHelper[S]()
+ extends ConvertZonedFloatingPointNumberParserUnparserHelper[Float]() {
+
+ val MAX_VALUE = new JBigDecimal(Float.MaxValue)
+ val MIN_VALUE = new JBigDecimal(Float.MinValue)
+
+ override def getNum(num: Number) = num.floatValue
+ override val xsdType = "float"
+ override val prettyType = "Float"
+ def isInvalidRange(n: java.lang.Number): Boolean = {
+ val f = n.floatValue() // This can truncated the number and void range checking
+ val bd = new JBigDecimal(n.toString)
+ (f.isNaN || bd.compareTo(MIN_VALUE) < 0 || bd.compareTo(MAX_VALUE) > 0)
+ }
+}
+
+abstract class ZonedFormatFactoryBase[S](parserHelper: ConvertZonedNumberParserUnparserHelperBase[S]) extends Serializable {
+
+ protected def checkUnique(
+ context: ThrowsSDE) = {
+
+ import scala.collection.mutable.{ HashMap, MultiMap, Set }
+
+ val mm = new HashMap[String, Set[String]] with MultiMap[String, String]
+
+ val dupes = mm.filter { case (k, s) => s.size > 1 }
+ val dupeStrings = dupes.map {
+ case (k, s) =>
+ "Non-distinct property '%s' found in: %s".format(k, s.mkString(", "))
+ }
+ context.schemaDefinitionUnless(dupeStrings.size == 0, dupeStrings.mkString("\n"))
+ }
+
+ protected def generateNumFormat(
+ checkPolicy: TextNumberCheckPolicy,
+ pattern: String,
+ rounding: TextNumberRounding,
+ roundingMode: Maybe[TextNumberRoundingMode],
+ roundingIncrement: MaybeDouble) = {
+
+ val dfs = new DecimalFormatSymbols()
+
+ val df = new DecimalFormat(pattern, dfs)
+
+ val cp = checkPolicy match {
+ case TextNumberCheckPolicy.Strict => true
+ case TextNumberCheckPolicy.Lax => false
+ }
+ df.setParseStrict(cp)
+
+ rounding match {
+ case TextNumberRounding.Pattern => {
+ df.setRoundingMode(JBigDecimal.ROUND_HALF_EVEN)
+ }
+ case TextNumberRounding.Explicit => {
+ val rm = roundingMode.get match {
+ case TextNumberRoundingMode.RoundCeiling => JBigDecimal.ROUND_CEILING
+ case TextNumberRoundingMode.RoundFloor => JBigDecimal.ROUND_FLOOR
+ case TextNumberRoundingMode.RoundDown => JBigDecimal.ROUND_DOWN
+ case TextNumberRoundingMode.RoundUp => JBigDecimal.ROUND_UP
+ case TextNumberRoundingMode.RoundHalfEven => JBigDecimal.ROUND_HALF_EVEN
+ case TextNumberRoundingMode.RoundHalfDown => JBigDecimal.ROUND_HALF_DOWN
+ case TextNumberRoundingMode.RoundHalfUp => JBigDecimal.ROUND_HALF_UP
+ case TextNumberRoundingMode.RoundUnnecessary => JBigDecimal.ROUND_UNNECESSARY
+ }
+ df.setRoundingMode(rm)
+ df.setRoundingIncrement(roundingIncrement.get)
+ }
+ }
+
+ if (parserHelper.isInt) {
+ df.setMaximumFractionDigits(0)
+ df.setDecimalSeparatorAlwaysShown(false)
+ df.setParseIntegerOnly(true)
+ }
+
+ df
+ }
+
+ protected def getRoundingIncrement(roundingInc: Double, context: ThrowsSDE): Double = {
+ context.schemaDefinitionUnless(roundingInc >= 0, "textNumberRoundingIncrement cannot be negative")
+ roundingInc
+ }
+
+ // as per ICU4J documentation, "DecimalFormat objects are not
+ // synchronized. Multiple threads should not access one formatter
+ // concurrently."
+ def getNumFormat(state: ParseOrUnparseState): ThreadLocal[DecimalFormat]
+
+}
+
+//
+// TODO: Complexity - why do we need both Static and Dynamic variants of this?
+// CachedDynamic hides this distinction (or should), as does CompiledExpression underneath that.
+
+class ZonedFormatFactoryStatic[S](context: ThrowsSDE,
+ parserHelper: ConvertZonedNumberParserUnparserHelperBase[S],
+ checkPolicy: TextNumberCheckPolicy,
+ pattern: String,
+ rounding: TextNumberRounding,
+ roundingMode: Maybe[TextNumberRoundingMode],
+ roundingIncrement: MaybeDouble)
+ extends ZonedFormatFactoryBase[S](parserHelper) {
+
+ val roundingInc: MaybeDouble = if (roundingIncrement.isEmpty) MaybeDouble.Nope else MaybeDouble { getRoundingIncrement(roundingIncrement.value, context) }
+
+ checkUnique(
+ context)
+
+ @transient lazy val numFormat = new ThreadLocal[DecimalFormat] {
+ override def initialValue() = {
+ generateNumFormat(
+ checkPolicy,
+ pattern,
+ rounding,
+ roundingMode,
+ roundingInc)
+ }
+ }
+
+ def getNumFormat(state: ParseOrUnparseState): ThreadLocal[DecimalFormat] = {
+ numFormat
+ }
+}
+
+class ZonedFormatFactoryDynamic[S](staticContext: ThrowsSDE,
+ parserHelper: ConvertZonedNumberParserUnparserHelperBase[S],
+ checkPolicy: TextNumberCheckPolicy,
+ pattern: String,
+ rounding: TextNumberRounding,
+ roundingMode: Maybe[TextNumberRoundingMode],
+ roundingIncrement: MaybeDouble)
+ extends ZonedFormatFactoryBase[S](parserHelper)
+ with Dynamic {
+
+ checkUnique(staticContext)
+
+ val roundingInc = if (roundingIncrement.isEmpty) MaybeDouble.Nope else MaybeDouble { getRoundingIncrement(roundingIncrement.value, staticContext) }
+
+ def getNumFormat(state: ParseOrUnparseState): ThreadLocal[DecimalFormat] = {
+
+ checkUnique(state)
+
+ val generatedNumFormat =
+ generateNumFormat(
+ checkPolicy,
+ pattern,
+ rounding,
+ roundingMode,
+ roundingInc)
+
+ val numFormat = new ThreadLocal[DecimalFormat] {
+ override def initialValue() = {
+ generatedNumFormat
+ }
+ }
+
+ numFormat
+ }
+
}