From 9825e082c92dcbe444af71f50946a3f27de9b871 Mon Sep 17 00:00:00 2001 From: David Waltermire Date: Thu, 25 Dec 2025 12:19:21 -0500 Subject: [PATCH] feat: enhance code generator Javadoc and null-safety annotations Improves the metaschema-maven-plugin code generator to produce binding classes with complete Javadoc and null-safety annotations. Code Generator Changes: - Fix Javadoc quote issue by using literal format instead of $S - Add Javadoc to constructor generation (no-arg and data constructors) - Add Javadoc to getter/setter generation with @param/@return tags - Add @NonNull/@Nullable annotations based on required attribute - Add isRequired() and isCollectionType() methods to type info classes - Add lazy initialization for collection getters (LinkedList/LinkedHashMap) Regenerated Files: - metaschema-testing binding classes regenerated with improvements Closes #568, #571, #575 --- .claude/rules/unit-testing.md | 18 ++ .../AbstractModelInstanceTypeInfo.java | 26 ++ .../AbstractNamedModelInstanceTypeInfo.java | 15 ++ .../typeinfo/AbstractPropertyTypeInfo.java | 82 ++++++- .../DefaultMetaschemaClassFactory.java | 5 + .../typeinfo/FlagInstanceTypeInfoImpl.java | 5 + .../typeinfo/INamedInstanceTypeInfo.java | 84 ++++++- .../typeinfo/INamedModelInstanceTypeInfo.java | 90 +++++++ .../codegen/typeinfo/IPropertyTypeInfo.java | 76 +++++- .../codegen/typeinfo/TypeInfoUtils.java | 20 ++ .../codegen/JavadocGenerationTest.java | 222 ++++++++++++++++++ .../metaschema/required_flag/metaschema.xml | 29 +++ metaschema-testing/pom-bootstrap.xml | 37 ++- .../model/testing/AbstractTestSuite.java | 70 +++--- .../testing/testsuite/GenerateSchema.java | 108 ++++++++- .../testing/testsuite/GenerationCase.java | 81 ++++++- .../model/testing/testsuite/Metaschema.java | 36 ++- .../testing/testsuite/TestCollection.java | 84 ++++++- .../model/testing/testsuite/TestScenario.java | 86 ++++++- .../model/testing/testsuite/TestSuite.java | 40 +++- .../testing/testsuite/ValidationCase.java | 81 ++++++- 21 files changed, 1220 insertions(+), 75 deletions(-) create mode 100644 databind/src/test/java/gov/nist/secauto/metaschema/databind/codegen/JavadocGenerationTest.java create mode 100644 databind/src/test/resources/metaschema/required_flag/metaschema.xml diff --git a/.claude/rules/unit-testing.md b/.claude/rules/unit-testing.md index 6e54bf619..1286c72ce 100644 --- a/.claude/rules/unit-testing.md +++ b/.claude/rules/unit-testing.md @@ -39,3 +39,21 @@ Every test class should prioritize edge case coverage over happy paths: 3. Add tests before completing the work Use the `unit-test-writing` skill for the detailed workflow. + +## Legacy Code Coverage + +**When improving or refactoring existing classes, add tests for legacy functionality.** + +This ensures: +- Existing behavior is documented through tests +- Regressions are caught if refactoring breaks something +- Test coverage improves incrementally over time + +### Process + +1. **Before changes**: Write tests capturing current behavior of code you're touching +2. **Verify tests pass**: Confirms tests accurately reflect existing behavior +3. **Make improvements**: Refactor or enhance the code +4. **Verify tests still pass**: Confirms behavioral equivalence + +This approach builds test coverage organically as the codebase evolves. diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/AbstractModelInstanceTypeInfo.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/AbstractModelInstanceTypeInfo.java index 1abc99355..7676397c8 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/AbstractModelInstanceTypeInfo.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/AbstractModelInstanceTypeInfo.java @@ -21,12 +21,15 @@ import gov.nist.secauto.metaschema.databind.codegen.typeinfo.def.IAssemblyDefinitionTypeInfo; import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; abstract class AbstractModelInstanceTypeInfo extends AbstractInstanceTypeInfo @@ -74,6 +77,29 @@ public TypeName getJavaFieldType() { return retval; } + @Override + public boolean isCollectionType() { + IModelInstanceAbsolute instance = getInstance(); + int maxOccurs = instance.getMaxOccurs(); + return maxOccurs == -1 || maxOccurs > 1; + } + + @Nullable + @Override + public Class getCollectionImplementationClass() { + IModelInstanceAbsolute instance = getInstance(); + int maxOccurs = instance.getMaxOccurs(); + if (maxOccurs == -1 || maxOccurs > 1) { + // This is a collection - return the appropriate implementation class + if (JsonGroupAsBehavior.KEYED.equals(instance.getJsonGroupAsBehavior())) { + return LinkedHashMap.class; + } + return LinkedList.class; + } + // Not a collection + return null; + } + @NonNull protected abstract AnnotationSpec.Builder newBindingAnnotation(); diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/AbstractNamedModelInstanceTypeInfo.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/AbstractNamedModelInstanceTypeInfo.java index f907cbe23..349917ca2 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/AbstractNamedModelInstanceTypeInfo.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/AbstractNamedModelInstanceTypeInfo.java @@ -42,6 +42,21 @@ public AbstractNamedModelInstanceTypeInfo( super(instance, parentDefinition); } + @Override + public boolean isRequired() { + INSTANCE instance = getInstance(); + // A model instance is required if minOccurs >= 1 AND it's a single item (not a + // collection) + return instance.getMinOccurs() >= 1 && instance.getMaxOccurs() == 1; + } + + @Override + public boolean isCollectionType() { + INSTANCE instance = getInstance(); + // A collection has maxOccurs > 1 or unbounded (-1) + return instance.getMaxOccurs() == -1 || instance.getMaxOccurs() > 1; + } + @NonNull @Override public String getBaseName() { diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/AbstractPropertyTypeInfo.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/AbstractPropertyTypeInfo.java index 82b61ea78..124f8d905 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/AbstractPropertyTypeInfo.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/AbstractPropertyTypeInfo.java @@ -5,6 +5,7 @@ package gov.nist.secauto.metaschema.databind.codegen.typeinfo; +import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; @@ -21,7 +22,52 @@ import javax.lang.model.element.Modifier; import edu.umd.cs.findbugs.annotations.NonNull; - +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * Abstract base class for generating Java property code including fields, + * getters, and setters with appropriate null-safety annotations. + * + *

Null-Safety Annotation Contract

+ * + *

+ * Generated getters and setters receive null-safety annotations based on two + * factors: whether the property is required and whether it is a collection. + * + *

Getter Annotations

+ *
    + *
  • {@code @NonNull} - Collection properties (lazy initialized) or required + * properties
  • + *
  • {@code @Nullable} - Optional non-collection properties
  • + *
+ * + *

Setter Annotations

+ *
    + *
  • {@code @NonNull} - Collection properties or required properties
  • + *
  • {@code @Nullable} - Optional non-collection properties
  • + *
+ * + *

Collection Handling

+ * + *

