IDataTypeAdapter.java
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/
package gov.nist.secauto.metaschema.core.datatype;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes;
import gov.nist.secauto.metaschema.core.metapath.function.InvalidValueForCastFunctionException;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import org.codehaus.stax2.XMLEventReader2;
import org.codehaus.stax2.XMLStreamWriter2;
import org.codehaus.stax2.evt.XMLEventFactory2;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.function.Supplier;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import edu.umd.cs.findbugs.annotations.NonNull;
/**
* Represents a data type implementation.
*
* @param <TYPE>
* the Java type of the underlying data value
*/
public interface IDataTypeAdapter<TYPE> {
/**
* Get the metaschema type names associated with this adapter. This name must be
* unique with respect to all other metaschema types.
* <p>
* At least one name must be provided, with the first name being the most
* preferred name.
*
* @return the name
*/
@NonNull
List<QName> getNames();
/**
* Get the most preferred name for this data type.
*
* @return the name
*/
@NonNull
default QName getPreferredName() {
return ObjectUtils.notNull(getNames().iterator().next());
}
/**
* The JSON primative type of the data type.
*
* @return the JSON data type
*/
JsonFormatTypes getJsonRawType();
/**
* Get the Java class supported by this adapter.
*
* @return the Java class
*/
@NonNull
Class<TYPE> getJavaClass();
/**
* Casts the provided value to the type associated with this adapter.
*
* @param value
* a value of the provided type
* @return the typed value
*/
@NonNull
TYPE toValue(@NonNull Object value);
/**
* Gets the value as a string suitable for writing as text. This is intended for
* data types that have a simple string-based structure in XML and JSON, such as
* for XML attributes or JSON keys. An adapter for a complex data structures
* that consist of XML elements will throw an
* {@link UnsupportedOperationException} when this is called.
*
* @param value
* the data to formatted as a string
* @return a string
* @throws IllegalArgumentException
* if the data type cannot be represented as a string
*/
@NonNull
String asString(@NonNull Object value);
/**
* Create a copy of the provided value.
*
* @param obj
* the value to copy
* @return the copy
*/
@NonNull
TYPE copy(@NonNull Object obj);
/**
* Determines if the data type is an atomic, scalar value. Complex structures
* such as Markup are not considered atomic.
*
* @return {@code true} if the data type is an atomic scalar value, or
* {@code false} otherwise
*/
default boolean isAtomic() {
return true;
}
/**
* Get the java type of the associated item.
*
* @return the java associated item type
*/
// TODO: move to IAnyAtomicItem
@NonNull
Class<? extends IAnyAtomicItem> getItemClass();
/**
* Construct a new item of this type using the provided value.
*
* @param value
* the item's value
* @return a new item
*/
// TODO: markup types are not atomic values.
// Figure out a better base type (i.e., IValuedItem)
// TODO: move to IAnyAtomicItem
@NonNull
IAnyAtomicItem newItem(@NonNull Object value);
/**
* Cast the provided item to an item of this type, if possible.
*
* @param item
* the atomic item to cast
* @return an atomic item of this type
* @throws InvalidValueForCastFunctionException
* if the provided item type cannot be cast to this item type
*/
// TODO: move to IAnyAtomicItem
@NonNull
IAnyAtomicItem cast(IAnyAtomicItem item);
/**
* Determines if adapter can parse the next element. The next element's
* {@link QName} is provided for this purpose.
* <p>
* This will be called when the parser encounter's an element it does not
* recognize. This gives the adapter a chance to request parsing of the data.
*
* @param nextElementQName
* the next element's namespace-qualified name
* @return {@code true} if the adapter will parse the element, or {@code false}
* otherwise
*/
boolean canHandleQName(@NonNull QName nextElementQName);
/**
* Parses a provided string. Used to parse XML attributes, simple XML character
* data, and JSON/YAML property values.
*
* @param value
* the string value to parse
* @return the parsed data as the adapter's type
* @throws IllegalArgumentException
* if the data is not valid to the data type
*/
@NonNull
TYPE parse(@NonNull String value);
/**
* This method is expected to parse content starting at the next event. Parsing
* will continue until the next event represents content that is not handled by
* this adapter. This means the event stream should be positioned after any
* {@link XMLEvent#END_ELEMENT} that corresponds to an
* {@link XMLEvent#START_ELEMENT} parsed by this adapter.
* <p>
* If this method parses the {@link XMLEvent#START_ELEMENT} for the element that
* contains the value data, then this method must also parse the corresponding
* {@link XMLEvent#END_ELEMENT}. Otherwise, the first event to parse will be the
* value data.
* <p>
* The value data is expected to be parsed completely, leaving the event stream
* on a peeked event corresponding to content that is not handled by this
* method.
*
* @param eventReader
* the XML parser used to read the parsed value
* @return the parsed value
* @throws IOException
* if a parsing error occurs
*/
// TODO: migrate code to XML parser implementation.
@NonNull
TYPE parse(@NonNull XMLEventReader2 eventReader) throws IOException;
/**
* Parses a JSON property value.
*
* @param parser
* the JSON parser used to read the parsed value
* @return the parsed value
* @throws IOException
* if a parsing error occurs
*/
// TODO: migrate code to JSON parser implementation.
@NonNull
TYPE parse(@NonNull JsonParser parser) throws IOException;
/**
* Parses a provided string using {@link #parse(String)}.
* <p>
* This method may pre-parse the data and then return copies, since the data can
* only be parsed once, but the supplier might be called multiple times.
*
* @param value
* the string value to parse
* @return a supplier that will provide new instances of the parsed data
* @throws IOException
* if an error occurs while parsing
* @throws IllegalArgumentException
* if the provided value is invalid based on the data type
* @see #parse(String)
*/
@NonNull
default Supplier<TYPE> parseAndSupply(@NonNull String value) throws IOException {
TYPE retval = parse(value);
return () -> copy(retval);
}
/**
* Parses a provided string using
* {@link IDataTypeAdapter#parse(XMLEventReader2)}.
* <p>
* This method may pre-parse the data and then return copies, since the data can
* only be parsed once, but the supplier might be called multiple times.
*
* @param eventReader
* the XML parser used to read the parsed value
* @return a supplier that will provide new instances of the parsed data
* @throws IOException
* if an error occurs while parsing
* @see #parse(String)
* @see #parse(XMLEventReader2)
*/
// TODO: migrate code to XML parser implementation.
@NonNull
default Supplier<TYPE> parseAndSupply(@NonNull XMLEventReader2 eventReader) throws IOException {
TYPE retval = parse(eventReader);
return () -> copy(retval);
}
/**
* Parses a provided string using {@link #parse(JsonParser)}.
* <p>
* This method may pre-parse the data and then return copies, since the data can
* only be parsed once, but the supplier might be called multiple times.
*
* @param parser
* the JSON parser used to read the parsed value
* @return a supplier that will provide new instances of the parsed data
* @throws IOException
* if an error occurs while parsing
* @see #parse(String)
* @see #parse(JsonParser)
*/
// TODO: migrate code to JSON parser implementation.
@NonNull
default Supplier<TYPE> parseAndSupply(@NonNull JsonParser parser) throws IOException {
TYPE retval = parse(parser);
return () -> copy(retval);
}
/**
* Writes the provided Java class instance data as XML. The parent element
* information is provided as a {@link StartElement} event, which allows
* namespace information to be obtained from the parent element using the
* {@link StartElement#getName()} and {@link StartElement#getNamespaceContext()}
* methods, which can be used when writing the provided instance value.
*
* @param instance
* the {@link Field} instance value to write
* @param parent
* the {@link StartElement} XML event that is the parent of the data to
* write
* @param eventFactory
* the XML event factory used to generate XML writing events
* @param eventWriter
* the XML writer used to output XML as events
* @throws IOException
* if an unexpected error occurred while writing to the output stream
*/
// TODO: migrate code to XML writer implementation.
void writeXmlValue(@NonNull Object instance, @NonNull StartElement parent, @NonNull XMLEventFactory2 eventFactory,
@NonNull XMLEventWriter eventWriter)
throws IOException;
/**
* Writes the provided Java class instance data as XML. The parent element
* information is provided as an XML {@link QName}, which allows namespace
* information to be obtained from the parent element. Additional namespace
* information can be gathered using the
* {@link XMLStreamWriter2#getNamespaceContext()} method, which can be used when
* writing the provided instance value.
*
* @param instance
* the {@link Field} instance value to write
* @param parentName
* the qualified name of the XML data's parent element
* @param writer
* the XML writer used to output the XML data
* @throws IOException
* if an unexpected error occurred while processing the XML output
*/
// TODO: migrate code to XML writer implementation.
void writeXmlValue(@NonNull Object instance, @NonNull QName parentName, @NonNull XMLStreamWriter2 writer)
throws IOException;
/**
* Writes the provided Java class instance as a JSON/YAML field value.
*
* @param instance
* the {@link Field} instance value to write
* @param writer
* the JSON/YAML writer used to output the JSON/YAML data
* @throws IOException
* if an unexpected error occurred while writing the JSON/YAML output
*/
// TODO: migrate code to JSON writer implementation.
void writeJsonValue(@NonNull Object instance, @NonNull JsonGenerator writer) throws IOException;
/**
* Gets the default value to use as the JSON/YAML field name for a Metaschema
* field value if no JSON value key flag or name is configured.
*
* @return the default field name to use
*/
// TODO: migrate code to JSON implementations.
@NonNull
String getDefaultJsonValueKey();
/**
* Determines if the data type's value is allowed to be unwrapped in XML when
* the value is a field value.
*
* @return {@code true} if allowed, or {@code false} otherwise.
*/
// TODO: migrate code to XML implementations.
boolean isUnrappedValueAllowedInXml();
/**
* Determines if the datatype uses mixed text and element content in XML.
*
* @return {@code true} if the datatype uses mixed text and element content in
* XML, or {@code false} otherwise
*/
// TODO: migrate code to XML implementations.
boolean isXmlMixed();
}