diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/AbstractMarkupString.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/AbstractMarkupString.java index 5b5f7ddda..bef3a08e7 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/AbstractMarkupString.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/AbstractMarkupString.java @@ -38,8 +38,8 @@ import gov.nist.secauto.metaschema.core.datatype.markup.flexmark.FlexmarkFactory; import gov.nist.secauto.metaschema.core.datatype.markup.flexmark.IMarkupVisitor; import gov.nist.secauto.metaschema.core.datatype.markup.flexmark.IMarkupWriter; +import gov.nist.secauto.metaschema.core.datatype.markup.flexmark.InsertAnchorExtension; import gov.nist.secauto.metaschema.core.datatype.markup.flexmark.InsertAnchorExtension.InsertAnchorNode; -import gov.nist.secauto.metaschema.core.datatype.markup.flexmark.InsertVisitor; import gov.nist.secauto.metaschema.core.datatype.markup.flexmark.MarkupVisitor; import gov.nist.secauto.metaschema.core.datatype.markup.flexmark.MarkupXmlEventWriter; import gov.nist.secauto.metaschema.core.datatype.markup.flexmark.MarkupXmlStreamWriter; @@ -288,7 +288,7 @@ public List getInserts() { @Override @NonNull public List getInserts(@NonNull Predicate filter) { - InsertVisitor visitor = new InsertVisitor(filter); + InsertAnchorExtension.InsertVisitor visitor = new InsertAnchorExtension.InsertVisitor(filter); visitor.visitChildren(getDocument()); return visitor.getInserts(); } 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..eac3f871b 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); @@ -66,18 +66,32 @@ protected static DataSet newParserOptions() { Collection currentExtensions = Parser.EXTENSIONS.get(FlexmarkConfiguration.FLEXMARK_CONFIG); List extensions = new LinkedList<>(currentExtensions); - extensions.add(SuppressPTagExtension.create()); + extensions.add(SuppressPTagExtension.newInstance()); Parser.EXTENSIONS.set(options, extensions); 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/FlexmarkConfiguration.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/FlexmarkConfiguration.java index 7c1c4b005..2c8bf7310 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/FlexmarkConfiguration.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/FlexmarkConfiguration.java @@ -65,13 +65,13 @@ private static DataSet initFlexmarkConfig() { List extensions = List.of( // Metaschema insert - InsertAnchorExtension.create(), + InsertAnchorExtension.newInstance(), // q tag handling - HtmlQuoteTagExtension.create(), + HtmlQuoteTagExtension.newInstance(), TypographicExtension.create(), TablesExtension.create(), // fix for code handling - HtmlCodeRenderExtension.create(), + HtmlCodeRenderExtension.newInstance(), // to ensure that escaped characters are not lost EscapedCharacterExtension.create(), SuperscriptExtension.create(), diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/FlexmarkFactory.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/FlexmarkFactory.java index 01b3ddbc2..4a8c6c36d 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/FlexmarkFactory.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/FlexmarkFactory.java @@ -35,6 +35,10 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * Provides factory methods for Flexmark processing to support HTML-to-markdown + * and markdown-to-HTML conversion. + */ @SuppressWarnings("PMD.DataClass") public final class FlexmarkFactory { @NonNull @@ -50,12 +54,25 @@ public final class FlexmarkFactory { @NonNull final ListOptions listOptions; + /** + * Get the static Flexmark factory instance. + * + * @return the instance + */ @SuppressWarnings("PMD.AvoidSynchronizedAtMethodLevel") @NonNull public static synchronized FlexmarkFactory instance() { return SINGLETON; } + /** + * Get a Flexmark factory instance that uses the provided Flexmark + * configuration. + * + * @param config + * the Flexmark configuration + * @return the instance + */ @NonNull public static FlexmarkFactory newInstance(@NonNull DataHolder config) { return new FlexmarkFactory(config); @@ -76,26 +93,54 @@ private FlexmarkFactory(@NonNull DataHolder config) { this.listOptions = ListOptions.get(config); } + /** + * Get configured options for processing HTML and markdown lists. + * + * @return the options + */ @NonNull public ListOptions getListOptions() { return listOptions; } + /** + * Get the Flexmark markdown parser, which can produce a markdown syntax tree. + * + * @return the parser + */ @NonNull public Parser getMarkdownParser() { return markdownParser; } + /** + * Get the Flexmark HTML renderer, which can produce HTML from a markdown syntax + * tree. + * + * @return the parser + */ @NonNull public HtmlRenderer getHtmlRenderer() { return htmlRenderer; } + /** + * Get the Flexmark formatter, which can produce markdown from a markdown syntax + * tree. + * + * @return the parser + */ @NonNull public Formatter getFormatter() { return formatter; } + /** + * Get the Flexmark HTML converter, which can produce markdown from HTML + * content. + * + * @return the parser + */ @NonNull public FlexmarkHtmlConverter getFlexmarkHtmlConverter() { return htmlConverter; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/HtmlCodeRenderExtension.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/HtmlCodeRenderExtension.java index e202be5fc..2b1521d9a 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/HtmlCodeRenderExtension.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/HtmlCodeRenderExtension.java @@ -57,7 +57,12 @@ public class HtmlCodeRenderExtension implements HtmlRenderer.HtmlRendererExtension { private static final Pattern EOL_PATTERN = Pattern.compile("\r\n|\r|\n"); - public static HtmlCodeRenderExtension create() { + /** + * Construct a new extension instance. + * + * @return the instance + */ + public static HtmlCodeRenderExtension newInstance() { return new HtmlCodeRenderExtension(); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/HtmlQuoteTagExtension.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/HtmlQuoteTagExtension.java index 894eb506f..1d52640e7 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/HtmlQuoteTagExtension.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/HtmlQuoteTagExtension.java @@ -61,7 +61,12 @@ public class HtmlQuoteTagExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, FlexmarkHtmlConverter.HtmlConverterExtension { - public static HtmlQuoteTagExtension create() { + /** + * Construct a new extension instance. + * + * @return the instance + */ + public static HtmlQuoteTagExtension newInstance() { return new HtmlQuoteTagExtension(); } @@ -172,6 +177,12 @@ public HtmlNodeRenderer apply(DataHolder options) { public static class DoubleQuoteNode extends TypographicQuotes { + /** + * Construct a new double quote node. + * + * @param node + * the typographic information pertaining to a double quote + */ @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") public DoubleQuoteNode(TypographicQuotes node) { super(node.getOpeningMarker(), node.getText(), node.getClosingMarker()); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/IMarkupVisitor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/IMarkupVisitor.java index d8a0a119b..8c81288e8 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/IMarkupVisitor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/IMarkupVisitor.java @@ -33,5 +33,15 @@ @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_THROWABLE") public interface IMarkupVisitor { + /** + * A visitor callback used to visit a markdown syntax tree. + * + * @param document + * the markdown syntax tree + * @param writer + * a markup writer used to generate markup output + * @throws E + * the visitor exception Java type + */ void visitDocument(@NonNull Document document, @NonNull IMarkupWriter writer) throws E; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/IMarkupWriter.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/IMarkupWriter.java index b95169762..253e50176 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/IMarkupWriter.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/IMarkupWriter.java @@ -41,7 +41,6 @@ import com.vladsch.flexmark.ast.IndentedCodeBlock; import com.vladsch.flexmark.ast.Link; import com.vladsch.flexmark.ast.ListBlock; -import com.vladsch.flexmark.ast.ListItem; import com.vladsch.flexmark.ast.MailLink; import com.vladsch.flexmark.ast.Paragraph; import com.vladsch.flexmark.ast.Text; @@ -58,8 +57,6 @@ import java.util.Map; -import javax.xml.namespace.QName; - import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -67,9 +64,18 @@ @SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_THROWABLE", justification = "There is a need to support varying exceptions from multiple stream writers") public interface IMarkupWriter { // NOPMD - @NonNull - QName asQName(@NonNull String localName); - + /** + * Write an HTML element with the provided local name, with no attributes. + * + * @param localName + * the element name + * @param node + * the markup node related to the element + * @param childHandler + * a handler used to process child node content + * @throws E + * if an error occurred while writing the markup + */ default void writeElement( @NonNull String localName, @NonNull Node node, @@ -77,135 +83,336 @@ default void writeElement( writeElement(localName, node, CollectionUtil.emptyMap(), childHandler); } - default void writeElement( - @NonNull String localName, - @NonNull Node node, - @NonNull Map attributes, - @Nullable ChildHandler childHandler) throws E { - QName qname = asQName(localName); - writeElement(qname, node, attributes, childHandler); - } - + /** + * Write an HTML element with the provided local name. + * + * @param localName + * the element name + * @param node + * the markup node related to the element + * @param attributes + * attributes associated with the element to also write + * @param childHandler + * the handler used to process child node content or {@code null} + * @throws E + * if an error occurred while writing the markup + */ void writeElement( - @NonNull QName qname, + @NonNull String localName, @NonNull Node node, @NonNull Map attributes, @Nullable ChildHandler childHandler) throws E; - default void writeEmptyElement( - @NonNull String localName, - @NonNull Map attributes) throws E { - QName qname = asQName(localName); - writeEmptyElement(qname, attributes); - } - + /** + * Write an empty HTML element with the provided local name. + * + * @param localName + * the element name + * @param attributes + * attributes associated with the element to also write + * @throws E + * if an error occurred while writing the markup + */ void writeEmptyElement( - @NonNull QName qname, - @NonNull Map attributes) throws E; - - default void writeElementStart( - @NonNull QName qname) throws E { - writeElementStart(qname, CollectionUtil.emptyMap()); - } - - void writeElementStart( - @NonNull QName qname, + @NonNull String localName, @NonNull Map attributes) throws E; - void writeElementEnd(@NonNull QName qname) throws E; - + /** + * Write a text node. + * + * @param node + * the text node to write + * @throws E + * if an error occurred while writing + */ void writeText(@NonNull Text node) throws E; /** - * Handle a combination of {@link Text} and {@link EscapedCharacter} node + * Write a combination of {@link Text} and {@link EscapedCharacter} node * children. * * @param node * the text node to write * @throws E - * if an error occured while writing + * if an error occurred while writing */ void writeText(@NonNull TextBase node) throws E; + /** + * Write a text string. + * + * @param text + * the text to write + * @throws E + * if an error occurred while writing + */ void writeText(@NonNull CharSequence text) throws E; + /** + * Write an HTML entity. + * + * @param node + * the entity node + * @throws E + * if an error occurred while writing + */ void writeHtmlEntity(@NonNull HtmlEntity node) throws E; + /** + * Write an HTML entity for a typographic character. + * + * @param node + * the entity node + * @throws E + * if an error occurred while writing + */ void writeHtmlEntity(@NonNull TypographicSmarts node) throws E; + /** + * Write a paragraph. + * + * @param node + * the paragraph node + * @param childHandler + * the handler used to process child node content + * @throws E + * if an error occurred while writing + */ void writeParagraph( @NonNull Paragraph node, @NonNull ChildHandler childHandler) throws E; + /** + * Write a link. + * + * @param node + * the link node + * @param childHandler + * the handler used to process child node content + * @throws E + * if an error occurred while writing + */ void writeLink( @NonNull Link node, @NonNull ChildHandler childHandler) throws E; + /** + * Write an email link. + * + * @param node + * the link node + * @throws E + * if an error occurred while writing + */ void writeLink(@NonNull MailLink node) throws E; + /** + * Write a link that was auto-detected. + * + * @param node + * the link node + * @throws E + * if an error occurred while writing + */ void writeLink(@NonNull AutoLink node) throws E; + /** + * Write a typographic quote(s). + * + * @param node + * the quote node + * @param childHandler + * the handler used to process child node content + * @throws E + * if an error occurred while writing + */ void writeTypographicQuotes( @NonNull TypographicQuotes node, @NonNull ChildHandler childHandler) throws E; + /** + * Write embedded HTML content. + * + * @param node + * the HTML content + * @throws E + * if an error occurred while writing + */ void writeInlineHtml(@NonNull HtmlInline node) throws E; + /** + * Write HTML block content. + * + * @param node + * the HTML block + * @throws E + * if an error occurred while writing + */ void writeBlockHtml(@NonNull HtmlBlock node) throws E; + /** + * Write a table. + * + * @param node + * the table node + * @param childHandler + * the handler used to process child node content + * @throws E + * if an error occurred while writing + */ void writeTable( @NonNull TableBlock node, - @NonNull ChildHandler cellChilddHandler) throws E; + @NonNull ChildHandler childHandler) throws E; + /** + * Write an image. + * + * @param node + * the image node + * @throws E + * if an error occurred while writing + */ void writeImage(@NonNull Image node) throws E; + /** + * Write a Metaschema markup insertion point. + * + * @param node + * the insert node + * @throws E + * if an error occurred while writing + */ void writeInsertAnchor(@NonNull InsertAnchorNode node) throws E; + /** + * Write a heading. + * + * @param node + * the heading node + * @param childHandler + * the handler used to process child node content + * @throws E + * if an error occurred while writing + */ void writeHeading( @NonNull Heading node, @NonNull ChildHandler childHandler) throws E; + /** + * Write an inline code block. + * + * @param node + * the code node + * @param childHandler + * the handler used to process child node content + * @throws E + * if an error occurred while writing + */ void writeCode( @NonNull Code node, @NonNull ChildHandler childHandler) throws E; + /** + * Write an indented code block. + * + * @param node + * the code node + * @param childHandler + * the handler used to process child node content + * @throws E + * if an error occurred while writing + */ void writeCodeBlock( @NonNull IndentedCodeBlock node, @NonNull ChildHandler childHandler) throws E; + /** + * Write a fenced code block. + * + * @param node + * the code node + * @param childHandler + * the handler used to process child node content + * @throws E + * if an error occurred while writing + */ void writeCodeBlock( @NonNull FencedCodeBlock node, @NonNull ChildHandler childHandler) throws E; + /** + * Write a code block. + * + * @param node + * the code node + * @param childHandler + * the handler used to process child node content + * @throws E + * if an error occurred while writing + */ void writeCodeBlock( @NonNull CodeBlock node, @NonNull ChildHandler childHandler) throws E; + /** + * Write a block quotation. + * + * @param node + * the quotation node + * @param childHandler + * the handler used to process child node content + * @throws E + * if an error occurred while writing + */ void writeBlockQuote( @NonNull BlockQuote node, @NonNull ChildHandler childHandler) throws E; - default void writeList( - @NonNull String localName, - @NonNull ListBlock node, - @NonNull ChildHandler listItemHandler) throws E { - QName qname = asQName(localName); - writeList(qname, node, listItemHandler); - } - + /** + * Write a list. + * + * @param localName + * the HTML element name to use + * @param node + * the list node + * @param handler + * the list item handler + * @throws E + * if an error occurred while writing + */ void writeList( - @NonNull QName qname, + @NonNull String localName, @NonNull ListBlock node, - @NonNull ChildHandler listItemHandler) throws E; - - void writeListItem( - @NonNull ListItem node, - @NonNull ChildHandler listItemHandler) throws E; + @NonNull ChildHandler handler) throws E; + /** + * Write a line break. + * + * @param node + * the break node + * @throws E + * if an error occurred while writing + */ void writeBreak(@NonNull HardLineBreak node) throws E; + /** + * Write a line break. + * + * @param node + * the break node + * @throws E + * if an error occurred while writing + */ void writeBreak(@NonNull ThematicBreak node) throws E; + /** + * Write a comment. + * + * @param node + * the comment node + * @throws E + * if an error occurred while writing + */ void writeComment(@NonNull HtmlCommentBlock node) throws E; /** @@ -217,7 +424,17 @@ void writeListItem( * the type of exception that can be thrown when a writing error occurs */ @FunctionalInterface - interface ChildHandler { // NOPMD + interface ChildHandler { + /** + * A callback used to process a given node. + * + * @param node + * the node to process + * @param writer + * used to write if an error occurred while writing + * @throws E + * if an error occurred while writing + */ void accept(@NonNull Node node, @NonNull IMarkupWriter writer) throws E; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/InsertAnchorExtension.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/InsertAnchorExtension.java index 36e422b17..902b35dd9 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/InsertAnchorExtension.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/InsertAnchorExtension.java @@ -50,17 +50,22 @@ import com.vladsch.flexmark.parser.LightInlineParser; import com.vladsch.flexmark.parser.Parser; import com.vladsch.flexmark.util.ast.Node; +import com.vladsch.flexmark.util.ast.NodeVisitorBase; import com.vladsch.flexmark.util.data.DataHolder; import com.vladsch.flexmark.util.data.DataKey; import com.vladsch.flexmark.util.data.MutableDataHolder; -import com.vladsch.flexmark.util.misc.Extension; import com.vladsch.flexmark.util.sequence.BasedSequence; import com.vladsch.flexmark.util.sequence.CharSubSequence; +import gov.nist.secauto.metaschema.core.datatype.markup.IMarkupString; + import org.jsoup.nodes.Element; import java.util.Collections; +import java.util.LinkedList; +import java.util.List; import java.util.Set; +import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -73,7 +78,12 @@ public class InsertAnchorExtension = new DataKey<>("ENABLE_INLINE_INSERT_ANCHORS", true); public static final DataKey ENABLE_RENDERING = new DataKey<>("ENABLE_RENDERING", true); - public static Extension create() { + /** + * Construct a new extension instance. + * + * @return the instance + */ + public static InsertAnchorExtension newInstance() { return new InsertAnchorExtension(); } @@ -138,26 +148,13 @@ protected void render(InsertAnchorNode node, NodeRendererContext context, HtmlWr } } - // @Override - // public Set> getNodeRenderingHandlers() { - // HashSet> set = new - // HashSet>(); - // set.add(new NodeRenderingHandler(Macro.class, new - // CustomNodeRenderer() { - // @Override - // public void render(Macro node, NodeRendererContext context, HtmlWriter html) - // { - // MacroNodeRenderer.this.render(node, context, html); } - // })); public static class Factory implements NodeRendererFactory { @Override public NodeRenderer apply(DataHolder options) { return new InsertAnchorNodeRenderer(options); } - } - } private static class InsertAnchorInlineParser implements InlineParserExtension { @@ -304,32 +301,64 @@ public static class InsertAnchorNode extends Node { @NonNull - private BasedSequence type; + private final BasedSequence type; @NonNull private BasedSequence idReference; + /** + * Construct a new Metaschema insert node. + * + * @param type + * the type of insertion + * @param idReference + * the identifier of the given type to use to determine what to insert + */ @SuppressWarnings("null") public InsertAnchorNode(@NonNull String type, @NonNull String idReference) { this(CharSubSequence.of(type), CharSubSequence.of(idReference)); } - public InsertAnchorNode(@NonNull BasedSequence type, @NonNull BasedSequence idReference) { + /** + * Construct a new Metaschema insert node. + * + * @param type + * the type of insertion + * @param idReference + * the identifier of the given type to use to determine what to insert + */ + protected InsertAnchorNode(@NonNull BasedSequence type, @NonNull BasedSequence idReference) { this.type = type; this.idReference = idReference; } + /** + * Get the type of insertion. + * + * @return the type of insertion + */ @NonNull public BasedSequence getType() { return type; } + /** + * Get the identifier of the given type to use to determine what to insert. + * + * @return the identifier + */ @NonNull public BasedSequence getIdReference() { return idReference; } - public void setIdReference(@NonNull BasedSequence value) { - this.idReference = value; + /** + * Set the identifier of the given type to use to determine what to insert. + * + * @param idReference + * the identifier + */ + public void setIdReference(@NonNull BasedSequence idReference) { + this.idReference = idReference; } @Override @@ -345,4 +374,60 @@ public void getAstExtra(StringBuilder out) { segmentSpanChars(out, getIdReference(), "id-ref"); } } + + /** + * Used to collect insert nodes. + */ + public static class InsertVisitor + extends NodeVisitorBase { + @NonNull + private final List inserts = new LinkedList<>(); + @NonNull + private final Predicate filter; + + /** + * Construct a new visitor that will use the provided filter to visit matching + * insert nodes. + * + * @param filter + * the match criteria to use to identify matching insert nodes + */ + public InsertVisitor(@NonNull Predicate filter) { + this.filter = filter; + } + + /** + * Process markup to identify insert nodes. + * + * @param markup + * the markup to process + * @return this visitor + */ + public InsertVisitor processNode(@NonNull IMarkupString markup) { + visit(markup.getDocument()); + return this; + } + + @Override + protected void visit(Node node) { + if (node instanceof InsertAnchorNode) { + InsertAnchorNode insert = (InsertAnchorNode) node; + if (filter.test(insert)) { + inserts.add((InsertAnchorNode) node); + } + } else { + visitChildren(node); + } + } + + /** + * Get the collected insert nodes. + * + * @return the nodes + */ + @NonNull + public List getInserts() { + return inserts; + } + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/MarkupVisitor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/MarkupVisitor.java index 42d44158a..c9fff8b52 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/MarkupVisitor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/MarkupVisitor.java @@ -69,7 +69,8 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** - * + * Supports the production of HTML content based on a markup syntax tree. + *

* This implementation is stateless. * * @param @@ -81,10 +82,23 @@ public class MarkupVisitor implements IMarkupVisitor { private final boolean handleBlockElements; + /** + * Construct a new visitor. + * + * @param handleBlockElements + * {@code true} process block content or {@code false} process the tree + * as a single line of markup + */ public MarkupVisitor(boolean handleBlockElements) { this.handleBlockElements = handleBlockElements; } + /** + * Determine block processing state. + * + * @return {@code true} process block content or {@code false} process the tree + * as a single line of markup + */ protected boolean isHandleBlockElements() { return handleBlockElements; } @@ -94,13 +108,33 @@ public void visitDocument(Document document, IMarkupWriter writer) throws visitChildren(document, writer); } - protected void visitChildren(@NonNull Node parentNode, @NonNull IMarkupWriter writer) throws E { - for (Node node : parentNode.getChildren()) { + /** + * Process child nodes of the provided parent. + * + * @param parent + * the node whose children are to be processed + * @param writer + * the markup writer used to write the HTML + * @throws E + * if an error occurred while writing + */ + protected void visitChildren(@NonNull Node parent, @NonNull IMarkupWriter writer) throws E { + for (Node node : parent.getChildren()) { assert node != null; visit(node, writer); } } + /** + * Process a node. + * + * @param node + * the node to process + * @param writer + * the markup writer used to write the HTML + * @throws E + * if an error occurred while writing + */ protected void visit(@NonNull Node node, @NonNull IMarkupWriter writer) throws E { boolean handled = processInlineElements(node, writer); if (!handled && node instanceof Block) { @@ -118,7 +152,19 @@ protected void visit(@NonNull Node node, @NonNull IMarkupWriter writer) th } } - protected boolean processInlineElements( // NOPMD dispatch method + /** + * Process a node that represents an inline element. + * + * @param node + * the node to process + * @param writer + * the markup writer used to write the HTML + * @return {@code true} if the node was processed or {@code false} otherwise + * @throws E + * if an error occurred while writing + */ + @SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.CognitiveComplexity", "PMD.NcssCount" }) + protected boolean processInlineElements( @NonNull Node node, @NonNull IMarkupWriter writer) throws E { // NOPMD - acceptable boolean retval = true; @@ -172,7 +218,19 @@ protected boolean processInlineElements( // NOPMD dispatch method return retval; } - protected boolean processBlockElements( // NOPMD dispatch method + /** + * Process a node that represents a block element. + * + * @param node + * the node to process + * @param writer + * the markup writer used to write the HTML + * @return {@code true} if the node was processed or {@code false} otherwise + * @throws E + * if an error occurred while writing + */ + @SuppressWarnings("PMD.CyclomaticComplexity") + protected boolean processBlockElements( @NonNull Node node, @NonNull IMarkupWriter writer) throws E { boolean retval = true; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/MarkupXmlEventWriter.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/MarkupXmlEventWriter.java index 9c5dbd9d7..ccfe2e099 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/MarkupXmlEventWriter.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/MarkupXmlEventWriter.java @@ -54,6 +54,18 @@ public class MarkupXmlEventWriter @NonNull protected final XMLEventFactory2 eventFactory; + /** + * Construct a new event writer. + * + * @param namespace + * the XML namespace to use for XMHTML content + * @param listOptions + * list production options + * @param writer + * the XML event stream to write to + * @param eventFactory + * the XML event factory used to generate XML events + */ public MarkupXmlEventWriter( @NonNull String namespace, @NonNull ListOptions listOptions, @@ -63,11 +75,23 @@ public MarkupXmlEventWriter( this.eventFactory = Objects.requireNonNull(eventFactory, "eventFactory"); } + /** + * Get the XML event factory used to generate XML events. + * + * @return the XML event factory + */ @NonNull protected XMLEventFactory2 getEventFactory() { return eventFactory; } + /** + * Get XML events for the provided attributes. + * + * @param attributes + * the mapping of attribute name to value + * @return the list of attribute events + */ @NonNull protected List handleAttributes(@NonNull Map attributes) { List attrs; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/SuppressPTagExtension.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/SuppressPTagExtension.java index 5f3f9822e..3d947f34e 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/SuppressPTagExtension.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/SuppressPTagExtension.java @@ -45,7 +45,12 @@ public class SuppressPTagExtension implements HtmlRenderer.HtmlRendererExtension { - public static SuppressPTagExtension create() { + /** + * Construct a new extension instance. + * + * @return the instance + */ + public static SuppressPTagExtension newInstance() { return new SuppressPTagExtension(); } 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/datatype/markup/flexmark/impl/AbstractMarkupWriter.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/impl/AbstractMarkupWriter.java index 97ce2f32b..f6841f767 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/impl/AbstractMarkupWriter.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/impl/AbstractMarkupWriter.java @@ -86,6 +86,7 @@ import javax.xml.namespace.QName; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** @@ -106,18 +107,8 @@ public abstract class AbstractMarkupWriter // NOPMD not static { ENTITY_MAP = new HashMap<>(); - // special cases + // force writing of non-breaking spaces ENTITY_MAP.put("&npsb;", "&npsb;"); - // ENTITY_MAP.put(">", ">"); - // normal cases - // ENTITY_MAP.put("&", "&"); - /* - * ENTITY_MAP.put("‘", "‘"); ENTITY_MAP.put("’", "’"); - * ENTITY_MAP.put("…", "…"); ENTITY_MAP.put("—", "—"); - * ENTITY_MAP.put("–", "–"); ENTITY_MAP.put("“", "“"); - * ENTITY_MAP.put("”", "”"); ENTITY_MAP.put("«", "«"); - * ENTITY_MAP.put("»", "»"); - */ } @NonNull @@ -129,33 +120,47 @@ public abstract class AbstractMarkupWriter // NOPMD not @NonNull private final ListOptions options; - public AbstractMarkupWriter(@NonNull String namespace, @NonNull ListOptions options, @NonNull T stream) { + /** + * Construct a new markup writer. + * + * @param namespace + * the XHTML namespace to use for elements + * @param options + * list production options + * @param stream + * the stream to write to + */ + protected AbstractMarkupWriter(@NonNull String namespace, @NonNull ListOptions options, @NonNull T stream) { this.namespace = namespace; this.options = options; this.stream = stream; } @NonNull - protected String getNamespace() { + private String getNamespace() { return namespace; } - protected ListOptions getOptions() { + private ListOptions getOptions() { return options; } + /** + * Get the underlying stream to write to. + * + * @return the stream + */ @NonNull protected T getStream() { return stream; } - @Override @NonNull - public QName asQName(@NonNull String localName) { + private QName asQName(@NonNull String localName) { return new QName(getNamespace(), localName); } - protected void visitChildren( + private void visitChildren( @NonNull Node parentNode, @NonNull ChildHandler childHandler) throws E { for (Node node : parentNode.getChildren()) { @@ -164,7 +169,7 @@ protected void visitChildren( } } - protected void writePrecedingNewline(@NonNull Block node) throws E { + private void writePrecedingNewline(@NonNull Block node) throws E { Node prev = node.getPrevious(); if (prev != null || !(node.getParent() instanceof com.vladsch.flexmark.util.ast.Document)) { @@ -172,7 +177,7 @@ protected void writePrecedingNewline(@NonNull Block node) throws E { } } - protected void writeTrailingNewline(@NonNull Block node) throws E { + private void writeTrailingNewline(@NonNull Block node) throws E { Node next = node.getNext(); if (next != null && !next.isOrDescendantOfType(Block.class) // handled by preceding block || next == null && !(node.getParent() instanceof com.vladsch.flexmark.util.ast.Document)) { @@ -180,12 +185,35 @@ protected void writeTrailingNewline(@NonNull Block node) throws E { } } + /** + * Write an HTML element with the provided local name. + * + * @param localName + * the element name + * @param node + * the markup node related to the element + * @param attributes + * attributes associated with the element to also write + * @param childHandler + * a handler used to process child node content or {@code null} + * @throws E + * if an error occurred while writing the markup + */ @Override - public final void writeElement( - QName qname, - Node node, - Map attributes, - ChildHandler childHandler) throws E { + public void writeElement( + @NonNull String localName, + @NonNull Node node, + @NonNull Map attributes, + @Nullable ChildHandler childHandler) throws E { + QName qname = asQName(localName); + writeElement(qname, node, attributes, childHandler); + } + + private void writeElement( + @NonNull QName qname, + @NonNull Node node, + @NonNull Map attributes, + @Nullable ChildHandler childHandler) throws E { if (node.hasChildren()) { writeElementStart(qname, attributes); if (childHandler != null) { @@ -197,6 +225,65 @@ public final void writeElement( } } + @Override + public void writeEmptyElement( + @NonNull String localName, + @NonNull Map attributes) throws E { + QName qname = asQName(localName); + writeEmptyElement(qname, attributes); + } + + /** + * Write an empty element with the provided qualified name and attributes. + * + * @param qname + * the qualified name to use for the element name + * @param attributes + * the attributes + * @throws E + * if an error occurred while writing + */ + protected abstract void writeEmptyElement( + @NonNull QName qname, + @NonNull Map attributes) throws E; + + /** + * Write a start element with the provided qualified name and no attributes. + * + * @param qname + * the qualified name to use for the element name + * @throws E + * if an error occurred while writing + */ + private void writeElementStart( + @NonNull QName qname) throws E { + writeElementStart(qname, CollectionUtil.emptyMap()); + } + + /** + * Write a start element with the provided qualified name and attributes. + * + * @param qname + * the qualified name to use for the element name + * @param attributes + * the attributes + * @throws E + * if an error occurred while writing + */ + protected abstract void writeElementStart( + @NonNull QName qname, + @NonNull Map attributes) throws E; + + /** + * Write an end element with the provided qualified name. + * + * @param qname + * the qualified name to use for the element name + * @throws E + * if an error occurred while writing + */ + protected abstract void writeElementEnd(@NonNull QName qname) throws E; + @SuppressWarnings({ "unchecked", "unused", @@ -273,6 +360,14 @@ private void writeHtmlEntity(String entityText) throws E { } } + /** + * Write an HTML entity. + * + * @param text + * the entity text + * @throws E + * if an error occurred while writing + */ protected void writeHtmlEntityInternal(@NonNull String text) throws E { writeText(text); } @@ -659,7 +754,18 @@ public void writeBlockQuote(BlockQuote node, ChildHandler childHandler) th } @Override - public void writeList(QName qname, ListBlock node, ChildHandler listItemHandler) throws E { + public void writeList( + @NonNull String localName, + @NonNull ListBlock node, + @NonNull ChildHandler listItemHandler) throws E { + QName qname = asQName(localName); + writeList(qname, node, listItemHandler); + } + + private void writeList( + @NonNull QName qname, + @NonNull ListBlock node, + @NonNull ChildHandler listItemHandler) throws E { Map attributes = new LinkedHashMap<>(); // NOPMD local use; thread-safe if (node instanceof OrderedList) { OrderedList ol = (OrderedList) node; @@ -681,8 +787,9 @@ public void writeList(QName qname, ListBlock node, ChildHandler listItemHa writeTrailingNewline(node); } - @Override - public void writeListItem(ListItem node, ChildHandler listItemHandler) throws E { + private void writeListItem( + @NonNull ListItem node, + @NonNull ChildHandler listItemHandler) throws E { QName qname = asQName("li"); writePrecedingNewline(node); writeElementStart(qname); @@ -721,9 +828,17 @@ public void writeComment(HtmlCommentBlock node) throws E { } + /** + * Write a comment. + * + * @param text + * the comment text + * @throws E + * if an error occurred while writing + */ protected abstract void writeComment(@NonNull CharSequence text) throws E; - protected static class NodeVisitorException + private static class NodeVisitorException extends IllegalStateException { /** * the serial version uid. diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/DynamicContext.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/DynamicContext.java index a9f17e2db..9069ed041 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/DynamicContext.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/DynamicContext.java @@ -30,8 +30,8 @@ import gov.nist.secauto.metaschema.core.configuration.IConfiguration; import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration; import gov.nist.secauto.metaschema.core.metapath.function.DefaultFunction.CallingContext; +import gov.nist.secauto.metaschema.core.metapath.function.IFunction.FunctionProperty; import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem; -import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; import gov.nist.secauto.metaschema.core.model.IUriResolver; import gov.nist.secauto.metaschema.core.util.ObjectUtils; @@ -48,6 +48,7 @@ import javax.xml.namespace.QName; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; // TODO: add support for in-scope namespaces /** @@ -60,12 +61,15 @@ public class DynamicContext { // NOPMD - intentional data class @NonNull private final SharedState sharedState; + /** + * Construct a new dynamic context with a default static context. + */ public DynamicContext() { this(StaticContext.instance()); } /** - * Construct a new Metapath dynamic context. + * Construct a new Metapath dynamic context using the provided static context. * * @param staticContext * the Metapath static context @@ -89,7 +93,9 @@ private static class SharedState { private final ZonedDateTime currentDateTime; @NonNull private final Map availableDocuments; + @NonNull private final Map> functionResultCache; + @Nullable private CachingLoader documentLoader; @NonNull private final IMutableConfiguration> configuration; @@ -109,11 +115,12 @@ public SharedState(@NonNull StaticContext staticContext) { } /** - * Generate a new dynamic context that is based on this object. + * Generate a new dynamic context that is a copy of this dynamic context. *

* This method can be used to create a new sub-context where changes can be made * without affecting this context. This is useful for setting information that - * is only used in a limited evaluation scope, such as variables. + * is only used in a limited evaluation sub-scope, such as for handling variable + * assignment. * * @return a new dynamic context */ @@ -152,40 +159,127 @@ public ZonedDateTime getCurrentDateTime() { return sharedState.currentDateTime; } + /** + * Get the mapping of loaded documents from the document URI to the document + * node. + * + * @return the map of document URIs to document nodes + */ @SuppressWarnings("null") @NonNull - public Map getAvailableDocuments() { + public Map getAvailableDocuments() { return Collections.unmodifiableMap(sharedState.availableDocuments); } + /** + * Get the document loader assigned to this dynamic context. + * + * @return the loader + * @throws DynamicMetapathException + * with an error code + * {@link DynamicMetapathException#DYNAMIC_CONTEXT_ABSENT} if a + * document loader is not configured for this dynamic context + */ + @NonNull public IDocumentLoader getDocumentLoader() { - return sharedState.documentLoader; + IDocumentLoader retval = sharedState.documentLoader; + if (retval == null) { + throw new DynamicMetapathException(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT, + "No document loader configured for the dynamic context."); + } + return retval; } + /** + * Assign a document loader to this dynamic context. + * + * @param documentLoader + * the document loader to assign + */ public void setDocumentLoader(@NonNull IDocumentLoader documentLoader) { this.sharedState.documentLoader = new CachingLoader(documentLoader); } + /** + * Get the cached function call result for evaluating a function that has the + * property {@link FunctionProperty#DETERMINISTIC}. + * + * @param callingContext + * the function calling context information that distinguishes the call + * from any other call + * @return the cached result sequence for the function call + */ + @Nullable public ISequence getCachedResult(@NonNull CallingContext callingContext) { return sharedState.functionResultCache.get(callingContext); } + /** + * Cache a function call result for a that has the property + * {@link FunctionProperty#DETERMINISTIC}. + * + * @param callingContext + * the calling context information that distinguishes the call from any + * other call + * @param result + * the function call result + */ + public void cacheResult(@NonNull CallingContext callingContext, @NonNull ISequence result) { + ISequence old = sharedState.functionResultCache.put(callingContext, result); + assert old == null; + } + + /** + * Used to disable the evaluation of predicate expressions during Metapath + * evaluation. + *

+ * This can be useful for determining the potential targets identified by a + * Metapath expression as a partial evaluation, without evaluating that these + * targets match the predicate. + * + * @return this dynamic context + */ @NonNull public DynamicContext disablePredicateEvaluation() { this.sharedState.configuration.disableFeature(MetapathEvaluationFeature.METAPATH_EVALUATE_PREDICATES); return this; } + /** + * Used to enable the evaluation of predicate expressions during Metapath + * evaluation. + *

+ * This is the default behavior if unchanged. + * + * @return this dynamic context + */ @NonNull - public IConfiguration> getConfiguration() { - return sharedState.configuration; + public DynamicContext enablePredicateEvaluation() { + this.sharedState.configuration.enableFeature(MetapathEvaluationFeature.METAPATH_EVALUATE_PREDICATES); + return this; } - public void cacheResult(@NonNull CallingContext callingContext, @NonNull ISequence result) { - ISequence old = sharedState.functionResultCache.put(callingContext, result); - assert old == null; + /** + * Get the Metapath evaluation configuration. + * + * @return the configuration + */ + @NonNull + public IConfiguration> getConfiguration() { + return sharedState.configuration; } + /** + * Get the sequence value assigned to a let variable with the provided qualified + * name. + * + * @param name + * the variable qualified name + * @return the non-null variable value + * @throws MetapathException + * of the variable has not been assigned or if the variable value is + * {@code null} + */ @NonNull public ISequence getVariableValue(@NonNull QName name) { ISequence retval = letVariableMap.get(name); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/EQNameUtils.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/EQNameUtils.java index 1e83efe8d..7f0201820 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/EQNameUtils.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/EQNameUtils.java @@ -33,7 +33,6 @@ import javax.xml.namespace.QName; import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; public final class EQNameUtils { private static final Pattern URI_QUALIFIED_NAME = Pattern.compile("^Q\\{([^{}]*)\\}(.+)$"); @@ -44,16 +43,44 @@ private EQNameUtils() { // disable construction } + /** + * Parse a name as a qualified name. + *

+ * The name can be: + *

    + *
  • A URI qualified name of the form Q{URI}name, where the URI + * represents the namespace
  • + *
  • A lexical name of the forms prefix:name or + * name, where the prefix represents the namespace
  • + *
+ * + * @param name + * the name to parse + * @param resolver + * the prefix resolver to use to determine the namespace for a given + * prefix + * @return the parsed qualified name + */ @NonNull public static QName parseName( @NonNull String name, - @Nullable IEQNamePrefixResolver resolver) { + @NonNull IEQNamePrefixResolver resolver) { Matcher matcher = URI_QUALIFIED_NAME.matcher(name); return matcher.matches() ? newUriQualifiedName(matcher) : parseLexicalQName(name, resolver); } + /** + * Parse a URI qualified name. + *

+ * The name is expected to be a URI qualified name of the form + * {URI}name, where the URI represents the namespace. + * + * @param name + * the name to parse + * @return the parsed qualified name + */ @NonNull public static QName parseUriQualifiedName(@NonNull String name) { Matcher matcher = URI_QUALIFIED_NAME.matcher(name); @@ -69,10 +96,24 @@ private static QName newUriQualifiedName(@NonNull Matcher matcher) { return new QName(matcher.group(1), matcher.group(2)); } + /** + * Parse a lexical name as a qualified name. + *

+ * The name is expected to be a lexical name of the forms + * prefix:name or name, where the prefix represents + * the namespace. + * + * @param name + * the name to parse + * @param resolver + * the prefix resolver to use to determine the namespace for a given + * prefix + * @return the parsed qualified name + */ @NonNull public static QName parseLexicalQName( @NonNull String name, - @Nullable IEQNamePrefixResolver resolver) { + @NonNull IEQNamePrefixResolver resolver) { Matcher matcher = LEXICAL_NAME.matcher(name); if (!matcher.matches()) { throw new IllegalArgumentException( @@ -85,16 +126,33 @@ public static QName parseLexicalQName( prefix = XMLConstants.DEFAULT_NS_PREFIX; } - String namespace = resolver == null ? XMLConstants.NULL_NS_URI : resolver.resolve(prefix); + String namespace = resolver.resolve(prefix); return new QName(namespace, matcher.group(2), prefix); } + /** + * Determine if the name is a non-colonized name. + * + * @param name + * the name to test + * @return {@code true} if the name is not colonized, or {@code false} otherwise + */ public static boolean isNcName(@NonNull String name) { return NCNAME.matcher(name).matches(); } + /** + * Provides a callback for resolving namespace prefixes. + */ @FunctionalInterface public interface IEQNamePrefixResolver { + /** + * Get the URI string for the provided namespace prefix. + * + * @param prefix + * the namespace prefix + * @return the URI string + */ @NonNull String resolve(@NonNull String prefix); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/ICollectionValue.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/ICollectionValue.java index 58520adcc..e3d330b4a 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/ICollectionValue.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/ICollectionValue.java @@ -34,10 +34,27 @@ import edu.umd.cs.findbugs.annotations.NonNull; public interface ICollectionValue { + /** + * Get the collection value as a sequence. + *

+ * If the collection value is a sequence, then the sequence is returned. + * + * @return the resulting sequence + */ // TODO: rename to toSequence and resolve conflicting methods? @NonNull ISequence asSequence(); + /** + * Get the stream of items for the collection value. + *

+ * If the collection value is a sequence, then the items in the collection are + * returned. + * + * @param value + * the collection value + * @return the sequence of related items + */ @NonNull static Stream normalizeAsItems(@NonNull ICollectionValue value) { return value instanceof IItem @@ -45,6 +62,13 @@ static Stream normalizeAsItems(@NonNull ICollectionValue value) : value.asSequence().stream(); } + /** + * Get the stream of items for the collection value. + *

+ * If the collection value contains items, then these items are returned. + * + * @return a stream of related items + */ @NonNull Stream flatten(); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/ISequence.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/ISequence.java index 0fe805b7b..145df6011 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/ISequence.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/ISequence.java @@ -97,10 +97,11 @@ default Iterator iterator() { Stream stream(); /** - * Retrieves the first item in a sequence. If the sequence is empty, a - * {@code null} result is returned. If requireSingleton is {@code true} and the - * sequence contains more than one item, a {@link TypeMetapathException} is - * thrown. + * Retrieves the first item in a sequence. + *

+ * If the sequence is empty, a {@code null} result is returned. If + * requireSingleton is {@code true} and the sequence contains more than one + * item, a {@link TypeMetapathException} is thrown. * * @param * the item type to return derived from the provided sequence @@ -119,10 +120,11 @@ static T getFirstItem(@NonNull ISequence items, boolean req } /** - * Retrieves the first item in a sequence. If the sequence is empty, a - * {@code null} result is returned. If requireSingleton is {@code true} and the - * sequence contains more than one item, a {@link TypeMetapathException} is - * thrown. + * Retrieves the first item in a stream of items. + *

+ * If the sequence is empty, a {@code null} result is returned. If + * requireSingleton is {@code true} and the sequence contains more than one + * item, a {@link TypeMetapathException} is thrown. * * @param * the item type to return derived from the provided sequence @@ -148,11 +150,31 @@ static T getFirstItem(@NonNull Stream items, boolean requir }).orElse(null); } + /** + * Retrieves the first item in this sequence. + *

+ * If the sequence is empty, a {@code null} result is returned. If + * requireSingleton is {@code true} and the sequence contains more than one + * item, a {@link TypeMetapathException} is thrown. + * + * @param requireSingleton + * if {@code true} then a {@link TypeMetapathException} is thrown if + * the sequence contains more than one item + * @return {@code null} if the sequence is empty, or the item otherwise + * @throws TypeMetapathException + * if the sequence contains more than one item and requireSingleton is + * {@code true} + */ @Nullable default ITEM getFirstItem(boolean requireSingleton) { return getFirstItem(this, requireSingleton); } + /** + * Get this sequence as a collection value. + * + * @return the collection value + */ @NonNull default ICollectionValue toCollectionValue() { ICollectionValue retval; @@ -161,9 +183,11 @@ default ICollectionValue toCollectionValue() { retval = empty(); break; case 1: + // get the singleton item retval = ObjectUtils.notNull(stream().findFirst().get()); break; default: + // get this sequence of 2 or more items retval = this; } return retval; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/MetapathExpression.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/MetapathExpression.java index 7d2fff298..61a74e031 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/MetapathExpression.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/MetapathExpression.java @@ -32,8 +32,8 @@ import gov.nist.secauto.metaschema.core.metapath.antlr.ParseTreePrinter; import gov.nist.secauto.metaschema.core.metapath.cst.BuildCSTVisitor; import gov.nist.secauto.metaschema.core.metapath.cst.CSTPrinter; -import gov.nist.secauto.metaschema.core.metapath.cst.ContextItem; import gov.nist.secauto.metaschema.core.metapath.cst.IExpression; +import gov.nist.secauto.metaschema.core.metapath.cst.path.ContextItem; import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; import gov.nist.secauto.metaschema.core.metapath.function.library.FnBoolean; import gov.nist.secauto.metaschema.core.metapath.function.library.FnData; @@ -225,6 +225,11 @@ protected IExpression getASTNode() { return expression; } + /** + * Get the static context used to compile this Metapath. + * + * @return the static context + */ @NonNull protected StaticContext getStaticContext() { return staticContext; @@ -351,7 +356,7 @@ public T evaluateAs( * if the provided sequence is incompatible with the requested result * type */ - @SuppressWarnings("PMD.NullAssignment") // for readability + @SuppressWarnings({ "PMD.NullAssignment", "PMD.CyclomaticComplexity" }) // for readability @Nullable protected T toResultType(@NonNull ISequence sequence, @NonNull ResultType resultType) { Object result; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/StaticContext.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/StaticContext.java index 10794c9df..252d567c3 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/StaticContext.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/StaticContext.java @@ -28,6 +28,7 @@ import gov.nist.secauto.metaschema.core.metapath.EQNameUtils.IEQNamePrefixResolver; import gov.nist.secauto.metaschema.core.util.CollectionUtil; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.net.URI; import java.util.Map; @@ -73,11 +74,11 @@ public final class StaticContext { MetapathConstants.NS_METAPATH_FUNCTIONS_MAP); WELL_KNOWN_NAMESPACES = CollectionUtil.unmodifiableMap(knownNamespaces); - WELL_KNOWN_URI_TO_PREFIX = WELL_KNOWN_NAMESPACES.entrySet().stream() + WELL_KNOWN_URI_TO_PREFIX = ObjectUtils.notNull(WELL_KNOWN_NAMESPACES.entrySet().stream() .collect(Collectors.toUnmodifiableMap( entry -> entry.getValue().toASCIIString(), Map.Entry::getKey, - (v1, v2) -> v2)); + (v1, v2) -> v2))); } @Nullable @@ -99,7 +100,7 @@ public final class StaticContext { * @return the mapping of prefix to namespace URI for all well-known namespaces */ @SuppressFBWarnings("MS_EXPOSE_REP") - public static Map getWellKnownNamespaces() { + public static Map getWellKnownNamespacesMap() { return WELL_KNOWN_NAMESPACES; } @@ -110,12 +111,20 @@ public static Map getWellKnownNamespaces() { * @return the mapping of namespace URI to prefix for all well-known namespaces */ @SuppressFBWarnings("MS_EXPOSE_REP") - public static Map getWellKnownURIToPrefix() { + public static Map getWellKnownURIToPrefixMap() { return WELL_KNOWN_URI_TO_PREFIX; } + /** + * Get the namespace prefix associated with the provided URI, if the URI is + * well-known. + * + * @param uri + * the URI to get the prefix for + * @return the prefix or {@code null} if the provided URI is not well-known + */ @Nullable - public static String getWellKnownPrefixForUri(String uri) { + public static String getWellKnownPrefixForUri(@NonNull String uri) { return WELL_KNOWN_URI_TO_PREFIX.get(uri); } @@ -129,20 +138,9 @@ public static StaticContext instance() { return builder().build(); } - /** - * Create a new static context builder that allows for fine-grained adjustments - * when creating a new static context. - * - * @return a new builder - */ - @NonNull - public static Builder builder() { - return new Builder(); - } - private StaticContext(Builder builder) { this.baseUri = builder.baseUri; - this.knownNamespaces = CollectionUtil.unmodifiableMap(Map.copyOf(builder.namespaces)); + this.knownNamespaces = CollectionUtil.unmodifiableMap(ObjectUtils.notNull(Map.copyOf(builder.namespaces))); this.defaultModelNamespace = builder.defaultModelNamespace; this.defaultFunctionNamespace = builder.defaultFunctionNamespace; } @@ -170,14 +168,14 @@ public URI getBaseUri() { * namespace bindings when a prefix match is not found. *

* The well-known namespace bindings can be retrieved using the - * {@link StaticContext#getWellKnownNamespaces()} method. + * {@link StaticContext#getWellKnownNamespacesMap()} method. * * @param prefix * the namespace prefix * @return the namespace URI bound to the prefix, or {@code null} if no * namespace is bound to the prefix * @see Builder#namespace(String, URI) - * @see #getWellKnownNamespaces() + * @see #getWellKnownNamespacesMap() */ @Nullable public URI lookupNamespaceURIForPrefix(@NonNull String prefix) { @@ -226,53 +224,131 @@ public URI getDefaultFunctionNamespace() { return defaultFunctionNamespace; } + /** + * Get a prefix resolver for use with Metapath function names that will attempt + * to identify the namespace corresponding to a given prefix. + *

+ * This will use the following lookup order, advancing to the next when a + * {@code null} value is returned: + *

    + *
  1. Lookup the prefix using + * {@link StaticContext#lookupNamespaceForPrefix(String)}
  2. + *
  3. Return the result of + * {@link StaticContext#getDefaultFunctionNamespace()}
  4. + *
  5. Return {@link XMLConstants#NULL_NS_URI}
  6. + *
+ * + * @return the resolver + */ @NonNull public IEQNamePrefixResolver getFunctionPrefixResolver() { - return (prefix) -> { - String ns = lookupNamespaceForPrefix(prefix); - if (ns == null) { - URI uri = getDefaultFunctionNamespace(); - if (uri != null) { - ns = uri.toASCIIString(); - } + return this::resolveFunctionPrefix; + } + + @NonNull + private String resolveFunctionPrefix(@NonNull String prefix) { + String ns = lookupNamespaceForPrefix(prefix); + if (ns == null) { + URI uri = getDefaultFunctionNamespace(); + if (uri != null) { + ns = uri.toASCIIString(); } - return ns == null ? XMLConstants.NULL_NS_URI : ns; - }; + } + return ns == null ? XMLConstants.NULL_NS_URI : ns; } + /** + * Get a prefix resolver for use with Metapath flag node names that will attempt + * to identify the namespace corresponding to a given prefix. + *

+ * This will use the following lookup order, advancing to the next when a + * {@code null} value is returned: + *

    + *
  1. Lookup the prefix using + * {@link StaticContext#lookupNamespaceForPrefix(String)}
  2. + *
  3. Return {@link XMLConstants#NULL_NS_URI}
  4. + *
+ * + * @return the resolver + */ @NonNull public IEQNamePrefixResolver getFlagPrefixResolver() { - return (prefix) -> { - String ns = lookupNamespaceForPrefix(prefix); - return ns == null ? XMLConstants.NULL_NS_URI : ns; - }; + return this::resolveFlagReferencePrefix; } + @NonNull + private String resolveFlagReferencePrefix(@NonNull String prefix) { + String ns = lookupNamespaceForPrefix(prefix); + return ns == null ? XMLConstants.NULL_NS_URI : ns; + } + + /** + * Get a prefix resolver for use with Metapath model node names that will + * attempt to identify the namespace corresponding to a given prefix. + *

+ * This will use the following lookup order, advancing to the next when a + * {@code null} value is returned: + *

    + *
  1. Lookup the prefix using + * {@link StaticContext#lookupNamespaceForPrefix(String)}
  2. + *
  3. Return the result of + * {@link StaticContext#getDefaultModelNamespace()}
  4. + *
  5. Return {@link XMLConstants#NULL_NS_URI}
  6. + *
+ * + * @return the resolver + */ @NonNull public IEQNamePrefixResolver getModelPrefixResolver() { - return (prefix) -> { - String ns = lookupNamespaceForPrefix(prefix); - if (ns == null) { - URI uri = getDefaultModelNamespace(); - if (uri != null) { - ns = uri.toASCIIString(); - } + return this::resolveModelReferencePrefix; + } + + @NonNull + private String resolveModelReferencePrefix(@NonNull String prefix) { + String ns = lookupNamespaceForPrefix(prefix); + if (ns == null) { + URI uri = getDefaultModelNamespace(); + if (uri != null) { + ns = uri.toASCIIString(); } - return ns == null ? XMLConstants.NULL_NS_URI : ns; - }; + } + return ns == null ? XMLConstants.NULL_NS_URI : ns; } + /** + * Get a prefix resolver for use with Metapath variable names that will attempt + * to identify the namespace corresponding to a given prefix. + *

+ * This will use the following lookup order, advancing to the next when a + * {@code null} value is returned: + *

    + *
  1. Lookup the prefix using + * {@link StaticContext#lookupNamespaceForPrefix(String)}
  2. + *
  3. Return {@link XMLConstants#NULL_NS_URI}
  4. + *
+ * + * @return the resolver + */ @NonNull public IEQNamePrefixResolver getVariablePrefixResolver() { - return (prefix) -> { - String ns = lookupNamespaceForPrefix(prefix); - return ns == null ? XMLConstants.NULL_NS_URI : ns; - }; + return this::resolveVariablePrefix; } + @NonNull + private String resolveVariablePrefix(@NonNull String prefix) { + String ns = lookupNamespaceForPrefix(prefix); + return ns == null ? XMLConstants.NULL_NS_URI : ns; + } + + /** + * Get a new static context builder that is pre-populated with the setting of + * this static context. + * + * @return a new builder + */ @NonNull public Builder buildFrom() { - Builder builder = new Builder(); + Builder builder = builder(); builder.baseUri = this.baseUri; builder.namespaces.putAll(this.knownNamespaces); builder.defaultModelNamespace = this.defaultModelNamespace; @@ -280,6 +356,17 @@ public Builder buildFrom() { return builder; } + /** + * Create a new static context builder that allows for fine-grained adjustments + * when creating a new static context. + * + * @return a new builder + */ + @NonNull + public static Builder builder() { + return new Builder(); + } + /** * A builder used to generate the static context. */ @@ -332,7 +419,7 @@ public Builder baseUri(@NonNull URI uri) { * {@link StaticContext#lookupNamespaceForPrefix(String)} method. *

* Well-known namespace bindings are used by default, which can be retrieved - * using the {@link StaticContext#getWellKnownNamespaces()} method. + * using the {@link StaticContext#getWellKnownNamespacesMap()} method. * * @param prefix * the prefix to associate with the namespace, which may be @@ -341,7 +428,7 @@ public Builder baseUri(@NonNull URI uri) { * @return this builder * @see StaticContext#lookupNamespaceForPrefix(String) * @see StaticContext#lookupNamespaceURIForPrefix(String) - * @see StaticContext#getWellKnownNamespaces() + * @see StaticContext#getWellKnownNamespacesMap() */ @NonNull public Builder namespace(@NonNull String prefix, @NonNull URI uri) { @@ -361,11 +448,11 @@ public Builder namespace(@NonNull String prefix, @NonNull URI uri) { * if the provided URI is invalid * @see StaticContext#lookupNamespaceForPrefix(String) * @see StaticContext#lookupNamespaceURIForPrefix(String) - * @see StaticContext#getWellKnownNamespaces() + * @see StaticContext#getWellKnownNamespacesMap() */ @NonNull public Builder namespace(@NonNull String prefix, @NonNull String uri) { - return namespace(prefix, URI.create(uri)); + return namespace(prefix, ObjectUtils.notNull(URI.create(uri))); } /** @@ -395,7 +482,7 @@ public Builder defaultModelNamespace(@NonNull URI uri) { */ @NonNull public Builder defaultModelNamespace(@NonNull String uri) { - return defaultModelNamespace(URI.create(uri)); + return defaultModelNamespace(ObjectUtils.notNull(URI.create(uri))); } /** @@ -425,7 +512,7 @@ public Builder defaultFunctionNamespace(@NonNull URI uri) { */ @NonNull public Builder defaultFunctionNamespace(@NonNull String uri) { - return defaultFunctionNamespace(URI.create(uri)); + return defaultFunctionNamespace(ObjectUtils.notNull(URI.create(uri))); } /** 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/AbstractExpressionVisitor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractExpressionVisitor.java index 78980013b..d83c8b90b 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractExpressionVisitor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractExpressionVisitor.java @@ -35,6 +35,7 @@ import gov.nist.secauto.metaschema.core.metapath.cst.math.Multiplication; import gov.nist.secauto.metaschema.core.metapath.cst.math.Subtraction; import gov.nist.secauto.metaschema.core.metapath.cst.path.Axis; +import gov.nist.secauto.metaschema.core.metapath.cst.path.ContextItem; import gov.nist.secauto.metaschema.core.metapath.cst.path.Flag; import gov.nist.secauto.metaschema.core.metapath.cst.path.ModelInstance; import gov.nist.secauto.metaschema.core.metapath.cst.path.NameTest; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractLookup.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractLookup.java index 5bb77d4b5..752b242f8 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractLookup.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractLookup.java @@ -26,206 +26,41 @@ package gov.nist.secauto.metaschema.core.metapath.cst; -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.InvalidTypeMetapathException; -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; -import gov.nist.secauto.metaschema.core.metapath.item.IItem; -import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; -import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem; -import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; -import gov.nist.secauto.metaschema.core.metapath.item.function.ArrayException; import gov.nist.secauto.metaschema.core.metapath.item.function.IArrayItem; +import gov.nist.secauto.metaschema.core.metapath.item.function.IKeySpecifier; import gov.nist.secauto.metaschema.core.metapath.item.function.IMapItem; -import gov.nist.secauto.metaschema.core.util.ObjectUtils; - -import java.util.stream.Stream; import edu.umd.cs.findbugs.annotations.NonNull; +/** + * An implementation of + * Lookup Operators + * supporting access to items in Metapath maps and arrays. + *

+ * Provides support for various types of key- and index-based lookups related to + * {@link IMapItem} and {@link IArrayItem} objects. + */ public abstract class AbstractLookup implements IExpression { @NonNull private final IKeySpecifier keySpecifier; + /** + * Construct a new lookup expression that uses the provided key specifier. + * + * @param keySpecifier + * the key specifier that identifies how to lookup entries + */ protected AbstractLookup(@NonNull IKeySpecifier keySpecifier) { this.keySpecifier = keySpecifier; } + /** + * Get the key specifier implementation. + * + * @return the key specifier + */ @NonNull public IKeySpecifier getKeySpecifier() { return keySpecifier; } - - protected interface IKeySpecifier { - - default Stream lookup( - @NonNull IItem item, - @NonNull DynamicContext dynamicContext, - @NonNull ISequence focus) { - Stream result; - if (item instanceof IArrayItem) { - result = lookupInArray((IArrayItem) item, dynamicContext, focus); - } else if (item instanceof IMapItem) { - result = lookupInMap((IMapItem) item, dynamicContext, focus); - } else { - throw new InvalidTypeMetapathException(item, - String.format("Item type '%s' is not an array or map.", item.getClass().getName())); - } - return result; - } - - @NonNull - Stream lookupInArray( - @NonNull IArrayItem item, - @NonNull DynamicContext dynamicContext, - @NonNull ISequence focus); - - @NonNull - Stream lookupInMap( - @NonNull IMapItem item, - @NonNull DynamicContext dynamicContext, - @NonNull ISequence focus); - } - - protected static class NCNameKeySpecifier implements IKeySpecifier { - @NonNull - private final String name; - - public NCNameKeySpecifier(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - @Override - public Stream lookupInArray( - IArrayItem item, - DynamicContext dynamicContext, - ISequence focus) { - throw new InvalidTypeMetapathException(item, - String.format("The key name-based lookup '%s' is not appropriate for an array.", getName())); - } - - @Override - public Stream lookupInMap( - IMapItem item, - DynamicContext dynamicContext, - ISequence focus) { - return ObjectUtils.notNull(Stream.ofNullable(MapGet.get(item, IStringItem.valueOf(name)))); - } - } - - protected static class IntegerLiteralKeySpecifier implements IKeySpecifier { - private final int index; - - public IntegerLiteralKeySpecifier(IIntegerItem literal) { - index = literal.asInteger().intValueExact(); - } - - @Override - public Stream lookupInArray( - IArrayItem item, - DynamicContext dynamicContext, - ISequence focus) { - try { - return ObjectUtils.notNull(Stream.ofNullable(ArrayGet.get(item, index))); - } catch (IndexOutOfBoundsException ex) { - throw new ArrayException( - ArrayException.INDEX_OUT_OF_BOUNDS, - String.format("The index %d is outside the range of values for the array size '%d'.", - index + 1, - item.size()), - ex); - } - } - - @Override - public Stream lookupInMap( - IMapItem item, - DynamicContext dynamicContext, - ISequence focus) { - return ObjectUtils.notNull(Stream.ofNullable(MapGet.get(item, IIntegerItem.valueOf(index)))); - } - } - - protected static class WildcardKeySpecifier implements IKeySpecifier { - - @Override - public Stream lookupInArray( - IArrayItem item, - DynamicContext dynamicContext, - ISequence focus) { - return ObjectUtils.notNull(item.stream()); - } - - @Override - public Stream lookupInMap( - IMapItem item, - DynamicContext dynamicContext, - ISequence focus) { - return ObjectUtils.notNull(item.values().stream()); - } - } - - public static class ParenthesizedExprKeySpecifier implements IKeySpecifier { - @NonNull - private final IExpression keyExpression; - - public ParenthesizedExprKeySpecifier(@NonNull IExpression keyExpression) { - this.keyExpression = keyExpression; - } - - public IExpression getKeyExpression() { - return keyExpression; - } - - @Override - public Stream lookupInArray( - IArrayItem item, - DynamicContext dynamicContext, - ISequence focus) { - ISequence keys = FnData.fnData(getKeyExpression().accept(dynamicContext, focus)); - - return ObjectUtils.notNull(keys.stream() - .flatMap(key -> { - if (key instanceof IIntegerItem) { - int index = ((IIntegerItem) key).asInteger().intValueExact(); - try { - return Stream.ofNullable(ArrayGet.get(item, index)); - } catch (IndexOutOfBoundsException ex) { - throw new ArrayException( - ArrayException.INDEX_OUT_OF_BOUNDS, - String.format("The index %d is outside the range of values for the array size '%d'.", - index + 1, - item.size()), - ex); - } - } - throw new InvalidTypeMetapathException(item, - String.format("The key '%s' of type '%s' is not appropriate for an array lookup.", - key.asString(), - key.getClass().getName())); - - })); - } - - @Override - public Stream lookupInMap( - IMapItem item, - DynamicContext dynamicContext, - ISequence focus) { - ISequence keys - = ObjectUtils.requireNonNull(FnData.fnData(getKeyExpression().accept(dynamicContext, focus))); - - return keys.stream() - .flatMap(key -> { - return Stream.ofNullable(MapGet.get(item, key)); - }); - } - } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ArraySequenceConstructor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ArraySequenceConstructor.java index 6b837b524..f80aa6979 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ArraySequenceConstructor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ArraySequenceConstructor.java @@ -34,10 +34,22 @@ import edu.umd.cs.findbugs.annotations.Nullable; +/** + * An implementation of the + * Array Curly + * Constructor supporting the creation of a Metapath {@link IArrayItem}. + */ public class ArraySequenceConstructor implements IExpression { @Nullable private final IExpression expr; + /** + * Construct a new array constructor expression that uses the provided + * expression to initialize the array. + * + * @param expr + * the expression used to produce the array members + */ public ArraySequenceConstructor(@Nullable IExpression expr) { this.expr = expr; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ArraySquareConstructor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ArraySquareConstructor.java index 6a98a508a..f670e2e54 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ArraySquareConstructor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ArraySquareConstructor.java @@ -35,10 +35,25 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * An implementation of the + * Array Square + * Constructor supporting the creation of a Metapath {@link IArrayItem}. + */ public class ArraySquareConstructor implements IExpression { @NonNull private final List children; + /** + * Construct a new array constructor expression that uses the provided + * expression to initialize the array. + *

+ * Each resulting array member contains the value of the corresponding argument + * expression. + * + * @param children + * the expressions used to produce the array members + */ public ArraySquareConstructor(@NonNull List children) { this.children = children; } 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..f2f407b05 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 @@ -82,7 +82,6 @@ import gov.nist.secauto.metaschema.core.metapath.antlr.Metapath10.VarrefContext; import gov.nist.secauto.metaschema.core.metapath.antlr.Metapath10.WildcardContext; import gov.nist.secauto.metaschema.core.metapath.antlr.Metapath10Lexer; -import gov.nist.secauto.metaschema.core.metapath.cst.AbstractLookup.IKeySpecifier; import gov.nist.secauto.metaschema.core.metapath.cst.comparison.GeneralComparison; import gov.nist.secauto.metaschema.core.metapath.cst.comparison.ValueComparison; import gov.nist.secauto.metaschema.core.metapath.cst.math.Addition; @@ -92,6 +91,7 @@ import gov.nist.secauto.metaschema.core.metapath.cst.math.Multiplication; import gov.nist.secauto.metaschema.core.metapath.cst.math.Subtraction; import gov.nist.secauto.metaschema.core.metapath.cst.path.Axis; +import gov.nist.secauto.metaschema.core.metapath.cst.path.ContextItem; import gov.nist.secauto.metaschema.core.metapath.cst.path.Flag; import gov.nist.secauto.metaschema.core.metapath.cst.path.INameTestExpression; import gov.nist.secauto.metaschema.core.metapath.cst.path.INodeTestExpression; @@ -105,7 +105,9 @@ import gov.nist.secauto.metaschema.core.metapath.cst.path.Step; import gov.nist.secauto.metaschema.core.metapath.cst.path.Wildcard; import gov.nist.secauto.metaschema.core.metapath.function.ComparisonFunctions; +import gov.nist.secauto.metaschema.core.metapath.impl.AbstractKeySpecifier; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem; +import gov.nist.secauto.metaschema.core.metapath.item.function.IKeySpecifier; import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem; import gov.nist.secauto.metaschema.core.util.CollectionUtil; import gov.nist.secauto.metaschema.core.util.ObjectUtils; @@ -143,6 +145,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 +159,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 +214,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 +243,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 +272,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 +295,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; @@ -328,17 +343,16 @@ protected IExpression handleUnarylookup(UnarylookupContext ctx) { IKeySpecifier keySpecifier; if (specifier.parenthesizedexpr() != null) { - keySpecifier - = new UnaryLookup.ParenthesizedExprKeySpecifier( - ObjectUtils.requireNonNull(specifier.parenthesizedexpr().accept(this))); + keySpecifier = AbstractKeySpecifier.newParenthesizedExprKeySpecifier( + ObjectUtils.requireNonNull(specifier.parenthesizedexpr().accept(this))); } else if (specifier.NCName() != null) { - keySpecifier - = new UnaryLookup.NCNameKeySpecifier(ObjectUtils.requireNonNull(specifier.NCName().getText())); + keySpecifier = AbstractKeySpecifier.newNameKeySpecifier( + ObjectUtils.requireNonNull(specifier.NCName().getText())); } else if (specifier.IntegerLiteral() != null) { - keySpecifier = new UnaryLookup.IntegerLiteralKeySpecifier( + keySpecifier = AbstractKeySpecifier.newIntegerLiteralKeySpecifier( IIntegerItem.valueOf(ObjectUtils.requireNonNull(specifier.IntegerLiteral().getText()))); } else if (specifier.STAR() != null) { - keySpecifier = new UnaryLookup.WildcardKeySpecifier(); + keySpecifier = AbstractKeySpecifier.newWildcardKeySpecifier(); } else { throw new UnsupportedOperationException("unknown key specifier"); } @@ -371,7 +385,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 +416,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 +483,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 +549,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, @@ -551,17 +567,16 @@ protected IExpression handlePostfixexpr(PostfixexprContext context) { IKeySpecifier keySpecifier; if (specifier.parenthesizedexpr() != null) { - keySpecifier - = new PostfixLookup.ParenthesizedExprKeySpecifier( - ObjectUtils.requireNonNull(specifier.parenthesizedexpr().accept(this))); + keySpecifier = AbstractKeySpecifier.newParenthesizedExprKeySpecifier( + ObjectUtils.requireNonNull(specifier.parenthesizedexpr().accept(this))); } else if (specifier.NCName() != null) { - keySpecifier - = new PostfixLookup.NCNameKeySpecifier(ObjectUtils.requireNonNull(specifier.NCName().getText())); + keySpecifier = AbstractKeySpecifier.newNameKeySpecifier( + ObjectUtils.requireNonNull(specifier.NCName().getText())); } else if (specifier.IntegerLiteral() != null) { - keySpecifier = new PostfixLookup.IntegerLiteralKeySpecifier( + keySpecifier = AbstractKeySpecifier.newIntegerLiteralKeySpecifier( IIntegerItem.valueOf(ObjectUtils.requireNonNull(specifier.IntegerLiteral().getText()))); } else if (specifier.STAR() != null) { - keySpecifier = new PostfixLookup.WildcardKeySpecifier(); + keySpecifier = AbstractKeySpecifier.newWildcardKeySpecifier(); } else { throw new UnsupportedOperationException("unknown key specifier"); } @@ -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/CSTPrinter.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/CSTPrinter.java index 5e3914b10..9a1168cb1 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/CSTPrinter.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/CSTPrinter.java @@ -35,6 +35,7 @@ import gov.nist.secauto.metaschema.core.metapath.cst.math.Multiplication; import gov.nist.secauto.metaschema.core.metapath.cst.math.Subtraction; import gov.nist.secauto.metaschema.core.metapath.cst.path.Axis; +import gov.nist.secauto.metaschema.core.metapath.cst.path.ContextItem; import gov.nist.secauto.metaschema.core.metapath.cst.path.Flag; import gov.nist.secauto.metaschema.core.metapath.cst.path.ModelInstance; import gov.nist.secauto.metaschema.core.metapath.cst.path.NameTest; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/DecimalLiteral.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/DecimalLiteral.java index f9e74f3d0..6cfdad1e4 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/DecimalLiteral.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/DecimalLiteral.java @@ -34,6 +34,12 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * An implementation of the + * Decimal Literal + * Expression supporting the creation of a Metapath constant literal + * {@link IDecimalItem}. + */ public class DecimalLiteral extends AbstractLiteralExpression { 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/For.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/For.java index cc87e3219..2101bf1ed 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/For.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/For.java @@ -37,6 +37,11 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * An implementation of the + * For + * expression supporting variable-based iteration. + */ @SuppressWarnings("PMD.ShortClassName") public class For implements IExpression { @NonNull @@ -44,16 +49,34 @@ public class For implements IExpression { @NonNull private final IExpression returnExpression; + /** + * Construct a new let expression using the provided variable and return clause. + * + * @param variable + * the variable declaration + * @param returnExpr + * the return clause that makes use of variables for evaluation + */ public For(@NonNull VariableDeclaration variable, @NonNull IExpression returnExpr) { this.variable = variable; this.returnExpression = returnExpr; } + /** + * Get the variable declaration. + * + * @return the variable declaration expression + */ @NonNull protected Let.VariableDeclaration getVariable() { return variable; } + /** + * Get the return expression. + * + * @return the return expression + */ @NonNull protected IExpression getReturnExpression() { return returnExpression; 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/IExpressionVisitor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/IExpressionVisitor.java index 007e1ad59..115179655 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/IExpressionVisitor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/IExpressionVisitor.java @@ -35,6 +35,7 @@ import gov.nist.secauto.metaschema.core.metapath.cst.math.Multiplication; import gov.nist.secauto.metaschema.core.metapath.cst.math.Subtraction; import gov.nist.secauto.metaschema.core.metapath.cst.path.Axis; +import gov.nist.secauto.metaschema.core.metapath.cst.path.ContextItem; import gov.nist.secauto.metaschema.core.metapath.cst.path.Flag; import gov.nist.secauto.metaschema.core.metapath.cst.path.ModelInstance; import gov.nist.secauto.metaschema.core.metapath.cst.path.NameTest; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/IntegerLiteral.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/IntegerLiteral.java index 9a86b0119..102a92b08 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/IntegerLiteral.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/IntegerLiteral.java @@ -34,6 +34,12 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * An implementation of the + * Integer Literal + * Expression supporting the creation of a Metapath constant literal + * {@link IIntegerItem}. + */ public class IntegerLiteral extends AbstractLiteralExpression { 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/Let.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/Let.java index 1568e3fe3..393195949 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/Let.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/Let.java @@ -104,12 +104,24 @@ public ISequence accept(DynamicContext dynamicContext, ISequenc return getReturnExpression().accept(subDynamicContext, focus); } + /** + * A Metapath expression that binds a variable name to an expresssion. + */ public static class VariableDeclaration { @NonNull private final QName name; @NonNull private final IExpression boundExpression; + /** + * Construct a new variable declaration, binding the provided variable name to + * the bound expression. + * + * @param name + * trhe variable name + * @param boundExpression + * the bound expression + */ public VariableDeclaration(@NonNull QName name, @NonNull IExpression boundExpression) { this.name = name; this.boundExpression = boundExpression; @@ -135,17 +147,27 @@ public IExpression getBoundExpression() { return boundExpression; } + /** + * Bind the variable name to the evaluation result of the bound expression. + * + * @param evaluationDynamicContext + * the {@link DynamicContext} used to evaluate the bound expression + * @param focus + * the evaluation focus to use to evaluate the bound expression + * @param boundDynamicContext + * the {@link DynamicContext} the variable is bound to + */ public void bind( - @NonNull DynamicContext evalContext, + @NonNull DynamicContext evaluationDynamicContext, @NonNull ISequence focus, - @NonNull DynamicContext boundContext) { + @NonNull DynamicContext boundDynamicContext) { - ISequence result = getBoundExpression().accept(evalContext, focus); + ISequence result = getBoundExpression().accept(evaluationDynamicContext, focus); // ensure this sequence is list backed result.getValue(); - boundContext.bindVariableValue(getName(), result); + boundDynamicContext.bindVariableValue(getName(), result); } } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/MapConstructor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/MapConstructor.java index 61bc9dc42..65f98f8d9 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/MapConstructor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/MapConstructor.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.InvalidTypeMetapathException; import gov.nist.secauto.metaschema.core.metapath.function.library.FnData; import gov.nist.secauto.metaschema.core.metapath.item.IItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; @@ -41,10 +42,22 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * An implementation of the + * Map + * Constructor supporting the creation of a Metapath {@link IMapItem}. + */ public class MapConstructor implements IExpression { @NonNull private final List entries; + /** + * Construct a new map constructor expression that uses the provided entry + * expressions to initialize the map entries. + * + * @param entries + * the expressions used to produce the map entries + */ public MapConstructor(@NonNull List entries) { this.entries = entries; } @@ -59,8 +72,13 @@ public ISequence accept(DynamicContext dynamicContext, ISequenc return IMapItem.ofCollection( ObjectUtils.notNull(getChildren().stream() .map(item -> { - IAnyAtomicItem key - = FnData.fnData(item.getKeyExpression().accept(dynamicContext, focus)).getFirstItem(true); + IExpression keyExpression = item.getKeyExpression(); + IAnyAtomicItem key = FnData.fnData(keyExpression.accept(dynamicContext, focus)) + .getFirstItem(true); + if (key == null) { + throw new InvalidTypeMetapathException(null, String.format( + "The expression '%s' did not result in a single key atomic value.", keyExpression.toASTString())); + } ICollectionValue value = item.getValueExpression().accept(dynamicContext, focus).toCollectionValue(); return IMapItem.entry(key, value); @@ -73,22 +91,44 @@ public RESULT accept(IExpressionVisitor visit return visitor.visitMapConstructor(this, context); } + /** + * A map entry expression used to produce an entry in a {@link IMapItem}. + */ public static class Entry implements IExpression { @NonNull private final IExpression keyExpression; @NonNull private final IExpression valueExpression; + /** + * Construct a new map entry expression using the provided ket and value + * expressions. + * + * @param keyExpression + * the expression used to get the map entry key + * @param valueExpression + * the expression used to get the map entry value + */ public Entry(@NonNull IExpression keyExpression, @NonNull IExpression valueExpression) { this.keyExpression = keyExpression; this.valueExpression = valueExpression; } + /** + * Get the map entry key expression. + * + * @return the key expression + */ @NonNull public IExpression getKeyExpression() { return keyExpression; } + /** + * Get the map entry value expression. + * + * @return the value expression + */ @NonNull public IExpression getValueExpression() { return valueExpression; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/PostfixLookup.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/PostfixLookup.java index 1e0108676..259ab0612 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/PostfixLookup.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/PostfixLookup.java @@ -30,17 +30,38 @@ import gov.nist.secauto.metaschema.core.metapath.ICollectionValue; import gov.nist.secauto.metaschema.core.metapath.ISequence; import gov.nist.secauto.metaschema.core.metapath.item.IItem; +import gov.nist.secauto.metaschema.core.metapath.item.function.IArrayItem; +import gov.nist.secauto.metaschema.core.metapath.item.function.IKeySpecifier; +import gov.nist.secauto.metaschema.core.metapath.item.function.IMapItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.List; import edu.umd.cs.findbugs.annotations.NonNull; +/** + * An implementation of + * Postfix Lookup + * Operators supporting access to items in Metapath maps and arrays. + *

+ * Provides support for various types of key- and index-based lookups related to + * {@link IMapItem} and {@link IArrayItem} objects. + */ public class PostfixLookup extends AbstractLookup { @NonNull private final IExpression base; + /** + * Construct a new postfix lookup expression that uses the provided key + * specifier. + * + * @param base + * the base expression used to get the target of the lookup + * @param keySpecifier + * the key specifier used to determine matching entries + */ public PostfixLookup(@NonNull IExpression base, @NonNull IKeySpecifier keySpecifier) { super(keySpecifier); this.base = base; @@ -68,9 +89,12 @@ public ISequence accept(DynamicContext dynamicContext, ISequenc IKeySpecifier specifier = getKeySpecifier(); - return ISequence.of(base.stream() - .flatMap(item -> specifier.lookup(item, dynamicContext, focus)) - .flatMap(ICollectionValue::normalizeAsItems)); + return ISequence.of(ObjectUtils.notNull(base.stream() + .flatMap(item -> { + assert item != null; + return specifier.lookup(item, dynamicContext, focus); + }) + .flatMap(ICollectionValue::normalizeAsItems))); } @Override diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/SimpleMap.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/SimpleMap.java index 9216e589d..2c83af005 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/SimpleMap.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/SimpleMap.java @@ -32,9 +32,23 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * An implementation of the + * Simple Map Operator + * ! supporting evaluating a right expression against every + * item in a sequence produced by a left expression. + */ public class SimpleMap extends AbstractBinaryExpression { + /** + * Construct a simple map expression. + * + * @param left + * the expression used to generate the right sequence + * @param right + * the expression used to evaluate each item in the right sequence + */ public SimpleMap(@NonNull IExpression left, @NonNull IExpression right) { super(left, right); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/StringLiteral.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/StringLiteral.java index 5d0fbaabe..9ada58e64 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/StringLiteral.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/StringLiteral.java @@ -35,6 +35,12 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * An implementation of the + * String Literal + * Expression supporting the creation of a Metapath constant literal + * {@link IStringItem}. + */ public class StringLiteral extends AbstractLiteralExpression { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/UnaryLookup.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/UnaryLookup.java index 2e2bb0d07..5378aa65f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/UnaryLookup.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/UnaryLookup.java @@ -30,14 +30,31 @@ import gov.nist.secauto.metaschema.core.metapath.ICollectionValue; import gov.nist.secauto.metaschema.core.metapath.ISequence; import gov.nist.secauto.metaschema.core.metapath.item.IItem; +import gov.nist.secauto.metaschema.core.metapath.item.function.IArrayItem; +import gov.nist.secauto.metaschema.core.metapath.item.function.IKeySpecifier; +import gov.nist.secauto.metaschema.core.metapath.item.function.IMapItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.List; import edu.umd.cs.findbugs.annotations.NonNull; +/** + * An implementation of + * Unary Lookup + * Operators supporting access to items in Metapath maps and arrays. + *

+ * Provides support for various types of key- and index-based lookups related to + * {@link IMapItem} and {@link IArrayItem} objects. + */ public class UnaryLookup extends AbstractLookup { - + /** + * Construct a new unary lookup expression that uses the provided key specifier. + * + * @param keySpecifier + * the key specifier used to determine matching entries + */ public UnaryLookup(@NonNull IKeySpecifier keySpecifier) { super(keySpecifier); } @@ -52,9 +69,12 @@ public List getChildren() { public ISequence accept(DynamicContext dynamicContext, ISequence focus) { IKeySpecifier specifier = getKeySpecifier(); - return ISequence.of(focus.stream() - .flatMap(item -> specifier.lookup(item, dynamicContext, focus)) - .flatMap(ICollectionValue::normalizeAsItems)); + return ISequence.of(ObjectUtils.notNull(focus.stream() + .flatMap(item -> { + assert item != null; + return specifier.lookup(item, dynamicContext, focus); + }) + .flatMap(ICollectionValue::normalizeAsItems))); } @Override diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/AbstractPathExpression.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/AbstractPathExpression.java index e0354e852..9f58eeaaa 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/AbstractPathExpression.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/AbstractPathExpression.java @@ -35,6 +35,7 @@ import gov.nist.secauto.metaschema.core.metapath.item.ItemUtils; import gov.nist.secauto.metaschema.core.metapath.item.node.ICycledAssemblyNodeItem; import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.stream.Stream; @@ -92,7 +93,7 @@ protected Stream searchExpression( matches = searchExpression( expression, dynamicContext, - ISequence.of(Stream.concat(flags, modelItems))); + ISequence.of(ObjectUtils.notNull(Stream.concat(flags, modelItems)))); } return matches; }); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Axis.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Axis.java index d9f61f15c..dd1340d87 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Axis.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Axis.java @@ -98,12 +98,12 @@ public ISequence accept( if (outerFocus.isEmpty()) { retval = ISequence.empty(); } else { - retval = ISequence.of(outerFocus.stream() + retval = ISequence.of(ObjectUtils.notNull(outerFocus.stream() .map(ItemUtils::checkItemIsNodeItemForStep) .flatMap(item -> { assert item != null; return execute(item); - }).distinct()); + }).distinct())); } return retval; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ContextItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/ContextItem.java similarity index 89% rename from core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ContextItem.java rename to core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/ContextItem.java index 79ec54571..beb9169bf 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ContextItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/ContextItem.java @@ -24,12 +24,13 @@ * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. */ -package gov.nist.secauto.metaschema.core.metapath.cst; +package gov.nist.secauto.metaschema.core.metapath.cst.path; import gov.nist.secauto.metaschema.core.metapath.DynamicContext; import gov.nist.secauto.metaschema.core.metapath.DynamicMetapathException; import gov.nist.secauto.metaschema.core.metapath.ISequence; -import gov.nist.secauto.metaschema.core.metapath.cst.path.AbstractPathExpression; +import gov.nist.secauto.metaschema.core.metapath.cst.IExpression; +import gov.nist.secauto.metaschema.core.metapath.cst.IExpressionVisitor; import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; import java.util.Collections; @@ -37,6 +38,12 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * An implementation of the + * Context + * Item Expression based on the current focus of the Metapath + * {@link DynamicContext}. + */ public final class ContextItem extends AbstractPathExpression { @NonNull diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Flag.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Flag.java index e04f6a13d..0a959feac 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Flag.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Flag.java @@ -34,6 +34,7 @@ import gov.nist.secauto.metaschema.core.metapath.item.ItemUtils; 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.stream.Stream; @@ -69,12 +70,12 @@ public RESULT accept(IExpressionVisitor visit public ISequence accept( DynamicContext dynamicContext, ISequence focus) { - return ISequence.of(focus.stream() + return ISequence.of(ObjectUtils.notNull(focus.stream() .map(ItemUtils::checkItemIsNodeItemForStep) .flatMap(item -> { assert item != null; return match(item); - })); + }))); } /** diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/ModelInstance.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/ModelInstance.java index c1ee8298a..9b63f740d 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/ModelInstance.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/ModelInstance.java @@ -34,6 +34,7 @@ import gov.nist.secauto.metaschema.core.metapath.item.ItemUtils; import gov.nist.secauto.metaschema.core.metapath.item.node.IModelNodeItem; import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.List; import java.util.stream.Stream; @@ -71,12 +72,12 @@ public RESULT accept(IExpressionVisitor visit public ISequence> accept( DynamicContext dynamicContext, ISequence focus) { - return ISequence.of(focus.stream() + return ISequence.of(ObjectUtils.notNull(focus.stream() .map(ItemUtils::checkItemIsNodeItemForStep) .flatMap(item -> { assert item != null; return match(item); - })); + }))); } /** 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..b4f52d3a7 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 @@ -32,6 +32,7 @@ import gov.nist.secauto.metaschema.core.metapath.item.ItemUtils; import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem; import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import javax.xml.namespace.QName; @@ -42,6 +43,7 @@ * expanded QName * name test. */ +@SuppressWarnings("PMD.TestClassWithoutTestCases") public class NameTest implements INameTestExpression { @@ -77,9 +79,9 @@ public RESULT accept(IExpressionVisitor visit public ISequence accept( DynamicContext dynamicContext, ISequence focus) { - return ISequence.of(focus.stream() + return ISequence.of(ObjectUtils.notNull(focus.stream() .map(ItemUtils::checkItemIsNodeItemForStep) - .filter(this::match)); + .filter(this::match))); } @SuppressWarnings("PMD.UnusedPrivateMethod") diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Wildcard.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Wildcard.java index 81a7a6ab6..ba666d16f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Wildcard.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Wildcard.java @@ -32,6 +32,7 @@ import gov.nist.secauto.metaschema.core.metapath.item.ItemUtils; import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem; import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.function.Predicate; import java.util.stream.Stream; @@ -48,6 +49,12 @@ public class Wildcard implements INameTestExpression { @Nullable private final Predicate> matcher; + /** + * Construct a new wildcard name test expression using the provided matcher. + * + * @param matcher + * the matcher used to determine matching nodes + */ public Wildcard(@Nullable Predicate> matcher) { this.matcher = matcher; } @@ -69,13 +76,22 @@ public ISequence accept( test.test((IDefinitionNodeItem) item); }); } - return ISequence.of(nodes); + return ISequence.of(ObjectUtils.notNull(nodes)); } + /** + * A wildcard matcher that matches a specific local name in any namespace. + */ public static class MatchAnyNamespace implements Predicate> { @NonNull private final String localName; + /** + * Construct the matcher using the provided local name for matching. + * + * @param localName + * the name used to match nodes + */ public MatchAnyNamespace(@NonNull String localName) { this.localName = localName; } @@ -86,10 +102,19 @@ public boolean test(IDefinitionNodeItem item) { } } + /** + * A wildcard matcher that matches any local name in a specific namespace. + */ public static class MatchAnyLocalName implements Predicate> { @NonNull private final String namespace; + /** + * Construct the matcher using the provided namespace for matching. + * + * @param namespace + * the namespace used to match nodes + */ public MatchAnyLocalName(@NonNull String namespace) { this.namespace = namespace; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ComparisonFunctions.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ComparisonFunctions.java index 81182def0..9c2509a13 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ComparisonFunctions.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ComparisonFunctions.java @@ -47,6 +47,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; +@SuppressWarnings({ "PMD.GodClass", "PMD.CyclomaticComplexity" }) public final class ComparisonFunctions { /** * Comparison operators. diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/DefaultFunction.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/DefaultFunction.java index 3d2151e1a..fbf5d1d13 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/DefaultFunction.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/DefaultFunction.java @@ -32,6 +32,7 @@ import gov.nist.secauto.metaschema.core.metapath.MetapathException; import gov.nist.secauto.metaschema.core.metapath.function.library.FnData; import gov.nist.secauto.metaschema.core.metapath.item.IItem; +import gov.nist.secauto.metaschema.core.metapath.item.TypeSystem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; @@ -193,70 +194,38 @@ public static List> convertArguments( String.format("argument signature doesn't match '%s'", function.toSignature())); } - assert argument != null; - ISequence parameter = parametersIterator.next(); + assert argument != null; + assert parameter != null; - int size = parameter.size(); - Occurrence occurrence = argument.getSequenceType().getOccurrence(); - switch (occurrence) { - case ONE: { - if (size != 1) { - throw new InvalidTypeMetapathException( - null, - String.format("a sequence of one expected, but found '%d'", size)); - } + retval.add(convertArgument(argument, parameter)); + } + return retval; + } - IItem item = parameter.getFirstItem(true); - parameter = item == null ? ISequence.empty() : ISequence.of(item); - break; - } - case ZERO_OR_ONE: { - if (size > 1) { - throw new InvalidTypeMetapathException( - null, - String.format("a sequence of zero or one expected, but found '%d'", size)); - } + @NonNull + private static ISequence convertArgument( + @NonNull IArgument argument, + @NonNull ISequence parameter) { + // apply occurrence + ISequence retval = argument.getSequenceType().getOccurrence().getSequenceHandler().handle(parameter); - IItem item = parameter.getFirstItem(false); - parameter = item == null ? ISequence.empty() : ISequence.of(item); - break; - } - case ONE_OR_MORE: - if (size < 1) { - throw new InvalidTypeMetapathException( - null, - String.format("a sequence of zero or more expected, but found '%d'", size)); - } - break; - case ZERO: - if (size != 0) { - throw new InvalidTypeMetapathException( - null, - String.format("an empty sequence expected, but found '%d'", size)); - } - break; - case ZERO_OR_MORE: - default: - // do nothing - } + // apply function conversion and type promotion to the parameter + if (!retval.isEmpty()) { + retval = convertSequence(argument, retval); + // verify resulting values Class argumentClass = argument.getSequenceType().getType(); - - // apply function conversion and type promotion to the parameter - parameter = convertSequence(argument, parameter); - - // check resulting values - for (IItem item : parameter.getValue()) { + for (IItem item : retval.getValue()) { Class itemClass = item.getClass(); if (!argumentClass.isAssignableFrom(itemClass)) { throw new InvalidTypeMetapathException( item, - String.format("The type '%s' is not a subtype of '%s'", itemClass.getName(), argumentClass.getName())); + String.format("The type '%s' is not a subtype of '%s'", + TypeSystem.getName(itemClass), + TypeSystem.getName(argumentClass))); } } - - retval.add(parameter); } return retval; } @@ -274,43 +243,38 @@ public static List> convertArguments( */ @NonNull protected static ISequence convertSequence(@NonNull IArgument argument, @NonNull ISequence sequence) { - @NonNull ISequence retval; - if (sequence.isEmpty()) { - retval = ISequence.empty(); - } else { - ISequenceType requiredSequenceType = argument.getSequenceType(); - Class requiredSequenceTypeClass = requiredSequenceType.getType(); + ISequenceType requiredSequenceType = argument.getSequenceType(); + Class requiredSequenceTypeClass = requiredSequenceType.getType(); - Stream stream = sequence.safeStream(); + Stream stream = sequence.safeStream(); - if (IAnyAtomicItem.class.isAssignableFrom(requiredSequenceTypeClass)) { - Stream atomicStream = stream.flatMap(FnData::atomize); + if (IAnyAtomicItem.class.isAssignableFrom(requiredSequenceTypeClass)) { + Stream atomicStream = stream.flatMap(FnData::atomize); - // if (IUntypedAtomicItem.class.isInstance(item)) { // NOPMD - // // TODO: apply cast to atomic type - // } + // if (IUntypedAtomicItem.class.isInstance(item)) { // NOPMD + // // TODO: apply cast to atomic type + // } - if (IStringItem.class.equals(requiredSequenceTypeClass)) { - // promote URIs to strings if a string is required - atomicStream = atomicStream.map(item -> IAnyUriItem.class.isInstance(item) ? IStringItem.cast(item) : item); - } - - stream = atomicStream; + if (IStringItem.class.equals(requiredSequenceTypeClass)) { + // promote URIs to strings if a string is required + atomicStream = atomicStream.map(item -> IAnyUriItem.class.isInstance(item) ? IStringItem.cast(item) : item); } - stream = stream.peek(item -> { - if (!requiredSequenceTypeClass.isInstance(item)) { - throw new InvalidTypeMetapathException( - item, - String.format("The type '%s' is not a subtype of '%s'", - item.getClass().getName(), - requiredSequenceTypeClass.getName())); - } - }); - - retval = ISequence.of(stream); + stream = atomicStream; } - return retval; + + stream = stream.peek(item -> { + if (!requiredSequenceTypeClass.isInstance(item)) { + throw new InvalidTypeMetapathException( + item, + String.format("The type '%s' is not a subtype of '%s'", + item.getClass().getName(), + requiredSequenceTypeClass.getName())); + } + }); + assert stream != null; + + return ISequence.of(stream); } @Override diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/FunctionService.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/FunctionService.java index 3e27244d2..409b0d11f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/FunctionService.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/FunctionService.java @@ -80,6 +80,11 @@ private ServiceLoader getLoader() { return loader; } + /** + * Retrieve the collection of function signatures in this library as a stream. + * + * @return a stream of function signatures + */ public Stream stream() { return this.library.stream(); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IArgument.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IArgument.java index 6012b557f..451323f9b 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IArgument.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IArgument.java @@ -76,8 +76,9 @@ static Builder builder() { */ final class Builder { private String name; + @NonNull private Class type = IItem.class; - private Occurrence occurrence = Occurrence.ONE; + private Occurrence occurrence; private Builder() { // construct a new non-initialized builder @@ -177,7 +178,7 @@ private Builder occurrence(@NonNull Occurrence occurrence) { public IArgument build() { return new ArgumentImpl( ObjectUtils.requireNonNull(name, "the argument name must not be null"), - new SequenceTypeImpl(type, occurrence)); + ISequenceType.of(type, ObjectUtils.requireNonNull(occurrence, "occurrence"))); } } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IFunction.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IFunction.java index cce562f37..80db61094 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IFunction.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IFunction.java @@ -259,7 +259,9 @@ final class Builder { private final EnumSet properties = EnumSet.noneOf(FunctionProperty.class); @NonNull private final List arguments = new LinkedList<>(); + @NonNull private Class returnType = IItem.class; + @NonNull private Occurrence returnOccurrence = Occurrence.ONE; private IFunctionExecutor functionHandler; @@ -512,15 +514,6 @@ public Builder functionHandler(@NonNull IFunctionExecutor handler) { */ @NonNull public IFunction build() { - ISequenceType sequenceType; - if (returnType == null) { - sequenceType = ISequenceType.EMPTY; - } else { - sequenceType = new SequenceTypeImpl( - returnType, - ObjectUtils.requireNonNull(returnOccurrence, "the return occurrence must not be null")); - } - if (properties.contains(FunctionProperty.UNBOUNDED_ARITY) && arguments.isEmpty()) { throw new IllegalStateException("to allow unbounded arity, at least one argument must be provided"); } @@ -530,7 +523,7 @@ public IFunction build() { ObjectUtils.requireNonNull(namespace, "the namespace must not be null"), properties, new ArrayList<>(arguments), - sequenceType, + ISequenceType.of(returnType, returnOccurrence), ObjectUtils.requireNonNull(functionHandler, "the function handler must not be null")); } } 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..cee125556 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 @@ -45,7 +45,7 @@ public Class getType() { @Override public Occurrence getOccurrence() { - return null; + return Occurrence.ZERO; } @Override @@ -54,9 +54,21 @@ public String toSignature() { } }; + /** + * Create new sequence type using the provide type and occurrence. + * + * @param type + * the sequence item type + * @param occurrence + * the expected occurrence of the sequence + * @return the new sequence type + */ + @SuppressWarnings("PMD.ShortMethodName") @NonNull static ISequenceType of(@NonNull Class type, @NonNull Occurrence occurrence) { - return new SequenceTypeImpl(type, occurrence); + return Occurrence.ZERO.equals(occurrence) + ? EMPTY + : new SequenceTypeImpl(type, occurrence); } /** @@ -76,8 +88,8 @@ static ISequenceType of(@NonNull Class type, @NonNull Occurrenc /** * Get the occurrence of the sequence. * - * @return the occurrence of the sequence or {@code null} if the sequence is - * empty + * @return the occurrence of the sequence or {@code Occurrence#ZERO} if the + * sequence is empty */ Occurrence getOccurrence(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/Occurrence.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/Occurrence.java index 95fde71dc..617fec1b7 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/Occurrence.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/Occurrence.java @@ -26,8 +26,14 @@ package gov.nist.secauto.metaschema.core.metapath.function; +import gov.nist.secauto.metaschema.core.metapath.ISequence; +import gov.nist.secauto.metaschema.core.metapath.InvalidTypeMetapathException; +import gov.nist.secauto.metaschema.core.metapath.item.IItem; + import java.util.Objects; +import edu.umd.cs.findbugs.annotations.NonNull; + /** * Identifies the occurrence of a sequence used a function argument or return * value. @@ -36,31 +42,35 @@ public enum Occurrence { /** * An empty sequence. */ - ZERO("", true), + ZERO("", true, Occurrence::handleZero), /** * The occurrence indicator {@code "?"}. */ - ZERO_OR_ONE("?", true), + ZERO_OR_ONE("?", true, Occurrence::handleZeroOrOne), /** * No occurrence indicator. */ - ONE("", false), + ONE("", false, Occurrence::handleOne), /** * The occurrence indicator {@code "*"}. */ - ZERO_OR_MORE("*", true), + ZERO_OR_MORE("*", true, Occurrence::handleZeroOrMore), /** * The occurrence indicator {@code "+"}. */ - ONE_OR_MORE("+", false); + ONE_OR_MORE("+", false, Occurrence::handleOneOrMore); + @NonNull private final String indicator; private final boolean optional; + @NonNull + private final ISequenceHandler sequenceHandler; - Occurrence(String indicator, boolean optional) { + Occurrence(@NonNull String indicator, boolean optional, @NonNull ISequenceHandler sequenceHandler) { Objects.requireNonNull(indicator, "indicator"); this.indicator = indicator; this.optional = optional; + this.sequenceHandler = sequenceHandler; } /** @@ -68,6 +78,7 @@ public enum Occurrence { * * @return the occurrence indicator */ + @NonNull public String getIndicator() { return indicator; } @@ -81,4 +92,88 @@ public String getIndicator() { public boolean isOptional() { return optional; } + + /** + * Get the handler used to check that a sequence meets the occurrence + * requirement. + * + * @return the handler + */ + @NonNull + public ISequenceHandler getSequenceHandler() { + return sequenceHandler; + } + + @NonNull + private static ISequence handleZero(@NonNull ISequence sequence) { + int size = sequence.size(); + if (size != 0) { + throw new InvalidTypeMetapathException( + null, + String.format("an empty sequence expected, but size is '%d'", size)); + } + return ISequence.empty(); + } + + @NonNull + private static ISequence handleOne(@NonNull ISequence sequence) { + int size = sequence.size(); + if (size != 1) { + throw new InvalidTypeMetapathException( + null, + String.format("a sequence of one expected, but size is '%d'", size)); + } + + T item = sequence.getFirstItem(true); + return item == null ? ISequence.empty() : ISequence.of(item); + } + + @NonNull + private static ISequence handleZeroOrOne(@NonNull ISequence sequence) { + int size = sequence.size(); + if (size > 1) { + throw new InvalidTypeMetapathException( + null, + String.format("a sequence of zero or one expected, but size is '%d'", size)); + } + + T item = sequence.getFirstItem(false); + return item == null ? ISequence.empty() : ISequence.of(item); + } + + @NonNull + private static ISequence handleZeroOrMore(@NonNull ISequence sequence) { + return sequence; + } + + @NonNull + private static ISequence handleOneOrMore(@NonNull ISequence sequence) { + int size = sequence.size(); + if (size < 1) { + throw new InvalidTypeMetapathException( + null, + String.format("a sequence of one or more expected, but size is '%d'", size)); + } + return sequence; + } + + @FunctionalInterface + public interface ISequenceHandler { + /** + * Check the provided sequence matches the occurrence. + *

+ * This method may return a new sequence that more efficiently addresses the + * occurrence. + * + * @param + * the sequence item Java type + * @param sequence + * the sequence to check occurrence for + * @return the sequence + * @throws InvalidTypeMetapathException + * if the sequence doesn't match the required occurrence + */ + @NonNull + ISequence handle(@NonNull ISequence sequence); + } } 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..2a8078eb8 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 @@ -62,14 +62,36 @@ private OperationFunctions() { // disable } + /** + * Based on XPath 3.1 op:add-yearMonthDuration-to-date. + * + * @param instant + * a point in time + * @param duration + * the duration to add + * @return the result of adding the duration to the date + */ @NonNull - public static IDateItem opAddYearMonthDurationToDate(@NonNull IDateItem arg1, @NonNull IYearMonthDurationItem arg2) { - return addDurationToDate(arg1.asZonedDateTime(), arg2.asPeriod()); + public static IDateItem opAddYearMonthDurationToDate(@NonNull IDateItem instant, + @NonNull IYearMonthDurationItem duration) { + return addDurationToDate(instant.asZonedDateTime(), duration.asPeriod()); } + /** + * Based on XPath 3.1 op:add-dayTimeDuration-to-date. + * + * @param instant + * a point in time + * @param duration + * the duration to add + * @return the result of adding the duration to the date + */ @NonNull - public static IDateItem opAddDayTimeDurationToDate(@NonNull IDateItem arg1, @NonNull IDayTimeDurationItem arg2) { - return addDurationToDate(arg1.asZonedDateTime(), arg2.asDuration()); + public static IDateItem opAddDayTimeDurationToDate(@NonNull IDateItem instant, + @NonNull IDayTimeDurationItem duration) { + return addDurationToDate(instant.asZonedDateTime(), duration.asDuration()); } @NonNull @@ -84,6 +106,16 @@ private static IDateItem addDurationToDate(@NonNull ZonedDateTime dateTime, @Non return IDateItem.valueOf(result); } + /** + * Based on XPath 3.1 op:add-yearMonthDurations. + * + * @param arg1 + * the first duration + * @param arg2 + * the second duration + * @return the sum of two duration values + */ @NonNull public static IYearMonthDurationItem opAddYearMonthDurations( @NonNull IYearMonthDurationItem arg1, @@ -101,6 +133,16 @@ public static IYearMonthDurationItem opAddYearMonthDurations( return IYearMonthDurationItem.valueOf(result); } + /** + * Based on XPath 3.1 op:add-dayTimeDurations. + * + * @param arg1 + * the first duration + * @param arg2 + * the second duration + * @return the sum of two duration values + */ @NonNull public static IDayTimeDurationItem opAddDayTimeDurations( @NonNull IDayTimeDurationItem arg1, @@ -118,13 +160,23 @@ public static IDayTimeDurationItem opAddDayTimeDurations( return IDayTimeDurationItem.valueOf(result); } + /** + * Based on XPath 3.1 op:add-yearMonthDuration-to-dateTime. + * + * @param instant + * a point in time + * @param duration + * the duration to add + * @return the result of adding the duration to the date + */ @NonNull public static IDateTimeItem opAddYearMonthDurationToDateTime( - @NonNull IDateTimeItem arg1, - @NonNull IYearMonthDurationItem arg2) { + @NonNull IDateTimeItem instant, + @NonNull IYearMonthDurationItem duration) { ZonedDateTime result; try { - result = arg1.asZonedDateTime().plus(arg2.asPeriod()); + result = instant.asZonedDateTime().plus(duration.asPeriod()); } catch (ArithmeticException ex) { throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex); } @@ -132,13 +184,23 @@ public static IDateTimeItem opAddYearMonthDurationToDateTime( return IDateTimeItem.valueOf(result); } + /** + * Based on XPath 3.1 op:add-dayTimeDuration-to-dateTime. + * + * @param instant + * a point in time + * @param duration + * the duration to add + * @return the result of adding the duration to the date + */ @NonNull public static IDateTimeItem opAddDayTimeDurationToDateTime( - @NonNull IDateTimeItem arg1, - @NonNull IDayTimeDurationItem arg2) { + @NonNull IDateTimeItem instant, + @NonNull IDayTimeDurationItem duration) { ZonedDateTime result; try { - result = arg1.asZonedDateTime().plus(arg2.asDuration()); + result = instant.asZonedDateTime().plus(duration.asDuration()); } catch (ArithmeticException ex) { throw new ArithmeticFunctionException(ArithmeticFunctionException.OVERFLOW_UNDERFLOW_ERROR, ex); } @@ -146,23 +208,53 @@ public static IDateTimeItem opAddDayTimeDurationToDateTime( return IDateTimeItem.valueOf(result); } + /** + * Based on XPath 3.1 op:subtract-dates. + * + * @param date1 + * the first point in time + * @param date2 + * the second point in time + * @return the elapsed time between the starting instant and ending instant + */ @NonNull - public static IDayTimeDurationItem opSubtractDates(@NonNull IDateItem arg1, @NonNull IDateItem arg2) { - return between(arg1.asZonedDateTime(), arg2.asZonedDateTime()); + public static IDayTimeDurationItem opSubtractDates(@NonNull IDateItem date1, @NonNull IDateItem date2) { + return between(date1.asZonedDateTime(), date2.asZonedDateTime()); } + /** + * Based on XPath 3.1 op:subtract-yearMonthDuration-from-date. + * + * @param date + * a point in time + * @param duration + * the duration to subtract + * @return the result of subtracting the duration from the date + */ @NonNull public static IDateItem opSubtractYearMonthDurationFromDate( - @NonNull IDateItem arg1, - @NonNull IYearMonthDurationItem arg2) { - return subtractDurationFromDate(arg1.asZonedDateTime(), arg2.asPeriod()); + @NonNull IDateItem date, + @NonNull IYearMonthDurationItem duration) { + return subtractDurationFromDate(date.asZonedDateTime(), duration.asPeriod()); } + /** + * Based on XPath 3.1 op:subtract-dayTimeDuration-from-date. + * + * @param date + * a point in time + * @param duration + * the duration to subtract + * @return the result of subtracting the duration from the date + */ @NonNull public static IDateItem opSubtractDayTimeDurationFromDate( - @NonNull IDateItem arg1, - @NonNull IDayTimeDurationItem arg2) { - return subtractDurationFromDate(arg1.asZonedDateTime(), arg2.asDuration()); + @NonNull IDateItem date, + @NonNull IDayTimeDurationItem duration) { + return subtractDurationFromDate(date.asZonedDateTime(), duration.asDuration()); } @NonNull @@ -174,6 +266,16 @@ private static IDateItem subtractDurationFromDate( return IDateItem.valueOf(result); } + /** + * Based on XPath 3.1 op:subtract-yearMonthDurations. + * + * @param arg1 + * the first duration + * @param arg2 + * the second duration + * @return the result of subtracting the second duration from the first + */ @NonNull public static IYearMonthDurationItem opSubtractYearMonthDurations( @NonNull IYearMonthDurationItem arg1, @@ -186,6 +288,16 @@ public static IYearMonthDurationItem opSubtractYearMonthDurations( return IYearMonthDurationItem.valueOf(duration); } + /** + * Based on XPath 3.1 op:subtract-dayTimeDurations. + * + * @param arg1 + * the first duration + * @param arg2 + * the second duration + * @return the result of subtracting the second duration from the first + */ @NonNull public static IDayTimeDurationItem opSubtractDayTimeDurations( @NonNull IDayTimeDurationItem arg1, @@ -198,11 +310,31 @@ public static IDayTimeDurationItem opSubtractDayTimeDurations( return IDayTimeDurationItem.valueOf(duration); } + /** + * Based on XPath 3.1 op:subtract-dateTimes. + * + * @param time1 + * the first point in time + * @param time2 + * the second point in time + * @return the duration the occurred between the two points in time + */ @NonNull - public static IDayTimeDurationItem opSubtractDateTimes(@NonNull IDateTimeItem arg1, @NonNull IDateTimeItem arg2) { - return between(arg1.asZonedDateTime(), arg2.asZonedDateTime()); + public static IDayTimeDurationItem opSubtractDateTimes(@NonNull IDateTimeItem time1, @NonNull IDateTimeItem time2) { + return between(time1.asZonedDateTime(), time2.asZonedDateTime()); } + /** + * Based on XPath 3.1 op:subtract-dateTimes. + * + * @param time1 + * the first point in time + * @param time2 + * the second point in time + * @return the duration the occurred between the two points in time + */ @NonNull private static IDayTimeDurationItem between(@NonNull ZonedDateTime time1, @NonNull ZonedDateTime time2) { @SuppressWarnings("null") @@ -210,25 +342,56 @@ private static IDayTimeDurationItem between(@NonNull ZonedDateTime time1, @NonNu return IDayTimeDurationItem.valueOf(between); } + /** + * Based on XPath 3.1 op:subtract-yearMonthDuration-from-dateTime. + * + * @param moment + * a point in time + * @param duration + * the duration to subtract + * @return the result of subtracting the duration from a point in time + */ @NonNull public static IDateTimeItem opSubtractYearMonthDurationFromDateTime( - @NonNull IDateTimeItem arg1, - @NonNull IYearMonthDurationItem arg2) { + @NonNull IDateTimeItem moment, + @NonNull IYearMonthDurationItem duration) { @SuppressWarnings("null") - @NonNull ZonedDateTime dateTime = arg1.asZonedDateTime().minus(arg2.asPeriod()); + @NonNull ZonedDateTime dateTime = moment.asZonedDateTime().minus(duration.asPeriod()); return IDateTimeItem.valueOf(dateTime); } + /** + * Based on XPath 3.1 op:subtract-dayTimeDuration-from-dateTime. + * + * @param moment + * a point in time + * @param duration + * the duration to subtract + * @return the result of subtracting the duration from a point in time + */ @NonNull public static IDateTimeItem opSubtractDayTimeDurationFromDateTime( - @NonNull IDateTimeItem arg1, - @NonNull IDayTimeDurationItem arg2) { + @NonNull IDateTimeItem moment, + @NonNull IDayTimeDurationItem duration) { @SuppressWarnings("null") - @NonNull ZonedDateTime dateTime = arg1.asZonedDateTime().plus(arg2.asDuration()); + @NonNull ZonedDateTime dateTime = moment.asZonedDateTime().plus(duration.asDuration()); return IDateTimeItem.valueOf(dateTime); } + /** + * Based on XPath 3.1 op:multiply-yearMonthDuration. + * + * @param arg1 + * the duration value + * @param arg2 + * the number to multiply by + * @return the result of multiplying a {@link IYearMonthDurationItem} by a + * number + */ @NonNull public static IYearMonthDurationItem opMultiplyYearMonthDuration( @NonNull IYearMonthDurationItem arg1, @@ -245,6 +408,16 @@ public static IYearMonthDurationItem opMultiplyYearMonthDuration( return IYearMonthDurationItem.valueOf(period); } + /** + * Based on XPath 3.1 op:multiply-dayTimeDuration. + * + * @param arg1 + * the duration value + * @param arg2 + * the number to multiply by + * @return the result of multiplying a {@link IDayTimeDurationItem} by a number + */ @NonNull public static IDayTimeDurationItem opMultiplyDayTimeDuration( @NonNull IDayTimeDurationItem arg1, @@ -261,6 +434,16 @@ public static IDayTimeDurationItem opMultiplyDayTimeDuration( return IDayTimeDurationItem.valueOf(duration); } + /** + * Based on XPath 3.1 op:divide-yearMonthDuration. + * + * @param arg1 + * the duration value + * @param arg2 + * the number to divide by + * @return the result of dividing a {@link IYearMonthDurationItem} by a number + */ @NonNull public static IYearMonthDurationItem opDivideYearMonthDuration( @NonNull IYearMonthDurationItem arg1, @@ -279,6 +462,16 @@ public static IYearMonthDurationItem opDivideYearMonthDuration( return IYearMonthDurationItem.valueOf(years, months); } + /** + * Based on XPath 3.1 op:divide-dayTimeDuration. + * + * @param arg1 + * the duration value + * @param arg2 + * the number to divide by + * @return the result of dividing a {@link IDayTimeDurationItem} by a number + */ @NonNull public static IDayTimeDurationItem opDivideDayTimeDuration( @NonNull IDayTimeDurationItem arg1, @@ -286,13 +479,23 @@ public static IDayTimeDurationItem opDivideDayTimeDuration( try { @SuppressWarnings("null") @NonNull Duration duration = arg1.asDuration().dividedBy(FunctionUtils.asLong(arg2.round())); - return IDayTimeDurationItem - .valueOf(duration); + return IDayTimeDurationItem.valueOf(duration); } catch (ArithmeticException ex) { throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO, "Division by zero", ex); } } + /** + * Based on XPath 3.1 op:divide-dayTimeDuration-by-dayTimeDuration. + * + * @param arg1 + * the first duration value + * @param arg2 + * the second duration value + * @return the ratio of two {@link IDayTimeDurationItem} values, as a decimal + * number + */ @NonNull public static IDecimalItem opDivideDayTimeDurationByDayTimeDuration( @NonNull IDayTimeDurationItem arg1, @@ -303,38 +506,115 @@ public static IDecimalItem opDivideDayTimeDurationByDayTimeDuration( IDecimalItem.valueOf(arg2.asSeconds()))); } + /** + * Based on XPath 3.1 op:date-equal. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is the same instant in time as the + * second, or {@code false} otherwise + */ @NonNull public static IBooleanItem opDateEqual(@NonNull IDateItem arg1, @NonNull IDateItem arg2) { // TODO: avoid cast? return opDateTimeEqual(IDateTimeItem.cast(arg1), IDateTimeItem.cast(arg2)); } + /** + * Based on XPath 3.1 op:dateTime-equal. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is the same instant in time as the + * second, or {@code false} otherwise + */ @NonNull public static IBooleanItem opDateTimeEqual(@NonNull IDateTimeItem arg1, @NonNull IDateTimeItem arg2) { return IBooleanItem.valueOf(arg1.asZonedDateTime().equals(arg2.asZonedDateTime())); } + /** + * Based on XPath 3.1 op:duration-equal. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is the same duration as the + * second, or {@code false} otherwise + */ @NonNull public static IBooleanItem opDurationEqual(@NonNull IDurationItem arg1, @NonNull IDurationItem arg2) { return IBooleanItem.valueOf(arg1.compareTo(arg2) == 0); } + /** + * Based on XPath 3.1 op:base64Binary-equal. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is equal to the second, or + * {@code false} otherwise + */ @NonNull public static IBooleanItem opBase64BinaryEqual(@NonNull IBase64BinaryItem arg1, @NonNull IBase64BinaryItem arg2) { return IBooleanItem.valueOf(arg1.asByteBuffer().equals(arg2.asByteBuffer())); } + /** + * Based on XPath 3.1 op:date-greater-than. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is a later instant in time than + * the second, or {@code false} otherwise + */ @NonNull public static IBooleanItem opDateGreaterThan(@NonNull IDateItem arg1, @NonNull IDateItem arg2) { // TODO: avoid cast? return opDateTimeGreaterThan(IDateTimeItem.cast(arg1), IDateTimeItem.cast(arg2)); } + /** + * Based on XPath 3.1 op:dateTime-greater-than. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is a later instant in time than + * the second, or {@code false} otherwise + */ @NonNull public static IBooleanItem opDateTimeGreaterThan(@NonNull IDateTimeItem arg1, @NonNull IDateTimeItem arg2) { return IBooleanItem.valueOf(arg1.asZonedDateTime().compareTo(arg2.asZonedDateTime()) > 0); } + /** + * Based on XPath 3.1 op:yearMonthDuration-greater-than. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is a longer duration than the + * second, or {@code false} otherwise + */ @NonNull public static IBooleanItem opYearMonthDurationGreaterThan( @NonNull IYearMonthDurationItem arg1, @@ -346,6 +626,17 @@ public static IBooleanItem opYearMonthDurationGreaterThan( return IBooleanItem.valueOf(p1.toTotalMonths() > p2.toTotalMonths()); } + /** + * Based on XPath 3.1 op:dayTimeDuration-greater-than. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is a longer duration than the + * second, or {@code false} otherwise + */ @NonNull public static IBooleanItem opDayTimeDurationGreaterThan( @NonNull IDayTimeDurationItem arg1, @@ -353,6 +644,17 @@ public static IBooleanItem opDayTimeDurationGreaterThan( return IBooleanItem.valueOf(arg1.compareTo(arg2) > 0); } + /** + * Based on XPath 3.1 op:base64Binary-greater-than. + * + * @param arg1 + * the first value + * @param arg2 + * the second value + * @return {@code true} if the first argument is greater than the second, or + * {@code false} otherwise + */ @NonNull public static IBooleanItem opBase64BinaryGreaterThan( @NonNull IBase64BinaryItem arg1, @@ -360,6 +662,17 @@ public static IBooleanItem opBase64BinaryGreaterThan( return IBooleanItem.valueOf(arg1.compareTo(arg2) > 0); } + /** + * Based on XPath 3.1 op:date-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 +680,17 @@ public static IBooleanItem opDateLessThan( return opDateTimeLessThan(IDateTimeItem.cast(arg1), IDateTimeItem.cast(arg2)); } + /** + * 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 opDateTimeLessThan( @NonNull IDateTimeItem arg1, @@ -374,6 +698,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 +719,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 +737,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 +755,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 +788,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 +821,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 +851,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 +878,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 +924,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 +954,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 +985,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 +1009,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 +1035,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 +1061,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 +1080,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 +1099,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/AbstractKeySpecifier.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractKeySpecifier.java new file mode 100644 index 000000000..69cf6052f --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractKeySpecifier.java @@ -0,0 +1,300 @@ +/* + * Portions of this software was developed by employees of the National Institute + * of Standards and Technology (NIST), an agency of the Federal Government and is + * being made available as a public service. Pursuant to title 17 United States + * Code Section 105, works of NIST employees are not subject to copyright + * protection in the United States. This software may be subject to foreign + * copyright. Permission in the United States and in foreign countries, to the + * extent that NIST may hold copyright, to use, copy, modify, create derivative + * works, and distribute this software and its documentation without fee is hereby + * granted on a non-exclusive basis, provided that this notice and disclaimer + * of warranty appears in all copies. + * + * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER + * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY + * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM + * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE + * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT + * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, + * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, + * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, + * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR + * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT + * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. + */ + +package gov.nist.secauto.metaschema.core.metapath.impl; + +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.InvalidTypeMetapathException; +import gov.nist.secauto.metaschema.core.metapath.cst.IExpression; +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; +import gov.nist.secauto.metaschema.core.metapath.item.IItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.metapath.item.function.ArrayException; +import gov.nist.secauto.metaschema.core.metapath.item.function.IArrayItem; +import gov.nist.secauto.metaschema.core.metapath.item.function.IKeySpecifier; +import gov.nist.secauto.metaschema.core.metapath.item.function.IMapItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * A common base class for all key specifiers. + */ +public abstract class AbstractKeySpecifier implements IKeySpecifier { + @Override + public Stream lookup( + @NonNull IItem targetItem, + @NonNull DynamicContext dynamicContext, + @NonNull ISequence focus) { + Stream result; + if (targetItem instanceof IArrayItem) { + result = lookupInArray((IArrayItem) targetItem, dynamicContext, focus); + } else if (targetItem instanceof IMapItem) { + result = lookupInMap((IMapItem) targetItem, dynamicContext, focus); + } else { + throw new InvalidTypeMetapathException(targetItem, + String.format("Item type '%s' is not an array or map.", targetItem.getClass().getName())); + } + return result; + } + + /** + * A dispatch method intended to handle lookups within an array item. + * + * @param targetItem + * the item to query + * @param dynamicContext + * the dynamic context to use for expression evaluation + * @param focus + * the focus item for expression evaluation + * @return a stream of collection values matching this key specifier + */ + @NonNull + protected abstract Stream lookupInArray( + @NonNull IArrayItem targetItem, + @NonNull DynamicContext dynamicContext, + @NonNull ISequence focus); + + /** + * A dispatch method intended to handle lookups within a map item. + * + * @param targetItem + * the item to query + * @param dynamicContext + * the dynamic context to use for expression evaluation + * @param focus + * the focus item for expression evaluation + * @return a stream of collection values matching this key specifier + */ + @NonNull + protected abstract Stream lookupInMap( + @NonNull IMapItem targetItem, + @NonNull DynamicContext dynamicContext, + @NonNull ISequence focus); + + /** + * Construct a new key specifier supporting name-based lookups. + * + * @param name + * the name to use for lookups + * @return the key specifier + */ + @NonNull + public static IKeySpecifier newNameKeySpecifier(@NonNull String name) { + return new AbstractKeySpecifier.NcNameKeySpecifier(name); + } + + /** + * Construct a new key specifier supporting integer-based lookups. + * + * @param integer + * the integer to use for lookups + * @return the key specifier + */ + @NonNull + public static IKeySpecifier newIntegerLiteralKeySpecifier(@NonNull IIntegerItem integer) { + return new AbstractKeySpecifier.IntegerLiteralKeySpecifier(integer); + } + + /** + * Construct a new key specifier supporting wildcard lookups. + * + * @return the key specifier + */ + @NonNull + public static IKeySpecifier newWildcardKeySpecifier() { + return new AbstractKeySpecifier.WildcardKeySpecifier(); + } + + /** + * Construct a new key specifier supporting key-based lookups. + * + * @param keyExpression + * the expression used to get a key to use for lookups + * @return the key specifier + */ + @NonNull + public static IKeySpecifier newParenthesizedExprKeySpecifier(@NonNull IExpression keyExpression) { + return new AbstractKeySpecifier.ParenthesizedExprKeySpecifier(keyExpression); + } + + private static class NcNameKeySpecifier + extends AbstractKeySpecifier { + @NonNull + private final String name; + + public NcNameKeySpecifier(@NonNull String name) { + this.name = name; + } + + @NonNull + public String getName() { + return name; + } + + @Override + protected Stream lookupInArray( + IArrayItem targetItem, + DynamicContext dynamicContext, + ISequence focus) { + throw new InvalidTypeMetapathException(targetItem, + String.format("A name-based lookup '%s' is not appropriate for an array.", getName())); + } + + @Override + protected Stream lookupInMap( + IMapItem targetItem, + DynamicContext dynamicContext, + ISequence focus) { + return ObjectUtils.notNull(Stream.ofNullable(MapGet.get(targetItem, IStringItem.valueOf(name)))); + } + } + + private static class IntegerLiteralKeySpecifier + extends AbstractKeySpecifier { + private final int index; + + public IntegerLiteralKeySpecifier(@NonNull IIntegerItem literal) { + index = literal.asInteger().intValueExact(); + } + + @Override + protected Stream lookupInArray( + IArrayItem targetItem, + DynamicContext dynamicContext, + ISequence focus) { + try { + return ObjectUtils.notNull(Stream.ofNullable(ArrayGet.get(targetItem, index))); + } catch (IndexOutOfBoundsException ex) { + throw new ArrayException( + ArrayException.INDEX_OUT_OF_BOUNDS, + String.format("The index '%d' is outside the range of values for the array size '%d'.", + index + 1, + targetItem.size()), + ex); + } + } + + @Override + protected Stream lookupInMap( + IMapItem targetItem, + DynamicContext dynamicContext, + ISequence focus) { + return ObjectUtils.notNull(Stream.ofNullable(MapGet.get(targetItem, IIntegerItem.valueOf(index)))); + } + } + + private static class WildcardKeySpecifier + extends AbstractKeySpecifier { + + public WildcardKeySpecifier() { + // do nothing + } + + @Override + protected Stream lookupInArray( + IArrayItem targetItem, + DynamicContext dynamicContext, + ISequence focus) { + return ObjectUtils.notNull(targetItem.stream()); + } + + @Override + protected Stream lookupInMap( + IMapItem targetItem, + DynamicContext dynamicContext, + ISequence focus) { + return ObjectUtils.notNull(targetItem.values().stream()); + } + } + + private static class ParenthesizedExprKeySpecifier + extends AbstractKeySpecifier { + @NonNull + private final IExpression keyExpression; + + public ParenthesizedExprKeySpecifier(@NonNull IExpression keyExpression) { + this.keyExpression = keyExpression; + } + + public IExpression getKeyExpression() { + return keyExpression; + } + + @Override + protected Stream lookupInArray( + IArrayItem targetItem, + DynamicContext dynamicContext, + ISequence focus) { + ISequence keys = FnData.fnData(getKeyExpression().accept(dynamicContext, focus)); + + return ObjectUtils.notNull(keys.stream() + .flatMap(key -> { + if (key instanceof IIntegerItem) { + int index = ((IIntegerItem) key).asInteger().intValueExact(); + try { + return Stream.ofNullable(ArrayGet.get(targetItem, index)); + } catch (IndexOutOfBoundsException ex) { + throw new ArrayException( + ArrayException.INDEX_OUT_OF_BOUNDS, + String.format("The index %d is outside the range of values for the array size '%d'.", + index + 1, + targetItem.size()), + ex); + } + } + throw new InvalidTypeMetapathException(targetItem, + String.format("The key '%s' of type '%s' is not appropriate for an array lookup.", + key.asString(), + key.getClass().getName())); + + })); + } + + @Override + protected Stream lookupInMap( + IMapItem targetItem, + DynamicContext dynamicContext, + ISequence focus) { + ISequence keys + = ObjectUtils.requireNonNull(FnData.fnData(getKeyExpression().accept(dynamicContext, focus))); + + return ObjectUtils.notNull(keys.stream() + .flatMap(key -> { + assert key != null; + return Stream.ofNullable(MapGet.get(targetItem, key)); + })); + } + } +} 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..89a739963 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); @@ -144,7 +153,12 @@ public static String getName(@NonNull Class clazz) { } private static String asPrefixedName(@NonNull QName qname) { - String prefix = StaticContext.getWellKnownPrefixForUri(qname.getNamespaceURI()); + String namespace = qname.getNamespaceURI(); + String prefix = namespace.isEmpty() ? null : StaticContext.getWellKnownPrefixForUri(namespace); 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..4b55c60cd 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) { @@ -67,8 +80,7 @@ public boolean equals(Object obj) { } AbstractTemporalItem.MapKey other = (AbstractTemporalItem.MapKey) obj; - return hasTimezone() == other.getKey().hasTimezone() - && getKey().equals(other.getKey()); + return getKey().compareTo(other.getKey()) == 0; } } } 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/IDateItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateItem.java index bf0252537..1d0eaac81 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateItem.java @@ -36,7 +36,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; -public interface IDateItem extends IAnyAtomicItem { +public interface IDateItem extends ITemporalItem { /** * Construct a new date item using the provided string {@code value}. @@ -100,28 +100,8 @@ default IDateItem castAsType(IAnyAtomicItem item) { return cast(item); } - /** - * Get the "wrapped" date value. - * - * @return the underlying date value - */ - @NonNull - ZonedDateTime asZonedDateTime(); - @Override default int compareTo(IAnyAtomicItem item) { return compareTo(cast(item)); } - - /** - * Compares this value with the argument. - * - * @param item - * the item to compare with this value - * @return a negative integer, zero, or a positive integer if this value is less - * than, equal to, or greater than the {@code item}. - */ - default int compareTo(@NonNull IDateItem item) { - return asZonedDateTime().compareTo(item.asZonedDateTime()); - } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateTimeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateTimeItem.java index 76e1f56cd..e0523362c 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateTimeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IDateTimeItem.java @@ -34,7 +34,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; -public interface IDateTimeItem extends IAnyAtomicItem { +public interface IDateTimeItem extends ITemporalItem { /** * Construct a new date/time item using the provided string {@code value}. * @@ -96,28 +96,8 @@ default IDateTimeItem castAsType(IAnyAtomicItem item) { return cast(item); } - /** - * Get the "wrapped" date/time value. - * - * @return the underlying date value - */ - @NonNull - ZonedDateTime asZonedDateTime(); - @Override default int compareTo(IAnyAtomicItem item) { return compareTo(cast(item)); } - - /** - * Compares this value with the argument. - * - * @param item - * the item to compare with this value - * @return a negative integer, zero, or a positive integer if this value is less - * than, equal to, or greater than the {@code item}. - */ - default int compareTo(@NonNull IDateTimeItem item) { - return asZonedDateTime().compareTo(item.asZonedDateTime()); - } } 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..f59f051b0 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 @@ -26,6 +26,36 @@ package gov.nist.secauto.metaschema.core.metapath.item.atomic; +import java.time.ZonedDateTime; + +import edu.umd.cs.findbugs.annotations.NonNull; + 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(); + + /** + * Get the "wrapped" date/time value. + * + * @return the underlying date value + */ + @NonNull + ZonedDateTime asZonedDateTime(); + + /** + * Compares this value with the argument. + * + * @param item + * the item to compare with this value + * @return a negative integer, zero, or a positive integer if this value is less + * than, equal to, or greater than the {@code item}. + */ + default int compareTo(@NonNull ITemporalItem item) { + return asZonedDateTime().compareTo(item.asZonedDateTime()); + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/IArrayItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/IArrayItem.java index 064a71b29..2d81b15d2 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/IArrayItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/IArrayItem.java @@ -66,6 +66,13 @@ */ @SuppressWarnings("PMD.ShortMethodName") public interface IArrayItem extends IFunction, IItem, List, IPrintable { + /** + * Get an empty, immutable array item. + * + * @param + * the item Java type + * @return an immutable map item + */ @NonNull static IArrayItem empty() { return AbstractArrayItem.empty(); @@ -216,6 +223,7 @@ default List subList(int fromIndex, int toIndex) { static Collector> toArrayItem() { return new Collector, IArrayItem>() { + @SuppressWarnings("null") @Override public Supplier> supplier() { return ArrayList::new; @@ -258,6 +266,15 @@ default Stream flatten() { .flatMap(ICollectionValue::flatten); } + /** + * Get a new, immutable array item that contains the items in the provided list. + * + * @param + * the item Java type + * @param items + * the list whose items are to be added to the new array + * @return an array item containing the specified entries + */ @NonNull static IArrayItem ofCollection( // NOPMD - intentional @NonNull List items) { 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/function/IKeySpecifier.java similarity index 64% 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/function/IKeySpecifier.java index 4d974917f..4d0b94554 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/function/IKeySpecifier.java @@ -24,42 +24,35 @@ * 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.function; -import gov.nist.secauto.metaschema.core.util.ObjectUtils; +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.item.IItem; -import javax.xml.namespace.QName; +import java.util.stream.Stream; 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(); - } - - @FunctionalInterface - public interface NameInitializer { - @NonNull - QName apply(@NonNull String name); - } +/** + * A common interface for all key specifier implementations. + */ +public interface IKeySpecifier { + + /** + * Perform a lookup on the provided target item. + * + * @param targetItem + * the item to query + * @param dynamicContext + * the dynamic context to use for expression evaluation + * @param focus + * the focus item for expression evaluation + * @return a stream of collection values matching this key specifier + */ + Stream lookup( + @NonNull IItem targetItem, + @NonNull DynamicContext dynamicContext, + @NonNull ISequence focus); } 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..f142cd76e 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 @@ -56,6 +56,13 @@ */ public interface IMapItem extends IFunction, IItem, Map, IPrintable { + /** + * Get an empty, immutable map item. + * + * @param + * the value Java type + * @return an immutable map item + */ @NonNull static IMapItem empty() { return AbstractMapItem.empty(); @@ -149,6 +156,15 @@ default ISequence> asSequence() { return ISequence.of(this); } + /** + * Get a new, immutable map item that contains the items in the provided map. + * + * @param + * the value Java type + * @param map + * the map whose items are to be added to the new map + * @return a map item containing the specified entries + */ @NonNull static IMapItem ofCollection( // NOPMD - intentional @NonNull Map map) { @@ -159,9 +175,10 @@ static IMapItem ofCollection( // NOPMD - intenti * Returns an unmodifiable map item containing zero mappings. * * @param - * the value type + * the value Java type * @return an empty {@code IMapItem} */ + @SuppressWarnings("PMD.ShortMethodName") @NonNull static IMapItem of() { return AbstractMapItem.empty(); @@ -171,17 +188,18 @@ static IMapItem of() { * Returns an unmodifiable map item containing a single mapping. * * @param - * the {@code Map}'s key type + * the map item's key type * @param - * the {@code Map}'s value type + * the map item's value type * @param k1 * the mapping's key * @param v1 * the mapping's value - * @return a {@code Map} containing the specified mapping + * @return a map item containing the specified mapping * @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)); @@ -191,9 +209,9 @@ static IMapItem of(@No * Returns an unmodifiable map item containing two mappings. * * @param - * the {@code Map}'s key type + * the map item's key type * @param - * the {@code Map}'s value type + * the map item's value type * @param k1 * the first mapping's key * @param v1 @@ -202,12 +220,13 @@ static IMapItem of(@No * the second mapping's key * @param v2 * the second mapping's value - * @return a {@code Map} containing the specified mappings + * @return a map item containing the specified mappings * @throws IllegalArgumentException * if the keys are duplicates * @throws NullPointerException * if any key or value is {@code null} */ + @SuppressWarnings("PMD.ShortMethodName") @NonNull static IMapItem of( @NonNull K k1, @NonNull V v1, @@ -221,9 +240,9 @@ static IMapItem of( * Returns an unmodifiable map item containing three mappings. * * @param - * the {@code Map}'s key type + * the map item's key type * @param - * the {@code Map}'s value type + * the map item's value type * @param k1 * the first mapping's key * @param v1 @@ -236,12 +255,13 @@ static IMapItem of( * the third mapping's key * @param v3 * the third mapping's value - * @return a {@code Map} containing the specified mappings + * @return a map item containing the specified mappings * @throws IllegalArgumentException * if there are any duplicate keys * @throws NullPointerException * if any key or value is {@code null} */ + @SuppressWarnings("PMD.ShortMethodName") @NonNull static IMapItem of( @@ -258,9 +278,9 @@ IMapItem of( * Returns an unmodifiable map item containing four mappings. * * @param - * the {@code Map}'s key type + * the map item's key type * @param - * the {@code Map}'s value type + * the map item's value type * @param k1 * the first mapping's key * @param v1 @@ -277,12 +297,13 @@ IMapItem of( * the fourth mapping's key * @param v4 * the fourth mapping's value - * @return a {@code Map} containing the specified mappings + * @return a map item containing the specified mappings * @throws IllegalArgumentException * if there are any duplicate keys * @throws NullPointerException * if any key or value is {@code null} */ + @SuppressWarnings("PMD.ShortMethodName") @NonNull static IMapItem of( @@ -301,9 +322,9 @@ IMapItem of( * Returns an unmodifiable map item containing five mappings. * * @param - * the {@code Map}'s key type + * the map item's key type * @param - * the {@code Map}'s value type + * the map item's value type * @param k1 * the first mapping's key * @param v1 @@ -324,12 +345,13 @@ IMapItem of( * the fifth mapping's key * @param v5 * the fifth mapping's value - * @return a {@code Map} containing the specified mappings + * @return a map item containing the specified mappings * @throws IllegalArgumentException * if there are any duplicate keys * @throws NullPointerException * if any key or value is {@code null} */ + @SuppressWarnings("PMD.ShortMethodName") @NonNull static IMapItem of( @@ -350,9 +372,9 @@ IMapItem of( * Returns an unmodifiable map item containing six mappings. * * @param - * the {@code Map}'s key type + * the map item's key type * @param - * the {@code Map}'s value type + * the map item's value type * @param k1 * the first mapping's key * @param v1 @@ -377,13 +399,16 @@ IMapItem of( * the sixth mapping's key * @param v6 * the sixth mapping's value - * @return a {@code Map} containing the specified mappings + * @return a map item containing the specified mappings * @throws IllegalArgumentException * if there are any duplicate keys * @throws NullPointerException * if any key or value is {@code null} */ - @SuppressWarnings("PMD.ExcessiveParameterList") + @SuppressWarnings({ + "PMD.ExcessiveParameterList", + "PMD.ShortMethodName" + }) @NonNull static IMapItem of( @@ -406,9 +431,9 @@ IMapItem of( * Returns an unmodifiable map item containing seven mappings. * * @param - * the {@code Map}'s key type + * the map item's key type * @param - * the {@code Map}'s value type + * the map item's value type * @param k1 * the first mapping's key * @param v1 @@ -437,13 +462,16 @@ IMapItem of( * the seventh mapping's key * @param v7 * the seventh mapping's value - * @return a {@code Map} containing the specified mappings + * @return a map item containing the specified mappings * @throws IllegalArgumentException * if there are any duplicate keys * @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, @@ -468,9 +496,9 @@ static IMapItem of( * Unmodifiable Maps for details. * * @param - * the {@code Map}'s key type + * the map item's key type * @param - * the {@code Map}'s value type + * the map item's value type * @param k1 * the first mapping's key * @param v1 @@ -503,13 +531,16 @@ static IMapItem of( * the eighth mapping's key * @param v8 * the eighth mapping's value - * @return a {@code Map} containing the specified mappings + * @return a map item containing the specified mappings * @throws IllegalArgumentException * if there are any duplicate keys * @throws NullPointerException * if any key or value is {@code null} */ - @SuppressWarnings("PMD.ExcessiveParameterList") + @SuppressWarnings({ + "PMD.ExcessiveParameterList", + "PMD.ShortMethodName" + }) @NonNull static IMapItem of( @@ -536,9 +567,9 @@ IMapItem of( * Returns an unmodifiable map item containing nine mappings. * * @param - * the {@code Map}'s key type + * the map item's key type * @param - * the {@code Map}'s value type + * the map item's value type * @param k1 * the first mapping's key * @param v1 @@ -575,13 +606,16 @@ IMapItem of( * the ninth mapping's key * @param v9 * the ninth mapping's value - * @return a {@code Map} containing the specified mappings + * @return a map item containing the specified mappings * @throws IllegalArgumentException * if there are any duplicate keys * @throws NullPointerException * if any key or value is {@code null} */ - @SuppressWarnings("PMD.ExcessiveParameterList") + @SuppressWarnings({ + "PMD.ExcessiveParameterList", + "PMD.ShortMethodName" + }) @NonNull static IMapItem of( @@ -602,9 +636,9 @@ IMapItem of( * Returns an unmodifiable map item containing ten mappings. * * @param - * the {@code Map}'s key type + * the map item's key type * @param - * the {@code Map}'s value type + * the map item's value type * @param k1 * the first mapping's key * @param v1 @@ -645,13 +679,16 @@ IMapItem of( * the tenth mapping's key * @param v10 * the tenth mapping's value - * @return a {@code Map} containing the specified mappings + * @return a map item containing the specified mappings * @throws IllegalArgumentException * if there are any duplicate keys * @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, @@ -682,13 +719,13 @@ static IMapItem of( * the given entries. The entries themselves are not stored in the map. * * @param - * the {@code Map}'s key type + * the map item's key type * @param - * the {@code Map}'s value type + * the map item's value type * @param entries * {@code Map.Entry}s containing the keys and values from which the map * is populated - * @return a {@code Map} containing the specified mappings + * @return a map item containing the specified mappings * @throws IllegalArgumentException * if there are any duplicate keys * @throws NullPointerException @@ -712,7 +749,7 @@ IMapItem ofEntries(Map.Entry... entries) { * the key * @param value * the value - * @return an {@code Entry} containing the specified key and value + * @return an {@code Map.Entry} containing the specified key and value * @throws NullPointerException * if the key or value is {@code null} */ @@ -721,6 +758,19 @@ static Map.Entry entry(@NonNull IAnyAto return entry(key.asMapKey(), value); } + /** + * Returns an unmodifiable {@link Entry} containing the given key and value. + * + * @param + * the value's type + * @param key + * the key + * @param value + * the value + * @return an {@code Map.Entry} containing the specified key and value + * @throws NullPointerException + * if the key or value is {@code null} + */ @SuppressWarnings("null") @NonNull static Map.Entry entry(@NonNull IMapKey key, @NonNull V value) { @@ -734,12 +784,12 @@ static Map.Entry entry(@NonNull IMapKey * such modifications. * * @param - * the {@code Map}'s key type + * the map item's key type * @param - * the {@code Map}'s value type + * the map item's value type * @param map - * a {@code Map} from which entries are drawn, must be non-null - * @return a {@code Map} containing the entries of the given {@code Map} + * a map item from which entries are drawn, must be non-null + * @return a map item containing the entries of the given {@code Map} * @throws NullPointerException * if map is null, or if it contains any null keys or values */ diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/IMapKey.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/IMapKey.java index 33ad72909..4f588c2f6 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/IMapKey.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/IMapKey.java @@ -32,12 +32,11 @@ public interface IMapKey { + /** + * Get the atomic item used as the key. + * + * @return the atomic item + */ @NonNull IAnyAtomicItem getKey(); - - @Override - int hashCode(); - - @Override - boolean equals(Object obj); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/InsertVisitor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractRecursionPreventingNodeItemVisitor.java similarity index 55% rename from core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/InsertVisitor.java rename to core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractRecursionPreventingNodeItemVisitor.java index 6e725d6d5..73470b52f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/markup/flexmark/InsertVisitor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractRecursionPreventingNodeItemVisitor.java @@ -24,52 +24,41 @@ * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. */ -package gov.nist.secauto.metaschema.core.datatype.markup.flexmark; +package gov.nist.secauto.metaschema.core.metapath.item.node; -import com.vladsch.flexmark.util.ast.Node; -import com.vladsch.flexmark.util.ast.NodeVisitorBase; +import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition; -import gov.nist.secauto.metaschema.core.datatype.markup.IMarkupString; -import gov.nist.secauto.metaschema.core.datatype.markup.flexmark.InsertAnchorExtension.InsertAnchorNode; - -import java.util.LinkedList; -import java.util.List; -import java.util.function.Predicate; - -import edu.umd.cs.findbugs.annotations.NonNull; - -public class InsertVisitor - extends NodeVisitorBase { - @NonNull - private final List inserts - = new LinkedList<>(); - @NonNull - private final Predicate filter; - - public InsertVisitor( - @NonNull Predicate filter) { - this.filter = filter; - } - - public InsertVisitor processNode(@NonNull IMarkupString markup) { - visit(markup.getDocument()); - return this; - } +public abstract class AbstractRecursionPreventingNodeItemVisitor + extends AbstractNodeItemVisitor { @Override - protected void visit(Node node) { - if (node instanceof InsertAnchorNode) { - InsertAnchorNode insert = (InsertAnchorNode) node; - if (filter.test(insert)) { - inserts.add((InsertAnchorNode) node); - } - } else { - visitChildren(node); - } + 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); } - @NonNull - public List getInserts() { - return inserts; + /** + * Determines if the provided node is a descendant of the assembly definition. + * + * @param node + * the node item to test + * @param assemblyDefinition + * the assembly definition to determine as an ancestor of the node + * @return {@code true} if the assembly definition is the node's ancestor, or + * {@code false} otherwise + */ + protected boolean isDecendant(IAssemblyNodeItem node, IAssemblyDefinition assemblyDefinition) { + return node.ancestor() + .map(ancestor -> { + boolean retval = false; + if (ancestor instanceof IAssemblyNodeItem) { + IAssemblyDefinition ancestorDef = ((IAssemblyNodeItem) ancestor).getDefinition(); + retval = ancestorDef.equals(assemblyDefinition); + } + return retval; + }).anyMatch(value -> value); } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/DefaultNodeItemFactory.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/DefaultNodeItemFactory.java index 041507362..ee0a95cad 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/DefaultNodeItemFactory.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/DefaultNodeItemFactory.java @@ -53,6 +53,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; +@SuppressWarnings("PMD.CouplingBetweenObjects") final class DefaultNodeItemFactory extends AbstractNodeItemFactory { @NonNull @@ -134,7 +135,7 @@ protected Map generateFlags(@NonNull IModelNodeItem * the parent assembly containing model instances * @return a mapping of model instance name to model node item(s) */ - @SuppressWarnings("PMD.UseConcurrentHashMap") // need an ordered map + @SuppressWarnings({ "PMD.UseConcurrentHashMap", "PMD.CognitiveComplexity" }) // need an ordered map @NonNull protected Map>> generateModelItems(@NonNull IAssemblyNodeItem parent) { Map>> retval = new LinkedHashMap<>(); @@ -147,13 +148,10 @@ protected Map generateFlags(@NonNull IModelNodeItem Object instanceValue = namedInstance.getValue(parentValue); if (instanceValue != null) { - // the item values will be all non-null items - Stream itemValues = namedInstance.getItemValues(instanceValue).stream(); - AtomicInteger index = new AtomicInteger(); // NOPMD - intentional - List> items = itemValues.map(itemValue -> { - assert itemValue != null; - return newModelItem(namedInstance, parent, index.incrementAndGet(), itemValue); - }).collect(Collectors.toUnmodifiableList()); + List> items = generateModelInstanceItems( + parent, + namedInstance, + ObjectUtils.notNull(namedInstance.getItemValues(instanceValue).stream())); retval.put(namedInstance.getXmlQName(), items); } } else if (instance instanceof IChoiceGroupInstance) { @@ -177,12 +175,10 @@ protected Map generateFlags(@NonNull IModelNodeItem INamedModelInstanceGrouped namedInstance = entry.getKey(); assert namedInstance != null; - AtomicInteger index = new AtomicInteger(); // NOPMD - intentional - List> items = entry.getValue().stream() - .map(itemValue -> { - assert itemValue != null; - return newModelItem(namedInstance, parent, index.incrementAndGet(), itemValue); - }).collect(Collectors.toUnmodifiableList()); + List> items = generateModelInstanceItems( + parent, + namedInstance, + ObjectUtils.notNull(entry.getValue().stream())); retval.put(namedInstance.getXmlQName(), items); } } @@ -192,6 +188,19 @@ protected Map generateFlags(@NonNull IModelNodeItem return retval.isEmpty() ? CollectionUtil.emptyMap() : CollectionUtil.unmodifiableMap(retval); } + private List> generateModelInstanceItems( + @NonNull IAssemblyNodeItem parent, + @NonNull INamedModelInstance namedInstance, + @NonNull Stream itemValues) { + AtomicInteger index = new AtomicInteger(); // NOPMD - intentional + + // the item values will be all non-null items + return itemValues.map(itemValue -> { + assert itemValue != null; + return newModelItem(namedInstance, parent, index.incrementAndGet(), itemValue); + }).collect(Collectors.toUnmodifiableList()); + } + @Override public Supplier newMetaschemaModelSupplier(@NonNull IModuleNodeItem item) { return () -> { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFeatureFlagContainerItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFeatureFlagContainerItem.java index a3e1de7a1..07839baa2 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFeatureFlagContainerItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFeatureFlagContainerItem.java @@ -48,6 +48,11 @@ */ public interface IFeatureFlagContainerItem extends INodeItem { + /** + * Get the model implementation that potentially contains flags. + * + * @return the model + */ @NonNull FlagContainer getModel(); @@ -74,21 +79,42 @@ default IFlagNodeItem getFlagByName(@NonNull QName name) { } /** - * Provides an abstract implementation of a lazy loaded collection of flags. + * Provides an abstract implementation of a model that contains a collection of + * flags. */ class FlagContainer { @NonNull private final Map flags; + /** + * Initialize the container with the provided collection of flags. + * + * @param flags + * a flag mapping of qualified name to corresponding + * {@link IFlagNodeItem} + */ protected FlagContainer(@NonNull Map flags) { this.flags = flags; } + /** + * Get a flag in this container using the associated flag qualified name. + * + * @param name + * the qualified name of the flag + * @return the corresponding flag item or {@code null} if no flag had the + * provided name + */ @Nullable public IFlagNodeItem getFlagByName(@NonNull QName name) { return flags.get(name); } + /** + * Get the flags in this container. + * + * @return the flags + */ @NonNull @SuppressWarnings("null") public Collection getFlags() { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/INodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/INodeItem.java index 005a75fbf..6b2c2a71f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/INodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/INodeItem.java @@ -302,6 +302,11 @@ default Stream flags() { return getModelItems().stream().flatMap(Collection::stream); } + /** + * Get the resource location information for the node, if known. + * + * @return the resource location information, or {@code null} if not known + */ @Nullable IResourceLocation getLocation(); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/INodeItemGenerator.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/INodeItemGenerator.java index 5f849c694..0f0232faa 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/INodeItemGenerator.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/INodeItemGenerator.java @@ -35,21 +35,72 @@ public interface INodeItemGenerator { + /** + * Generate the child Metapath node items for the provided item. + * + * @param item + * the parent item to generate child node items for + * @return a container that consisting of the child node items + */ @NonNull Supplier newDataModelSupplier(@NonNull IFieldNodeItem item); + /** + * Generate the child Metapath node items for the provided item. + * + * @param item + * the parent item to generate child node items for + * @return a container that consisting of the child node items + */ @NonNull Supplier newDataModelSupplier(@NonNull IAssemblyNodeItem item); + /** + * Generate the child Metapath node items for the provided item. + * + * @param item + * the parent item to generate child node items for + * @return a container that consisting of the child node items + */ @NonNull Supplier newDataModelSupplier(@NonNull IRootAssemblyNodeItem item); + /** + * Generate the child Metapath node items for the provided item. + *

+ * The provided item is based on a Metaschema module and has no associated + * value. As a result, the child items will have no associated value. + * + * @param item + * the parent item to generate child node items for + * @return a container that consisting of the child node items + */ @NonNull Supplier newMetaschemaModelSupplier(@NonNull IFieldNodeItem item); + /** + * Generate the child Metapath node items for the provided item. + *

+ * The provided item is based on a Metaschema module and has no associated + * value. As a result, the child items will have no associated value. + * + * @param item + * the parent item to generate child node items for + * @return a container that consisting of the child node items + */ @NonNull Supplier newMetaschemaModelSupplier(@NonNull IAssemblyNodeItem item); + /** + * Generate the child Metapath node items for the provided item. + *

+ * The provided item is a Metaschema module and has no associated value. As a + * result, the child items will have no associated value. + * + * @param item + * the parent item to generate child node items for + * @return a container that consisting of the child node items + */ @NonNull Supplier newMetaschemaModelSupplier(@NonNull IModuleNodeItem item); } 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..b97149178 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,18 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for an assembly that is a member of a containing model. + * + * @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 containing assembly definition + */ public abstract class AbstractAssemblyInstance< PARENT extends IContainerModel, DEFINITION extends IAssemblyDefinition, @@ -38,6 +50,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..e4a386b44 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,21 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for a choice group that is a member of a containing model. + * + * @param + * the Java type of the containing assembly definition + * @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 +51,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..4eb9d2b4f 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,21 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for a choice that is a member of a containing model. + * + * @param + * the Java type of the containing assembly definition + * @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 +52,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/AbstractFieldInstance.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractFieldInstance.java index 630a336e4..67f228f70 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractFieldInstance.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractFieldInstance.java @@ -30,6 +30,19 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for a field that is a member of a containing model. + * + * @param + * the Java type of the parent model (i.e., assembly, choice, + * choiceGroup). + * @param + * the Java type of the definition for this member field + * @param + * the Java type of the instance for this member field + * @param + * the Java type of the containing assembly definition + */ public abstract class AbstractFieldInstance< PARENT extends IContainerModel, DEFINITION extends IFieldDefinition, @@ -38,6 +51,12 @@ public abstract class AbstractFieldInstance< extends AbstractNamedModelInstance implements IFieldInstance, IFeatureDefinitionReferenceInstance { + /** + * Construct a new field instance. + * + * @param parent + * the parent model containing this instance + */ protected AbstractFieldInstance(@NonNull PARENT parent) { super(parent); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractFlagInstance.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractFlagInstance.java index 82ca43adb..079de235b 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractFlagInstance.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractFlagInstance.java @@ -30,6 +30,16 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for a flag that is a member of a containing model. + * + * @param + * the Java type of the parent model (i.e., assembly, field). + * @param + * the Java type of the definition for this member flag + * @param + * the Java type of the instance for this member flag + */ public abstract class AbstractFlagInstance< PARENT extends IModelDefinition, DEFINITION extends IFlagDefinition, @@ -37,6 +47,12 @@ public abstract class AbstractFlagInstance< extends AbstractNamedInstance implements IFlagInstance, IFeatureDefinitionReferenceInstance { + /** + * Construct a new flag instance. + * + * @param parent + * the parent model containing this instance + */ protected AbstractFlagInstance(@NonNull PARENT parent) { super(parent, name -> parent.getContainingModule().toFlagQName(name)); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalAssemblyDefinition.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalAssemblyDefinition.java index 7188a7ff0..69e4f7a12 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalAssemblyDefinition.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalAssemblyDefinition.java @@ -28,6 +28,29 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for an assembly definition defined globally within a Metaschema + * module. + * + * @param + * the Java type of the containing module + * @param + * the expected Java type of an instance of this definition + * @param + * the expected Java type of flag children + * @param + * the expected Java type of model children + * @param + * the expected Java type of named model children + * @param + * the expected Java type of field children + * @param + * the expected Java type of assembly children + * @param + * the expected Java type of choice children + * @param + * the expected Java type of choice group children + */ public abstract class AbstractGlobalAssemblyDefinition< MODULE extends IModule, INSTANCE extends IAssemblyInstance, @@ -47,6 +70,12 @@ public abstract class AbstractGlobalAssemblyDefinition< CHOICE, CHOICE_GROUP> { + /** + * Construct a new global assembly definition. + * + * @param module + * the parent module containing this definition + */ protected AbstractGlobalAssemblyDefinition(@NonNull MODULE module) { super(module, module::toModelQName); } 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..009c66c31 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,42 @@ 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; +/** + * A base class for definitions defined globally within a Metaschema module. + * + * @param + * the Java type of the containing module + * @param + * the expected Java type of an instance of this definition + */ public abstract class AbstractGlobalDefinition - extends AbstractDefinition { + implements IDefinition { @NonNull private final MODULE module; + @NonNull + private final Lazy qname; + @NonNull + private final Lazy definitionQName; + /** + * Construct a new global definition. + * + * @param module + * the parent module containing this instance + * @param initializer + * the callback used to generate qualified names + */ 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 +69,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 +92,20 @@ public final INSTANCE getInlineInstance() { // never inline return null; } + + /** + * Provides a callback for generating a qualified name from a name. + */ + @FunctionalInterface + public interface NameInitializer { + /** + * Produce a qualified name by parsing the provided name. + * + * @param name + * the name to parse + * @return the qualified name for the provided name + */ + @NonNull + QName apply(@NonNull String name); + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalFieldDefinition.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalFieldDefinition.java index 425567667..a7feead8e 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalFieldDefinition.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalFieldDefinition.java @@ -28,6 +28,17 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for a field definition defined globally within a Metaschema + * module. + * + * @param + * the Java type of the containing module + * @param + * the expected Java type of an instance of this definition + * @param + * the expected Java type of flag children + */ public abstract class AbstractGlobalFieldDefinition< MODULE extends IModule, INSTANCE extends IFieldInstance, @@ -35,6 +46,12 @@ public abstract class AbstractGlobalFieldDefinition< extends AbstractGlobalDefinition implements IFieldDefinition, IFeatureContainerFlag { + /** + * Construct a new global field definition. + * + * @param module + * the parent module containing this definition + */ protected AbstractGlobalFieldDefinition(@NonNull MODULE module) { super(module, module::toModelQName); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalFlagDefinition.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalFlagDefinition.java index ab8056991..c3b1262bb 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalFlagDefinition.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractGlobalFlagDefinition.java @@ -28,10 +28,25 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for a flag definition defined globally within a Metaschema + * module. + * + * @param + * the Java type of the containing module + * @param + * the expected Java type of an instance of this definition + */ public abstract class AbstractGlobalFlagDefinition extends AbstractGlobalDefinition implements IFlagDefinition { + /** + * Construct a new global flag definition. + * + * @param module + * the parent module containing this definition + */ protected AbstractGlobalFlagDefinition(@NonNull MODULE module) { super(module, module::toFlagQName); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractInlineAssemblyDefinition.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractInlineAssemblyDefinition.java index 563bed25d..42c63e0d8 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractInlineAssemblyDefinition.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractInlineAssemblyDefinition.java @@ -30,6 +30,32 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for an assembly instance defined inline. + * + * @param + * the Java type of the parent model container for this instance + * @param + * the Java type of the related assembly definition + * @param + * the expected Java type of an instance of this definition + * @param + * the Java type of the containing assembly definition + * @param + * the expected Java type of flag children + * @param + * the expected Java type of model children + * @param + * the expected Java type of named model children + * @param + * the expected Java type of field children + * @param + * the expected Java type of assembly children + * @param + * the expected Java type of choice children + * @param + * the expected Java type of choice group children + */ public abstract class AbstractInlineAssemblyDefinition< PARENT extends IContainerModel, DEFINITION extends IAssemblyDefinition, @@ -53,6 +79,12 @@ public abstract class AbstractInlineAssemblyDefinition< CHOICE_GROUP>, IFeatureDefinitionInstanceInlined { + /** + * Construct a new inline assembly definition. + * + * @param parent + * the parent model containing this instance + */ protected AbstractInlineAssemblyDefinition(@NonNull PARENT parent) { super(parent); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractInlineFieldDefinition.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractInlineFieldDefinition.java index 17ea2e6ea..068673556 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractInlineFieldDefinition.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractInlineFieldDefinition.java @@ -30,6 +30,20 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for an assembly instance defined inline. + * + * @param + * the Java type of the parent model container for this instance + * @param + * the Java type of the related assembly definition + * @param + * the expected Java type of an instance of this definition + * @param + * the Java type of the containing assembly definition + * @param + * the expected Java type of flag children + */ public abstract class AbstractInlineFieldDefinition< PARENT extends IContainerModel, DEFINITION extends IFieldDefinition, @@ -41,6 +55,12 @@ public abstract class AbstractInlineFieldDefinition< IFeatureContainerFlag, IFeatureDefinitionInstanceInlined { + /** + * Construct a new inline assembly definition. + * + * @param parent + * the parent model containing this instance + */ protected AbstractInlineFieldDefinition(@NonNull PARENT parent) { super(parent); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractInlineFlagDefinition.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractInlineFlagDefinition.java index 927efc077..211faaa59 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractInlineFlagDefinition.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractInlineFlagDefinition.java @@ -30,6 +30,16 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for an assembly instance defined inline. + * + * @param + * the Java type of the parent model container for this instance + * @param + * the Java type of the related assembly definition + * @param + * the expected Java type of an instance of this definition + */ public abstract class AbstractInlineFlagDefinition< PARENT extends IModelDefinition, DEFINITION extends IFlagDefinition, @@ -38,6 +48,12 @@ public abstract class AbstractInlineFlagDefinition< implements IFlagInstance, IFlagDefinition, IFeatureDefinitionInstanceInlined { + /** + * Construct a new inline assembly definition. + * + * @param parent + * the parent model containing this instance + */ protected AbstractInlineFlagDefinition(@NonNull PARENT parent) { super(parent, name -> parent.getContainingModule().toFlagQName(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/AbstractNamedModelInstance.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractNamedModelInstance.java index b5d7a8ba2..d01a0f2ff 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractNamedModelInstance.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/AbstractNamedModelInstance.java @@ -30,18 +30,36 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for name members of a containing model. + * + * @param + * the Java type of the parent model container for this instance + * @param + * the Java type of the containing assembly definition + */ public abstract class AbstractNamedModelInstance< PARENT extends IContainerModel, PARENT_DEFINITION extends IAssemblyDefinition> extends AbstractNamedInstance implements INamedModelInstance { + /** + * Construct a new instance. + * + * @param parent + * the parent containing the instance + */ protected AbstractNamedModelInstance(@NonNull PARENT parent) { super(parent, name -> parent.getOwningDefinition().getContainingModule().toModelQName(name)); } @Override public final PARENT_DEFINITION getContainingDefinition() { + // TODO: look for ways to avoid this cast. The problem is that IContainerModel + // is not easily generalized, since this interface is extended by core model + // interfaces. Perhaps moving default implementation into abstract or concrete + // implementation is a possible path? return ObjectUtils.asType(getParentContainer().getOwningDefinition()); } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/FlagContainerBuilder.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/FlagContainerBuilder.java index 105ac46e1..b979385af 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/FlagContainerBuilder.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/FlagContainerBuilder.java @@ -52,6 +52,14 @@ public class FlagContainerBuilder implements IFlagConta @NonNull private final List flags; + /** + * Construct a new flag container using the provided flag qualified name as the + * JSON key. + * + * @param jsonKeyName + * the qualified name of the JSON key or {@code null} if no JSON key is + * configured + */ public FlagContainerBuilder(@Nullable QName jsonKeyName) { this.jsonKeyName = jsonKeyName; this.flags = new LinkedList<>(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/IBoundObject.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/IBoundObject.java index 11cb5bb0a..ae6bbb61b 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/IBoundObject.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/IBoundObject.java @@ -28,7 +28,17 @@ import edu.umd.cs.findbugs.annotations.Nullable; +/** + * A common interface found bound objects that have a complex model consisting + * of flags, fields, or assemblies. + */ public interface IBoundObject { + /** + * Get additional Metaschema-related information for the object (i.e., resource + * location). + * + * @return the Metaschema-related information + */ @Nullable IMetaschemaData getMetaschemaData(); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/IContainerFlagSupport.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/IContainerFlagSupport.java index 112c0031b..95b81fa97 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/IContainerFlagSupport.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/IContainerFlagSupport.java @@ -40,7 +40,7 @@ public interface IContainerFlagSupport { * Provides an empty instance. * * @param - * the Java type of the flag instance + * the Java type of the flag instances * @return an empty instance */ @SuppressWarnings("unchecked") @@ -49,11 +49,28 @@ static IContainerFlagSupport empty() { return (IContainerFlagSupport) EmptyFlagContainer.EMPTY; } + /** + * Create a new flag container without a JSON key. + * + * @param + * the Java type of the flag instances + * @return the flag container + */ @NonNull static IFlagContainerBuilder builder() { return new FlagContainerBuilder<>(null); } + /** + * Create a new flag container using the provided flag qualified name as the + * JSON key. + * + * @param + * the Java type of the flag instances + * @param jsonKey + * the qualified name of the JSON key + * @return the flag container + */ @NonNull static IFlagContainerBuilder builder(@NonNull QName jsonKey) { return new FlagContainerBuilder<>(jsonKey); @@ -67,6 +84,11 @@ static IFlagContainerBuilder builder(@NonNull QName @NonNull Map getFlagInstanceMap(); + /** + * Get the JSON key flag instance. + * + * @return the flag instance or {@code null} if no JSON key is configured + */ @Nullable FI getJsonKeyFlagInstance(); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/IDefinition.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/IDefinition.java index 2d2af4a88..70945d9cb 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/IDefinition.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/IDefinition.java @@ -108,6 +108,14 @@ default String toCoordinates() { hashCode()); } + /** + * Get the resource location information for the provided item, if known. + * + * @param itemValue + * the item to get the location information for + * + * @return the resource location information, or {@code null} if not known + */ @Nullable default IResourceLocation getLocation(@NonNull Object itemValue) { return itemValue instanceof IBoundObject ? ((IBoundObject) itemValue).getMetaschemaData() : null; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/IFlagContainerBuilder.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/IFlagContainerBuilder.java index 59ffae730..0a2f7c7e7 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/IFlagContainerBuilder.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/IFlagContainerBuilder.java @@ -29,9 +29,21 @@ import edu.umd.cs.findbugs.annotations.NonNull; public interface IFlagContainerBuilder { + /** + * Add a flag instance to the flag container. + * + * @param instance + * the flag instance to add + * @return this builder + */ @NonNull IFlagContainerBuilder flag(@NonNull T instance); + /** + * Build the flag container. + * + * @return the built flag container + */ @NonNull IContainerFlagSupport build(); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/model/IModule.java b/core/src/main/java/gov/nist/secauto/metaschema/core/model/IModule.java index 02aae7400..6f1711bfd 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/model/IModule.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/model/IModule.java @@ -346,12 +346,28 @@ default QName getQName() { @Nullable IAssemblyDefinition getExportedRootAssemblyDefinitionByName(QName name); + /** + * Used to parse a flag name reference to produce a qualified name. + * + * @param nameRef + * the name reference + * @return the qualified name + */ @NonNull default QName toFlagQName(@NonNull String nameRef) { // TODO: handle namespace prefix return new QName(nameRef); } + /** + * Used to parse a flag name reference to produce a qualified name. + * + * @param modelNamespace + * the namespace to use or {@code null} + * @param nameRef + * the name reference + * @return the qualified name + */ @NonNull default QName toFlagQName(@Nullable String modelNamespace, @NonNull String nameRef) { // TODO: handle namespace prefix @@ -360,11 +376,27 @@ default QName toFlagQName(@Nullable String modelNamespace, @NonNull String nameR : new QName(modelNamespace, nameRef); } + /** + * Used to parse a model name reference to produce a qualified name. + * + * @param nameRef + * the name reference + * @return the qualified name + */ @NonNull default QName toModelQName(@NonNull String nameRef) { return toModelQName(null, nameRef); } + /** + * Used to parse a flag name reference to produce a qualified name. + * + * @param modelNamespace + * the namespace to use or {@code null} + * @param nameRef + * the name reference + * @return the qualified name + */ @NonNull default QName toModelQName(@Nullable String modelNamespace, @NonNull String nameRef) { // TODO: handle namespace prefix 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/main/java/module-info.java b/core/src/main/java/module-info.java index d79ae1575..64f244464 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -88,7 +88,7 @@ requires flexmark.ext.escaped.character; requires flexmark.ext.gfm.strikethrough; requires flexmark.ext.superscript; - requires flexmark.ext.tables; + requires transitive flexmark.ext.tables; requires transitive flexmark.ext.typographic; requires transitive flexmark.html2md.converter; requires transitive flexmark.util.ast; @@ -99,7 +99,7 @@ requires flexmark.util.format; requires flexmark.util.html; requires flexmark.util.misc; - requires flexmark.util.sequence; + requires transitive flexmark.util.sequence; requires flexmark.util.visitor; exports gov.nist.secauto.metaschema.core.configuration; diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/ExpressionTestBase.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/ExpressionTestBase.java index 3c8946660..e3632bd55 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/ExpressionTestBase.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/ExpressionTestBase.java @@ -46,6 +46,11 @@ public class ExpressionTestBase { @RegisterExtension private final Mockery context = new JUnit5Mockery(); + /** + * Get the mocking context. + * + * @return the mocking context + */ @NonNull protected Mockery getContext() { return context; @@ -65,8 +70,13 @@ protected static DynamicContext newDynamicContext() { .build()); } + /** + * Get a mocked document node item. + * + * @return the mocked node item + */ @NonNull - protected IDocumentNodeItem newDocumentNodeContext() { + protected IDocumentNodeItem newDocumentNodeMock() { IDocumentNodeItem retval = getContext().mock(IDocumentNodeItem.class); assert retval != null; @@ -82,8 +92,16 @@ protected IDocumentNodeItem newDocumentNodeContext() { return retval; } + /** + * Get a mocked node item. + * + * @param mockName + * the name of the mocked object + * + * @return the mocked node item + */ @NonNull - protected INodeItem newNonDocumentNodeContext(@NonNull String mockName) { + protected INodeItem newNonDocumentNodeMock(@NonNull String mockName) { INodeItem retval = getContext().mock(INodeItem.class, mockName); assert retval != null; 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..2e154b3f9 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 @@ -26,7 +26,6 @@ package gov.nist.secauto.metaschema.core.metapath; -import gov.nist.secauto.metaschema.core.metapath.function.IFunction; import gov.nist.secauto.metaschema.core.metapath.item.IItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem; @@ -39,52 +38,97 @@ import gov.nist.secauto.metaschema.core.metapath.item.function.IArrayItem; 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.CollectionUtil; import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; import java.net.URI; -import java.util.List; import java.util.Map; import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; public final class TestUtils { - private TestUtils() { - // disable construction - } - + /** + * Create an empty sequence. + * + * @param + * the item Java type contained within the sequence + * @return the sequence + */ @NonNull public static ISequence sequence() { return ISequence.of(); } + /** + * Create a sequence containing the provided items. + * + * @param + * the item Java type contained within the sequence + * @param items + * the items to add to the sequence + * @return the sequence + */ @SafeVarargs @NonNull public static ISequence sequence(@NonNull T... items) { return ISequence.of(items); } + /** + * Create an empty array item. + * + * @param + * the item Java type contained within the array item + * @return the array item + */ @NonNull public static IArrayItem array() { return IArrayItem.of(); } + /** + * Create a array item containing the provided items. + * + * @param + * the item Java type contained within the array item + * @param items + * the items to add to the array item + * @return the array item + */ @SafeVarargs @NonNull public static IArrayItem array(@NonNull T... items) { return IArrayItem.of(items); } + /** + * Create a map item containing the provided entries. + * + * @param + * the entry value Java type contained within the map item + * @param entries + * the entries to add to the map item + * @return the map item + */ @SafeVarargs @NonNull public static IMapItem map(@NonNull Map.Entry... entries) { return IMapItem.ofEntries(entries); } + /** + * Create a map entry using the provided key and value. + * + * @param + * the entry value Java type + * @param key + * the entry key + * @param value + * the entry value + * @return the map entry + */ @NonNull public static Map.Entry entry( @NonNull IAnyAtomicItem key, @@ -92,35 +136,84 @@ public static Map.Entry entry( return IMapItem.entry(key, value); } + /** + * Create a boolean item using the provided value. + * + * @param value + * the boolean value + * @return the boolean item + */ @NonNull public static IBooleanItem bool(boolean value) { return IBooleanItem.valueOf(value); } + /** + * Create a decimal item using the provided value. + * + * @param value + * the decimal value + * @return the decimal item + */ public static IDecimalItem decimal(@NonNull String value) { return IDecimalItem.valueOf(new BigDecimal(value, MathContext.DECIMAL64)); } + /** + * Create a decimal item using the provided value. + * + * @param value + * the decimal value + * @return the decimal item + */ @NonNull public static IDecimalItem decimal(int value) { return IDecimalItem.valueOf(value); } + /** + * Create a decimal item using the provided value. + * + * @param value + * the decimal value + * @return the decimal item + */ @NonNull public static IDecimalItem decimal(double value) { return IDecimalItem.valueOf(value); } + /** + * Create an integer item using the provided value. + * + * @param value + * the integer value + * @return the integer item + */ @NonNull public static IIntegerItem integer(int value) { return IIntegerItem.valueOf(ObjectUtils.notNull(BigInteger.valueOf(value))); } + /** + * Create a string item using the provided value. + * + * @param value + * the string value + * @return the string item + */ @NonNull public static IStringItem string(@NonNull String value) { return IStringItem.valueOf(value); } + /** + * Create a uri item using the provided value. + * + * @param value + * the uri value + * @return the uri item + */ @NonNull public static IAnyUriItem uri(@NonNull String value) { URI uri = URI.create(value); @@ -128,30 +221,33 @@ public static IAnyUriItem uri(@NonNull String value) { return IAnyUriItem.valueOf(uri); } + /** + * Create a duration item using the provided value indicating the years, months, + * and days of the duration. + * + * @param value + * the duration value + * @return the duration item + */ @NonNull public static IYearMonthDurationItem yearMonthDuration(@NonNull String value) { return IYearMonthDurationItem.valueOf(value); } + /** + * Create a duration item using the provided value indicating the seconds and + * nanoseconds of the duration. + * + * @param value + * the duration value + * @return the duration item + */ @NonNull public static IDayTimeDurationItem dayTimeDuration(@NonNull String value) { return IDayTimeDurationItem.valueOf(value); } - @SuppressWarnings("unchecked") - public static ISequence executeFunction( - @NonNull IFunction function, - @Nullable DynamicContext dynamicContext, - @Nullable ISequence focus, - List> arguments) { - - DynamicContext context = dynamicContext == null ? new DynamicContext() : dynamicContext; - ISequence focusSeqence = function.isFocusDepenent() - ? ObjectUtils.requireNonNull(focus, "Function call requires a focus") - : ISequence.empty(); - return (ISequence) function.execute( - arguments == null ? CollectionUtil.emptyList() : arguments, - 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/metapath/cst/RootSlashOnlyTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/RootSlashOnlyTest.java index efb946be2..b3da5559e 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/RootSlashOnlyTest.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/RootSlashOnlyTest.java @@ -42,7 +42,7 @@ class RootSlashOnlyTest @Test void testRootSlashOnlyPathUsingDocument() { - IDocumentNodeItem nodeContext = newDocumentNodeContext(); + IDocumentNodeItem nodeContext = newDocumentNodeMock(); assert nodeContext != null; RootSlashOnlyPath expr = new RootSlashOnlyPath(); @@ -54,7 +54,7 @@ void testRootSlashOnlyPathUsingDocument() { @Test void testRootSlashOnlyPathUsingNonDocument() { - INodeItem item = newNonDocumentNodeContext("non-document"); + INodeItem item = newNonDocumentNodeMock("non-document"); assert item != null; RootSlashOnlyPath expr = new RootSlashOnlyPath(); diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FunctionTestBase.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FunctionTestBase.java index ea021df8b..121e4d561 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FunctionTestBase.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FunctionTestBase.java @@ -28,49 +28,36 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase; import gov.nist.secauto.metaschema.core.metapath.ISequence; -import gov.nist.secauto.metaschema.core.metapath.TestUtils; -import gov.nist.secauto.metaschema.core.metapath.function.FunctionService; import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; import gov.nist.secauto.metaschema.core.metapath.function.IFunction; +import gov.nist.secauto.metaschema.core.metapath.item.IItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem; import gov.nist.secauto.metaschema.core.util.CollectionUtil; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.List; -import javax.xml.namespace.QName; - import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; public class FunctionTestBase extends ExpressionTestBase { - public static void assertFunctionResult( - @NonNull QName functionName, - @NonNull ISequence expectedResult, - List> arguments) { - assertFunctionResult(functionName, null, expectedResult, arguments); - } - - public static void assertFunctionResult( - @NonNull QName functionName, - @Nullable ISequence focus, - @NonNull ISequence expectedResult, - List> arguments) { - - List> usedArguments = arguments == null ? CollectionUtil.emptyList() : arguments; - - IFunction function = FunctionService.getInstance().getFunction(functionName, usedArguments.size()); - - assertNotNull(function, String.format("Function '%s' not found.", functionName)); - - assertFunctionResultInternal(function, focus, expectedResult, usedArguments); - } - + /** + * Assert that the execution of the provided function and arguments produce the + * desired results. + * + * @param function + * the function to test + * @param expectedResult + * the expected result produced by the function + * @param arguments + * the function arguments to use for evaluation + */ public static void assertFunctionResult( @NonNull IFunction function, @NonNull ISequence expectedResult, @@ -78,6 +65,19 @@ public static void assertFunctionResult( assertFunctionResult(function, null, expectedResult, arguments); } + /** + * Assert that the execution of the provided function and arguments produce the + * desired results. + * + * @param function + * the function to test + * @param focus + * the item focus to use for evaluation + * @param expectedResult + * the expected result produced by the function + * @param arguments + * the function arguments to use for evaluation + */ public static void assertFunctionResult( @NonNull IFunction function, @Nullable ISequence focus, @@ -86,11 +86,14 @@ public static void assertFunctionResult( List> usedArguments = arguments == null ? CollectionUtil.emptyList() : arguments; - QName functionName = function.getQName(); - - IFunction resolvedFunction = FunctionService.getInstance().getFunction(functionName, usedArguments.size()); - - assertNotNull(resolvedFunction, String.format("Function '%s' not found in function service.", functionName)); + // QName functionName = function.getQName(); + // + // IFunction resolvedFunction = + // FunctionService.getInstance().getFunction(functionName, + // usedArguments.size()); + // + // assertNotNull(resolvedFunction, String.format("Function '%s' not found in + // function service.", functionName)); assertFunctionResultInternal(function, focus, expectedResult, usedArguments); } @@ -100,7 +103,7 @@ private static void assertFunctionResultInternal( @Nullable ISequence focus, @NonNull ISequence expectedResult, List> arguments) { - ISequence result = TestUtils.executeFunction( + ISequence result = FunctionTestBase.executeFunction( function, newDynamicContext(), focus, @@ -113,4 +116,21 @@ private static void assertFunctionResultInternal( FunctionUtils.getTypes(result.getValue()))); } + + @SuppressWarnings("unchecked") + private static ISequence executeFunction( + @NonNull IFunction function, + @Nullable DynamicContext dynamicContext, + @Nullable ISequence focus, + List> arguments) { + + DynamicContext context = dynamicContext == null ? new DynamicContext() : dynamicContext; + ISequence focusSeqence = function.isFocusDepenent() + ? ObjectUtils.requireNonNull(focus, "Function call requires a focus") + : ISequence.empty(); + return (ISequence) function.execute( + arguments == null ? CollectionUtil.emptyList() : arguments, + context, + focusSeqence); + } } diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/item/node/MockNodeItemFactory.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/item/node/MockNodeItemFactory.java index f6d99a46c..c4edc7df1 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/item/node/MockNodeItemFactory.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/item/node/MockNodeItemFactory.java @@ -51,6 +51,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; // TODO: Integrate with classes in gov.nist.secauto.metaschema.core.testing +@SuppressWarnings("checkstyle:MissingJavadocMethodCheck") @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT") public class MockNodeItemFactory { @@ -219,7 +220,9 @@ public IFieldNodeItem field(@NonNull QName name, @NonNull IAnyAtomicItem value) } @NonNull - public IFieldNodeItem field(@NonNull QName name, @NonNull IAnyAtomicItem value, + public IFieldNodeItem field( + @NonNull QName name, + @NonNull IAnyAtomicItem value, @NonNull List flags) { IFieldNodeItem retval = newMock(IFieldNodeItem.class, ObjectUtils.notNull(name.toString())); @@ -244,7 +247,9 @@ public IFieldNodeItem field(@NonNull QName name, @NonNull IAnyAtomicItem value, } @NonNull - public IAssemblyNodeItem assembly(@NonNull QName name, @NonNull List flags, + public IAssemblyNodeItem assembly( + @NonNull QName name, + @NonNull List flags, @NonNull List> modelItems) { IAssemblyNodeItem retval = newMock(IAssemblyNodeItem.class, ObjectUtils.notNull(name.toString())); 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/testing/AbstractModelBuilder.java b/core/src/test/java/gov/nist/secauto/metaschema/core/testing/AbstractModelBuilder.java index dec8b1726..501fac740 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/testing/AbstractModelBuilder.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/testing/AbstractModelBuilder.java @@ -50,10 +50,21 @@ public abstract class AbstractModelBuilder> private String namespace; private String name; + /** + * Construct a new builder using the provided mocking context. + * + * @param ctx + * the mocking context + */ protected AbstractModelBuilder(@NonNull Mockery ctx) { super(ctx); } + /** + * Reset the builder back to a default state. + * + * @return this builder + */ @NonNull @SuppressWarnings("unchecked") public T reset() { @@ -62,6 +73,13 @@ public T reset() { return (T) this; } + /** + * Apply the provided namespace to use for names built using this builder. + * + * @param name + * the namespace to use + * @return this builder + */ @SuppressWarnings("unchecked") @NonNull public T namespace(@NonNull String name) { @@ -69,6 +87,13 @@ public T namespace(@NonNull String name) { return (T) this; } + /** + * Apply the provided namespace to use for names built using this builder. + * + * @param name + * the namespace to use + * @return this builder + */ @SuppressWarnings("unchecked") @NonNull public T namespace(@NonNull URI name) { @@ -76,6 +101,13 @@ public T namespace(@NonNull URI name) { return (T) this; } + /** + * Apply the provided names to use for names built using this builder. + * + * @param name + * the name to use + * @return this builder + */ @SuppressWarnings("unchecked") @NonNull public T name(@NonNull String name) { @@ -83,16 +115,39 @@ public T name(@NonNull String name) { return (T) this; } + /** + * Validate the data provided to this builder to ensure correct and required + * information is provided. + */ protected void validate() { ObjectUtils.requireNonEmpty(name, "name"); } + /** + * Apply expectations to the mocking context for the provided definition. + * + * @param definition + * the definition to apply mocking expectations for + */ protected void applyDefinition(@NonNull IDefinition definition) { applyModelElement(definition); applyNamed(definition); applyAttributable(definition); } + /** + * Apply expectations to the mocking context for the provided instance, + * definition, and parent definition. + * + * @param + * the Java type of the definition + * @param instance + * the instance to apply mocking expectations for + * @param definition + * the definition to apply mocking expectations for + * @param parent + * the parent definition to apply mocking expectations for + */ protected void applyNamedInstance( @NonNull INamedInstance instance, @NonNull DEF definition, @@ -116,6 +171,13 @@ protected void applyNamedInstance( }); } + /** + * Apply expectations to the mocking context for the provided named model + * element. + * + * @param element + * the named model element to apply mocking expectations for + */ protected void applyNamed(@NonNull INamedModelElement element) { getContext().checking(new Expectations() { { @@ -135,6 +197,13 @@ protected void applyNamed(@NonNull INamedModelElement element) { }); } + /** + * Apply expectations to the mocking context for the provided attributable + * element. + * + * @param element + * the element to apply mocking expectations for + */ protected void applyAttributable(@NonNull IAttributable element) { getContext().checking(new Expectations() { { @@ -144,6 +213,12 @@ protected void applyAttributable(@NonNull IAttributable element) { }); } + /** + * Apply expectations to the mocking context for the provided model element. + * + * @param element + * the model element to apply mocking expectations for + */ protected void applyModelElement(@NonNull IModelElement element) { getContext().checking(new Expectations() { { diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/testing/AssemblyBuilder.java b/core/src/test/java/gov/nist/secauto/metaschema/core/testing/AssemblyBuilder.java index 99ab38395..07a985fd7 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/testing/AssemblyBuilder.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/testing/AssemblyBuilder.java @@ -56,6 +56,13 @@ private AssemblyBuilder(@NonNull Mockery ctx) { super(ctx); } + /** + * Create a new builder using the provided mocking context. + * + * @param ctx + * the mocking context + * @return the new builder + */ @NonNull public static AssemblyBuilder builder(@NonNull Mockery ctx) { return new AssemblyBuilder(ctx).reset(); @@ -68,11 +75,25 @@ public AssemblyBuilder reset() { return this; } + /** + * Use the provided flag instances for built fields. + * + * @param flags + * the flags to use + * @return this builder + */ public AssemblyBuilder flags(@Nullable List flags) { this.flags = flags == null ? CollectionUtil.emptyList() : flags; return this; } + /** + * Use the provided model instances for built fields. + * + * @param modelInstances + * the model instances to use + * @return this builder + */ public AssemblyBuilder modelInstances(@Nullable List modelInstances) { this.modelInstances = modelInstances == null ? CollectionUtil.emptyList() : modelInstances; return this; @@ -86,6 +107,16 @@ public IAssemblyInstanceAbsolute toInstance( return toInstance(parent, def); } + /** + * Build a mocked assembly instance, using the provided definition, as a child + * of the provided parent. + * + * @param parent + * the parent containing the new instance + * @param definition + * the definition to base the instance on + * @return the new mocked instance + */ @NonNull public IAssemblyInstanceAbsolute toInstance( @NonNull IAssemblyDefinition parent, @@ -97,6 +128,11 @@ public IAssemblyInstanceAbsolute toInstance( return retval; } + /** + * Build a mocked assembly definition. + * + * @return the new mocked definition + */ @SuppressWarnings("null") @NonNull public IAssemblyDefinition toDefinition() { diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/testing/FieldBuilder.java b/core/src/test/java/gov/nist/secauto/metaschema/core/testing/FieldBuilder.java index dd1ec28fb..12517568d 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/testing/FieldBuilder.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/testing/FieldBuilder.java @@ -59,6 +59,13 @@ private FieldBuilder(@NonNull Mockery ctx) { super(ctx); } + /** + * Create a new builder using the provided mocking context. + * + * @param ctx + * the mocking context + * @return the new builder + */ @NonNull public static FieldBuilder builder(@NonNull Mockery ctx) { return new FieldBuilder(ctx).reset(); @@ -72,21 +79,50 @@ public FieldBuilder reset() { return this; } + /** + * Apply the provided data type adapter to built fields. + * + * @param dataTypeAdapter + * the data type adapter to use + * @return this builder + */ public FieldBuilder dataTypeAdapter(@NonNull IDataTypeAdapter dataTypeAdapter) { this.dataTypeAdapter = dataTypeAdapter; return this; } + /** + * Apply the provided data type adapter to built fields. + * + * @param defaultValue + * the default value to use + * @return this builder + */ public FieldBuilder defaultValue(@NonNull Object defaultValue) { this.defaultValue = defaultValue; return this; } + /** + * Use the provided flag instances for built fields. + * + * @param flags + * the flags to use + * @return this builder + */ public FieldBuilder flags(@Nullable List flags) { this.flags = flags == null ? CollectionUtil.emptyList() : flags; return this; } + /** + * Build a mocked field instance, based on a mocked definition, as a child of + * the provided parent. + * + * @param parent + * the parent containing the new instance + * @return the new mocked instance + */ @Override @NonNull public IFieldInstanceAbsolute toInstance( @@ -95,6 +131,16 @@ public IFieldInstanceAbsolute toInstance( return toInstance(parent, def); } + /** + * Build a mocked field instance, using the provided definition, as a child of + * the provided parent. + * + * @param parent + * the parent containing the new instance + * @param definition + * the definition to base the instance on + * @return the new mocked instance + */ @NonNull public IFieldInstanceAbsolute toInstance( @NonNull IAssemblyDefinition parent, @@ -106,6 +152,11 @@ public IFieldInstanceAbsolute toInstance( return retval; } + /** + * Build a mocked field definition. + * + * @return the new mocked definition + */ @SuppressWarnings("null") @NonNull public IFieldDefinition toDefinition() { diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/testing/FlagBuilder.java b/core/src/test/java/gov/nist/secauto/metaschema/core/testing/FlagBuilder.java index 35053427d..9feef96d9 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/testing/FlagBuilder.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/testing/FlagBuilder.java @@ -26,6 +26,7 @@ package gov.nist.secauto.metaschema.core.testing; +import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter; import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider; import gov.nist.secauto.metaschema.core.model.IFlagDefinition; import gov.nist.secauto.metaschema.core.model.IFlagInstance; @@ -36,15 +37,27 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A builder that generates mock flag definitions and instances. + */ public final class FlagBuilder extends AbstractModelBuilder { + private IDataTypeAdapter dataTypeAdapter; + private Object defaultValue = null; private boolean required; private FlagBuilder(@NonNull Mockery ctx) { super(ctx); } + /** + * Create a new builder using the provided mocking context. + * + * @param ctx + * the mocking context + * @return the new builder + */ @NonNull public static FlagBuilder builder(@NonNull Mockery ctx) { return new FlagBuilder(ctx).reset(); @@ -52,22 +65,72 @@ public static FlagBuilder builder(@NonNull Mockery ctx) { @Override public FlagBuilder reset() { + this.dataTypeAdapter = MetaschemaDataTypeProvider.DEFAULT_DATA_TYPE; + this.defaultValue = null; this.required = IFlagInstance.DEFAULT_FLAG_REQUIRED; return this; } + /** + * Apply the provided required setting to built flags. + * + * @param required + * {@code true} if the flag is required or {@code false} otherwise + * @return this builder + */ public FlagBuilder required(boolean required) { this.required = required; return this; } + /** + * Apply the provided data type adapter to built flags. + * + * @param dataTypeAdapter + * the data type adapter to use + * @return this builder + */ + public FlagBuilder dataTypeAdapter(@NonNull IDataTypeAdapter dataTypeAdapter) { + this.dataTypeAdapter = dataTypeAdapter; + return this; + } + + /** + * Apply the provided data type adapter to built flags. + * + * @param defaultValue + * the default value to use + * @return this builder + */ + public FlagBuilder defaultValue(@NonNull Object defaultValue) { + this.defaultValue = defaultValue; + return this; + } + + /** + * Build a mocked flag instance, based on a mocked definition, as a child of the + * provided parent. + * + * @param parent + * the parent containing the new instance + * @return the new mocked instance + */ @NonNull - public IFlagInstance toInstance( - @NonNull IModelDefinition parent) { + public IFlagInstance toInstance(@NonNull IModelDefinition parent) { IFlagDefinition def = toDefinition(); return toInstance(parent, def); } + /** + * Build a mocked flag instance, using the provided definition, as a child of + * the provided parent. + * + * @param parent + * the parent containing the new instance + * @param definition + * the definition to base the instance on + * @return the new mocked instance + */ @NonNull public IFlagInstance toInstance( @NonNull IModelDefinition parent, @@ -88,6 +151,11 @@ public IFlagInstance toInstance( return retval; } + /** + * Build a mocked flag definition. + * + * @return the new mocked definition + */ @NonNull public IFlagDefinition toDefinition() { validate(); @@ -98,7 +166,9 @@ public IFlagDefinition toDefinition() { getContext().checking(new Expectations() { { allowing(retval).getJavaTypeAdapter(); - will(returnValue(MetaschemaDataTypeProvider.STRING)); + will(returnValue(dataTypeAdapter)); + allowing(retval).getDefaultValue(); + will(returnValue(defaultValue)); } }); return retval; diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/testing/MockedModelTestSupport.java b/core/src/test/java/gov/nist/secauto/metaschema/core/testing/MockedModelTestSupport.java index 0ecbd3327..ad35042ec 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/testing/MockedModelTestSupport.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/testing/MockedModelTestSupport.java @@ -37,16 +37,31 @@ public class MockedModelTestSupport implements IMockFactory { @NonNull Mockery context = new JUnit5Mockery(); + /** + * Get a new flag builder. + * + * @return the builder + */ @NonNull protected FlagBuilder flag() { return FlagBuilder.builder(context); } + /** + * Get a new field builder. + * + * @return the builder + */ @NonNull protected FieldBuilder field() { return FieldBuilder.builder(context); } + /** + * Get a new assembly builder. + * + * @return the builder + */ @NonNull protected AssemblyBuilder assembly() { return AssemblyBuilder.builder(context); 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 d6859828b..ef46b7a3c 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 @@ -326,11 +326,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/pom.xml b/databind/pom.xml index d4dbb8f05..21d4c0d02 100644 --- a/databind/pom.xml +++ b/databind/pom.xml @@ -10,7 +10,7 @@ metaschema-databind jar - Metaschema Java Data/Object Binding and Code Generation + Metaschema Java Data Binding and Code Generation A Metaschema binding-based parser and code generator for Java objects supporting serialization of conformant XML, JSON, and YAML data. 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/databind/src/main/java/gov/nist/secauto/metaschema/databind/package-info.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/package-info.java index 53cdd7345..63bab10ac 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/package-info.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/package-info.java @@ -38,8 +38,7 @@ * ({@link gov.nist.secauto.metaschema.databind.model.binding.metaschema}). The * {@link gov.nist.secauto.metaschema.databind.model.metaschema.BindingConstraintLoader} * can be used to load any Metaschema module using this method. Once loaded, the - * module can be registered with the binding context using - * {@link gov.nist.secauto.metaschema.databind.IBindingContext#registerModule(gov.nist.secauto.metaschema.core.model.IModule, java.nio.file.Path)}. + * module can be registered with the binding context. * */ diff --git a/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/metapath/ListFunctionsSubcommand.java b/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/metapath/ListFunctionsSubcommand.java index b7779ae25..4ee7db43e 100644 --- a/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/metapath/ListFunctionsSubcommand.java +++ b/metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/commands/metapath/ListFunctionsSubcommand.java @@ -86,7 +86,7 @@ protected ExitStatus executeCommand( IFunction::getName, Collectors.toList()))); - Map namespaceToPrefixMap = StaticContext.getWellKnownNamespaces().entrySet().stream() + Map namespaceToPrefixMap = StaticContext.getWellKnownNamespacesMap().entrySet().stream() .collect(Collectors.toMap(entry -> entry.getValue().toASCIIString(), Map.Entry::getKey)); List namespaces = new ArrayList<>(namespaceToNameToFunctionMap.keySet()); 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());