diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaGDateHolderEx.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaGDateHolderEx.java index 7f4a478e5..fa429c4b5 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/JavaGDateHolderEx.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaGDateHolderEx.java @@ -101,15 +101,20 @@ public static void validateValue(GDateSpecification v, SchemaType sType, Validat context.invalid(XmlErrorCodes.DATE, new Object[]{"Date (" + v + ") does not have the set of fields required for " + QNameHelper.readable(sType)}); } + // compareToGDate returns 2 for an incomparable pair, which satisfies no bound + int c; + if ((x = sType.getFacet(SchemaType.FACET_MIN_EXCLUSIVE)) != null) { - if (v.compareToGDate(g = ((XmlObjectBase) x).getGDateValue()) <= 0) { + c = v.compareToGDate(g = ((XmlObjectBase) x).getGDateValue()); + if (c <= 0 || c == 2) { context.invalid(XmlErrorCodes.DATATYPE_MIN_EXCLUSIVE_VALID, new Object[]{"date", v, g, QNameHelper.readable(sType)}); } } if ((x = sType.getFacet(SchemaType.FACET_MIN_INCLUSIVE)) != null) { - if (v.compareToGDate(g = ((XmlObjectBase) x).getGDateValue()) < 0) { + c = v.compareToGDate(g = ((XmlObjectBase) x).getGDateValue()); + if (c < 0 || c == 2) { context.invalid(XmlErrorCodes.DATATYPE_MIN_INCLUSIVE_VALID, new Object[]{"date", v, g, QNameHelper.readable(sType)}); } diff --git a/src/main/java/org/apache/xmlbeans/impl/values/JavaGDurationHolderEx.java b/src/main/java/org/apache/xmlbeans/impl/values/JavaGDurationHolderEx.java index 9612dd2ab..875fc3dc7 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/JavaGDurationHolderEx.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/JavaGDurationHolderEx.java @@ -102,15 +102,20 @@ public static void validateValue(GDurationSpecification v, SchemaType sType, Val XmlObject x; GDuration g; + // compareToGDuration returns 2 for an incomparable pair, which satisfies no bound + int c; + if ((x = sType.getFacet(SchemaType.FACET_MIN_EXCLUSIVE)) != null) { - if (v.compareToGDuration(g = ((XmlObjectBase) x).getGDurationValue()) <= 0) { + c = v.compareToGDuration(g = ((XmlObjectBase) x).getGDurationValue()); + if (c <= 0 || c == 2) { context.invalid(XmlErrorCodes.DATATYPE_MIN_EXCLUSIVE_VALID, new Object[]{"duration", v, g, QNameHelper.readable(sType)}); } } if ((x = sType.getFacet(SchemaType.FACET_MIN_INCLUSIVE)) != null) { - if (v.compareToGDuration(g = ((XmlObjectBase) x).getGDurationValue()) < 0) { + c = v.compareToGDuration(g = ((XmlObjectBase) x).getGDurationValue()); + if (c < 0 || c == 2) { context.invalid(XmlErrorCodes.DATATYPE_MIN_INCLUSIVE_VALID, new Object[]{"duration", v, g, QNameHelper.readable(sType)}); } diff --git a/src/test/java/misc/checkin/IncomparableMinFacetValidateTest.java b/src/test/java/misc/checkin/IncomparableMinFacetValidateTest.java new file mode 100644 index 000000000..75deb5713 --- /dev/null +++ b/src/test/java/misc/checkin/IncomparableMinFacetValidateTest.java @@ -0,0 +1,115 @@ +/* Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package misc.checkin; + +import org.apache.xmlbeans.SchemaTypeLoader; +import org.apache.xmlbeans.XmlBeans; +import org.apache.xmlbeans.XmlError; +import org.apache.xmlbeans.XmlObject; +import org.apache.xmlbeans.XmlOptions; +import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class IncomparableMinFacetValidateTest { + + // dateTime and duration are only partially ordered. A dateTime without a + // timezone is incomparable with one that has a timezone when the two are + // within 14 hours of each other, and P1M is incomparable with P30D because a + // month is 28 to 31 days long. + private static final String DT_BOUND = "2000-01-01T12:00:00Z"; + private static final String DT_INCOMPARABLE = "2000-01-01T12:00:00"; + private static final String DUR_BOUND = "P30D"; + private static final String DUR_INCOMPARABLE = "P1M"; + + private static final String[] FACETS = { + "minInclusive", "minExclusive", "maxInclusive", "maxExclusive" + }; + + private static String element(String name, String base, String facet, String bound) { + return + " " + + " " + + " " + + " " + + " " + + " " + + " "; + } + + private static String xsd() { + StringBuilder sb = new StringBuilder( + ""); + for (String facet : FACETS) { + sb.append(element("dt-" + facet, "dateTime", facet, DT_BOUND)); + sb.append(element("dur-" + facet, "duration", facet, DUR_BOUND)); + } + return sb.append("").toString(); + } + + private static boolean validate(String element, String value) throws Exception { + SchemaTypeLoader loader = XmlBeans.loadXsd(new XmlObject[]{SchemaDocument.Factory.parse(xsd())}); + XmlObject doc = loader.parse( + "" + value + "", null, null); + List errors = new ArrayList<>(); + return doc.validate(new XmlOptions().setErrorListener(errors)); + } + + @Test + void incomparableDateTimeFailsEveryBound() throws Exception { + // not greater than, less than, or equal to the bound, so no facet is satisfied + for (String facet : FACETS) { + assertFalse(validate("dt-" + facet, DT_INCOMPARABLE), facet); + } + } + + @Test + void incomparableDurationFailsEveryBound() throws Exception { + for (String facet : FACETS) { + assertFalse(validate("dur-" + facet, DUR_INCOMPARABLE), facet); + } + } + + @Test + void comparableDateTimeStillChecked() throws Exception { + // equal to the bound + assertTrue(validate("dt-minInclusive", "2000-01-01T12:00:00Z")); + assertFalse(validate("dt-minExclusive", "2000-01-01T12:00:00Z")); + // more than 14 hours past the bound, so the missing timezone does not matter + assertTrue(validate("dt-minInclusive", "2000-01-03T12:00:00")); + assertTrue(validate("dt-minExclusive", "2000-01-03T12:00:00")); + // more than 14 hours short of it + assertFalse(validate("dt-minInclusive", "1999-12-30T12:00:00")); + assertFalse(validate("dt-minExclusive", "1999-12-30T12:00:00")); + } + + @Test + void comparableDurationStillChecked() throws Exception { + assertTrue(validate("dur-minInclusive", "P30D")); + assertFalse(validate("dur-minExclusive", "P30D")); + // two months are at least 59 days + assertTrue(validate("dur-minInclusive", "P2M")); + assertTrue(validate("dur-minExclusive", "P2M")); + assertFalse(validate("dur-minInclusive", "P27D")); + assertFalse(validate("dur-minExclusive", "P27D")); + } +}