ModelUtil.java
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/
package gov.nist.secauto.metaschema.databind.model.annotations;
import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider;
import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
import gov.nist.secauto.metaschema.core.model.IAttributable;
import gov.nist.secauto.metaschema.core.model.IBoundObject;
import gov.nist.secauto.metaschema.core.model.IMetaschemaData;
import gov.nist.secauto.metaschema.core.model.IModule;
import gov.nist.secauto.metaschema.core.util.CollectionUtil;
import gov.nist.secauto.metaschema.databind.IBindingContext;
import gov.nist.secauto.metaschema.databind.model.IGroupAs;
import gov.nist.secauto.metaschema.databind.model.impl.DefaultGroupAs;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
public final class ModelUtil {
// TODO: replace NO_STRING_VALUE with NULL_VALUE where possible. URIs will not
// allow NULL_VALUE.
public static final String NO_STRING_VALUE = "##none";
public static final String DEFAULT_STRING_VALUE = "##default";
/**
* A placeholder for a {@code null} value for use in annotations, which cannot
* be null by default.
* <p>
* Use of {@code "\u0000"} simple substitute for {@code null} to allow
* implementations to recognize the "no default value" state.
*/
public static final String NULL_VALUE = "\u0000";
private ModelUtil() {
// disable construction
}
/**
* Get the requested annotation from the provided Java class.
*
* @param <A>
* the annotation Java type
* @param clazz
* the Java class to get the annotation from
* @param annotationClass
* the annotation class instance
* @return the annotation
* @throws IllegalArgumentException
* if the annotation was not present on the class
*/
@NonNull
public static <A extends Annotation> A getAnnotation(
@NonNull Class<?> clazz,
Class<A> annotationClass) {
A annotation = clazz.getAnnotation(annotationClass);
if (annotation == null) {
throw new IllegalArgumentException(
String.format("Class '%s' is missing the '%s' annotation.",
clazz.getName(),
annotationClass.getName()));
}
return annotation;
}
/**
* Get the requested annotation from the provided Java field.
*
* @param <A>
* the annotation Java type
* @param javaField
* the Java field to get the annotation from
* @param annotationClass
* the annotation class instance
* @return the annotation
* @throws IllegalArgumentException
* if the annotation was not present on the field
*/
@NonNull
public static <A extends Annotation> A getAnnotation(
@NonNull Field javaField,
Class<A> annotationClass) {
A annotation = javaField.getAnnotation(annotationClass);
if (annotation == null) {
throw new IllegalArgumentException(
String.format("Field '%s' is missing the '%s' annotation.",
javaField.toGenericString(),
annotationClass.getName()));
}
return annotation;
}
/**
* Resolves a string value. If the value is {@code null} or "##default", then
* the provided default value will be used instead. If the value is "##none",
* then the value will be {@code null}. Otherwise, the value is returned.
*
* @param value
* the requested value
* @param defaultValue
* the default value
* @return the resolved value or {@code null}
*/
@Nullable
public static String resolveNoneOrDefault(@Nullable String value, @Nullable String defaultValue) {
String retval;
if (value == null || DEFAULT_STRING_VALUE.equals(value)) {
retval = defaultValue;
} else if (NO_STRING_VALUE.equals(value)) {
retval = null; // NOPMD - intentional
} else {
retval = value;
}
return retval;
}
/**
* Get the processed value of a string. If the value is "##none", then the value
* will be {@code null}. Otherwise the value is returned.
*
* @param value
* text or {@code "##none"} if no text is provided
* @return the resolved value or {@code null}
*/
@Nullable
public static String resolveNoneOrValue(@NonNull String value) {
return NO_STRING_VALUE.equals(value) ? null : value;
}
/**
* Get the markup value of a markdown string.
*
* @param value
* markdown text or {@code "##none"} if no text is provided
* @return the markup line content or {@code null} if no markup content was
* provided
*/
@Nullable
public static MarkupLine resolveToMarkupLine(@NonNull String value) {
return resolveNoneOrValue(value) == null ? null : MarkupLine.fromMarkdown(value);
}
/**
* Get the markup value of a markdown string.
*
* @param value
* markdown text or {@code "##none"} if no text is provided
* @return the markup line content or {@code null} if no markup content was
* provided
*/
@Nullable
public static MarkupMultiline resolveToMarkupMultiline(@NonNull String value) {
return resolveNoneOrValue(value) == null ? null : MarkupMultiline.fromMarkdown(value);
}
/**
* Get the data type adapter instance of the provided adapter class.
* <p>
* If the provided adapter Java class is the {@link NullJavaTypeAdapter} class,
* then the default data type adapter will be returned.
*
* @param adapterClass
* the data type adapter class to get the data type adapter instance
* for
* @param bindingContext
* the Metaschema binding context used to lookup the data type adapter
* @return the data type adapter
* @throws IllegalArgumentException
* if the provided adapter is not registered with the binding context
*/
@NonNull
public static IDataTypeAdapter<?> getDataTypeAdapter(
@NonNull Class<? extends IDataTypeAdapter<?>> adapterClass,
@NonNull IBindingContext bindingContext) {
IDataTypeAdapter<?> retval;
if (NullJavaTypeAdapter.class.equals(adapterClass)) {
retval = MetaschemaDataTypeProvider.DEFAULT_DATA_TYPE;
} else {
retval = bindingContext.getJavaTypeAdapterInstance(adapterClass);
if (retval == null) {
throw new IllegalArgumentException("Unable to get type adapter instance for class: " + adapterClass.getName());
}
}
return retval;
}
/**
* Given a provided default value string, get the data type specific default
* value using the provided data type adapter.
* <p>
* If the provided default value is {@link ModelUtil#NULL_VALUE}, then this
* method will return a {@code null} value.
*
* @param defaultValue
* the string representation of the default value
* @param adapter
* the data type adapter instance used to cast the default string value
* to a data type specific object
* @return the data type specific object or {@code null} if the provided default
* value was {@link ModelUtil#NULL_VALUE}
*/
@Nullable
public static Object resolveDefaultValue(@NonNull String defaultValue, IDataTypeAdapter<?> adapter) {
Object retval = null;
if (!NULL_VALUE.equals(defaultValue)) {
retval = adapter.parse(defaultValue);
}
return retval;
}
/**
* Resolves an integer value by determining if an actual value is provided or
* -2^31, which indicates that no actual value was provided.
* <p>
* The integer value -2^31 cannot be used, since this indicates no value.
*
* @param value
* the integer value to resolve
* @return the integer value or {@code null} if the provided value was -2^31
*/
public static Integer resolveDefaultInteger(int value) {
return value == Integer.MIN_VALUE ? null : value;
}
/**
* Resolves a {@link GroupAs} annotation determining if an actual value is
* provided or if the value is the default, which indicates that no actual
* GroupAs was provided.
*
* @param groupAs
* the GroupAs value to resolve
* @param module
* the containing module instance
* @return a new {@link IGroupAs} instance or a singleton group as if the
* provided value was the default value
*/
@NonNull
public static IGroupAs resolveDefaultGroupAs(
@NonNull GroupAs groupAs,
@NonNull IModule module) {
return NULL_VALUE.equals(groupAs.name())
? IGroupAs.SINGLETON_GROUP_AS
: new DefaultGroupAs(groupAs, module);
}
public static String toLocation(@NonNull IBoundObject obj) {
IMetaschemaData data = obj.getMetaschemaData();
String retval = "";
if (data != null) {
int line = data.getLine();
if (line > -1) {
retval = line + ":" + data.getColumn();
}
}
return retval;
}
public static String toLocation(@NonNull IBoundObject obj, @Nullable URI uri) {
String retval = uri == null ? "" : uri.toASCIIString();
String location = toLocation(obj);
if (!location.isEmpty()) {
retval = retval.isEmpty() ? location : retval + "@" + location;
}
return retval;
}
public static Map.Entry<IAttributable.Key, Set<String>> toPropertyEntry(@NonNull Property property) {
String name = property.name();
String namespace = property.namespace();
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") // ok
IAttributable.Key key = IAttributable.key(namespace, name);
String[] values = property.values();
List<String> valueList = Arrays.asList(values);
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") // ok
Set<String> valueSet = new LinkedHashSet<>(valueList);
return Map.entry(key, CollectionUtil.unmodifiableSet(valueSet));
}
}