diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/IMarkupString.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/IMarkupString.java index a2d162b56..9e97a1236 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/IMarkupString.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/IMarkupString.java @@ -49,12 +49,27 @@ public interface IMarkupString> extends ICustomJavaDataType { + /** + * Get the underlying Flexmark factory supporting markup serialization. + * + * @return the factory + */ @NonNull FlexmarkFactory getFlexmarkFactory(); + /** + * Get the top-level Flexmark document node for the markup. + * + * @return the node + */ @NonNull Document getDocument(); + /** + * Determine if the markup has no contents. + * + * @return {@code true} if the markup has no contents or {@code false} otherwise + */ boolean isEmpty(); // /** @@ -73,15 +88,49 @@ public interface IMarkupString> // throws // XMLStreamException; + /** + * Get the HyperText Markup Language (HTML) representation of this markup + * string. + * + * @return the HTML + */ @NonNull String toHtml(); + /** + * Get the Extensible HyperText Markup Language (XHTML) representation of this + * markup string. + * + * @param namespace + * the XML namespace to use for XHTML elements + * + * @return the XHTML + * @throws XMLStreamException + * if an error occurred while establishing or writing to the + * underlying XML stream + * @throws IOException + * if an error occurred while generating the XHTML data + */ @NonNull String toXHtml(@NonNull String namespace) throws XMLStreamException, IOException; + /** + * Get the Commonmark Markdown representation of this markup string. + * + * @return the Markdown + */ @NonNull String toMarkdown(); + /** + * Get a Markdown representation of this markup string, which will be created by + * the provided formatter. + * + * @param formatter + * the specific Markdown formatter to use in producing the Markdown + * + * @return the Markdown + */ @NonNull String toMarkdown(@NonNull Formatter formatter); @@ -93,6 +142,11 @@ public interface IMarkupString> @NonNull Stream getNodesAsStream(); + /** + * Get markup inserts used as place holders within the string. + * + * @return a list of insets or an empty list if no inserts are present + */ @NonNull default List getInserts() { return getInserts(insert -> true); @@ -118,10 +172,37 @@ List getInserts( */ boolean isBlock(); + /** + * Write the Extensible HyperText Markup Language (XHTML) representation of this + * markup string to the provided stream writer. + * + * @param namespace + * the XML namespace to use for XHTML elements + * @param streamWriter + * the XML stream to write to + * @throws XMLStreamException + * if an error occurred while establishing or writing to the XML + * stream + */ void writeXHtml( @NonNull String namespace, @NonNull XMLStreamWriter2 streamWriter) throws XMLStreamException; + /** + * Write the Extensible HyperText Markup Language (XHTML) representation of this + * markup string to the provided stream writer using the provided XML event + * factory. + * + * @param namespace + * the XML namespace to use for XHTML elements + * @param eventFactory + * the XML event factory used to generate XML events to write + * @param eventWriter + * the XML event stream to write to + * @throws XMLStreamException + * if an error occurred while establishing or writing to the XML + * stream + */ void writeXHtml( @NonNull String namespace, @NonNull XMLEventFactory2 eventFactory, diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/MarkupLine.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/MarkupLine.java index 196b5f303..800e896b9 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/MarkupLine.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/MarkupLine.java @@ -56,7 +56,7 @@ public final class MarkupLine @SuppressWarnings("null") @NonNull - protected static DataSet newParserOptions() { + private static DataSet newParserOptions() { MutableDataSet options = new MutableDataSet(); // disable inline HTML options.set(Parser.HTML_BLOCK_PARSER, false); @@ -72,12 +72,26 @@ protected static DataSet newParserOptions() { return FlexmarkConfiguration.newFlexmarkConfig(options); } + /** + * Convert the provided HTML string into markup. + * + * @param html + * the HTML + * @return the markup instance + */ @NonNull public static MarkupLine fromHtml(@NonNull String html) { return new MarkupLine( parseHtml(html, FLEXMARK_FACTORY.getFlexmarkHtmlConverter(), FLEXMARK_FACTORY.getMarkdownParser())); } + /** + * Convert the provided markdown string into markup. + * + * @param markdown + * the markup + * @return the markup instance + */ @NonNull public static MarkupLine fromMarkdown(@NonNull String markdown) { return new MarkupLine(parseMarkdown(markdown, FLEXMARK_FACTORY.getMarkdownParser())); @@ -88,6 +102,12 @@ public FlexmarkFactory getFlexmarkFactory() { return FLEXMARK_FACTORY; } + /** + * Construct a new single line markup instance. + * + * @param astNode + * the parsed markup AST + */ protected MarkupLine(@NonNull Document astNode) { super(astNode); Node child = astNode.getFirstChild(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/MarkupMultiline.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/MarkupMultiline.java index 534621a1c..dbcb2236b 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/MarkupMultiline.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/MarkupMultiline.java @@ -43,7 +43,7 @@ public class MarkupMultiline * * @param html * the HTML - * @return the multiline markup instance + * @return the markup instance */ @NonNull public static MarkupMultiline fromHtml(@NonNull String html) { @@ -59,7 +59,7 @@ public static MarkupMultiline fromHtml(@NonNull String html) { * * @param markdown * the markup - * @return the multiline markup instance + * @return the markup instance */ @NonNull public static MarkupMultiline fromMarkdown(@NonNull String markdown) { 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 1ba89bdc9..065daca48 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 @@ -74,6 +74,11 @@ public final class XmlMarkupParser { @NonNull private static final XmlMarkupParser SINGLETON = new XmlMarkupParser(); + /** + * Get the singleton markup parser instance. + * + * @return the instance + */ @SuppressWarnings("PMD.AvoidSynchronizedAtMethodLevel") @NonNull public static synchronized XmlMarkupParser instance() { @@ -84,6 +89,15 @@ private XmlMarkupParser() { // disable construction } + /** + * Parse a single line of markup from XHTML. + * + * @param reader + * the XML event stream reader + * @return the markup string + * @throws XMLStreamException + * if an error occurred while parsing + */ public MarkupLine parseMarkupline(XMLEventReader2 reader) throws XMLStreamException { // NOPMD - acceptable StringBuilder buffer = new StringBuilder(); parseContents(reader, null, buffer); @@ -91,6 +105,15 @@ public MarkupLine parseMarkupline(XMLEventReader2 reader) throws XMLStreamExcept return html.isEmpty() ? null : MarkupLine.fromHtml(html); } + /** + * Parse a markup multiline from XHTML. + * + * @param reader + * the XML event stream reader + * @return the markup string + * @throws XMLStreamException + * if an error occurred while parsing + */ public MarkupMultiline parseMarkupMultiline(XMLEventReader2 reader) throws XMLStreamException { StringBuilder buffer = new StringBuilder(); parseToString(reader, buffer); @@ -102,6 +125,16 @@ public MarkupMultiline parseMarkupMultiline(XMLEventReader2 reader) throws XMLSt return html.isEmpty() ? null : MarkupMultiline.fromHtml(html); } + /** + * Parse a markup multiline from XHTML. + * + * @param reader + * the XML event stream reader + * @param buffer + * the markup string buffer + * @throws XMLStreamException + * if an error occurred while parsing + */ private void parseToString(XMLEventReader2 reader, StringBuilder buffer) // NOPMD - acceptable throws XMLStreamException { // if (LOGGER.isDebugEnabled()) { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/antlr/AbstractAstVisitor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/antlr/AbstractAstVisitor.java index a6719255c..1cd5daa56 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/antlr/AbstractAstVisitor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/antlr/AbstractAstVisitor.java @@ -531,6 +531,7 @@ public R visitEqname(EqnameContext ctx) { * the provided expression context * @return the result */ + @NonNull protected abstract R handleWildcard(@NonNull WildcardContext ctx); @Override diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractCSTVisitorBase.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractCSTVisitorBase.java index 99d4a2009..44f7fc102 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractCSTVisitorBase.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractCSTVisitorBase.java @@ -49,6 +49,9 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +@SuppressWarnings({ + "PMD.CouplingBetweenObjects" +}) public abstract class AbstractCSTVisitorBase extends AbstractAstVisitor { @@ -131,6 +134,27 @@ public IExpression visit(ParseTree tree) { return super.visit(tree); } + /** + * Parse the provided context as an n-ary phrase. + * + * @param + * the Java type of the antlr context to parse + * @param + * the Java type of the child expressions produced by this parser + * @param + * the Java type of the outer expression produced by the parser + * @param context + * the antlr context to parse + * @param startIndex + * the child index to start parsing on + * @param step + * the increment to advance while parsing child expressions + * @param parser + * a binary function used to produce child expressions + * @param supplier + * a function used to produce the other expression + * @return the outer expression or {@code null} if no children exist to parse + */ @Nullable protected R nairyToCollection( diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/BuildCSTVisitor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/BuildCSTVisitor.java index e22bbd382..bb6a305a7 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/BuildCSTVisitor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/BuildCSTVisitor.java @@ -143,6 +143,12 @@ public class BuildCSTVisitor @NonNull private final StaticContext context; + /** + * Construct a new compact syntax tree generating visitor. + * + * @param context + * the static Metapath evaluation context + */ public BuildCSTVisitor(@NonNull StaticContext context) { this.context = context; } @@ -151,6 +157,11 @@ public BuildCSTVisitor(@NonNull StaticContext context) { // Expressions - https://www.w3.org/TR/xpath-31/#id-expressions // ============================================================ + /** + * Get the static Metapath evaluation context. + * + * @return the context + */ @NonNull protected StaticContext getContext() { return context; @@ -201,7 +212,7 @@ protected IExpression handleNumericLiteral(NumericliteralContext ctx) { protected IExpression handleVarref(VarrefContext ctx) { return new VariableReference( EQNameUtils.parseName( - ctx.varname().eqname().getText(), + ObjectUtils.notNull(ctx.varname().eqname().getText()), getContext().getVariablePrefixResolver())); } @@ -230,7 +241,7 @@ protected IExpression handleForexpr(ForexprContext ctx) { assert boundExpression != null; QName qname = EQNameUtils.parseName( - varName.eqname().getText(), + ObjectUtils.notNull(varName.eqname().getText()), getContext().getVariablePrefixResolver()); Let.VariableDeclaration variable = new Let.VariableDeclaration(qname, boundExpression); @@ -259,7 +270,7 @@ protected IExpression handleLet(LetexprContext context) { assert boundExpression != null; QName varName = EQNameUtils.parseName( - simpleCtx.varname().eqname().getText(), + ObjectUtils.notNull(simpleCtx.varname().eqname().getText()), getContext().getVariablePrefixResolver()); retval = new Let(varName, boundExpression, retval); // NOPMD intended @@ -282,7 +293,9 @@ protected MapConstructor handleMapConstructor(MapconstructorContext context) { int pos = (idx - 3) / 2; MapconstructorentryContext entry = ctx.mapconstructorentry(pos); assert entry != null; - return new MapConstructor.Entry(entry.mapkeyexpr().accept(this), entry.mapvalueexpr().accept(this)); + return new MapConstructor.Entry( + ObjectUtils.notNull(entry.mapkeyexpr().accept(this)), + ObjectUtils.notNull(entry.mapvalueexpr().accept(this))); }, children -> { assert children != null; @@ -371,7 +384,7 @@ protected IExpression handleQuantifiedexpr(QuantifiedexprContext ctx) { for (; offset < numVars; offset++) { // $ QName varName = EQNameUtils.parseName( - ctx.varname(offset).eqname().getText(), + ObjectUtils.notNull(ctx.varname(offset).eqname().getText()), getContext().getVariablePrefixResolver()); // in @@ -402,7 +415,7 @@ protected IExpression handleArrowexpr(ArrowexprContext context) { ArgumentlistContext argumentCtx = ctx.getChild(ArgumentlistContext.class, offset); QName name = EQNameUtils.parseName( - fcCtx.eqname().getText(), + ObjectUtils.notNull(fcCtx.eqname().getText()), getContext().getFunctionPrefixResolver()); try (Stream args = Stream.concat( @@ -469,7 +482,7 @@ protected Stream parseArgumentList(@NonNull ArgumentlistContext con @Override protected IExpression handleFunctioncall(FunctioncallContext ctx) { QName qname = EQNameUtils.parseName( - ctx.eqname().getText(), + ObjectUtils.notNull(ctx.eqname().getText()), getContext().getFunctionPrefixResolver()); return new StaticFunctionCall( qname, @@ -535,13 +548,15 @@ protected IExpression handlePostfixexpr(PostfixexprContext context) { 0, 1, (ctx, idx, left) -> { + assert left != null; + ParseTree tree = ctx.getChild(idx); IExpression result; if (tree instanceof ArgumentlistContext) { // map or array access using function call syntax result = new FunctionCallAccessor( left, - parseArgumentList((ArgumentlistContext) tree).findFirst().get()); + ObjectUtils.notNull(parseArgumentList((ArgumentlistContext) tree).findFirst().get())); } else if (tree instanceof PredicateContext) { result = new PredicateExpression( left, @@ -682,13 +697,12 @@ protected IExpression handleForwardstep(ForwardstepContext ctx) { default: throw new UnsupportedOperationException(token.getText()); } - retval = new Step(axis, parseNodeTest(ctx.nodetest(), false)); + retval = new Step(axis, + parseNodeTest(ctx.nodetest(), false)); } else { retval = new Step( Axis.CHILDREN, - parseNodeTest( - ctx.nodetest(), - abbrev.AT() != null)); + parseNodeTest(ctx.nodetest(), abbrev.AT() != null)); } return retval; } @@ -720,18 +734,38 @@ protected IExpression handleReversestep(ReversestepContext ctx) { // Node Tests - https://www.w3.org/TR/xpath-31/#node-tests // ======================================================= + /** + * Parse an antlr node test expression. + * + * @param ctx + * the antrl context + * @param flag + * if the context is within a flag's scope + * @return the resulting expression + */ + @NonNull protected INodeTestExpression parseNodeTest(NodetestContext ctx, boolean flag) { // TODO: implement kind test NametestContext nameTestCtx = ctx.nametest(); return parseNameTest(nameTestCtx, flag); } + /** + * Parse an antlr name test expression. + * + * @param ctx + * the antrl context + * @param flag + * if the context is within a flag's scope + * @return the resulting expression + */ + @NonNull protected INameTestExpression parseNameTest(NametestContext ctx, boolean flag) { ParseTree testType = ObjectUtils.requireNonNull(ctx.getChild(0)); INameTestExpression retval; if (testType instanceof EqnameContext) { QName qname = EQNameUtils.parseName( - ctx.eqname().getText(), + ObjectUtils.notNull(ctx.eqname().getText()), flag ? getContext().getFlagPrefixResolver() : getContext().getModelPrefixResolver()); retval = new NameTest(qname); } else { // wildcard @@ -746,7 +780,7 @@ protected Wildcard handleWildcard(WildcardContext ctx) { if (ctx.STAR() == null) { if (ctx.CS() != null) { // specified prefix, any local-name - String prefix = ctx.NCName().getText(); + String prefix = ObjectUtils.notNull(ctx.NCName().getText()); String namespace = getContext().lookupNamespaceForPrefix(prefix); if (namespace == null) { throw new IllegalStateException(String.format("Prefix '%s' did not map to a namespace.", prefix)); @@ -754,11 +788,11 @@ protected Wildcard handleWildcard(WildcardContext ctx) { matcher = new Wildcard.MatchAnyLocalName(namespace); } else if (ctx.SC() != null) { // any prefix, specified local-name - matcher = new Wildcard.MatchAnyNamespace(ctx.NCName().getText()); + matcher = new Wildcard.MatchAnyNamespace(ObjectUtils.notNull(ctx.NCName().getText())); } else { // specified braced namespace, any local-name String bracedUriLiteral = ctx.BracedURILiteral().getText(); - String namespace = bracedUriLiteral.substring(2, bracedUriLiteral.length() - 1); + String namespace = ObjectUtils.notNull(bracedUriLiteral.substring(2, bracedUriLiteral.length() - 1)); matcher = new Wildcard.MatchAnyLocalName(namespace); } } // star needs no matcher: any prefix, any local-name @@ -1073,9 +1107,11 @@ protected IExpression handleIfexpr(IfexprContext ctx) { @Override protected IExpression handleSimplemapexpr(SimplemapexprContext context) { return handleGroupedNAiry(context, 0, 2, (ctx, idx, left) -> { + assert left != null; + // the next child is "!" assert "!".equals(ctx.getChild(idx).getText()); - IExpression right = ctx.getChild(idx + 1).accept(this); + IExpression right = ObjectUtils.notNull(ctx.getChild(idx + 1).accept(this)); return new SimpleMap(left, right); }); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/Except.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/Except.java index 8070ad825..7ceab5c41 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/Except.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/Except.java @@ -28,6 +28,7 @@ import gov.nist.secauto.metaschema.core.metapath.ISequence; import gov.nist.secauto.metaschema.core.metapath.item.IItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.List; @@ -55,8 +56,8 @@ public Except(@NonNull IExpression left, @NonNull IExpression right) { @Override protected ISequence applyFilterTo(@NonNull ISequence result, @NonNull List items) { - return ISequence.of(result.stream() - .filter(item -> !items.contains(item))); + return ISequence.of(ObjectUtils.notNull(result.stream() + .filter(item -> !items.contains(item)))); } @Override diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/FunctionCallAccessor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/FunctionCallAccessor.java index d019f292a..9d16f88cb 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/FunctionCallAccessor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/FunctionCallAccessor.java @@ -29,6 +29,7 @@ import gov.nist.secauto.metaschema.core.metapath.DynamicContext; import gov.nist.secauto.metaschema.core.metapath.ICollectionValue; import gov.nist.secauto.metaschema.core.metapath.ISequence; +import gov.nist.secauto.metaschema.core.metapath.StaticMetapathException; import gov.nist.secauto.metaschema.core.metapath.function.library.ArrayGet; import gov.nist.secauto.metaschema.core.metapath.function.library.FnData; import gov.nist.secauto.metaschema.core.metapath.function.library.MapGet; @@ -48,9 +49,19 @@ public class FunctionCallAccessor implements IExpression { @NonNull private final IExpression argument; - public FunctionCallAccessor(@NonNull IExpression base, @NonNull IExpression argument) { + /** + * Construct a new functional call accessor. + * + * @param base + * the expression whose result is used as the map or array to perform + * the lookup on + * @param keyOrIndex + * the value to find, which will be the key for a map or the index for + * an array + */ + public FunctionCallAccessor(@NonNull IExpression base, @NonNull IExpression keyOrIndex) { this.base = base; - this.argument = argument; + this.argument = keyOrIndex; } /** @@ -84,14 +95,16 @@ public ISequence accept(DynamicContext dynamicContext, ISequenc ISequence target = getBase().accept(dynamicContext, focus); IItem collection = target.getFirstItem(true); IAnyAtomicItem key = FnData.fnData(getArgument().accept(dynamicContext, focus)).getFirstItem(false); + if (key == null) { + throw new StaticMetapathException(StaticMetapathException.NO_FUNCTION_MATCH, + "No key provided for functional call lookup"); + } - ICollectionValue retval; + ICollectionValue retval = null; if (collection instanceof IArrayItem) { retval = ArrayGet.get((IArrayItem) collection, IIntegerItem.cast(key)); } else if (collection instanceof IMapItem) { retval = MapGet.get((IMapItem) collection, key); - } else { - retval = null; } return retval == null ? ISequence.empty() : retval.asSequence(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/Intersect.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/Intersect.java index 3a51e26d4..10dad12a0 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/Intersect.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/Intersect.java @@ -28,6 +28,7 @@ import gov.nist.secauto.metaschema.core.metapath.ISequence; import gov.nist.secauto.metaschema.core.metapath.item.IItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.List; @@ -56,9 +57,9 @@ public Intersect(@NonNull IExpression left, @NonNull IExpression right) { @Override protected ISequence applyFilterTo(@NonNull ISequence result, @NonNull List items) { - return ISequence.of(result.stream() + return ISequence.of(ObjectUtils.notNull(result.stream() .distinct() - .filter(items::contains)); + .filter(items::contains))); } @Override diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/NameTest.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/NameTest.java index 884d7491f..47676f533 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/NameTest.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/NameTest.java @@ -42,6 +42,7 @@ * expanded QName * name test. */ +@SuppressWarnings("PMD.TestClassWithoutTestCases") public class NameTest implements INameTestExpression { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ISequenceType.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ISequenceType.java index 8c54d5765..7caf92628 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ISequenceType.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ISequenceType.java @@ -54,6 +54,7 @@ public String toSignature() { } }; + @SuppressWarnings("PMD.ShortMethodName") @NonNull static ISequenceType of(@NonNull Class type, @NonNull Occurrence occurrence) { return new SequenceTypeImpl(type, occurrence); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/OperationFunctions.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/OperationFunctions.java index 018a10512..025cf1723 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/OperationFunctions.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/OperationFunctions.java @@ -360,6 +360,17 @@ public static IBooleanItem opBase64BinaryGreaterThan( return IBooleanItem.valueOf(arg1.compareTo(arg2) > 0); } + /** + * Based on XPath 3.1 op:dateTime-less-than. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is an earlier instant in time than + * the second, or {@code false} otherwise + */ @NonNull public static IBooleanItem opDateLessThan( @NonNull IDateItem arg1, @@ -367,6 +378,17 @@ public static IBooleanItem opDateLessThan( return opDateTimeLessThan(IDateTimeItem.cast(arg1), IDateTimeItem.cast(arg2)); } + /** + * Based on XPath 3.1 op:yearMonthDuration-less-than. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is an earlier instant in time than + * the second, or {@code false} otherwise + */ @NonNull public static IBooleanItem opDateTimeLessThan( @NonNull IDateTimeItem arg1, @@ -374,6 +396,17 @@ public static IBooleanItem opDateTimeLessThan( return IBooleanItem.valueOf(arg1.asZonedDateTime().compareTo(arg2.asZonedDateTime()) < 0); } + /** + * Based on XPath 3.1 op:yearMonthDuration-less-than. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is a shorter duration than the + * second, or {@code false} otherwise + */ @NonNull public static IBooleanItem opYearMonthDurationLessThan(@NonNull IYearMonthDurationItem arg1, @NonNull IYearMonthDurationItem arg2) { @@ -384,6 +417,17 @@ public static IBooleanItem opYearMonthDurationLessThan(@NonNull IYearMonthDurati return IBooleanItem.valueOf(p1.toTotalMonths() < p2.toTotalMonths()); } + /** + * Based on XPath 3.1 op:dayTimeDuration-less-than. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is a shorter duration than the + * second, or {@code false} otherwise + */ @NonNull public static IBooleanItem opDayTimeDurationLessThan( @NonNull IDayTimeDurationItem arg1, @@ -391,6 +435,17 @@ public static IBooleanItem opDayTimeDurationLessThan( return IBooleanItem.valueOf(arg1.compareTo(arg2) < 0); } + /** + * Based on XPath 3.1 op:base64Binary-less-than. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is less than the second, or + * {@code false} otherwise + */ @NonNull public static IBooleanItem opBase64BinaryLessThan( @NonNull IBase64BinaryItem arg1, @@ -398,6 +453,16 @@ public static IBooleanItem opBase64BinaryLessThan( return IBooleanItem.valueOf(arg1.compareTo(arg2) < 0); } + /** + * Based on XPath 3.1 op:numeric-add. + * + * @param left + * the first number + * @param right + * the second number + * @return the result of adding the second number to the first number + */ @NonNull public static INumericItem opNumericAdd(@NonNull INumericItem left, @NonNull INumericItem right) { INumericItem retval; @@ -421,6 +486,16 @@ public static INumericItem opNumericAdd(@NonNull INumericItem left, @NonNull INu return retval; } + /** + * Based on XPath 3.1 op:numeric-subtract. + * + * @param left + * the first number + * @param right + * the second number + * @return the result of subtracting the second number from the first number + */ @NonNull public static INumericItem opNumericSubtract(@NonNull INumericItem left, @NonNull INumericItem right) { INumericItem retval; @@ -444,6 +519,16 @@ public static INumericItem opNumericSubtract(@NonNull INumericItem left, @NonNul return retval; } + /** + * Based on XPath 3.1 op:numeric-multiply. + * + * @param left + * the first number + * @param right + * the second number + * @return the result of multiplying the first number by the second number + */ @NonNull public static INumericItem opNumericMultiply(@NonNull INumericItem left, @NonNull INumericItem right) { INumericItem retval; @@ -464,6 +549,16 @@ public static INumericItem opNumericMultiply(@NonNull INumericItem left, @NonNul return retval; } + /** + * Based on XPath 3.1 op:numeric-divide. + * + * @param dividend + * the number to be divided + * @param divisor + * the number to divide by + * @return the quotient + */ @NonNull public static IDecimalItem opNumericDivide(@NonNull INumericItem dividend, @NonNull INumericItem divisor) { // create a decimal result @@ -481,6 +576,16 @@ public static IDecimalItem opNumericDivide(@NonNull INumericItem dividend, @NonN return IDecimalItem.valueOf(result); } + /** + * Based on XPath 3.1 op:numeric-integer-divide. + * + * @param dividend + * the number to be divided + * @param divisor + * the number to divide by + * @return the quotient + */ @NonNull public static IIntegerItem opNumericIntegerDivide(@NonNull INumericItem dividend, @NonNull INumericItem divisor) { IIntegerItem retval; @@ -517,7 +622,7 @@ public static IIntegerItem opNumericIntegerDivide(@NonNull INumericItem dividend /** * Based on XPath 3.1 func:numeric-mod. + * "https://www.w3.org/TR/xpath-functions-31/#func-numeric-mod">op:numeric-mod. * * @param dividend * the number to be divided @@ -547,6 +652,14 @@ public static INumericItem opNumericMod(@NonNull INumericItem dividend, @NonNull return retval; } + /** + * Based on XPath 3.1 op:numeric-unary-minus. + * + * @param item + * the number whose sign is to be reversed + * @return the number with a reversed sign + */ @NonNull public static INumericItem opNumericUnaryMinus(@NonNull INumericItem item) { INumericItem retval; @@ -570,6 +683,17 @@ public static INumericItem opNumericUnaryMinus(@NonNull INumericItem item) { return retval; } + /** + * Based on XPath 3.1 op:numeric-equal. + * + * @param arg1 + * the first number to check for equality + * @param arg2 + * the second number to check for equality + * @return {@code true} if the numbers are numerically equal or {@code false} + * otherwise + */ @NonNull public static IBooleanItem opNumericEqual(@Nullable INumericItem arg1, @Nullable INumericItem arg2) { IBooleanItem retval; @@ -583,6 +707,17 @@ public static IBooleanItem opNumericEqual(@Nullable INumericItem arg1, @Nullable return retval; } + /** + * Based on XPath 3.1 op:numeric-greater-than. + * + * @param arg1 + * the first number to check + * @param arg2 + * the second number to check + * @return {@code true} if the first number is greater than or equal to the + * second, or {@code false} otherwise + */ @NonNull public static IBooleanItem opNumericGreaterThan(@Nullable INumericItem arg1, @Nullable INumericItem arg2) { IBooleanItem retval; @@ -598,6 +733,17 @@ public static IBooleanItem opNumericGreaterThan(@Nullable INumericItem arg1, @Nu return retval; } + /** + * Based on XPath 3.1 op:numeric-less-than. + * + * @param arg1 + * the first number to check + * @param arg2 + * the second number to check + * @return {@code true} if the first number is less than or equal to the second, + * or {@code false} otherwise + */ @NonNull public static IBooleanItem opNumericLessThan(@Nullable INumericItem arg1, @Nullable INumericItem arg2) { IBooleanItem retval; @@ -613,6 +759,17 @@ public static IBooleanItem opNumericLessThan(@Nullable INumericItem arg1, @Nulla return retval; } + /** + * Based on XPath 3.1 op:boolean-equal. + * + * @param arg1 + * the first boolean to check + * @param arg2 + * the second boolean to check + * @return {@code true} if the first boolean is equal to the second, or + * {@code false} otherwise + */ @NonNull public static IBooleanItem opBooleanEqual(@Nullable IBooleanItem arg1, @Nullable IBooleanItem arg2) { boolean left = arg1 != null && arg1.toBoolean(); @@ -621,6 +778,17 @@ public static IBooleanItem opBooleanEqual(@Nullable IBooleanItem arg1, @Nullable return IBooleanItem.valueOf(left == right); } + /** + * Based on XPath 3.1 op:boolean-greater-than. + * + * @param arg1 + * the first boolean to check + * @param arg2 + * the second boolean to check + * @return {@code true} if the first argument is {@link IBooleanItem#TRUE} and + * the second is {@link IBooleanItem#FALSE}, or {@code false} otherwise + */ @NonNull public static IBooleanItem opBooleanGreaterThan(@Nullable IBooleanItem arg1, @Nullable IBooleanItem arg2) { boolean left = arg1 != null && arg1.toBoolean(); @@ -629,6 +797,17 @@ public static IBooleanItem opBooleanGreaterThan(@Nullable IBooleanItem arg1, @Nu return IBooleanItem.valueOf(left && !right); } + /** + * Based on XPath 3.1 op:boolean-less-than. + * + * @param arg1 + * the first boolean to check + * @param arg2 + * the second boolean to check + * @return {@code true} if the first argument is {@link IBooleanItem#FALSE} and + * the second is {@link IBooleanItem#TRUE}, or {@code false} otherwise + */ @NonNull public static IBooleanItem opBooleanLessThan(@Nullable IBooleanItem arg1, @Nullable IBooleanItem arg2) { boolean left = arg1 != null && arg1.toBoolean(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/SequenceTypeImpl.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/SequenceTypeImpl.java index ece58474a..d4dfca126 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/SequenceTypeImpl.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/SequenceTypeImpl.java @@ -28,6 +28,7 @@ import gov.nist.secauto.metaschema.core.metapath.item.IItem; import gov.nist.secauto.metaschema.core.metapath.item.TypeSystem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.Objects; @@ -76,7 +77,7 @@ public String toSignature() { // occurrence .append(getOccurrence().getIndicator()); - return builder.toString(); + return ObjectUtils.notNull(builder.toString()); } @Override diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayFlatten.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayFlatten.java index ca0059262..39f0f77c5 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayFlatten.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayFlatten.java @@ -77,17 +77,25 @@ private static ISequence execute(@NonNull IFunction function, * An implementation of XPath 3.1 array:flatten. * - * @param input + * @param items * the items to flatten - * @return the flattened items + * @return the stream of flattened items */ @SuppressWarnings("null") @NonNull - public static Stream flatten(@NonNull List input) { - return input.stream() + public static Stream flatten(@NonNull List items) { + return items.stream() .flatMap(ArrayFlatten::flatten); } + /** + * An implementation of XPath 3.1 array:flatten. + * + * @param item + * the item to flatten + * @return the stream of flattened items + */ @SuppressWarnings("null") @NonNull public static Stream flatten(@NonNull IItem item) { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayGet.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayGet.java index 8aa5d49be..b03ccaeb5 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayGet.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayGet.java @@ -103,6 +103,20 @@ public static T get( return get(target, positionItem.asInteger().intValue()); } + /** + * An implementation of XPath 3.1 array:get. + * + * @param + * the type of items in the given Metapath array + * @param target + * the array of Metapath items that is the target of retrieval + * @param position + * the integer position of the item to retrieve + * @return the retrieved item + * @throws ArrayException + * if the position is not in the range of 1 to array:size + */ @NonNull public static T get( @NonNull List target, diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayInsertBefore.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayInsertBefore.java index 3d7807396..94568f1e1 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayInsertBefore.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayInsertBefore.java @@ -113,8 +113,26 @@ public static IArrayItem insertBefore( return insertBefore(array, positionItem.asInteger().intValueExact(), member); } + /** + * An implementation of XPath 3.1 array:insert-before. + * + * @param + * the type of items in the given Metapath array + * @param array + * the target Metapath array + * @param position + * the integer position of the item to insert before + * @param member + * the Metapath item to insert into the identified array + * @return a new array containing the modification + * @throws ArrayException + * if the position is not in the range of 1 to array:size + */ @NonNull - public static IArrayItem insertBefore(@NonNull IArrayItem array, int position, + public static IArrayItem insertBefore( + @NonNull IArrayItem array, + int position, @NonNull T member) { return ArrayJoin.join(ObjectUtils.notNull(List.of( ArraySubarray.subarray(array, 1, position - 1), diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayPut.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayPut.java index e84f7cc93..6777a14d2 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayPut.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayPut.java @@ -114,8 +114,26 @@ public static IArrayItem put( return put(array, positionItem.asInteger().intValueExact(), member); } + /** + * An implementation of XPath 3.1 array:put. + * + * @param + * the type of items in the given Metapath array + * @param array + * the target Metapath array + * @param position + * the integer position of the item to replace + * @param member + * the Metapath item to replace the identified array member with + * @return a new array containing the modification + * @throws ArrayException + * if the position is not in the range of 1 to array:size + */ @NonNull - public static IArrayItem put(@NonNull IArrayItem array, int position, + public static IArrayItem put( + @NonNull IArrayItem array, + int position, @NonNull T member) { List copy = new ArrayList<>(array); try { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayRemove.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayRemove.java index 64a1a2019..d3791f3a7 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayRemove.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayRemove.java @@ -112,6 +112,20 @@ public static IArrayItem removeItems( .collect(Collectors.toSet()))); } + /** + * An implementation of XPath 3.1 array:remove. + * + * @param + * the type of items in the given Metapath array + * @param array + * the target Metapath array + * @param positions + * the integer position of the items to remove + * @return a new array containing the modification + * @throws ArrayException + * if the position is not in the range of 1 to array:size + */ @NonNull public static IArrayItem remove( @NonNull IArrayItem array, diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArraySubarray.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArraySubarray.java index f0a4ab406..90d830535 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArraySubarray.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArraySubarray.java @@ -134,7 +134,7 @@ private static ISequence> executeThre * the target Metapath array * @param startItem * the integer position of the item to start with (inclusive) - * @return a new array consisting of the items in the identified range + * @return a new array item consisting of the items in the identified range * @throws ArrayException * if the position is not in the range of 1 to array:size */ @@ -159,7 +159,7 @@ public static IArrayItem subarray( * @param lengthItem * the integer count of items to include starting with the item at the * start position - * @return a new array consisting of the items in the identified range + * @return a new array item consisting of the items in the identified range * @throws ArrayException * if the length is negative or the position is not in the range of 1 * to array:size @@ -173,13 +173,50 @@ public static IArrayItem subarray( return subarray(array, startItem.asInteger().intValueExact(), lengthItem.asInteger().intValueExact()); } + /** + * An implementation of XPath 3.1 array:subarray. + * + * @param + * the type of items in the given Metapath array + * @param array + * the target Metapath array + * @param start + * the integer position of the item to start with (inclusive) + * @return a new array item consisting of the items in the identified range + * @throws ArrayException + * if the length is negative or the position is not in the range of 1 + * to array:size + */ @NonNull - public static IArrayItem subarray(@NonNull IArrayItem array, int start) { + public static IArrayItem subarray( + @NonNull IArrayItem array, + int start) { return subarray(array, start, array.size() - start + 1); } + /** + * An implementation of XPath 3.1 array:subarray. + * + * @param + * the type of items in the given Metapath array + * @param array + * the target Metapath array + * @param start + * the integer position of the item to start with (inclusive) + * @param length + * the integer count of items to include starting with the item at the + * start position + * @return a new array item consisting of the items in the identified range + * @throws ArrayException + * if the length is negative or the position is not in the range of 1 + * to array:size + */ @NonNull - public static IArrayItem subarray(@NonNull IArrayItem array, int start, + public static IArrayItem subarray( + @NonNull IArrayItem array, + int start, int length) { if (length < 0) { throw new ArrayException( diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnAvg.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnAvg.java index a392a53de..b339475ac 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnAvg.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnAvg.java @@ -172,6 +172,7 @@ public static IAnyAtomicItem average(@NonNull Collection R average( @NonNull Collection items, diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapFind.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapFind.java index 2b1bd6b22..b5a4b39c7 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapFind.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapFind.java @@ -91,7 +91,7 @@ private static ISequence execute(@NonNull IFunction function, * An implementation of XPath 3.1 map:find. * - * @param input + * @param items * the item sequence to search for key matches * @param key * the key for the item to retrieve @@ -99,13 +99,23 @@ private static ISequence execute(@NonNull IFunction function, */ @NonNull public static Stream find( - @NonNull Collection input, + @NonNull Collection items, @NonNull IAnyAtomicItem key) { - return ObjectUtils.notNull(input.stream() + return ObjectUtils.notNull(items.stream() // handle item .flatMap(item -> find(ObjectUtils.notNull(item), key))); } + /** + * An implementation of XPath 3.1 map:find. + * + * @param item + * the item to search for key matches + * @param key + * the key for the item to retrieve + * @return the retrieved item + */ @NonNull public static Stream find( @NonNull IItem item, @@ -143,14 +153,27 @@ private static Stream find( return retval; } + /** + * An implementation of XPath 3.1 map:find. + *

+ * This is a specialized method for processing an item that is a map item, which + * can be searched for a key in a much more efficient way. + * + * @param item + * the item to search for key matches + * @param key + * the key for the item to retrieve + * @return the retrieved item + */ @NonNull public static Stream find( - @NonNull IMapItem map, + @NonNull IMapItem item, @NonNull IAnyAtomicItem key) { return ObjectUtils.notNull(Stream.concat( // add matching value, if it exists - Stream.ofNullable(MapGet.get(map, key)), - map.values().stream() + Stream.ofNullable(MapGet.get(item, key)), + item.values().stream() // handle map values .flatMap(value -> find(ObjectUtils.notNull(value), key)))); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapKeys.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapKeys.java index deef6fc93..c64b55794 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapKeys.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapKeys.java @@ -66,14 +66,6 @@ private MapKeys() { // disable construction } - /** - * An implementation of XPath 3.1 map:size. - * - * @param array - * the arrays to join - * @return a new combined array - */ @SuppressWarnings("unused") @NonNull private static ISequence execute(@NonNull IFunction function, @@ -85,10 +77,16 @@ private static ISequence execute(@NonNull IFunction function, return ISequence.of(keys(map)); } - public static Stream keys(@NonNull IMapItem map) { - return keys((Map) map); - } - + /** + * An implementation of XPath 3.1 map:keys. + * + * @param map + * the map to get the keys for + * @return a stream of map keys + */ + @SuppressWarnings("null") + @NonNull public static Stream keys(@NonNull Map map) { return map.keySet().stream() .map(IMapKey::getKey); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapMerge.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapMerge.java index d5b030682..8b6a9dd76 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapMerge.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapMerge.java @@ -107,10 +107,12 @@ private enum Duplicates { USE_FIRST("use-first", (key, v1, v2) -> v1), USE_LAST("use-last", (key, v1, v2) -> v2), USE_ANY("use-any", (key, v1, v2) -> RANDOM.nextBoolean() ? v1 : v2), - COMBINE( - "combine", - (key, v1, v2) -> Stream.concat(v1.asSequence().stream(), v2.asSequence().stream()) - .collect(ISequence.toSequence())); + @SuppressWarnings("null") + COMBINE("combine", (key, v1, v2) -> { + return Stream.concat( + v1.asSequence().stream(), + v2.asSequence().stream()).collect(ISequence.toSequence()); + }); private static final Map BY_NAME; @@ -120,7 +122,7 @@ private enum Duplicates { private final CustomCollectors.DuplicateHandler duplicateHander; static { - Map map = new HashMap<>(); + @SuppressWarnings("PMD.UseConcurrentHashMap") Map map = new HashMap<>(); for (Duplicates value : values()) { map.put(value.getName(), value); } @@ -176,7 +178,7 @@ private static ISequence executeTwoArg(@NonNull IFunction function, /** * An implementation of XPath 3.1 array:flatten. + * "https://www.w3.org/TR/xpath-functions-31/#func-map-merge">map:merge. * * @param maps * a collection of maps to merge @@ -184,7 +186,7 @@ private static ISequence executeTwoArg(@NonNull IFunction function, * settings that affect the merge behavior * @return a map containing the merged entries */ - @SuppressWarnings("null") + @SuppressWarnings({ "null", "PMD.OnlyOneReturn" }) @NonNull public static IMapItem merge(@NonNull Collection> maps, @NonNull Map options) { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapPut.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapPut.java index 84013f091..a621d6cec 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapPut.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/MapPut.java @@ -109,7 +109,7 @@ public static IMapItem put( @NonNull IMapItem map, @NonNull IAnyAtomicItem key, @NonNull V value) { - Map copy = new HashMap<>(map); + @SuppressWarnings("PMD.UseConcurrentHashMap") Map copy = new HashMap<>(map); copy.put(key.asMapKey(), value); return IMapItem.ofCollection(copy); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractArrayItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractArrayItem.java index dd6dd1b64..ee02e2a45 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractArrayItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractArrayItem.java @@ -48,7 +48,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; public abstract class AbstractArrayItem - extends ImmutableCollections.AbstractImmutableDelegatedCollection + extends ImmutableCollections.AbstractImmutableDelegatedList implements IArrayItem { @NonNull public static final QName QNAME = new QName("array"); @@ -65,6 +65,13 @@ public abstract class AbstractArrayItem @NonNull private static final IArrayItem EMPTY = new ArrayItemN<>(); + /** + * Get an immutable array item that is empty. + * + * @param + * the item Java type + * @return the empty array item + */ @SuppressWarnings("unchecked") @NonNull public static IArrayItem empty() { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractMapItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractMapItem.java index 41ab230cd..5177e8704 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractMapItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractMapItem.java @@ -66,6 +66,14 @@ public abstract class AbstractMapItem @NonNull private static final IMapItem EMPTY = new MapItemN<>(); + /** + * Get an immutable map item that is empty. + * + * @param + * the item Java type + * @return the empty map item + */ + @SuppressWarnings("unchecked") @NonNull public static IMapItem empty() { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractSequence.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractSequence.java index 2d2a42783..e091098d9 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractSequence.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractSequence.java @@ -35,13 +35,19 @@ import edu.umd.cs.findbugs.annotations.NonNull; public abstract class AbstractSequence - extends ImmutableCollections.AbstractImmutableDelegatedCollection + extends ImmutableCollections.AbstractImmutableDelegatedList implements ISequence { - @SuppressWarnings({ "rawtypes", "unchecked" }) @NonNull private static final ISequence EMPTY = new SequenceN<>(); + /** + * Get an immutable sequence that is empty. + * + * @param + * the item Java type + * @return the empty sequence + */ @SuppressWarnings("unchecked") public static ISequence empty() { return (ISequence) EMPTY; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/ArrayItemN.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/ArrayItemN.java index a700c5669..15142b6a4 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/ArrayItemN.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/ArrayItemN.java @@ -34,16 +34,34 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * An array item that supports an unbounded number of items. + * + * @param + * the Java type of the items + */ public class ArrayItemN extends AbstractArrayItem { @NonNull private final List items; + /** + * Construct a new array item with the provided items. + * + * @param items + * the items to add to the array + */ @SafeVarargs public ArrayItemN(@NonNull ITEM... items) { this(ObjectUtils.notNull(List.of(items))); } + /** + * Construct a new array item using the items from the provided list. + * + * @param items + * a list containing the items to add to the array + */ public ArrayItemN(@NonNull List items) { this.items = CollectionUtil.unmodifiableList(items); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/ImmutableCollections.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/ImmutableCollections.java index b56173120..8d9cc5e52 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/ImmutableCollections.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/ImmutableCollections.java @@ -48,6 +48,7 @@ * This implementation is inspired by the similar implementation provided by the * JDK. */ +@SuppressWarnings("PMD.MissingStaticMethodInNonInstantiatableClass") public final class ImmutableCollections { private ImmutableCollections() { @@ -58,6 +59,12 @@ private static UnsupportedOperationException unsupported() { return new UnsupportedOperationException("method not supported"); } + /** + * A base class for an immutable collection. + * + * @param + * the item Java type + */ public abstract static class AbstractImmutableCollection extends AbstractCollection { @@ -97,6 +104,12 @@ public final boolean retainAll(Collection collection) { } } + /** + * A base class for an immutable list. + * + * @param + * the item Java type + */ public abstract static class AbstractImmutableList extends AbstractImmutableCollection implements List { @@ -122,11 +135,22 @@ public T remove(int index) { } } - public abstract static class AbstractImmutableDelegatedCollection + /** + * A base class for an immutable list that wraps a list. + * + * @param + * the item Java type + */ + public abstract static class AbstractImmutableDelegatedList extends AbstractImmutableList { + /** + * Get the wrapped list. + * + * @return the list + */ @NonNull - public abstract List getValue(); + protected abstract List getValue(); @Override public T get(int index) { @@ -179,6 +203,14 @@ public String toString() { } } + /** + * A base class for an immutable map. + * + * @param + * the map key Java type + * @param + * the map value Java type + */ public abstract static class AbstractImmutableMap extends AbstractMap { @Override @@ -247,11 +279,24 @@ public void replaceAll(BiFunction function) { } } + /** + * A base class for an immutable map that wraps a map. + * + * @param + * the map key Java type + * @param + * the map value Java type + */ public abstract static class AbstractImmutableDelegatedMap extends AbstractImmutableMap { + /** + * Get the wrapped map. + * + * @return the map + */ @NonNull - public abstract Map getValue(); + protected abstract Map getValue(); @Override public Set> entrySet() { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/MapItemN.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/MapItemN.java index 0085a25b6..19879d5a7 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/MapItemN.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/MapItemN.java @@ -35,18 +35,35 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * An map item that supports an unbounded number of entries. + * + * @param + * the Java type of the entry values + */ public class MapItemN extends AbstractMapItem { @NonNull private final Map entries; + /** + * Construct a new map item with the provided entries. + * + * @param entries + * the entries to add to the map + */ @SafeVarargs public MapItemN(@NonNull Map.Entry... entries) { this(ObjectUtils.notNull(Map.ofEntries(entries))); } + /** + * Construct a new map item using the entries from the provided map. + * + * @param entries + * a map containing the entries to add to the map + */ public MapItemN(@NonNull Map entries) { - this.entries = CollectionUtil.unmodifiableMap(entries); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/SequenceN.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/SequenceN.java index 618c0a50b..cf557cfcb 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/SequenceN.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/SequenceN.java @@ -36,31 +36,63 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A Metapath sequence supporting an unbounded number of items. + * + * @param + * the Java type of the items + */ public class SequenceN extends AbstractSequence { @NonNull private final List items; - public SequenceN(@NonNull Collection items) { - this(new ArrayList<>(items), false); - } - + /** + * Construct a new sequence with the provided items. + * + * @param items + * a collection containing the items to add to the sequence + * @param copy + * if {@code true} make a defensive copy of the list or {@code false} + * otherwise + */ public SequenceN(@NonNull List items, boolean copy) { this.items = CollectionUtil.unmodifiableList(copy ? new ArrayList<>(items) : items); } + /** + * Construct a new sequence with the provided items. + * + * @param items + * the items to add to the sequence + */ @SafeVarargs public SequenceN(@NonNull ITEM... items) { - this(CollectionUtil.unmodifiableList(ObjectUtils.notNull(List.of(items)))); + this(ObjectUtils.notNull(List.of(items)), false); } + /** + * Construct a new sequence with the provided items. + * + * @param items + * a collection containing the items to add to the sequence + */ + public SequenceN(@NonNull Collection items) { + this(new ArrayList<>(items), false); + } + + /** + * Construct a new sequence with the provided items. + * + * @param items + * a list containing the items to add to the sequence + */ public SequenceN(@NonNull List items) { - this.items = items; + this(items, false); } @Override public List getValue() { return items; } - } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/SingletonSequence.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/SingletonSequence.java index 4c4356188..8d0e046f4 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/SingletonSequence.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/SingletonSequence.java @@ -35,21 +35,27 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A Metapath sequence supporting a singleton item. + * + * @param + * the Java type of the items + */ public class SingletonSequence extends AbstractSequence { @NonNull private final ITEM item; + /** + * Construct a new sequence with the provided item. + * + * @param item + * the item to add to the sequence + */ public SingletonSequence(@NonNull ITEM item) { this.item = item; } - @NonNull - protected ITEM getItem() { - return item; - } - - @SuppressWarnings("null") @Override public List getValue() { return CollectionUtil.singletonList(item); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/StreamSequence.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/StreamSequence.java index d4c840bfd..3612b4a90 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/StreamSequence.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/StreamSequence.java @@ -37,12 +37,25 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A Metapath sequence supporting an unbounded number of items backed initially + * by a stream. + * + * @param + * the Java type of the items + */ public class StreamSequence extends AbstractSequence { private Stream stream; private List list; + /** + * Construct a new sequence using the provided item stream. + * + * @param stream + * the items to add to the sequence + */ public StreamSequence(@NonNull Stream stream) { Objects.requireNonNull(stream, "stream"); this.stream = stream; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/TypeSystem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/TypeSystem.java index c78c39b56..9352b7a0d 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/TypeSystem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/TypeSystem.java @@ -60,6 +60,7 @@ import gov.nist.secauto.metaschema.core.metapath.item.node.IFieldNodeItem; import gov.nist.secauto.metaschema.core.metapath.item.node.IFlagNodeItem; import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.Arrays; import java.util.Collections; @@ -70,7 +71,8 @@ import edu.umd.cs.findbugs.annotations.NonNull; -public class TypeSystem { +@SuppressWarnings("removal") +public final class TypeSystem { private static final Map, QName> ITEM_CLASS_TO_QNAME_MAP; static { @@ -133,9 +135,16 @@ private static Stream> getItemInterfaces(@NonNull Class clazz) { Class itemClass = getItemInterfaces(clazz).findFirst().orElse(null); @@ -147,4 +156,8 @@ private static String asPrefixedName(@NonNull QName qname) { String prefix = StaticContext.getWellKnownPrefixForUri(qname.getNamespaceURI()); return prefix == null ? qname.toString() : prefix + ":" + qname.getLocalPart(); } + + private TypeSystem() { + // disable construction + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractDecimalItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractDecimalItem.java index 8ba4daf57..8b5f2933e 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractDecimalItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractDecimalItem.java @@ -34,6 +34,12 @@ public abstract class AbstractDecimalItem extends AbstractAnyAtomicItem implements IDecimalItem { + /** + * Construct a new item with the provided {@code value}. + * + * @param value + * the value to wrap + */ protected AbstractDecimalItem(@NonNull TYPE value) { super(value); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractTemporalItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractTemporalItem.java index e36c1d75c..fb1355705 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractTemporalItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractTemporalItem.java @@ -30,10 +30,22 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for temporal items. + * + * @param + * the Java type of the wrapped value + */ public abstract class AbstractTemporalItem extends AbstractAnyAtomicItem implements ITemporalItem { + /** + * Construct a new temporal item. + * + * @param value + * the wrapped value + */ protected AbstractTemporalItem(@NonNull TYPE value) { super(value); } @@ -56,6 +68,7 @@ public int hashCode() { return getKey().hashCode(); } + @SuppressWarnings("PMD.OnlyOneReturn") @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractUntypedAtomicItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractUntypedAtomicItem.java index 4435fd7a6..3567c69b6 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractUntypedAtomicItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractUntypedAtomicItem.java @@ -31,10 +31,22 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for untyped atomic items. + * + * @param + * the Java type of the wrapped value + */ public abstract class AbstractUntypedAtomicItem extends AbstractAnyAtomicItem implements IUntypedAtomicItem { + /** + * Construct a new untyped atomic valued item. + * + * @param value + * the value + */ protected AbstractUntypedAtomicItem(@NonNull TYPE value) { super(value); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IAnyAtomicItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IAnyAtomicItem.java index c905e4e90..69ef9e9dc 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IAnyAtomicItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IAnyAtomicItem.java @@ -28,6 +28,7 @@ import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter; import gov.nist.secauto.metaschema.core.metapath.IPrintable; +import gov.nist.secauto.metaschema.core.metapath.item.function.IMapItem; import gov.nist.secauto.metaschema.core.metapath.item.function.IMapKey; import gov.nist.secauto.metaschema.core.util.ObjectUtils; @@ -75,6 +76,11 @@ default IAnyAtomicItem toAtomicItem() { @NonNull String asString(); + /** + * Get the atomic item value as a map key for use with an {@link IMapItem}. + * + * @return the map key + */ @NonNull IMapKey asMapKey(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/ITemporalItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/ITemporalItem.java index a93b308b6..28b73a881 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/ITemporalItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/ITemporalItem.java @@ -27,5 +27,11 @@ package gov.nist.secauto.metaschema.core.metapath.item.atomic; public interface ITemporalItem extends IAnyAtomicItem { + /** + * Determine if the temporal item has a timezone. + * + * @return {@code true} if the temporal item has a timezone or {@code false} + * otherwise + */ boolean hasTimezone(); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/IMapItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/IMapItem.java index b1b2a69ba..5cac8bf44 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/IMapItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/IMapItem.java @@ -162,6 +162,7 @@ static IMapItem ofCollection( // NOPMD - intenti * the value type * @return an empty {@code IMapItem} */ + @SuppressWarnings("PMD.ShortMethodName") @NonNull static IMapItem of() { return AbstractMapItem.empty(); @@ -182,6 +183,7 @@ static IMapItem of() { * @throws NullPointerException * if the key or the value is {@code null} */ + @SuppressWarnings("PMD.ShortMethodName") @NonNull static IMapItem of(@NonNull K k1, @NonNull V v1) { return new MapItemN<>(entry(k1, v1)); @@ -208,6 +210,7 @@ static IMapItem of(@No * @throws NullPointerException * if any key or value is {@code null} */ + @SuppressWarnings("PMD.ShortMethodName") @NonNull static IMapItem of( @NonNull K k1, @NonNull V v1, @@ -242,6 +245,7 @@ static IMapItem of( * @throws NullPointerException * if any key or value is {@code null} */ + @SuppressWarnings("PMD.ShortMethodName") @NonNull static IMapItem of( @@ -283,6 +287,7 @@ IMapItem of( * @throws NullPointerException * if any key or value is {@code null} */ + @SuppressWarnings("PMD.ShortMethodName") @NonNull static IMapItem of( @@ -330,6 +335,7 @@ IMapItem of( * @throws NullPointerException * if any key or value is {@code null} */ + @SuppressWarnings("PMD.ShortMethodName") @NonNull static IMapItem of( @@ -383,7 +389,10 @@ IMapItem of( * @throws NullPointerException * if any key or value is {@code null} */ - @SuppressWarnings("PMD.ExcessiveParameterList") + @SuppressWarnings({ + "PMD.ExcessiveParameterList", + "PMD.ShortMethodName" + }) @NonNull static IMapItem of( @@ -443,7 +452,10 @@ IMapItem of( * @throws NullPointerException * if any key or value is {@code null} */ - @SuppressWarnings("PMD.ExcessiveParameterList") + @SuppressWarnings({ + "PMD.ExcessiveParameterList", + "PMD.ShortMethodName" + }) @NonNull static IMapItem of( @NonNull K k1, @NonNull V v1, @@ -509,7 +521,10 @@ static IMapItem of( * @throws NullPointerException * if any key or value is {@code null} */ - @SuppressWarnings("PMD.ExcessiveParameterList") + @SuppressWarnings({ + "PMD.ExcessiveParameterList", + "PMD.ShortMethodName" + }) @NonNull static IMapItem of( @@ -581,7 +596,10 @@ IMapItem of( * @throws NullPointerException * if any key or value is {@code null} */ - @SuppressWarnings("PMD.ExcessiveParameterList") + @SuppressWarnings({ + "PMD.ExcessiveParameterList", + "PMD.ShortMethodName" + }) @NonNull static IMapItem of( @@ -651,7 +669,10 @@ IMapItem of( * @throws NullPointerException * if any key or value is {@code null} */ - @SuppressWarnings("PMD.ExcessiveParameterList") + @SuppressWarnings({ + "PMD.ExcessiveParameterList", + "PMD.ShortMethodName" + }) @NonNull static IMapItem of( @NonNull K k1, @NonNull V v1, diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractDefinition.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractRecursionPreventingNodeItemVisitor.java similarity index 62% rename from core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractDefinition.java rename to core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractRecursionPreventingNodeItemVisitor.java index 4d974917f..a3d5bd0af 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractDefinition.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractRecursionPreventingNodeItemVisitor.java @@ -24,42 +24,31 @@ * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. */ -package gov.nist.secauto.metaschema.core.model; +package gov.nist.secauto.metaschema.core.metapath.item.node; -import gov.nist.secauto.metaschema.core.util.ObjectUtils; +import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition; -import javax.xml.namespace.QName; +public abstract class AbstractRecursionPreventingNodeItemVisitor + extends AbstractNodeItemVisitor { -import edu.umd.cs.findbugs.annotations.NonNull; -import nl.talsmasoftware.lazy4j.Lazy; - -public abstract class AbstractDefinition - implements IDefinition { - @NonNull - private final Lazy qname; - @NonNull - private final Lazy definitionQName; - - protected AbstractDefinition(@NonNull NameInitializer initializer) { - this.qname = ObjectUtils.notNull(Lazy.lazy(() -> initializer.apply(getEffectiveName()))); - this.definitionQName = ObjectUtils.notNull(Lazy.lazy(() -> initializer.apply(getName()))); - } - - @SuppressWarnings("null") - @Override - public final QName getXmlQName() { - return qname.get(); - } - - @SuppressWarnings("null") @Override - public final QName getDefinitionQName() { - return definitionQName.get(); + public RESULT visitAssembly(IAssemblyNodeItem item, CONTEXT context) { + // only walk new records to avoid looping + // check if this item's definition is the same as an ancestor's + return isDecendant(item, item.getDefinition()) + ? defaultResult() + : super.visitAssembly(item, context); } - @FunctionalInterface - public interface NameInitializer { - @NonNull - QName apply(@NonNull String name); + protected boolean isDecendant(IAssemblyNodeItem child, IAssemblyDefinition parentDefinition) { + return child.ancestor() + .map(ancestor -> { + boolean retval = false; + if (ancestor instanceof IAssemblyNodeItem) { + IAssemblyDefinition ancestorDef = ((IAssemblyNodeItem) ancestor).getDefinition(); + retval = ancestorDef.equals(parentDefinition); + } + return retval; + }).anyMatch(value -> value); } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractAssemblyInstance.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractAssemblyInstance.java index 640761284..bee60f49d 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractAssemblyInstance.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractAssemblyInstance.java @@ -30,6 +30,19 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * This abstract class enforces the data contract of a Metaschema assembly + * instance. + * + * @param + * the Java type of the parent model container for this instance + * @param + * the Java type of the related assembly definition + * @param + * the Java type of the implementing instance type + * @param + * the Java type of the assembly instance containing this instance + */ public abstract class AbstractAssemblyInstance< PARENT extends IContainerModel, DEFINITION extends IAssemblyDefinition, @@ -38,6 +51,13 @@ public abstract class AbstractAssemblyInstance< extends AbstractNamedModelInstance implements IAssemblyInstance, IFeatureDefinitionReferenceInstance { + /** + * Construct a new assembly instance that is contained with the provided parent + * container. + * + * @param parent + * the parent container for this instance + */ protected AbstractAssemblyInstance(@NonNull PARENT parent) { super(parent); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractChoiceGroupInstance.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractChoiceGroupInstance.java index fc91042b0..46dd39926 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractChoiceGroupInstance.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractChoiceGroupInstance.java @@ -28,6 +28,22 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * This abstract class enforces the data contract of a Metaschema choice group + * instance. + * + * @param + * the Java type of the parent model container for this instance + * @param + * the Java type of child named model instances supported by this + * choice group + * @param + * the Java type of child field instances supported by this choice + * group + * @param + * the Java type of child assembly instances supported by this choice + * group + */ public abstract class AbstractChoiceGroupInstance< PARENT extends IAssemblyDefinition, NAMED_MODEL extends INamedModelInstanceGrouped, @@ -36,6 +52,13 @@ public abstract class AbstractChoiceGroupInstance< extends AbstractInstance implements IChoiceGroupInstance, IFeatureContainerModelGrouped { + /** + * Construct a new choice group instance that is contained with the provided + * parent assembly definition container. + * + * @param parent + * the parent assembly definition container for this instance + */ protected AbstractChoiceGroupInstance(@NonNull PARENT parent) { super(parent); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractChoiceInstance.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractChoiceInstance.java index 4d3eaa901..de091a9df 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractChoiceInstance.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractChoiceInstance.java @@ -28,6 +28,22 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * This abstract class enforce the data contract of a Metaschema choice + * instance. + * + * @param + * the Java type of the parent model container for this instance + * @param + * the Java type of child model instances supported by this choice + * @param + * the Java type of child named model instances supported by this + * choice + * @param + * the Java type of child field instances supported by this choice + * @param + * the Java type of child assembly instances supported by this choice + */ public abstract class AbstractChoiceInstance< PARENT extends IAssemblyDefinition, MODEL extends IModelInstanceAbsolute, @@ -37,6 +53,13 @@ public abstract class AbstractChoiceInstance< extends AbstractInstance implements IChoiceInstance, IFeatureContainerModelAbsolute { + /** + * Construct a new choice instance that is contained with the provided parent + * assembly definition. + * + * @param parent + * the parent assembly definition container for this instance + */ protected AbstractChoiceInstance(@NonNull PARENT parent) { super(parent); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalDefinition.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalDefinition.java index aec1bc70c..227254266 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalDefinition.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalDefinition.java @@ -26,16 +26,26 @@ package gov.nist.secauto.metaschema.core.model; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import javax.xml.namespace.QName; + import edu.umd.cs.findbugs.annotations.NonNull; +import nl.talsmasoftware.lazy4j.Lazy; public abstract class AbstractGlobalDefinition - extends AbstractDefinition { + implements IDefinition { @NonNull private final MODULE module; + @NonNull + private final Lazy qname; + @NonNull + private final Lazy definitionQName; protected AbstractGlobalDefinition(@NonNull MODULE module, @NonNull NameInitializer initializer) { - super(initializer); this.module = module; + this.qname = ObjectUtils.notNull(Lazy.lazy(() -> initializer.apply(getEffectiveName()))); + this.definitionQName = ObjectUtils.notNull(Lazy.lazy(() -> initializer.apply(getName()))); } @Override @@ -43,6 +53,18 @@ public final MODULE getContainingModule() { return module; } + @SuppressWarnings("null") + @Override + public final QName getXmlQName() { + return qname.get(); + } + + @SuppressWarnings("null") + @Override + public final QName getDefinitionQName() { + return definitionQName.get(); + } + @Override public final boolean isInline() { // never inline @@ -54,4 +76,10 @@ public final INSTANCE getInlineInstance() { // never inline return null; } + + @FunctionalInterface + public interface NameInitializer { + @NonNull + QName apply(@NonNull String name); + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractNamedInstance.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractNamedInstance.java index e651b9ee9..622a458e3 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractNamedInstance.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractNamedInstance.java @@ -26,7 +26,7 @@ package gov.nist.secauto.metaschema.core.model; -import gov.nist.secauto.metaschema.core.model.AbstractDefinition.NameInitializer; +import gov.nist.secauto.metaschema.core.model.AbstractGlobalDefinition.NameInitializer; import gov.nist.secauto.metaschema.core.util.ObjectUtils; import javax.xml.namespace.QName; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/IResourceLocation.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/IResourceLocation.java index eaa511cab..1cf2987eb 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/IResourceLocation.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/IResourceLocation.java @@ -26,6 +26,9 @@ package gov.nist.secauto.metaschema.core.model; +/** + * Represents a location within a resource. + */ public interface IResourceLocation { /** * Get the line for a location within a resource. @@ -41,7 +44,17 @@ public interface IResourceLocation { */ int getColumn(); + /** + * Get the zero-based character offset for a location within a resource. + * + * @return the character offset or {@code -1} if unknown + */ long getCharOffset(); + /** + * Get the zero-based byte offset for a location within a resource. + * + * @return the byte offset or {@code -1} if unknown + */ long getByteOffset(); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConstraintValidationHandler.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConstraintValidationHandler.java index e45aa20a4..d242e2301 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConstraintValidationHandler.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/AbstractConstraintValidationHandler.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.Objects; +import java.util.regex.Pattern; import java.util.stream.Collectors; import edu.umd.cs.findbugs.annotations.NonNull; @@ -203,6 +204,8 @@ protected String newUniqueKeyViolationMessage( * the target matching the constraint * @param value * the target's value + * @param pattern + * the expected pattern * @return the new message */ @SuppressWarnings("null") @@ -211,10 +214,11 @@ protected String newMatchPatternViolationMessage( @NonNull IMatchesConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, - @NonNull String value) { + @NonNull String value, + @NonNull Pattern pattern) { return String.format("Value '%s' did not match the pattern '%s' at path '%s'", value, - constraint.getPattern().pattern(), + pattern.pattern(), toPath(target)); } @@ -230,6 +234,8 @@ protected String newMatchPatternViolationMessage( * the target matching the constraint * @param value * the target's value + * @param adapter + * the expected data type adapter * @return the new message */ @SuppressWarnings("null") @@ -238,8 +244,8 @@ protected String newMatchDatatypeViolationMessage( @NonNull IMatchesConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, - @NonNull String value) { - IDataTypeAdapter adapter = constraint.getDataType(); + @NonNull String value, + @NonNull IDataTypeAdapter adapter) { return String.format("Value '%s' did not conform to the data type '%s' at path '%s'", value, adapter.getPreferredName(), toPath(target)); } @@ -268,7 +274,7 @@ protected String newExpectViolationMessage( @NonNull DynamicContext dynamicContext) { String message; if (constraint.getMessage() != null) { - message = constraint.generateMessage(target, dynamicContext).toString(); + message = constraint.generateMessage(target, dynamicContext); } else { message = String.format("Expect constraint '%s' did not match the data at path '%s'", constraint.getTest(), @@ -372,8 +378,8 @@ protected String newIndexMissMessage( */ @SuppressWarnings("null") @NonNull - protected String newGenericValidationViolationMessage( - @NonNull IConstraint constraint, + protected String newMissingIndexViolationMessage( + @NonNull IIndexHasKeyConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, @NonNull String message) { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ConstraintValidationFinding.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ConstraintValidationFinding.java index 6317ebdad..fd6470c3f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ConstraintValidationFinding.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ConstraintValidationFinding.java @@ -84,6 +84,11 @@ public String getIdentifier() { return constraints.size() == 1 ? constraints.get(0).getId() : null; } + /** + * Get the constraints associated with the finding. + * + * @return the constraints + */ @NonNull public List getConstraints() { return constraints; @@ -94,16 +99,32 @@ public String getMessage() { return message; } + /** + * Get the context node used to evaluate the constraints. + * + * @return the context node + */ @NonNull public INodeItem getNode() { return node; } + /** + * Get the target of the finding. + * + * @return the target node + */ @NonNull public INodeItem getTarget() { return target; } + /** + * Get the subjects of the finding, which are resolved by evaluating the + * constraint target expression. + * + * @return the subject nodes + */ @NonNull public List getSubjects() { return subjects; @@ -154,11 +175,29 @@ public URI getDocumentUri() { return getTarget().getBaseUri(); } + /** + * Construct a new finding builder. + * + * @param constraints + * the constraints associated with this finding + * @param node + * the context node used to evaluate the constraints + * @return a new builder + */ @NonNull public static Builder builder(@NonNull List constraints, @NonNull INodeItem node) { return new Builder(constraints, node); } + /** + * Construct a new finding builder. + * + * @param constraint + * the constraint associated with this finding + * @param node + * the context node used to evaluate the constraints + * @return a new builder + */ @NonNull public static Builder builder(@NonNull IConstraint constraint, @NonNull INodeItem node) { return new Builder(CollectionUtil.singletonList(constraint), node); @@ -183,41 +222,88 @@ private Builder(@NonNull List constraints, @NonNull INode this.target = node; } + /** + * Use the provided target for the validation finding. + * + * @param target + * the finding target + * @return this builder + */ public Builder target(@NonNull INodeItem target) { this.target = target; return this; } + /** + * Use the provided message for the validation finding. + * + * @param message + * the message target + * @return this builder + */ @NonNull public Builder message(@NonNull String message) { this.message = message; return this; } + /** + * Use the provided subjects for the validation finding. + * + * @param subjects + * the finding subjects + * @return this builder + */ @NonNull - public Builder subjects(@NonNull List targets) { - this.subjects = CollectionUtil.unmodifiableList(targets); + public Builder subjects(@NonNull List subjects) { + this.subjects = CollectionUtil.unmodifiableList(subjects); return this; } + /** + * Use the provided cause for the validation finding. + * + * @param cause + * the finding cause + * @return this builder + */ @NonNull public Builder cause(@NonNull Throwable cause) { this.cause = cause; return this; } + /** + * Use the provided kind for the validation finding. + * + * @param kind + * the finding kind + * @return this builder + */ @NonNull public Builder kind(@NonNull Kind kind) { this.kind = kind; return this; } + /** + * Use the provided severity for the validation finding. + * + * @param severity + * the finding severity + * @return this builder + */ @NonNull public Builder severity(@NonNull Level severity) { this.severity = severity; return this; } + /** + * Generate the finding using the previously provided data. + * + * @return a new finding + */ @NonNull public ConstraintValidationFinding build() { Level severity = ObjectUtils.notNull(this.severity == null ? constraints.stream() diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/DefaultConstraintValidator.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/DefaultConstraintValidator.java index 6b7071f17..4426638ed 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/DefaultConstraintValidator.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/DefaultConstraintValidator.java @@ -47,6 +47,7 @@ import gov.nist.secauto.metaschema.core.model.IFieldDefinition; import gov.nist.secauto.metaschema.core.model.IFlagDefinition; import gov.nist.secauto.metaschema.core.util.CollectionUtil; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; @@ -71,7 +72,10 @@ *

* This class is not thread safe. */ -@SuppressWarnings("PMD.CouplingBetweenObjects") +@SuppressWarnings({ + "PMD.CouplingBetweenObjects", + "PMD.GodClass" // provides validators for all types +}) public class DefaultConstraintValidator implements IConstraintValidator, IMutableConfiguration> { // NOPMD - intentional private static final Logger LOGGER = LogManager.getLogger(DefaultConstraintValidator.class); @@ -492,38 +496,45 @@ private void validateMatches( @NonNull IMatchesConstraint constraint, @NonNull INodeItem node, @NonNull ISequence targets) { - IConstraintValidationHandler handler = getConstraintValidationHandler(); targets.stream() .forEachOrdered(item -> { assert item != null; if (item.hasValue()) { - String value = FnData.fnDataItem(item).asString(); - - boolean violation = false; - Pattern pattern = constraint.getPattern(); - if (pattern != null && !pattern.asMatchPredicate().test(value)) { - // failed pattern match - handler.handleMatchPatternViolation(constraint, node, item, value); - violation = true; - } - - IDataTypeAdapter adapter = constraint.getDataType(); - if (adapter != null) { - try { - adapter.parse(value); - } catch (IllegalArgumentException ex) { - handler.handleMatchDatatypeViolation(constraint, node, item, value, ex); - violation = true; - } - } - - if (!violation) { - handlePass(constraint, node, item); - } + validateMatchesItem(constraint, node, item); } }); } + private void validateMatchesItem( + @NonNull IMatchesConstraint constraint, + @NonNull INodeItem node, + @NonNull INodeItem item) { + String value = FnData.fnDataItem(item).asString(); + + IConstraintValidationHandler handler = getConstraintValidationHandler(); + boolean valid = true; + Pattern pattern = constraint.getPattern(); + if (pattern != null && !pattern.asMatchPredicate().test(value)) { + // failed pattern match + handler.handleMatchPatternViolation(constraint, node, item, value, pattern); + valid = false; + } + + IDataTypeAdapter adapter = constraint.getDataType(); + if (adapter != null) { + try { + adapter.parse(value); + } catch (IllegalArgumentException ex) { + handler.handleMatchDatatypeViolation(constraint, node, item, value, adapter, ex); + valid = false; + } + } + + if (valid) { + handlePass(constraint, node, item); + } + } + /** * Evaluates the provided collection of {@code constraints} in the context of * the {@code item}. @@ -625,16 +636,15 @@ private void validateExpect( IConstraintValidationHandler handler = getConstraintValidationHandler(); targets.stream() - .map(item -> (INodeItem) item) .forEachOrdered(item -> { assert item != null; if (item.hasValue()) { try { ISequence result = metapath.evaluate(item, dynamicContext); - if (!FnBoolean.fnBoolean(result).toBoolean()) { - handler.handleExpectViolation(constraint, node, item, dynamicContext); - } else { + if (FnBoolean.fnBoolean(result).toBoolean()) { handlePass(constraint, node, item); + } else { + handler.handleExpectViolation(constraint, node, item, dynamicContext); } } catch (MetapathException ex) { rethrowConstraintError(constraint, item, ex); @@ -766,12 +776,11 @@ protected void handleAllowedValues(@NonNull INodeItem targetItem) { public void finalizeValidation(DynamicContext dynamicContext) { // key references for (Map.Entry> entry : indexNameToKeyRefMap.entrySet()) { - String indexName = entry.getKey(); + String indexName = ObjectUtils.notNull(entry.getKey()); IIndex index = indexNameToIndexMap.get(indexName); List keyRefs = entry.getValue(); - IConstraintValidationHandler handler = getConstraintValidationHandler(); for (KeyRef keyRef : keyRefs) { IIndexHasKeyConstraint constraint = keyRef.getConstraint(); @@ -780,26 +789,37 @@ public void finalizeValidation(DynamicContext dynamicContext) { for (INodeItem item : targets) { assert item != null; - try { - List key = IIndex.toKey(item, constraint.getKeyFields(), dynamicContext); + validateKeyRef(constraint, node, item, indexName, index, dynamicContext); + } + } + } + } + + private void validateKeyRef( + @NonNull IIndexHasKeyConstraint constraint, + @NonNull INodeItem contextNode, + @NonNull INodeItem item, + @NonNull String indexName, + @Nullable IIndex index, + @NonNull DynamicContext dynamicContext) { + IConstraintValidationHandler handler = getConstraintValidationHandler(); + try { + List key = IIndex.toKey(item, constraint.getKeyFields(), dynamicContext); - if (index == null) { - handler.handleGenericValidationViolation(constraint, node, item, - String.format("Key reference to undefined index with name '%s'", indexName)); - } else { - INodeItem referencedItem = index.get(key); + if (index == null) { + handler.handleMissingIndexViolation(constraint, contextNode, item, ObjectUtils.notNull( + String.format("Key reference to undefined index with name '%s'", indexName))); + } else { + INodeItem referencedItem = index.get(key); - if (referencedItem == null) { - handler.handleIndexMiss(constraint, node, item, key); - } else { - handlePass(constraint, node, item); - } - } - } catch (MetapathException ex) { - handler.handleKeyMatchError(constraint, node, item, ex); - } + if (referencedItem == null) { + handler.handleIndexMiss(constraint, contextNode, item, key); + } else { + handlePass(constraint, contextNode, item); } } + } catch (MetapathException ex) { + handler.handleKeyMatchError(constraint, contextNode, item, ex); } } @@ -854,7 +874,7 @@ public void validate() { IConstraintValidationHandler handler = getConstraintValidationHandler(); for (Pair> pair : constraints) { IAllowedValuesConstraint allowedValues = pair.getLeft(); - IDefinitionNodeItem node = pair.getRight(); + IDefinitionNodeItem node = ObjectUtils.notNull(pair.getRight()); IAllowedValue matchingValue = allowedValues.getAllowedValue(value); if (matchingValue != null) { match = true; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/FindingCollectingConstraintValidationHandler.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/FindingCollectingConstraintValidationHandler.java index 0b0729419..ebfc278e4 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/FindingCollectingConstraintValidationHandler.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/FindingCollectingConstraintValidationHandler.java @@ -26,6 +26,7 @@ package gov.nist.secauto.metaschema.core.model.constraint; +import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter; import gov.nist.secauto.metaschema.core.metapath.DynamicContext; import gov.nist.secauto.metaschema.core.metapath.ISequence; import gov.nist.secauto.metaschema.core.metapath.MetapathException; @@ -38,6 +39,7 @@ import java.util.LinkedList; import java.util.List; +import java.util.regex.Pattern; import edu.umd.cs.findbugs.annotations.NonNull; @@ -176,12 +178,13 @@ public void handleMatchPatternViolation( @NonNull IMatchesConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, - @NonNull String value) { + @NonNull String value, + @NonNull Pattern pattern) { addFinding(ConstraintValidationFinding.builder(constraint, node) .severity(constraint.getLevel()) .kind(toKind(constraint.getLevel())) .target(target) - .message(newMatchPatternViolationMessage(constraint, node, target, value)) + .message(newMatchPatternViolationMessage(constraint, node, target, value, pattern)) .build()); } @@ -191,12 +194,13 @@ public void handleMatchDatatypeViolation( @NonNull INodeItem node, @NonNull INodeItem target, @NonNull String value, + @NonNull IDataTypeAdapter adapter, @NonNull IllegalArgumentException cause) { addFinding(ConstraintValidationFinding.builder(constraint, node) .severity(constraint.getLevel()) .kind(toKind(constraint.getLevel())) .target(target) - .message(newMatchDatatypeViolationMessage(constraint, node, target, value)) + .message(newMatchDatatypeViolationMessage(constraint, node, target, value, adapter)) .cause(cause) .build()); } @@ -252,13 +256,16 @@ public void handleIndexMiss(IIndexHasKeyConstraint constraint, INodeItem node, I } @Override - public void handleGenericValidationViolation(IConstraint constraint, INodeItem node, INodeItem target, + public void handleMissingIndexViolation( + IIndexHasKeyConstraint constraint, + INodeItem node, + INodeItem target, String message) { addFinding(ConstraintValidationFinding.builder(constraint, node) .severity(constraint.getLevel()) .kind(toKind(constraint.getLevel())) .target(target) - .message(newGenericValidationViolationMessage(constraint, node, target, message)) + .message(newMissingIndexViolationMessage(constraint, node, target, message)) .build()); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IAllowedValuesConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IAllowedValuesConstraint.java index 8df69d8f6..9af790685 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IAllowedValuesConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IAllowedValuesConstraint.java @@ -108,7 +108,7 @@ default R accept(IConstraintVisitor visitor, T state) { } /** - * Get a new constraint builder. + * Create a new constraint builder. * * @return the builder */ @@ -129,24 +129,54 @@ private Builder() { // disable construction } + /** + * Use the provided allowed value to validate associated values. + * + * @param allowedValue + * an expected allowed value + * @return this builder + */ @NonNull public Builder allowedValue(@NonNull IAllowedValue allowedValue) { this.allowedValues.put(allowedValue.getValue(), allowedValue); return this; } + /** + * Use the provided allowed values to validate associated values. + * + * @param allowedValues + * an expected allowed values + * @return this builder + */ @NonNull public Builder allowedValues(@NonNull Map allowedValues) { this.allowedValues.putAll(allowedValues); return this; } + /** + * Determine if unspecified values are allowed and will result in the constraint + * always passing. + * + * @param bool + * {@code true} if other values are allowed or {@code false} otherwise + * @return this builder + */ @NonNull - public Builder allowedOther(boolean bool) { + public Builder allowsOther(boolean bool) { this.allowedOther = bool; return this; } + /** + * Determine the allowed scope of extension for other constraints matching this + * constraint's target. + * + * @param extensible + * the degree of allowed extension + * @return this builder + */ @NonNull public Builder extensible(@NonNull Extensible extensible) { this.extensible = extensible; @@ -159,16 +189,16 @@ protected Builder getThis() { } @NonNull - protected Map getAllowedValues() { + private Map getAllowedValues() { return allowedValues; } - protected boolean isAllowedOther() { + private boolean isAllowedOther() { return allowedOther; } @NonNull - protected Extensible getExtensible() { + private Extensible getExtensible() { return extensible; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ICardinalityConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ICardinalityConstraint.java index 32ffbfa62..3039dba96 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ICardinalityConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ICardinalityConstraint.java @@ -65,6 +65,11 @@ default R accept(IConstraintVisitor visitor, T state) { return visitor.visitCardinalityConstraint(this, state); } + /** + * Create a new constraint builder. + * + * @return the builder + */ @NonNull static Builder builder() { return new Builder(); @@ -79,11 +84,25 @@ private Builder() { // disable construction } + /** + * Use the provided minimum occurrence to validate associated targets. + * + * @param value + * the expected occurrence + * @return this builder + */ public Builder minOccurs(int value) { this.minOccurs = value; return this; } + /** + * Use the provided maximum occurrence to validate associated targets. + * + * @param value + * the expected occurrence + * @return this builder + */ public Builder maxOccurs(int value) { this.maxOccurs = value; return this; @@ -103,11 +122,11 @@ protected void validate() { } } - protected Integer getMinOccurs() { + private Integer getMinOccurs() { return minOccurs; } - protected Integer getMaxOccurs() { + private Integer getMaxOccurs() { return maxOccurs; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraintSet.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraintSet.java index d2ffaef73..b0bc024ae 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraintSet.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraintSet.java @@ -33,9 +33,21 @@ import edu.umd.cs.findbugs.annotations.NonNull; public interface IConstraintSet { + /** + * Get the constraints in the constraint set that apply to the provided module. + * + * @param module + * a Metaschema module + * @return an iterator over the constraints that target the module + */ @NonNull Iterable getTargetedConstraintsForModule(@NonNull IModule module); + /** + * Get constraint sets imported by this constraint set. + * + * @return the imported constraint sets + */ @NonNull Collection getImportedConstraintSets(); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraintValidationHandler.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraintValidationHandler.java index c1688e811..1e15581c8 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraintValidationHandler.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IConstraintValidationHandler.java @@ -26,90 +26,266 @@ package gov.nist.secauto.metaschema.core.model.constraint; +import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter; import gov.nist.secauto.metaschema.core.metapath.DynamicContext; import gov.nist.secauto.metaschema.core.metapath.ISequence; import gov.nist.secauto.metaschema.core.metapath.MetapathException; import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; import java.util.List; +import java.util.regex.Pattern; import edu.umd.cs.findbugs.annotations.NonNull; public interface IConstraintValidationHandler { + /** + * Handle a cardinality constraint minimum violation. + * + * @param constraint + * the constraint that was evaluated + * @param node + * the node the constraint was evaluated as the focus to determine + * targets + * @param targets + * the targets of evaluation + */ void handleCardinalityMinimumViolation( @NonNull ICardinalityConstraint constraint, @NonNull INodeItem node, @NonNull ISequence targets); + /** + * Handle a cardinality constraint maximum violation. + * + * @param constraint + * the constraint that was evaluated + * @param node + * the node the constraint was evaluated as the focus to determine + * targets + * @param targets + * the targets of evaluation + */ void handleCardinalityMaximumViolation( @NonNull ICardinalityConstraint constraint, @NonNull INodeItem node, @NonNull ISequence targets); + /** + * Handle a duplicate index violation. + * + * @param constraint + * the constraint that was evaluated + * @param node + * the node the constraint was evaluated as the focus to determine + * targets + */ void handleIndexDuplicateViolation( @NonNull IIndexConstraint constraint, @NonNull INodeItem node); + /** + * Handle an index duplicate key violation. + *

+ * This happens when two target nodes have the same key. + * + * @param constraint + * the constraint that was evaluated + * @param node + * the node the constraint was evaluated as the focus to determine + * targets + * @param oldItem + * the node that exists in the index for the related key + * @param target + * the target of evaluation + */ void handleIndexDuplicateKeyViolation( @NonNull IIndexConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem oldItem, @NonNull INodeItem target); + /** + * Handle an unique key violation. + *

+ * This happens when two target nodes have the same key. + * + * @param constraint + * the constraint that was evaluated + * @param node + * the node the constraint was evaluated as the focus to determine + * targets + * @param oldItem + * the other node with the same key + * @param target + * the target of evaluation + */ void handleUniqueKeyViolation( @NonNull IUniqueConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem oldItem, @NonNull INodeItem target); + /** + * Handle an error that occurred while generating a key. + * + * @param constraint + * the constraint that was evaluated + * @param node + * the node the constraint was evaluated as the focus to determine + * targets + * @param target + * the target of evaluation + * @param exception + * the resulting Metapath exception + */ + void handleKeyMatchError( + @NonNull IKeyConstraint constraint, + @NonNull INodeItem node, + @NonNull INodeItem target, + @NonNull MetapathException exception); + + /** + * Handle a missing index violation. + *

+ * This happens when an index-has-key constraint references a missing index. + * + * @param constraint + * the constraint that was evaluated + * @param node + * the node the constraint was evaluated as the focus to determine + * targets + * @param target + * the target of evaluation + * @param message + * the error message + */ + void handleMissingIndexViolation( + @NonNull IIndexHasKeyConstraint constraint, + @NonNull INodeItem node, + @NonNull INodeItem target, + @NonNull String message); + + /** + * Handle an index lookup key miss violation. + *

+ * This happens when another node references an expected member of an index that + * does not actually exist in the index. + * + * @param constraint + * the constraint that was evaluated + * @param node + * the node the constraint was evaluated as the focus to determine + * targets + * @param target + * the target of evaluation + * @param key + * the key that was used to lookup the index entry + */ + void handleIndexMiss( + @NonNull IIndexHasKeyConstraint constraint, + @NonNull INodeItem node, + @NonNull INodeItem target, + @NonNull List key); + + /** + * Handle a match pattern violation. + *

+ * This happens when the target value does not match the specified pattern. + * + * @param constraint + * the constraint that was evaluated + * @param node + * the node the constraint was evaluated as the focus to determine + * targets + * @param target + * the target of evaluation + * @param value + * the value used for pattern matching + * @param pattern + * the pattern used for pattern matching + */ void handleMatchPatternViolation( @NonNull IMatchesConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, - @NonNull String value); + @NonNull String value, + @NonNull Pattern pattern); + /** + * Handle a match data type violation. + *

+ * This happens when the target value does not conform to the specified data + * type. + * + * @param constraint + * the constraint that was evaluated + * @param node + * the node the constraint was evaluated as the focus to determine + * targets + * @param target + * the target of evaluation + * @param value + * the value used for data type matching + * @param adapter + * the data type used for data type matching + * @param cause + * the data type exception related to this violation + */ void handleMatchDatatypeViolation( @NonNull IMatchesConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, @NonNull String value, + @NonNull IDataTypeAdapter adapter, @NonNull IllegalArgumentException cause); + /** + * Handle an expect test violation. + *

+ * This happens when the test does not evaluate to true. + * + * @param constraint + * the constraint that was evaluated + * @param node + * the node the constraint was evaluated as the focus to determine + * targets + * @param target + * the target of evaluation + * @param metapathContext + * the Metapath evaluation context + */ void handleExpectViolation( @NonNull IExpectConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, @NonNull DynamicContext metapathContext); - void handleKeyMatchError( - @NonNull IKeyConstraint constraint, - @NonNull INodeItem node, - @NonNull INodeItem target, - @NonNull MetapathException ex); - - void handleIndexMiss( - @NonNull IIndexHasKeyConstraint constraint, - @NonNull INodeItem node, - @NonNull INodeItem target, - @NonNull List key); - + /** + * Handle an allowed values constraint violation. + * + * @param failedConstraints + * the allowed values constraints that did not match. + * @param target + * the target of evaluation + */ void handleAllowedValuesViolation( @NonNull List failedConstraints, @NonNull INodeItem target); - void handleGenericValidationViolation( - @NonNull IConstraint constraint, - @NonNull INodeItem node, - @NonNull INodeItem target, - @NonNull String message); - + /** + * Handle a constraint that has passed validation. + * + * @param constraint + * the constraint that was evaluated + * @param node + * the node the constraint was evaluated as the focus to determine + * targets + * @param target + * the target of evaluation + */ void handlePass( @NonNull IConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target); - // void handlePass( - // @NonNull IConstraint constraint, - // @NonNull INodeItem node, - // @NonNull List targets); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IExpectConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IExpectConstraint.java index d1c24b0ff..8c3486223 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IExpectConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IExpectConstraint.java @@ -39,26 +39,45 @@ *

* A custom message can be used to indicate what a test failure signifies. */ - public interface IExpectConstraint extends IConstraint { + /** + * Get the test to use to validate selected nodes. + * + * @return the test metapath expression to use + */ @NonNull String getTest(); /** * A message to emit when the constraint is violated. Allows embedded Metapath - * expressions using the syntax {@code \{metapath\}}. + * expressions using the syntax {@code \{ metapath \}}. * * @return the message if defined or {@code null} otherwise */ String getMessage(); - CharSequence generateMessage(@NonNull INodeItem item, @NonNull DynamicContext context); + /** + * Generate a violation message using the provide item and dynamic context for + * inline Metapath value insertion. + * + * @param item + * the target Metapath item to use as the focus for Metapath evaluation + * @param context + * the dynamic context for Metapath evaluation + * @return the message + */ + String generateMessage(@NonNull INodeItem item, @NonNull DynamicContext context); @Override default R accept(IConstraintVisitor visitor, T state) { return visitor.visitExpectConstraint(this, state); } + /** + * Create a new constraint builder. + * + * @return the builder + */ @NonNull static Builder builder() { return new Builder(); @@ -73,12 +92,27 @@ private Builder() { // disable construction } + /** + * Use the provided test to validate selected nodes. + * + * @param test + * the test metapath expression to use + * @return this builder + */ @NonNull public Builder test(@NonNull String test) { this.test = test; return this; } + /** + * A message to emit when the constraint is violated. Allows embedded Metapath + * expressions using the syntax {@code \{ metapath \}}. + * + * @param message + * the message if defined or {@code null} otherwise + * @return this builder + */ @NonNull public Builder message(@NonNull String message) { this.message = message; @@ -97,11 +131,11 @@ protected void validate() { ObjectUtils.requireNonNull(getTest()); } - protected String getTest() { + private String getTest() { return test; } - protected String getMessage() { + private String getMessage() { return message; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IIndexConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IIndexConstraint.java index c2504572d..050703e2e 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IIndexConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IIndexConstraint.java @@ -54,27 +54,25 @@ default R accept(IConstraintVisitor visitor, T state) { } /** - * Get a new constraint builder. + * Create a new constraint builder. + * + * @param name + * the identifier for the index * * @return the builder */ @NonNull - static Builder builder() { - return new Builder(); + static Builder builder(@NonNull String name) { + return new Builder(name); } final class Builder extends AbstractKeyConstraintBuilder { - private String name; - - private Builder() { - // disable construction - } - @NonNull - public Builder name(@NonNull String name) { + private final String name; + + private Builder(@NonNull String name) { this.name = name; - return this; } @Override @@ -82,14 +80,8 @@ protected Builder getThis() { return this; } - @Override - protected void validate() { - super.validate(); - - ObjectUtils.requireNonNull(name); - } - - protected String getName() { + @NonNull + private String getName() { return name; } @@ -103,7 +95,7 @@ protected DefaultIndexConstraint newInstance() { getLevel(), getTarget(), getProperties(), - ObjectUtils.notNull(getName()), + getName(), getKeyFields(), getRemarks()); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IIndexHasKeyConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IIndexHasKeyConstraint.java index 7a6be6f8b..7c2cc17b8 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IIndexHasKeyConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IIndexHasKeyConstraint.java @@ -37,6 +37,11 @@ * {@link IIndexConstraint}. */ public interface IIndexHasKeyConstraint extends IKeyConstraint { + /** + * The name of the index used to verify cross references. + * + * @return the index name + */ @NonNull String getIndexName(); @@ -45,23 +50,25 @@ default R accept(IConstraintVisitor visitor, T state) { return visitor.visitIndexHasKeyConstraint(this, state); } + /** + * Create a new constraint builder. + * + * @param useIndex + * the index name + * @return the builder + */ @NonNull - static Builder builder() { - return new Builder(); + static Builder builder(@NonNull String useIndex) { + return new Builder(useIndex); } final class Builder extends AbstractKeyConstraintBuilder { - private String indexName; - - private Builder() { - // disable construction - } - @NonNull - public Builder name(@NonNull String name) { - this.indexName = name; - return this; + private final String indexName; + + private Builder(@NonNull String useIndex) { + this.indexName = useIndex; } @Override @@ -69,14 +76,8 @@ protected Builder getThis() { return this; } - @Override - protected void validate() { - super.validate(); - - ObjectUtils.requireNonNull(indexName); - } - - protected String getIndexName() { + @NonNull + private String getIndexName() { return indexName; } @@ -90,7 +91,7 @@ protected IIndexHasKeyConstraint newInstance() { getLevel(), getTarget(), getProperties(), - ObjectUtils.notNull(getIndexName()), + getIndexName(), getKeyFields(), getRemarks()); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IMatchesConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IMatchesConstraint.java index d757c26ce..72561a30d 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IMatchesConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IMatchesConstraint.java @@ -33,14 +33,28 @@ import java.util.regex.Pattern; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * Represents a rule requiring the value of a field or flag to match a pattern * and/or conform to an identified data type. */ public interface IMatchesConstraint extends IConstraint { + /** + * Get the expected pattern. + * + * @return the expected pattern or {@code null} if there is no expected pattern + */ + @Nullable Pattern getPattern(); + /** + * Get the expected data type. + * + * @return the expected data type or {@code null} if there is no expected data + * type + */ + @Nullable IDataTypeAdapter getDataType(); @Override @@ -48,6 +62,11 @@ default R accept(IConstraintVisitor visitor, T state) { return visitor.visitMatchesConstraint(this, state); } + /** + * Create a new constraint builder. + * + * @return the builder + */ @NonNull static Builder builder() { return new Builder(); @@ -62,15 +81,36 @@ private Builder() { // disable construction } + /** + * Use the provided pattern to validate associated values. + * + * @param pattern + * the pattern to use + * @return this builder + */ public Builder regex(@NonNull String pattern) { return regex(ObjectUtils.notNull(Pattern.compile(pattern))); } + /** + * Use the provided pattern to validate associated values. + * + * @param pattern + * the expected pattern + * @return this builder + */ public Builder regex(@NonNull Pattern pattern) { this.pattern = pattern; return this; } + /** + * Use the provided data type to validate associated values. + * + * @param datatype + * the expected data type + * @return this builder + */ public Builder datatype(@NonNull IDataTypeAdapter datatype) { this.datatype = datatype; return this; @@ -90,11 +130,11 @@ protected void validate() { } } - protected Pattern getPattern() { + private Pattern getPattern() { return pattern; } - protected IDataTypeAdapter getDatatype() { + private IDataTypeAdapter getDatatype() { return datatype; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IScopedContraints.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IScopedContraints.java index f3848427e..88f809b7b 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IScopedContraints.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IScopedContraints.java @@ -31,13 +31,32 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * Represents a set of target constraints that apply to a given Metaschema + * module namespace and short name. + */ public interface IScopedContraints { + /** + * The Metaschema module namespace the constraints apply to. + * + * @return the namespace + */ @NonNull URI getModuleNamespace(); + /** + * The Metaschema module short name the constraints apply to. + * + * @return the short name + */ @NonNull String getModuleShortName(); + /** + * The collection of target constraints. + * + * @return the constraints + */ @NonNull List getTargetedContraints(); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ITargetedConstraints.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ITargetedConstraints.java index 55e5cc7f7..8e3924e35 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ITargetedConstraints.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/ITargetedConstraints.java @@ -37,12 +37,35 @@ * Metapath expression. */ public interface ITargetedConstraints extends IValueConstrained { + /** + * Get the Metapath expression used to identify the target of the constraint. + * + * @return the uncompiled Metapath expression + */ @NonNull String getTargetExpression(); + /** + * Apply the constraint to the provided definition. + * + * @param definition + * the definition to apply the constraint to + */ void target(@NonNull IFlagDefinition definition); + /** + * Apply the constraint to the provided definition. + * + * @param definition + * the definition to apply the constraint to + */ void target(@NonNull IFieldDefinition definition); + /** + * Apply the constraint to the provided definition. + * + * @param definition + * the definition to apply the constraint to + */ void target(@NonNull IAssemblyDefinition definition); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IUniqueConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IUniqueConstraint.java index 8bb93182c..b441441eb 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IUniqueConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/IUniqueConstraint.java @@ -45,6 +45,11 @@ default R accept(IConstraintVisitor visitor, T state) { return visitor.visitUniqueConstraint(this, state); } + /** + * Create a new constraint builder. + * + * @return the builder + */ @NonNull static Builder builder() { return new Builder(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/LoggingConstraintValidationHandler.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/LoggingConstraintValidationHandler.java index a45a73b64..f8206e672 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/LoggingConstraintValidationHandler.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/LoggingConstraintValidationHandler.java @@ -26,6 +26,7 @@ package gov.nist.secauto.metaschema.core.model.constraint; +import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter; import gov.nist.secauto.metaschema.core.metapath.DynamicContext; import gov.nist.secauto.metaschema.core.metapath.ISequence; import gov.nist.secauto.metaschema.core.metapath.MetapathException; @@ -39,6 +40,7 @@ import java.util.Comparator; import java.util.List; +import java.util.regex.Pattern; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -180,10 +182,11 @@ public void handleMatchPatternViolation( @NonNull IMatchesConstraint constraint, @NonNull INodeItem node, @NonNull INodeItem target, - @NonNull String value) { + @NonNull String value, + @NonNull Pattern pattern) { Level level = constraint.getLevel(); if (isLogged(level)) { - logConstraint(level, target, newMatchPatternViolationMessage(constraint, node, target, value), null); + logConstraint(level, target, newMatchPatternViolationMessage(constraint, node, target, value, pattern), null); } } @@ -193,10 +196,11 @@ public void handleMatchDatatypeViolation( @NonNull INodeItem node, @NonNull INodeItem target, @NonNull String value, + @NonNull IDataTypeAdapter adapter, @NonNull IllegalArgumentException cause) { Level level = constraint.getLevel(); if (isLogged(level)) { - logConstraint(level, target, newMatchDatatypeViolationMessage(constraint, node, target, value), cause); + logConstraint(level, target, newMatchDatatypeViolationMessage(constraint, node, target, value, adapter), cause); } } @@ -242,11 +246,11 @@ public void handleIndexMiss(IIndexHasKeyConstraint constraint, INodeItem node, I } @Override - public void handleGenericValidationViolation(IConstraint constraint, INodeItem node, INodeItem target, + public void handleMissingIndexViolation(IIndexHasKeyConstraint constraint, INodeItem node, INodeItem target, String message) { Level level = constraint.getLevel(); if (isLogged(level)) { - logConstraint(level, node, newGenericValidationViolationMessage(constraint, node, target, message), null); + logConstraint(level, node, newMissingIndexViolationMessage(constraint, node, target, message), null); } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultExpectConstraint.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultExpectConstraint.java index ddd27730f..1769c4e0a 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultExpectConstraint.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/DefaultExpectConstraint.java @@ -111,7 +111,7 @@ public String getMessage() { } @Override - public CharSequence generateMessage(@NonNull INodeItem item, @NonNull DynamicContext context) { + public String generateMessage(@NonNull INodeItem item, @NonNull DynamicContext context) { String message = getMessage(); return message == null ? null @@ -120,6 +120,6 @@ public CharSequence generateMessage(@NonNull INodeItem item, @NonNull DynamicCon @NonNull String metapath = match.group(2); MetapathExpression expr = MetapathExpression.compile(metapath); return expr.evaluateAs(item, MetapathExpression.ResultType.STRING, context); - }); + }).toString(); } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/ExternalSource.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/ExternalSource.java index 6c274c8ca..b46b67f69 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/ExternalSource.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/ExternalSource.java @@ -27,6 +27,7 @@ package gov.nist.secauto.metaschema.core.model.constraint.impl; import gov.nist.secauto.metaschema.core.model.constraint.ISource; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.net.URI; import java.util.HashMap; @@ -58,7 +59,9 @@ public final class ExternalSource implements ISource { public static ISource instance(@NonNull URI location) { ISource retval; synchronized (sources) { - retval = sources.computeIfAbsent(location, (uri) -> new ExternalSource(uri)); + retval = ObjectUtils.notNull(sources.computeIfAbsent( + location, + (uri) -> new ExternalSource(ObjectUtils.notNull(uri)))); } return retval; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/InternalModelSource.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/InternalModelSource.java index 4a8ffa91a..a9642ee29 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/InternalModelSource.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/constraint/impl/InternalModelSource.java @@ -27,6 +27,7 @@ package gov.nist.secauto.metaschema.core.model.constraint.impl; import gov.nist.secauto.metaschema.core.model.constraint.ISource; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.net.URI; import java.util.HashMap; @@ -57,7 +58,9 @@ public final class InternalModelSource implements ISource { public static ISource instance(@NonNull URI location) { ISource retval; synchronized (sources) { - retval = sources.computeIfAbsent(location, (uri) -> new InternalModelSource(uri)); + retval = ObjectUtils.notNull(sources.computeIfAbsent( + location, + (uri) -> new InternalModelSource(ObjectUtils.notNull(uri)))); } return retval; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/util/JsonUtil.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/util/JsonUtil.java index e4691f77d..6bf189915 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/util/JsonUtil.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/util/JsonUtil.java @@ -55,11 +55,25 @@ private JsonUtil() { // disable construction } + /** + * Parse the input stream into a JSON object. + * + * @param is + * the input stream to parse + * @return the JSON object + */ @NonNull - public static JSONObject toJsonObject(@NonNull InputStream schemaInputStream) { - return new JSONObject(new JSONTokener(schemaInputStream)); + public static JSONObject toJsonObject(@NonNull InputStream is) { + return new JSONObject(new JSONTokener(is)); } + /** + * Parse the reader into a JSON object. + * + * @param reader + * the reader to parse + * @return the JSON object + */ @NonNull public static JSONObject toJsonObject(@NonNull Reader reader) { return new JSONObject(new JSONTokener(reader)); @@ -104,8 +118,19 @@ public static String toString(@NonNull JsonLocation location) { .toString(); } + /** + * Advance the parser to the next location matching the provided token. + * + * @param parser + * the JSON parser + * @param token + * the expected token + * @return the current token or {@code null} if no tokens remain in the stream + * @throws IOException + * if an error occurred while parsing the JSON + */ @Nullable - public static JsonToken advanceTo(@NonNull JsonParser parser, JsonToken token) throws IOException { + public static JsonToken advanceTo(@NonNull JsonParser parser, @NonNull JsonToken token) throws IOException { JsonToken currentToken = null; while (parser.hasCurrentToken() && !token.equals(currentToken = parser.currentToken())) { currentToken = parser.nextToken(); @@ -118,6 +143,15 @@ public static JsonToken advanceTo(@NonNull JsonParser parser, JsonToken token) t return currentToken; } + /** + * Skip the next JSON value in the stream. + * + * @param parser + * the JSON parser + * @return the current token or {@code null} if no tokens remain in the stream + * @throws IOException + * if an error occurred while parsing the JSON + */ @SuppressWarnings({ "resource", // parser not owned "PMD.CyclomaticComplexity" // acceptable @@ -156,118 +190,207 @@ public static JsonToken skipNextValue(@NonNull JsonParser parser) throws IOExcep // advance past the value return parser.nextToken(); } + // + // @SuppressWarnings("PMD.CyclomaticComplexity") // acceptable + // private static boolean checkEndOfValue(@NonNull JsonParser parser, @NonNull + // JsonToken startToken) { + // JsonToken currentToken = parser.getCurrentToken(); + // + // boolean retval; + // switch (startToken) { // NOPMD - intentional fall through + // case START_OBJECT: + // retval = JsonToken.END_OBJECT.equals(currentToken); + // break; + // case START_ARRAY: + // retval = JsonToken.END_ARRAY.equals(currentToken); + // break; + // case VALUE_EMBEDDED_OBJECT: + // case VALUE_FALSE: + // case VALUE_NULL: + // case VALUE_NUMBER_FLOAT: + // case VALUE_NUMBER_INT: + // case VALUE_STRING: + // case VALUE_TRUE: + // retval = true; + // break; + // default: + // retval = false; + // } + // return retval; + // } - @SuppressWarnings("PMD.CyclomaticComplexity") // acceptable - public static boolean checkEndOfValue(@NonNull JsonParser parser, @NonNull JsonToken startToken) { - JsonToken currentToken = parser.getCurrentToken(); - - boolean retval; - switch (startToken) { // NOPMD - intentional fall through - case START_OBJECT: - retval = JsonToken.END_OBJECT.equals(currentToken); - break; - case START_ARRAY: - retval = JsonToken.END_ARRAY.equals(currentToken); - break; - case VALUE_EMBEDDED_OBJECT: - case VALUE_FALSE: - case VALUE_NULL: - case VALUE_NUMBER_FLOAT: - case VALUE_NUMBER_INT: - case VALUE_STRING: - case VALUE_TRUE: - retval = true; - break; - default: - retval = false; - } - return retval; - } - + /** + * Ensure that the current token is one of the provided tokens. + *

+ * Note: This uses a Java assertion to support debugging in a whay that doesn't + * impact parser performance during production operation. + * + * @param parser + * the JSON parser + * @param expectedTokens + * the tokens for which one is expected to match against the current + * token + */ public static void assertCurrent( @NonNull JsonParser parser, @NonNull JsonToken... expectedTokens) { JsonToken current = parser.currentToken(); assert Arrays.stream(expectedTokens) - .anyMatch(expected -> expected.equals(current)) : getAssertMessage( + .anyMatch(expected -> expected.equals(current)) : generateExpectedMessage( 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'%s.", - token, - generateLocationMessage(parser)); - } + // 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'%s.", + // token, + // generateLocationMessage(parser)); + // } + /** + * Ensure that the current token is the one expected and then advance the token + * stream. + * + * @param parser + * the JSON parser + * @param expectedToken + * the expected token + * @return the next token + * @throws IOException + * if an error occurred while reading the token stream + */ @Nullable public static JsonToken assertAndAdvance( @NonNull JsonParser parser, @NonNull JsonToken expectedToken) throws IOException { JsonToken token = parser.currentToken(); - assert expectedToken.equals(token) : getAssertMessage( + assert expectedToken.equals(token) : generateExpectedMessage( parser, expectedToken, token); return parser.nextToken(); } + /** + * Advance the token stream, then ensure that the current token is the one + * expected. + * + * @param parser + * the JSON parser + * @param expectedToken + * the expected token + * @return the next token + * @throws IOException + * if an error occurred while reading the token stream + */ @Nullable public static JsonToken advanceAndAssert( @NonNull JsonParser parser, @NonNull JsonToken expectedToken) throws IOException { JsonToken token = parser.nextToken(); - assert expectedToken.equals(token) : getAssertMessage( + assert expectedToken.equals(token) : generateExpectedMessage( parser, expectedToken, token); return token; } + /** + * Generate a message intended for error reporting based on a presumed token. + * + * @param parser + * the JSON parser + * @param expectedToken + * the expected token + * @param actualToken + * the actual token found + * @return the message string + */ @NonNull - public static String getAssertMessage( + private static String generateExpectedMessage( @NonNull JsonParser parser, - @NonNull JsonToken expected, - JsonToken actual) { + @NonNull JsonToken expectedToken, + JsonToken actualToken) { return ObjectUtils.notNull( String.format("Expected JsonToken '%s', but found JsonToken '%s'%s.", - expected, - actual, + expectedToken, + actualToken, generateLocationMessage(parser))); } + /** + * Generate a message intended for error reporting based on a presumed set of + * tokens. + * + * @param parser + * the JSON parser + * @param expectedTokens + * the set of expected tokens, one of which was expected to match the + * actual token + * @param actualToken + * the actual token found + * @return the message string + */ @NonNull - public static String getAssertMessage( + private static String generateExpectedMessage( @NonNull JsonParser parser, - @NonNull JsonToken[] expected, - JsonToken actual) { - List expectedTokens = ObjectUtils.notNull(Arrays.asList(expected)); - return getAssertMessage(parser, expectedTokens, actual); + @NonNull JsonToken[] expectedTokens, + JsonToken actualToken) { + List expectedTokensList = ObjectUtils.notNull(Arrays.asList(expectedTokens)); + return generateExpectedMessage(parser, expectedTokensList, actualToken); } + /** + * Generate a message intended for error reporting based on a presumed set of + * tokens. + * + * @param parser + * the JSON parser + * @param expectedTokens + * the set of expected tokens, one of which was expected to match the + * actual token + * @param actualToken + * the actual token found + * @return the message string + */ @NonNull - public static String getAssertMessage( + private static String generateExpectedMessage( @NonNull JsonParser parser, - @NonNull Collection expected, - JsonToken actual) { + @NonNull Collection expectedTokens, + JsonToken actualToken) { return ObjectUtils.notNull( String.format("Expected JsonToken(s) '%s', but found JsonToken '%s'%s.", - expected.stream().map(Enum::name).collect(CustomCollectors.joiningWithOxfordComma("or")), - actual, + expectedTokens.stream().map(Enum::name).collect(CustomCollectors.joiningWithOxfordComma("or")), + actualToken, generateLocationMessage(parser))); } + /** + * Generate a location string for the current location in the JSON token stream. + * + * @param parser + * the JSON parser + * @return the location string + */ @NonNull public static CharSequence generateLocationMessage(@NonNull JsonParser parser) { JsonLocation location = parser.getCurrentLocation(); return location == null ? "" : generateLocationMessage(location); } + /** + * Generate a location string for the current location in the JSON token stream. + * + * @param location + * a JSON token stream location + * @return the location string + */ @SuppressWarnings("null") @NonNull public static CharSequence generateLocationMessage(@NonNull JsonLocation location) { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/util/XmlEventUtil.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/util/XmlEventUtil.java index 3ad22a6ce..1a4e8c370 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/util/XmlEventUtil.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/util/XmlEventUtil.java @@ -109,7 +109,7 @@ private static String escape(char ch) { * {@link XMLEvent}. * * @param xmlEvent - * the event to generate the message for + * the XML event to generate the message for * @return the message */ @NonNull @@ -162,7 +162,7 @@ public static CharSequence toString(@Nullable Location location) { * reader. * * @param reader - * the stream reader + * the XML event stream reader * @return the generated string */ @NonNull @@ -192,7 +192,7 @@ public static CharSequence toString(@NonNull XMLStreamReader2 reader) { // NO_UC * Retrieve the resource location of {@code event}. * * @param event - * the event to identify the location for + * the XML event to identify the location for * @return the location or {@code null} if the location is unknown */ @Nullable @@ -215,7 +215,7 @@ public static Location toLocation(@NonNull XMLEvent event) { * Retrieve the name of the node associated with {@code event}. * * @param event - * the event to get the {@link QName} for + * the XML event to get the {@link QName} for * @return the name of the node or {@code null} if the event is not a start or * end element */ @@ -236,7 +236,7 @@ public static QName toQName(@NonNull XMLEvent event) { * Get the event name of the {@code event}. * * @param event - * the event to get the event name for + * the XML event to get the event name for * @return the event name */ @NonNull @@ -267,7 +267,7 @@ public static String toEventName(int eventType) { * {@code eventType} is reached or the end of stream is found. * * @param reader - * the event reader to advance + * the XML event reader to advance * @param eventType * the event type to stop on as defined by {@link XMLStreamConstants} * @return the next event of the specified type or {@code null} if the end of @@ -296,6 +296,15 @@ public static XMLEvent advanceTo(@NonNull XMLEventReader2 reader, int eventType) return xmlEvent; } + /** + * Skip over the next element in the event stream. + * + * @param reader + * the XML event stream reader + * @return the next XML event + * @throws XMLStreamException + * if an error occurred while reading the event stream + */ @SuppressWarnings("PMD.OnlyOneReturn") public static XMLEvent skipElement(@NonNull XMLEventReader2 reader) throws XMLStreamException { XMLEvent xmlEvent = reader.peek(); @@ -325,7 +334,7 @@ public static XMLEvent skipElement(@NonNull XMLEventReader2 reader) throws XMLSt * Skip over any processing instructions. * * @param reader - * the event reader to advance + * the XML event reader to advance * @return the last processing instruction event or the reader's next event if * no processing instruction was found * @throws XMLStreamException @@ -344,7 +353,7 @@ public static XMLEvent skipProcessingInstructions(@NonNull XMLEventReader2 reade * Skip over any whitespace. * * @param reader - * the event reader to advance + * the XML event reader to advance * @return the last character event containing whitespace or the reader's next * event if no character event was found * @throws XMLStreamException @@ -371,7 +380,7 @@ public static XMLEvent skipWhitespace(@NonNull XMLEventReader2 reader) throws XM * provided {@code expectedQName}. * * @param event - * the event + * the XML event * @param expectedQName * the expected element name * @return {@code true} if the next event matches the {@code expectedQName} @@ -386,7 +395,7 @@ public static boolean isEventEndElement(XMLEvent event, @NonNull QName expectedQ * Determine if the {@code event} is an end of document event. * * @param event - * the event + * the XML event * @return {@code true} if the next event is an end of document event */ public static boolean isEventEndDocument(XMLEvent event) { @@ -418,7 +427,7 @@ public static boolean isEventStartElement(XMLEvent event, @NonNull QName expecte * the type identified by {@code presumedEventType}. * * @param reader - * the event reader + * the XML event reader * @param presumedEventType * the expected event type as defined by {@link XMLStreamConstants} * @return the next event @@ -436,7 +445,7 @@ public static XMLEvent consumeAndAssert(XMLEventReader2 reader, int presumedEven * by {@code presumedName}. * * @param reader - * the event reader + * the XML event reader * @param presumedEventType * the expected event type as defined by {@link XMLStreamConstants} * @param presumedName @@ -460,6 +469,20 @@ public static XMLEvent consumeAndAssert(XMLEventReader2 reader, int presumedEven return retval; } + /** + * Ensure that the next event is an XML start element that matches the presumed + * name. + * + * @param reader + * the XML event reader + * @param presumedName + * the qualified name of the expected next event + * @return the XML start element event + * @throws IOException + * if an error occurred while parsing the resource + * @throws XMLStreamException + * if an error occurred while parsing the XML event stream + */ @NonNull public static StartElement requireStartElement( @NonNull XMLEventReader2 reader, @@ -474,6 +497,20 @@ public static StartElement requireStartElement( return ObjectUtils.notNull(retval.asStartElement()); } + /** + * Ensure that the next event is an XML start element that matches the presumed + * name. + * + * @param reader + * the XML event reader + * @param presumedName + * the qualified name of the expected next event + * @return the XML start element event + * @throws IOException + * if an error occurred while parsing the resource + * @throws XMLStreamException + * if an error occurred while parsing the XML event stream + */ @NonNull public static EndElement requireEndElement( @NonNull XMLEventReader2 reader, @@ -489,7 +526,7 @@ public static EndElement requireEndElement( } /** - * Assert that the next event from {@code reader} is of the type identified by + * Ensure that the next event from {@code reader} is of the type identified by * {@code presumedEventType}. * * @param reader @@ -510,7 +547,7 @@ public static XMLEvent assertNext( } /** - * Assert that the next event from {@code reader} is of the type identified by + * Ensure that the next event from {@code reader} is of the type identified by * {@code presumedEventType} and has the name identified by * {@code presumedName}. * @@ -543,17 +580,43 @@ public static XMLEvent assertNext( return nextEvent; } + /** + * Generate a location string for the current location in the XML event stream. + * + * @param event + * an XML event + * @return the location string + */ public static CharSequence generateLocationMessage(@NonNull XMLEvent event) { Location location = toLocation(event); return location == null ? "" : generateLocationMessage(location); } + /** + * Generate a location string for the current location in the XML event stream. + * + * @param location + * an XML event stream location + * @return the location string + */ public static CharSequence generateLocationMessage(@NonNull Location location) { return new StringBuilder(12) .append(" at ") .append(XmlEventUtil.toString(location)); } + /** + * Generate a message intended for error reporting based on a presumed event. + * + * @param event + * the current XML event + * @param presumedEventType + * the expected event type ({@link XMLEvent#getEventType()}) + * @param presumedName + * the expected event qualified name or {@code null} if there is no + * expectation + * @return the message string + */ public static CharSequence generateExpectedMessage( @Nullable XMLEvent event, int presumedEventType, diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/AggregateValidationResult.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/AggregateValidationResult.java index eebd89682..e4711a500 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/AggregateValidationResult.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/AggregateValidationResult.java @@ -37,6 +37,10 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * Provides the means to aggregate multiple validation result sets into a single + * result set. + */ public final class AggregateValidationResult implements IValidationResult { @NonNull private final List findings; @@ -48,10 +52,13 @@ private AggregateValidationResult(@NonNull List findings, @N this.highestSeverity = highestSeverity; } - public static IValidationResult aggregate(@NonNull IValidationResult result) { - return result; - } - + /** + * Aggregate multiple provided results into a single result set. + * + * @param results + * the results to aggregate + * @return the combined results + */ public static IValidationResult aggregate(@NonNull IValidationResult... results) { Stream stream = Stream.empty(); for (IValidationResult result : results) { @@ -61,7 +68,7 @@ public static IValidationResult aggregate(@NonNull IValidationResult... results) return aggregate(stream); } - public static IValidationResult aggregate(@NonNull Stream findingStream) { + private static IValidationResult aggregate(@NonNull Stream findingStream) { AtomicReference highestSeverity = new AtomicReference<>(Level.INFORMATIONAL); List findings = new LinkedList<>(); @@ -85,5 +92,4 @@ public Level getHighestSeverity() { public List getFindings() { return findings; } - } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/IValidationFinding.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/IValidationFinding.java index a37e929f9..88cfeb6cc 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/IValidationFinding.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/IValidationFinding.java @@ -39,13 +39,34 @@ * completed content validation. */ public interface IValidationFinding { + /** + * The finding type. + */ enum Kind { + /** + * The finding does not apply to the intended purpose of the validation. + */ NOT_APPLICABLE, + /** + * The finding represents a successful result. + */ PASS, + /** + * The finding represents an unsuccessful result. + */ FAIL, + /** + * The finding is providing information that does not indicate success or + * failure. + */ INFORMATIONAL; } + /** + * Get the unique identifier for the finding. + * + * @return the identifier + */ @Nullable String getIdentifier(); @@ -57,6 +78,11 @@ enum Kind { @NonNull IConstraint.Level getSeverity(); + /** + * Get the finding type. + * + * @return the finding type + */ @NonNull Kind getKind(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/JsonSchemaContentValidator.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/JsonSchemaContentValidator.java index 09ca0e259..325c27709 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/JsonSchemaContentValidator.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/JsonSchemaContentValidator.java @@ -54,74 +54,142 @@ public class JsonSchemaContentValidator @NonNull private final Schema schema; + /** + * Construct a new JSON schema validator using the provided reader to load the + * JSON schema. + * + * @param reader + * the JSON schema reader + */ public JsonSchemaContentValidator(@NonNull Reader reader) { this(new JSONTokener(reader)); } + /** + * Construct a new JSON schema validator using the provided input stream to load + * the JSON schema. + * + * @param is + * the JSON schema input source + */ public JsonSchemaContentValidator(@NonNull InputStream is) { this(new JSONTokener(is)); } + /** + * Construct a new JSON schema validator using the provided JSON object for the + * JSON schema. + * + * @param jsonSchema + * the JSON schema + */ public JsonSchemaContentValidator(@NonNull JSONObject jsonSchema) { this(ObjectUtils.notNull(SchemaLoader.load(jsonSchema))); } + /** + * Construct a new JSON schema validator using the provided JSON tokenizer to + * load the schema. + * + * @param tokenizer + * the JSON schema token stream + */ protected JsonSchemaContentValidator(@NonNull JSONTokener tokenizer) { this(new JSONObject(tokenizer)); } + /** + * Construct a new JSON schema validator using the preloaded JSON schema. + * + * @param schema + * the preloaded JSON schema + */ protected JsonSchemaContentValidator(@NonNull Schema schema) { this.schema = ObjectUtils.requireNonNull(schema, "schema"); } @Override - public IValidationResult validate(InputStream is, URI documentUri) throws IOException { + public IValidationResult validate(InputStream is, URI resourceUri) throws IOException { JSONObject json; try { json = new JSONObject(new JSONTokener(is)); } catch (JSONException ex) { - throw new IOException(String.format("Unable to parse JSON from '%s'", documentUri), ex); + throw new IOException(String.format("Unable to parse JSON from '%s'", resourceUri), ex); } - return validate(json, documentUri); + return validate(json, resourceUri); } + /** + * Validate the provided JSON. + * + * @param json + * the JSON to validate + * @param resourceUri + * the source URI for the JSON to validate + * @return the validation results + */ @SuppressWarnings("null") @NonNull - public IValidationResult validate(@NonNull JSONObject json, @NonNull URI documentUri) { + public IValidationResult validate(@NonNull JSONObject json, @NonNull URI resourceUri) { IValidationResult retval; try { schema.validate(json); retval = IValidationResult.PASSING_RESULT; } catch (ValidationException ex) { - retval = new JsonValidationResult(handleValidationException(ex, documentUri).collect(Collectors.toList())); + retval = new JsonValidationResult(handleValidationException(ex, resourceUri).collect(Collectors.toList())); } return retval; } + /** + * Build validation findings from a validation exception. + * + * @param exception + * the JSON schema validation exception generated during schema + * validation representing the issue + * @param resourceUri + * the resource the issue was found in + * @return the stream of findings + */ @SuppressWarnings("null") @NonNull - protected Stream handleValidationException(@NonNull ValidationException ex, - @NonNull URI documentUri) { - JsonValidationFinding finding = new JsonValidationFinding(ex, documentUri); - Stream childFindings = ex.getCausingExceptions().stream() - .flatMap(exception -> { - return handleValidationException(exception, documentUri); + protected Stream handleValidationException( + @NonNull ValidationException exception, + @NonNull URI resourceUri) { + JsonValidationFinding finding = new JsonValidationFinding(exception, resourceUri); + Stream childFindings = exception.getCausingExceptions().stream() + .flatMap(ex -> { + return handleValidationException(ex, resourceUri); }); return Stream.concat(Stream.of(finding), childFindings); } + /** + * Records an identified individual validation result found during JSON schema + * validation. + */ public static class JsonValidationFinding implements IValidationFinding { @NonNull private final ValidationException exception; @NonNull private final URI documentUri; + /** + * Construct a new XML schema validation finding, which represents an issue + * identified during XML schema validation. + * + * @param exception + * the JSON schema validation exception generated during schema + * validation representing the issue + * @param resourceUri + * the resource the issue was found in + */ public JsonValidationFinding( @NonNull ValidationException exception, - @NonNull URI documentUri) { + @NonNull URI resourceUri) { this.exception = ObjectUtils.requireNonNull(exception, "exception"); - this.documentUri = ObjectUtils.requireNonNull(documentUri, "documentUri"); + this.documentUri = ObjectUtils.requireNonNull(resourceUri, "documentUri"); } @Override diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/XmlSchemaContentValidator.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/XmlSchemaContentValidator.java index 120598929..a28a0059e 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/XmlSchemaContentValidator.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/validation/XmlSchemaContentValidator.java @@ -50,6 +50,9 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * Supports validating an XML resource using an XML schema. + */ public class XmlSchemaContentValidator extends AbstractContentValidator { private final Schema schema; @@ -70,15 +73,30 @@ private static Schema toSchema(@NonNull List schemaSources) th return retval; } + /** + * Construct a new XML schema validator using the provided XML schema sources. + * + * @param schemaSources + * the XML schemas to use for validation + * @throws SAXException + * if an error occurred while parsing the provided XML schemas + */ public XmlSchemaContentValidator(@NonNull List schemaSources) throws SAXException { this(toSchema(ObjectUtils.requireNonNull(schemaSources, "schemaSources"))); } + /** + * Construct a new XML schema validator using the provided pre-parsed XML + * schema(s). + * + * @param schema + * the pre-parsed XML schema(s) to use for validation + */ protected XmlSchemaContentValidator(@NonNull Schema schema) { this.schema = ObjectUtils.requireNonNull(schema, "schema"); } - public Schema getSchema() { + private Schema getSchema() { return schema; } @@ -86,7 +104,7 @@ public Schema getSchema() { public IValidationResult validate(InputStream is, URI documentUri) throws IOException { Source xmlSource = new StreamSource(is, documentUri.toASCIIString()); - Validator validator = schema.newValidator(); + Validator validator = getSchema().newValidator(); XmlValidationErrorHandler errorHandler = new XmlValidationErrorHandler(documentUri); validator.setErrorHandler(errorHandler); try { @@ -97,6 +115,10 @@ public IValidationResult validate(InputStream is, URI documentUri) throws IOExce return errorHandler; } + /** + * Records an identified individual validation result found during XML schema + * validation. + */ public static class XmlValidationFinding implements IValidationFinding, IResourceLocation { @NonNull private final URI documentUri; @@ -105,13 +127,25 @@ public static class XmlValidationFinding implements IValidationFinding, IResourc @NonNull private final Level severity; + /** + * Construct a new XML schema validation finding, which represents an issue + * identified during XML schema validation. + * + * @param severity + * the finding significance + * @param exception + * the XML schema validation exception generated during schema + * validation representing the issue + * @param resourceUri + * the resource the issue was found in + */ public XmlValidationFinding( @NonNull Level severity, @NonNull SAXParseException exception, - @NonNull URI documentUri) { - this.documentUri = ObjectUtils.requireNonNull(documentUri, "documentUri"); - this.exception = ObjectUtils.requireNonNull(exception, "exception"); + @NonNull URI resourceUri) { this.severity = ObjectUtils.requireNonNull(severity, "severity"); + this.exception = ObjectUtils.requireNonNull(exception, "exception"); + this.documentUri = ObjectUtils.requireNonNull(resourceUri, "documentUri"); } @Override diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/ExternalConstraintsModulePostProcessor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/ExternalConstraintsModulePostProcessor.java index bb93284b1..8cf42476f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/ExternalConstraintsModulePostProcessor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/ExternalConstraintsModulePostProcessor.java @@ -52,11 +52,23 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A module loading post processor that integrates applicable external + * constraints into a given module when loaded. + * + * @see ModuleLoader#ModuleLoader(List) + */ public class ExternalConstraintsModulePostProcessor implements IModuleLoader.IModulePostProcessor { private static final Logger LOGGER = LogManager.getLogger(ExternalConstraintsModulePostProcessor.class); @NonNull private final List registeredConstraintSets; + /** + * Create a new post processor. + * + * @param additionalConstraintSets + * the external constraint sets to apply + */ public ExternalConstraintsModulePostProcessor(@NonNull Collection additionalConstraintSets) { this.registeredConstraintSets = ObjectUtils.notNull(additionalConstraintSets.stream() .flatMap(set -> Stream.concat( @@ -66,6 +78,11 @@ public ExternalConstraintsModulePostProcessor(@NonNull Collection getRegisteredConstraintSets() { return registeredConstraintSets; } @@ -81,23 +98,34 @@ public void processModule(IModule module) { DynamicContext dynamicContext = new DynamicContext(staticContext); for (IConstraintSet set : getRegisteredConstraintSets()) { - for (ITargetedConstraints targeted : set.getTargetedConstraintsForModule(module)) { - // apply targeted constraints - String targetExpression = targeted.getTargetExpression(); - MetapathExpression metapath = MetapathExpression.compile(targetExpression, staticContext); - ISequence items = metapath.evaluateAs(moduleItem, ResultType.SEQUENCE, dynamicContext); + assert set != null; + applyConstraints(module, moduleItem, set, visitor, dynamicContext); + } + } + + private static void applyConstraints( + @NonNull IModule module, + @NonNull IModuleNodeItem moduleItem, + @NonNull IConstraintSet set, + @NonNull ConstraintComposingVisitor visitor, + @NonNull DynamicContext dynamicContext) { + for (ITargetedConstraints targeted : set.getTargetedConstraintsForModule(module)) { + // apply targeted constraints + String targetExpression = targeted.getTargetExpression(); + MetapathExpression metapath = MetapathExpression.compile(targetExpression, dynamicContext.getStaticContext()); + ISequence items = metapath.evaluateAs(moduleItem, ResultType.SEQUENCE, dynamicContext); + assert items != null; - if (items != null && !items.isEmpty()) { - for (IItem item : items) { - if (item instanceof IDefinitionNodeItem) { - ((IDefinitionNodeItem) item).accept(visitor, targeted); - } else { - // log error - if (LOGGER.isErrorEnabled()) { - LOGGER.atError().log("Found non-definition item '{}' while applying external constraints.", - item.toString()); - } - } + for (IItem item : items) { + if (item instanceof IDefinitionNodeItem) { + ((IDefinitionNodeItem) item).accept(visitor, targeted); + } else { + // log error + if (LOGGER.isErrorEnabled()) { + LOGGER.atError().log( + "Found non-definition item '{}' while applying external constraints using target expression '{}'.", + item.toString(), + targetExpression); } } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/XmlConstraintLoader.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/XmlConstraintLoader.java index f2bd9b3ae..97e76bea1 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/XmlConstraintLoader.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/XmlConstraintLoader.java @@ -81,6 +81,7 @@ * every use. Any constraint set imported is also loaded and cached * automatically. */ +@SuppressWarnings("PMD.CouplingBetweenObjects") public class XmlConstraintLoader extends AbstractLoader implements IConstraintLoader { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/IXmlObjectBinding.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/IXmlObjectBinding.java index 7c6970d81..25e1d260f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/IXmlObjectBinding.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/IXmlObjectBinding.java @@ -31,6 +31,7 @@ import org.apache.xmlbeans.XmlObject; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; public interface IXmlObjectBinding { /** @@ -41,6 +42,13 @@ public interface IXmlObjectBinding { @NonNull XmlObject getXmlObject(); + /** + * Get the location information for this object, if the location is available. + * + * @return the location information or {@code null} if the location information + * is unavailable + */ + @Nullable default IResourceLocation getLocation() { return XmlBeansLocation.toLocation(getXmlObject()); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/ModelFactory.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/ModelFactory.java index 9e5b4acca..60d515340 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/ModelFactory.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/ModelFactory.java @@ -194,7 +194,7 @@ private static IAllowedValuesConstraint newAllowedValuesConstraint( builder.allowedValues(toAllowedValues(xmlObject)); if (xmlObject.isSetAllowOther()) { - builder.allowedOther(xmlObject.getAllowOther()); + builder.allowsOther(xmlObject.getAllowOther()); } if (xmlObject.isSetExtensible()) { builder.extensible(ObjectUtils.notNull(xmlObject.getExtensible())); @@ -325,7 +325,7 @@ public static IUniqueConstraint newUniqueConstraint( public static IIndexConstraint newIndexConstraint( @NonNull TargetedIndexConstraintType xmlObject, @NonNull ISource source) { - IIndexConstraint.Builder builder = IIndexConstraint.builder(); + IIndexConstraint.Builder builder = IIndexConstraint.builder(ObjectUtils.requireNonNull(xmlObject.getName())); applyToBuilder(xmlObject, target(xmlObject.getTarget()), source, builder); @@ -333,7 +333,6 @@ public static IIndexConstraint newIndexConstraint( builder.remarks(remarks(ObjectUtils.notNull(xmlObject.getRemarks()))); } - builder.name(ObjectUtils.requireNonNull(xmlObject.getName())); buildKeyFields(xmlObject, builder); return builder.build(); @@ -377,7 +376,8 @@ private static IIndexHasKeyConstraint newIndexHasKeyConstraint( @NonNull IndexHasKeyConstraintType xmlObject, @NonNull String target, @NonNull ISource source) { - IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(); + IIndexHasKeyConstraint.Builder builder + = IIndexHasKeyConstraint.builder(ObjectUtils.requireNonNull(xmlObject.getName())); applyToBuilder(xmlObject, target, source, builder); @@ -385,7 +385,6 @@ private static IIndexHasKeyConstraint newIndexHasKeyConstraint( builder.remarks(remarks(ObjectUtils.notNull(xmlObject.getRemarks()))); } - builder.name(ObjectUtils.requireNonNull(xmlObject.getName())); buildKeyFields(xmlObject, builder); return builder.build(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlAssemblyModelContainer.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlAssemblyModelContainer.java index 5aa6b2cb1..3f3c72ceb 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlAssemblyModelContainer.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlAssemblyModelContainer.java @@ -197,6 +197,12 @@ private static void handleChoiceGroup( // NOPMD false positive container.getModelInstances().add(instance); } + /** + * Adds the provided instance to the tail of the model. + * + * @param instance + * the instance to append + */ public void append(@NonNull IFieldInstanceAbsolute instance) { QName key = instance.getXmlQName(); getFieldInstanceMap().put(key, instance); @@ -204,6 +210,12 @@ public void append(@NonNull IFieldInstanceAbsolute instance) { getModelInstances().add(instance); } + /** + * Adds the provided instance to the tail of the model. + * + * @param instance + * the instance to append + */ public void append(@NonNull IAssemblyInstanceAbsolute instance) { QName key = instance.getXmlQName(); getAssemblyInstanceMap().put(key, instance); @@ -211,6 +223,12 @@ public void append(@NonNull IAssemblyInstanceAbsolute instance) { getModelInstances().add(instance); } + /** + * Adds the provided instance to the tail of the model. + * + * @param instance + * the instance to append + */ public void append(@NonNull IChoiceInstance instance) { getChoiceInstances().add(instance); getModelInstances().add(instance); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlBeansLocation.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlBeansLocation.java index 66c91ada4..9de1cf9ae 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlBeansLocation.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlBeansLocation.java @@ -34,11 +34,25 @@ import org.apache.xmlbeans.XmlObject; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; -public class XmlBeansLocation implements IResourceLocation { +/** + * Provides location information for an XMLBeans object. + */ +public final class XmlBeansLocation implements IResourceLocation { @NonNull private final XmlLineNumber lineNumber; + /** + * Get the location information for an XMLBeans object if the location is + * available. + * + * @param xmlObject + * the XMLBeans object to get the location information for + * @return the location information or {@code null} if the location information + * is unavailable + */ + @Nullable public static IResourceLocation toLocation(@NonNull XmlObject xmlObject) { try (XmlCursor cursor = xmlObject.newCursor()) { XmlBookmark bookmark = cursor.getBookmark(XmlLineNumber.class); @@ -46,7 +60,7 @@ public static IResourceLocation toLocation(@NonNull XmlObject xmlObject) { } } - public XmlBeansLocation(@NonNull XmlLineNumber lineNumber) { + private XmlBeansLocation(@NonNull XmlLineNumber lineNumber) { this.lineNumber = lineNumber; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlFlagContainerSupport.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlFlagContainerSupport.java index 5f3c64b7e..885852edd 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlFlagContainerSupport.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlFlagContainerSupport.java @@ -54,6 +54,10 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +/** + * Supports parsing Metaschema assembly and field XMLBeans objects that contain + * flags. + */ final class XmlFlagContainerSupport { @SuppressWarnings("PMD.UseConcurrentHashMap") @NonNull @@ -107,6 +111,7 @@ private static void handleDefineFlag( // NOPMD false positive * @param container * the field containing the flag */ + @SuppressWarnings("PMD.OnlyOneReturn") static IContainerFlagSupport newInstance( @NonNull GlobalFieldDefinitionType xmlField, @NonNull IFieldDefinition container) { @@ -129,6 +134,7 @@ static IContainerFlagSupport newInstance( * @param container * the field containing the flag */ + @SuppressWarnings("PMD.OnlyOneReturn") static IContainerFlagSupport newInstance( @NonNull InlineFieldDefinitionType xmlField, @NonNull IFieldDefinition container) { @@ -151,6 +157,7 @@ static IContainerFlagSupport newInstance( * @param container * the field containing the flag */ + @SuppressWarnings("PMD.OnlyOneReturn") static IContainerFlagSupport newInstance( @NonNull GroupedInlineFieldDefinitionType xmlField, @NonNull IFieldDefinition container, @@ -173,6 +180,7 @@ static IContainerFlagSupport newInstance( * @param container * the field containing the flag */ + @SuppressWarnings("PMD.OnlyOneReturn") static IContainerFlagSupport newInstance( @NonNull GlobalAssemblyDefinitionType xmlAssembly, @NonNull IAssemblyDefinition container) { @@ -195,6 +203,7 @@ static IContainerFlagSupport newInstance( * @param container * the field containing the flag */ + @SuppressWarnings("PMD.OnlyOneReturn") static IContainerFlagSupport newInstance( @NonNull InlineAssemblyDefinitionType xmlAssembly, @NonNull IAssemblyDefinition container) { @@ -217,6 +226,7 @@ static IContainerFlagSupport newInstance( * @param container * the field containing the flag */ + @SuppressWarnings("PMD.OnlyOneReturn") static IContainerFlagSupport newInstance( @NonNull GroupedInlineAssemblyDefinitionType xmlAssembly, @NonNull IAssemblyDefinition container, diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlModelParser.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlModelParser.java index 830333a7b..c82b98d58 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlModelParser.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlModelParser.java @@ -43,6 +43,13 @@ private XmlModelParser() { // disable construction } + /** + * Get the group-as/@in-json value based on the XMLBeans representation. + * + * @param groupAs + * the XMLBeans value + * @return the in-json value + */ @NonNull public static JsonGroupAsBehavior getJsonGroupAsBehavior(@Nullable GroupAsType groupAs) { JsonGroupAsBehavior retval = IGroupable.DEFAULT_JSON_GROUP_AS_BEHAVIOR; @@ -52,6 +59,13 @@ public static JsonGroupAsBehavior getJsonGroupAsBehavior(@Nullable GroupAsType g return retval; } + /** + * Get the group-as/@in-xml value based on the XMLBeans representation. + * + * @param groupAs + * the XMLBeans value + * @return the in-xml value + */ @NonNull public static XmlGroupAsBehavior getXmlGroupAsBehavior(@Nullable GroupAsType groupAs) { XmlGroupAsBehavior retval = IGroupable.DEFAULT_XML_GROUP_AS_BEHAVIOR; @@ -61,6 +75,13 @@ public static XmlGroupAsBehavior getXmlGroupAsBehavior(@Nullable GroupAsType gro return retval; } + /** + * Convert the XMLBeans max occurrence to an integer value. + * + * @param value + * the XMLBeans value + * @return the integer value + */ public static int getMinOccurs(@Nullable BigInteger value) { int retval = IGroupable.DEFAULT_GROUP_AS_MIN_OCCURS; if (value != null) { @@ -69,11 +90,20 @@ public static int getMinOccurs(@Nullable BigInteger value) { return retval; } + /** + * Convert the XMLBeans max occurrence to an integer value. + *

+ * If the source value is "unbounded", the the value {@code -1} is used. + * + * @param value + * the XMLBeans value + * @return the integer value + */ public static int getMaxOccurs(@Nullable Object value) { int retval = IGroupable.DEFAULT_GROUP_AS_MAX_OCCURS; if (value != null) { if (value instanceof String) { - // unbounded + // must be "unbounded" retval = -1; } else if (value instanceof BigInteger) { retval = ((BigInteger) value).intValueExact(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlModule.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlModule.java index 7efcb9750..ad0025873 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlModule.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlModule.java @@ -53,6 +53,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -217,72 +218,109 @@ private Definitions(@NonNull METASCHEMA metaschemaNode) { // handle definitions in this module // TODO: switch implementation to use the XmlObjectParser - { - // start with flag definitions - try (XmlCursor cursor = metaschemaNode.newCursor()) { - cursor.selectPath("declare namespace m='http://csrc.nist.gov/ns/oscal/metaschema/1.0';$this/m:define-flag"); - - Map flagDefinitions = new LinkedHashMap<>(); // NOPMD - intentional - while (cursor.toNextSelection()) { - GlobalFlagDefinitionType obj = ObjectUtils.notNull((GlobalFlagDefinitionType) cursor.getObject()); - XmlGlobalFlagDefinition flag = new XmlGlobalFlagDefinition(obj, XmlModule.this); // NOPMD - intentional - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("New flag definition '{}'", flag.toCoordinates()); - } - flagDefinitions.put(flag.getDefinitionQName(), flag); - } - this.flagDefinitions - = flagDefinitions.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(flagDefinitions); + try (XmlCursor cursor = metaschemaNode.newCursor()) { + assert cursor != null; + + this.flagDefinitions = parseFlags(cursor); + this.fieldDefinitions = parseFields(cursor); + this.assemblyDefinitions = parseAssemblies(cursor); + this.rootAssemblyDefinitions = this.assemblyDefinitions.isEmpty() + ? Collections.emptyMap() + : Collections.unmodifiableMap(this.assemblyDefinitions.values().stream() + .filter(IAssemblyDefinition::isRoot) + .collect(Collectors.toMap( + IAssemblyDefinition::getRootXmlQName, + Function.identity(), + (v1, v2) -> { + throw new IllegalStateException( + String.format("Duplicate root QName '%s' for root assemblies: %s and %s.", + v1.getName(), + v2.getName())); + }, + LinkedHashMap::new))); + } + } + + @SuppressWarnings({ + "PMD.UseConcurrentHashMap", + "PMD.AvoidInstantiatingObjectsInLoops" + }) + private Map parseFlags(@NonNull XmlCursor cursor) { + cursor.push(); + + // start with flag definitions + cursor.selectPath("declare namespace m='http://csrc.nist.gov/ns/oscal/metaschema/1.0';$this/m:define-flag"); + + Map flags = new LinkedHashMap<>(); + while (cursor.toNextSelection()) { + GlobalFlagDefinitionType obj = ObjectUtils.notNull((GlobalFlagDefinitionType) cursor.getObject()); + XmlGlobalFlagDefinition flag = new XmlGlobalFlagDefinition(obj, XmlModule.this); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("New flag definition '{}'", flag.toCoordinates()); } + flags.put(flag.getDefinitionQName(), flag); } - { - // now field definitions - try (XmlCursor cursor = metaschemaNode.newCursor()) { - cursor.selectPath("declare namespace m='http://csrc.nist.gov/ns/oscal/metaschema/1.0';$this/m:define-field"); - - Map fieldDefinitions = new LinkedHashMap<>(); // NOPMD - intentional - while (cursor.toNextSelection()) { - GlobalFieldDefinitionType obj = ObjectUtils.notNull((GlobalFieldDefinitionType) cursor.getObject()); - XmlGlobalFieldDefinition field = new XmlGlobalFieldDefinition(obj, XmlModule.this); // NOPMD - intentional - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("New field definition '{}'", field.toCoordinates()); - } - fieldDefinitions.put(field.getDefinitionQName(), field); - } - this.fieldDefinitions - = fieldDefinitions.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(fieldDefinitions); + cursor.pop(); + + return flags.isEmpty() + ? Collections.emptyMap() + : Collections.unmodifiableMap(flags); + } + + @SuppressWarnings({ + "PMD.UseConcurrentHashMap", + "PMD.AvoidInstantiatingObjectsInLoops" + }) + private Map parseFields(@NonNull XmlCursor cursor) { + cursor.push(); + + // now field definitions + cursor.selectPath("declare namespace m='http://csrc.nist.gov/ns/oscal/metaschema/1.0';$this/m:define-field"); + + Map fields = new LinkedHashMap<>(); + while (cursor.toNextSelection()) { + GlobalFieldDefinitionType obj = ObjectUtils.notNull((GlobalFieldDefinitionType) cursor.getObject()); + XmlGlobalFieldDefinition field = new XmlGlobalFieldDefinition(obj, XmlModule.this); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("New field definition '{}'", field.toCoordinates()); } + fields.put(field.getDefinitionQName(), field); } - { - // finally assembly definitions - Map assemblyDefinitions = new LinkedHashMap<>(); // NOPMD - intentional - Map rootAssemblyDefinitions = new LinkedHashMap<>(); // NOPMD - intentional - - try (XmlCursor cursor = metaschemaNode.newCursor()) { - cursor.selectPath( - "declare namespace m='http://csrc.nist.gov/ns/oscal/metaschema/1.0';$this/m:define-assembly"); - - while (cursor.toNextSelection()) { - GlobalAssemblyDefinitionType obj = ObjectUtils.notNull((GlobalAssemblyDefinitionType) cursor.getObject()); - XmlGlobalAssemblyDefinition assembly = new XmlGlobalAssemblyDefinition(obj, XmlModule.this); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("New assembly definition '{}'", assembly.toCoordinates()); - } - assemblyDefinitions.put(assembly.getDefinitionQName(), assembly); - if (assembly.isRoot()) { - rootAssemblyDefinitions.put(ObjectUtils.notNull(assembly.getRootXmlQName()), assembly); - } - } - - this.assemblyDefinitions - = assemblyDefinitions.isEmpty() ? Collections.emptyMap() - : Collections.unmodifiableMap(assemblyDefinitions); - this.rootAssemblyDefinitions = rootAssemblyDefinitions.isEmpty() ? Collections.emptyMap() - : Collections.unmodifiableMap(rootAssemblyDefinitions); + cursor.pop(); + + return fields.isEmpty() + ? Collections.emptyMap() + : Collections.unmodifiableMap(fields); + } + + @SuppressWarnings({ + "PMD.UseConcurrentHashMap", + "PMD.AvoidInstantiatingObjectsInLoops" + }) + private Map parseAssemblies(XmlCursor cursor) { + cursor.push(); + + // finally assembly definitions + cursor.selectPath( + "declare namespace m='http://csrc.nist.gov/ns/oscal/metaschema/1.0';$this/m:define-assembly"); + + Map assemblies = new LinkedHashMap<>(); + while (cursor.toNextSelection()) { + GlobalAssemblyDefinitionType obj = ObjectUtils.notNull((GlobalAssemblyDefinitionType) cursor.getObject()); + XmlGlobalAssemblyDefinition assembly = new XmlGlobalAssemblyDefinition(obj, XmlModule.this); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("New assembly definition '{}'", assembly.toCoordinates()); } + assemblies.put(assembly.getDefinitionQName(), assembly); } + + cursor.pop(); + + return assemblies.isEmpty() + ? Collections.emptyMap() + : Collections.unmodifiableMap(assemblies); } public Map getFlagDefinitionMap() { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlObjectParser.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlObjectParser.java index 878e179db..dd4f67e35 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlObjectParser.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/xml/impl/XmlObjectParser.java @@ -46,6 +46,14 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +/** + * Supports parsing Metaschema assembly and field XMLBeans objects that contain + * other Metaschema objects. + * + * @param + * the Java type of the state that is passed to the element parsing + * handlers + */ public class XmlObjectParser { private static final XmlOptions XML_OPTIONS = new XmlOptions().setXPathUseSaxon(false).setXPathUseXmlBeans(true); private final Map> elementNameToHandlerMap; @@ -107,12 +115,26 @@ private String getXpath() { return xpath; } + /** + * Get the resource location of the provided object. + * + * @param obj + * the XMLBeans object to get the location for + * @return the resource location or {@code null} if the location is not known + */ @SuppressWarnings({ "resource", "null" }) @Nullable public static String toLocation(@NonNull XmlObject obj) { return toLocation(obj.newCursor()); } + /** + * Get the resource location of the provided cursor. + * + * @param cursor + * the XMLBeans cursor to get the location for + * @return the resource location or {@code null} if the location is not known + */ @Nullable public static String toLocation(@NonNull XmlCursor cursor) { String retval = null; 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 6634b4271..80e5ce5c8 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 @@ -45,12 +45,8 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +@SuppressWarnings("PMD.CouplingBetweenObjects") public final class CollectionUtil { - - private CollectionUtil() { - // disable construction - } - /** * Get a {@link Stream} for the provided {@link Iterable}. * @@ -161,16 +157,44 @@ public static Iterator descendingIterator(@NonNull List list) { return ObjectUtils.notNull(retval); } + /** + * Require that the provided collection contains at least a single item. + * + * @param + * the Java type of the collection + * @param + * the Java type of the collection's items + * @param collection + * the collection to test + * @return the provided collection + * @throws IllegalStateException + * if the collection is empty + */ @NonNull - public static , A> T requireNonEmpty(@NonNull T collection) { + public static , U> T requireNonEmpty(@NonNull T collection) { if (collection.isEmpty()) { throw new IllegalStateException(); } return collection; } + /** + * Require that the provided collection contains at least a single item. + * + * @param + * the Java type of the collection + * @param + * the Java type of the collection's items + * @param collection + * the collection to test + * @param message + * the exception message to use if the collection is empty + * @return the provided collection + * @throws IllegalStateException + * if the collection is empty + */ @NonNull - public static , A> T requireNonEmpty(@NonNull T collection, @NonNull String message) { + public static , U> T requireNonEmpty(@NonNull T collection, @NonNull String message) { if (collection.isEmpty()) { throw new IllegalStateException(message); } @@ -178,14 +202,14 @@ public static , A> T requireNonEmpty(@NonNull T collecti } /** - * A wrapper of the {@link Collections#unmodifiableCollection(Collection)} - * method that ensure a {@link NonNull} result is returned. + * An implementation of {@link Collections#unmodifiableCollection(Collection)} + * that respects non-nullness. * * @param * the collection's item type * @param collection * the collection - * @return a non-null unmodifiable instance of the provided collection + * @return an unmodifiable view of the collection */ @SuppressWarnings("null") @NonNull @@ -193,69 +217,188 @@ public static Collection unmodifiableCollection(@NonNull Collection co return Collections.unmodifiableCollection(collection); } + /** + * An implementation of {@link Collections#singleton(Object)} that respects + * non-nullness. + * + * @param + * the Java type of the set items + * @param instance + * the singleton item to use + * @return an unmodifiable set containing the singleton item + */ @SuppressWarnings("null") @NonNull - public static Set singleton(@NonNull T value) { - return Collections.singleton(value); + public static Set singleton(@NonNull T instance) { + return Collections.singleton(instance); } + /** + * An implementation of {@link Collections#emptySet()} that respects + * non-nullness. + * + * @param + * the Java type of the set items + * @return an unmodifiable empty set + */ @SuppressWarnings("null") @NonNull public static Set emptySet() { return Collections.emptySet(); } + /** + * An implementation of {@link Collections#unmodifiableSet(Set)} that respects + * non-nullness. + * + * @param + * the Java type of the set items + * @param set + * the set to prevent modification of + * @return an unmodifiable view of the set + */ @SuppressWarnings("null") @NonNull public static Set unmodifiableSet(@NonNull Set set) { return Collections.unmodifiableSet(set); } + /** + * Provides an unmodifiable list containing the provided list. + *

+ * If the provided list is {@code null}, an empty list will be provided. + * + * @param + * the Java type of the list items + * @param list + * the list, which may be {@code null} + * @return an unmodifiable list containing the items + */ @NonNull public static List listOrEmpty(@Nullable List list) { - return list == null ? emptyList() : list; + return list == null ? emptyList() : unmodifiableList(list); } + /** + * Generates a new unmodifiable list containing the provided items. + *

+ * If the provided array is {@code null}, an empty list will be provided. + * + * @param + * the Java type of the list items + * @param array + * the array of items to use to populate the list, which may be + * {@code null} + * @return an unmodifiable list containing the items + */ @SafeVarargs @SuppressWarnings("null") @NonNull public static List listOrEmpty(@Nullable T... array) { - return array == null || array.length == 0 ? emptyList() : Arrays.asList(array); + return array == null || array.length == 0 ? emptyList() : unmodifiableList(Arrays.asList(array)); } + /** + * An implementation of {@link Collections#emptyList()} that respects + * non-nullness. + * + * @param + * the Java type of the list items + * @return an unmodifiable empty list + */ @SuppressWarnings("null") @NonNull public static List emptyList() { return Collections.emptyList(); } + /** + * An implementation of {@link Collections#unmodifiableList(List)} that respects + * non-nullness. + * + * @param + * the Java type of the list items + * @param list + * the list to prevent modification of + * @return an unmodifiable view of the list + */ @SuppressWarnings("null") @NonNull public static List unmodifiableList(@NonNull List list) { return Collections.unmodifiableList(list); } + /** + * An implementation of {@link Collections#singletonList(Object)} that respects + * non-nullness. + * + * @param + * the Java type of the list items + * @param instance + * the singleton item to use + * @return an unmodifiable list containing the singleton item + */ @SuppressWarnings("null") @NonNull public static List singletonList(@NonNull T instance) { return Collections.singletonList(instance); } + /** + * An implementation of {@link Collections#emptyMap()} that respects + * non-nullness. + * + * @param + * the Java type of the map's keys + * @param + * the Java type of the map's values + * @return an unmodifiable empty map + */ @SuppressWarnings("null") @NonNull public static Map emptyMap() { return Collections.emptyMap(); } + /** + * An implementation of {@link Collections#singletonMap(Object, Object)} that + * respects non-nullness. + * + * @param + * the Java type of the map's keys + * @param + * the Java type of the map's values + * @param key + * the singleton key + * @param value + * the singleton value + * @return an unmodifiable map containing the singleton entry + */ @SuppressWarnings("null") @NonNull public static Map singletonMap(@NonNull K key, @NonNull V value) { return Collections.singletonMap(key, value); } + /** + * An implementation of {@link Collections#unmodifiableMap(Map)} that respects + * non-nullness. + * + * @param map + * the map to prevent modification of + * @param + * the Java type of the map's keys + * @param + * the Java type of the map's values + * @return an unmodifiable view of the map + */ @SuppressWarnings("null") @NonNull public static Map unmodifiableMap(@NonNull Map map) { return Collections.unmodifiableMap(map); } + + private CollectionUtil() { + // disable construction + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/util/CustomCollectors.java b/core/src/main/java/gov/nist/secauto/metaschema/core/util/CustomCollectors.java index 532e8f6b0..4975f1e50 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/util/CustomCollectors.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/util/CustomCollectors.java @@ -40,17 +40,28 @@ import edu.umd.cs.findbugs.annotations.NonNull; +@SuppressWarnings("PMD.CouplingBetweenObjects") public final class CustomCollectors { - private CustomCollectors() { - // disable - } - + /** + * An implementation of {@link Function#identity()} that respects non-nullness. + * + * @param + * the Java type of the identity object + * @return the identity function + */ @SuppressWarnings("null") @NonNull public static Function identity() { return Function.identity(); } + /** + * Joins a sequence of string values using oxford-style serial commas. + * + * @param conjunction + * the conjunction to use after the penultimate comma (e.g., and, or) + * @return a collector that will perform the joining + */ public static Collector joiningWithOxfordComma(@NonNull String conjunction) { return Collectors.collectingAndThen(Collectors.toList(), withOxfordComma(conjunction)); } @@ -124,6 +135,26 @@ public static Stream distinctByKey( return uniqueRoles.values().stream(); } + /** + * Produces a map collector that uses the provided key and value mappers, and a + * duplicate hander to manage duplicate key insertion. + * + * @param + * the item Java type + * @param + * the map key Java type + * @param + * the map value Java type + * @param keyMapper + * the function used to produce the map's key based on the provided + * item + * @param valueMapper + * the function used to produce the map's value based on the provided + * item + * @param duplicateHander + * the handler used to manage duplicate key insertion + * @return the collector + */ @NonNull public static Collector> toMap( @NonNull Function keyMapper, @@ -132,6 +163,30 @@ public static Stream distinctByKey( return toMap(keyMapper, valueMapper, duplicateHander, HashMap::new); } + /** + * Produces a map collector that uses the provided key and value mappers, and a + * duplicate hander to manage duplicate key insertion. + * + * @param + * the item Java type + * @param + * the map key Java type + * @param + * the map value Java type + * @param + * the Java type of the resulting map + * @param keyMapper + * the function used to produce the map's key based on the provided + * item + * @param valueMapper + * the function used to produce the map's value based on the provided + * item + * @param duplicateHander + * the handler used to manage duplicate key insertion + * @param supplier + * the supplier used to create the resulting map + * @return the collector + */ @NonNull public static > Collector toMap( @NonNull Function keyMapper, @@ -163,19 +218,57 @@ public static Stream distinctByKey( })); } + /** + * A handler that supports resolving duplicate keys while inserting values into + * a map. + * + * @param + * the Java type of the map's keys + * @param + * the Java type of the map's values + */ @FunctionalInterface public interface DuplicateHandler { + /** + * The handler callback. + * + * @param key + * the duplicate key + * @param value1 + * the first value associated with the key + * @param value2 + * the second value associated with the key + * @return the value to insert into the map + */ @NonNull V handle(K key, @NonNull V value1, V value2); } + /** + * A binary operator that will always use the first of two values. + * + * @param + * the item type + * @return the operator + */ @NonNull public static BinaryOperator useFirstMapper() { return (value1, value2) -> value1; } + /** + * A binary operator that will always use the second of two values. + * + * @param + * the item type + * @return the operator + */ @NonNull public static BinaryOperator useLastMapper() { return (value1, value2) -> value2; } + + private CustomCollectors() { + // disable construction + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/util/IVersionInfo.java b/core/src/main/java/gov/nist/secauto/metaschema/core/util/IVersionInfo.java index 73057178a..0f049e490 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/util/IVersionInfo.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/util/IVersionInfo.java @@ -28,25 +28,63 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * Provides version information for a runtime dependency or application. + */ public interface IVersionInfo { + /** + * The subject's name. + * + * @return the name + */ @NonNull String getName(); + /** + * The subject's version. + * + * @return the version + */ @NonNull String getVersion(); + /** + * The time the subject was last built. + * + * @return the build time + */ @NonNull String getBuildTimestamp(); + /** + * The git repository URL used to retrieve the branch. + * + * @return the git repository URL + */ @NonNull String getGitOriginUrl(); + /** + * The last git commit hash. + * + * @return the commit hash + */ @NonNull String getGitCommit(); + /** + * The current git branch. + * + * @return the git branch + */ @NonNull String getGitBranch(); + /** + * The closest tag in the git commit history. + * + * @return a tag name + */ @NonNull String getGitClosestTag(); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/util/UriUtils.java b/core/src/main/java/gov/nist/secauto/metaschema/core/util/UriUtils.java index 6d7dada9c..35da35e76 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/util/UriUtils.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/util/UriUtils.java @@ -129,6 +129,7 @@ private static boolean hasSameSchemeAndAuthority(URI base, URI other) { * the URI to relativize against the base * @return the relativized URI */ + @SuppressWarnings("PMD.CyclomaticComplexity") public static String prependRelativePath(String base, String target) { // Split paths into segments diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/TestUtils.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/TestUtils.java index f987a9791..91e1b147b 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/TestUtils.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/TestUtils.java @@ -53,10 +53,6 @@ import edu.umd.cs.findbugs.annotations.Nullable; public final class TestUtils { - private TestUtils() { - // disable construction - } - @NonNull public static ISequence sequence() { return ISequence.of(); @@ -154,4 +150,8 @@ public static ISequence executeFunction( context, focusSeqence); } + + private TestUtils() { + // disable construction + } } diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/BuildCstVisitorTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/BuildCstVisitorTest.java index b941dece8..c483d2995 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/BuildCstVisitorTest.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/BuildCstVisitorTest.java @@ -62,6 +62,7 @@ import gov.nist.secauto.metaschema.core.metapath.item.node.IFlagNodeItem; import gov.nist.secauto.metaschema.core.metapath.item.node.IRootAssemblyNodeItem; import gov.nist.secauto.metaschema.core.metapath.item.node.MockNodeItemFactory; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; @@ -84,13 +85,19 @@ @SuppressWarnings("PMD.TooManyStaticImports") class BuildCstVisitorTest { - private static final URI NS_URI = URI.create("http://example.com/ns"); - private static final String NS = NS_URI.toASCIIString(); - + @NonNull + private static final URI NS_URI = ObjectUtils.notNull(URI.create("http://example.com/ns")); + @NonNull + private static final String NS = ObjectUtils.notNull(NS_URI.toASCIIString()); + @NonNull private static final QName ROOT = new QName(NS, "root"); + @NonNull private static final QName FIELD1 = new QName(NS, "field1"); + @NonNull private static final QName FIELD2 = new QName(NS, "field2"); + @NonNull private static final QName UUID = new QName(NS, "uuid"); + @NonNull private static final QName FLAG = new QName("flag"); @RegisterExtension diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/model/constraint/DefaultConstraintValidatorTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/model/constraint/DefaultConstraintValidatorTest.java index 9f4567f63..2b3b6b636 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/model/constraint/DefaultConstraintValidatorTest.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/model/constraint/DefaultConstraintValidatorTest.java @@ -83,7 +83,7 @@ void testAllowedValuesAllowOther() { IAllowedValuesConstraint allowedValues = IAllowedValuesConstraint.builder() .source(ISource.modelSource()) .allowedValue(IAllowedValue.of("other", MarkupLine.fromMarkdown("some documentation"))) - .allowedOther(true) + .allowsOther(true) .build(); DynamicContext dynamicContext = new DynamicContext(); @@ -129,12 +129,12 @@ void testAllowedValuesMultipleAllowOther() { IAllowedValuesConstraint allowedValues1 = IAllowedValuesConstraint.builder() .source(ISource.modelSource()) .allowedValue(IAllowedValue.of("other", MarkupLine.fromMarkdown("some documentation"))) - .allowedOther(true) + .allowsOther(true) .build(); IAllowedValuesConstraint allowedValues2 = IAllowedValuesConstraint.builder() .source(ISource.modelSource()) .allowedValue(IAllowedValue.of("other2", MarkupLine.fromMarkdown("some documentation"))) - .allowedOther(true) + .allowsOther(true) .build(); List allowedValuesConstraints @@ -184,12 +184,12 @@ void testMultipleAllowedValuesConflictingAllowOther() { IAllowedValuesConstraint allowedValues1 = IAllowedValuesConstraint.builder() .source(ISource.modelSource()) .allowedValue(IAllowedValue.of("other", MarkupLine.fromMarkdown("some documentation"))) - .allowedOther(true) + .allowsOther(true) .build(); IAllowedValuesConstraint allowedValues2 = IAllowedValuesConstraint.builder() .source(ISource.modelSource()) .allowedValue(IAllowedValue.of("other2", MarkupLine.fromMarkdown("some documentation"))) - .allowedOther(false) + .allowsOther(false) .build(); List allowedValuesConstraints diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/util/UriUtilsTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/util/UriUtilsTest.java index 1ead33a92..6a95f7742 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/util/UriUtilsTest.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/util/UriUtilsTest.java @@ -26,12 +26,16 @@ package gov.nist.secauto.metaschema.core.util; +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.assertTrue; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; @@ -41,34 +45,38 @@ import edu.umd.cs.findbugs.annotations.NonNull; class UriUtilsTest { - private static final boolean VALID = true; - private static final boolean INVALID = false; - - private static Stream provideValuesTestToUri() { + private static Stream provideValuesTestToUri() throws MalformedURLException, URISyntaxException { + String base = Paths.get("").toAbsolutePath().toUri().toURL().toURI().toASCIIString(); return Stream.of( - Arguments.of("http://example.org/valid", VALID), - Arguments.of("https://example.org/valid", VALID), - Arguments.of("http://example.org/valid", VALID), - Arguments.of("ftp://example.org/valid", VALID), - Arguments.of("ssh://example.org/valid", VALID), - Arguments.of("example.org/good", VALID), - Arguments.of("bad.txt", VALID), - Arguments.of("relative\\windows\\path\\resource.txt", VALID), - Arguments.of("C:\\absolute\\valid.txt", VALID), - Arguments.of("local/relative/path/is/invalid.txt", VALID), - Arguments.of("/absolute/local/path/is/invalid.txt", VALID), - Arguments.of("1;", VALID)); + Arguments.of("http://example.org/valid", "http://example.org/valid", true), + Arguments.of("https://example.org/valid", "https://example.org/valid", true), + Arguments.of("http://example.org/valid", "http://example.org/valid", true), + Arguments.of("ftp://example.org/valid", "ftp://example.org/valid", true), + // Arguments.of("ssh://example.org/valid", "ssh://example.org/valid", true), + Arguments.of("example.org/good", base + "example.org/good", true), + Arguments.of("bad.txt", base + "bad.txt", true), + // Arguments.of("relative\\windows\\path\\resource.txt", base + + // "relative/windows/path/resource.txt", true), + // Arguments.of("C:\\absolute\\valid.txt", "C:\\absolute\\valid.txt",true), + Arguments.of("local/relative/path/is/invalid.txt", base + "local/relative/path/is/invalid.txt", true), + // Arguments.of("/absolute/local/path/is/invalid.txt", true), + Arguments.of("1;", base + "1;", true)); } @ParameterizedTest @MethodSource("provideValuesTestToUri") - void testToUri(@NonNull String location, boolean expectedResult) throws URISyntaxException { - boolean result = INVALID; + void testToUri(@NonNull String location, @NonNull String expectedLocation, boolean expectedResult) + throws MalformedURLException { Path cwd = Paths.get(""); - URI uri = UriUtils.toUri(location, cwd.toAbsolutePath().toUri()); - result = VALID; - // System.out.println(String.format("%s -> %s", location, uri.toASCIIString())); - assertEquals(result, expectedResult); + try { + URI uri = UriUtils.toUri(location, ObjectUtils.notNull(cwd.toAbsolutePath().toUri())).normalize().toURL().toURI(); + System.out.println(String.format("%s -> %s", location, uri.toASCIIString())); + assertAll( + () -> assertEquals(uri.toASCIIString(), expectedLocation), + () -> assertTrue(expectedResult)); + } catch (URISyntaxException ex) { + assertFalse(expectedResult); + } } private static Stream provideArgumentsTestRelativize() { diff --git a/databind-metaschema/src/main/java/gov/nist/secauto/metaschema/modules/sarif/SarifValidationHandler.java b/databind-metaschema/src/main/java/gov/nist/secauto/metaschema/modules/sarif/SarifValidationHandler.java index 0219cc0b0..0e2f7ff5f 100644 --- a/databind-metaschema/src/main/java/gov/nist/secauto/metaschema/modules/sarif/SarifValidationHandler.java +++ b/databind-metaschema/src/main/java/gov/nist/secauto/metaschema/modules/sarif/SarifValidationHandler.java @@ -349,11 +349,10 @@ protected void message(@NonNull IValidationFinding finding, @NonNull Result resu if (message == null) { message = ""; } - if (message != null) { - Message msg = new Message(); - msg.setText(message); - result.setMessage(msg); - } + + Message msg = new Message(); + msg.setText(message); + result.setMessage(msg); } protected void location(@NonNull IValidationFinding finding, @NonNull Result result, @NonNull URI base) diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/impl/ConstraintFactory.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/impl/ConstraintFactory.java index ef970c78e..97c8a24c6 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/impl/ConstraintFactory.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/impl/ConstraintFactory.java @@ -205,7 +205,7 @@ static IAllowedValuesConstraint newAllowedValuesConstraint( applyRemarks(builder, constraint.remarks()); applyAllowedValues(builder, constraint); - builder.allowedOther(constraint.allowOthers()); + builder.allowsOther(constraint.allowOthers()); builder.extensible(constraint.extensible()); return builder.build(); @@ -271,7 +271,7 @@ static IUniqueConstraint newUniqueConstraint(@NonNull IsUnique constraint, @NonN @NonNull static IIndexConstraint newIndexConstraint(@NonNull Index constraint, @NonNull ISource source) { - IIndexConstraint.Builder builder = IIndexConstraint.builder(); + IIndexConstraint.Builder builder = IIndexConstraint.builder(constraint.name()); applyId(builder, constraint.id()); applyFormalName(builder, constraint.formalName()); applyDescription(builder, constraint.description()); @@ -282,7 +282,6 @@ static IIndexConstraint newIndexConstraint(@NonNull Index constraint, @NonNull I applyProperties(builder, constraint.properties()); applyRemarks(builder, constraint.remarks()); - builder.name(constraint.name()); applyKeyFields(builder, constraint.keyFields()); return builder.build(); @@ -292,7 +291,7 @@ static IIndexConstraint newIndexConstraint(@NonNull Index constraint, @NonNull I static IIndexHasKeyConstraint newIndexHasKeyConstraint( @NonNull IndexHasKey constraint, @NonNull ISource source) { - IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(); + IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(constraint.indexName()); applyId(builder, constraint.id()); applyFormalName(builder, constraint.formalName()); applyDescription(builder, constraint.description()); @@ -303,7 +302,6 @@ static IIndexHasKeyConstraint newIndexHasKeyConstraint( applyProperties(builder, constraint.properties()); applyRemarks(builder, constraint.remarks()); - builder.name(constraint.indexName()); applyKeyFields(builder, constraint.keyFields()); return builder.build(); diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/impl/ConstraintBindingSupport.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/impl/ConstraintBindingSupport.java index f1621475c..6334caaa6 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/impl/ConstraintBindingSupport.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/metaschema/impl/ConstraintBindingSupport.java @@ -178,7 +178,7 @@ private static IAllowedValuesConstraint newAllowedValues( @NonNull FlagAllowedValues obj, @NonNull ISource source) { IAllowedValuesConstraint.Builder builder = IAllowedValuesConstraint.builder() - .allowedOther(ModelSupport.yesOrNo(obj.getAllowOther())) + .allowsOther(ModelSupport.yesOrNo(obj.getAllowOther())) .extensible(extensible(obj.getExtensible())); applyCommonValues(obj, null, source, builder); @@ -193,7 +193,7 @@ private static IAllowedValuesConstraint newAllowedValues( @NonNull TargetedAllowedValuesConstraint obj, @NonNull ISource source) { IAllowedValuesConstraint.Builder builder = IAllowedValuesConstraint.builder() - .allowedOther(ModelSupport.yesOrNo(obj.getAllowOther())) + .allowsOther(ModelSupport.yesOrNo(obj.getAllowOther())) .extensible(extensible(ObjectUtils.requireNonNull(obj.getExtensible()))); applyCommonValues(obj, obj.getTarget(), source, builder); @@ -255,8 +255,7 @@ private static IExpectConstraint newExpect( private static IIndexHasKeyConstraint newIndexHasKey( @NonNull FlagIndexHasKey obj, @NonNull ISource source) { - IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder() - .name(ObjectUtils.requireNonNull(obj.getName())); + IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(ObjectUtils.requireNonNull(obj.getName())); applyCommonValues(obj, null, source, builder); handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder); return builder.build(); @@ -266,8 +265,7 @@ private static IIndexHasKeyConstraint newIndexHasKey( private static IIndexHasKeyConstraint newIndexHasKey( @NonNull TargetedIndexHasKeyConstraint obj, @NonNull ISource source) { - IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder() - .name(ObjectUtils.requireNonNull(obj.getName())); + IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(ObjectUtils.requireNonNull(obj.getName())); applyCommonValues(obj, obj.getTarget(), source, builder); handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder); return builder.build(); @@ -319,8 +317,7 @@ private static IMatchesConstraint newMatches( private static IIndexConstraint newIndex( @NonNull TargetedIndexConstraint obj, @NonNull ISource source) { - IIndexConstraint.Builder builder = IIndexConstraint.builder() - .name(ObjectUtils.requireNonNull(obj.getName())); + IIndexConstraint.Builder builder = IIndexConstraint.builder(ObjectUtils.requireNonNull(obj.getName())); applyCommonValues(obj, obj.getTarget(), source, builder); handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder); 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 78d16eb18..1944bc22a 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 @@ -353,7 +353,6 @@ private DynamicContainer generateScenario( Stream.concat(Stream.of(validateSchema), contentTests).sequential()); } - @SuppressWarnings("unchecked") protected Path convertContent( @NonNull URI contentUri, @NonNull Path generationPath, diff --git a/schemagen/src/main/java/gov/nist/secauto/metaschema/schemagen/json/impl/AssemblyDefinitionJsonSchema.java b/schemagen/src/main/java/gov/nist/secauto/metaschema/schemagen/json/impl/AssemblyDefinitionJsonSchema.java index 792403494..5db38df96 100644 --- a/schemagen/src/main/java/gov/nist/secauto/metaschema/schemagen/json/impl/AssemblyDefinitionJsonSchema.java +++ b/schemagen/src/main/java/gov/nist/secauto/metaschema/schemagen/json/impl/AssemblyDefinitionJsonSchema.java @@ -169,7 +169,7 @@ protected void generateBody( protected void generateChoices( List propertyChoices, @NonNull ObjectNode definitionNode, - @NonNull IJsonGenerationState state) throws IOException { + @NonNull IJsonGenerationState state) { ArrayNode anyOfdNode = ObjectUtils.notNull(JsonNodeFactory.instance.arrayNode()); for (PropertyCollection propertyChoice : propertyChoices) { ObjectNode choiceDefinitionNode = ObjectUtils.notNull(JsonNodeFactory.instance.objectNode());