diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b2b84b79b..e50655472 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,6 +75,11 @@ jobs: - name: Perform CodeQL Analysis if: github.event_name == 'push' uses: github/codeql-action/analyze@46ed16ded91731b2df79a2893d3aea8e9f03b5c4 + - name: Upload CodeQL SARIF file + uses: github/codeql-action/upload-sarif@46ed16ded91731b2df79a2893d3aea8e9f03b5c4 + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref_name == 'develop' + with: + token: ${{ secrets.COMMIT_TOKEN }} - name: Test Website run: | # this needs to be run as a second build to ensure source is fully generated by the previous step diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/XmlMarkupParser.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/XmlMarkupParser.java index 917135e17..f682abfe2 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/XmlMarkupParser.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/XmlMarkupParser.java @@ -154,7 +154,8 @@ private void parseStartElement(XMLEventReader2 reader, StartElement start, Strin QName name = start.getName(); buffer.append('<') .append(name.getLocalPart()); - for (Attribute attribute : CollectionUtil.toIterable(start.getAttributes())) { + for (Attribute attribute : CollectionUtil.toIterable( + ObjectUtils.notNull(start.getAttributes()))) { buffer .append(' ') .append(attribute.getName().getLocalPart()) diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/XmlUtil.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/util/XmlUtil.java similarity index 92% rename from core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/XmlUtil.java rename to core/src/main/java/gov/nist/secauto/metaschema/core/model/util/XmlUtil.java index 1a3197209..7e43e6535 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/XmlUtil.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/util/XmlUtil.java @@ -24,7 +24,7 @@ * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. */ -package gov.nist.secauto.metaschema.core.model.xml; +package gov.nist.secauto.metaschema.core.model.util; import java.io.IOException; import java.net.URL; @@ -32,6 +32,8 @@ import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; +import edu.umd.cs.findbugs.annotations.NonNull; + public final class XmlUtil { private XmlUtil() { @@ -51,7 +53,8 @@ private XmlUtil() { * if an error occurred while creating the underlying stream */ @SuppressWarnings("resource") // user of source is expected to close - public static StreamSource getStreamSource(URL url) throws IOException { + @NonNull + public static StreamSource getStreamSource(@NonNull URL url) throws IOException { return new StreamSource(url.openStream(), url.toString()); } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/util/CollectionUtil.java b/core/src/main/java/gov/nist/secauto/metaschema/core/util/CollectionUtil.java index b09e7d397..b3294af60 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/util/CollectionUtil.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/util/CollectionUtil.java @@ -76,7 +76,7 @@ public static Stream toStream(@NonNull Iterator iterator) { */ @NonNull public static Iterable toIterable(@NonNull Stream stream) { - return toIterable(stream.iterator()); + return toIterable(ObjectUtils.notNull(stream.iterator())); } /** diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 7c189989c..059168b93 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -51,7 +51,7 @@ requires org.apache.commons.text; requires org.apache.logging.log4j; requires org.apache.xmlbeans; - requires org.json; + requires transitive org.json; requires org.jsoup; requires transitive com.fasterxml.jackson.databind; @@ -67,7 +67,7 @@ requires flexmark.ext.tables; requires transitive flexmark.ext.typographic; requires flexmark.html2md.converter; - requires flexmark.util.ast; + requires transitive flexmark.util.ast; requires flexmark.util.builder; requires flexmark.util.collection; requires flexmark.util.data; diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/DynamicBindingContext.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/DynamicBindingContext.java index 13316c1d0..e4ac39ef3 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/DynamicBindingContext.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/DynamicBindingContext.java @@ -44,8 +44,7 @@ import edu.umd.cs.findbugs.annotations.Nullable; /** - * Used to dynamically generate, compile, and load a set of Metaschema annotated - * Java classes. + * Used to dynamically generate, compile, and load a set of Metaschema annotated Java classes. */ public class DynamicBindingContext extends DefaultBindingContext { diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/DefaultMetaschemaClassFactory.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/DefaultMetaschemaClassFactory.java index 8445d837b..f089f4c8a 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/DefaultMetaschemaClassFactory.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/DefaultMetaschemaClassFactory.java @@ -117,11 +117,12 @@ class DefaultMetaschemaClassFactory implements IMetaschemaClassFactory { private final ITypeResolver typeResolver; /** - * Get a new instance of the this class generation factory that uses the provided - * {@code typeResolver}. + * Get a new instance of the this class generation factory that uses the + * provided {@code typeResolver}. * * @param typeResolver - * the resolver used to generate type information for Metasschema constructs + * the resolver used to generate type information for Metasschema + * constructs * @return the new class factory */ @NonNull @@ -130,11 +131,12 @@ public static DefaultMetaschemaClassFactory newInstance(@NonNull ITypeResolver t } /** - * Construct a new instance of the this class ganeration factory that uses the provided - * {@code typeResolver}. + * Construct a new instance of the this class ganeration factory that uses the + * provided {@code typeResolver}. * * @param typeResolver - * the resolver used to generate type information for Metasschema constructs + * the resolver used to generate type information for Metasschema + * constructs */ protected DefaultMetaschemaClassFactory(@NonNull ITypeResolver typeResolver) { this.typeResolver = typeResolver; @@ -165,6 +167,8 @@ public IGeneratedMetaschemaClass generateClass( module.getFieldDefinitions().stream()); Set classNames = new HashSet<>(); + + @SuppressWarnings("PMD.UseConcurrentHashMap") // map is unmodifiable Map definitionProductions = ObjectUtils.notNull(globalDefinitions // Get type information for assembly and field definitions. @@ -266,8 +270,8 @@ public IGeneratedClass generatePackageInfoClass( } /** - * Creates and configures a builder, for a Metaschema module, that can be used to generate a Java - * class. + * Creates and configures a builder, for a Metaschema module, that can be used + * to generate a Java class. * * @param metaschema * a parsed Metaschema module @@ -417,13 +421,14 @@ protected TypeSpec.Builder newClassBuilder( } /** - * Creates and configures a builder, for a Metaschema model definition, that can be used to generate - * a Java class. + * Creates and configures a builder, for a Metaschema model definition, that can + * be used to generate a Java class. * * @param typeInfo * the type information for the class to generate * @param isChild - * {@code true} if the class to be generated is a child class, or {@code false} otherwise + * {@code true} if the class to be generated is a child class, or + * {@code false} otherwise * @return the class builder * @throws IOException * if an error occurred while building the Java class @@ -466,13 +471,15 @@ protected TypeSpec.Builder newClassBuilder( } /** - * Generate the contents of the class represented by the provided {@code builder}. + * Generate the contents of the class represented by the provided + * {@code builder}. * * @param typeInfo * the type information for the class to build * @param builder * the builder to use for generating the class content - * @return the set of additional definitions for which child classes need to be generated + * @return the set of additional definitions for which child classes need to be + * generated */ protected Set buildClass( @NonNull IAssemblyDefinitionTypeInfo typeInfo, @@ -503,13 +510,15 @@ protected Set buildClass( } /** - * Generate the contents of the class represented by the provided {@code builder}. + * Generate the contents of the class represented by the provided + * {@code builder}. * * @param typeInfo * the type information for the class to build * @param builder * the builder to use for generating the class content - * @return the set of additional definitions for which child classes need to be generated + * @return the set of additional definitions for which child classes need to be + * generated */ protected Set buildClass( @NonNull IFieldDefinitionTypeInfo typeInfo, @@ -529,13 +538,15 @@ protected Set buildClass( } /** - * Generate the contents of the class represented by the provided {@code builder}. + * Generate the contents of the class represented by the provided + * {@code builder}. * * @param typeInfo * the type information for the class to build * @param builder * the builder to use for generating the class content - * @return the set of additional definitions for which child classes need to be generated + * @return the set of additional definitions for which child classes need to be + * generated */ @NonNull protected Set buildClass( @@ -627,7 +638,8 @@ protected Set buildClass( } /** - * Build the core property annotations that are common to all Metaschema classes. + * Build the core property annotations that are common to all Metaschema + * classes. * * @param typeInfo * the type information for the Java property to build diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/AbstractDeserializer.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/AbstractDeserializer.java index 1fcab45ea..342ada923 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/AbstractDeserializer.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/AbstractDeserializer.java @@ -62,13 +62,14 @@ public abstract class AbstractDeserializer * @param bindingContext * the binding context used to supply bound Java classes while writing * @param classBinding - * the bound class information for the Java type this deserializer is operating on + * the bound class information for the Java type this deserializer is + * operating on */ protected AbstractDeserializer(@NonNull IBindingContext bindingContext, @NonNull IAssemblyClassBinding classBinding) { super(bindingContext, classBinding); } - @Override + @NonNull public IConstraintValidationHandler getConstraintValidationHandler() { synchronized (this) { if (constraintValidationHandler == null) { diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/AbstractProblemHandler.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/AbstractProblemHandler.java new file mode 100644 index 000000000..480649016 --- /dev/null +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/AbstractProblemHandler.java @@ -0,0 +1,53 @@ +/* + * Portions of this software was developed by employees of the National Institute + * of Standards and Technology (NIST), an agency of the Federal Government and is + * being made available as a public service. Pursuant to title 17 United States + * Code Section 105, works of NIST employees are not subject to copyright + * protection in the United States. This software may be subject to foreign + * copyright. Permission in the United States and in foreign countries, to the + * extent that NIST may hold copyright, to use, copy, modify, create derivative + * works, and distribute this software and its documentation without fee is hereby + * granted on a non-exclusive basis, provided that this notice and disclaimer + * of warranty appears in all copies. + * + * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER + * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY + * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM + * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE + * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT + * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, + * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, + * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, + * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR + * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT + * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. + */ + +package gov.nist.secauto.metaschema.databind.io; + +import gov.nist.secauto.metaschema.databind.model.IBoundNamedInstance; + +import java.io.IOException; +import java.util.Collection; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public abstract class AbstractProblemHandler implements IProblemHandler { + + protected static void applyDefaults( + @NonNull Object targetObject, + @NonNull Collection unhandledInstances) throws IOException { + for (TYPE instance : unhandledInstances) { + Object value; + try { + value = instance.defaultValue(); + } catch (BindingException ex) { + throw new IOException(ex); + } + if (value != null) { + instance.setValue(targetObject, value); + } + } + } +} diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/DefaultBoundLoader.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/DefaultBoundLoader.java index eca6d3ec2..37f769151 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/DefaultBoundLoader.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/DefaultBoundLoader.java @@ -385,8 +385,11 @@ protected IDocumentNodeItem loadAsNodeItemInternal(@NonNull BufferedInputStream } @NonNull - protected IDocumentNodeItem deserializeToNodeItem(@NonNull Class clazz, @NonNull Format format, - @NonNull BufferedInputStream bis, @NonNull URI documentUri) throws IOException { + protected IDocumentNodeItem deserializeToNodeItem( + @NonNull Class clazz, + @NonNull Format format, + @NonNull BufferedInputStream bis, + @NonNull URI documentUri) throws IOException { try { bis.reset(); } catch (IOException ex) { diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/IBoundLoader.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/IBoundLoader.java index b90d85723..719583fb8 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/IBoundLoader.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/IBoundLoader.java @@ -112,11 +112,12 @@ default Format detectFormat(@NonNull File file) throws IOException { /** * Determine the format of the provided resource. *

- * This method will consume data from the provided {@link InputStream}. If the caller of this method - * intends to read data from the stream after determining the format, the caller should pass in a - * stream that can be reset. + * This method will consume data from the provided {@link InputStream}. If the + * caller of this method intends to read data from the stream after determining + * the format, the caller should pass in a stream that can be reset. *

