FunctionUtils.java
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/
package gov.nist.secauto.metaschema.core.metapath.function;
import gov.nist.secauto.metaschema.core.metapath.ISequence;
import gov.nist.secauto.metaschema.core.metapath.InvalidTypeMetapathException;
import gov.nist.secauto.metaschema.core.metapath.TypeMetapathException;
import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
import gov.nist.secauto.metaschema.core.metapath.item.IItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDecimalItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
/**
* A collection of utility functions for use in implementing Metapath functions.
*/
@SuppressWarnings("PMD.CouplingBetweenObjects")
public final class FunctionUtils {
public static final MathContext MATH_CONTEXT = MathContext.DECIMAL64;
private FunctionUtils() {
// disable
}
/**
* Converts a {@link INumericItem} value to an integer value.
*
* @param value
* the value to convert
* @return the integer value
* @throws ArithmeticException
* if the provided value will not exactly fit in an {@code int}
*/
public static int asInteger(@NonNull INumericItem value) {
return asInteger(value.asInteger());
}
/**
* Converts a {@link BigInteger} value to an integer value.
*
* @param value
* the value to convert
* @return the integer value
* @throws ArithmeticException
* if the provided value will not exactly fit in an {@code int}
*/
public static int asInteger(@NonNull BigInteger value) {
return value.intValueExact();
}
/**
* Converts a {@link INumericItem} value to a long value.
*
* @param value
* the value to convert
* @return the long value
* @throws ArithmeticException
* if the provided value will not exactly fit in an {@code long}
*/
public static long asLong(@NonNull INumericItem value) {
return asLong(value.asInteger());
}
/**
* Converts a {@link BigInteger} value to a long value.
*
* @param value
* the value to convert
* @return the long value
* @throws ArithmeticException
* if the provided value will not exactly fit in an {@code long}
*/
public static long asLong(@NonNull BigInteger value) {
return value.longValueExact();
}
/**
* Gets the first item of the provided sequence as a {@link INumericItem} value.
* If the sequence is empty, then a {@code null} value is returned.
*
* @param sequence
* a Metapath sequence containing the value to convert
* @param requireSingleton
* if {@code true} then a {@link TypeMetapathException} is thrown if
* the sequence contains more than one item
* @return the numeric item value, or {@code null} if the result is an empty
* sequence
* @throws TypeMetapathException
* if the sequence contains more than one item, or the item cannot be
* cast to a numeric value
*
*/
@Nullable
public static INumericItem toNumeric(@NonNull ISequence<?> sequence, boolean requireSingleton) {
IItem item = sequence.getFirstItem(requireSingleton);
return item == null ? null : toNumeric(item);
}
/**
* Gets the provided item value as a {@link INumericItem} value.
*
* @param item
* the value to convert
* @return the numeric item value
* @throws TypeMetapathException
* if the sequence contains more than one item, or the item cannot be
* cast to a numeric value
*/
@NonNull
public static INumericItem toNumeric(@NonNull IItem item) {
// atomize
IAnyAtomicItem atomicItem = ISequence.getFirstItem(FnData.atomize(item), true);
return toNumeric(atomicItem);
}
/**
* Gets the provided item value as a {@link INumericItem} value.
*
* @param item
* the value to convert
* @return the numeric item value
* @throws TypeMetapathException
* if the item cannot be cast to a numeric value
*/
@NonNull
public static INumericItem toNumeric(@Nullable IAnyAtomicItem item) {
try {
return IDecimalItem.cast(item);
} catch (InvalidValueForCastFunctionException ex) {
throw new InvalidTypeMetapathException(item, ex.getLocalizedMessage(), ex);
}
}
/**
* Gets the provided item value as a {@link INumericItem} value. If the item is
* {@code null}, then a {@code null} value is returned.
*
* @param item
* the value to convert
* @return the numeric item value
* @throws TypeMetapathException
* if the item cannot be cast to a numeric value
*/
@Nullable
public static INumericItem toNumericOrNull(@Nullable IAnyAtomicItem item) {
return item == null ? null : toNumeric(item);
}
/**
* Casts the provided {@code item} as the result type, if the item is not
* {@code null}.
*
* @param <TYPE>
* the Java type to cast to
* @param item
* the value to cast
* @return the item cast to the required type or {@code null} if the item is
* {@code null}
* @throws ClassCastException
* if the item's type is not compatible with the requested type
*/
@SuppressWarnings("unchecked")
@Nullable
public static <TYPE extends IItem> TYPE asTypeOrNull(@Nullable IItem item) {
return (TYPE) item;
}
/**
* Casts the provided {@code item} as the result type.
*
* @param <TYPE>
* the Java type to cast to
* @param item
* the value to cast
* @return the item cast to the required type
* @throws ClassCastException
* if the item's type is not compatible with the requested type
*/
@SuppressWarnings("unchecked")
@NonNull
public static <TYPE extends IItem> TYPE asType(@NonNull IItem item) {
return (TYPE) item;
}
/**
* Casts the provided {@code item} as the result sequence type.
*
* @param <TYPE>
* the Java type to cast to
* @param sequence
* the values to cast
* @return the sequence cast to the required type
* @throws ClassCastException
* if the sequence's type is not compatible with the requested type
*/
@SuppressWarnings("unchecked")
@NonNull
public static <TYPE extends IItem> ISequence<TYPE> asType(@NonNull ISequence<?> sequence) {
return (ISequence<TYPE>) sequence;
}
/**
* Casts the provided {@code item} as the result type.
*
* @param <TYPE>
* the Java type to cast to
* @param clazz
* the Java class instance for the requested type
* @param item
* the value to cast
* @return the item cast to the required type
* @throws InvalidTypeMetapathException
* if the provided item is {@code null} or if the item's type is not
* assignment compatible to the requested type
*/
@NonNull
public static <TYPE extends IItem> TYPE requireType(Class<TYPE> clazz, IItem item) {
if (item == null) {
throw new InvalidTypeMetapathException(
null,
String.format("Expected non-null type '%s', but the node was null.",
clazz.getName()));
} else if (!clazz.isInstance(item)) {
throw new InvalidTypeMetapathException(
item,
String.format("Expected type '%s', but the node was type '%s'.",
clazz.getName(),
item.getClass().getName()));
}
return asType(item);
}
/**
* Casts the provided {@code item} as the result type, if the item is not
* {@code null}.
*
* @param <TYPE>
* the Java type to cast to
* @param clazz
* the Java class instance for the requested type
* @param item
* the value to cast
* @return the item cast to the required type or {@code null} if the item is
* {@code null}
* @throws InvalidTypeMetapathException
* if the provided item is {@code null} or if the item's type is not
* assignment compatible to the requested type
*/
@Nullable
public static <TYPE extends IItem> TYPE requireTypeOrNull(Class<TYPE> clazz, @Nullable IItem item) {
if (item == null || clazz.isInstance(item)) {
return asTypeOrNull(item);
}
throw new InvalidTypeMetapathException(
item,
String.format("Expected type '%s', but the node was type '%s'.",
clazz.getName(),
item.getClass().getName()));
}
/**
* Get a stream of item data types for the stream of items.
*
* @param items
* the Metapath items to get the data types for
* @return a stream of data type classes
*/
@NonNull
public static Stream<Class<?>> getTypes(@NonNull Stream<? extends IItem> items) {
return ObjectUtils.notNull(items.map(Object::getClass));
}
/**
* Generate a list of Metapath item Java type classes from a list of Metapath
* items.
*
* @param <T>
* the base Java types of the items
* @param items
* the items to get Java type class for
* @return a list of corresponding Java type classes for the provided items
*/
@SuppressWarnings("unchecked")
@NonNull
public static <T extends IItem> List<Class<? extends T>> getTypes(@NonNull List<T> items) {
return ObjectUtils.notNull(items.stream()
.map(item -> (Class<? extends T>) item.getClass())
.collect(Collectors.toList()));
}
/**
* Count the occurrences of the provided data type item {@code classes} used in
* the set of provided {@code items}.
*
* @param <T>
* the class type
* @param classes
* the Metapath item classes to count
* @param items
* the Metapath items to analyze
* @return a mapping of Metapath item class to count
*/
@NonNull
public static <T extends IItem> Map<Class<? extends T>, Integer> countTypes(
@NonNull Set<Class<? extends T>> classes,
@NonNull Collection<? extends T> items) {
Map<Class<? extends T>, Integer> retval = new HashMap<>(); // NOPMD
for (T item : items) {
Class<?> itemClass = item.getClass();
for (Class<? extends T> clazz : classes) {
if (clazz.isAssignableFrom(itemClass)) {
retval.compute(clazz, (cl, current) -> current == null ? 1 : current + 1);
}
}
}
return retval;
}
}