diff --git a/src/main/java/org/apache/xmlbeans/impl/xsd2inst/SampleXmlUtil.java b/src/main/java/org/apache/xmlbeans/impl/xsd2inst/SampleXmlUtil.java index ca9248357..fc19e4aaa 100644 --- a/src/main/java/org/apache/xmlbeans/impl/xsd2inst/SampleXmlUtil.java +++ b/src/main/java/org/apache/xmlbeans/impl/xsd2inst/SampleXmlUtil.java @@ -382,6 +382,24 @@ private String formatToLength(String s, SchemaType sType) { return result; } + /** + * The number of digits before the decimal point, which is the same for every + * representation of a value. + */ + private static long integerDigits(BigDecimal value) { + return (long) value.precision() - value.scale(); + } + + /** + * The largest value that fits in the given number of digits, ie 999...9. Computed as + * 10^digits - 1 rather than built as a string of nines, which the maximum number of + * characters allowed for a number rejects once the schema asks for more digits than + * that - xsd:totalDigits is a positiveInteger and has no such ceiling. + */ + private static BigDecimal widestValue(int digits) { + return BigDecimal.TEN.pow(digits).subtract(BigDecimal.ONE); + } + private String formatDecimal(String start, SchemaType sType) { BigDecimal result = MathUtil.parseAsBigDecimal(start); XmlDecimal xmlD; @@ -411,19 +429,22 @@ private String formatDecimal(String start, SchemaType sType) { if (xmlD != null) { totalDigits = MathUtil.toInt(xmlD.getBigDecimalValue()); - StringBuilder sb = new StringBuilder(totalDigits); - for (int i = 0; i < totalDigits; i++) { - sb.append('9'); - } - BigDecimal digitsLimit = MathUtil.parseAsBigDecimal(sb.toString()); - if (max != null && max.compareTo(digitsLimit) > 0) { - max = digitsLimit; - maxInclusive = true; + // the widest value the facet allows can only narrow a bound that is at least + // as wide, so it is computed only when it can bind - which also keeps it away + // from a totalDigits far larger than any bound the schema declares + if (max != null && integerDigits(max) >= totalDigits) { + BigDecimal digitsLimit = widestValue(totalDigits); + if (max.compareTo(digitsLimit) > 0) { + max = digitsLimit; + maxInclusive = true; + } } - digitsLimit = digitsLimit.negate(); - if (min != null && min.compareTo(digitsLimit) < 0) { - min = digitsLimit; - minInclusive = true; + if (min != null && integerDigits(min) >= totalDigits) { + BigDecimal digitsLimit = widestValue(totalDigits).negate(); + if (min.compareTo(digitsLimit) < 0) { + min = digitsLimit; + minInclusive = true; + } } } @@ -441,12 +462,8 @@ private String formatDecimal(String start, SchemaType sType) { } else { fractionDigits = MathUtil.toInt(xmlD.getBigDecimalValue()); if (fractionDigits > 0) { - StringBuilder sb = new StringBuilder("0."); - for (int i = 1; i < fractionDigits; i++) { - sb.append('0'); - } - sb.append('1'); - increment = MathUtil.parseAsBigDecimal(sb.toString()); + // 1 shifted fractionDigits places right, ie 0.00...1 + increment = BigDecimal.ONE.scaleByPowerOfTen(-fractionDigits); } else { increment = BigDecimal.ONE; } diff --git a/src/test/java/tools/xsd2inst/checkin/Xsd2InstTest.java b/src/test/java/tools/xsd2inst/checkin/Xsd2InstTest.java index ed9f47cc9..3bad771fa 100644 --- a/src/test/java/tools/xsd2inst/checkin/Xsd2InstTest.java +++ b/src/test/java/tools/xsd2inst/checkin/Xsd2InstTest.java @@ -62,6 +62,50 @@ void testBase64Binary() throws Exception { } } + private static String decimalSchema(String facets) { + return "" + + "" + + "" + + "" + facets + "" + + ""; + } + + private static String sampleFor(String facets) throws Exception { + XmlObject xsd = XmlObject.Factory.parse(decimalSchema(facets)); + SchemaTypeSystem sts = XmlBeans.compileXsd(new XmlObject[]{xsd}, + XmlBeans.getBuiltinTypeSystem(), new XmlOptions()); + return SampleXmlUtil.createSampleForType(sts.globalElements()[0]); + } + + @Test + void testDigitFacetsWiderThanTheNumberLengthLimit() throws Exception { + // xsd:totalDigits and xsd:fractionDigits are positiveInteger and so have no + // ceiling of their own; building the bounds they imply as strings of digits ran + // them into the maximum number of characters allowed for a number + String result = sampleFor(""); + assertTrue(result.contains(""), result); + try (InputStream docStream = new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8))) { + assertNotNull(DocumentHelper.readDocument(new XmlOptions(), docStream)); + } + + result = sampleFor(""); + assertTrue(result.contains(""), result); + try (InputStream docStream = new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8))) { + assertNotNull(DocumentHelper.readDocument(new XmlOptions(), docStream)); + } + } + + @Test + void testTotalDigitsStillNarrowsTheBounds() throws Exception { + // the sample seed for a decimal is 1000.00, which xsd:totalDigits has to pull + // down to the widest value the facet allows + String result = sampleFor(""); + assertTrue(result.contains("999"), result); + + String unconstrained = sampleFor(""); + assertTrue(unconstrained.contains("1000.00"), unconstrained); + } + @Test void testSampleXmlUtil() throws Exception { XmlObject xobj;