- * This method will not close the provided {@link InputStream}, since it does not own the stream. + * This method will not close the provided {@link InputStream}, since it does + * not own the stream. * * @param is * an input stream for the resource @@ -132,12 +133,13 @@ default Format detectFormat(@NonNull InputStream is) throws IOException { /** * Determine the format of the provided resource. *

- * This method will consume data from any {@link InputStream} provided by the {@link InputSource}. - * If the caller of this method intends to read data from the stream after determining the format, - * the caller should pass in a stream that can be reset. + * This method will consume data from any {@link InputStream} provided by the + * {@link InputSource}. If the caller of this method intends to read data from + * the stream after determining the format, the caller should pass in a stream + * that can be reset. *

- * This method will not close any {@link InputStream} provided by the {@link InputSource}, since it - * does not own the stream. + * This method will not close any {@link InputStream} provided by the + * {@link InputSource}, since it does not own the stream. * * @param source * information about how to access the resource @@ -206,7 +208,8 @@ default Format detectFormat(@NonNull InputStream is) throws IOException { *

* This method should auto-detect the format of the provided resource. *

- * This method will not close the provided {@link InputStream}, since it does not own the stream. + * This method will not close the provided {@link InputStream}, since it does + * not own the stream. * * @param * the type of the bound object to return @@ -227,8 +230,8 @@ default Format detectFormat(@NonNull InputStream is) throws IOException { *

* This method will auto-detect the format of the provided resource. *

- * This method will not close any {@link InputStream} provided by the {@link InputSource}, since it - * does not own the stream. + * This method will not close any {@link InputStream} provided by the + * {@link InputSource}, since it does not own the stream. * * @param * the type of the bound object to return @@ -243,8 +246,8 @@ default Format detectFormat(@NonNull InputStream is) throws IOException { CLASS load(@NonNull InputSource source) throws IOException; /** - * Load data from the specified resource into a bound object with the type of the specified Java - * class. + * Load data from the specified resource into a bound object with the type of + * the specified Java class. * * @param * the Java type to load data into @@ -257,7 +260,9 @@ default Format detectFormat(@NonNull InputStream is) throws IOException { * if an error occurred while loading the data in the specified file */ @NonNull - default CLASS load(@NonNull Class clazz, @NonNull Path path) throws IOException { + default CLASS load( + @NonNull Class clazz, + @NonNull Path path) throws IOException { try { return load(clazz, ObjectUtils.notNull(path.toUri())); } catch (URISyntaxException ex) { @@ -266,8 +271,8 @@ default CLASS load(@NonNull Class clazz, @NonNull Path path) thro } /** - * Load data from the specified resource into a bound object with the type of the specified Java - * class. + * Load data from the specified resource into a bound object with the type of + * the specified Java class. * * @param * the Java type to load data into @@ -280,13 +285,15 @@ default CLASS load(@NonNull Class clazz, @NonNull Path path) thro * if an error occurred while loading the data in the specified file */ @NonNull - default CLASS load(@NonNull Class clazz, @NonNull File file) throws IOException { + default CLASS load( + @NonNull Class clazz, + @NonNull File file) throws IOException { return load(clazz, ObjectUtils.notNull(file.toPath())); } /** - * Load data from the specified resource into a bound object with the type of the specified Java - * class. + * Load data from the specified resource into a bound object with the type of + * the specified Java class. * * @param * the Java type to load data into @@ -301,13 +308,15 @@ default CLASS load(@NonNull Class clazz, @NonNull File file) thro * if the provided {@code url} is malformed */ @NonNull - default CLASS load(@NonNull Class clazz, @NonNull URL url) throws IOException, URISyntaxException { + default CLASS load( + @NonNull Class clazz, + @NonNull URL url) throws IOException, URISyntaxException { return load(clazz, ObjectUtils.notNull(url.toURI())); } /** - * Load data from the specified resource into a bound object with the type of the specified Java - * class. + * Load data from the specified resource into a bound object with the type of + * the specified Java class. * * @param * the Java type to load data into @@ -322,21 +331,24 @@ default CLASS load(@NonNull Class clazz, @NonNull URL url) throws * if the provided {@code url} is malformed */ @NonNull - default CLASS load(@NonNull Class clazz, @NonNull URI uri) throws IOException, URISyntaxException { + default CLASS load( + @NonNull Class clazz, + @NonNull URI uri) throws IOException, URISyntaxException { return load(clazz, toInputSource(ObjectUtils.requireNonNull(uri))); } /** - * Load data from the specified resource into a bound object with the type of the specified Java - * class. + * Load data from the specified resource into a bound object with the type of + * the specified Java class. *

- * This method will not close the provided {@link InputStream}, since it does not own the stream. + * This method will not close the provided {@link InputStream}, since it does + * not own the stream. *

- * Implementations of this method will do format detection. This process might leave the provided - * {@link InputStream} at a position beyond the last parsed location. If you want to avoid this - * possibility, use and implementation of {@link IDeserializer#deserialize(InputStream, URI)} - * instead, such as what is provided by - * {@link DefaultBindingContext#newDeserializer(Format, Class)}. + * Implementations of this method will do format detection. This process might + * leave the provided {@link InputStream} at a position beyond the last parsed + * location. If you want to avoid this possibility, use and implementation of + * {@link IDeserializer#deserialize(InputStream, URI)} instead, such as what is + * provided by {@link DefaultBindingContext#newDeserializer(Format, Class)}. * * @param * the Java type to load data into @@ -344,14 +356,36 @@ default CLASS load(@NonNull Class clazz, @NonNull URI uri) throws * the class for the java type * @param source * information about how to access the resource - * @return the loaded instance data + * @return the loaded data * @throws IOException - * if an error occurred while loading the data in the specified file + * if an error occurred while loading the data from the specified + * resource */ @NonNull - CLASS load(@NonNull Class clazz, @NonNull InputSource source) throws IOException; + CLASS load( + @NonNull Class clazz, + @NonNull InputSource source) throws IOException; - IDocumentNodeItem loadAsNodeItem(@NonNull Format format, @NonNull InputSource source) throws IOException; + /** + * Load data expressed using the provided {@code format} and return that data as + * a Metapath node item. + *

+ * The specific Metaschema model is auto-detected by analyzing the source. The + * class reported is implementation specific. + * + * @param format + * the expected format of the data to parse + * @param source + * information about how to access the resource + * @return the Metapath node item for the parsed data + * @throws IOException + * if an error occurred while loading the data from the specified + * resource + */ + @NonNull + IDocumentNodeItem loadAsNodeItem( + @NonNull Format format, + @NonNull InputSource source) throws IOException; /** * Get the configured Metaschema binding context to use to load Java types. @@ -360,6 +394,29 @@ default CLASS load(@NonNull Class clazz, @NonNull URI uri) throws */ IBindingContext getBindingContext(); + /** + * Auto convert the provided {@code source} to the provided {@code toFormat}. + * Write the converted content to the provided {@code destination}. + *

+ * The format of the source is expected to be auto detected using + * {@link #detectFormat(Path)}. + * + * @param + * the Java type to load data into + * @param source + * the resource to convert + * @param destination + * the resource to write converted content to + * @param toFormat + * the format to convert to + * @param rootClass + * the class for the Java type to load data into + * @throws FileNotFoundException + * the the provided source file was not found + * @throws IOException + * if an error occurred while loading the data from the specified + * resource or writing the converted data to the specified destination + */ default void convert( @NonNull Path source, @NonNull Path destination, @@ -371,6 +428,29 @@ default void convert( serializer.serialize(object, destination); } + /** + * Auto convert the provided {@code source} to the provided {@code toFormat}. + * Write the converted content to the provided {@code destination}. + *

+ * The format of the source is expected to be auto detected using + * {@link #detectFormat(Path)}. + * + * @param + * the Java type to load data into + * @param source + * the resource to convert + * @param os + * the output stream to write converted content to + * @param toFormat + * the format to convert to + * @param rootClass + * the class for the Java type to load data into + * @throws FileNotFoundException + * the the provided source file was not found + * @throws IOException + * if an error occurred while loading the data from the specified + * resource or writing the converted data to the specified destination + */ default void convert( @NonNull Path source, @NonNull OutputStream os, diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/IDeserializer.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/IDeserializer.java index 0d9b8a065..77f415af3 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/IDeserializer.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/IDeserializer.java @@ -47,8 +47,8 @@ import edu.umd.cs.findbugs.annotations.NonNull; /** - * Implementations of this interface are able to read structured data into a bound object instance - * of the parameterized type. + * Implementations of this interface are able to read structured data into a + * bound object instance of the parameterized type. * * @param * the Java type into which data can be read @@ -70,15 +70,15 @@ public interface IDeserializer extends IMutableConfiguration extends AbstractDeserializer { private JsonFactory jsonFactory; - public DefaultJsonDeserializer(@NonNull IBindingContext bindingContext, @NonNull IAssemblyClassBinding classBinding) { + /** + * Construct a new JSON deserializer that will parse the bound class identified by the + * {@code classBinding}. + * + * @param bindingContext + * the binding context used to supply bound Java classes while writing + * @param classBinding + * the bound class information for the Java type this deserializer is operating on + */ + public DefaultJsonDeserializer( + @NonNull IBindingContext bindingContext, + @NonNull IAssemblyClassBinding classBinding) { super(bindingContext, classBinding); } + /** + * Get a JSON factory instance. + *

+ * This method can be used by sub-classes to create a customized factory instance. + * + * @return the factory + */ @NonNull - protected JsonFactory getJsonFactoryInstance() { + protected JsonFactory newJsonFactoryInstance() { return JsonFactoryFactory.instance(); } + /** + * Get the parser factory associated with this deserializer. + * + * @return the factory instance + */ @NonNull protected JsonFactory getJsonFactory() { synchronized (this) { if (jsonFactory == null) { - jsonFactory = getJsonFactoryInstance(); + jsonFactory = newJsonFactoryInstance(); } assert jsonFactory != null; return jsonFactory; } } - protected void setJsonFactory(@NonNull JsonFactory jsonFactory) { - synchronized (this) { - this.jsonFactory = jsonFactory; - } - } - + /** + * Using the managed JSON factory, create a new JSON parser instance using the provided reader. + * + * @param reader + * the reader for the parser to read data from + * @return the new parser + * @throws IOException + * if an error occurred while creating the parser + */ + @SuppressWarnings("resource") // reader resource not owned @NonNull - protected JsonParser newJsonParser(@NonNull Reader reader) throws IOException { + protected final JsonParser newJsonParser(@NonNull Reader reader) throws IOException { return ObjectUtils.notNull(getJsonFactory().createParser(reader)); } @@ -96,7 +123,7 @@ protected INodeItem deserializeToNodeItemInternal(@NonNull Reader reader, @NonNu RootAssemblyDefinition root = new RootAssemblyDefinition(classBinding); // now parse the root property - @SuppressWarnings("unchecked") CLASS value = ObjectUtils.requireNonNull((CLASS) parser.read(root)); + CLASS value = ObjectUtils.requireNonNull(parser.read(root)); // // we should be at the end object // JsonUtil.assertCurrent(parser, JsonToken.END_OBJECT); @@ -109,7 +136,7 @@ protected INodeItem deserializeToNodeItemInternal(@NonNull Reader reader, @NonNu JsonUtil.assertAndAdvance(jsonParser, JsonToken.START_OBJECT); @SuppressWarnings("unchecked") CLASS value - = (CLASS) parser.readAssemblyDefinitionValue(classBinding, null); + = (CLASS) parser.readDefinitionValue(classBinding, null, false); // advance past the end object JsonUtil.assertAndAdvance(jsonParser, JsonToken.END_OBJECT); diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/DefaultJsonProblemHandler.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/DefaultJsonProblemHandler.java index f76676da8..a0c1b011a 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/DefaultJsonProblemHandler.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/DefaultJsonProblemHandler.java @@ -26,66 +26,46 @@ package gov.nist.secauto.metaschema.databind.io.json; -import com.fasterxml.jackson.core.JsonParser; - -import gov.nist.secauto.metaschema.databind.io.BindingException; -import gov.nist.secauto.metaschema.databind.model.IAssemblyClassBinding; +import gov.nist.secauto.metaschema.databind.io.AbstractProblemHandler; import gov.nist.secauto.metaschema.databind.model.IBoundNamedInstance; import gov.nist.secauto.metaschema.databind.model.IClassBinding; -import gov.nist.secauto.metaschema.databind.model.IJsonBindingSupplier; import java.io.IOException; -import java.util.Collections; +import java.util.Collection; import java.util.HashSet; -import java.util.Map; import java.util.Set; -public class DefaultJsonProblemHandler implements IJsonProblemHandler { - private static final String JSON_SCHEMA_ROOT_FIELD_NAME = "$schema"; - private static final Set IGNORED_ROOT_FIELD_NAMES; +public class DefaultJsonProblemHandler + extends AbstractProblemHandler + implements IJsonProblemHandler { + private static final String JSON_SCHEMA_FIELD_NAME = "$schema"; + private static final Set IGNORED_FIELD_NAMES; static { - IGNORED_ROOT_FIELD_NAMES = new HashSet<>(); - IGNORED_ROOT_FIELD_NAMES.add(JSON_SCHEMA_ROOT_FIELD_NAME); + IGNORED_FIELD_NAMES = new HashSet<>(); + IGNORED_FIELD_NAMES.add(JSON_SCHEMA_FIELD_NAME); } + @SuppressWarnings("resource") @Override - public boolean handleUnknownRootProperty( - IAssemblyClassBinding classBinding, + public boolean handleUnknownProperty( + IClassBinding classBinding, + Object targetObject, String fieldName, - JsonParser parser) throws IOException { + IJsonParsingContext parsingContext) throws IOException { boolean retval = false; - if (IGNORED_ROOT_FIELD_NAMES.contains(fieldName)) { - JsonUtil.skipNextValue(parser); + if (IGNORED_FIELD_NAMES.contains(fieldName)) { + JsonUtil.skipNextValue(parsingContext.getReader()); retval = true; } return retval; } - // TODO: implement this - @Override - public boolean canHandleUnknownProperty( - IClassBinding classBinding, - String propertyName, - JsonParser parser) throws IOException { - return false; - } - @Override - public boolean handleUnknownProperty( - IClassBinding classBinding, - String propertyName, - JsonParser parser) throws IOException { - return false; + public void handleMissingInstances( + IClassBinding parentDefinition, + Object targetObject, + Collection unhandledInstances) throws IOException { + applyDefaults(targetObject, unhandledInstances); } - - // TODO: implement this - @Override - public Map handleMissingFields( - IClassBinding classBinding, - Map missingPropertyBindings, - JsonParser parser) throws BindingException { - return Collections.emptyMap(); - } - } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/IJsonParsingContext.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/IJsonParsingContext.java index 5253eacc0..9ce2c4e8b 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/IJsonParsingContext.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/IJsonParsingContext.java @@ -34,12 +34,29 @@ import java.io.IOException; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; public interface IJsonParsingContext extends IParsingContext { + /** + * Parse a JSON value described by the provided {@code definition}. + * + * @param + * the resulting value type + * @param definition + * the bound Metaschema definition describing the structure of the JSON data to parse + * @param parentInstance + * the parent Java object that will contain this data + * @param requiresJsonKey + * if {@code true} the data will have a JSON key, or {@code false} the data will not have a + * JSON key + * @return the Java object containing the parsed data + * @throws IOException + * if an error occurred while parsing the JSON + */ @NonNull - Object readDefinitionValue( - @NonNull IClassBinding classBinding, - @NonNull Object parentInstance, + T readDefinitionValue( + @NonNull IClassBinding definition, + @Nullable Object parentInstance, boolean requiresJsonKey) throws IOException; } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/IJsonProblemHandler.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/IJsonProblemHandler.java index e59c9b127..ba33dfc94 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/IJsonProblemHandler.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/IJsonProblemHandler.java @@ -26,56 +26,60 @@ package gov.nist.secauto.metaschema.databind.io.json; -import com.fasterxml.jackson.core.JsonParser; - -import gov.nist.secauto.metaschema.databind.io.BindingException; import gov.nist.secauto.metaschema.databind.io.IProblemHandler; -import gov.nist.secauto.metaschema.databind.model.IAssemblyClassBinding; import gov.nist.secauto.metaschema.databind.model.IBoundNamedInstance; import gov.nist.secauto.metaschema.databind.model.IClassBinding; -import gov.nist.secauto.metaschema.databind.model.IJsonBindingSupplier; import java.io.IOException; -import java.util.Map; +import java.util.Collection; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; public interface IJsonProblemHandler extends IProblemHandler { - boolean handleUnknownRootProperty( - @NonNull IAssemblyClassBinding classBinding, - @NonNull String fieldName, - @NonNull JsonParser parser) throws IOException; - - boolean canHandleUnknownProperty( - @NonNull IClassBinding classBinding, - @NonNull String propertyName, - @NonNull JsonParser parser) - throws IOException; + /** + * Callback used to handle a JSON property that is unknown to the model being + * parsed. + * + * @param classBinding + * the bound class currently describing the data being parsed + * @param targetObject + * the Java object for the {@code parentDefinition} + * @param fieldName + * the unknown JSON field name + * @param parsingContext + * the JSON parsing context used for parsing + * @return {@code true} if the attribute was handled by this method, or + * {@code false} otherwise + * @throws IOException + * if an error occurred while handling the unrecognized data + */ boolean handleUnknownProperty( @NonNull IClassBinding classBinding, - @NonNull String propertyName, - @NonNull JsonParser parser) throws IOException; + @Nullable Object targetObject, + @NonNull String fieldName, + @NonNull IJsonParsingContext parsingContext) throws IOException; /** - * A callback used to handle bound properties for which no data was found when the content was - * parsed. + * A callback used to handle bound properties for which no data was found when + * the content was parsed. *

- * This can be used to supply default or prescribed values based on application logic. + * This can be used to supply default or prescribed values based on application + * logic. * - * @param classBinding + * @param parentDefinition * the bound class on which the missing properties are found - * @param missingPropertyBindings - * a map of field names to property bindings for missing fields - * @param parser - * the parser used for deserialziation - * @return a mapping of property to suppliers for any properties handled by this method - * @throws BindingException - * if an unhandled binding error has occurred for any reason + * @param targetObject + * the Java object for the {@code parentDefinition} + * @param unhandledInstances + * the set of instances that had no data to parse + * @throws IOException + * if an error occurred while handling the missing instances */ - Map handleMissingFields( - @NonNull IClassBinding classBinding, - @NonNull Map missingPropertyBindings, - @NonNull JsonParser parser) - throws BindingException; + void handleMissingInstances( + @NonNull IClassBinding parentDefinition, + @NonNull Object targetObject, + @NonNull Collection unhandledInstances) + throws IOException; } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/JsonFactoryFactory.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/JsonFactoryFactory.java index 41c566d19..e9f7d0f27 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/JsonFactoryFactory.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/JsonFactoryFactory.java @@ -40,22 +40,38 @@ private JsonFactoryFactory() { // disable construction } + /** + * Create a new {@link JsonFactory}. + * + * @return the factory + */ @NonNull - public static JsonFactory newJsonFactoryInstance() { + private static JsonFactory newJsonFactoryInstance() { JsonFactory retval = new JsonFactory(); configureJsonFactory(retval); return retval; } + /** + * Get the cached {@link JsonFactory} instance. + * + * @return the factory + */ @NonNull public static JsonFactory instance() { return SINGLETON; } + /** + * Apply a standard configuration to the provided JSON {@code factory}. + * + * @param factory + * the factory to configure + */ public static void configureJsonFactory(@NonNull JsonFactory factory) { - // avoid automatically closing streams not owned by the reader + // avoid automatically closing parsing streams not owned by the reader factory.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE); - // avoid automatically closing streams not owned by the reader + // avoid automatically closing generation streams not owned by the reader factory.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); } } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/JsonUtil.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/JsonUtil.java index af11ee261..458d3163c 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/JsonUtil.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/JsonUtil.java @@ -44,6 +44,7 @@ import java.util.List; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; public final class JsonUtil { private static final Logger LOGGER = LogManager.getLogger(JsonUtil.class); @@ -52,33 +53,54 @@ private JsonUtil() { // disable construction } + /** + * Generate an informational string describing the token at the current location + * of the provided {@code parser}. + * + * @param parser + * the JSON parser + * @return the informational string + * @throws IOException + * if an error occurred while getting the information from the parser + */ + @SuppressWarnings("null") + @NonNull public static String toString(@NonNull JsonParser parser) throws IOException { - StringBuilder builder = new StringBuilder(32); - builder + return new StringBuilder(32) .append(parser.currentToken().name()) .append(" '") .append(parser.getText()) - .append("' at location '") - .append(toString(ObjectUtils.notNull(parser.getCurrentLocation()))) - .append('\''); - return builder.toString(); + .append('\'') + .append(generateLocationMessage(parser)) + .toString(); } + /** + * Generate an informational string describing the provided {@code location}. + * + * @param location + * a JSON parser location + * @return the informational string + */ + @SuppressWarnings("null") + @NonNull public static String toString(@NonNull JsonLocation location) { - StringBuilder builder = new StringBuilder(); - builder + return new StringBuilder(8) .append(location.getLineNr()) .append(':') - .append(location.getColumnNr()); - return builder.toString(); + .append(location.getColumnNr()) + .toString(); } + @Nullable public static JsonToken advanceTo(@NonNull JsonParser parser, JsonToken token) throws IOException { JsonToken currentToken = null; while (parser.hasCurrentToken() && !token.equals(currentToken = parser.currentToken())) { currentToken = parser.nextToken(); if (LOGGER.isWarnEnabled()) { - LOGGER.warn("skipping over: {}", toString(parser)); + LOGGER.warn("skipping over: {}{}", + toString(parser), + generateLocationMessage(parser)); } } return currentToken; @@ -88,6 +110,7 @@ public static JsonToken advanceTo(@NonNull JsonParser parser, JsonToken token) t "resource", // parser not owned "PMD.CyclomaticComplexity" // acceptable }) + @Nullable public static JsonToken skipNextValue(@NonNull JsonParser parser) throws IOException { JsonToken currentToken = parser.currentToken(); @@ -111,7 +134,9 @@ public static JsonToken skipNextValue(@NonNull JsonParser parser) throws IOExcep break; default: // error - String msg = String.format("Unhandled JsonToken %s", toString(parser)); + String msg = String.format("Unhandled JsonToken %s%s.", + toString(parser), + generateLocationMessage(parser)); LOGGER.error(msg); throw new UnsupportedOperationException(msg); } @@ -147,79 +172,113 @@ public static boolean checkEndOfValue(@NonNull JsonParser parser, @NonNull JsonT return retval; } - public static void assertCurrent(@NonNull JsonParser parser, @NonNull JsonToken... expectedTokens) { + public static void assertCurrent( + @NonNull JsonParser parser, + @NonNull JsonToken... expectedTokens) { JsonToken current = parser.currentToken(); - assert Arrays.stream(expectedTokens).anyMatch(expected -> expected.equals(current)) : getAssertMessage( - expectedTokens, parser.currentToken(), ObjectUtils.notNull(parser.getCurrentLocation())); + assert Arrays.stream(expectedTokens) + .anyMatch(expected -> expected.equals(current)) : getAssertMessage( + parser, + expectedTokens, + parser.currentToken()); } public static void assertCurrentIsFieldValue(@NonNull JsonParser parser) { JsonToken token = parser.currentToken(); assert token.isStructStart() || token.isScalarValue() : String.format( - "Expected a START_OBJECT, START_ARRAY, or VALUE_xxx token, but found JsonToken '%s' at '%s'.", + "Expected a START_OBJECT, START_ARRAY, or VALUE_xxx token, but found JsonToken '%s'%s.", token, - JsonUtil.toString(ObjectUtils.notNull(parser.getCurrentLocation()))); + generateLocationMessage(parser)); } - public static JsonToken assertAndAdvance(@NonNull JsonParser parser, @NonNull JsonToken expectedToken) + @Nullable + public static JsonToken assertAndAdvance( + @NonNull JsonParser parser, + @NonNull JsonToken expectedToken) throws IOException { JsonToken token = parser.currentToken(); - assert expectedToken.equals(token) : getAssertMessage(expectedToken, token, - ObjectUtils.notNull(parser.getCurrentLocation())); + assert expectedToken.equals(token) : getAssertMessage( + parser, + expectedToken, + token); return parser.nextToken(); } - public static JsonToken advanceAndAssert(@NonNull JsonParser parser, @NonNull JsonToken expectedToken) + @Nullable + public static JsonToken advanceAndAssert( + @NonNull JsonParser parser, + @NonNull JsonToken expectedToken) throws IOException { JsonToken token = parser.nextToken(); - assert expectedToken.equals(token) : getAssertMessage(expectedToken, token, - ObjectUtils.notNull(parser.getCurrentLocation())); + assert expectedToken.equals(token) : getAssertMessage( + parser, + expectedToken, + token); return token; } @NonNull - public static String getAssertMessage(@NonNull JsonToken expected, JsonToken actual, @NonNull JsonLocation location) { + public static String getAssertMessage( + @NonNull JsonParser parser, + @NonNull JsonToken expected, + JsonToken actual) { return ObjectUtils.notNull( - String.format("Expected JsonToken '%s', but found JsonToken '%s' at '%s'.", + String.format("Expected JsonToken '%s', but found JsonToken '%s'%s.", expected, actual, - JsonUtil.toString(location))); + generateLocationMessage(parser))); } @NonNull - public static String getAssertMessage(@NonNull JsonToken[] expected, JsonToken actual, - @NonNull JsonLocation location) { + public static String getAssertMessage( + @NonNull JsonParser parser, + @NonNull JsonToken[] expected, + JsonToken actual) { List expectedTokens = ObjectUtils.notNull(Arrays.asList(expected)); - return getAssertMessage(expectedTokens, actual, location); + return getAssertMessage(parser, expectedTokens, actual); } @NonNull - public static String getAssertMessage(@NonNull Collection expected, JsonToken actual, - @NonNull JsonLocation location) { + public static String getAssertMessage( + @NonNull JsonParser parser, + @NonNull Collection expected, + JsonToken actual) { return ObjectUtils.notNull( - String.format("Expected JsonToken(s) '%s', but found JsonToken '%s' at '%s'.", + String.format("Expected JsonToken(s) '%s', but found JsonToken '%s'%s.", expected.stream().map(token -> token.name()).collect(CustomCollectors.joiningWithOxfordComma("and")), actual, - JsonUtil.toString(location))); + generateLocationMessage(parser))); } @NonNull - public static String toLocationContext(@NonNull JsonParser parser, @NonNull IClassBinding classBinding, - IBoundNamedInstance property) { - StringBuilder builder = new StringBuilder(64); - builder - .append("property '") - .append(property.getEffectiveName()) - .append("' on class '") - .append(classBinding.getBoundClass().getName()) - .append("' at location '"); + public static CharSequence generateLocationMessage(@NonNull JsonParser parser) { JsonLocation location = parser.getCurrentLocation(); - builder + return location == null ? "" : generateLocationMessage(location); + } + + @SuppressWarnings("null") + @NonNull + public static CharSequence generateLocationMessage(@NonNull JsonLocation location) { + return new StringBuilder() + .append(" at location '") .append(location.getLineNr()) .append(':') .append(location.getColumnNr()) .append('\''); - return ObjectUtils.notNull(builder.toString()); } + @SuppressWarnings("null") + @NonNull + public static String toLocationContext( + @NonNull JsonParser parser, + @NonNull IClassBinding classBinding, + IBoundNamedInstance property) { + return new StringBuilder() + .append("property '") + .append(property.getEffectiveName()) + .append("' on class '") + .append(classBinding.getBoundClass().getName()) + .append('\'') + .append(generateLocationMessage(parser)).toString(); + } } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/MetaschemaJsonParser.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/MetaschemaJsonParser.java index f5ac5e3ad..ace1b6bfc 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/MetaschemaJsonParser.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/MetaschemaJsonParser.java @@ -46,13 +46,15 @@ import org.apache.logging.log4j.Logger; import java.io.IOException; -import java.util.HashSet; +import java.util.Collection; import java.util.Map; -import java.util.Set; -import java.util.function.Predicate; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; public class MetaschemaJsonParser implements IJsonParsingContext { @@ -63,11 +65,27 @@ public class MetaschemaJsonParser @NonNull private final IJsonProblemHandler problemHandler; + /** + * Construct a new Metaschema-aware JSON parser using the default problem + * handler. + * + * @param parser + * the JSON parser to parse with + * @see DefaultJsonProblemHandler + */ public MetaschemaJsonParser( @NonNull JsonParser parser) { this(parser, new DefaultJsonProblemHandler()); } + /** + * Construct a new Metaschema-aware JSON parser. + * + * @param parser + * the JSON parser to parse with + * @param problemHandler + * the problem handler implementation to use + */ public MetaschemaJsonParser( @NonNull JsonParser parser, @NonNull IJsonProblemHandler problemHandler) { @@ -99,7 +117,7 @@ public IJsonProblemHandler getProblemHandler() { *

  • a {@link JsonToken#FIELD_NAME} representing the root field to parse, * or
  • *
  • a peer field to the root field that will be handled by the - * {@link IJsonProblemHandler#handleUnknownRootProperty(IAssemblyClassBinding, String, JsonParser)} + * {@link IJsonProblemHandler#handleUnknownProperty(IClassBinding, Object, String, IJsonParsingContext)} * method.
  • * *

    @@ -116,14 +134,19 @@ public IJsonProblemHandler getProblemHandler() { * found. * * + * @param + * the Java type of the resulting bound instance * @param definition * the root definition to parse * @return the bound object instance representing the JSON object * @throws IOException - * if an error occurred while reading the JSON + * if an error occurred while parsing the JSON */ + @SuppressWarnings({ + "PMD.CyclomaticComplexity" // acceptable + }) @Nullable - public Object read(@NonNull IRootAssemblyClassBinding definition) throws IOException { + public T read(@NonNull IRootAssemblyClassBinding definition) throws IOException { boolean objectWrapper = false; if (parser.currentToken() == null) { parser.nextToken(); @@ -149,9 +172,10 @@ public Object read(@NonNull IRootAssemblyClassBinding definition) throws IOExcep JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME); JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT); - instance = readAssemblyDefinitionValue( + instance = readDefinitionValue( definition.getRootDefinition(), - null); + null, + false); // advance past the end object JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT); @@ -160,7 +184,7 @@ public Object read(@NonNull IRootAssemblyClassBinding definition) throws IOExcep break; } - if (!getProblemHandler().handleUnknownRootProperty(definition, fieldName, parser)) { + if (!getProblemHandler().handleUnknownProperty(definition, instance, fieldName, this)) { LOGGER.warn("Skipping unhandled top-level JSON field '{}'.", fieldName); JsonUtil.skipNextValue(parser); } @@ -175,496 +199,282 @@ public Object read(@NonNull IRootAssemblyClassBinding definition) throws IOExcep JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT); } - return instance; - } - - @Override - public Object readDefinitionValue(IClassBinding targetDefinition, Object targetObject, boolean requiresJsonKey) - throws IOException { - Object retval; - if (targetDefinition instanceof IAssemblyClassBinding) { - retval = readAssemblyDefinitionValue((IAssemblyClassBinding) targetDefinition, targetObject); - } else if (targetDefinition instanceof IFieldClassBinding) { - retval = readFieldDefinitionValue((IFieldClassBinding) targetDefinition, targetObject, requiresJsonKey); - } else { - throw new UnsupportedOperationException( - String.format("Unsupported class binding type: %s", targetDefinition.getClass().getName())); - } - return retval; + return ObjectUtils.asType(instance); } /** - * Reads a JSON/YAML object storing the associated data in the Java object - * {@code parentInstance}. - *

    - * When called the current {@link JsonToken} of the {@link JsonParser} is - * expected to be a {@link JsonToken#START_OBJECT}. + * Read the data associated with the {@code instance} and apply it to the + * provided {@code parentObject}. *

    - * After returning the current {@link JsonToken} of the {@link JsonParser} is - * expected to be the next token after the {@link JsonToken#END_OBJECT} for this - * class. + * Consumes the field if the field's name matches. If it matches, then + * {@code true} is returned after parsing the value. Otherwise, {@code false} is + * returned to indicate the property was not parsed. * - * @param targetDefinition - * the definition to parse - * @param targetObject - * the parent Java object to store the data in, which can be - * {@code null} if there is no parent - * @return the instance + * @param instance + * the instance to parse data for + * @param parentObject + * the Java object that data parsed by this method will be stored in + * @return {@code true} if the instance was parsed, or {@code false} if the data + * did not contain information for this instance * @throws IOException - * if an error occurred while reading the parsed content + * if an error occurred while parsing the input */ - @NonNull - protected Object readAssemblyDefinitionValue( - @NonNull IAssemblyClassBinding targetDefinition, - @Nullable Object targetObject) throws IOException { - JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); + protected boolean readInstance( + @NonNull IBoundNamedInstance instance, + @NonNull Object parentObject) throws IOException { + // the parser's current token should be the JSON field name + JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME); - try { - Object instance = targetDefinition.newInstance(); + String propertyName = parser.currentName(); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("reading property {}", propertyName); + } - readAssemblyDefinitionContents(targetDefinition, instance, targetObject); + boolean handled = instance.getJsonName().equals(propertyName); + if (handled) { + // advance past the field name + parser.nextToken(); - return instance; - } catch (BindingException ex) { - throw new IOException( - String.format("Failed to parse JSON object for '%s'", targetDefinition.getBoundClass().getName()), ex); + Object value = readInstanceValue(instance, parentObject); + if (value != null) { + instance.setValue(parentObject, value); + } } + + // the current token will be either the next instance field name or the end of + // the parent object + JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); + return handled; } /** - * Parses the JSON field contents related to the bound assembly. - *

    - * This method expects the parser's current token to be at the first field name - * to parse. - *

    - * After parsing the current token will be the token at the end object - * immediately after all the fields and values. + * Read the data associated with the {@code instance}. * - * @param targetDefinition - * the Metaschema definition for the target object being read * @param instance - * the bound object to read data into - * @param parentInstance - * the parent object used for deserialization callbacks + * the instance that describes the syntax of the data to read + * @param parentObject + * the Java object that data parsed by this method will be stored in + * @return the parsed value(s) * @throws IOException - * if an error occurred while reading the JSON + * if an error occurred while parsing the input */ - protected void readAssemblyDefinitionContents( - @NonNull IAssemblyClassBinding targetDefinition, - @NonNull Object instance, - @Nullable Object parentInstance) throws IOException { - JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); - - try { - targetDefinition.callBeforeDeserialize(instance, parentInstance); - } catch (BindingException ex) { - throw new IOException("an error occured calling the beforeDeserialize() method", ex); - } - - IBoundFlagInstance jsonKey = targetDefinition.getJsonKeyFlagInstance(); - Map properties; - if (jsonKey == null) { - properties = targetDefinition.getNamedInstances(null); + protected Object readInstanceValue( + @NonNull IBoundNamedInstance instance, + @NonNull Object parentObject) throws IOException { + Object value; + if (instance instanceof IBoundNamedModelInstance) { + + // Deal with the collection or value type + IModelPropertyInfo info = ((IBoundNamedModelInstance) instance).getPropertyInfo(); + IPropertyCollector collector = info.newPropertyCollector(); + + // let the property info parse the value + info.readValue(collector, parentObject, this); + + // get the underlying value + value = collector.getValue(); + } else if (instance instanceof IBoundFlagInstance) { + // just read the value directly + value = ((IBoundFlagInstance) instance).getDefinition().getJavaTypeAdapter().parse(parser); + } else if (instance instanceof IBoundFieldValueInstance) { + // just read the value directly + value = ((IBoundFieldValueInstance) instance).getJavaTypeAdapter().parse(parser); } else { - properties = targetDefinition.getNamedInstances((flag) -> !jsonKey.equals(flag)); - - // if there is a json key, the first field will be the key - String key = ObjectUtils.notNull(parser.getCurrentName()); - - Object value = jsonKey.getDefinition().getJavaTypeAdapter().parse(key); - jsonKey.setValue(instance, value.toString()); - - // advance past the FIELD_NAME - // next the value will be a start object - JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME); - // - // JsonUtil.assertAndAdvance(jsonParser, JsonToken.START_OBJECT); + throw new UnsupportedOperationException( + String.format("Unsupported instance type: %s", instance.getClass().getName())); } + return value; + } - Set handledProperties = new HashSet<>(); - while (!JsonToken.END_OBJECT.equals(parser.currentToken())) { - String propertyName = parser.getCurrentName(); - IBoundNamedInstance property = properties.get(propertyName); + @NonNull + private static Map getInstancesToParse( + @NonNull IClassBinding targetDefinition, + boolean requiresJsonKey) { + Collection flags = targetDefinition.getFlagInstances(); + int flagCount = flags.size() - (requiresJsonKey ? 1 : 0); - boolean handled = false; - if (property != null) { - handled = readInstanceValues(property, instance); - } + @SuppressWarnings("resource") Stream instanceStream; + if (targetDefinition instanceof IAssemblyClassBinding) { + instanceStream = ((IAssemblyClassBinding) targetDefinition).getModelInstances().stream(); + // .flatMap((instance) -> { + // return instance instanceof IChoiceInstance ? + // ((IChoiceInstance)instance).getNamedModelInstances().stream() + // }); + } else if (targetDefinition instanceof IFieldClassBinding) { + IFieldClassBinding targetFieldDefinition = (IFieldClassBinding) targetDefinition; - if (handled) { - handledProperties.add(propertyName); + IBoundFlagInstance jsonValueKeyFlag = targetFieldDefinition.getJsonValueKeyFlagInstance(); + if (jsonValueKeyFlag == null && flagCount > 0) { + // the field value is handled as named field + IBoundFieldValueInstance fieldValue = targetFieldDefinition.getFieldValueInstance(); + instanceStream = Stream.of(fieldValue); } else { - if (LOGGER.isWarnEnabled()) { - LOGGER.warn("Unrecognized property named '{}' at '{}'", propertyName, - JsonUtil.toString(ObjectUtils.notNull(parser.getCurrentLocation()))); - } - JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME); - JsonUtil.skipNextValue(parser); + // only the value, with no flags or a JSON value key flag + instanceStream = Stream.empty(); } + } else { + throw new UnsupportedOperationException( + String.format("Unsupported class binding type: %s", targetDefinition.getClass().getName())); } - // set undefined properties - // TODO: re-implement this by removing the parsed properties from the properties - // map to speed up - for (Map.Entry entry : properties.entrySet()) { - if (!handledProperties.contains(entry.getKey())) { - // REFACTOR: Implement problem handler - // use the default value of the collector - IBoundNamedInstance property = ObjectUtils.notNull(entry.getValue()); - try { - property.setValue(instance, property.defaultValue()); - } catch (BindingException ex) { - throw new IOException(ex); - } - } + if (requiresJsonKey) { + IBoundFlagInstance jsonKey = targetDefinition.getJsonKeyFlagInstance(); + assert jsonKey != null; + instanceStream = Stream.concat( + flags.stream().filter((flag) -> !jsonKey.equals(flag)), + instanceStream); + } else { + instanceStream = Stream.concat( + flags.stream(), + instanceStream); } + return ObjectUtils.notNull(instanceStream.collect( + Collectors.toMap( + IBoundNamedInstance::getJsonName, + Function.identity()))); + } - if (jsonKey != null) { - // read the END_OBJECT for the JSON key value - JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT); + @Override + public T readDefinitionValue(IClassBinding targetDefinition, Object parentObject, boolean requiresJsonKey) + throws IOException { + Object targetObject; + try { + targetObject = targetDefinition.newInstance(); + targetDefinition.callBeforeDeserialize(targetObject, parentObject); + } catch (BindingException ex) { + throw new IOException(ex); } + readDefinitionValueContents(targetDefinition, targetObject, requiresJsonKey); + try { - targetDefinition.callAfterDeserialize(instance, parentInstance); + targetDefinition.callAfterDeserialize(targetObject, parentObject); } catch (BindingException ex) { - throw new IOException("an error occured calling the afterDeserialize() method", ex); + throw new IOException(ex); } - - JsonUtil.assertCurrent(parser, JsonToken.END_OBJECT); + return ObjectUtils.asType(targetObject); } - /** - * Reads a JSON/YAML object storing the associated data in the Java object - * {@code parentInstance}. - *

    - * When called the current {@link JsonToken} of the {@link JsonParser} is - * expected to be a {@link JsonToken#START_OBJECT}. - *

    - * After returning the current {@link JsonToken} of the {@link JsonParser} is - * expected to be the next token after the {@link JsonToken#END_OBJECT} for this - * class. - * - * @param targetDefinition - * the definition to parse - * @param targetObject - * the parent Java object to store the data in, which can be - * {@code null} if there is no parent - * @param requiresJsonKey - * when {@code true} indicates that the item will have a JSON key - * @return the instance - * @throws IOException - * if an error occurred while reading the parsed content - */ - @NonNull - protected Object readFieldDefinitionValue( - @NonNull IFieldClassBinding targetDefinition, + @SuppressFBWarnings(value = "UC_USELESS_CONDITION", justification = "false positive") + private void readDefinitionValueContents( + @NonNull IClassBinding targetDefinition, @NonNull Object targetObject, boolean requiresJsonKey) throws IOException { + boolean keyObjectWrapper = false; if (requiresJsonKey) { - // the start object has already been parsed, the next field name is the JSON key - JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME); - } else { - // JsonUtil.assertAndAdvance(jsonParser, JsonToken.START_OBJECT); - // This could be an empty assembly signified by a END_OBJECT, or a series of - // properties signified by - // a FIELD_NAME - JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); - } - - return readNormal(targetDefinition, targetObject, requiresJsonKey); - } - - @NonNull - private Object readNormal( - @NonNull IFieldClassBinding targetDefinition, - @NonNull Object parentInstance, - boolean requiresJsonKey) - throws IOException { - Predicate flagFilter = null; + readDefinitionJsonKey(targetDefinition, targetObject); - IBoundFlagInstance jsonKey = null; - if (requiresJsonKey) { - IBoundFlagInstance instance = targetDefinition.getJsonKeyFlagInstance(); - if (instance == null) { - throw new IOException("This property is configured to use a JSON key, but no JSON key was found"); + keyObjectWrapper = JsonToken.START_OBJECT.equals(parser.currentToken()); + if (keyObjectWrapper) { + JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT); } - - flagFilter = (flag) -> { - return !instance.equals(flag); - }; - jsonKey = instance; } - IBoundFlagInstance jsonValueKey = targetDefinition.getJsonValueKeyFlagInstance(); - if (jsonValueKey != null) { - if (flagFilter == null) { - flagFilter = (flag) -> { - return !jsonValueKey.equals(flag); - }; - } else { - flagFilter = flagFilter.and((flag) -> { - return !jsonValueKey.equals(flag); - }); - } + if (keyObjectWrapper || JsonToken.FIELD_NAME.equals(parser.currentToken())) { + Map properties = getInstancesToParse(targetDefinition, requiresJsonKey); + readDefinitionContents(targetDefinition, targetObject, properties); + } else if (parser.currentToken().isScalarValue()) { + // this is a value + IFieldClassBinding fieldDefinition = (IFieldClassBinding) targetDefinition; + Object fieldValue = fieldDefinition.getJavaTypeAdapter().parse(parser); + fieldDefinition.getFieldValueInstance().setValue(targetObject, fieldValue); } - Map properties = targetDefinition.getNamedInstances(flagFilter); - - try { - Object instance = targetDefinition.newInstance(); - - targetDefinition.callBeforeDeserialize(instance, parentInstance); - - if (jsonKey != null) { - // if there is a json key, the first field will be the key - String key = parser.currentName(); - assert key != null; - jsonKey.setValue(instance, jsonKey.getDefinition().getJavaTypeAdapter().parse(key)); - - // advance past the field name - if (properties.isEmpty()) { - // the value will be a standard value - // advance past the field name - JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME); - } else { - // the value will be a start object - JsonUtil.advanceAndAssert(parser, JsonToken.START_OBJECT); - // advance past the start object - parser.nextToken(); - } - } - - Set handledProperties = new HashSet<>(); - if (properties.isEmpty()) { - // this may be a value key value, an unrecognized flag, or the field value - IBoundFieldValueInstance fieldValue = targetDefinition.getFieldValueInstance(); - // parse the value - Object value = fieldValue.getJavaTypeAdapter().parse(parser); - fieldValue.setValue(instance, value); - handledProperties.add(fieldValue.getJsonValueKeyName()); - } else { - // This could be an empty assembly signified by a END_OBJECT, or a series of - // properties signified by - // a FIELD_NAME - JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); - - boolean parsedValueKey = false; - // now parse each property until the end object is reached - while (!parser.hasTokenId(JsonToken.END_OBJECT.id())) { - String propertyName = parser.getCurrentName(); - assert propertyName != null; - // JsonUtil.assertAndAdvance(jsonParser, JsonToken.FIELD_NAME); - - IBoundNamedInstance namedProperty = properties.get(propertyName); - - boolean handled = false; - if (namedProperty != null) { - // this is a recognized flag - - if (namedProperty.equals(jsonValueKey)) { - throw new IOException( - String.format("JSON value key configured, but found standard flag for the value key '%s'", - namedProperty.toCoordinates())); - } - - // Now parse - handled = readInstanceValues(namedProperty, instance); - } - - if (namedProperty == null && !parsedValueKey) { - // this may be a value key value, an unrecognized flag, or the field value - IBoundFieldValueInstance fieldValueInstance = targetDefinition.getFieldValueInstance(); - parsedValueKey = readFieldValueInstanceValue(fieldValueInstance, instance); - - if (parsedValueKey) { - handled = true; - } else { - if (getProblemHandler().canHandleUnknownProperty(targetDefinition, propertyName, parser)) { - handled = getProblemHandler().handleUnknownProperty(targetDefinition, propertyName, parser); - } - } - } - - if (handled) { - handledProperties.add(propertyName); - } else { - if (LOGGER.isWarnEnabled()) { - LOGGER.warn("Unrecognized property named '{}' at '{}'", propertyName, - JsonUtil.toString(ObjectUtils.notNull(parser.getCurrentLocation()))); - } - JsonUtil.skipNextValue(parser); - } - } - } - - // set undefined properties - for (Map.Entry entry : properties.entrySet()) { - if (!handledProperties.contains(entry.getKey())) { - IBoundNamedInstance property = ObjectUtils.notNull(entry.getValue()); - // use the default value of the collector - // REFACTOR: Implement problem handler - property.setValue(instance, property.defaultValue()); - } - - } - - if (jsonKey != null && !properties.isEmpty()) { - // read the END_OBJECT for the JSON key value - JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT); - } - - if (properties.isEmpty()) { - // this is the next field or the end of the containing object of this field - JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); - } else { - // this is the current end element, but we are not responsible for parsing it. - JsonUtil.assertCurrent(parser, JsonToken.END_OBJECT); - } - - targetDefinition.callAfterDeserialize(instance, parentInstance); - return instance; - } catch (BindingException ex) { - throw new IOException(ex); + if (keyObjectWrapper) { + // advance past the END_OBJECT for the JSON key + JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT); } } - /** - * Read JSON data associated with this property and apply it to the provided - * {@code objectInstance} on which this property exists. - *

    - * The parser's current token is expected to be the {@link JsonToken#FIELD_NAME} - * for the field value being parsed. - *

    - * After parsing the parser's current token will be the next token after the - * field's value. - * - * @param instance - * the instance to parse data for - * @param objectInstance - * an instance of the class on which this property exists - * @return {@code true} if the property was parsed, or {@code false} if the data - * did not contain information for this property - * @throws IOException - * if there was an error when reading JSON data - */ - public boolean readInstanceValues( - @NonNull IBoundNamedInstance instance, - @NonNull Object objectInstance) throws IOException { - // the parser's current token should be the JSON field name - JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME); - - String propertyName = parser.currentName(); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("reading property {}", propertyName); - } - - boolean handled = instance.getJsonName().equals(propertyName); - if (handled) { - Object value; - if (instance instanceof IBoundFlagInstance) { - value = readFlagInstanceValue((IBoundFlagInstance) instance); - } else if (instance instanceof IBoundNamedModelInstance) { - value = readModelInstanceValue((IBoundNamedModelInstance) instance, objectInstance); - } else { - throw new UnsupportedOperationException( - String.format("Unsupported instance type: %s", instance.getClass().getName())); - } - instance.setValue(objectInstance, value); + private void readDefinitionJsonKey( + @NonNull IClassBinding targetDefinition, + @NonNull Object targetObject) throws IOException { + IBoundFlagInstance jsonKey = targetDefinition.getJsonKeyFlagInstance(); + if (jsonKey == null) { + throw new IOException(String.format("JSON key not defined for object '%s'%s", + targetDefinition.toCoordinates(), JsonUtil.generateLocationMessage(parser))); } - JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); - return handled; - } + // the field will be the JSON key + String key = ObjectUtils.notNull(parser.getCurrentName()); - @NonNull - protected Object readFlagInstanceValue( - @NonNull IBoundFlagInstance instance) throws IOException { - // advance past the property name - parser.nextFieldName(); + Object value = jsonKey.getDefinition().getJavaTypeAdapter().parse(key); + jsonKey.setValue(targetObject, value.toString()); - // parse the value - return instance.getDefinition().getJavaTypeAdapter().parse(parser); + // advance past the FIELD_NAME + JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME); } - @Nullable - protected Object readModelInstanceValue( - @NonNull IBoundNamedModelInstance instance, - @NonNull Object parentInstance) throws IOException { - // the parser's current token should be the JSON field name - // advance past the property name - JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME); + @SuppressWarnings({ + "PMD.NullAssignment", // readability + "PMD.CyclomaticComplexity", // acceptable + "PMD.CognitiveComplexity" // acceptable + }) + private void readDefinitionContents( + @NonNull IClassBinding targetDefinition, + @NonNull Object targetObject, + @NonNull Map instances) throws IOException { - IModelPropertyInfo info = instance.getPropertyInfo(); - IPropertyCollector collector = info.newPropertyCollector(); + IBoundFlagInstance valueKeyFlag = null; + if (targetDefinition instanceof IFieldClassBinding) { + IFieldClassBinding targetFieldDefinition = (IFieldClassBinding) targetDefinition; + valueKeyFlag = targetFieldDefinition.getJsonValueKeyFlagInstance(); + } - // parse the value - info.readValue(collector, parentInstance, this); + while (!JsonToken.END_OBJECT.equals(parser.currentToken())) { - JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); + boolean handled = false; + String propertyName = parser.getCurrentName(); + assert propertyName != null; - return collector.getValue(); - } + if (JsonToken.FIELD_NAME.equals(parser.currentToken())) { + IBoundNamedInstance property = instances.get(propertyName); + if (property != null) { + handled = readInstance(property, targetObject); + instances.remove(propertyName); + } + } else { + throw new IOException( + String.format("Unexpected token: " + JsonUtil.toString(parser))); + } - /** - * Read JSON data associated with this property and apply it to the provided - * {@code objectInstance} on which this property exists. - *

    - * The parser's current token is expected to be the {@link JsonToken#FIELD_NAME} - * for the field value being parsed. - *

    - * After parsing the parser's current token will be the next token after the - * field's value. - * - * @param instance - * the instance to parse data for - * @param objectInstance - * an instance of the class on which this property exists - * @return {@code true} if the property was parsed, or {@code false} if the data - * did not contain information for this property - * @throws IOException - * if there was an error when reading JSON data - */ - public boolean readFieldValueInstanceValue( - @NonNull IBoundFieldValueInstance instance, - @NonNull Object objectInstance) throws IOException { - // the parser's current token should be the JSON field name - JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME); + if (!handled) { + // LOGGER.atInfo().log("Current token: " + JsonUtil.toString(parser)); + if (valueKeyFlag != null) { + // Handle JSON value key flag case + IFieldClassBinding targetFieldDefinition = (IFieldClassBinding) targetDefinition; + valueKeyFlag.setValue(targetObject, + valueKeyFlag.getDefinition().getJavaTypeAdapter().parse(propertyName)); - boolean handled; - IBoundFlagInstance jsonValueKey = instance.getParentClassBinding().getJsonValueKeyFlagInstance(); - if (jsonValueKey != null) { - // assume this is the JSON value key case - handled = true; - } else { - handled = instance.getJsonValueKeyName().equals(parser.currentName()); - } + // advance past the FIELD_NAME to get the value + JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME); - if (handled) { - // There are two modes: - // 1) use of a JSON value key, or - // 2) a simple value named "value" - if (jsonValueKey != null) { - // this is the JSON value key case - String fieldName = ObjectUtils.notNull(parser.currentName()); - jsonValueKey.setValue(objectInstance, jsonValueKey.getDefinition().getJavaTypeAdapter().parse(fieldName)); - } else { - String valueKeyName = instance.getJsonValueKeyName(); - String fieldName = parser.getCurrentName(); - if (!fieldName.equals(valueKeyName)) { - throw new IOException( - String.format("Expecteded to parse the value property named '%s', but found a property named '%s'.", - valueKeyName, fieldName)); + IBoundFieldValueInstance fieldValue = targetFieldDefinition.getFieldValueInstance(); + fieldValue.setValue( + targetObject, + fieldValue.getJavaTypeAdapter().parse(parser)); + valueKeyFlag = null; + } else if (!getProblemHandler().handleUnknownProperty( + targetDefinition, + targetObject, + propertyName, + this)) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Unrecognized property named '{}' at '{}'", propertyName, + JsonUtil.toString(ObjectUtils.notNull(parser.getCurrentLocation()))); + } + JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME); + JsonUtil.skipNextValue(parser); } } - // advance past the property name - parser.nextToken(); + } - // parse the value - Object retval = instance.getJavaTypeAdapter().parse(parser); - instance.setValue(objectInstance, retval); + if (!instances.isEmpty()) { + getProblemHandler().handleMissingInstances( + targetDefinition, + targetObject, + ObjectUtils.notNull(instances.values())); } - return handled; } - } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/DefaultXmlDeserializer.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/DefaultXmlDeserializer.java index ba403f4ee..56a9b6275 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/DefaultXmlDeserializer.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/DefaultXmlDeserializer.java @@ -59,14 +59,13 @@ public class DefaultXmlDeserializer private final RootAssemblyDefinition rootDefinition; /** - * Construct a new Metaschema binding-based deserializer that reads XML-based - * Metaschema content. + * Construct a new Metaschema binding-based deserializer that reads XML-based Metaschema content. * * @param bindingContext * the Metaschema data binding context * @param classBinding - * the assembly class binding describing the Java objects this - * deserializer parses data into + * the assembly class binding describing the Java objects this deserializer parses data + * into */ public DefaultXmlDeserializer(@NonNull IBindingContext bindingContext, @NonNull IAssemblyClassBinding classBinding) { super(bindingContext, classBinding); @@ -100,8 +99,7 @@ private XMLInputFactory2 getXMLInputFactory() { } /** - * Provide a XML input factory instance that will be used to create XML parser - * instances. + * Provide a XML input factory instance that will be used to create XML parser instances. * * @param factory * the factory instance @@ -128,8 +126,8 @@ protected final IDocumentNodeItem deserializeToNodeItemInternal(Reader reader, U @Override public final CLASS deserializeToValue(Reader reader, URI documentUri) throws IOException { // doesn't auto close the underlying reader - try (AutoCloser closer - = new AutoCloser<>(newXMLEventReader2(reader), event -> event.close())) { + try (AutoCloser closer = new AutoCloser<>( + newXMLEventReader2(reader), event -> event.close())) { return parseXmlInternal(closer.getResource()); } catch (XMLStreamException ex) { throw new IOException("Unable to create a new XMLEventReader2 instance.", ex); @@ -138,10 +136,16 @@ public final CLASS deserializeToValue(Reader reader, URI documentUri) throws IOE @NonNull private CLASS parseXmlInternal(@NonNull XMLEventReader2 reader) - throws IOException, XMLStreamException { + throws IOException { MetaschemaXmlParser parser = new MetaschemaXmlParser(reader, new DefaultXmlProblemHandler()); - return parser.read(rootDefinition); + try { + return parser.read(rootDefinition); + } catch (IOException | XMLStreamException | AssertionError ex) { + throw new IOException( + String.format("An unexpected error occured during parsing: %s", ex.getMessage()), + ex); + } } } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/DefaultXmlProblemHandler.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/DefaultXmlProblemHandler.java index aeb0504df..2a27e2228 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/DefaultXmlProblemHandler.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/DefaultXmlProblemHandler.java @@ -27,11 +27,10 @@ package gov.nist.secauto.metaschema.databind.io.xml; import gov.nist.secauto.metaschema.core.model.util.XmlEventUtil; -import gov.nist.secauto.metaschema.databind.io.BindingException; +import gov.nist.secauto.metaschema.databind.io.AbstractProblemHandler; import gov.nist.secauto.metaschema.databind.model.IAssemblyClassBinding; import gov.nist.secauto.metaschema.databind.model.IBoundFlagInstance; import gov.nist.secauto.metaschema.databind.model.IBoundModelDefinition; -import gov.nist.secauto.metaschema.databind.model.IBoundNamedInstance; import gov.nist.secauto.metaschema.databind.model.IBoundNamedModelInstance; import gov.nist.secauto.metaschema.databind.model.IClassBinding; @@ -44,20 +43,18 @@ import java.util.Set; import javax.xml.namespace.QName; -import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.StartElement; -import edu.umd.cs.findbugs.annotations.NonNull; - /** * Handles problems identified in the parsed XML. *

    - * The default problem handler will report unknown attributes, and provide empty - * collections for multi-valued model items and default values for flags and - * single valued fields. + * The default problem handler will report unknown attributes, and provide empty collections for + * multi-valued model items and default values for flags and single valued fields. */ -public class DefaultXmlProblemHandler implements IXmlProblemHandler { +public class DefaultXmlProblemHandler + extends AbstractProblemHandler + implements IXmlProblemHandler { private static final Logger LOGGER = LogManager.getLogger(DefaultXmlProblemHandler.class); private static final QName XSI_SCHEMA_LOCATION @@ -94,8 +91,10 @@ public boolean handleUnknownElement(IAssemblyClassBinding parentDefinition, Obje } @Override - public void handleMissingFlagInstances(IClassBinding classBinding, Object targetObject, - Collection unhandledFlags) throws IOException, XMLStreamException { + public void handleMissingFlagInstances( + IClassBinding classBinding, + Object targetObject, + Collection unhandledFlags) throws IOException { applyDefaults(targetObject, unhandledFlags); } @@ -103,23 +102,8 @@ public void handleMissingFlagInstances(IClassBinding classBinding, Object target public void handleMissingModelInstances( IAssemblyClassBinding classBinding, Object targetObject, - Collection unhandledInstances) throws IOException, XMLStreamException { + Collection unhandledInstances) throws IOException { applyDefaults(targetObject, unhandledInstances); } - private static void applyDefaults( - @NonNull Object targetObject, - @NonNull Collection unhandledInstances) throws IOException { - for (TYPE instance : unhandledInstances) { - Object value; - try { - value = instance.defaultValue(); - } catch (BindingException ex) { - throw new IOException(ex); - } - if (value != null) { - instance.setValue(targetObject, value); - } - } - } } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/IXmlParsingContext.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/IXmlParsingContext.java index 9b4fa2afc..e3b25007c 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/IXmlParsingContext.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/IXmlParsingContext.java @@ -46,8 +46,8 @@ public interface IXmlParsingContext extends IParsingContext { /** - * Read the XML data associated with the {@code targetInstance} and apply it to - * the provided {@code parentObject}. + * Read the XML data associated with the {@code targetInstance} and apply it to the provided + * {@code parentObject}. * * @param * the resulting object type @@ -70,27 +70,23 @@ T readModelInstanceValue( @NonNull StartElement start) throws XMLStreamException, IOException; /** - * Reads a XML element storing the associated data in a Java class instance, - * returning the resulting instance. + * Reads a XML element storing the associated data in a Java class instance, returning the resulting + * instance. *

    - * When called the next {@link XMLEvent} of the {@link XMLStreamReader2} is - * expected to be a {@link XMLStreamConstants#START_ELEMENT} that is the XML - * element associated with the Java class. + * When called the next {@link XMLEvent} of the {@link XMLStreamReader2} is expected to be a + * {@link XMLStreamConstants#START_ELEMENT} that is the XML element associated with the Java class. *

    - * After returning the next {@link XMLEvent} of the {@link XMLStreamReader2} is - * expected to be a the next event after the - * {@link XMLStreamConstants#END_ELEMENT} for the XML - * {@link XMLStreamConstants#START_ELEMENT} element associated with the Java - * class. + * After returning the next {@link XMLEvent} of the {@link XMLStreamReader2} is expected to be a the + * next event after the {@link XMLStreamConstants#END_ELEMENT} for the XML + * {@link XMLStreamConstants#START_ELEMENT} element associated with the Java class. * * @param * the resulting object type * @param targetDefinition - * the Metaschema definition that describes the syntax of the data to - * read + * the Metaschema definition that describes the syntax of the data to read * @param parentObject - * the Java object parent of the target object, which can be - * {@code null} if there is no parent + * the Java object parent of the target object, which can be {@code null} if there is no + * parent * @param start * the XML element start and attribute data previously parsed * @return the Java object containing the data parsed by this method diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/IXmlProblemHandler.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/IXmlProblemHandler.java index a17d9cf45..cfafe8a53 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/IXmlProblemHandler.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/IXmlProblemHandler.java @@ -36,10 +36,8 @@ import java.io.IOException; import java.util.Collection; -import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.StartElement; -import javax.xml.stream.events.XMLEvent; import edu.umd.cs.findbugs.annotations.NonNull; @@ -49,7 +47,7 @@ public interface IXmlProblemHandler extends IProblemHandler { * parsed. * * @param parentDefinition - * the bound assembly class on which the missing instances are found + * the bound class currently describing the data being parsed * @param targetObject * the Java object for the {@code parentDefinition} * @param attribute @@ -58,12 +56,14 @@ public interface IXmlProblemHandler extends IProblemHandler { * the XML parsing context used for parsing * @return {@code true} if the attribute was handled by this method, or * {@code false} otherwise + * @throws IOException + * if an error occurred while handling the unrecognized data */ boolean handleUnknownAttribute( @NonNull IBoundModelDefinition parentDefinition, @NonNull Object targetObject, @NonNull Attribute attribute, - @NonNull IXmlParsingContext parsingContext); + @NonNull IXmlParsingContext parsingContext) throws IOException; /** * Callback used to handle an element that is unknown to the model being parsed. @@ -78,12 +78,14 @@ boolean handleUnknownAttribute( * the XML parsing context used for parsing * @return {@code true} if the element was handled by this method, or * {@code false} otherwise + * @throws IOException + * if an error occurred while handling the unrecognized data */ boolean handleUnknownElement( @NonNull IAssemblyClassBinding parentDefinition, @NonNull Object targetObject, @NonNull StartElement start, - @NonNull IXmlParsingContext parsingContext); + @NonNull IXmlParsingContext parsingContext) throws IOException; /** * A callback used to handle bound flag instances for which no data was found @@ -99,15 +101,13 @@ boolean handleUnknownElement( * @param unhandledFlags * the set of instances that had no data to parse * @throws IOException - * if there was an error when reading XML data - * @throws XMLStreamException - * if there was an error generating an {@link XMLEvent} from the XML + * if an error occurred while handling the missing instances */ void handleMissingFlagInstances( @NonNull IClassBinding parentDefinition, @NonNull Object targetObject, @NonNull Collection unhandledFlags) - throws IOException, XMLStreamException; + throws IOException; /** * A callback used to handle bound model instances for which no data was found @@ -123,13 +123,11 @@ void handleMissingFlagInstances( * @param unhandledInstances * the set of instances that had no data to parse * @throws IOException - * if there was an error when reading XML data - * @throws XMLStreamException - * if there was an error generating an {@link XMLEvent} from the XML + * if an error occurred while handling the missing instances */ void handleMissingModelInstances( @NonNull IAssemblyClassBinding parentDefinition, @NonNull Object targetObject, @NonNull Collection unhandledInstances) - throws IOException, XMLStreamException; + throws IOException; } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/MetaschemaXmlParser.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/MetaschemaXmlParser.java index c206004c7..5ac2ad718 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/MetaschemaXmlParser.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/MetaschemaXmlParser.java @@ -69,7 +69,8 @@ public class MetaschemaXmlParser private final IXmlProblemHandler problemHandler; /** - * Construct a new Metaschema-aware parser using the default problem handler. + * Construct a new Metaschema-aware XML parser using the default problem + * handler. * * @param reader * the XML reader to parse with @@ -345,7 +346,7 @@ protected boolean isNextInstance( } /** - * Read the XML data associated with the {@code instance} and apply it to the + * Read the data associated with the {@code instance} and apply it to the * provided {@code parentObject}. * * @param instance @@ -361,6 +362,7 @@ protected boolean isNextInstance( * @throws XMLStreamException * if an error occurred while parsing XML events */ + // REFACTOR: rename to readModelInstance protected boolean readModelInstanceValues( @NonNull IBoundNamedModelInstance instance, @NonNull Object parentObject, @@ -498,7 +500,10 @@ protected Object readModelInstanceValue( reader.nextEvent(); currentStart = ObjectUtils.notNull(event.asStartElement()); } else { - throw new IOException(String.format("Did not find expected element '%s'.", xmlQName)); + throw new IOException(String.format("Found '%s' instead of expected element '%s'%s.", + event.asStartElement().getName(), + xmlQName, + XmlEventUtil.generateLocationMessage(event))); } } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/yaml/DefaultYamlDeserializer.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/yaml/DefaultYamlDeserializer.java index c798c5e5a..69360c9f1 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/yaml/DefaultYamlDeserializer.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/yaml/DefaultYamlDeserializer.java @@ -37,6 +37,15 @@ public class DefaultYamlDeserializer extends DefaultJsonDeserializer { + /** + * Construct a new YAML deserializer that will parse the bound class identified by the + * {@code classBinding}. + * + * @param bindingContext + * the binding context used to supply bound Java classes while writing + * @param classBinding + * the bound class information for the Java type this deserializer is operating on + */ public DefaultYamlDeserializer(@NonNull IBindingContext bindingContext, @NonNull IAssemblyClassBinding classBinding) { super(bindingContext, classBinding); } @@ -46,8 +55,15 @@ public DefaultYamlDeserializer(@NonNull IBindingContext bindingContext, @NonNull // return Format.YAML; // } + /** + * {@inheritDoc} + *

    + * This method provides a YAML version of the JSON factory. + * + * @return the factory + */ @Override - protected YAMLFactory getJsonFactoryInstance() { + protected YAMLFactory newJsonFactoryInstance() { return YamlFactoryFactory.newParserFactoryInstance(getConfiguration()); } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/yaml/YamlFactoryFactory.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/yaml/YamlFactoryFactory.java index e5fd467b5..87a2a486d 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/yaml/YamlFactoryFactory.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/yaml/YamlFactoryFactory.java @@ -45,8 +45,17 @@ private YamlFactoryFactory() { // disable construction } + /** + * Create a new {@link YAMLFactory} configured to parse YAML. + * + * @param config + * the deserialization configuration + * + * @return the factory + */ @NonNull - public static YAMLFactory newParserFactoryInstance(@NonNull IMutableConfiguration> config) { + public static YAMLFactory newParserFactoryInstance( + @NonNull IMutableConfiguration> config) { YAMLFactoryBuilder builder = YAMLFactory.builder(); LoaderOptions loaderOptions = builder.loaderOptions(); if (loaderOptions == null) { @@ -62,10 +71,17 @@ public static YAMLFactory newParserFactoryInstance(@NonNull IMutableConfiguratio return retval; } - @SuppressWarnings("unused") + /** + * Create a new {@link YAMLFactory} configured to generate YAML. + * + * @param config + * the serialization configuration + * + * @return the factory + */ @NonNull - public static YAMLFactory - newGeneratorFactoryInstance(@NonNull IMutableConfiguration> config) { + public static YAMLFactory newGeneratorFactoryInstance( + @NonNull IMutableConfiguration> config) { YAMLFactoryBuilder builder = YAMLFactory.builder(); YAMLFactory retval = ObjectUtils.notNull(builder .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/AbstractClassBinding.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/AbstractClassBinding.java index 1d8263d0b..7c4b5fd41 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/AbstractClassBinding.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/AbstractClassBinding.java @@ -59,7 +59,8 @@ import edu.umd.cs.findbugs.annotations.Nullable; abstract class AbstractClassBinding implements IClassBinding { - // private static final Logger logger = LogManager.getLogger(AbstractClassBinding.class); + // private static final Logger logger = + // LogManager.getLogger(AbstractClassBinding.class); @NonNull private final IBindingContext bindingContext; @@ -240,6 +241,7 @@ public IBoundFlagInstance getJsonKeyFlagInstance() { return jsonKeyFlag; } + // REFACTOR: remove @Override public Map getNamedInstances(Predicate filter) { Map retval; diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/AbstractNamedModelProperty.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/AbstractNamedModelProperty.java index ca38944f4..6cc7c9a2d 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/AbstractNamedModelProperty.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/AbstractNamedModelProperty.java @@ -83,8 +83,8 @@ public XmlGroupAsBehavior getXmlGroupAsBehavior() { private final Lazy propertyInfo; /** - * Construct a new bound model instance based on a Java property. The name of - * the property is bound to the name of the instance. + * Construct a new bound model instance based on a Java property. The name of the property is bound + * to the name of the instance. * * @param field * the field instance associated with this property diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/ClassBindingFieldProperty.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/ClassBindingFieldProperty.java index 25e421338..23f6997aa 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/ClassBindingFieldProperty.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/ClassBindingFieldProperty.java @@ -39,8 +39,8 @@ class ClassBindingFieldProperty private final IFieldClassBinding definition; /** - * Construct a new bound flag instance based on a Java property. The name of the - * property is bound to the name of the instance. + * Construct a new bound flag instance based on a Java property. The name of the property is bound + * to the name of the instance. * * @param field * the Java field to bind to diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/DefaultFieldValueProperty.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/DefaultFieldValueProperty.java index c26a3f400..c736b60ca 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/DefaultFieldValueProperty.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/DefaultFieldValueProperty.java @@ -61,6 +61,7 @@ class DefaultFieldValueProperty @Nullable private final Object defaultValue; + @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") public DefaultFieldValueProperty( @NonNull IFieldClassBinding fieldClassBinding, @NonNull Field field) { @@ -131,6 +132,11 @@ public MarkupMultiline getRemarks() { return null; } + @Override + public String getName() { + return getJsonValueKeyName(); + } + @Override public String getUseName() { // TODO: implement? diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/DefaultFlagProperty.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/DefaultFlagProperty.java index e55fa1bd7..98d0631b3 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/DefaultFlagProperty.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/DefaultFlagProperty.java @@ -69,8 +69,8 @@ class DefaultFlagProperty private InternalFlagDefinition definition; /** - * Construct a new bound flag instance based on a Java property. The name of the - * property is bound to the name of the instance. + * Construct a new bound flag instance based on a Java property. The name of the property is bound + * to the name of the instance. * * @param field * the Java field to bind to diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IBoundFieldValueInstance.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IBoundFieldValueInstance.java index a135b11ff..4d88fb17b 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IBoundFieldValueInstance.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IBoundFieldValueInstance.java @@ -48,6 +48,11 @@ public interface IBoundFieldValueInstance extends IBoundNamedInstance { @NonNull String getJsonValueKeyName(); + @Override + default String getJsonName() { + return getJsonValueKeyName(); + } + void writeValue(Object value, @NonNull IJsonWritingContext context) throws IOException; @Nullable diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IBoundNamedInstance.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IBoundNamedInstance.java index a5fb67431..37ec15f4a 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IBoundNamedInstance.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IBoundNamedInstance.java @@ -60,8 +60,7 @@ default String getName() { } /** - * Get the {@link IClassBinding} for the Java class within which this property - * exists. + * Get the {@link IClassBinding} for the Java class within which this property exists. * * @return the containing class's binding */ @@ -84,8 +83,8 @@ default String getJavaFieldName() { /** * Get the actual Java type of the underlying bound object. *

    - * This may be the same as the what is returned by {@link #getItemType()}, or - * may be a Java collection class. + * This may be the same as the what is returned by {@link #getItemType()}, or may be a Java + * collection class. * * @return the raw type of the bound object */ @@ -96,8 +95,8 @@ default Type getType() { } /** - * Get the item type of the bound object. An item type is the primitive or - * specialized type that represents that data associated with this binding. + * Get the item type of the bound object. An item type is the primitive or specialized type that + * represents that data associated with this binding. * * @return the item type of the bound object */ @@ -107,9 +106,8 @@ default Class getItemType() { } /** - * Get the current value from the provided {@code parentInstance} object. The - * provided object must be of the type associated with the definition containing - * this property. + * Get the current value from the provided {@code parentInstance} object. The provided object must + * be of the type associated with the definition containing this property. * * @param parentInstance * the object associated with the definition containing this property @@ -136,14 +134,14 @@ default Object getValue(@NonNull Object parentInstance) { } /** - * Set the provided value on the provided object. The provided object must be of - * the item's type associated with this property. + * Set the provided value on the provided object. The provided object must be of the item's type + * associated with this property. * * @param parentInstance * the object * @param value - * a value, which may be a simple {@link Type} or a - * {@link ParameterizedType} for a collection + * a value, which may be a simple {@link Type} or a {@link ParameterizedType} for a + * collection */ default void setValue(@NonNull Object parentInstance, Object value) { Field field = getField(); diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IBoundNamedModelInstance.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IBoundNamedModelInstance.java index 508ef18f2..6aa1ab280 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IBoundNamedModelInstance.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IBoundNamedModelInstance.java @@ -47,8 +47,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; /** - * This marker interface provides common methods for interacting with bound - * object values. + * This marker interface provides common methods for interacting with bound object values. */ public interface IBoundNamedModelInstance extends IBoundNamedInstance, INamedModelInstance { diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IClassBinding.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IClassBinding.java index d35d5f95f..5a9ba021f 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IClassBinding.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/IClassBinding.java @@ -77,6 +77,7 @@ void callAfterDeserialize( * a filter to apply or {@code null} if no filtering is needed * @return a collection of properties */ + // REFACTOR: remove @NonNull Map getNamedInstances(@Nullable Predicate flagFilter); @@ -89,7 +90,8 @@ default void writeItem(@NonNull Object item, boolean writeObjectWrapper, @NonNul writeItems(CollectionUtil.singleton(item), writeObjectWrapper, context); } - // for JSON, the entire value needs to be processed to deal with collapsable fields + // for JSON, the entire value needs to be processed to deal with collapsable + // fields void writeItems(@NonNull Collection items, boolean writeObjectWrapper, @NonNull IJsonWritingContext context) throws IOException; diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/JavaTypeAdapterDataTypeHandler.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/JavaTypeAdapterDataTypeHandler.java index b1de418d5..f96206511 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/JavaTypeAdapterDataTypeHandler.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/JavaTypeAdapterDataTypeHandler.java @@ -95,6 +95,7 @@ public void accept(Object item, QName currentParentName, IXmlWritingContext cont getJavaTypeAdapter().writeXmlValue(item, currentParentName, context.getWriter()); } + @SuppressWarnings("resource") // resource not owned @Override public void writeItems( Collection items, diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/ListPropertyInfo.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/ListPropertyInfo.java index 696923764..8f9e56066 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/ListPropertyInfo.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/ListPropertyInfo.java @@ -125,55 +125,42 @@ public boolean readValue( return handled; } - @SuppressWarnings("resource") // not owned + @SuppressWarnings({ + "resource", // not owned + "PMD.ImplicitSwitchFallThrough" // false positive + }) + @Override - public void readValue(IPropertyCollector collector, Object parentInstance, IJsonParsingContext context) + public void readValue( + IPropertyCollector collector, + Object parentInstance, + IJsonParsingContext context) throws IOException { - JsonParser parser = context.getReader(); // NOPMD - intentional + JsonParser parser = context.getReader(); - if (JsonGroupAsBehavior.SINGLETON_OR_LIST.equals(getProperty().getJsonGroupAsBehavior()) - && !JsonToken.START_ARRAY.equals(parser.currentToken())) { - // boolean isObject = JsonToken.START_OBJECT.equals(parser.currentToken()); - // - // if (isObject) { - // // read the object's START_OBJECT - // JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT); - // } - - // this is a singleton, just parse the value as a single item - Object value = getDataTypeHandler().get(parentInstance, false, context); - collector.add(value); - - // if (isObject) { - // // read the object's END_OBJECT - // JsonUtil.assertAndAdvance(context.getReader(), JsonToken.END_OBJECT); - // } - } else if (JsonToken.VALUE_NULL.equals(parser.currentToken())) { - JsonUtil.assertAndAdvance(parser, JsonToken.VALUE_NULL); - } else { + switch (parser.currentToken()) { + case START_ARRAY: { // this is an array, we need to parse the array wrapper then each item JsonUtil.assertAndAdvance(parser, JsonToken.START_ARRAY); // parse items while (!JsonToken.END_ARRAY.equals(parser.currentToken())) { - // - // boolean isObject = JsonToken.START_OBJECT.equals(parser.currentToken()); - // if (isObject) { - // // read the object's START_OBJECT - // JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT); - // } - Object value = getDataTypeHandler().get(parentInstance, false, context); collector.add(value); - - // if (isObject) { - // // read the object's END_OBJECT - // JsonUtil.assertAndAdvance(context.getReader(), JsonToken.END_OBJECT); - // } } // this is the other side of the array wrapper, advance past it JsonUtil.assertAndAdvance(parser, JsonToken.END_ARRAY); + break; + } + case VALUE_NULL: { + JsonUtil.assertAndAdvance(parser, JsonToken.VALUE_NULL); + break; + } + default: + // this is a singleton, just parse the value as a single item + Object value = getDataTypeHandler().get(parentInstance, false, context); + collector.add(value); } } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/SimpleFieldProperty.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/SimpleFieldProperty.java index f5d4bec07..39b0875bf 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/SimpleFieldProperty.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/SimpleFieldProperty.java @@ -57,8 +57,8 @@ class SimpleFieldProperty private final Lazy definition; /** - * Construct a new bound flag instance based on a Java property. The name of the - * property is bound to the name of the instance. + * Construct a new bound flag instance based on a Java property. The name of the property is bound + * to the name of the instance. * * @param field * the Java field to bind to diff --git a/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultAssemblyClassBindingTest.java b/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultAssemblyClassBindingTest.java index 2bd9b4f36..9f04414de 100644 --- a/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultAssemblyClassBindingTest.java +++ b/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultAssemblyClassBindingTest.java @@ -69,7 +69,8 @@ void testMetaschema() { } // @Test - // void testSimpleJson() throws JsonParseException, IOException, BindingException { + // void testSimpleJson() throws JsonParseException, IOException, + // BindingException { // File testContent // = new // File(getClass().getClassLoader().getResource("test-content/bound-class-simple.json").getFile()); @@ -89,7 +90,8 @@ void testMetaschema() { // } // // @Test - // void testSimpleXml() throws BindingException, XMLStreamException, IOException { + // void testSimpleXml() throws BindingException, XMLStreamException, IOException + // { // File testContent // = new // File(getClass().getClassLoader().getResource("test-content/bound-class-simple.xml").getFile()); @@ -145,7 +147,8 @@ void testMetaschema() { // } // // @Test - // void testComplexXml() throws BindingException, XMLStreamException, IOException { + // void testComplexXml() throws BindingException, XMLStreamException, + // IOException { // File testContent // = new // File(getClass().getClassLoader().getResource("test-content/bound-class-complex.xml").getFile()); diff --git a/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultFieldPropertyTest.java b/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultFieldPropertyTest.java index d73851695..66ccdf509 100644 --- a/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultFieldPropertyTest.java +++ b/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultFieldPropertyTest.java @@ -28,8 +28,8 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.core.JsonFactory; @@ -37,25 +37,21 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; -import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider; -import gov.nist.secauto.metaschema.core.datatype.adapter.StringAdapter; import gov.nist.secauto.metaschema.core.model.IMetaschema; -import gov.nist.secauto.metaschema.core.util.ObjectUtils; import gov.nist.secauto.metaschema.databind.IBindingContext; +import gov.nist.secauto.metaschema.databind.io.json.JsonUtil; import gov.nist.secauto.metaschema.databind.io.json.MetaschemaJsonParser; import gov.nist.secauto.metaschema.databind.model.test.MultiFieldAssembly; +import gov.nist.secauto.metaschema.databind.model.test.SimpleAssembly; -import org.jmock.Expectations; import org.jmock.auto.Mock; import org.jmock.junit5.JUnit5Mockery; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import java.io.IOException; -import java.net.URI; import java.util.Collections; import java.util.LinkedList; -import java.util.List; class DefaultFieldPropertyTest { @RegisterExtension @@ -69,61 +65,51 @@ class DefaultFieldPropertyTest { private IBindingContext bindingContext; // NOPMD - it's injected @Test - void testJsonRead() - throws JsonParseException, IOException, NoSuchFieldException { - - String json = "{ \"field1\": \"field1value\", \"fields2\": [ \"field2value\" ] } }"; + void testJsonReadFlag() + throws JsonParseException, IOException { + String json = "{ \"test\": { \"id\": \"theId\", \"number\": 1 } }"; JsonFactory factory = new JsonFactory(); try (JsonParser jsonParser = factory.createParser(json)) { assert jsonParser != null; - Class theClass = MultiFieldAssembly.class; - - context.checking(new Expectations() { - { // NOPMD - intentional - allowing(bindingContext).getJavaTypeAdapterInstance(StringAdapter.class); - will(returnValue(MetaschemaDataTypeProvider.STRING)); - allowing(bindingContext).getClassBinding(String.class); - will(returnValue(null)); - - allowing(classBinding).getBoundClass(); - will(returnValue(theClass)); - allowing(classBinding).getName(); - will(returnValue(null)); - allowing(classBinding).getBindingContext(); - will(returnValue(bindingContext)); - allowing(classBinding).getContainingMetaschema(); - will(returnValue(metaschema)); - - allowing(metaschema).getLocation(); - will(returnValue(URI.create("relativeLocation"))); - } - }); - - java.lang.reflect.Field field1 = ObjectUtils.requireNonNull(theClass.getDeclaredField("field1")); - IBoundFieldInstance field1Property = IBoundFieldInstance.newInstance( - field1, - ObjectUtils.notNull(classBinding)); - - java.lang.reflect.Field field2 = ObjectUtils.requireNonNull(theClass.getDeclaredField("_field2")); - IBoundFieldInstance field2Property = IBoundFieldInstance.newInstance( - field2, - ObjectUtils.notNull(classBinding)); - - MultiFieldAssembly obj = new MultiFieldAssembly(); + IBindingContext bindingContext = IBindingContext.instance(); + IAssemblyClassBinding classBinding = (IAssemblyClassBinding) bindingContext.getClassBinding(SimpleAssembly.class); + assert classBinding != null; + IRootAssemblyClassBinding root = new RootAssemblyDefinition(classBinding); MetaschemaJsonParser parser = new MetaschemaJsonParser(jsonParser); + SimpleAssembly obj = parser.read(root); + assert obj != null; + assertAll( - () -> assertEquals(JsonToken.START_OBJECT, jsonParser.nextToken()), - () -> assertEquals("field1", jsonParser.nextFieldName()), - () -> assertTrue(parser.readInstanceValues(field1Property, obj)), - () -> assertEquals("field1value", obj.getField1()), + () -> assertEquals("theId", obj.getId())); + } + } + + @Test + void testJsonReadField() + throws JsonParseException, IOException { + + String json = "{ \"field1\": \"field1value\", \"fields2\": [ \"field2value\" ] }"; + JsonFactory factory = new JsonFactory(); + try (JsonParser jsonParser = factory.createParser(json)) { + assert jsonParser != null; + // get first token + jsonParser.nextToken(); - () -> assertEquals(JsonToken.FIELD_NAME, jsonParser.currentToken()), - () -> assertEquals("fields2", jsonParser.currentName()), + IBindingContext bindingContext = IBindingContext.instance(); + IClassBinding classBinding = bindingContext.getClassBinding(MultiFieldAssembly.class); + assert classBinding != null; - () -> assertTrue(parser.readInstanceValues(field2Property, obj)), + MetaschemaJsonParser parser = new MetaschemaJsonParser(jsonParser); + + JsonUtil.assertAndAdvance(jsonParser, JsonToken.START_OBJECT); + MultiFieldAssembly obj = parser.readDefinitionValue(classBinding, null, false); + JsonUtil.assertAndAdvance(jsonParser, JsonToken.END_OBJECT); + + assertAll( + () -> assertEquals("field1value", obj.getField1()), () -> assertTrue(obj.getField2() instanceof LinkedList), () -> assertIterableEquals(Collections.singleton("field2value"), obj.getField2())); @@ -135,66 +121,81 @@ void testJsonRead() @Test void testJsonReadMissingFieldValue() - throws JsonParseException, IOException, NoSuchFieldException { - String json = "{ \"test\":\n" + " { \"fields2\": [\n" + " \"field2value\"\n" + " ]\n" + " }\n" + "}\n"; + throws JsonParseException, IOException { + String json = "{ \"fields2\": [\n" + + " \"field2value\"\n" + + " ]\n" + + "}\n"; JsonFactory factory = new JsonFactory(); try (JsonParser jsonParser = factory.createParser(json)) { assert jsonParser != null; + // get first token + jsonParser.nextToken(); - Class theClass = MultiFieldAssembly.class; - - context.checking(new Expectations() { - { // NOPMD - intentional - allowing(bindingContext).getJavaTypeAdapterInstance(StringAdapter.class); - will(returnValue(MetaschemaDataTypeProvider.STRING)); - allowing(bindingContext).getClassBinding(String.class); - will(returnValue(null)); - - allowing(classBinding).getBoundClass(); - will(returnValue(theClass)); - allowing(classBinding).getName(); - will(returnValue(null)); - allowing(classBinding).getBindingContext(); - will(returnValue(bindingContext)); - allowing(classBinding).getContainingMetaschema(); - will(returnValue(metaschema)); - - allowing(metaschema).getLocation(); - will(returnValue(URI.create("relativeLocation"))); - } - }); - - java.lang.reflect.Field field1 = theClass.getDeclaredField("field1"); - IBoundFieldInstance field1Property = IBoundFieldInstance.newInstance( - ObjectUtils.notNull(field1), - ObjectUtils.notNull(classBinding)); - java.lang.reflect.Field field2 = theClass.getDeclaredField("_field2"); - IBoundFieldInstance field2Property = IBoundFieldInstance.newInstance( - ObjectUtils.notNull(field2), - ObjectUtils.notNull(classBinding)); - - MultiFieldAssembly obj = new MultiFieldAssembly(); + IBindingContext bindingContext = IBindingContext.instance(); + IClassBinding classBinding = bindingContext.getClassBinding(MultiFieldAssembly.class); + assert classBinding != null; MetaschemaJsonParser parser = new MetaschemaJsonParser(jsonParser); - // Advance to first property to parse - assertEquals(JsonToken.START_OBJECT, jsonParser.nextToken()); - assertEquals("test", jsonParser.nextFieldName()); - assertEquals(JsonToken.START_OBJECT, jsonParser.nextToken()); - assertEquals("fields2", jsonParser.nextFieldName()); + JsonUtil.assertAndAdvance(jsonParser, JsonToken.START_OBJECT); + MultiFieldAssembly obj = parser.readDefinitionValue(classBinding, null, false); + JsonUtil.assertAndAdvance(jsonParser, JsonToken.END_OBJECT); + + assertAll( + () -> assertNull(obj.getField1()), + () -> assertTrue(obj.getField2() instanceof LinkedList), + () -> assertIterableEquals(Collections.singleton("field2value"), obj.getField2())); + } + } + + @Test + void testJsonReadFieldValueKey() + throws JsonParseException, IOException { + String json = "{ \"field-value-key\": { \"a-value\": \"theValue\" } }"; + JsonFactory factory = new JsonFactory(); + try (JsonParser jsonParser = factory.createParser(json)) { + assert jsonParser != null; + // get first token + jsonParser.nextToken(); - // attempt to parse the missing property - assertFalse(parser.readInstanceValues(field1Property, obj)); - assertEquals(null, obj.getField1()); + IBindingContext bindingContext = IBindingContext.instance(); + IClassBinding classBinding = bindingContext.getClassBinding(MultiFieldAssembly.class); + assert classBinding != null; - // attempt to parse the existing property - assertEquals(JsonToken.FIELD_NAME, jsonParser.currentToken()); - assertEquals("fields2", jsonParser.currentName()); + MetaschemaJsonParser parser = new MetaschemaJsonParser(jsonParser); + + JsonUtil.assertAndAdvance(jsonParser, JsonToken.START_OBJECT); + MultiFieldAssembly obj = parser.readDefinitionValue(classBinding, null, false); + JsonUtil.assertAndAdvance(jsonParser, JsonToken.END_OBJECT); - assertTrue(parser.readInstanceValues(field2Property, obj)); - assertTrue(obj.getField2() instanceof LinkedList); - assertEquals(List.of("field2value"), obj.getField2()); + assertAll( + () -> assertEquals("theValue", obj.getField3().getValue())); } } + @Test + void testJsonReadFieldDefaultValueKey() + throws JsonParseException, IOException { + String json = "{ \"field-default-value-key\": { \"STRVALUE\": \"theValue\" } }"; + JsonFactory factory = new JsonFactory(); + try (JsonParser jsonParser = factory.createParser(json)) { + assert jsonParser != null; + // get first token + jsonParser.nextToken(); + + IBindingContext bindingContext = IBindingContext.instance(); + IClassBinding classBinding = bindingContext.getClassBinding(MultiFieldAssembly.class); + assert classBinding != null; + + MetaschemaJsonParser parser = new MetaschemaJsonParser(jsonParser); + + JsonUtil.assertAndAdvance(jsonParser, JsonToken.START_OBJECT); + MultiFieldAssembly obj = parser.readDefinitionValue(classBinding, null, false); + JsonUtil.assertAndAdvance(jsonParser, JsonToken.END_OBJECT); + + assertAll( + () -> assertEquals("theValue", obj.getField4().getValue())); + } + } } diff --git a/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultFieldValuePropertyTest.java b/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultFieldValuePropertyTest.java deleted file mode 100644 index 0020c5003..000000000 --- a/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultFieldValuePropertyTest.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Portions of this software was developed by employees of the National Institute - * of Standards and Technology (NIST), an agency of the Federal Government and is - * being made available as a public service. Pursuant to title 17 United States - * Code Section 105, works of NIST employees are not subject to copyright - * protection in the United States. This software may be subject to foreign - * copyright. Permission in the United States and in foreign countries, to the - * extent that NIST may hold copyright, to use, copy, modify, create derivative - * works, and distribute this software and its documentation without fee is hereby - * granted on a non-exclusive basis, provided that this notice and disclaimer - * of warranty appears in all copies. - * - * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER - * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY - * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM - * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE - * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT - * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, - * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, - * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, - * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR - * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT - * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. - */ - -package gov.nist.secauto.metaschema.databind.model; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; - -import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider; -import gov.nist.secauto.metaschema.core.datatype.adapter.StringAdapter; -import gov.nist.secauto.metaschema.core.util.ObjectUtils; -import gov.nist.secauto.metaschema.databind.IBindingContext; -import gov.nist.secauto.metaschema.databind.io.json.MetaschemaJsonParser; -import gov.nist.secauto.metaschema.databind.model.test.DefaultValueKeyField; -import gov.nist.secauto.metaschema.databind.model.test.ValueKeyField; - -import org.jmock.Expectations; -import org.jmock.junit5.JUnit5Mockery; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import java.io.IOException; -import java.lang.reflect.Field; - -class DefaultFieldValuePropertyTest { - @RegisterExtension - JUnit5Mockery context = new JUnit5Mockery(); - - private final IFieldClassBinding classBinding = context.mock(IFieldClassBinding.class); - private final IBindingContext bindingContext = context.mock(IBindingContext.class); - - @Test - void testJsonRead() - throws JsonParseException, IOException, NoSuchFieldException { - String json = "{ \"a-value\": \"theValue\" }"; - JsonFactory factory = new JsonFactory(); - try (JsonParser jsonParser = factory.createParser(json)) { - assert jsonParser != null; - - Class theClass = ValueKeyField.class; - - Field field = theClass.getDeclaredField("_value"); - - context.checking(new Expectations() { - { // NOPMD - intentional - atMost(1).of(bindingContext).getJavaTypeAdapterInstance(StringAdapter.class); - will(returnValue(MetaschemaDataTypeProvider.STRING)); - - allowing(classBinding).getBoundClass(); - will(returnValue(theClass)); - allowing(classBinding).getBoundClass(); - will(returnValue(theClass)); - allowing(classBinding).getBindingContext(); - will(returnValue(bindingContext)); - allowing(classBinding).getJsonValueKeyFlagInstance(); - will(returnValue(null)); - } - }); - - DefaultFieldValueProperty idProperty = new DefaultFieldValueProperty( - ObjectUtils.notNull(classBinding), - ObjectUtils.notNull(field)); - - assertEquals(JsonToken.START_OBJECT, jsonParser.nextToken()); - assertEquals("a-value", jsonParser.nextFieldName()); - // assertEquals(JsonToken.START_OBJECT, jsonParser.nextToken()); - // assertEquals(JsonToken.FIELD_NAME, jsonParser.nextToken()); - // assertEquals("id", jsonParser.currentName()); - - ValueKeyField obj = new ValueKeyField(); - - assertTrue(new MetaschemaJsonParser(jsonParser).readFieldValueInstanceValue(idProperty, obj)); - - assertEquals("theValue", obj.getValue()); - } - } - - // TODO: check if the default name is actually tested - @Test - void testJsonDefaultNameRead() throws JsonParseException, IOException, NoSuchFieldException { - String json = "{ \"STRVALUE\": \"theValue\" }"; - JsonFactory factory = new JsonFactory(); - try (JsonParser jsonParser = factory.createParser(json)) { - assert jsonParser != null; - - Class theClass = DefaultValueKeyField.class; - - Field field = theClass.getDeclaredField("_value"); - - context.checking(new Expectations() { - { // NOPMD - intentional - atMost(1).of(bindingContext).getJavaTypeAdapterInstance(StringAdapter.class); - will(returnValue(MetaschemaDataTypeProvider.STRING)); - - allowing(classBinding).getBoundClass(); - will(returnValue(theClass)); - allowing(classBinding).getBoundClass(); - will(returnValue(theClass)); - allowing(classBinding).getJsonValueKeyFlagInstance(); - will(returnValue(null)); - allowing(classBinding).getBindingContext(); - will(returnValue(bindingContext)); - } - }); - - DefaultFieldValueProperty idProperty = new DefaultFieldValueProperty( - ObjectUtils.notNull(classBinding), - ObjectUtils.notNull(field)); - - assertEquals(JsonToken.START_OBJECT, jsonParser.nextToken()); - assertEquals("STRVALUE", jsonParser.nextFieldName()); - // assertEquals(JsonToken.START_OBJECT, jsonParser.nextToken()); - // assertEquals(JsonToken.FIELD_NAME, jsonParser.nextToken()); - // assertEquals("id", jsonParser.currentName()); - - DefaultValueKeyField obj = new DefaultValueKeyField(); - - assertTrue(new MetaschemaJsonParser(jsonParser).readFieldValueInstanceValue(idProperty, obj)); - - assertEquals("theValue", obj.getValue()); - } - } - -} diff --git a/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultFlagPropertyTest.java b/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultFlagPropertyTest.java deleted file mode 100644 index 080e3c2de..000000000 --- a/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/DefaultFlagPropertyTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Portions of this software was developed by employees of the National Institute - * of Standards and Technology (NIST), an agency of the Federal Government and is - * being made available as a public service. Pursuant to title 17 United States - * Code Section 105, works of NIST employees are not subject to copyright - * protection in the United States. This software may be subject to foreign - * copyright. Permission in the United States and in foreign countries, to the - * extent that NIST may hold copyright, to use, copy, modify, create derivative - * works, and distribute this software and its documentation without fee is hereby - * granted on a non-exclusive basis, provided that this notice and disclaimer - * of warranty appears in all copies. - * - * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER - * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY - * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM - * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE - * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT - * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, - * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, - * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, - * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR - * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT - * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. - */ - -package gov.nist.secauto.metaschema.databind.model; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; - -import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider; -import gov.nist.secauto.metaschema.core.datatype.adapter.StringAdapter; -import gov.nist.secauto.metaschema.core.util.ObjectUtils; -import gov.nist.secauto.metaschema.databind.IBindingContext; -import gov.nist.secauto.metaschema.databind.io.json.IJsonParsingContext; -import gov.nist.secauto.metaschema.databind.io.json.MetaschemaJsonParser; -import gov.nist.secauto.metaschema.databind.model.test.SimpleAssembly; - -import org.jmock.Expectations; -import org.jmock.junit5.JUnit5Mockery; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import java.io.IOException; -import java.lang.reflect.Field; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -class DefaultFlagPropertyTest { - @RegisterExtension - JUnit5Mockery context = new JUnit5Mockery(); - - private final IClassBinding classBinding = context.mock(IClassBinding.class); - private final IBindingContext bindingContext = context.mock(IBindingContext.class); - private final IJsonParsingContext jsonParsingContext = context.mock(IJsonParsingContext.class); - - @SuppressWarnings("resource") // mocked - @Test - @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT") - void testJsonRead() - throws JsonParseException, IOException, NoSuchFieldException { - String json = "{ \"test\": { \"id\": \"theId\", \"number\": 1 } }"; - JsonFactory factory = new JsonFactory(); - try (JsonParser jsonParser = factory.createParser(json)) { - - Field field = SimpleAssembly.class.getDeclaredField("_id"); - - context.checking(new Expectations() { - { // NOPMD - intentional - atMost(1).of(bindingContext).getJavaTypeAdapterInstance(StringAdapter.class); - will(returnValue(MetaschemaDataTypeProvider.STRING)); - - allowing(classBinding).getBoundClass(); - will(returnValue(SimpleAssembly.class)); - allowing(classBinding).getBindingContext(); - will(returnValue(bindingContext)); - - allowing(jsonParsingContext).getReader(); - will(returnValue(jsonParser)); - } - }); - - DefaultFlagProperty idProperty = new DefaultFlagProperty( - ObjectUtils.notNull(field), - ObjectUtils.notNull(classBinding)); - - assertEquals(JsonToken.START_OBJECT, jsonParser.nextToken()); - assertEquals("test", jsonParser.nextFieldName()); - assertEquals(JsonToken.START_OBJECT, jsonParser.nextToken()); - assertEquals(JsonToken.FIELD_NAME, jsonParser.nextToken()); - assertEquals("id", jsonParser.currentName()); - - SimpleAssembly obj = new SimpleAssembly(); - - assertTrue(new MetaschemaJsonParser(jsonParser).readInstanceValues(idProperty, obj)); - - assertEquals("theId", obj.getId()); - } - } -} diff --git a/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/JsonKeyTest.java b/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/JsonKeyTest.java new file mode 100644 index 000000000..c51648d6a --- /dev/null +++ b/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/JsonKeyTest.java @@ -0,0 +1,65 @@ +/* + * Portions of this software was developed by employees of the National Institute + * of Standards and Technology (NIST), an agency of the Federal Government and is + * being made available as a public service. Pursuant to title 17 United States + * Code Section 105, works of NIST employees are not subject to copyright + * protection in the United States. This software may be subject to foreign + * copyright. Permission in the United States and in foreign countries, to the + * extent that NIST may hold copyright, to use, copy, modify, create derivative + * works, and distribute this software and its documentation without fee is hereby + * granted on a non-exclusive basis, provided that this notice and disclaimer + * of warranty appears in all copies. + * + * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER + * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY + * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM + * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE + * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT + * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, + * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, + * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, + * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR + * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT + * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. + */ + +package gov.nist.secauto.metaschema.databind.model; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import gov.nist.secauto.metaschema.core.model.IMetaschema; +import gov.nist.secauto.metaschema.core.model.MetaschemaException; +import gov.nist.secauto.metaschema.core.model.xml.MetaschemaLoader; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; +import gov.nist.secauto.metaschema.databind.DynamicBindingContext; +import gov.nist.secauto.metaschema.databind.IBindingContext; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import edu.umd.cs.findbugs.annotations.NonNull; + +class JsonKeyTest + extends AbstractBoundModelTestSupport { + // @TempDir + // Path generationDir; + @NonNull + Path generationDir = ObjectUtils.notNull(Paths.get("target/generated-test-sources/metaschema")); + + @Test + void testJsonKey() throws IOException, MetaschemaException { + IMetaschema module = new MetaschemaLoader().load(ObjectUtils.requireNonNull( + Paths.get("src/test/resources/metaschema/json-key/metaschema.xml"))); + + IBindingContext bindingContext = DynamicBindingContext.forMetaschema(module, generationDir); + + Object obj = bindingContext.newBoundLoader().load( + ObjectUtils.requireNonNull(Paths.get("src/test/resources/metaschema/json-key/test.json"))); + + assertNotNull(obj); + } +} diff --git a/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/test/MultiFieldAssembly.java b/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/test/MultiFieldAssembly.java index 5c1fd21d4..1748bd4ec 100644 --- a/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/test/MultiFieldAssembly.java +++ b/databind/src/test/java/gov/nist/secauto/metaschema/databind/model/test/MultiFieldAssembly.java @@ -29,8 +29,11 @@ import gov.nist.secauto.metaschema.core.model.JsonGroupAsBehavior; import gov.nist.secauto.metaschema.core.model.XmlGroupAsBehavior; import gov.nist.secauto.metaschema.databind.model.annotations.BoundField; +import gov.nist.secauto.metaschema.databind.model.annotations.BoundFlag; import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs; import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly; +import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaField; +import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaFieldValue; import java.util.List; @@ -50,6 +53,12 @@ public class MultiFieldAssembly { inJson = JsonGroupAsBehavior.LIST) private List _field2; + @BoundField + private ValueKeyField field3; + + @BoundField + private DefaultValueKeyField field4; + public MultiFieldAssembly() { } @@ -61,4 +70,58 @@ public String getField1() { public List getField2() { return _field2; } + + public ValueKeyField getField3() { + return field3; + } + + public void setField3(ValueKeyField field3) { + this.field3 = field3; + } + + public DefaultValueKeyField getField4() { + return field4; + } + + public void setField4(DefaultValueKeyField field4) { + this.field4 = field4; + } + + @SuppressWarnings("PMD") + @MetaschemaField( + name = "field-value-key", + metaschema = TestMetaschema.class) + public static class ValueKeyField { + @BoundFlag + private String flag; + + @MetaschemaFieldValue(valueKeyName = "a-value") + private String _value; + + public ValueKeyField() { + } + + public String getValue() { + return _value; + } + } + + @SuppressWarnings("PMD") + @MetaschemaField( + name = "field-default-value-key", + metaschema = TestMetaschema.class) + public static class DefaultValueKeyField { + @BoundFlag + private String flag; + + @MetaschemaFieldValue + private String _value; + + public DefaultValueKeyField() { + } + + public String getValue() { + return _value; + } + } } diff --git a/databind/src/test/resources/metaschema/json-key/metaschema.xml b/databind/src/test/resources/metaschema/json-key/metaschema.xml new file mode 100644 index 000000000..5fa259075 --- /dev/null +++ b/databind/src/test/resources/metaschema/json-key/metaschema.xml @@ -0,0 +1,86 @@ + + JSON key test Metaschema + 1.0 + assembly + http://csrc.nist.gov/ns/metaschema/testing/json-key + http://csrc.nist.gov/ns/metaschema/testing/json-key + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + top + + + + + + + diff --git a/databind/src/test/resources/metaschema/json-key/schema.json b/databind/src/test/resources/metaschema/json-key/schema.json new file mode 100644 index 000000000..b7d1b41d7 --- /dev/null +++ b/databind/src/test/resources/metaschema/json-key/schema.json @@ -0,0 +1,165 @@ +{ + "$schema" : "http://json-schema.org/draft-07/schema#", + "$id" : "http://csrc.nist.gov/ns/metaschema/testing/json-key/assembly-1.0-schema.json", + "$comment" : "JSON key test Metaschema", + "type" : "object", + "definitions" : { + "FlagAssemblyOtherType" : { + "$id" : "#/definitions/FlagAssemblyOtherType", + "$ref" : "#/definitions/StringDatatype" + }, + "FieldAssemblyFieldKeyValueType" : { + "$id" : "#/definitions/FieldAssemblyFieldKeyValueType", + "$ref" : "#/definitions/StringDatatype" + }, + "FlagAssemblyIdType" : { + "$id" : "#/definitions/FlagAssemblyIdType", + "$ref" : "#/definitions/StringDatatype" + }, + "FieldAssemblyFieldKeyFlagValueType" : { + "$id" : "#/definitions/FieldAssemblyFieldKeyFlagValueType", + "type" : "object", + "properties" : { + "other" : { + "$ref" : "#/definitions/FlagAssemblyOtherType" + }, + "STRVALUE" : { + "$ref" : "#/definitions/StringDatatype" + } + }, + "required" : [ "STRVALUE" ], + "additionalProperties" : false + }, + "AssemblyAssemblyBottomFlagKeyType" : { + "$id" : "#/definitions/AssemblyAssemblyBottomFlagKeyType", + "type" : "object", + "properties" : { + "other" : { + "$ref" : "#/definitions/FlagAssemblyOtherType" + } + }, + "additionalProperties" : false + }, + "AssemblyAssemblyTopType" : { + "$id" : "#/definitions/AssemblyAssemblyTopType", + "type" : "object", + "properties" : { + "middle" : { + "oneOf" : [ { + "$ref" : "#/definitions/AssemblyAssemblyMiddleType" + }, { + "type" : "array", + "items" : { + "$ref" : "#/definitions/AssemblyAssemblyMiddleType" + }, + "minItems" : 2 + } ] + } + }, + "required" : [ "middle" ], + "additionalProperties" : false + }, + "AssemblyAssemblyMiddleType" : { + "$id" : "#/definitions/AssemblyAssemblyMiddleType", + "type" : "object", + "properties" : { + "field-value" : { + "$ref" : "#/definitions/FieldAssemblyFieldValueType" + }, + "field-key-values" : { + "type" : "object", + "minProperties" : 1, + "propertyNames" : { + "$ref" : "#/definitions/StringDatatype" + }, + "additionalProperties" : { + "$ref" : "#/definitions/FieldAssemblyFieldKeyValueType" + } + }, + "field-key-flag-values" : { + "type" : "object", + "minProperties" : 1, + "propertyNames" : { + "$ref" : "#/definitions/StringDatatype" + }, + "additionalProperties" : { + "$ref" : "#/definitions/FieldAssemblyFieldKeyFlagValueType" + } + }, + "bottom-keys" : { + "type" : "object", + "minProperties" : 1, + "propertyNames" : { + "$ref" : "#/definitions/StringDatatype" + }, + "additionalProperties" : { + "$ref" : "#/definitions/AssemblyAssemblyBottomKeyType" + } + }, + "bottom-flag-keys" : { + "type" : "object", + "minProperties" : 1, + "propertyNames" : { + "$ref" : "#/definitions/StringDatatype" + }, + "additionalProperties" : { + "$ref" : "#/definitions/AssemblyAssemblyBottomFlagKeyType" + } + }, + "bottom-flag-key-values" : { + "type" : "object", + "minProperties" : 1, + "propertyNames" : { + "$ref" : "#/definitions/StringDatatype" + }, + "additionalProperties" : { + "$ref" : "#/definitions/AssemblyAssemblyBottomFlagKeyValueType" + } + } + }, + "additionalProperties" : false + }, + "FieldAssemblyFieldValueType" : { + "$id" : "#/definitions/FieldAssemblyFieldValueType", + "$ref" : "#/definitions/StringDatatype" + }, + "AssemblyAssemblyBottomKeyType" : { + "$id" : "#/definitions/AssemblyAssemblyBottomKeyType", + "type" : "object", + "additionalProperties" : false + }, + "AssemblyAssemblyBottomFlagKeyValueType" : { + "$id" : "#/definitions/AssemblyAssemblyBottomFlagKeyValueType", + "type" : "object", + "properties" : { + "other" : { + "$ref" : "#/definitions/FlagAssemblyOtherType" + }, + "values" : { + "type" : "array", + "items" : { + "$ref" : "#/definitions/FieldAssemblyFieldValueType" + }, + "minItems" : 1 + } + }, + "additionalProperties" : false + }, + "StringDatatype" : { + "description" : "A non-empty string with leading and trailing whitespace disallowed. Whitespace is: U+9, U+10, U+32 or [ \n\t]+", + "type" : "string", + "pattern" : "^\\S(.*\\S)?$" + } + }, + "properties" : { + "$schema" : { + "type" : "string", + "format" : "uri-reference" + }, + "top" : { + "$ref" : "#/definitions/AssemblyAssemblyTopType" + } + }, + "required" : [ "top" ], + "additionalProperties" : false +} \ No newline at end of file diff --git a/databind/src/test/resources/metaschema/json-key/test.json b/databind/src/test/resources/metaschema/json-key/test.json new file mode 100644 index 000000000..dc4b38ef7 --- /dev/null +++ b/databind/src/test/resources/metaschema/json-key/test.json @@ -0,0 +1,70 @@ +{ + "$schema": "file:/C:/Users/davidwal/git/github/david-waltermire-nist/metaschema-java/databind/src/test/resources/metaschema/json-key/schema.json", + "top": { + "middle": [ + {"field-value": "value1"}, + { + "field-value": "value2", + "field-key-flag-values": { + "no-other": {"STRVALUE": "value3"} + }, + "bottom-keys": { + "id1": {} + } + }, + { + "field-value": "value4", + "field-key-values": {"id2": "value5"}, + "field-key-flag-values": { + "other": { + "other": "other-value", + "STRVALUE": "value6" + } + }, + "bottom-keys": { + "id3": {} + }, + "bottom-flag-keys": { + "id4": {"other": "value7"} + }, + "bottom-flag-key-values": { + "id5": { + "other": "value8", + "values": ["value9"] + } + } + }, + { + "field-value": "value10", + "field-key-values": { + "id6": "value11", + "id7": "value12" + }, + "field-key-flag-values": { + "no-other": {"STRVALUE": "value13"}, + "other": { + "other": "other-value", + "STRVALUE": "value14" + } + }, + "bottom-keys": { + "id8": {}, + "id9": {} + }, + "bottom-flag-keys": { + "id10": {"other": "value15"}, + "id11": {"other": "value16"} + }, + "bottom-flag-key-values": { + "id12": { + "other": "value17", + "values": [ + "value18", + "value19" + ] + } + } + } + ] + } +} \ No newline at end of file diff --git a/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/GenerateSchemaCommand.java b/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/GenerateSchemaCommand.java index 8b9451ff6..baf3e230f 100644 --- a/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/GenerateSchemaCommand.java +++ b/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/GenerateSchemaCommand.java @@ -82,6 +82,7 @@ public class GenerateSchemaCommand private static final Option AS_OPTION = ObjectUtils.notNull( Option.builder() .longOpt("as") + .required() .hasArg() .argName("FORMAT") .desc("source format: xml, json, or yaml") @@ -127,8 +128,10 @@ public List getExtraArguments() { @Override public void validateOptions(CallingContext callingContext, CommandLine cmdLine) throws InvalidArgumentException { try { - String toFormatText = cmdLine.getOptionValue(AS_OPTION); - SchemaFormat.valueOf(toFormatText.toUpperCase(Locale.ROOT)); + String asFormatText = cmdLine.getOptionValue(AS_OPTION); + if (asFormatText != null) { + SchemaFormat.valueOf(asFormatText.toUpperCase(Locale.ROOT)); + } } catch (IllegalArgumentException ex) { InvalidArgumentException newEx = new InvalidArgumentException( // NOPMD - intentional String.format("Invalid '%s' argument. The format must be one of: %s.", diff --git a/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/ValidateContentWithMetaschemaCommand.java b/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/ValidateContentWithMetaschemaCommand.java index 18b8c8a8d..4c16bee0e 100644 --- a/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/ValidateContentWithMetaschemaCommand.java +++ b/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/ValidateContentWithMetaschemaCommand.java @@ -34,9 +34,9 @@ import gov.nist.secauto.metaschema.core.model.IMetaschema; import gov.nist.secauto.metaschema.core.model.MetaschemaException; import gov.nist.secauto.metaschema.core.model.constraint.IConstraintSet; +import gov.nist.secauto.metaschema.core.model.util.XmlUtil; import gov.nist.secauto.metaschema.core.model.validation.JsonSchemaContentValidator; import gov.nist.secauto.metaschema.core.model.xml.MetaschemaLoader; -import gov.nist.secauto.metaschema.core.model.xml.XmlUtil; import gov.nist.secauto.metaschema.core.util.CollectionUtil; import gov.nist.secauto.metaschema.core.util.ObjectUtils; import gov.nist.secauto.metaschema.databind.DynamicBindingContext; diff --git a/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/ValidateMetaschemaCommand.java b/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/ValidateMetaschemaCommand.java index b845654b7..e9e333e43 100644 --- a/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/ValidateMetaschemaCommand.java +++ b/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/ValidateMetaschemaCommand.java @@ -36,10 +36,10 @@ import gov.nist.secauto.metaschema.cli.processor.command.ExtraArgument; import gov.nist.secauto.metaschema.cli.processor.command.ICommandExecutor; import gov.nist.secauto.metaschema.cli.util.LoggingValidationHandler; +import gov.nist.secauto.metaschema.core.model.util.XmlUtil; import gov.nist.secauto.metaschema.core.model.validation.IContentValidator; import gov.nist.secauto.metaschema.core.model.validation.IValidationResult; import gov.nist.secauto.metaschema.core.model.xml.MetaschemaLoader; -import gov.nist.secauto.metaschema.core.model.xml.XmlUtil; import gov.nist.secauto.metaschema.core.util.CollectionUtil; import gov.nist.secauto.metaschema.core.util.ObjectUtils; @@ -91,7 +91,9 @@ public List getExtraArguments() { protected List getXmlSchemaSources() throws IOException { List retval = new LinkedList<>(); retval.add(XmlUtil.getStreamSource( - MetaschemaLoader.class.getResource("/schema/xml/metaschema.xsd"))); + ObjectUtils.requireNonNull( + MetaschemaLoader.class.getResource("/schema/xml/metaschema.xsd"), + "Unable to load '/schema/xml/metaschema.xsd' on the classpath"))); return CollectionUtil.unmodifiableList(retval); } diff --git a/metaschema-schema-generator/src/test/java/gov/nist/secauto/metaschema/schemagen/AbstractSchemaGeneratorTestSuite.java b/metaschema-schema-generator/src/test/java/gov/nist/secauto/metaschema/schemagen/AbstractSchemaGeneratorTestSuite.java index f093bcb87..86a100799 100644 --- a/metaschema-schema-generator/src/test/java/gov/nist/secauto/metaschema/schemagen/AbstractSchemaGeneratorTestSuite.java +++ b/metaschema-schema-generator/src/test/java/gov/nist/secauto/metaschema/schemagen/AbstractSchemaGeneratorTestSuite.java @@ -178,7 +178,7 @@ protected void doTest( @NonNull ContentCase... contentCases) throws IOException, MetaschemaException { Path generationDir = getGenerationPath(); - Path testSuite = Paths.get("../metaschema-model/metaschema/test-suite/schema-generation/"); + Path testSuite = Paths.get("../core/metaschema/test-suite/schema-generation/"); Path collectionPath = testSuite.resolve(collectionName); MetaschemaLoader loader = new MetaschemaLoader(); @@ -221,7 +221,10 @@ protected void doTest( } @NonNull - protected ContentCase contentCase(@NonNull Format actualFormat, @NonNull String contentName, boolean valid) { + protected ContentCase contentCase( + @NonNull Format actualFormat, + @NonNull String contentName, + boolean valid) { return new ContentCase(contentName, actualFormat, valid); } diff --git a/metaschema-schema-generator/src/test/java/gov/nist/secauto/metaschema/schemagen/XmlSuiteTest.java b/metaschema-schema-generator/src/test/java/gov/nist/secauto/metaschema/schemagen/XmlSuiteTest.java index 01cca3256..2642b0e67 100644 --- a/metaschema-schema-generator/src/test/java/gov/nist/secauto/metaschema/schemagen/XmlSuiteTest.java +++ b/metaschema-schema-generator/src/test/java/gov/nist/secauto/metaschema/schemagen/XmlSuiteTest.java @@ -60,7 +60,8 @@ class XmlSuiteTest // private static final XmlSchemaContentValidator SCHEMA_VALIDATOR; // static { - // URL schemaResource = MetaschemaLoader.class.getResource("/schema/xml/XMLSchema.xsd"); + // URL schemaResource = + // MetaschemaLoader.class.getResource("/schema/xml/XMLSchema.xsd"); // try { // List schemaSources = Collections.singletonList( // new StreamSource(schemaResource.openStream(), schemaResource.toString())); @@ -120,7 +121,6 @@ void testCollapsibleMultiple() throws IOException, MetaschemaException { // NOPM contentCase(Format.JSON, "collapsible_test_singleton_PASS.json", true)); } - @Disabled @Test void testJsonValueKeyField() throws IOException, MetaschemaException { // NOPMD - delegated to doTest doTest( @@ -157,8 +157,10 @@ void testAllowedValues() throws IOException, MetaschemaException { // NOPMD - de "allowed-values", "allowed-values-basic_metaschema.xml", "allowed-values-basic-schema", - // contentCase(Format.JSON, "allowed-values-basic_test_baddates_FAIL.json", false), - // contentCase(Format.JSON, "allowed-values-basic_test_badvalues_FAIL.json", false), + // contentCase(Format.JSON, "allowed-values-basic_test_baddates_FAIL.json", + // false), + // contentCase(Format.JSON, "allowed-values-basic_test_badvalues_FAIL.json", + // false), contentCase(Format.XML, "allowed-values-basic_test_valid_FAIL.xml", false), // contentCase(Format.JSON, "allowed-values-basic_test_valid_PASS.json", true), contentCase(Format.XML, "allowed-values-basic_test_valid_PASS.xml", true)); diff --git a/pom.xml b/pom.xml index d56a54b47..d1277ca9b 100644 --- a/pom.xml +++ b/pom.xml @@ -486,6 +486,7 @@ com.github.spotbugs spotbugs-annotations ${dependency.spotbugs-annotations.version} + provided org.junit.jupiter