+ * Collection properties (where {@link #isCollectionType()} returns + * {@code true}) use lazy initialization in their getters. The getter checks if + * the field is null and initializes it with a new collection instance from + * {@link #getCollectionImplementationClass()} before returning. This ensures + * collection getters never return null, allowing them to be annotated + * {@code @NonNull}. + * + *

+ * Contract: Subclasses must ensure that + * {@link #isCollectionType()} and {@link #getCollectionImplementationClass()} + * are consistent: {@code isCollectionType()} returns {@code true} if and only + * if {@code getCollectionImplementationClass()} returns non-null. + * + * @param + * the type of the parent definition type info + * @see IPropertyTypeInfo#isCollectionType() + * @see IPropertyTypeInfo#getCollectionImplementationClass() + */ public abstract class AbstractPropertyTypeInfo extends AbstractTypeInfo implements IPropertyTypeInfo { @@ -53,6 +99,19 @@ public Set build(@NonNull TypeSpec.Builder builder) { return retval; } + /** + * Build getter and setter methods for this property. + * + *

+ * This method generates accessor methods with appropriate null-safety + * annotations and Javadoc based on the property's characteristics. Collection + * getters use lazy initialization to ensure they never return null. + * + * @param typeBuilder + * the class builder to add methods to + * @param fieldBuilder + * the field spec for the backing field + */ protected void buildExtraMethods( @NonNull TypeSpec.Builder typeBuilder, @NonNull FieldSpec fieldBuilder) { @@ -60,20 +119,39 @@ protected void buildExtraMethods( TypeName javaFieldType = getJavaFieldType(); String propertyName = getPropertyName(); { + Class collectionImplClass = getCollectionImplementationClass(); + // Collections are always @NonNull (lazy initialized), otherwise based on + // isRequired() + Class nullAnnotation = collectionImplClass != null || isRequired() ? NonNull.class : Nullable.class; MethodSpec.Builder method = MethodSpec.methodBuilder("get" + propertyName) .returns(javaFieldType) + .addAnnotation(AnnotationSpec.builder(nullAnnotation).build()) .addModifiers(Modifier.PUBLIC); assert method != null; + buildGetterJavadoc(method); + + if (collectionImplClass != null) { + // Use lazy initialization for collections + method.beginControlFlow("if ($N == null)", fieldBuilder) + .addStatement("$N = new $T<>()", fieldBuilder, collectionImplClass) + .endControlFlow(); + } method.addStatement("return $N", fieldBuilder); typeBuilder.addMethod(method.build()); } { - ParameterSpec valueParam = ParameterSpec.builder(javaFieldType, "value").build(); + // Add null-safety annotation to setter parameter + // Collections get @NonNull (lazy initialized), required properties get @NonNull + ParameterSpec.Builder paramBuilder = ParameterSpec.builder(javaFieldType, "value"); + Class paramAnnotation = isCollectionType() || isRequired() ? NonNull.class : Nullable.class; + paramBuilder.addAnnotation(AnnotationSpec.builder(paramAnnotation).build()); + ParameterSpec valueParam = paramBuilder.build(); MethodSpec.Builder method = MethodSpec.methodBuilder("set" + propertyName) .addModifiers(Modifier.PUBLIC) .addParameter(valueParam); assert method != null; + buildSetterJavadoc(method, "value"); method.addStatement("$N = $N", fieldBuilder, valueParam); typeBuilder.addMethod(method.build()); } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/DefaultMetaschemaClassFactory.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/DefaultMetaschemaClassFactory.java index b2f35b309..526686923 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/DefaultMetaschemaClassFactory.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/DefaultMetaschemaClassFactory.java @@ -386,11 +386,16 @@ protected TypeSpec.Builder newClassBuilder( builder.addMethod(MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) + .addJavadoc("Constructs a new {@code $L} instance with no metadata.\n", typeInfo.getClassName()) .addStatement("this(null)") .build()); builder.addMethod(MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) + .addJavadoc("Constructs a new {@code $L} instance with the specified metadata.\n", typeInfo.getClassName()) + .addJavadoc("\n") + .addJavadoc("@param data\n") + .addJavadoc(" the metaschema data, or {@code null} if none\n") .addParameter(IMetaschemaData.class, "data") .addStatement("this.$N = $N", "__metaschemaData", "data") .build()); diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/FlagInstanceTypeInfoImpl.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/FlagInstanceTypeInfoImpl.java index 7f81c0b90..15ed9f656 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/FlagInstanceTypeInfoImpl.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/FlagInstanceTypeInfoImpl.java @@ -42,6 +42,11 @@ public String getBaseName() { return getInstance().getEffectiveName(); } + @Override + public boolean isRequired() { + return getInstance().isRequired(); + } + @Override public TypeName getJavaFieldType() { return ObjectUtils.notNull(ClassName.get(getInstance().getDefinition().getJavaTypeAdapter().getJavaClass())); diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/INamedInstanceTypeInfo.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/INamedInstanceTypeInfo.java index 5df7a6dce..9a7dedfe1 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/INamedInstanceTypeInfo.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/INamedInstanceTypeInfo.java @@ -6,19 +6,101 @@ package gov.nist.secauto.metaschema.databind.codegen.typeinfo; import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine; import gov.nist.secauto.metaschema.core.model.INamedInstance; +import edu.umd.cs.findbugs.annotations.NonNull; + public interface INamedInstanceTypeInfo extends IInstanceTypeInfo { @Override INamedInstance getInstance(); + /** + * {@inheritDoc} + * + *

+ * This implementation adds the effective description from the named instance as + * the field's Javadoc content. + */ @Override default void buildFieldJavadoc(FieldSpec.Builder builder) { MarkupLine description = getInstance().getEffectiveDescription(); if (description != null) { - builder.addJavadoc("$S", description.toHtml()); + builder.addJavadoc("$L\n", description.toHtml()); + } + } + + /** + * {@inheritDoc} + * + *

+ * This implementation generates getter Javadoc using the instance's formal name + * (if available) or property name, adds the effective description, and includes + * an appropriate {@code @return} tag based on whether the property is required + * or a collection. + */ + @Override + default void buildGetterJavadoc(@NonNull MethodSpec.Builder builder) { + MarkupLine description = getInstance().getEffectiveDescription(); + String formalName = getInstance().getEffectiveFormalName(); + String propertyName = getInstance().getEffectiveName(); + + // Use formal name if available, otherwise property name + if (formalName != null) { + builder.addJavadoc("Get the $L.\n", TypeInfoUtils.toLowerFirstChar(formalName)); + } else { + builder.addJavadoc("Get the {@code $L} property.\n", propertyName); + } + + // Add description as a second paragraph if available + if (description != null) { + builder.addJavadoc("\n"); + builder.addJavadoc("

\n"); + builder.addJavadoc("$L\n", description.toHtml()); + } + + builder.addJavadoc("\n"); + // Collections are always @NonNull (lazy initialized), required properties are + // @NonNull + if (isRequired() || isCollectionType()) { + builder.addJavadoc("@return the $L value\n", propertyName); + } else { + builder.addJavadoc("@return the $L value, or {@code null} if not set\n", propertyName); + } + } + + /** + * {@inheritDoc} + * + *

+ * This implementation generates setter Javadoc using the instance's formal name + * (if available) or property name, adds the effective description, and includes + * a {@code @param} tag for the value parameter. + */ + @Override + default void buildSetterJavadoc(@NonNull MethodSpec.Builder builder, @NonNull String paramName) { + MarkupLine description = getInstance().getEffectiveDescription(); + String formalName = getInstance().getEffectiveFormalName(); + String propertyName = getInstance().getEffectiveName(); + + // Use formal name if available, otherwise property name + if (formalName != null) { + builder.addJavadoc("Set the $L.\n", TypeInfoUtils.toLowerFirstChar(formalName)); + } else { + builder.addJavadoc("Set the {@code $L} property.\n", propertyName); + } + + // Add description as a second paragraph if available + if (description != null) { + builder.addJavadoc("\n"); + builder.addJavadoc("

\n"); + builder.addJavadoc("$L\n", description.toHtml()); } + + builder.addJavadoc("\n"); + builder.addJavadoc("@param $L\n", paramName); + builder.addJavadoc(" the $L value to set\n", propertyName); } } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/INamedModelInstanceTypeInfo.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/INamedModelInstanceTypeInfo.java index 295943035..5fd5ad7b1 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/INamedModelInstanceTypeInfo.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/INamedModelInstanceTypeInfo.java @@ -6,7 +6,10 @@ package gov.nist.secauto.metaschema.databind.codegen.typeinfo; import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine; import gov.nist.secauto.metaschema.core.model.INamedModelInstanceAbsolute; import edu.umd.cs.findbugs.annotations.NonNull; @@ -24,4 +27,91 @@ public interface INamedModelInstanceTypeInfo extends IModelInstanceTypeInfo { default void buildBindingAnnotationCommon(@NonNull AnnotationSpec.Builder annotation) { TypeInfoUtils.buildCommonBindingAnnotationValues(getInstance(), annotation); } + + /** + * {@inheritDoc} + * + *

+ * This implementation adds the effective description from the named model + * instance as the field's Javadoc content. + */ + @Override + default void buildFieldJavadoc(@NonNull FieldSpec.Builder builder) { + MarkupLine description = getInstance().getEffectiveDescription(); + if (description != null) { + builder.addJavadoc("$L\n", description.toHtml()); + } + } + + /** + * {@inheritDoc} + * + *

+ * This implementation generates getter Javadoc using the instance's formal name + * (if available) or property name, adds the effective description, and includes + * an appropriate {@code @return} tag based on whether the property is required + * or a collection. + */ + @Override + default void buildGetterJavadoc(@NonNull MethodSpec.Builder builder) { + MarkupLine description = getInstance().getEffectiveDescription(); + String formalName = getInstance().getEffectiveFormalName(); + String propertyName = getInstance().getEffectiveName(); + + // Use formal name if available, otherwise property name + if (formalName != null) { + builder.addJavadoc("Get the $L.\n", TypeInfoUtils.toLowerFirstChar(formalName)); + } else { + builder.addJavadoc("Get the {@code $L} property.\n", propertyName); + } + + // Add description as a second paragraph if available + if (description != null) { + builder.addJavadoc("\n"); + builder.addJavadoc("

\n"); + builder.addJavadoc("$L\n", description.toHtml()); + } + + builder.addJavadoc("\n"); + // Collections are always @NonNull (lazy initialized), required singles are + // @NonNull + if (isRequired() || isCollectionType()) { + builder.addJavadoc("@return the $L value\n", propertyName); + } else { + builder.addJavadoc("@return the $L value, or {@code null} if not set\n", propertyName); + } + } + + /** + * {@inheritDoc} + * + *

+ * This implementation generates setter Javadoc using the instance's formal name + * (if available) or property name, adds the effective description, and includes + * a {@code @param} tag for the value parameter. + */ + @Override + default void buildSetterJavadoc(@NonNull MethodSpec.Builder builder, @NonNull String paramName) { + MarkupLine description = getInstance().getEffectiveDescription(); + String formalName = getInstance().getEffectiveFormalName(); + String propertyName = getInstance().getEffectiveName(); + + // Use formal name if available, otherwise property name + if (formalName != null) { + builder.addJavadoc("Set the $L.\n", TypeInfoUtils.toLowerFirstChar(formalName)); + } else { + builder.addJavadoc("Set the {@code $L} property.\n", propertyName); + } + + // Add description as a second paragraph if available + if (description != null) { + builder.addJavadoc("\n"); + builder.addJavadoc("

\n"); + builder.addJavadoc("$L\n", description.toHtml()); + } + + builder.addJavadoc("\n"); + builder.addJavadoc("@param $L\n", paramName); + builder.addJavadoc(" the $L value to set\n", propertyName); + } } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/IPropertyTypeInfo.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/IPropertyTypeInfo.java index f806c6f0b..32085c456 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/IPropertyTypeInfo.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/IPropertyTypeInfo.java @@ -6,6 +6,7 @@ package gov.nist.secauto.metaschema.databind.codegen.typeinfo; import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import gov.nist.secauto.metaschema.core.model.IModelDefinition; @@ -13,8 +14,59 @@ import java.util.Set; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; public interface IPropertyTypeInfo extends ITypeInfo { + /** + * Determines if this property is required to have a value. + * + *

+ * For flags, this checks + * {@link gov.nist.secauto.metaschema.core.model.IFlagInstance#isRequired()}. + * For model instances, this is based on minimum occurrence constraints. + * + * @return {@code true} if a value is required, or {@code false} otherwise + */ + default boolean isRequired() { + return false; + } + + /** + * Determines if this property represents a collection (list or map). + * + *

+ * Collections are model instances with maxOccurs greater than 1 or unbounded. + * Collection getters use lazy initialization and are always {@code @NonNull}. + * + * @return {@code true} if this property is a collection, or {@code false} + * otherwise + */ + default boolean isCollectionType() { + return false; + } + + /** + * Get the implementation class to use for lazy initialization of collections. + * + *

+ * For list-based collections, this returns {@link java.util.LinkedList}. For + * map-based (keyed) collections, this returns {@link java.util.LinkedHashMap}. + * For non-collection properties, this returns {@code null}. + * + *

+ * Contract: This method must return non-null if and only if + * {@link #isCollectionType()} returns {@code true}. Implementations must + * maintain this invariant. + * + * @return the collection implementation class, or {@code null} if not a + * collection + * @see #isCollectionType() + */ + @Nullable + default Class getCollectionImplementationClass() { + return null; + } + /** * Generate the Java field associated with this property. * @@ -25,7 +77,7 @@ public interface IPropertyTypeInfo extends ITypeInfo { Set build(@NonNull TypeSpec.Builder builder); /** - * Get the Javadoc description for the current property. + * Add the Javadoc for the current property's field. * * @param builder * the field builder to annotate with the Javadoc @@ -33,4 +85,26 @@ public interface IPropertyTypeInfo extends ITypeInfo { default void buildFieldJavadoc(@NonNull FieldSpec.Builder builder) { // do nothing by default } + + /** + * Add the Javadoc for the current property's getter method. + * + * @param builder + * the method builder to annotate with the Javadoc + */ + default void buildGetterJavadoc(@NonNull MethodSpec.Builder builder) { + // do nothing by default + } + + /** + * Add the Javadoc for the current property's setter method. + * + * @param builder + * the method builder to annotate with the Javadoc + * @param paramName + * the name of the parameter + */ + default void buildSetterJavadoc(@NonNull MethodSpec.Builder builder, @NonNull String paramName) { + // do nothing by default + } } diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/TypeInfoUtils.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/TypeInfoUtils.java index b741bcdb9..0352c3c9d 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/TypeInfoUtils.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/codegen/typeinfo/TypeInfoUtils.java @@ -18,6 +18,26 @@ private TypeInfoUtils() { // disable construction } + /** + * Convert the first character of a string to lowercase. + * + *

+ * This is useful for forming Javadoc sentences where a description needs to + * flow naturally after a prefix like "Get the" or "Set the". + * + * @param text + * the text to convert + * @return the text with the first character lowercased, or the original text if + * empty or already lowercase + */ + @NonNull + public static String toLowerFirstChar(@NonNull String text) { + if (text.isEmpty() || Character.isLowerCase(text.charAt(0))) { + return text; + } + return Character.toLowerCase(text.charAt(0)) + text.substring(1); + } + public static void buildCommonBindingAnnotationValues( @NonNull INamedModelInstance instance, @NonNull AnnotationSpec.Builder annotation) { diff --git a/databind/src/test/java/gov/nist/secauto/metaschema/databind/codegen/JavadocGenerationTest.java b/databind/src/test/java/gov/nist/secauto/metaschema/databind/codegen/JavadocGenerationTest.java new file mode 100644 index 000000000..1c5b3b1e9 --- /dev/null +++ b/databind/src/test/java/gov/nist/secauto/metaschema/databind/codegen/JavadocGenerationTest.java @@ -0,0 +1,222 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.databind.codegen; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import gov.nist.secauto.metaschema.core.model.IModule; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; +import gov.nist.secauto.metaschema.databind.IBindingContext; +import gov.nist.secauto.metaschema.databind.codegen.config.DefaultBindingConfiguration; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.regex.Pattern; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Tests for verifying the quality of generated Javadoc in binding classes. + * + *

+ * These tests verify that generated code meets project Javadoc standards: + *

    + *
  • Descriptions do not have extraneous quotes
  • + *
  • Constructors have proper Javadoc
  • + *
  • Accessor methods have proper Javadoc with @param/@return tags
  • + *
  • Null-safety annotations are present
  • + *
+ */ +class JavadocGenerationTest + extends AbstractMetaschemaTest { + + // Use simple_with_field which has descriptions on fields for better testing + private static final Path TEST_METASCHEMA = ObjectUtils.notNull( + Paths.get("src/test/resources/metaschema/simple_with_field/metaschema.xml")); + + // The assembly is named "top-level" which generates class "TopLevel" + private static final String TEST_CLASS_NAME = "TopLevel"; + + private Path classDir; + + @BeforeEach + void setUp() throws IOException { + Files.createDirectories(generationDir); + classDir = ObjectUtils.notNull(Files.createTempDirectory(generationDir, "javadoc-test-")); + } + + /** + * Generates code and returns the content of the generated Java file. + * + * @param metaschemaPath + * the path to the Metaschema module + * @param className + * the simple name of the class to find + * @return the content of the generated Java file + * @throws Exception + * if generation fails + */ + @NonNull + private String generateAndReadClass(@NonNull Path metaschemaPath, @NonNull String className) throws Exception { + IBindingContext context = newBindingContext(); + IModule module = context.loadMetaschema(metaschemaPath); + + DefaultBindingConfiguration bindingConfiguration = new DefaultBindingConfiguration(); + ModuleCompilerHelper.compileModule(module, classDir, bindingConfiguration); + + // Find the generated Java file + Path generatedFile = findGeneratedFile(classDir, className + ".java"); + return ObjectUtils.notNull(Files.readString(generatedFile)); + } + + /** + * Finds a generated file by name within the given directory tree. + * + * @param dir + * the root directory to search + * @param fileName + * the name of the file to find + * @return the path to the found file + * @throws IOException + * if the file cannot be found or an I/O error occurs + */ + @NonNull + private Path findGeneratedFile(@NonNull Path dir, @NonNull String fileName) throws IOException { + return ObjectUtils.notNull(Files.walk(dir) + .filter(p -> p.getFileName().toString().equals(fileName)) + .findFirst() + .orElseThrow(() -> new IOException("Could not find generated file: " + fileName))); + } + + @Test + void testFieldJavadocDoesNotContainQuotes() throws Exception { + String content = generateAndReadClass(TEST_METASCHEMA, TEST_CLASS_NAME); + + // Pattern to find Javadoc comments that start with a quote + // Good: /** Some description */ + // Bad: /** "Some description" */ (single-line or multi-line) + // Match both single-line and multi-line Javadoc with leading quotes + Pattern quotedJavadoc = Pattern.compile("/\\*\\*\\s*(\\n\\s*\\*\\s*)?\"[^\"]+\""); + + assertFalse(quotedJavadoc.matcher(content).find(), + "Generated Javadoc should not contain quoted descriptions. Found in:\n" + content); + } + + @Test + void testConstructorHasJavadoc() throws Exception { + String content = generateAndReadClass(TEST_METASCHEMA, TEST_CLASS_NAME); + + // Check that the no-arg constructor has Javadoc + assertTrue(content.contains("Constructs a new"), + "Generated constructors should have Javadoc. Content:\n" + content); + } + + @Test + void testDataConstructorHasParamTag() throws Exception { + String content = generateAndReadClass(TEST_METASCHEMA, TEST_CLASS_NAME); + + // Check that the data constructor has @param tag + assertTrue(content.contains("@param data"), + "Data constructor should have @param tag for data parameter. Content:\n" + content); + } + + @Test + void testGetterHasReturnTag() throws Exception { + String content = generateAndReadClass(TEST_METASCHEMA, TEST_CLASS_NAME); + + // Check that getter methods have @return tag + // This assumes the metaschema has at least one field that generates a getter + // Use DOTALL and allow for @Nullable annotation between Javadoc and method + Pattern getterWithReturn + = Pattern.compile("@return.*?\\*/\\s*(@Nullable\\s+)?public\\s+\\w+\\s+get", Pattern.DOTALL); + + assertTrue(getterWithReturn.matcher(content).find(), + "Getter methods should have @return tag. Content:\n" + content); + } + + @Test + void testSetterHasParamTag() throws Exception { + String content = generateAndReadClass(TEST_METASCHEMA, TEST_CLASS_NAME); + + // Check that setter methods have @param tag + // Use DOTALL so . matches newlines in multi-line Javadoc + Pattern setterWithParam = Pattern.compile("@param\\s+value.*?\\*/\\s*public\\s+void\\s+set", Pattern.DOTALL); + + assertTrue(setterWithParam.matcher(content).find(), + "Setter methods should have @param tag. Content:\n" + content); + } + + @Test + void testNullableAnnotationPresent() throws Exception { + String content = generateAndReadClass(TEST_METASCHEMA, TEST_CLASS_NAME); + + // Check that @Nullable annotations are present + assertTrue(content.contains("@Nullable") || content.contains("edu.umd.cs.findbugs.annotations.Nullable"), + "Generated code should include @Nullable annotations. Content:\n" + content); + } + + @Test + void testRequiredFlagHasNonNullAnnotation() throws Exception { + Path requiredFlagMetaschema = ObjectUtils.notNull( + Paths.get("src/test/resources/metaschema/required_flag/metaschema.xml")); + String content = generateAndReadClass(requiredFlagMetaschema, "Item"); + + // Required flag getter should have @NonNull annotation + // Pattern: @NonNull followed by public getter for RequiredId + Pattern nonNullRequired = Pattern.compile("@NonNull\\s+public\\s+String\\s+getRequiredId"); + + assertTrue(nonNullRequired.matcher(content).find(), + "Required flag getter should have @NonNull annotation. Content:\n" + content); + } + + @Test + void testOptionalFlagHasNullableAnnotation() throws Exception { + Path requiredFlagMetaschema = ObjectUtils.notNull( + Paths.get("src/test/resources/metaschema/required_flag/metaschema.xml")); + String content = generateAndReadClass(requiredFlagMetaschema, "Item"); + + // Optional flag getter should have @Nullable annotation + Pattern nullableOptional = Pattern.compile("@Nullable\\s+public\\s+String\\s+getOptionalName"); + + assertTrue(nullableOptional.matcher(content).find(), + "Optional flag getter should have @Nullable annotation. Content:\n" + content); + } + + @Test + void testRequiredFlagJavadocDoesNotMentionNull() throws Exception { + Path requiredFlagMetaschema = ObjectUtils.notNull( + Paths.get("src/test/resources/metaschema/required_flag/metaschema.xml")); + String content = generateAndReadClass(requiredFlagMetaschema, "Item"); + + // Find the getter Javadoc for required-id and verify it doesn't say "or null if + // not set" + // The @return should just say "@return the required-id value" + Pattern requiredGetterJavadoc = Pattern.compile( + "@return the required-id value\\s*\\n\\s*\\*/\\s*@NonNull", Pattern.DOTALL); + + assertTrue(requiredGetterJavadoc.matcher(content).find(), + "Required flag @return should not mention null. Content:\n" + content); + } + + @Test + void testRequiredFlagSetterHasNonNullParameter() throws Exception { + Path requiredFlagMetaschema = ObjectUtils.notNull( + Paths.get("src/test/resources/metaschema/required_flag/metaschema.xml")); + String content = generateAndReadClass(requiredFlagMetaschema, "Item"); + + // Required flag setter should have @NonNull on the parameter + Pattern nonNullSetterParam = Pattern.compile("setRequiredId\\(@NonNull\\s+String\\s+value\\)"); + + assertTrue(nonNullSetterParam.matcher(content).find(), + "Required flag setter should have @NonNull parameter. Content:\n" + content); + } +} diff --git a/databind/src/test/resources/metaschema/required_flag/metaschema.xml b/databind/src/test/resources/metaschema/required_flag/metaschema.xml new file mode 100644 index 000000000..aac7b927b --- /dev/null +++ b/databind/src/test/resources/metaschema/required_flag/metaschema.xml @@ -0,0 +1,29 @@ + + + Required Flag Test + 1.0 + required-flag-test + http://csrc.nist.gov/ns/metaschema/testing/required-flag + http://csrc.nist.gov/ns/metaschema/testing/required-flag + + + Item + An item with required and optional flags + item + + + + + + Required Identifier + A required identifier for the item + + + + Optional Name + An optional name for the item + + + diff --git a/metaschema-testing/pom-bootstrap.xml b/metaschema-testing/pom-bootstrap.xml index 40b8a6c16..ecb1aa7fc 100644 --- a/metaschema-testing/pom-bootstrap.xml +++ b/metaschema-testing/pom-bootstrap.xml @@ -5,10 +5,9 @@ Usage: mvn -f metaschema-testing/pom-bootstrap.xml generate-sources - After running, copy the generated classes from: - target/generated-sources/metaschema/gov/nist/secauto/metaschema/model/testing/testsuite/ - to: - src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/ + This will: + 1. Delete existing generated classes in the testsuite package + 2. Generate new binding classes directly into src/main/java --> 4.0.0 @@ -22,8 +21,36 @@ Metaschema Testing - Binding Class Generator Standalone POM for regenerating test suite binding classes. + + ${project.basedir}/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite + + + + org.apache.maven.plugins + maven-clean-plugin + + + clean-generated-sources + initialize + + clean + + + true + + + ${testsuite.package.dir} + + **/*.java + + + + + + + ${project.groupId} metaschema-maven-plugin @@ -37,7 +64,7 @@ ${project.basedir}/src/main/metaschema - ${project.build.directory}/generated-sources/metaschema + ${project.basedir}/src/main/java ${project.basedir}/src/main/metaschema-bindings/test-suite-bindings.xml diff --git a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/AbstractTestSuite.java b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/AbstractTestSuite.java index b260616d5..0d2a59679 100644 --- a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/AbstractTestSuite.java +++ b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/AbstractTestSuite.java @@ -151,16 +151,14 @@ protected Stream testFactory(@NonNull IBindingContext bindingContex URL testSuiteUrl = testSuiteUri.toURL(); TestSuite testSuite = bindingContext.newBoundLoader().load(TestSuite.class, testSuiteUrl); List testCollections = testSuite.getTestCollections(); - return ObjectUtils.notNull(testCollections == null - ? Stream.empty() - : testCollections.stream() - .flatMap( - collection -> Stream - .of(generateCollection( - ObjectUtils.notNull(collection), - testSuiteUri, - generationPath, - bindingContext)))); + return ObjectUtils.notNull(testCollections.stream() + .flatMap( + collection -> Stream + .of(generateCollection( + ObjectUtils.notNull(collection), + testSuiteUri, + generationPath, + bindingContext)))); } catch (IOException | URISyntaxException ex) { throw new JUnitException("Unable to generate tests", ex); } @@ -225,18 +223,16 @@ private DynamicContainer generateCollection( return DynamicContainer.dynamicContainer( collection.getName(), testSuiteUri, - testScenarios == null - ? Stream.empty() - : testScenarios.stream() - .flatMap(scenario -> { - assert scenario != null; - return Stream.of(generateScenario( - scenario, - collectionUri, - collectionGenerationPath, - bindingContext)); - }) - .sequential()); + testScenarios.stream() + .flatMap(scenario -> { + assert scenario != null; + return Stream.of(generateScenario( + scenario, + collectionUri, + collectionGenerationPath, + bindingContext)); + }) + .sequential()); } @NonNull @@ -330,6 +326,10 @@ private DynamicContainer generateScenario( })); GenerateSchema generateSchema = scenario.getGenerateSchema(); + if (generateSchema == null) { + throw new JUnitException(String.format( + "Scenario '%s' does not have a generate-schema directive", scenario.getName())); + } Metaschema metaschemaDirective = generateSchema.getMetaschema(); URI metaschemaUri = collectionUri.resolve(metaschemaDirective.getLocation()); @@ -368,20 +368,18 @@ private DynamicContainer generateScenario( }); List validationCases = scenario.getValidationCases(); - Stream contentTests = validationCases == null - ? Stream.empty() - : validationCases.stream() - .flatMap(contentCase -> { - assert contentCase != null; - DynamicTest test - = generateValidationCase( - contentCase, - bindingContext, - lazyContentValidator, - collectionUri, - ObjectUtils.notNull(scenarioGenerationPath)); - return test == null ? Stream.empty() : Stream.of(test); - }).sequential(); + Stream contentTests = validationCases.stream() + .flatMap(contentCase -> { + assert contentCase != null; + DynamicTest test + = generateValidationCase( + contentCase, + bindingContext, + lazyContentValidator, + collectionUri, + ObjectUtils.notNull(scenarioGenerationPath)); + return test == null ? Stream.empty() : Stream.of(test); + }).sequential(); return DynamicContainer.dynamicContainer( scenario.getName(), diff --git a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/GenerateSchema.java b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/GenerateSchema.java index 8d27a7df5..3ebd97f70 100644 --- a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/GenerateSchema.java +++ b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/GenerateSchema.java @@ -5,6 +5,8 @@ package gov.nist.secauto.metaschema.model.testing.testsuite; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import gov.nist.secauto.metaschema.core.datatype.adapter.TokenAdapter; import gov.nist.secauto.metaschema.core.model.IBoundObject; import gov.nist.secauto.metaschema.core.model.IMetaschemaData; @@ -37,7 +39,7 @@ public class GenerateSchema implements IBoundObject { private final IMetaschemaData __metaschemaData; /** - * "The expected result of schema generation." + * The expected result of schema generation. */ @BoundFlag( formalName = "Generation Result", @@ -51,7 +53,7 @@ public class GenerateSchema implements IBoundObject { private String _generationResult; /** - * "The expected result of content validation." + * The expected result of content validation. */ @BoundFlag( formalName = "Validation Result", @@ -65,6 +67,9 @@ public class GenerateSchema implements IBoundObject { description = "Validation resulted in failure caused by some content defect or error.") }))) private String _validationResult; + /** + * Reference to a metaschema module to load. + */ @BoundAssembly( formalName = "Metaschema", description = "Reference to a metaschema module to load.", @@ -72,6 +77,9 @@ public class GenerateSchema implements IBoundObject { minOccurs = 1) private Metaschema _metaschema; + /** + * A schema generation comparison test case. + */ @BoundAssembly( formalName = "Generation Case", description = "A schema generation comparison test case.", @@ -80,10 +88,23 @@ public class GenerateSchema implements IBoundObject { groupAs = @GroupAs(name = "generation-cases", inJson = JsonGroupAsBehavior.LIST)) private List _generationCases; + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.GenerateSchema} + * instance with no metadata. + */ public GenerateSchema() { this(null); } + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.GenerateSchema} + * instance with the specified metadata. + * + * @param data + * the metaschema data, or {@code null} if none + */ public GenerateSchema(IMetaschemaData data) { this.__metaschemaData = data; } @@ -93,35 +114,110 @@ public IMetaschemaData getMetaschemaData() { return __metaschemaData; } + /** + * Get the generation Result. + * + *

+ * The expected result of schema generation. + * + * @return the generation-result value, or {@code null} if not set + */ + @Nullable public String getGenerationResult() { return _generationResult; } - public void setGenerationResult(String value) { + /** + * Set the generation Result. + * + *

+ * The expected result of schema generation. + * + * @param value + * the generation-result value to set + */ + public void setGenerationResult(@Nullable String value) { _generationResult = value; } + /** + * Get the validation Result. + * + *

+ * The expected result of content validation. + * + * @return the validation-result value, or {@code null} if not set + */ + @Nullable public String getValidationResult() { return _validationResult; } - public void setValidationResult(String value) { + /** + * Set the validation Result. + * + *

+ * The expected result of content validation. + * + * @param value + * the validation-result value to set + */ + public void setValidationResult(@Nullable String value) { _validationResult = value; } + /** + * Get the metaschema. + * + *

+ * Reference to a metaschema module to load. + * + * @return the metaschema value + */ + @NonNull public Metaschema getMetaschema() { return _metaschema; } - public void setMetaschema(Metaschema value) { + /** + * Set the metaschema. + * + *

+ * Reference to a metaschema module to load. + * + * @param value + * the metaschema value to set + */ + public void setMetaschema(@NonNull Metaschema value) { _metaschema = value; } + /** + * Get the generation Case. + * + *

+ * A schema generation comparison test case. + * + * @return the generation-case value + */ + @NonNull public List getGenerationCases() { + if (_generationCases == null) { + _generationCases = new LinkedList<>(); + } return _generationCases; } - public void setGenerationCases(List value) { + /** + * Set the generation Case. + * + *

+ * A schema generation comparison test case. + * + * @param value + * the generation-case value to set + */ + public void setGenerationCases(@NonNull List value) { _generationCases = value; } diff --git a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/GenerationCase.java b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/GenerationCase.java index f3b43adff..6d212508c 100644 --- a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/GenerationCase.java +++ b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/GenerationCase.java @@ -5,6 +5,8 @@ package gov.nist.secauto.metaschema.model.testing.testsuite; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import gov.nist.secauto.metaschema.core.datatype.adapter.TokenAdapter; import gov.nist.secauto.metaschema.core.datatype.adapter.UriReferenceAdapter; import gov.nist.secauto.metaschema.core.model.IBoundObject; @@ -33,7 +35,7 @@ public class GenerationCase implements IBoundObject { private final IMetaschemaData __metaschemaData; /** - * "The format of the source content." + * The format of the source content. */ @BoundFlag( formalName = "Source Format", @@ -47,7 +49,7 @@ public class GenerationCase implements IBoundObject { private String _sourceFormat; /** - * "A URI reference to the expected schema file location." + * A URI reference to the expected schema file location. */ @BoundFlag( formalName = "Location", @@ -58,7 +60,7 @@ public class GenerationCase implements IBoundObject { private URI _location; /** - * "The expected result of content comparison." + * The expected result of content comparison. */ @BoundFlag( formalName = "Match Result", @@ -71,10 +73,23 @@ public class GenerationCase implements IBoundObject { @AllowedValue(value = "MISMATCH", description = "The actual content did not match the expected content.") }))) private String _matchResult; + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.GenerationCase} + * instance with no metadata. + */ public GenerationCase() { this(null); } + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.GenerationCase} + * instance with the specified metadata. + * + * @param data + * the metaschema data, or {@code null} if none + */ public GenerationCase(IMetaschemaData data) { this.__metaschemaData = data; } @@ -84,27 +99,81 @@ public IMetaschemaData getMetaschemaData() { return __metaschemaData; } + /** + * Get the source Format. + * + *

+ * The format of the source content. + * + * @return the source-format value, or {@code null} if not set + */ + @Nullable public String getSourceFormat() { return _sourceFormat; } - public void setSourceFormat(String value) { + /** + * Set the source Format. + * + *

+ * The format of the source content. + * + * @param value + * the source-format value to set + */ + public void setSourceFormat(@Nullable String value) { _sourceFormat = value; } + /** + * Get the location. + * + *

+ * A URI reference to the expected schema file location. + * + * @return the location value + */ + @NonNull public URI getLocation() { return _location; } - public void setLocation(URI value) { + /** + * Set the location. + * + *

+ * A URI reference to the expected schema file location. + * + * @param value + * the location value to set + */ + public void setLocation(@NonNull URI value) { _location = value; } + /** + * Get the match Result. + * + *

+ * The expected result of content comparison. + * + * @return the match-result value, or {@code null} if not set + */ + @Nullable public String getMatchResult() { return _matchResult; } - public void setMatchResult(String value) { + /** + * Set the match Result. + * + *

+ * The expected result of content comparison. + * + * @param value + * the match-result value to set + */ + public void setMatchResult(@Nullable String value) { _matchResult = value; } diff --git a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/Metaschema.java b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/Metaschema.java index b06ff067e..740900843 100644 --- a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/Metaschema.java +++ b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/Metaschema.java @@ -5,6 +5,7 @@ package gov.nist.secauto.metaschema.model.testing.testsuite; +import edu.umd.cs.findbugs.annotations.NonNull; import gov.nist.secauto.metaschema.core.datatype.adapter.UriReferenceAdapter; import gov.nist.secauto.metaschema.core.model.IBoundObject; import gov.nist.secauto.metaschema.core.model.IMetaschemaData; @@ -28,7 +29,7 @@ public class Metaschema implements IBoundObject { private final IMetaschemaData __metaschemaData; /** - * "A URI reference to the metaschema module location." + * A URI reference to the metaschema module location. */ @BoundFlag( formalName = "Location", @@ -38,10 +39,23 @@ public class Metaschema implements IBoundObject { typeAdapter = UriReferenceAdapter.class) private URI _location; + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.Metaschema} + * instance with no metadata. + */ public Metaschema() { this(null); } + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.Metaschema} + * instance with the specified metadata. + * + * @param data + * the metaschema data, or {@code null} if none + */ public Metaschema(IMetaschemaData data) { this.__metaschemaData = data; } @@ -51,11 +65,29 @@ public IMetaschemaData getMetaschemaData() { return __metaschemaData; } + /** + * Get the location. + * + *

+ * A URI reference to the metaschema module location. + * + * @return the location value + */ + @NonNull public URI getLocation() { return _location; } - public void setLocation(URI value) { + /** + * Set the location. + * + *

+ * A URI reference to the metaschema module location. + * + * @param value + * the location value to set + */ + public void setLocation(@NonNull URI value) { _location = value; } diff --git a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/TestCollection.java b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/TestCollection.java index dd23837f9..9ac7968a7 100644 --- a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/TestCollection.java +++ b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/TestCollection.java @@ -5,6 +5,7 @@ package gov.nist.secauto.metaschema.model.testing.testsuite; +import edu.umd.cs.findbugs.annotations.NonNull; import gov.nist.secauto.metaschema.core.datatype.adapter.StringAdapter; import gov.nist.secauto.metaschema.core.datatype.adapter.UriReferenceAdapter; import gov.nist.secauto.metaschema.core.model.IBoundObject; @@ -35,7 +36,7 @@ public class TestCollection implements IBoundObject { private final IMetaschemaData __metaschemaData; /** - * "A URI reference to the location of this test collection." + * A URI reference to the location of this test collection. */ @BoundFlag( formalName = "Location", @@ -46,7 +47,7 @@ public class TestCollection implements IBoundObject { private URI _location; /** - * "The name of this test collection." + * The name of this test collection. */ @BoundFlag( formalName = "Name", @@ -56,6 +57,9 @@ public class TestCollection implements IBoundObject { typeAdapter = StringAdapter.class) private String _name; + /** + * A test scenario that validates a metaschema and its content. + */ @BoundAssembly( formalName = "Test Scenario", description = "A test scenario that validates a metaschema and its content.", @@ -65,10 +69,23 @@ public class TestCollection implements IBoundObject { groupAs = @GroupAs(name = "test-scenarios", inJson = JsonGroupAsBehavior.LIST)) private List _testScenarios; + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.TestCollection} + * instance with no metadata. + */ public TestCollection() { this(null); } + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.TestCollection} + * instance with the specified metadata. + * + * @param data + * the metaschema data, or {@code null} if none + */ public TestCollection(IMetaschemaData data) { this.__metaschemaData = data; } @@ -78,27 +95,84 @@ public IMetaschemaData getMetaschemaData() { return __metaschemaData; } + /** + * Get the location. + * + *

+ * A URI reference to the location of this test collection. + * + * @return the location value + */ + @NonNull public URI getLocation() { return _location; } - public void setLocation(URI value) { + /** + * Set the location. + * + *

+ * A URI reference to the location of this test collection. + * + * @param value + * the location value to set + */ + public void setLocation(@NonNull URI value) { _location = value; } + /** + * Get the name. + * + *

+ * The name of this test collection. + * + * @return the name value + */ + @NonNull public String getName() { return _name; } - public void setName(String value) { + /** + * Set the name. + * + *

+ * The name of this test collection. + * + * @param value + * the name value to set + */ + public void setName(@NonNull String value) { _name = value; } + /** + * Get the test Scenario. + * + *

+ * A test scenario that validates a metaschema and its content. + * + * @return the test-scenario value + */ + @NonNull public List getTestScenarios() { + if (_testScenarios == null) { + _testScenarios = new LinkedList<>(); + } return _testScenarios; } - public void setTestScenarios(List value) { + /** + * Set the test Scenario. + * + *

+ * A test scenario that validates a metaschema and its content. + * + * @param value + * the test-scenario value to set + */ + public void setTestScenarios(@NonNull List value) { _testScenarios = value; } diff --git a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/TestScenario.java b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/TestScenario.java index f32a9368b..ea3566143 100644 --- a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/TestScenario.java +++ b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/TestScenario.java @@ -5,6 +5,8 @@ package gov.nist.secauto.metaschema.model.testing.testsuite; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import gov.nist.secauto.metaschema.core.datatype.adapter.StringAdapter; import gov.nist.secauto.metaschema.core.model.IBoundObject; import gov.nist.secauto.metaschema.core.model.IMetaschemaData; @@ -33,7 +35,7 @@ public class TestScenario implements IBoundObject { private final IMetaschemaData __metaschemaData; /** - * "The name of this test scenario." + * The name of this test scenario. */ @BoundFlag( formalName = "Name", @@ -43,12 +45,18 @@ public class TestScenario implements IBoundObject { typeAdapter = StringAdapter.class) private String _name; + /** + * Defines schema generation parameters and expected results. + */ @BoundAssembly( formalName = "Generate Schema", description = "Defines schema generation parameters and expected results.", useName = "generate-schema") private GenerateSchema _generateSchema; + /** + * A content validation test case. + */ @BoundAssembly( formalName = "Validation Case", description = "A content validation test case.", @@ -57,10 +65,23 @@ public class TestScenario implements IBoundObject { groupAs = @GroupAs(name = "validation-cases", inJson = JsonGroupAsBehavior.LIST)) private List _validationCases; + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.TestScenario} + * instance with no metadata. + */ public TestScenario() { this(null); } + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.TestScenario} + * instance with the specified metadata. + * + * @param data + * the metaschema data, or {@code null} if none + */ public TestScenario(IMetaschemaData data) { this.__metaschemaData = data; } @@ -70,27 +91,84 @@ public IMetaschemaData getMetaschemaData() { return __metaschemaData; } + /** + * Get the name. + * + *

+ * The name of this test scenario. + * + * @return the name value + */ + @NonNull public String getName() { return _name; } - public void setName(String value) { + /** + * Set the name. + * + *

+ * The name of this test scenario. + * + * @param value + * the name value to set + */ + public void setName(@NonNull String value) { _name = value; } + /** + * Get the generate Schema. + * + *

+ * Defines schema generation parameters and expected results. + * + * @return the generate-schema value, or {@code null} if not set + */ + @Nullable public GenerateSchema getGenerateSchema() { return _generateSchema; } - public void setGenerateSchema(GenerateSchema value) { + /** + * Set the generate Schema. + * + *

+ * Defines schema generation parameters and expected results. + * + * @param value + * the generate-schema value to set + */ + public void setGenerateSchema(@Nullable GenerateSchema value) { _generateSchema = value; } + /** + * Get the validation Case. + * + *

+ * A content validation test case. + * + * @return the validation-case value + */ + @NonNull public List getValidationCases() { + if (_validationCases == null) { + _validationCases = new LinkedList<>(); + } return _validationCases; } - public void setValidationCases(List value) { + /** + * Set the validation Case. + * + *

+ * A content validation test case. + * + * @param value + * the validation-case value to set + */ + public void setValidationCases(@NonNull List value) { _validationCases = value; } diff --git a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/TestSuite.java b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/TestSuite.java index b40c6efb7..cee892b23 100644 --- a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/TestSuite.java +++ b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/TestSuite.java @@ -5,6 +5,7 @@ package gov.nist.secauto.metaschema.model.testing.testsuite; +import edu.umd.cs.findbugs.annotations.NonNull; import gov.nist.secauto.metaschema.core.model.IBoundObject; import gov.nist.secauto.metaschema.core.model.IMetaschemaData; import gov.nist.secauto.metaschema.core.model.JsonGroupAsBehavior; @@ -31,6 +32,9 @@ public class TestSuite implements IBoundObject { private final IMetaschemaData __metaschemaData; + /** + * A collection of test scenarios located at a specific path. + */ @BoundAssembly( formalName = "Test Collection", description = "A collection of test scenarios located at a specific path.", @@ -40,10 +44,23 @@ public class TestSuite implements IBoundObject { groupAs = @GroupAs(name = "test-collections", inJson = JsonGroupAsBehavior.LIST)) private List _testCollections; + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.TestSuite} + * instance with no metadata. + */ public TestSuite() { this(null); } + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.TestSuite} + * instance with the specified metadata. + * + * @param data + * the metaschema data, or {@code null} if none + */ public TestSuite(IMetaschemaData data) { this.__metaschemaData = data; } @@ -53,11 +70,32 @@ public IMetaschemaData getMetaschemaData() { return __metaschemaData; } + /** + * Get the test Collection. + * + *

+ * A collection of test scenarios located at a specific path. + * + * @return the test-collection value + */ + @NonNull public List getTestCollections() { + if (_testCollections == null) { + _testCollections = new LinkedList<>(); + } return _testCollections; } - public void setTestCollections(List value) { + /** + * Set the test Collection. + * + *

+ * A collection of test scenarios located at a specific path. + * + * @param value + * the test-collection value to set + */ + public void setTestCollections(@NonNull List value) { _testCollections = value; } diff --git a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/ValidationCase.java b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/ValidationCase.java index 54a6c729f..81efffebe 100644 --- a/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/ValidationCase.java +++ b/metaschema-testing/src/main/java/gov/nist/secauto/metaschema/model/testing/testsuite/ValidationCase.java @@ -5,6 +5,8 @@ package gov.nist.secauto.metaschema.model.testing.testsuite; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import gov.nist.secauto.metaschema.core.datatype.adapter.TokenAdapter; import gov.nist.secauto.metaschema.core.datatype.adapter.UriReferenceAdapter; import gov.nist.secauto.metaschema.core.model.IBoundObject; @@ -33,7 +35,7 @@ public class ValidationCase implements IBoundObject { private final IMetaschemaData __metaschemaData; /** - * "The format of the source content." + * The format of the source content. */ @BoundFlag( formalName = "Source Format", @@ -47,7 +49,7 @@ public class ValidationCase implements IBoundObject { private String _sourceFormat; /** - * "A URI reference to the content file location." + * A URI reference to the content file location. */ @BoundFlag( formalName = "Location", @@ -58,7 +60,7 @@ public class ValidationCase implements IBoundObject { private URI _location; /** - * "The expected result of content validation." + * The expected result of content validation. */ @BoundFlag( formalName = "Validation Result", @@ -72,10 +74,23 @@ public class ValidationCase implements IBoundObject { description = "Validation resulted in failure caused by some content defect or error.") }))) private String _validationResult; + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.ValidationCase} + * instance with no metadata. + */ public ValidationCase() { this(null); } + /** + * Constructs a new + * {@code gov.nist.secauto.metaschema.model.testing.testsuite.ValidationCase} + * instance with the specified metadata. + * + * @param data + * the metaschema data, or {@code null} if none + */ public ValidationCase(IMetaschemaData data) { this.__metaschemaData = data; } @@ -85,27 +100,81 @@ public IMetaschemaData getMetaschemaData() { return __metaschemaData; } + /** + * Get the source Format. + * + *

+ * The format of the source content. + * + * @return the source-format value, or {@code null} if not set + */ + @Nullable public String getSourceFormat() { return _sourceFormat; } - public void setSourceFormat(String value) { + /** + * Set the source Format. + * + *

+ * The format of the source content. + * + * @param value + * the source-format value to set + */ + public void setSourceFormat(@Nullable String value) { _sourceFormat = value; } + /** + * Get the location. + * + *

+ * A URI reference to the content file location. + * + * @return the location value + */ + @NonNull public URI getLocation() { return _location; } - public void setLocation(URI value) { + /** + * Set the location. + * + *

+ * A URI reference to the content file location. + * + * @param value + * the location value to set + */ + public void setLocation(@NonNull URI value) { _location = value; } + /** + * Get the validation Result. + * + *

+ * The expected result of content validation. + * + * @return the validation-result value, or {@code null} if not set + */ + @Nullable public String getValidationResult() { return _validationResult; } - public void setValidationResult(String value) { + /** + * Set the validation Result. + * + *

+ * The expected result of content validation. + * + * @param value + * the validation-result value to set + */ + public void setValidationResult(@Nullable String value) { _validationResult = value; }