diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ComparisonFunctions.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ComparisonFunctions.java index e06f048c5..3c971c03d 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ComparisonFunctions.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ComparisonFunctions.java @@ -362,7 +362,9 @@ public static IBooleanItem booleanCompare(@NonNull IBooleanItem left, @NonNull O * @return the comparison result */ @NonNull - public static IBooleanItem dateTimeCompare(@NonNull IDateTimeItem left, @NonNull Operator operator, + public static IBooleanItem dateTimeCompare( + @NonNull IDateTimeItem left, + @NonNull Operator operator, @NonNull IDateTimeItem right) { IBooleanItem retval; switch (operator) { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/OperationFunctions.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/OperationFunctions.java index ac2c09ae3..1e8f4f46f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/OperationFunctions.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/OperationFunctions.java @@ -52,6 +52,8 @@ public final class OperationFunctions { @NonNull private static final IDateItem DATE_1972_12_31 = IDateItem.valueOf(ObjectUtils.notNull(LocalDate.of(1972, 12, 31))); + @NonNull + private static final ITimeItem TIME_00_00_00 = ITimeItem.valueOf(OffsetTime.of(0, 0, 0, 0, ZoneOffset.UTC), false); /** * Identifies the types and substypes that support aggregation. @@ -602,7 +604,9 @@ public static IDecimalItem opDivideDayTimeDurationByDayTimeDuration( */ @NonNull public static IBooleanItem opDateEqual(@NonNull IDateItem arg1, @NonNull IDateItem arg2) { - return IBooleanItem.valueOf(arg1.asZonedDateTime().equals(arg2.asZonedDateTime())); + IDateTimeItem time1 = IDateTimeItem.valueOf(arg1, TIME_00_00_00); + IDateTimeItem time2 = IDateTimeItem.valueOf(arg2, TIME_00_00_00); + return opDateTimeEqual(time1, time2); } /** @@ -618,7 +622,7 @@ public static IBooleanItem opDateEqual(@NonNull IDateItem arg1, @NonNull IDateIt */ @NonNull public static IBooleanItem opDateTimeEqual(@NonNull IDateTimeItem arg1, @NonNull IDateTimeItem arg2) { - return IBooleanItem.valueOf(arg1.asZonedDateTime().equals(arg2.asZonedDateTime())); + return IBooleanItem.valueOf(arg1.asZonedDateTime().isEqual(arg2.asZonedDateTime())); } /** @@ -700,7 +704,7 @@ public static IBooleanItem opDateGreaterThan(@NonNull IDateItem arg1, @NonNull I */ @NonNull public static IBooleanItem opDateTimeGreaterThan(@NonNull IDateTimeItem arg1, @NonNull IDateTimeItem arg2) { - return IBooleanItem.valueOf(arg1.asZonedDateTime().compareTo(arg2.asZonedDateTime()) > 0); + return IBooleanItem.valueOf(arg1.asZonedDateTime().isAfter(arg2.asZonedDateTime())); } /** @@ -828,7 +832,7 @@ public static IBooleanItem opTimeLessThan( public static IBooleanItem opDateTimeLessThan( @NonNull IDateTimeItem arg1, @NonNull IDateTimeItem arg2) { - return IBooleanItem.valueOf(arg1.asZonedDateTime().compareTo(arg2.asZonedDateTime()) < 0); + return IBooleanItem.valueOf(arg1.asZonedDateTime().isBefore(arg2.asZonedDateTime())); } /** diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractCalendarMapKey.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractCalendarMapKey.java new file mode 100644 index 000000000..b4c808a5c --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractCalendarMapKey.java @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.impl; + +import gov.nist.secauto.metaschema.core.metapath.item.function.ICalendarMapKey; + +public abstract class AbstractCalendarMapKey + extends AbstractMapKey + implements ICalendarMapKey { + + @Override + public boolean equals(Object obj) { + return this == obj + || obj instanceof ICalendarMapKey + && getKey().equals(((ICalendarMapKey) obj).getKey()); + } + + @Override + public int hashCode() { + return getKey().hashCode(); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractDecimalMapKey.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractDecimalMapKey.java index 47abd6a2f..a5a4999cb 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractDecimalMapKey.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractDecimalMapKey.java @@ -8,22 +8,25 @@ import gov.nist.secauto.metaschema.core.metapath.item.function.IDecimalMapKey; import gov.nist.secauto.metaschema.core.metapath.item.function.IMapKey; +import nl.talsmasoftware.lazy4j.Lazy; + /** * An implementation of a {@link IMapKey} that uses a string-based value. */ public abstract class AbstractDecimalMapKey extends AbstractMapKey implements IDecimalMapKey { + private final Lazy hashCode = Lazy.lazy(() -> asDecimal().stripTrailingZeros().hashCode()); @Override public int hashCode() { - return asDecimal().hashCode(); + return hashCode.get(); } @Override public boolean equals(Object obj) { return this == obj // TODO: implement fn:codepoint-equal per spec - || obj instanceof IDecimalMapKey && asDecimal().equals(((IDecimalMapKey) obj).asDecimal()); + || obj instanceof IDecimalMapKey && asDecimal().compareTo(((IDecimalMapKey) obj).asDecimal()) == 0; } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractMapKey.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractMapKey.java index 48fe859f8..65b4a601b 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractMapKey.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractMapKey.java @@ -8,6 +8,12 @@ import gov.nist.secauto.metaschema.core.metapath.item.function.IMapKey; public abstract class AbstractMapKey implements IMapKey { + @Override + public abstract int hashCode(); + + @Override + public abstract boolean equals(Object obj); + @Override public String toString() { return getKey().toSignature(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractStringMapKey.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractStringMapKey.java index 92a990b3f..1c08baa11 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractStringMapKey.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractStringMapKey.java @@ -24,6 +24,6 @@ public int hashCode() { public boolean equals(Object obj) { return this == obj // TODO: implement fn:codepoint-equal per spec - || obj instanceof IStringMapKey && getKey().asString().equals(((IStringMapKey) obj).getKey().asString()); + || obj instanceof IStringMapKey && asString().equals(((IStringMapKey) obj).asString()); } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractTemporalMapKey.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractTemporalMapKey.java deleted file mode 100644 index 0edf61765..000000000 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractTemporalMapKey.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SPDX-FileCopyrightText: none - * SPDX-License-Identifier: CC0-1.0 - */ - -package gov.nist.secauto.metaschema.core.metapath.impl; - -import gov.nist.secauto.metaschema.core.metapath.item.atomic.ITemporalItem; -import gov.nist.secauto.metaschema.core.metapath.item.function.ITemporalMapKey; - -import java.time.ZoneOffset; - -import edu.umd.cs.findbugs.annotations.NonNull; - -public abstract class AbstractTemporalMapKey - extends AbstractMapKey - implements ITemporalMapKey { - @Override - public int hashCode() { - ITemporalItem temporal = asTemporalItem(); - int hash = 7; - hash = 31 * hash + temporal.getYear(); - hash = 31 * hash + temporal.getDay(); - hash = 31 * hash + temporal.getHour(); - hash = 31 * hash + temporal.getYear(); - hash = 31 * hash + temporal.getMinute(); - hash = 31 * hash + temporal.getSecond(); - hash = 31 * hash + temporal.getNano(); - - ZoneOffset offset = temporal.getZoneOffset(); - return 31 * hash + (offset == null ? 0 : offset.hashCode()); - } - - @Override - public boolean equals(Object obj) { - return this == obj - || obj instanceof ITemporalMapKey && equalsInternal((ITemporalMapKey) obj); - } - - private boolean equalsInternal(@NonNull ITemporalMapKey other) { - ITemporalItem focus = asTemporalItem(); - ITemporalItem that = other.asTemporalItem(); - return focus.hasTimezone() == that.hasTimezone() && focus.deepEquals(that); - } -} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateItem.java index 93c8ad22a..4a0882d43 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateItem.java @@ -48,6 +48,11 @@ default IAtomicOrUnionType getType() { return type(); } + @Override + default Class getItemBaseType() { + return IDateItem.class; + } + /** * Construct a new date item using the provided string {@code value}. * diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateTimeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateTimeItem.java index 994f1f80f..ef621b197 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateTimeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateTimeItem.java @@ -18,6 +18,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetTime; +import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @@ -50,6 +51,11 @@ default IAtomicOrUnionType getType() { return type(); } + @Override + default Class getItemBaseType() { + return IDateTimeItem.class; + } + /** * Construct a new date/time item using the provided string {@code value}. * @@ -73,12 +79,32 @@ static IDateTimeItem valueOf(@NonNull String value) { @NonNull static IDateTimeItem valueOf(@NonNull IDateItem date, @NonNull ITimeItem time) { - throw new UnsupportedOperationException(); + ZonedDateTime zDate = ObjectUtils.notNull(date.asZonedDateTime()); + ZoneId tzDate = date.hasTimezone() ? zDate.getZone() : null; + OffsetTime oTime = ObjectUtils.notNull(time.asOffsetTime()); + ZoneId tzTime = time.hasTimezone() ? oTime.getOffset() : null; + + if (tzDate != null && tzTime != null && !tzDate.equals(tzTime)) { + // exception + + } + + // either both have the same timezone, both are null, or only one has a timezone + ZoneId zone = tzDate == null + ? tzTime == null ? null : tzTime + : tzDate; + + return valueOf( + ObjectUtils.notNull(ZonedDateTime.of( + zDate.toLocalDate(), + oTime.toLocalTime(), + zone == null ? ZoneOffset.UTC : zone)), + zone != null); } @NonNull - static IDateTimeItem valueOf(@NonNull ICalendarTemporalItem date) { - return valueOf(date.asZonedDateTime(), date.hasTimezone()); + static IDateTimeItem valueOf(@NonNull ICalendarTemporalItem item) { + return valueOf(item.asZonedDateTime(), item.hasTimezone()); } /** diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/ITemporalItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/ITemporalItem.java index c5b490af4..5f8a3dffc 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/ITemporalItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/ITemporalItem.java @@ -19,6 +19,9 @@ */ public interface ITemporalItem extends IAnyAtomicItem { + @NonNull + Class getItemBaseType(); + int getYear(); int getMonth(); @@ -113,14 +116,4 @@ default IDayTimeDurationItem getOffset() { */ @NonNull ITemporalItem replaceTimezone(@Nullable IDayTimeDurationItem offset); - - /** - * Compares this value with the argument. - * - * @param item - * the item to compare with this value - * @return a negative integer, zero, or a positive integer if this value is less - * than, equal to, or greater than the {@code item}. - */ - int compareTo(@NonNull ITemporalItem item); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/ITimeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/ITimeItem.java index 3090f71c2..cacfb0891 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/ITimeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/ITimeItem.java @@ -46,6 +46,11 @@ default IAtomicOrUnionType getType() { return type(); } + @Override + default Class getItemBaseType() { + return ITimeItem.class; + } + /** * Construct a new date/time item using the provided string {@code value}. * diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractDateItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractDateItem.java index a071c20db..f70c01ce1 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractDateItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractDateItem.java @@ -5,7 +5,11 @@ package gov.nist.secauto.metaschema.core.metapath.item.atomic.impl; +import gov.nist.secauto.metaschema.core.metapath.impl.AbstractCalendarMapKey; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateItem; +import gov.nist.secauto.metaschema.core.metapath.item.function.IMapKey; + +import java.time.ZoneOffset; import edu.umd.cs.findbugs.annotations.NonNull; @@ -31,13 +35,36 @@ protected AbstractDateItem(@NonNull TYPE value) { @Override public int hashCode() { - return asZonedDateTime().hashCode(); + int result = asZonedDateTime().withZoneSameInstant(ZoneOffset.UTC).hashCode(); + result = hasTimezone() ? 31 * result * Boolean.hashCode(hasTimezone()) : result; + return 31 * result * getClass().hashCode(); } @SuppressWarnings("PMD.OnlyOneReturn") @Override public boolean equals(Object obj) { - return this == obj - || obj instanceof IDateItem && compareTo((IDateItem) obj) == 0; + if (this == obj) { + return true; + } + + if (obj instanceof IDateItem) { + IDateItem that = (IDateItem) obj; + return hasTimezone() == that.hasTimezone() + && deepEquals(that); + } + return false; + } + + @Override + public IMapKey asMapKey() { + return new MapKey(); + } + + private final class MapKey + extends AbstractCalendarMapKey { + @Override + public IDateItem getKey() { + return AbstractDateItem.this; + } } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractDateTimeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractDateTimeItem.java index 790cfe4d8..af8625deb 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractDateTimeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractDateTimeItem.java @@ -5,7 +5,11 @@ package gov.nist.secauto.metaschema.core.metapath.item.atomic.impl; +import gov.nist.secauto.metaschema.core.metapath.impl.AbstractCalendarMapKey; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateTimeItem; +import gov.nist.secauto.metaschema.core.metapath.item.function.IMapKey; + +import java.time.ZoneOffset; import edu.umd.cs.findbugs.annotations.NonNull; @@ -31,13 +35,35 @@ protected AbstractDateTimeItem(@NonNull TYPE value) { @Override public int hashCode() { - return asZonedDateTime().hashCode(); + int result = asZonedDateTime().withZoneSameInstant(ZoneOffset.UTC).hashCode(); + result = hasTimezone() ? 31 * result * Boolean.hashCode(hasTimezone()) : result; + return 31 * result * getClass().hashCode(); } @SuppressWarnings("PMD.OnlyOneReturn") @Override public boolean equals(Object obj) { - return this == obj - || obj instanceof IDateTimeItem && compareTo((IDateTimeItem) obj) == 0; + if (this == obj) { + return true; + } + + if (obj instanceof IDateTimeItem) { + IDateTimeItem that = (IDateTimeItem) obj; + return hasTimezone() == that.hasTimezone() && deepEquals(that); + } + return false; + } + + @Override + public IMapKey asMapKey() { + return new MapKey(); + } + + private final class MapKey + extends AbstractCalendarMapKey { + @Override + public IDateTimeItem getKey() { + return AbstractDateTimeItem.this; + } } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractTemporalItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractTemporalItem.java index 02cd708c0..e4304c096 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractTemporalItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractTemporalItem.java @@ -5,10 +5,8 @@ package gov.nist.secauto.metaschema.core.metapath.item.atomic.impl; -import gov.nist.secauto.metaschema.core.metapath.impl.AbstractTemporalMapKey; import gov.nist.secauto.metaschema.core.metapath.item.atomic.AbstractAnyAtomicItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.ITemporalItem; -import gov.nist.secauto.metaschema.core.metapath.item.function.IMapKey; import java.util.Comparator; @@ -46,29 +44,4 @@ protected AbstractTemporalItem(@NonNull TYPE value) { protected String getValueSignature() { return "'" + asString() + "'"; } - - @Override - public int compareTo(@NonNull ITemporalItem item) { - return COMPARATOR.compare(this, item); - } - - @Override - public IMapKey asMapKey() { - return new MapKey(); - } - - protected final class MapKey - extends AbstractTemporalMapKey { - - @Override - public ITemporalItem getKey() { - return AbstractTemporalItem.this; - } - - @Override - public ITemporalItem asTemporalItem() { - return getKey(); - } - - } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractTimeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractTimeItem.java index 0c66f2dd1..880fd3858 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractTimeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractTimeItem.java @@ -5,9 +5,12 @@ package gov.nist.secauto.metaschema.core.metapath.item.atomic.impl; +import gov.nist.secauto.metaschema.core.metapath.impl.AbstractMapKey; import gov.nist.secauto.metaschema.core.metapath.item.atomic.ITimeItem; import gov.nist.secauto.metaschema.core.metapath.item.function.IMapKey; +import java.time.ZoneOffset; + import edu.umd.cs.findbugs.annotations.NonNull; /** @@ -32,18 +35,46 @@ protected AbstractTimeItem(@NonNull TYPE value) { @Override public int hashCode() { - return asOffsetTime().hashCode(); + return asOffsetTime().withOffsetSameInstant(ZoneOffset.UTC).hashCode(); } @SuppressWarnings("PMD.OnlyOneReturn") @Override public boolean equals(Object obj) { - return this == obj - || obj instanceof ITimeItem && compareTo((ITimeItem) obj) == 0; + if (this == obj) { + return true; + } + + if (obj instanceof ITimeItem) { + ITimeItem that = (ITimeItem) obj; + return hasTimezone() == that.hasTimezone() && deepEquals(that); + } + return false; } @Override public IMapKey asMapKey() { return new MapKey(); } + + protected final class MapKey + extends AbstractMapKey { + + @Override + public ITimeItem getKey() { + return AbstractTimeItem.this; + } + + @Override + public boolean equals(Object obj) { + return this == obj + || obj instanceof AbstractTimeItem.MapKey + && getKey().equals(((AbstractTimeItem.MapKey) obj).getKey()); + } + + @Override + public int hashCode() { + return getKey().hashCode(); + } + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/UuidItemImpl.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/UuidItemImpl.java index 022f0bc12..166eb6fb3 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/UuidItemImpl.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/UuidItemImpl.java @@ -12,10 +12,12 @@ import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IUuidItem; import gov.nist.secauto.metaschema.core.metapath.item.function.IMapKey; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.UUID; import edu.umd.cs.findbugs.annotations.NonNull; +import nl.talsmasoftware.lazy4j.Lazy; /** * An implementation of a Metapath atomic item containing a UUID data value. @@ -23,6 +25,7 @@ public class UuidItemImpl extends AbstractAnyAtomicItem implements IUuidItem { + private final Lazy stringValue = Lazy.lazy(super::asString); /** * Construct a new item with the provided {@code value}. @@ -39,6 +42,11 @@ public UUID asUuid() { return getValue(); } + @Override + public String asString() { + return ObjectUtils.notNull(stringValue.get()); + } + @Override public UuidAdapter getJavaTypeAdapter() { return MetaschemaDataTypeProvider.UUID; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/ICalendarMapKey.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/ICalendarMapKey.java new file mode 100644 index 000000000..3625563fa --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/ICalendarMapKey.java @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.item.function; + +import gov.nist.secauto.metaschema.core.metapath.item.atomic.ICalendarTemporalItem; + +public interface ICalendarMapKey extends IMapKey { + @Override + ICalendarTemporalItem getKey(); +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/ITemporalMapKey.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/ITemporalMapKey.java index 0e738ae42..27ad43247 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/ITemporalMapKey.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/ITemporalMapKey.java @@ -5,11 +5,6 @@ package gov.nist.secauto.metaschema.core.metapath.item.function; -import gov.nist.secauto.metaschema.core.metapath.item.atomic.ITemporalItem; - -import edu.umd.cs.findbugs.annotations.NonNull; - public interface ITemporalMapKey extends IMapKey { - @NonNull - ITemporalItem asTemporalItem(); + // no additional methods } diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/datatype/adapter/UuidAdapterTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/datatype/adapter/UuidAdapterTest.java new file mode 100644 index 000000000..a97591c71 --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/datatype/adapter/UuidAdapterTest.java @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.datatype.adapter; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.UUID; +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; + +class UuidAdapterTest { + private static final UuidAdapter ADAPTER = new UuidAdapter(); + + /** + * Provides test cases for date-time parsing. + *

+ * Each argument contains: + *

    + *
  • input string to parse + *
  • boolean indicating if the datetime is ambiguous (no timezone) + *
  • expected ZonedDateTime result + *
+ * + * @return Stream of test cases + */ + private static Stream provideValues() { + return Stream.of( + // Cases without timezone (ambiguous) + Arguments.of( + "f0b23719-2687-407c-b22f-f4b7ee832571"), + Arguments.of( + "8eb2fb0f-47ac-40be-adb0-1a70fe7fad76")); + } + + @ParameterizedTest + @MethodSource("provideValues") + void testSimpleDateTime(@NonNull String expected) { + UUID uuid = ADAPTER.parse(expected); + assertAll( + () -> assertEquals(expected, uuid.toString()), + () -> assertEquals(expected, ADAPTER.asString(uuid))); + } +} diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/TestUtils.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/TestUtils.java index aae5ec896..2f454810b 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/TestUtils.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/TestUtils.java @@ -20,6 +20,7 @@ import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.ITimeItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IUuidItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IYearMonthDurationItem; import gov.nist.secauto.metaschema.core.metapath.item.function.IArrayItem; import gov.nist.secauto.metaschema.core.metapath.item.function.IMapItem; @@ -30,7 +31,6 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; -import java.net.URI; import java.util.Map; import edu.umd.cs.findbugs.annotations.NonNull; @@ -206,6 +206,18 @@ public static IStringItem string(@NonNull String value) { return IStringItem.valueOf(value); } + /** + * Create a uuid item using the provided value. + * + * @param value + * the uuid value + * @return the uuid item + */ + @NonNull + public static IUuidItem uuid(@NonNull String value) { + return IUuidItem.valueOf(value); + } + /** * Create a uri item using the provided value. * @@ -215,9 +227,7 @@ public static IStringItem string(@NonNull String value) { */ @NonNull public static IAnyUriItem uri(@NonNull String value) { - URI uri = URI.create(value); - assert uri != null; - return IAnyUriItem.valueOf(uri); + return IAnyUriItem.valueOf(value); } /** diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/impl/OperationFunctionsTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/impl/OperationFunctionsTest.java index 02d329c7c..0336a8a1d 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/impl/OperationFunctionsTest.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/impl/OperationFunctionsTest.java @@ -25,6 +25,7 @@ import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IYearMonthDurationItem; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -40,6 +41,7 @@ class OperationFunctionsTest { @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) + @DisplayName("Functions and operators on numerics") class Numeric { private Stream provideValuesOpNumericAdd() { return Stream.of( @@ -51,6 +53,7 @@ private Stream provideValuesOpNumericAdd() { @ParameterizedTest @MethodSource("provideValuesOpNumericAdd") + @DisplayName("op:numeric-add") void testOpNumericAdd( @NonNull INumericItem expected, @NonNull INumericItem addend1, @@ -68,6 +71,7 @@ private Stream provideValuesOpNumericSubtract() { @ParameterizedTest @MethodSource("provideValuesOpNumericSubtract") + @DisplayName("op:numeric-subtract") void testOpNumericSubtract( @NonNull INumericItem expected, @NonNull INumericItem minuend, @@ -87,6 +91,7 @@ private Stream provideValuesOpNumericMultiply() { @ParameterizedTest @MethodSource("provideValuesOpNumericMultiply") + @DisplayName("op:numeric-multiply") void testOpNumericMultiply( @NonNull INumericItem expected, @NonNull INumericItem multiplicand, @@ -94,82 +99,95 @@ void testOpNumericMultiply( assertEquals(expected, OperationFunctions.opNumericMultiply(multiplicand, multiplier)); } - private Stream provideValuesOpNumericDivide() { - return Stream.of( - Arguments.of(decimal("0"), integer(0), integer(1)), - Arguments.of(decimal("1"), integer(1), integer(1)), - Arguments.of(decimal("0.5"), integer(1), integer(2)), - Arguments.of(decimal("1.0"), decimal("1.0"), integer(1)), - Arguments.of(decimal("1.0"), integer(1), decimal("1.0")), - Arguments.of(decimal("1.0"), decimal("1.0"), decimal("1.0"))); - } - - @ParameterizedTest - @MethodSource("provideValuesOpNumericDivide") - void testOpNumericDivide( - @NonNull INumericItem expected, - @NonNull INumericItem dividend, - @NonNull INumericItem divisor) { - assertEquals(expected, OperationFunctions.opNumericDivide(dividend, divisor)); - } - - private Stream provideValuesOpNumericDivideByZero() { - return Stream.of( - Arguments.of(integer(0), integer(0)), - Arguments.of(decimal("1.0"), integer(0)), - Arguments.of(integer(1), decimal("0.0")), - Arguments.of(integer(1), decimal("0"))); - } - - @ParameterizedTest - @MethodSource("provideValuesOpNumericDivideByZero") - void testOpNumericDivideByZero( - @NonNull INumericItem dividend, - @NonNull INumericItem divisor) { - ArithmeticFunctionException thrown = assertThrows(ArithmeticFunctionException.class, () -> { - OperationFunctions.opNumericDivide(dividend, divisor); - }); - assertEquals(ArithmeticFunctionException.DIVISION_BY_ZERO, thrown.getCode()); - } - - private Stream provideValuesOpNumericIntegerDivide() { - return Stream.of( - Arguments.of(integer(3), integer(10), integer(3)), - Arguments.of(integer(-1), integer(3), integer(-2)), - Arguments.of(integer(-1), integer(-3), integer(2)), - Arguments.of(integer(3), decimal("9.0"), integer(3)), - Arguments.of(integer(-1), decimal("-3.5"), integer(3)), - Arguments.of(integer(0), decimal("3.0"), integer(4)), - Arguments.of(integer(5), decimal("3.1E1"), integer(6)), - Arguments.of(integer(4), decimal("3.1E1"), integer(7))); - } - - @ParameterizedTest - @MethodSource("provideValuesOpNumericIntegerDivide") - void testOpNumericIntegerDivide( - @NonNull IIntegerItem expected, - @NonNull INumericItem dividend, - @NonNull INumericItem divisor) { - assertEquals(expected, OperationFunctions.opNumericIntegerDivide(dividend, divisor)); - } - - private Stream provideValuesOpNumericIntegerDivideByZero() { - return Stream.of( - Arguments.of(integer(0), integer(0)), - Arguments.of(decimal("1.0"), integer(0)), - Arguments.of(integer(1), decimal("0.0")), - Arguments.of(integer(1), decimal("0"))); - } - - @ParameterizedTest - @MethodSource("provideValuesOpNumericIntegerDivideByZero") - void testOpNumericIntegerDivideByZero( - @NonNull INumericItem dividend, - @NonNull INumericItem divisor) { - ArithmeticFunctionException thrown = assertThrows(ArithmeticFunctionException.class, () -> { - OperationFunctions.opNumericIntegerDivide(dividend, divisor); - }); - assertEquals(ArithmeticFunctionException.DIVISION_BY_ZERO, thrown.getCode()); + @Nested + @DisplayName("op:numeric-divide") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class NumericDivide { + private Stream provideValuesOpNumericDivide() { + return Stream.of( + Arguments.of(decimal("0"), integer(0), integer(1)), + Arguments.of(decimal("1"), integer(1), integer(1)), + Arguments.of(decimal("0.5"), integer(1), integer(2)), + Arguments.of(decimal("1.0"), decimal("1.0"), integer(1)), + Arguments.of(decimal("1.0"), integer(1), decimal("1.0")), + Arguments.of(decimal("1.0"), decimal("1.0"), decimal("1.0"))); + } + + @ParameterizedTest + @MethodSource("provideValuesOpNumericDivide") + @DisplayName("op:numeric-divide - known good") + void testOpNumericDivide( + @NonNull INumericItem expected, + @NonNull INumericItem dividend, + @NonNull INumericItem divisor) { + assertEquals(expected, OperationFunctions.opNumericDivide(dividend, divisor)); + } + + private Stream provideValuesOpNumericDivideByZero() { + return Stream.of( + Arguments.of(integer(0), integer(0)), + Arguments.of(decimal("1.0"), integer(0)), + Arguments.of(integer(1), decimal("0.0")), + Arguments.of(integer(1), decimal("0"))); + } + + @ParameterizedTest + @MethodSource("provideValuesOpNumericDivideByZero") + @DisplayName("op:numeric-divide - by zero") + void testOpNumericDivideByZero( + @NonNull INumericItem dividend, + @NonNull INumericItem divisor) { + ArithmeticFunctionException thrown = assertThrows(ArithmeticFunctionException.class, () -> { + OperationFunctions.opNumericDivide(dividend, divisor); + }); + assertEquals(ArithmeticFunctionException.DIVISION_BY_ZERO, thrown.getCode()); + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + @DisplayName("op:numeric-integer-divide") + class NumericIntegerDivide { + private Stream provideValuesOpNumericIntegerDivide() { + return Stream.of( + Arguments.of(integer(3), integer(10), integer(3)), + Arguments.of(integer(-1), integer(3), integer(-2)), + Arguments.of(integer(-1), integer(-3), integer(2)), + Arguments.of(integer(3), decimal("9.0"), integer(3)), + Arguments.of(integer(-1), decimal("-3.5"), integer(3)), + Arguments.of(integer(0), decimal("3.0"), integer(4)), + Arguments.of(integer(5), decimal("3.1E1"), integer(6)), + Arguments.of(integer(4), decimal("3.1E1"), integer(7))); + } + + @ParameterizedTest + @MethodSource("provideValuesOpNumericIntegerDivide") + @DisplayName("op:numeric-integer-divide - by zero") + void testOpNumericIntegerDivide( + @NonNull IIntegerItem expected, + @NonNull INumericItem dividend, + @NonNull INumericItem divisor) { + assertEquals(expected, OperationFunctions.opNumericIntegerDivide(dividend, divisor)); + } + + private Stream provideValuesOpNumericIntegerDivideByZero() { + return Stream.of( + Arguments.of(integer(0), integer(0)), + Arguments.of(decimal("1.0"), integer(0)), + Arguments.of(integer(1), decimal("0.0")), + Arguments.of(integer(1), decimal("0"))); + } + + @ParameterizedTest + @MethodSource("provideValuesOpNumericIntegerDivideByZero") + void testOpNumericIntegerDivideByZero( + @NonNull INumericItem dividend, + @NonNull INumericItem divisor) { + ArithmeticFunctionException thrown = assertThrows(ArithmeticFunctionException.class, () -> { + OperationFunctions.opNumericIntegerDivide(dividend, divisor); + }); + assertEquals(ArithmeticFunctionException.DIVISION_BY_ZERO, thrown.getCode()); + } } private Stream provideValuesOpNumericMod() { @@ -185,212 +203,337 @@ private Stream provideValuesOpNumericMod() { @ParameterizedTest @MethodSource("provideValuesOpNumericMod") + @DisplayName("op:numeric-mod") void testOpNumericMod(@Nullable INumericItem expected, @NonNull INumericItem dividend, @NonNull INumericItem divisor) { assertEquals(expected, OperationFunctions.opNumericMod(dividend, divisor)); } } + // TODO: op:numeric-unary-minus + @Nested + @DisplayName("Comparison operators on numeric values") @TestInstance(TestInstance.Lifecycle.PER_CLASS) - class Duration { - private Stream provideValuesDurationEqual() { - return Stream.of( - Arguments.of(bool(true), duration("P1Y"), duration("P12M")), - Arguments.of(bool(true), duration("PT24H"), duration("P1D")), - Arguments.of(bool(false), duration("P1Y"), duration("P365D")), - Arguments.of(bool(true), duration("P0Y"), duration("P0D")), - Arguments.of(bool(true), yearMonthDuration("P0Y"), dayTimeDuration("P0D")), - Arguments.of(bool(false), yearMonthDuration("P1Y"), dayTimeDuration("P365D")), - Arguments.of(bool(true), yearMonthDuration("P2Y"), yearMonthDuration("P24M")), - Arguments.of(bool(true), dayTimeDuration("P10D"), dayTimeDuration("PT240H")), - Arguments.of(bool(true), duration("P2Y0M0DT0H0M0S"), yearMonthDuration("P24M")), - Arguments.of(bool(true), duration("P0Y0M10D"), dayTimeDuration("PT240H"))); - } - - @ParameterizedTest - @MethodSource("provideValuesDurationEqual") - void testOpDurationEqual( - @NonNull IBooleanItem expected, - @NonNull IDurationItem arg1, - @NonNull IDurationItem arg2) { - assertEquals(expected, OperationFunctions.opDurationEqual(arg1, arg2)); - } + class NumericComparison { + // TODO: op:numeric-equal + // TODO: op:numeric-less-than + // TODO: op:numeric-greater-than } @Nested - class YearMonthDuration { - - @Test - void testOpAddYearMonthDurations() { - assertEquals( - IYearMonthDurationItem.valueOf("P6Y2M"), - OperationFunctions.opAddYearMonthDurations( - IYearMonthDurationItem.valueOf("P2Y11M"), - IYearMonthDurationItem.valueOf("P3Y3M"))); - } - - @Test - void testOpAddYearMonthDurationsOverflow() { - DateTimeFunctionException thrown = assertThrows(DateTimeFunctionException.class, () -> { - OperationFunctions.opAddYearMonthDurations( - IYearMonthDurationItem.valueOf("P" + Integer.MAX_VALUE + "Y"), - IYearMonthDurationItem.valueOf("P" + Integer.MAX_VALUE + "Y")); - }); - assertEquals(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, thrown.getCode()); - } - - @Test - void testOpSubtractYearMonthDurations() { - assertEquals( - IYearMonthDurationItem.valueOf("-P4M"), - OperationFunctions.opSubtractYearMonthDurations( - IYearMonthDurationItem.valueOf("P2Y11M"), - IYearMonthDurationItem.valueOf("P3Y3M"))); - } - - @Test - void testOpSubtractYearMonthDurationsOverflow() { - DateTimeFunctionException thrown = assertThrows(DateTimeFunctionException.class, () -> { - OperationFunctions.opSubtractYearMonthDurations( - IYearMonthDurationItem.valueOf("-P" + Integer.MAX_VALUE + "Y"), - IYearMonthDurationItem.valueOf("P" + Integer.MAX_VALUE + "Y")); - }); - assertEquals(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, thrown.getCode()); - } + @DisplayName("Comparison operators on numeric values") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class BooleanComparison { + // TODO: op:boolean-equal + // TODO: op:boolean-less-than + // TODO: op:boolean-greater-than + } - @Test - void testOpMultiplyYearMonthDuration() { - assertAll( - () -> assertEquals( - IYearMonthDurationItem.valueOf("P6Y9M"), - OperationFunctions.opMultiplyYearMonthDuration( + @Nested + @DisplayName("Functions and operators on durations") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class Duration { + @Nested + @DisplayName("Comparison operators on durations") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class DurationComparison { + private Stream provideValuesDurationEqual() { + return Stream.of( + Arguments.of(bool(true), duration("P1Y"), duration("P12M")), + Arguments.of(bool(true), duration("PT24H"), duration("P1D")), + Arguments.of(bool(false), duration("P1Y"), duration("P365D")), + Arguments.of(bool(true), duration("P0Y"), duration("P0D")), + Arguments.of(bool(true), yearMonthDuration("P0Y"), dayTimeDuration("P0D")), + Arguments.of(bool(false), yearMonthDuration("P1Y"), dayTimeDuration("P365D")), + Arguments.of(bool(true), yearMonthDuration("P2Y"), yearMonthDuration("P24M")), + Arguments.of(bool(true), dayTimeDuration("P10D"), dayTimeDuration("PT240H")), + Arguments.of(bool(true), duration("P2Y0M0DT0H0M0S"), yearMonthDuration("P24M")), + Arguments.of(bool(true), duration("P0Y0M10D"), dayTimeDuration("PT240H"))); + } + + @ParameterizedTest + @DisplayName("op:duration-equal") + @MethodSource("provideValuesDurationEqual") + void testOpDurationEqual( + @NonNull IBooleanItem expected, + @NonNull IDurationItem arg1, + @NonNull IDurationItem arg2) { + assertEquals(expected, OperationFunctions.opDurationEqual(arg1, arg2)); + } + + // TODO: op:yearMonthDuration-less-than + // TODO: op:yearMonthDuration-greater-than + // TODO: op:dayTimeDuration-less-than + // TODO: op:dayTimeDuration-greater-than + } + + @Nested + @DisplayName("Arithmetic operators on durations") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class DurationArithmetic { + @Nested + class YearMonthDuration { + + @Test + @DisplayName("op:add-yearMonthDurations: known good") + void testOpAddYearMonthDurations() { + assertEquals( + IYearMonthDurationItem.valueOf("P6Y2M"), + OperationFunctions.opAddYearMonthDurations( IYearMonthDurationItem.valueOf("P2Y11M"), - IDecimalItem.valueOf("2.3"))), - () -> assertEquals( - IYearMonthDurationItem.valueOf("P0M"), - OperationFunctions.opMultiplyYearMonthDuration( - IYearMonthDurationItem.valueOf("P1Y"), - IDecimalItem.valueOf("0"))), - () -> assertEquals( - IYearMonthDurationItem.valueOf("-P2Y"), - OperationFunctions.opMultiplyYearMonthDuration( - IYearMonthDurationItem.valueOf("P1Y"), - IDecimalItem.valueOf("-2")))); - } - - @Test - void testOpMultiplyYearMonthDurationsOverflow() { - DateTimeFunctionException thrown = assertThrows(DateTimeFunctionException.class, () -> { - OperationFunctions.opMultiplyYearMonthDuration( - IYearMonthDurationItem.valueOf("P" + Integer.MAX_VALUE + "Y"), - IDecimalItem.valueOf("2.5")); - }); - assertEquals(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, thrown.getCode()); - } - - @Test - void testOpDivideYearMonthDuration() { - assertEquals( - IYearMonthDurationItem.valueOf("P1Y11M"), - OperationFunctions.opDivideYearMonthDuration( - IYearMonthDurationItem.valueOf("P2Y11M"), - IDecimalItem.valueOf("1.5"))); - } - - @Test - void testOpDivideYearMonthDurationByYearMonthDuration() { - assertEquals( - IDecimalItem.valueOf("-2.5"), - OperationFunctions.opDivideYearMonthDurationByYearMonthDuration( - IYearMonthDurationItem.valueOf("P3Y4M"), - IYearMonthDurationItem.valueOf("-P1Y4M"))); + IYearMonthDurationItem.valueOf("P3Y3M"))); + } + + @Test + @DisplayName("op:add-yearMonthDurations: overflow") + void testOpAddYearMonthDurationsOverflow() { + DateTimeFunctionException thrown = assertThrows(DateTimeFunctionException.class, () -> { + OperationFunctions.opAddYearMonthDurations( + IYearMonthDurationItem.valueOf("P" + Integer.MAX_VALUE + "Y"), + IYearMonthDurationItem.valueOf("P" + Integer.MAX_VALUE + "Y")); + }); + assertEquals(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, thrown.getCode()); + } + + @Test + @DisplayName("op:subtract-yearMonthDurations: known good") + void testOpSubtractYearMonthDurations() { + assertEquals( + IYearMonthDurationItem.valueOf("-P4M"), + OperationFunctions.opSubtractYearMonthDurations( + IYearMonthDurationItem.valueOf("P2Y11M"), + IYearMonthDurationItem.valueOf("P3Y3M"))); + } + + @Test + @DisplayName("op:subtract-yearMonthDurations: overflow") + void testOpSubtractYearMonthDurationsOverflow() { + DateTimeFunctionException thrown = assertThrows(DateTimeFunctionException.class, () -> { + OperationFunctions.opSubtractYearMonthDurations( + IYearMonthDurationItem.valueOf("-P" + Integer.MAX_VALUE + "Y"), + IYearMonthDurationItem.valueOf("P" + Integer.MAX_VALUE + "Y")); + }); + assertEquals(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, thrown.getCode()); + } + + @Test + @DisplayName("op:multiply-yearMonthDurations: known good") + void testOpMultiplyYearMonthDuration() { + assertAll( + () -> assertEquals( + IYearMonthDurationItem.valueOf("P6Y9M"), + OperationFunctions.opMultiplyYearMonthDuration( + IYearMonthDurationItem.valueOf("P2Y11M"), + IDecimalItem.valueOf("2.3"))), + () -> assertEquals( + IYearMonthDurationItem.valueOf("P0M"), + OperationFunctions.opMultiplyYearMonthDuration( + IYearMonthDurationItem.valueOf("P1Y"), + IDecimalItem.valueOf("0"))), + () -> assertEquals( + IYearMonthDurationItem.valueOf("-P2Y"), + OperationFunctions.opMultiplyYearMonthDuration( + IYearMonthDurationItem.valueOf("P1Y"), + IDecimalItem.valueOf("-2")))); + } + + @Test + @DisplayName("op:multiply-yearMonthDurations: overflow") + void testOpMultiplyYearMonthDurationsOverflow() { + DateTimeFunctionException thrown = assertThrows(DateTimeFunctionException.class, () -> { + OperationFunctions.opMultiplyYearMonthDuration( + IYearMonthDurationItem.valueOf("P" + Integer.MAX_VALUE + "Y"), + IDecimalItem.valueOf("2.5")); + }); + assertEquals(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, thrown.getCode()); + } + + @Test + @DisplayName("op:divide-yearMonthDurations: known good") + void testOpDivideYearMonthDuration() { + assertEquals( + IYearMonthDurationItem.valueOf("P1Y11M"), + OperationFunctions.opDivideYearMonthDuration( + IYearMonthDurationItem.valueOf("P2Y11M"), + IDecimalItem.valueOf("1.5"))); + } + + @Test + @DisplayName("op:divide-yearMonthDuration-by-yearMonthDuration: known good") + void testOpDivideYearMonthDurationByYearMonthDuration() { + assertEquals( + IDecimalItem.valueOf("-2.5"), + OperationFunctions.opDivideYearMonthDurationByYearMonthDuration( + IYearMonthDurationItem.valueOf("P3Y4M"), + IYearMonthDurationItem.valueOf("-P1Y4M"))); + } + } + + @Nested + class DayTimeDuration { + + @Test + @DisplayName("op:add-dayTimeDurations: known good") + void testOpAddDayTimeDurations() { + assertEquals( + IDayTimeDurationItem.valueOf("P8DT5M"), + OperationFunctions.opAddDayTimeDurations( + IDayTimeDurationItem.valueOf("P2DT12H5M"), + IDayTimeDurationItem.valueOf("P5DT12H"))); + } + + @Test + @DisplayName("op:add-dayTimeDurations: overflow") + void testOpAddDayTimeDurationsOverflow() { + DateTimeFunctionException thrown = assertThrows(DateTimeFunctionException.class, () -> { + OperationFunctions.opAddDayTimeDurations( + // subtracting 807 ensures the long doesn't overflow + IDayTimeDurationItem.valueOf("PT" + (Long.MAX_VALUE - 807) + "S"), + IDayTimeDurationItem.valueOf("PT" + (Long.MAX_VALUE - 807) + "S")); + }); + assertEquals(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, thrown.getCode()); + } + + @Test + @DisplayName("op:subtract-dayTimeDurations: known good") + void testOpSubtractDayTimeDurations() { + assertEquals( + IDayTimeDurationItem.valueOf("P1DT1H30M"), + OperationFunctions.opSubtractDayTimeDurations( + IDayTimeDurationItem.valueOf("P2DT12H"), + IDayTimeDurationItem.valueOf("P1DT10H30M"))); + } + + @Test + @DisplayName("op:subtract-dayTimeDurations: overflow") + void testOpSubtractDayTimeDurationsOverflow() { + DateTimeFunctionException thrown = assertThrows(DateTimeFunctionException.class, () -> { + OperationFunctions.opSubtractDayTimeDurations( + IDayTimeDurationItem.valueOf("-PT" + (Long.MAX_VALUE - 807) + "S"), + IDayTimeDurationItem.valueOf("PT" + (Long.MAX_VALUE - 807) + "S")); + }); + assertEquals(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, thrown.getCode()); + } + + @Test + @DisplayName("op:multiply-dayTimeDuration: known good") + void testOpMultiplyDayTimeDuration() { + assertEquals( + IDayTimeDurationItem.valueOf("PT4H33M"), + OperationFunctions.opMultiplyDayTimeDuration( + IDayTimeDurationItem.valueOf("PT2H10M"), + IDecimalItem.valueOf("2.1"))); + } + + @Test + @DisplayName("op:multiply-dayTimeDuration: overflow") + void testOpMultiplyDayTimeDurationsOverflow() { + DateTimeFunctionException thrown = assertThrows(DateTimeFunctionException.class, () -> { + OperationFunctions.opMultiplyDayTimeDuration( + IDayTimeDurationItem.valueOf("PT" + Long.MAX_VALUE / 2 + "S"), + IDecimalItem.valueOf("5")); + }); + assertEquals(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, thrown.getCode()); + } + + @Test + @DisplayName("op:divide-dayTimeDuration: known good") + void testOpDivideDayTimeDuration() { + assertEquals( + IDayTimeDurationItem.valueOf("PT17H40M7S"), + OperationFunctions.opDivideDayTimeDuration( + IDayTimeDurationItem.valueOf("P1DT2H30M10.5S"), + IDecimalItem.valueOf("1.5"))); + } + + @Test + @DisplayName("op:divide-dayTimeDuration-by-dayTimeDuration: known good") + void testOpDivideDayTimeDurationByDayTimeDuration() { + assertAll( + () -> assertEquals( + IDecimalItem.valueOf("1.437834967320261"), + OperationFunctions.opDivideDayTimeDurationByDayTimeDuration( + IDayTimeDurationItem.valueOf("P2DT53M11S"), + IDayTimeDurationItem.valueOf("P1DT10H"))), + () -> assertEquals( + IDecimalItem.valueOf("175991.0"), + OperationFunctions.opDivideDayTimeDurationByDayTimeDuration( + IDayTimeDurationItem.valueOf("P2DT53M11S"), + IDayTimeDurationItem.valueOf("PT1S")))); + } + } } } @Nested - class DayTimeDuration { - - @Test - void testOpAddDayTimeDurations() { - assertEquals( - IDayTimeDurationItem.valueOf("P8DT5M"), - OperationFunctions.opAddDayTimeDurations( - IDayTimeDurationItem.valueOf("P2DT12H5M"), - IDayTimeDurationItem.valueOf("P5DT12H"))); - } - - @Test - void testOpAddDayTimeDurationsOverflow() { - DateTimeFunctionException thrown = assertThrows(DateTimeFunctionException.class, () -> { - OperationFunctions.opAddDayTimeDurations( - // subtracting 807 ensures the long doesn't overflow - IDayTimeDurationItem.valueOf("PT" + (Long.MAX_VALUE - 807) + "S"), - IDayTimeDurationItem.valueOf("PT" + (Long.MAX_VALUE - 807) + "S")); - }); - assertEquals(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, thrown.getCode()); - } - - @Test - void testOpSubtractDayTimeDurations() { - assertEquals( - IDayTimeDurationItem.valueOf("P1DT1H30M"), - OperationFunctions.opSubtractDayTimeDurations( - IDayTimeDurationItem.valueOf("P2DT12H"), - IDayTimeDurationItem.valueOf("P1DT10H30M"))); - } - - @Test - void testOpSubtractDayTimeDurationsOverflow() { - DateTimeFunctionException thrown = assertThrows(DateTimeFunctionException.class, () -> { - OperationFunctions.opSubtractDayTimeDurations( - IDayTimeDurationItem.valueOf("-PT" + (Long.MAX_VALUE - 807) + "S"), - IDayTimeDurationItem.valueOf("PT" + (Long.MAX_VALUE - 807) + "S")); - }); - assertEquals(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, thrown.getCode()); - } - - @Test - void testOpMultiplyDayTimeDuration() { - assertEquals( - IDayTimeDurationItem.valueOf("PT4H33M"), - OperationFunctions.opMultiplyDayTimeDuration( - IDayTimeDurationItem.valueOf("PT2H10M"), - IDecimalItem.valueOf("2.1"))); - } - - @Test - void testOpMultiplyDayTimeDurationsOverflow() { - DateTimeFunctionException thrown = assertThrows(DateTimeFunctionException.class, () -> { - OperationFunctions.opMultiplyDayTimeDuration( - IDayTimeDurationItem.valueOf("PT" + Long.MAX_VALUE / 2 + "S"), - IDecimalItem.valueOf("5")); - }); - assertEquals(DateTimeFunctionException.DURATION_OVERFLOW_UNDERFLOW_ERROR, thrown.getCode()); + @DisplayName("Functions and operators on dates and times") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class DateTime { + @Nested + @DisplayName("Functions and operators on dates and times") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class DateTimeOperators { + // TODO: op:dateTime-equal + // TODO: op:dateTime-less-than + // TODO: op:dateTime-greater-than + + // TODO: op:date-equal + // TODO: op:date-less-than + // TODO: op:date-greater-than + + // TODO: op:time-equal + // TODO: op:time-less-than + // TODO: op:time-greater-than + + // TODO: op:gYearMonth-equal + // TODO: op:gYear-equal + // TODO: op:gMonthDay-equal + // TODO: op:gMonth-equal + // TODO: op:gDay-equal + } + + @Nested + @DisplayName("Arithmetic operators on durations, dates and times") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class DateTimeArithmetic { + // TODO: op:subtract-dateTimes + // TODO: op:subtract-dates + // TODO: op:subtract-times + // TODO: op:add-yearMonthDuration-to-dateTime + // TODO: op:add-dayTimeDuration-to-dateTime + // TODO: op:subtract-yearMonthDuration-from-dateTime + // TODO: op:subtract-dayTimeDuration-from-dateTime + // TODO: op:add-yearMonthDuration-to-date + // TODO: op:add-dayTimeDuration-to-date + // TODO: op:subtract-yearMonthDuration-from-date + // TODO: op:subtract-dayTimeDuration-from-date + // TODO: op:add-dayTimeDuration-to-time + // TODO: op:subtract-dayTimeDuration-from-time } + } - @Test - void testOpDivideDayTimeDuration() { - assertEquals( - IDayTimeDurationItem.valueOf("PT17H40M7S"), - OperationFunctions.opDivideDayTimeDuration( - IDayTimeDurationItem.valueOf("P1DT2H30M10.5S"), - IDecimalItem.valueOf("1.5"))); - } + @Nested + @DisplayName("Functions and operators related to QNames") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class QName { + // TODO: op:QName-equal + } - @Test - void testOpDivideDayTimeDurationByDayTimeDuration() { - assertAll( - () -> assertEquals( - IDecimalItem.valueOf("1.437834967320261"), - OperationFunctions.opDivideDayTimeDurationByDayTimeDuration( - IDayTimeDurationItem.valueOf("P2DT53M11S"), - IDayTimeDurationItem.valueOf("P1DT10H"))), - () -> assertEquals( - IDecimalItem.valueOf("175991.0"), - OperationFunctions.opDivideDayTimeDurationByDayTimeDuration( - IDayTimeDurationItem.valueOf("P2DT53M11S"), - IDayTimeDurationItem.valueOf("PT1S")))); + @Nested + @DisplayName("Operators on base64Binary and hexBinary") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class Binary { + @Nested + @DisplayName("Comparisons of base64Binary and hexBinary values") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class BinaryOperators { + // TODO: op:hexBinary-equal + // TODO: op:hexBinary-less-than + // TODO: op:hexBinary-greater-than + // TODO: op:base64Binary-equal + // TODO: op:base64Binary-less-than + // TODO: op:base64Binary-greater-than } } } diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/item/function/IMapKeyTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/item/function/IMapKeyTest.java index 503bdb64e..af9bfa43a 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/item/function/IMapKeyTest.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/item/function/IMapKeyTest.java @@ -5,25 +5,105 @@ package gov.nist.secauto.metaschema.core.metapath.item.function; +import static gov.nist.secauto.metaschema.core.metapath.TestUtils.date; +import static gov.nist.secauto.metaschema.core.metapath.TestUtils.dateTime; +import static gov.nist.secauto.metaschema.core.metapath.TestUtils.decimal; import static gov.nist.secauto.metaschema.core.metapath.TestUtils.string; +import static gov.nist.secauto.metaschema.core.metapath.TestUtils.time; import static gov.nist.secauto.metaschema.core.metapath.TestUtils.uri; +import static gov.nist.secauto.metaschema.core.metapath.TestUtils.uuid; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateTimeItem; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.util.Objects; import java.util.stream.Stream; import edu.umd.cs.findbugs.annotations.NonNull; class IMapKeyTest { - private static Stream stringKeyValues() { // NOPMD - false positive + private static Stream keyValues() { return Stream.of( + // ================= + // binary-based keys + // ================= + // TODO: generate some test cases + + // =================== + // temporal-based keys + // =================== + // same keys + Arguments.of( + true, + date("2025-01-13Z"), + date("2025-01-13Z")), + Arguments.of( + true, + date("2025-01-13"), + date("2025-01-13")), + Arguments.of( + true, + IDateTimeItem.valueOf(date("2025-01-13Z")), + dateTime("2025-01-13T00:00:00Z")), + Arguments.of( + true, + dateTime("2025-01-12T23:00:00-01:00"), + dateTime("2025-01-13T00:00:00Z")), + Arguments.of( + true, + dateTime("2025-01-12T23:00:00"), + dateTime("2025-01-12T23:00:00")), + Arguments.of( + true, + time("23:00:00"), + time("23:00:00")), + Arguments.of( + true, + time("24:00:00"), + time("24:00:00")), + Arguments.of( + true, + time("00:00:00"), + time("00:00:00")), + // different keys + Arguments.of( + false, + date("2025-01-13Z"), + date("2025-01-13")), + Arguments.of( + false, + date("2025-01-13Z"), + dateTime("2025-01-13T00:00:00Z")), + Arguments.of( + false, + date("2025-01-13Z"), + time("00:00:00Z")), + Arguments.of( + false, + time("00:00:00Z"), + dateTime("2025-01-13T00:00:00Z")), + // ================== + // decimal-based keys + // ================== + Arguments.of( + true, + decimal("0.0"), + decimal("0.00")), + Arguments.of( + true, + decimal("10.0"), + decimal("10.00")), + Arguments.of( + true, + decimal("-1.0"), + decimal("-1.00")), + // string-based keys Arguments.of( true, string("https://example.com/resource"), @@ -35,17 +115,33 @@ private static Stream stringKeyValues() { // NOPMD - false positive Arguments.of( false, string("https://example.com/resource"), - uri("https://example.com/other-resource"))); + uri("https://example.com/other-resource")), + Arguments.of( + true, + string("8eb2fb0f-47ac-40be-adb0-1a70fe7fad76"), + uuid("8eb2fb0f-47ac-40be-adb0-1a70fe7fad76")), + Arguments.of( + false, + uuid("0889bbb3-e3d3-41d3-bc98-07c18534462a"), + uuid("8eb2fb0f-47ac-40be-adb0-1a70fe7fad76"))); } @ParameterizedTest - @MethodSource("stringKeyValues") + @MethodSource("keyValues") void testStringKeys(boolean expectedEquality, @NonNull IAnyAtomicItem item1, @NonNull IAnyAtomicItem item2) { IMapKey key1 = item1.asMapKey(); IMapKey key2 = item2.asMapKey(); - assertAll( - () -> assertEquals(expectedEquality, key1.equals(key2)), - () -> assertEquals(expectedEquality, Objects.equals(key1.hashCode(), key2.hashCode()))); + if (expectedEquality) { + assertAll( + () -> assertEquals(key1, key2, "keys are equivalent"), + () -> assertEquals(key2, key1, "keys are reflexively equivalent"), + () -> assertEquals(key1.hashCode(), key2.hashCode(), "keys have the same hash code")); + } else { + assertAll( + () -> assertNotEquals(key1, key2, "keys are different"), + () -> assertNotEquals(key2, key1, "keys are reflexively different"), + () -> assertNotEquals(key1.hashCode(), key2.hashCode(), "keys have different hash codes")); + } } } diff --git a/databind/src/test/java/gov/nist/secauto/metaschema/databind/codegen/BasicMetaschemaTest.java b/databind/src/test/java/gov/nist/secauto/metaschema/databind/codegen/BasicMetaschemaTest.java index f8ab98a14..2093d156b 100644 --- a/databind/src/test/java/gov/nist/secauto/metaschema/databind/codegen/BasicMetaschemaTest.java +++ b/databind/src/test/java/gov/nist/secauto/metaschema/databind/codegen/BasicMetaschemaTest.java @@ -26,6 +26,8 @@ import gov.nist.secauto.metaschema.databind.model.metaschema.IBindingMetaschemaModule; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.commons.util.ReflectionUtils; import java.io.IOException; @@ -36,6 +38,8 @@ import java.util.Map; import java.util.Set; +@Execution(value = ExecutionMode.SAME_THREAD, + reason = "FIXME: produces output that is not unique to each test: out.xml.") class BasicMetaschemaTest extends AbstractMetaschemaTest {