diff --git a/core/src/main/antlr4/Metapath10.g4 b/core/src/main/antlr4/Metapath10.g4 index 80c80de41..ecfb5f43d 100644 --- a/core/src/main/antlr4/Metapath10.g4 +++ b/core/src/main/antlr4/Metapath10.g4 @@ -61,8 +61,7 @@ reversestep : reverseaxis nodetest | abbrevreversestep ; reverseaxis : KW_PARENT COLONCOLON | KW_ANCESTOR COLONCOLON | KW_PRECEDING_SIBLING COLONCOLON | KW_PRECEDING COLONCOLON | KW_ANCESTOR_OR_SELF COLONCOLON ; // [45] abbrevreversestep : DD ; -// nodetest : kindtest | nametest ; -nodetest : nametest ; +nodetest : kindtest | nametest ; nametest : eqname | wildcard ; wildcard : STAR | NCName CS | SC NCName | BracedURILiteral STAR ; postfixexpr : primaryexpr (predicate | argumentlist | lookup)* ; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/IDMAssemblyNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/IDMAssemblyNodeItem.java new file mode 100644 index 000000000..09cf2017c --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/IDMAssemblyNodeItem.java @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.mdm; + +import gov.nist.secauto.metaschema.core.mdm.impl.IDMModelNodeItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem; +import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition; +import gov.nist.secauto.metaschema.core.model.IAssemblyInstance; +import gov.nist.secauto.metaschema.core.model.IFieldInstance; +import gov.nist.secauto.metaschema.core.model.IResourceLocation; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * An assembly node item implementation that is backed by a simple Metaschema + * module-based data model. + */ +public interface IDMAssemblyNodeItem + extends IAssemblyNodeItem, IDMModelNodeItem { + /** + * Create and add a new field to the underlying data model. + * + * @param instance + * the Metaschema field instance describing the field + * @param resourceLocation + * information about the location of the field within the containing + * resource + * @param value + * the atomic field value + * @return the new field node item + */ + @NonNull + IDMFieldNodeItem newField( + @NonNull IFieldInstance instance, + @NonNull IResourceLocation resourceLocation, + @NonNull IAnyAtomicItem value); + + /** + * Create and add a new assembly to the underlying data model. + * + * @param instance + * the Metaschema assembly instance describing the assembly + * @param resourceLocation + * information about the location of the assembly within the containing + * resource + * @return the new assembly node item + */ + @NonNull + IDMAssemblyNodeItem newAssembly( + @NonNull IAssemblyInstance instance, + @NonNull IResourceLocation resourceLocation); +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/IDMDocumentNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/IDMDocumentNodeItem.java new file mode 100644 index 000000000..275b5a341 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/IDMDocumentNodeItem.java @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.mdm; + +import gov.nist.secauto.metaschema.core.mdm.impl.DocumentImpl; +import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem; +import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition; +import gov.nist.secauto.metaschema.core.model.IResourceLocation; + +import java.net.URI; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * A document node item implementation that is backed by a simple Metaschema + * module-based data model. + */ +public interface IDMDocumentNodeItem + extends IDocumentNodeItem { + @Override + IDMRootAssemblyNodeItem getRootAssemblyNodeItem(); + + /** + * Create a new Metaschema document-based data model. + * + * @param resource + * the base URI of the document resource + * @param resourceLocation + * information about the (intended) location of the document resource + * @param rootAssembly + * the assembly that is at the root of the node tree for this document + * @param rootAssemblyLocation + * information about the (intended) location of the root assembly + * resource + * @return the document node item + */ + @NonNull + static IDMDocumentNodeItem newInstance( + @NonNull URI resource, + @NonNull IResourceLocation resourceLocation, + @NonNull IAssemblyDefinition rootAssembly, + @NonNull IResourceLocation rootAssemblyLocation) { + return new DocumentImpl(resource, resourceLocation, rootAssembly, rootAssemblyLocation); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/IDMFieldNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/IDMFieldNodeItem.java new file mode 100644 index 000000000..f26e3680f --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/IDMFieldNodeItem.java @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.mdm; + +import gov.nist.secauto.metaschema.core.mdm.impl.IDMModelNodeItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.IFieldNodeItem; +import gov.nist.secauto.metaschema.core.model.IFieldDefinition; +import gov.nist.secauto.metaschema.core.model.IFieldInstance; + +/** + * A field node item implementation that is backed by a simple Metaschema + * module-based data model. + */ +public interface IDMFieldNodeItem + extends IFieldNodeItem, IDMModelNodeItem { + // no additional methods +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/IDMRootAssemblyNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/IDMRootAssemblyNodeItem.java new file mode 100644 index 000000000..03cab04ce --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/IDMRootAssemblyNodeItem.java @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.mdm; + +import gov.nist.secauto.metaschema.core.metapath.item.node.IRootAssemblyNodeItem; + +/** + * A root assembly node item implementation that is backed by a simple + * Metaschema module-based data model. + */ +public interface IDMRootAssemblyNodeItem + extends IDMAssemblyNodeItem, IRootAssemblyNodeItem { + // no additional methods +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/AbstractDMAssemblyNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/AbstractDMAssemblyNodeItem.java new file mode 100644 index 000000000..6eda28488 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/AbstractDMAssemblyNodeItem.java @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.mdm.impl; + +import gov.nist.secauto.metaschema.core.mdm.IDMAssemblyNodeItem; +import gov.nist.secauto.metaschema.core.mdm.IDMFieldNodeItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.AbstractNodeItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.IFlagNodeItem; +import gov.nist.secauto.metaschema.core.model.IAssemblyInstance; +import gov.nist.secauto.metaschema.core.model.IFieldInstance; +import gov.nist.secauto.metaschema.core.model.IFlagInstance; +import gov.nist.secauto.metaschema.core.model.IResourceLocation; +import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; +import gov.nist.secauto.metaschema.core.util.CollectionUtil; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public abstract class AbstractDMAssemblyNodeItem + extends AbstractNodeItem + implements IDMAssemblyNodeItem { + @NonNull + private final Map flags = new ConcurrentHashMap<>(); + @NonNull + private final Map>> modelItems + = new ConcurrentHashMap<>(); + + protected AbstractDMAssemblyNodeItem() { + // nothing to do + } + + @Override + public Object getValue() { + return this; + } + + @Override + public String stringValue() { + return ""; + } + + @Override + protected String getValueSignature() { + return ""; + } + + @Override + public Collection getFlags() { + return ObjectUtils.notNull(flags.values()); + } + + @Override + public IFlagNodeItem getFlagByName(IEnhancedQName name) { + return flags.get(name); + } + + @Override + public IFlagNodeItem newFlag( + @NonNull IFlagInstance instance, + @NonNull IResourceLocation resourceLocation, + @NonNull IAnyAtomicItem value) { + IFlagNodeItem flag = new FlagImpl(instance, this, resourceLocation, value); + flags.put(instance.getQName(), flag); + return flag; + } + + @Override + public Collection>> getModelItems() { + return ObjectUtils.notNull(modelItems.values()); + } + + @Override + public List> getModelItemsByName(IEnhancedQName name) { + List> retval = modelItems.get(name); + return retval == null ? CollectionUtil.emptyList() : retval; + } + + @Override + public IDMFieldNodeItem newField(IFieldInstance instance, IResourceLocation resourceLocation, IAnyAtomicItem value) { + List> result = modelItems.computeIfAbsent( + instance.getQName(), + name -> Collections.synchronizedList(new LinkedList>())); + IDMFieldNodeItem field = new FieldImpl(instance, this, resourceLocation, value); + result.add(field); + return field; + } + + @Override + public IDMAssemblyNodeItem newAssembly(IAssemblyInstance instance, IResourceLocation resourceLocation) { + List> result = modelItems.computeIfAbsent( + instance.getQName(), + name -> Collections.synchronizedList(new LinkedList>())); + IDMAssemblyNodeItem assembly = new AssemblyImpl(instance, this, resourceLocation); + result.add(assembly); + return assembly; + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/AbstractDMInstanceNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/AbstractDMInstanceNodeItem.java new file mode 100644 index 000000000..6d372c501 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/AbstractDMInstanceNodeItem.java @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.mdm.impl; + +import gov.nist.secauto.metaschema.core.metapath.StaticContext; +import gov.nist.secauto.metaschema.core.metapath.item.node.AbstractInstanceNodeItem; +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.model.IDefinition; +import gov.nist.secauto.metaschema.core.model.IModelDefinition; +import gov.nist.secauto.metaschema.core.model.INamedInstance; +import gov.nist.secauto.metaschema.core.model.IResourceLocation; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public abstract class AbstractDMInstanceNodeItem< + D extends IDefinition, + I extends INamedInstance, + P extends IModelNodeItem> + extends AbstractInstanceNodeItem + implements INodeItem { + @NonNull + private final IResourceLocation resourceLocation; + + protected AbstractDMInstanceNodeItem( + @NonNull I instance, + @NonNull P parent, + @NonNull IResourceLocation resourceLocation) { + super(instance, parent); + this.resourceLocation = resourceLocation; + } + + @Override + public IResourceLocation getLocation() { + return resourceLocation; + } + + @Override + public StaticContext getStaticContext() { + return getParentNodeItem().getStaticContext(); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/AssemblyImpl.java b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/AssemblyImpl.java new file mode 100644 index 000000000..7c4e047c7 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/AssemblyImpl.java @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.mdm.impl; + +import gov.nist.secauto.metaschema.core.mdm.IDMAssemblyNodeItem; +import gov.nist.secauto.metaschema.core.metapath.StaticContext; +import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition; +import gov.nist.secauto.metaschema.core.model.IAssemblyInstance; +import gov.nist.secauto.metaschema.core.model.IResourceLocation; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public class AssemblyImpl + extends AbstractDMAssemblyNodeItem { + @NonNull + private final IAssemblyInstance instance; + @NonNull + private final IDMAssemblyNodeItem parent; + @NonNull + private final IResourceLocation resourceLocation; + + public AssemblyImpl( + @NonNull IAssemblyInstance instance, + @NonNull IDMAssemblyNodeItem parent, + @NonNull IResourceLocation resourceLocation) { + this.instance = instance; + this.parent = parent; + this.resourceLocation = resourceLocation; + } + + @Override + public IResourceLocation getLocation() { + return resourceLocation; + } + + @Override + public int getPosition() { + return getParentNodeItem().getModelItemsByName(getQName()).indexOf(this); + } + + @Override + @NonNull + public IDMAssemblyNodeItem getParentNodeItem() { + return getParentContentNodeItem(); + } + + @Override + @NonNull + public IDMAssemblyNodeItem getParentContentNodeItem() { + return parent; + } + + @Override + public IAssemblyDefinition getDefinition() { + return getInstance().getDefinition(); + } + + @Override + public IAssemblyInstance getInstance() { + return instance; + } + + @Override + public StaticContext getStaticContext() { + return getParentNodeItem().getStaticContext(); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/DocumentImpl.java b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/DocumentImpl.java new file mode 100644 index 000000000..822233e9a --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/DocumentImpl.java @@ -0,0 +1,144 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.mdm.impl; + +import gov.nist.secauto.metaschema.core.mdm.IDMDocumentNodeItem; +import gov.nist.secauto.metaschema.core.mdm.IDMRootAssemblyNodeItem; +import gov.nist.secauto.metaschema.core.metapath.StaticContext; +import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.IFlagNodeItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.IModelNodeItem; +import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition; +import gov.nist.secauto.metaschema.core.model.IResourceLocation; +import gov.nist.secauto.metaschema.core.model.ISource; +import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; +import gov.nist.secauto.metaschema.core.util.CollectionUtil; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.net.URI; +import java.util.Collection; +import java.util.List; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public class DocumentImpl implements IDMDocumentNodeItem { + @NonNull + private final RootAssembly root; + @NonNull + private final IResourceLocation resourceLocation; + @NonNull + private final ISource source; + + public DocumentImpl( + @NonNull URI resource, + @NonNull IResourceLocation resourceLocation, + @NonNull IAssemblyDefinition root, + @NonNull IResourceLocation assemblyLocation) { + this.root = new RootAssembly(root, assemblyLocation); + this.resourceLocation = resourceLocation; + this.source = ISource.externalSource(resource); + } + + @Override + public URI getDocumentUri() { + return ObjectUtils.notNull(source.getSource()); + } + + @Override + public Collection getFlags() { + // no flags + return CollectionUtil.emptyList(); + } + + @Override + public IFlagNodeItem getFlagByName(IEnhancedQName name) { + // no flags + return null; + } + + @Override + public Collection>> getModelItems() { + return CollectionUtil.singleton(CollectionUtil.singletonList(root)); + } + + @Override + public List> getModelItemsByName(IEnhancedQName name) { + return root.getQName().equals(name) + ? CollectionUtil.singletonList(root) + : CollectionUtil.emptyList(); + } + + @Override + public IResourceLocation getLocation() { + return resourceLocation; + } + + @Override + public String stringValue() { + return ""; + } + + @Override + public StaticContext getStaticContext() { + return source.getStaticContext(); + } + + @Override + public Object getValue() { + return this; + } + + @Override + public String toSignature() { + return ObjectUtils.notNull(new StringBuilder() + .append(getType().toSignature()) + .append('\u2ABB') + .append(getMetapath()) + .append('\u2ABC') + .toString()); + } + + @Override + public IDMRootAssemblyNodeItem getRootAssemblyNodeItem() { + return root; + } + + private class RootAssembly + extends AbstractDMAssemblyNodeItem + implements IDMRootAssemblyNodeItem { + @NonNull + private final IAssemblyDefinition definition; + @NonNull + private final IResourceLocation resourceLocation; + + public RootAssembly( + @NonNull IAssemblyDefinition definition, + @NonNull IResourceLocation location) { + this.definition = definition; + this.resourceLocation = location; + } + + @Override + public IEnhancedQName getQName() { + return definition.getRootQName(); + } + + @Override + public IResourceLocation getLocation() { + return resourceLocation; + } + + @Override + public IDocumentNodeItem getDocumentNodeItem() { + return DocumentImpl.this; + } + + @Override + public IAssemblyDefinition getDefinition() { + return definition; + } + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/FieldImpl.java b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/FieldImpl.java new file mode 100644 index 000000000..329efcfc9 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/FieldImpl.java @@ -0,0 +1,109 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.mdm.impl; + +import gov.nist.secauto.metaschema.core.mdm.IDMFieldNodeItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.IFlagNodeItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.IModelNodeItem; +import gov.nist.secauto.metaschema.core.model.IFieldDefinition; +import gov.nist.secauto.metaschema.core.model.IFieldInstance; +import gov.nist.secauto.metaschema.core.model.IFlagInstance; +import gov.nist.secauto.metaschema.core.model.IResourceLocation; +import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; +import gov.nist.secauto.metaschema.core.util.CollectionUtil; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public class FieldImpl + extends AbstractDMInstanceNodeItem + implements IDMFieldNodeItem { + @NonNull + private IAnyAtomicItem value; + @NonNull + private final Map flags = new ConcurrentHashMap<>(); + + public FieldImpl( + @NonNull IFieldInstance instance, + @NonNull IAssemblyNodeItem parent, + @NonNull IResourceLocation resourceLocation, + @NonNull IAnyAtomicItem value) { + super(instance, parent, resourceLocation); + this.value = value; + } + + @Override + public IAnyAtomicItem toAtomicItem() { + return value; + } + + public void setValue(@NonNull IAnyAtomicItem value) { + this.value = getValueItemType().cast(value); + } + + public void setValue(@NonNull Object value) { + this.value = getValueItemType().newItem(value); + } + + @Override + public Object getValue() { + return toAtomicItem().getValue(); + } + + @Override + public String stringValue() { + return toAtomicItem().asString(); + } + + @Override + protected String getValueSignature() { + return toAtomicItem().toSignature(); + } + + @Override + public int getPosition() { + return getParentNodeItem().getModelItemsByName(getQName()).indexOf(this); + } + + @Override + public Collection getFlags() { + return ObjectUtils.notNull(flags.values()); + } + + @Override + public IFlagNodeItem getFlagByName(IEnhancedQName name) { + return flags.get(name); + } + + @Override + public Collection>> getModelItems() { + // no model items + return CollectionUtil.emptyList(); + } + + @Override + public List> getModelItemsByName(IEnhancedQName name) { + // no model items + return CollectionUtil.emptyList(); + } + + @Override + public IFlagNodeItem newFlag( + @NonNull IFlagInstance instance, + @NonNull IResourceLocation resourceLocation, + @NonNull IAnyAtomicItem value) { + IFlagNodeItem flag = new FlagImpl(instance, this, resourceLocation, value); + flags.put(instance.getQName(), flag); + return flag; + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/FlagImpl.java b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/FlagImpl.java new file mode 100644 index 000000000..1335777a3 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/FlagImpl.java @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.mdm.impl; + +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.IFlagNodeItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.IModelNodeItem; +import gov.nist.secauto.metaschema.core.model.IFlagDefinition; +import gov.nist.secauto.metaschema.core.model.IFlagInstance; +import gov.nist.secauto.metaschema.core.model.IResourceLocation; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public class FlagImpl + extends AbstractDMInstanceNodeItem> + implements IFlagNodeItem { + @NonNull + private IAnyAtomicItem value; + + public FlagImpl( + @NonNull IFlagInstance instance, + @NonNull IModelNodeItem parent, + @NonNull IResourceLocation resourceLocation, + @NonNull IAnyAtomicItem value) { + super(instance, parent, resourceLocation); + this.value = value; + } + + @Override + public IAnyAtomicItem toAtomicItem() { + return value; + } + + @Override + public Object getValue() { + return this; + } + + @Override + public String stringValue() { + return toAtomicItem().asString(); + } + + @Override + protected String getValueSignature() { + return toAtomicItem().toSignature(); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/IDMAtomicValuedNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/IDMAtomicValuedNodeItem.java new file mode 100644 index 000000000..6fa3c2af8 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/IDMAtomicValuedNodeItem.java @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.mdm.impl; + +import gov.nist.secauto.metaschema.core.metapath.item.node.IAtomicValuedNodeItem; + +public interface IDMAtomicValuedNodeItem extends IAtomicValuedNodeItem { + @Override + default String stringValue() { + return toAtomicItem().asString(); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/IDMModelNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/IDMModelNodeItem.java new file mode 100644 index 000000000..f0df08fdc --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/mdm/impl/IDMModelNodeItem.java @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.mdm.impl; + +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.IFlagNodeItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.IModelNodeItem; +import gov.nist.secauto.metaschema.core.model.IFlagInstance; +import gov.nist.secauto.metaschema.core.model.IModelDefinition; +import gov.nist.secauto.metaschema.core.model.INamedModelInstance; +import gov.nist.secauto.metaschema.core.model.IResourceLocation; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public interface IDMModelNodeItem + extends IModelNodeItem { + @NonNull + IFlagNodeItem newFlag( + @NonNull IFlagInstance instance, + @NonNull IResourceLocation resourceLocation, + @NonNull IAnyAtomicItem value); +} 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 45ab73d3f..731313041 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 @@ -225,8 +225,7 @@ public String lookupNamespaceForPrefix(@NonNull String prefix) { */ @Nullable public String lookupPrefixForNamespace(@NonNull String namespace) { - String result = lookupPrefixForNamespaceURI(namespace); - return result == null ? XMLConstants.DEFAULT_NS_PREFIX : result; + return lookupPrefixForNamespaceURI(namespace); } /** 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 208723d80..9641d3b01 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 @@ -37,15 +37,16 @@ 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.KindNodeTest; import gov.nist.secauto.metaschema.core.metapath.cst.path.ModelInstance; -import gov.nist.secauto.metaschema.core.metapath.cst.path.NameTest; +import gov.nist.secauto.metaschema.core.metapath.cst.path.NameNodeTest; import gov.nist.secauto.metaschema.core.metapath.cst.path.RelativeDoubleSlashPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RelativeSlashPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RootDoubleSlashPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RootSlashOnlyPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RootSlashPath; 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.cst.path.WildcardNodeTest; import gov.nist.secauto.metaschema.core.metapath.cst.type.Cast; import gov.nist.secauto.metaschema.core.metapath.cst.type.Castable; import gov.nist.secauto.metaschema.core.metapath.cst.type.InstanceOf; @@ -259,7 +260,7 @@ public RESULT visitMultiplication(Multiplication expr, CONTEXT context) { } @Override - public RESULT visitName(NameTest expr, CONTEXT context) { + public RESULT visitNameNodeTest(NameNodeTest expr, CONTEXT context) { return defaultResult(); } @@ -324,7 +325,7 @@ public RESULT visitUnion(Union expr, CONTEXT context) { } @Override - public RESULT visitWildcard(Wildcard expr, CONTEXT context) { + public RESULT visitWildcardNodeTest(WildcardNodeTest expr, CONTEXT context) { return defaultResult(); } @@ -422,4 +423,9 @@ public RESULT visitCastable(Castable expr, CONTEXT context) { public RESULT visitTreat(Treat expr, CONTEXT context) { return visitChildren(expr, context); } + + @Override + public RESULT visitKindNodeTest(KindNodeTest expr, CONTEXT context) { + return visitChildren(expr, context); + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractNamedInstanceExpression.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractNamedInstanceExpression.java index 13152f9ea..cc540b378 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractNamedInstanceExpression.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractNamedInstanceExpression.java @@ -7,8 +7,8 @@ import gov.nist.secauto.metaschema.core.metapath.cst.path.AbstractPathExpression; import gov.nist.secauto.metaschema.core.metapath.cst.path.INodeTestExpression; -import gov.nist.secauto.metaschema.core.metapath.cst.path.NameTest; -import gov.nist.secauto.metaschema.core.metapath.cst.path.Wildcard; +import gov.nist.secauto.metaschema.core.metapath.cst.path.NameNodeTest; +import gov.nist.secauto.metaschema.core.metapath.cst.path.WildcardNodeTest; import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; import java.util.List; @@ -32,7 +32,7 @@ public AbstractNamedInstanceExpression(@NonNull INodeTestExpression test) { } /** - * Get the {@link Wildcard} or {@link NameTest} test. + * Get the {@link WildcardNodeTest} or {@link NameNodeTest} test. * * @return the test */ 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 7c8243227..b62659679 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 @@ -43,18 +43,18 @@ 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; import gov.nist.secauto.metaschema.core.metapath.cst.path.IWildcardMatcher; +import gov.nist.secauto.metaschema.core.metapath.cst.path.KindNodeTest; import gov.nist.secauto.metaschema.core.metapath.cst.path.ModelInstance; -import gov.nist.secauto.metaschema.core.metapath.cst.path.NameTest; +import gov.nist.secauto.metaschema.core.metapath.cst.path.NameNodeTest; import gov.nist.secauto.metaschema.core.metapath.cst.path.RelativeDoubleSlashPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RelativeSlashPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RootDoubleSlashPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RootSlashOnlyPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RootSlashPath; 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.cst.path.WildcardNodeTest; import gov.nist.secauto.metaschema.core.metapath.cst.type.Cast; import gov.nist.secauto.metaschema.core.metapath.cst.type.Castable; import gov.nist.secauto.metaschema.core.metapath.cst.type.InstanceOf; @@ -688,9 +688,17 @@ protected IExpression handleReversestep(Metapath10.ReversestepContext ctx) { */ @NonNull protected INodeTestExpression parseNodeTest(Metapath10.NodetestContext ctx, boolean flag) { - // TODO: implement kind test - Metapath10.NametestContext nameTestCtx = ctx.nametest(); - return parseNameTest(nameTestCtx, flag); + INodeTestExpression retval; + if (ctx.kindtest() != null) { + IItemType itemType = TypeTestSupport.parseKindTest( + ObjectUtils.notNull(ctx.kindtest()), + getContext()); + retval = new KindNodeTest(itemType); + } else { + Metapath10.NametestContext nameTestCtx = ctx.nametest(); + retval = parseNameTest(nameTestCtx, flag); + } + return retval; } /** @@ -703,12 +711,12 @@ protected INodeTestExpression parseNodeTest(Metapath10.NodetestContext ctx, bool * @return the resulting expression */ @NonNull - protected INameTestExpression parseNameTest(Metapath10.NametestContext ctx, boolean flag) { + protected INodeTestExpression parseNameTest(Metapath10.NametestContext ctx, boolean flag) { ParseTree testType = ObjectUtils.requireNonNull(ctx.getChild(0)); StaticContext staticContext = getContext(); - INameTestExpression retval; + INodeTestExpression retval; if (testType instanceof Metapath10.EqnameContext) { String name = ObjectUtils.notNull(ctx.eqname().getText()); IEnhancedQName qname = flag @@ -719,9 +727,9 @@ protected INameTestExpression parseNameTest(Metapath10.NametestContext ctx, bool && qname.getNamespace().isEmpty() && staticContext.isUseWildcardWhenNamespaceNotDefaulted()) { // Use a wildcard namespace - retval = new Wildcard(IWildcardMatcher.anyNamespace(ObjectUtils.notNull(qname.getLocalName()))); + retval = new WildcardNodeTest(IWildcardMatcher.anyNamespace(ObjectUtils.notNull(qname.getLocalName()))); } else { - retval = new NameTest(qname); + retval = new NameNodeTest(qname); } } else { // wildcard retval = handleWildcard((Metapath10.WildcardContext) testType); @@ -730,7 +738,7 @@ protected INameTestExpression parseNameTest(Metapath10.NametestContext ctx, bool } @Override - protected Wildcard handleWildcard(Metapath10.WildcardContext ctx) { + protected WildcardNodeTest handleWildcard(Metapath10.WildcardContext ctx) { IWildcardMatcher matcher = null; if (ctx.STAR() == null) { if (ctx.CS() != null) { @@ -752,7 +760,7 @@ protected Wildcard handleWildcard(Metapath10.WildcardContext ctx) { } } // star needs no matcher: any prefix, any local-name - return new Wildcard(matcher); + return new WildcardNodeTest(matcher); } // ====================================================================== 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 0c449750a..e451fb272 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 @@ -37,15 +37,16 @@ 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.KindNodeTest; import gov.nist.secauto.metaschema.core.metapath.cst.path.ModelInstance; -import gov.nist.secauto.metaschema.core.metapath.cst.path.NameTest; +import gov.nist.secauto.metaschema.core.metapath.cst.path.NameNodeTest; import gov.nist.secauto.metaschema.core.metapath.cst.path.RelativeDoubleSlashPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RelativeSlashPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RootDoubleSlashPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RootSlashOnlyPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RootSlashPath; 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.cst.path.WildcardNodeTest; import gov.nist.secauto.metaschema.core.metapath.cst.type.Cast; import gov.nist.secauto.metaschema.core.metapath.cst.type.Castable; import gov.nist.secauto.metaschema.core.metapath.cst.type.InstanceOf; @@ -241,8 +242,8 @@ public String visitMultiplication(Multiplication expr, State context) { } @Override - public String visitName(NameTest expr, State context) { - return appendNode(expr, super.visitName(expr, context), context); + public String visitNameNodeTest(NameNodeTest expr, State context) { + return appendNode(expr, super.visitNameNodeTest(expr, context), context); } @Override @@ -311,8 +312,8 @@ public String visitUnion(Union expr, State context) { } @Override - public String visitWildcard(Wildcard expr, State context) { - return appendNode(expr, super.visitWildcard(expr, context), context); + public String visitWildcardNodeTest(WildcardNodeTest expr, State context) { + return appendNode(expr, super.visitWildcardNodeTest(expr, context), context); } @Override @@ -409,6 +410,12 @@ public String visitCastable(Castable expr, State context) { public String visitTreat(Treat expr, State context) { return appendNode(expr, super.visitTreat(expr, context), context); } + + @Override + public String visitKindNodeTest(KindNodeTest expr, State context) { + return appendNode(expr, super.visitKindNodeTest(expr, context), context); + } + } static class State { 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 19480dfe2..45ec44931 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 @@ -37,15 +37,16 @@ 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.KindNodeTest; import gov.nist.secauto.metaschema.core.metapath.cst.path.ModelInstance; -import gov.nist.secauto.metaschema.core.metapath.cst.path.NameTest; +import gov.nist.secauto.metaschema.core.metapath.cst.path.NameNodeTest; import gov.nist.secauto.metaschema.core.metapath.cst.path.RelativeDoubleSlashPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RelativeSlashPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RootDoubleSlashPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RootSlashOnlyPath; import gov.nist.secauto.metaschema.core.metapath.cst.path.RootSlashPath; 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.cst.path.WildcardNodeTest; import gov.nist.secauto.metaschema.core.metapath.cst.type.Cast; import gov.nist.secauto.metaschema.core.metapath.cst.type.Castable; import gov.nist.secauto.metaschema.core.metapath.cst.type.InstanceOf; @@ -306,7 +307,7 @@ public interface IExpressionVisitor { * the processing context * @return the visitation result or {@code null} if no result was produced */ - RESULT visitName(@NonNull NameTest expr, @NonNull CONTEXT context); + RESULT visitNameNodeTest(@NonNull NameNodeTest expr, @NonNull CONTEXT context); /** * Visit the CST node. @@ -449,7 +450,7 @@ public interface IExpressionVisitor { * the processing context * @return the visitation result or {@code null} if no result was produced */ - RESULT visitWildcard(@NonNull Wildcard expr, @NonNull CONTEXT context); + RESULT visitWildcardNodeTest(@NonNull WildcardNodeTest expr, @NonNull CONTEXT context); /** * Visit the CST node. @@ -659,4 +660,15 @@ public interface IExpressionVisitor { * @return the visitation result or {@code null} if no result was produced */ RESULT visitTreat(@NonNull Treat expr, @NonNull CONTEXT context); + + /** + * Visit the CST node. + * + * @param expr + * the CST node to visit + * @param context + * the processing context + * @return the visitation result or {@code null} if no result was produced + */ + RESULT visitKindNodeTest(@NonNull KindNodeTest expr, @NonNull CONTEXT context); } 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 b78a167b5..a62979ebe 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 @@ -68,14 +68,14 @@ protected Stream match(@NonNull INodeItem focusedItem) Stream retval; INodeTestExpression test = getTest(); - if (test instanceof NameTest) { - IEnhancedQName name = ((NameTest) getTest()).getName(); + if (test instanceof NameNodeTest) { + IEnhancedQName name = ((NameNodeTest) getTest()).getName(); IFlagNodeItem item = focusedItem.getFlagByName(name); retval = item == null ? Stream.empty() : Stream.of(item); - } else if (test instanceof Wildcard) { + } else if (test instanceof WildcardNodeTest) { // match all items - retval = ((Wildcard) test).match(focusedItem.flags()); + retval = ((WildcardNodeTest) test).matchStream(focusedItem.flags()); } else { throw new UnsupportedOperationException(test.getClass().getName()); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/INameTestExpression.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/INameTestExpression.java deleted file mode 100644 index c5b607227..000000000 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/INameTestExpression.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SPDX-FileCopyrightText: none - * SPDX-License-Identifier: CC0-1.0 - */ - -package gov.nist.secauto.metaschema.core.metapath.cst.path; - -import gov.nist.secauto.metaschema.core.metapath.cst.IExpression; -import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; - -import java.util.Collections; -import java.util.List; - -public interface INameTestExpression extends INodeTestExpression { - @SuppressWarnings("null") - @Override - default List getChildren() { - return Collections.emptyList(); - } - - @Override - default Class getBaseResultType() { - return INodeItem.class; - } - - @Override - default Class getStaticResultType() { - return getBaseResultType(); - } -} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/INodeTestExpression.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/INodeTestExpression.java index 32137b111..06a7f13f1 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/INodeTestExpression.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/INodeTestExpression.java @@ -6,7 +6,25 @@ package gov.nist.secauto.metaschema.core.metapath.cst.path; import gov.nist.secauto.metaschema.core.metapath.cst.IExpression; +import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; + +import java.util.Collections; +import java.util.List; public interface INodeTestExpression extends IExpression { - // no additional methods + @SuppressWarnings("null") + @Override + default List getChildren() { + return Collections.emptyList(); + } + + @Override + default Class getBaseResultType() { + return INodeItem.class; + } + + @Override + default Class getStaticResultType() { + return getBaseResultType(); + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/KindNodeTest.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/KindNodeTest.java new file mode 100644 index 000000000..ba5d5ee47 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/KindNodeTest.java @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.cst.path; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.cst.IExpressionVisitor; +import gov.nist.secauto.metaschema.core.metapath.item.ISequence; +import gov.nist.secauto.metaschema.core.metapath.item.ItemUtils; +import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.metapath.type.IItemType; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * The CST node for a Metapath + * expanded QName + * name test. + */ +@SuppressWarnings("PMD.TestClassWithoutTestCases") +public class KindNodeTest + implements INodeTestExpression { + + @NonNull + private final IItemType type; + + /** + * Construct a new expanded QName-based literal expression. + * + * @param name + * the literal value + */ + public KindNodeTest(@NonNull IItemType type) { + this.type = type; + } + + @Override + public RESULT accept(IExpressionVisitor visitor, CONTEXT context) { + return visitor.visitKindNodeTest(this, context); + } + + @Override + public ISequence accept(DynamicContext dynamicContext, ISequence focus) { + return ISequence.of(ObjectUtils.notNull(focus.stream() + .map(ItemUtils::checkItemIsNodeItemForStep) + .filter(type::isInstance))); + } + + @SuppressWarnings("null") + @Override + public String toASTString() { + return String.format("%s[kind=%s]", getClass().getName(), type.toSignature()); + } +} 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 3f87895ea..eb58c88fd 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 @@ -74,13 +74,13 @@ public RESULT accept(IExpressionVisitor visit Stream> retval; INodeTestExpression test = getTest(); - if (test instanceof NameTest) { - IEnhancedQName name = ((NameTest) getTest()).getName(); + if (test instanceof NameNodeTest) { + IEnhancedQName name = ((NameNodeTest) getTest()).getName(); List> items = focusedItem.getModelItemsByName(name); retval = items.stream(); - } else if (test instanceof Wildcard) { + } else if (test instanceof WildcardNodeTest) { // match all items - retval = ((Wildcard) test).match(focusedItem.modelItems()); + retval = ((WildcardNodeTest) test).matchStream(focusedItem.modelItems()); } else { throw new UnsupportedOperationException(test.getClass().getName()); } 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/NameNodeTest.java similarity index 87% rename from core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/NameTest.java rename to core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/NameNodeTest.java index b787ee242..a0e894a57 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/NameNodeTest.java @@ -22,8 +22,8 @@ * name test. */ @SuppressWarnings("PMD.TestClassWithoutTestCases") -public class NameTest - implements INameTestExpression { +public class NameNodeTest + implements INodeTestExpression { @NonNull private final IEnhancedQName name; @@ -34,7 +34,7 @@ public class NameTest * @param name * the literal value */ - public NameTest(@NonNull IEnhancedQName name) { + public NameNodeTest(@NonNull IEnhancedQName name) { this.name = name; } @@ -50,13 +50,11 @@ public IEnhancedQName getName() { @Override public RESULT accept(IExpressionVisitor visitor, CONTEXT context) { - return visitor.visitName(this, context); + return visitor.visitNameNodeTest(this, context); } @Override - public ISequence accept( - DynamicContext dynamicContext, - ISequence focus) { + public ISequence accept(DynamicContext dynamicContext, ISequence focus) { return ISequence.of(ObjectUtils.notNull(focus.stream() .map(ItemUtils::checkItemIsNodeItemForStep) .filter(this::match))); 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/WildcardNodeTest.java similarity index 67% rename from core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Wildcard.java rename to core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/WildcardNodeTest.java index fbb0d49fd..33d06978a 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/WildcardNodeTest.java @@ -24,7 +24,7 @@ * wildcard name * test. */ -public class Wildcard implements INameTestExpression { +public class WildcardNodeTest implements INodeTestExpression { @Nullable private final Predicate> matcher; @@ -34,20 +34,25 @@ public class Wildcard implements INameTestExpression { * @param matcher * the matcher used to determine matching nodes */ - public Wildcard(@Nullable IWildcardMatcher matcher) { + public WildcardNodeTest(@Nullable IWildcardMatcher matcher) { this.matcher = matcher; } @Override public RESULT accept(IExpressionVisitor visitor, CONTEXT context) { - return visitor.visitWildcard(this, context); + return visitor.visitWildcardNodeTest(this, context); } @Override - public ISequence accept( - DynamicContext dynamicContext, ISequence focus) { - Stream nodes = ObjectUtils.notNull(focus.stream().map(ItemUtils::checkItemIsNodeItemForStep)); - return ISequence.of(match(nodes)); + public ISequence accept(DynamicContext dynamicContext, ISequence focus) { + Stream stream = focus.stream() + .map(ItemUtils::checkItemIsNodeItemForStep); + + if (matcher != null) { + stream = stream.filter(this::match); + } + + return ISequence.of(ObjectUtils.notNull(stream)); } /** @@ -63,7 +68,7 @@ public ISequence accept( * @return the matching items */ @NonNull - public Stream match(@SuppressWarnings("resource") @NonNull Stream items) { + public Stream matchStream(@SuppressWarnings("resource") @NonNull Stream items) { Stream nodes = items; if (matcher != null) { Predicate> test = matcher; @@ -76,6 +81,20 @@ public Stream match(@SuppressWarnings("resource") @NonN return nodes; } + /** + * Check the provided item to determine if it matches the wildcard. + * + * @param item + * the item to check for a match + * @return {@code true} if the item matches or {@code false} otherwise + */ + private boolean match(@NonNull INodeItem item) { + assert matcher != null; + Predicate> test = matcher; + return !(item instanceof IDefinitionNodeItem) || + test.test((IDefinitionNodeItem) item); + } + @SuppressWarnings("null") @Override public String toASTString() { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/type/TypeTestSupport.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/type/TypeTestSupport.java index bd3181fb6..e2eae4af9 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/type/TypeTestSupport.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/type/TypeTestSupport.java @@ -100,7 +100,7 @@ private static IItemType parseItemType( } @NonNull - private static IItemType parseKindTest( + public static IItemType parseKindTest( @NonNull ParseTree tree, @NonNull StaticContext staticContext) { ParseTree child = ObjectUtils.requireNonNull(tree.getChild(0)); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/AbstractFunction.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/AbstractFunction.java index 069f7a8ab..9ab4a0aab 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/AbstractFunction.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/AbstractFunction.java @@ -155,24 +155,17 @@ private static ISequence convertArgument( ISequenceType sequenceType = argument.getSequenceType(); // apply occurrence - ISequence retval = sequenceType.getOccurrence().getSequenceHandler().handle(parameter); + ISequence result = sequenceType.getOccurrence().getSequenceHandler().handle(parameter); // apply function conversion and type promotion to the parameter - if (!retval.isEmpty()) { + if (!result.isEmpty()) { IItemType type = sequenceType.getType(); // this is not required to be an empty sequence - retval = convertSequence(argument, retval, type); - - // verify resulting values - if (!sequenceType.matches(retval)) { - throw new InvalidTypeMetapathException( - null, - String.format("The argument '%s' is not a '%s'", - retval.toSignature(), - sequenceType.toSignature())); - } + result = convertSequence(argument, result, type); } - return retval; + + // verify resulting values + return sequenceType.test(result); } /** @@ -259,6 +252,9 @@ public ISequence execute( result = executeInternal(convertedArguments, dynamicContext, contextItem); if (callingContext != null) { + // FIXME: ensure the result sequence is list backed, otherwise the stream will + // exhaust on subsequent access + // result.getValue(); // add result to cache dynamicContext.cacheResult(callingContext, result); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java index 50f3b8ec2..e18bdecc6 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java @@ -92,7 +92,9 @@ public DefaultFunctionLibrary() { // NOPMD - intentional // P2: https://www.w3.org/TR/xpath-functions-31/#func-format-number // P2: https://www.w3.org/TR/xpath-functions-31/#func-format-time // P1: https://www.w3.org/TR/xpath-functions-31/#func-generate-id - // P2: https://www.w3.org/TR/xpath-functions-31/#func-has-children + // https://www.w3.org/TR/xpath-functions-31/#func-has-children + registerFunction(FnHasChildren.SIGNATURE_NO_ARG); + registerFunction(FnHasChildren.SIGNATURE_ONE_ARG); // https://www.w3.org/TR/xpath-functions-31/#func-head registerFunction(FnHead.SIGNATURE); // https://www.w3.org/TR/xpath-functions-31/#func-hours-from-dateTime @@ -103,10 +105,14 @@ public DefaultFunctionLibrary() { // NOPMD - intentional // https://www.w3.org/TR/xpath-functions-31/#func-index-of registerFunction(FnIndexOf.SIGNATURE_TWO_ARG); // https://www.w3.org/TR/xpath-functions-31/#func-innermost + registerFunction(FnInnermost.SIGNATURE); // https://www.w3.org/TR/xpath-functions-31/#func-insert-before registerFunction(FnInsertBefore.SIGNATURE); // https://www.w3.org/TR/xpath-functions-31/#func-iri-to-uri // P1: https://www.w3.org/TR/xpath-functions-31/#func-last + // https://www.w3.org/TR/xpath-functions-31/#func-local-name + registerFunction(FnLocalName.SIGNATURE_NO_ARG); + registerFunction(FnLocalName.SIGNATURE_ONE_ARG); // https://www.w3.org/TR/xpath-functions-31/#func-lower-case registerFunction(FnLowerCase.SIGNATURE_ONE_ARG); // https://www.w3.org/TR/xpath-functions-31/#func-matches @@ -122,6 +128,12 @@ public DefaultFunctionLibrary() { // NOPMD - intentional // https://www.w3.org/TR/xpath-functions-31/#func-month-from-date // https://www.w3.org/TR/xpath-functions-31/#func-month-from-dateTime // https://www.w3.org/TR/xpath-functions-31/#func-months-from-duration + // https://www.w3.org/TR/xpath-functions-31/#func-name + registerFunction(FnName.SIGNATURE_NO_ARG); + registerFunction(FnName.SIGNATURE_ONE_ARG); + // https://www.w3.org/TR/xpath-functions-31/#func-namespace-uri + registerFunction(FnNamespaceUri.SIGNATURE_NO_ARG); + registerFunction(FnNamespaceUri.SIGNATURE_ONE_ARG); // https://www.w3.org/TR/xpath-functions-31/#func-node-name // https://www.w3.org/TR/xpath-functions-31/#func-normalize-space registerFunction(FnNormalizeSpace.SIGNATURE_NO_ARG); @@ -133,6 +145,7 @@ public DefaultFunctionLibrary() { // NOPMD - intentional // https://www.w3.org/TR/xpath-functions-31/#func-one-or-more registerFunction(FnOneOrMore.SIGNATURE); // https://www.w3.org/TR/xpath-functions-31/#func-outermost + registerFunction(FnOutermost.SIGNATURE); // https://www.w3.org/TR/xpath-functions-31/#func-parse-ietf-date // https://www.w3.org/TR/xpath-functions-31/#func-path registerFunction(FnPath.SIGNATURE_NO_ARG); @@ -146,7 +159,9 @@ public DefaultFunctionLibrary() { // NOPMD - intentional registerFunction(FnResolveUri.SIGNATURE_TWO_ARG); // https://www.w3.org/TR/xpath-functions-31/#func-reverse registerFunction(FnReverse.SIGNATURE_ONE_ARG); - // P1: https://www.w3.org/TR/xpath-functions-31/#func-root + // https://www.w3.org/TR/xpath-functions-31/#func-root + registerFunction(FnRoot.SIGNATURE_NO_ARG); + registerFunction(FnRoot.SIGNATURE_ONE_ARG); // https://www.w3.org/TR/xpath-functions-31/#func-round registerFunction(FnRound.SIGNATURE); registerFunction(FnRound.SIGNATURE_WITH_PRECISION); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnHasChildren.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnHasChildren.java new file mode 100644 index 000000000..15a30af40 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnHasChildren.java @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.MetapathConstants; +import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; +import gov.nist.secauto.metaschema.core.metapath.function.IArgument; +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.ISequence; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBooleanItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.List; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * /** Implements + * fn:root + * functions. + */ +public final class FnHasChildren { + @NonNull + private static final String NAME = "has-children"; + @NonNull + static final IFunction SIGNATURE_NO_ARG = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextDependent() + .focusDependent() + .returnType(IStringItem.type()) + .returnOne() + .functionHandler(FnHasChildren::executeNoArg) + .build(); + @NonNull + static final IFunction SIGNATURE_ONE_ARG = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextIndependent() + .focusIndependent() + .argument(IArgument.builder() + .name("arg") + .type(INodeItem.type()) + .zeroOrOne() + .build()) + .returnType(IBooleanItem.type()) + .returnOne() + .functionHandler(FnHasChildren::executeOneArg) + .build(); + + @SuppressWarnings("unused") + @NonNull + private static ISequence executeNoArg(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + + INodeItem arg = FunctionUtils.asType( + // test that the focus is an INodeItem + INodeItem.type().test(ObjectUtils.requireNonNull(focus))); + + return ISequence.of(IBooleanItem.valueOf(fnHasChildren(arg))); + } + + @SuppressWarnings("unused") + @NonNull + private static ISequence executeOneArg(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + INodeItem arg = FunctionUtils.asTypeOrNull(ObjectUtils.requireNonNull(arguments.get(0)).getFirstItem(true)); + + return arg == null ? ISequence.empty() : ISequence.of(IBooleanItem.valueOf(fnHasChildren(arg))); + } + + /** + * Determine if the provided node argument has model item children. + *

+ * Based on the XPath 3.1 fn:has-children + * function. + * + * @param arg + * the node item to check for children + * @return {@code true} if the provided node has model item children, or + * {@code false} otherwise + */ + public static boolean fnHasChildren(@NonNull INodeItem arg) { + return arg.modelItems().findFirst().isPresent(); + } + + private FnHasChildren() { + // disable construction + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnInnermost.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnInnermost.java new file mode 100644 index 000000000..a93e781aa --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnInnermost.java @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.MetapathConstants; +import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; +import gov.nist.secauto.metaschema.core.metapath.function.IArgument; +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.ISequence; +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.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * /** Implements + * fn:root + * functions. + */ +public final class FnInnermost { + @NonNull + private static final String NAME = "innermost"; + @NonNull + static final IFunction SIGNATURE = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextIndependent() + .focusDependent() + .argument(IArgument.builder() + .name("nodes") + .type(INodeItem.type()) + .zeroOrMore() + .build()) + .returnType(INodeItem.type()) + .returnZeroOrMore() + .functionHandler(FnInnermost::execute) + .build(); + + @SuppressWarnings("unused") + @NonNull + private static ISequence execute(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + ISequence nodes = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(0))); + + return ISequence.of(fnInnermost(nodes.getValue())); + } + + /** + * Get every node within the provided list that is not an ancestor of another + * member of the provided list. + *

+ * The nodes are returned in document order with duplicates eliminated. + *

+ * Based on the XPath 3.1 fn:innermost + * function. + * + * @param arg + * the node items check + * @return the nodes that are not an ancestor of another member of the provided + * list + */ + @NonNull + public static Stream fnInnermost(@NonNull List arg) { + Set ancestors = arg.stream() + .distinct() + .flatMap(INodeItem::ancestor) + .collect(Collectors.toSet()); + + return ObjectUtils.notNull(arg.stream() + .distinct() + .filter(node -> !ancestors.contains(node))); + } + + private FnInnermost() { + // disable construction + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnLocalName.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnLocalName.java new file mode 100644 index 000000000..a03bdad24 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnLocalName.java @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.MetapathConstants; +import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; +import gov.nist.secauto.metaschema.core.metapath.function.IArgument; +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.ISequence; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +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.List; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * /** Implements fn:local-name + * functions. + */ +public final class FnLocalName { + @NonNull + private static final String NAME = "local-name"; + @NonNull + static final IFunction SIGNATURE_NO_ARG = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextDependent() + .focusDependent() + .returnType(IStringItem.type()) + .returnOne() + .functionHandler(FnLocalName::executeNoArg) + .build(); + @NonNull + static final IFunction SIGNATURE_ONE_ARG = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextIndependent() + .focusIndependent() + .argument(IArgument.builder() + .name("arg") + .type(INodeItem.type()) + .zeroOrOne() + .build()) + .returnType(IStringItem.type()) + .returnOne() + .functionHandler(FnLocalName::executeOneArg) + .build(); + + @SuppressWarnings("unused") + @NonNull + private static ISequence executeNoArg(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + + INodeItem arg = FunctionUtils.asType( + // test that the focus is an INodeItem + INodeItem.type().test(ObjectUtils.requireNonNull(focus))); + + return ISequence.of( + IStringItem.valueOf(fnLocalName(arg))); + } + + @SuppressWarnings("unused") + @NonNull + private static ISequence executeOneArg(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + INodeItem arg = FunctionUtils.asTypeOrNull(ObjectUtils.requireNonNull(arguments.get(0)).getFirstItem(true)); + + return ISequence.of( + IStringItem.valueOf(arg == null ? "" : fnLocalName(arg))); + } + + /** + * Get the name of the provided node item. + *

+ * Based on the XPath 3.1 fn:local-name + * function. + * + * @param arg + * the node item to get the name for + * @return the name of the node if it has one, or an empty string otherwise + */ + @NonNull + public static String fnLocalName(@NonNull INodeItem arg) { + return arg instanceof IDefinitionNodeItem + ? ((IDefinitionNodeItem) arg).getQName().getLocalName() + : ""; + } + + private FnLocalName() { + // disable construction + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnName.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnName.java new file mode 100644 index 000000000..ba2f83cdf --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnName.java @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.MetapathConstants; +import gov.nist.secauto.metaschema.core.metapath.StaticContext; +import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; +import gov.nist.secauto.metaschema.core.metapath.function.IArgument; +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.ISequence; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +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.List; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * /** Implements + * fn:name + * functions. + */ +public final class FnName { + @NonNull + private static final String NAME = "name"; + @NonNull + static final IFunction SIGNATURE_NO_ARG = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextDependent() + .focusDependent() + .returnType(IStringItem.type()) + .returnOne() + .functionHandler(FnName::executeNoArg) + .build(); + @NonNull + static final IFunction SIGNATURE_ONE_ARG = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextIndependent() + .focusIndependent() + .argument(IArgument.builder() + .name("arg") + .type(INodeItem.type()) + .zeroOrOne() + .build()) + .returnType(IStringItem.type()) + .returnOne() + .functionHandler(FnName::executeOneArg) + .build(); + + @SuppressWarnings("unused") + @NonNull + private static ISequence executeNoArg( + @NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + INodeItem arg = FunctionUtils.asType( + // test that the focus is an INodeItem + INodeItem.type().test(ObjectUtils.requireNonNull(focus))); + + return ISequence.of( + IStringItem.valueOf(fnName(arg, dynamicContext.getStaticContext()))); + } + + @SuppressWarnings("unused") + @NonNull + private static ISequence executeOneArg(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + INodeItem arg = FunctionUtils.asTypeOrNull(ObjectUtils.requireNonNull(arguments.get(0)).getFirstItem(true)); + + return ISequence.of( + IStringItem.valueOf(arg == null ? "" : fnName(arg, dynamicContext.getStaticContext()))); + } + + /** + * Get the name of the provided node item. + *

+ * Based on the XPath 3.1 + * fn:name + * function. + * + * @param arg + * the node item to get the name for + * @param staticContext + * the static context used to resolve the namespace prefix + * @return the name of the node if it has one, or an empty string otherwise + */ + @NonNull + public static String fnName(@NonNull INodeItem arg, @NonNull StaticContext staticContext) { + return arg instanceof IDefinitionNodeItem + ? ((IDefinitionNodeItem) arg).getQName().toEQName(staticContext) + : ""; + } + + private FnName() { + // disable construction + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnNamespaceUri.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnNamespaceUri.java new file mode 100644 index 000000000..2910ff1d5 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnNamespaceUri.java @@ -0,0 +1,109 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.MetapathConstants; +import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; +import gov.nist.secauto.metaschema.core.metapath.function.IArgument; +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.ISequence; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +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.List; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * /** Implements fn:namespace-uri + * functions. + */ +public final class FnNamespaceUri { + @NonNull + private static final String NAME = "namespace-uri"; + @NonNull + static final IFunction SIGNATURE_NO_ARG = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextDependent() + .focusDependent() + .returnType(IStringItem.type()) + .returnOne() + .functionHandler(FnNamespaceUri::executeNoArg) + .build(); + @NonNull + static final IFunction SIGNATURE_ONE_ARG = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextIndependent() + .focusIndependent() + .argument(IArgument.builder() + .name("arg") + .type(INodeItem.type()) + .zeroOrOne() + .build()) + .returnType(IStringItem.type()) + .returnOne() + .functionHandler(FnNamespaceUri::executeOneArg) + .build(); + + @SuppressWarnings("unused") + @NonNull + private static ISequence executeNoArg(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + + INodeItem arg = FunctionUtils.asType( + // test that the focus is an INodeItem + INodeItem.type().test(ObjectUtils.requireNonNull(focus))); + + return ISequence.of( + IStringItem.valueOf(fnNamespaceUri(arg))); + } + + @SuppressWarnings("unused") + @NonNull + private static ISequence executeOneArg(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + INodeItem arg = FunctionUtils.asTypeOrNull(ObjectUtils.requireNonNull(arguments.get(0)).getFirstItem(true)); + + return ISequence.of( + IStringItem.valueOf(arg == null ? "" : fnNamespaceUri(arg))); + } + + /** + * Get the namespace URI of the provided node item. + *

+ * Based on the XPath 3.1 fn:namespace-uri + * function. + * + * @param arg + * the node item to get the namespace URI for + * @return the namespace URI of the node if it has one, or an empty string + * otherwise + */ + @NonNull + public static String fnNamespaceUri(@NonNull INodeItem arg) { + return arg instanceof IDefinitionNodeItem + ? ((IDefinitionNodeItem) arg).getQName().getNamespace() + : ""; + } + + private FnNamespaceUri() { + // disable construction + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnOutermost.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnOutermost.java new file mode 100644 index 000000000..9cbd009c3 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnOutermost.java @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.MetapathConstants; +import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; +import gov.nist.secauto.metaschema.core.metapath.function.IArgument; +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.ISequence; +import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * /** Implements + * fn:root + * functions. + */ +public final class FnOutermost { + @NonNull + private static final String NAME = "outermost"; + @NonNull + static final IFunction SIGNATURE = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextIndependent() + .focusDependent() + .argument(IArgument.builder() + .name("nodes") + .type(INodeItem.type()) + .zeroOrMore() + .build()) + .returnType(INodeItem.type()) + .returnZeroOrMore() + .functionHandler(FnOutermost::execute) + .build(); + + @SuppressWarnings("unused") + @NonNull + private static ISequence execute(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + ISequence nodes = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(0))); + + return ISequence.of(fnOutermost(nodes.getValue())); + } + + /** + * Get every node within the provided list that is not an ancestor of another + * member of the provided list. + *

+ * The nodes are returned in document order with duplicates eliminated. + *

+ * Based on the XPath 3.1 fn:innermost + * function. + * + * @param arg + * the node items check + * @return the nodes that are not an ancestor of another member of the provided + * list + */ + @NonNull + public static Stream fnOutermost(@NonNull List arg) { + Set values = new HashSet<>(arg); + + return ObjectUtils.notNull(arg.stream() + .distinct() + .filter(node -> !node.ancestor().anyMatch(values::contains))); + } + + private FnOutermost() { + // disable construction + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnRoot.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnRoot.java new file mode 100644 index 000000000..5caaa190b --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnRoot.java @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.MetapathConstants; +import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; +import gov.nist.secauto.metaschema.core.metapath.function.IArgument; +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.ISequence; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.List; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * /** Implements + * fn:root + * functions. + */ +public final class FnRoot { + @NonNull + private static final String NAME = "root"; + @NonNull + static final IFunction SIGNATURE_NO_ARG = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextDependent() + .focusDependent() + .returnType(IStringItem.type()) + .returnOne() + .functionHandler(FnRoot::executeNoArg) + .build(); + @NonNull + static final IFunction SIGNATURE_ONE_ARG = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextIndependent() + .focusDependent() + .argument(IArgument.builder() + .name("arg") + .type(INodeItem.type()) + .zeroOrOne() + .build()) + .returnType(INodeItem.type()) + .returnZeroOrOne() + .functionHandler(FnRoot::executeOneArg) + .build(); + + @SuppressWarnings("unused") + @NonNull + private static ISequence executeNoArg(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + + INodeItem arg = FunctionUtils.asType( + // test that the focus is an INodeItem + INodeItem.type().test(ObjectUtils.requireNonNull(focus))); + + return ISequence.of(fnRoot(arg)); + } + + @SuppressWarnings("unused") + @NonNull + private static ISequence executeOneArg(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + INodeItem arg = FunctionUtils.asTypeOrNull(ObjectUtils.requireNonNull(arguments.get(0)).getFirstItem(true)); + + return arg == null ? ISequence.empty() : ISequence.of(fnRoot(arg)); + } + + /** + * Get the root of the tree to which the provided node argument belongs. + *

+ * Based on the XPath 3.1 + * fn:root + * function. + * + * @param arg + * the node item to get the namespace URI for + * @return the root of the tree to which the provided node argument belongs + */ + @NonNull + public static INodeItem fnRoot(@NonNull INodeItem arg) { + INodeItem retval = arg; + while (retval.getParentNodeItem() != null) { + retval = retval.getParentNodeItem(); + } + + return retval; + } + + private FnRoot() { + // disable construction + } +} 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 c8315574a..31ef93830 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 @@ -10,19 +10,14 @@ import gov.nist.secauto.metaschema.core.metapath.function.IArgument; import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue; import gov.nist.secauto.metaschema.core.metapath.item.ISequence; -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.function.IArrayItem; -import gov.nist.secauto.metaschema.core.metapath.type.ISequenceType; -import gov.nist.secauto.metaschema.core.metapath.type.Occurrence; import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; import gov.nist.secauto.metaschema.core.util.ObjectUtils; -import java.util.EnumSet; import java.util.Iterator; import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.stream.Collectors; import edu.umd.cs.findbugs.annotations.NonNull; @@ -36,18 +31,12 @@ */ public abstract class AbstractArrayItem extends ImmutableCollections.AbstractImmutableDelegatedList - implements IArrayItem { + implements IArrayItem, IFeatureCollectionFunctionItem { @NonNull private static final IEnhancedQName QNAME = IEnhancedQName.of("array"); @NonNull - private static final Set PROPERTIES = ObjectUtils.notNull( - EnumSet.of(FunctionProperty.DETERMINISTIC)); - @NonNull private static final List ARGUMENTS = ObjectUtils.notNull(List.of( IArgument.builder().name("position").type(IIntegerItem.type()).one().build())); - @NonNull - private static final ISequenceType RESULT = ISequenceType.of( - IAnyAtomicItem.type(), Occurrence.ZERO_OR_ONE); @NonNull private static final IArrayItem EMPTY = new ArrayItemN<>(); @@ -65,51 +54,16 @@ public static IArrayItem empty() { return (IArrayItem) EMPTY; } - @Override - public boolean isDeterministic() { - return true; - } - - @Override - public boolean isContextDepenent() { - return false; - } - - @Override - public boolean isFocusDependent() { - return false; - } - @Override public IEnhancedQName getQName() { return QNAME; } - @Override - public Set getProperties() { - return PROPERTIES; - } - @Override public List getArguments() { return ARGUMENTS; } - @Override - public int arity() { - return 1; - } - - @Override - public boolean isArityUnbounded() { - return false; - } - - @Override - public ISequenceType getResult() { - return RESULT; - } - @Override public ISequence execute(List> arguments, DynamicContext dynamicContext, ISequence focus) { 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 ee9925fa3..c1c748531 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 @@ -15,17 +15,13 @@ import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem; 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.metapath.type.ISequenceType; -import gov.nist.secauto.metaschema.core.metapath.type.Occurrence; import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; import gov.nist.secauto.metaschema.core.util.ObjectUtils; -import java.util.EnumSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.stream.Collectors; import edu.umd.cs.findbugs.annotations.NonNull; @@ -39,18 +35,12 @@ */ public abstract class AbstractMapItem extends ImmutableCollections.AbstractImmutableDelegatedMap - implements IMapItem { + implements IMapItem, IFeatureCollectionFunctionItem { /** * The function qualified name. */ @NonNull private static final IEnhancedQName QNAME = IEnhancedQName.of("map"); - /** - * The function properties. - */ - @NonNull - private static final Set PROPERTIES = ObjectUtils.notNull( - EnumSet.of(FunctionProperty.DETERMINISTIC)); /** * The function arguments. */ @@ -58,10 +48,6 @@ public abstract class AbstractMapItem private static final List ARGUMENTS = ObjectUtils.notNull(List.of( IArgument.builder().name("key").type(IAnyAtomicItem.type()).one().build())); @NonNull - private static final ISequenceType RESULT = ISequenceType.of( - IAnyAtomicItem.type(), Occurrence.ZERO_OR_ONE); - - @NonNull private static final IMapItem EMPTY = new MapItemN<>(); /** @@ -78,51 +64,16 @@ public static IMapItem empty() { return (IMapItem) EMPTY; } - @Override - public boolean isDeterministic() { - return true; - } - - @Override - public boolean isContextDepenent() { - return false; - } - - @Override - public boolean isFocusDependent() { - return false; - } - @Override public IEnhancedQName getQName() { return QNAME; } - @Override - public Set getProperties() { - return PROPERTIES; - } - @Override public List getArguments() { return ARGUMENTS; } - @Override - public int arity() { - return 1; - } - - @Override - public boolean isArityUnbounded() { - return false; - } - - @Override - public ISequenceType getResult() { - return RESULT; - } - @Override public ISequence execute(List> arguments, DynamicContext dynamicContext, ISequence focus) { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/IFeatureCollectionFunctionItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/IFeatureCollectionFunctionItem.java new file mode 100644 index 000000000..ebcc5ae02 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/IFeatureCollectionFunctionItem.java @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.impl; + +import gov.nist.secauto.metaschema.core.metapath.function.IFunction; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; +import gov.nist.secauto.metaschema.core.metapath.type.ISequenceType; +import gov.nist.secauto.metaschema.core.metapath.type.Occurrence; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.EnumSet; +import java.util.Set; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public interface IFeatureCollectionFunctionItem extends IFunction { + /** + * The function properties. + */ + @NonNull + Set PROPERTIES = ObjectUtils.notNull( + EnumSet.of(FunctionProperty.DETERMINISTIC)); + /** + * The function result. + */ + @NonNull + ISequenceType RESULT = ISequenceType.of( + IAnyAtomicItem.type(), Occurrence.ZERO_OR_ONE); + + @Override + default boolean isDeterministic() { + return true; + } + + @Override + default boolean isContextDepenent() { + return false; + } + + @Override + default boolean isFocusDependent() { + return false; + } + + @Override + default Set getProperties() { + return PROPERTIES; + } + + @Override + default int arity() { + return 1; + } + + @Override + default boolean isArityUnbounded() { + return false; + } + + @Override + default ISequenceType getResult() { + return RESULT; + } +} 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 b5654339a..fa223c7c0 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 @@ -60,6 +60,9 @@ public List getValue() { instanceLock.lock(); try { if (list == null) { + if (stream == null) { + throw new IllegalStateException("stream is already consumed"); + } list = stream.collect(Collectors.toUnmodifiableList()); stream = null; } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractDefinitionNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractDefinitionNodeItem.java index 53b445b70..8f23466a3 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractDefinitionNodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractDefinitionNodeItem.java @@ -6,7 +6,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; -abstract class AbstractDefinitionNodeItem +public abstract class AbstractDefinitionNodeItem extends AbstractNodeItem implements IFeatureOrhpanedDefinitionNodeItem { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractFlagInstanceNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractFlagInstanceNodeItem.java index dd7203c60..a2b0dd22f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractFlagInstanceNodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractFlagInstanceNodeItem.java @@ -9,7 +9,7 @@ /** * A {@link INodeItem} supported by a {@link IFlagInstance}. */ -abstract class AbstractFlagInstanceNodeItem +public abstract class AbstractFlagInstanceNodeItem extends AbstractInstanceNodeItem> implements IFlagNodeItem { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractGlobalDefinitionNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractGlobalDefinitionNodeItem.java index 4734fb40c..2c0a62470 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractGlobalDefinitionNodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractGlobalDefinitionNodeItem.java @@ -15,7 +15,7 @@ * @param * the definition type */ -abstract class AbstractGlobalDefinitionNodeItem +public abstract class AbstractGlobalDefinitionNodeItem extends AbstractDefinitionNodeItem { @NonNull private final IModuleNodeItem metaschemaNodeItem; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractInstanceNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractInstanceNodeItem.java index db0d6074b..fffeca305 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractInstanceNodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractInstanceNodeItem.java @@ -7,7 +7,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; -abstract class AbstractInstanceNodeItem< +public abstract class AbstractInstanceNodeItem< D extends IDefinition, I extends INamedInstance, P extends IModelNodeItem> diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractOrphanedDefinitionNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractOrphanedDefinitionNodeItem.java index a35267aa1..33671bba6 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractOrphanedDefinitionNodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractOrphanedDefinitionNodeItem.java @@ -11,7 +11,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -abstract class AbstractOrphanedDefinitionNodeItem +public abstract class AbstractOrphanedDefinitionNodeItem extends AbstractDefinitionNodeItem { @Nullable diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IAtomicValuedNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IAtomicValuedNodeItem.java new file mode 100644 index 000000000..0e4bd8285 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IAtomicValuedNodeItem.java @@ -0,0 +1,17 @@ + +package gov.nist.secauto.metaschema.core.metapath.item.node; + +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAtomicValuedItem; +import gov.nist.secauto.metaschema.core.metapath.type.IAtomicOrUnionType; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public interface IAtomicValuedNodeItem extends IAtomicValuedItem, INodeItem { + @NonNull + IAtomicOrUnionType getValueItemType(); + + @Override + default String stringValue() { + return toAtomicItem().asString(); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFieldNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFieldNodeItem.java index 503494598..6a36c902f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFieldNodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFieldNodeItem.java @@ -4,7 +4,7 @@ import gov.nist.secauto.metaschema.core.metapath.StaticContext; import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter; import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue; -import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAtomicValuedItem; +import gov.nist.secauto.metaschema.core.metapath.type.IAtomicOrUnionType; import gov.nist.secauto.metaschema.core.metapath.type.IItemType; import gov.nist.secauto.metaschema.core.metapath.type.IKindTest; import gov.nist.secauto.metaschema.core.model.IFieldDefinition; @@ -20,7 +20,7 @@ */ public interface IFieldNodeItem extends IModelNodeItem, - IAtomicValuedItem { + IAtomicValuedNodeItem { /** * Get the static type information of the node item. * @@ -55,6 +55,11 @@ default IKindTest getType() { staticContext); } + @Override + default IAtomicOrUnionType getValueItemType() { + return getDefinition().getJavaTypeAdapter().getItemType(); + } + @Override @Nullable default URI getBaseUri() { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFlagNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFlagNodeItem.java index fbf8fce91..38e81ac81 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFlagNodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFlagNodeItem.java @@ -4,7 +4,7 @@ import gov.nist.secauto.metaschema.core.metapath.StaticContext; import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter; import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue; -import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAtomicValuedItem; +import gov.nist.secauto.metaschema.core.metapath.type.IAtomicOrUnionType; import gov.nist.secauto.metaschema.core.metapath.type.IItemType; import gov.nist.secauto.metaschema.core.metapath.type.IKindTest; import gov.nist.secauto.metaschema.core.model.IFlagDefinition; @@ -25,7 +25,7 @@ */ public interface IFlagNodeItem - extends IDefinitionNodeItem, IAtomicValuedItem { + extends IDefinitionNodeItem, IAtomicValuedNodeItem { /** * Get the static type information of the node item. * @@ -66,6 +66,11 @@ default IKindTest getType() { staticContext); } + @Override + default IAtomicOrUnionType getValueItemType() { + return getDefinition().getJavaTypeAdapter().getItemType(); + } + @Override @Nullable default URI getBaseUri() { 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 91bd12e6a..08205239f 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 @@ -178,12 +178,12 @@ default Stream ancestor() { */ @NonNull default Stream ancestorOrSelf() { - return ObjectUtils.notNull(Stream.concat(Stream.of(this), ancestor())); + return ObjectUtils.notNull(Stream.concat(ancestor(), Stream.of(this))); } /** * Get a stream of the ancestors of the provided {@code item}. The stream is - * ordered from the closest to farthest ancestor. + * ordered from the farthest ancestor to the closest. * * @param item * the target item to get ancestors for @@ -193,7 +193,7 @@ default Stream ancestorOrSelf() { @NonNull static Stream ancestorsOf(@NonNull INodeItem item) { INodeItem parent = item.getParentNodeItem(); - return ObjectUtils.notNull(parent == null ? Stream.empty() : Stream.concat(Stream.of(parent), ancestorsOf(parent))); + return ObjectUtils.notNull(parent == null ? Stream.empty() : Stream.concat(ancestorsOf(parent), Stream.of(parent))); } /** diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/DataTypeItemType.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/DataTypeItemType.java index 1664d8dfa..3f5b8390a 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/DataTypeItemType.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/DataTypeItemType.java @@ -53,6 +53,12 @@ public IEnhancedQName getQName() { return getAdapter().getPreferredName(); } + @SuppressWarnings("unchecked") + @Override + public T newItem(Object value) { + return (T) getAdapter().newItem(value); + } + @Override public String toString() { return toSignature(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/IAtomicOrUnionType.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/IAtomicOrUnionType.java index 1de1ed90c..dcdf22673 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/IAtomicOrUnionType.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/IAtomicOrUnionType.java @@ -110,6 +110,7 @@ default boolean isSubType(@NonNull IAtomicOrUnionType other) { * @throws InvalidValueForCastFunctionException * if the provided {@code item} cannot be cast to this type */ + @NonNull I cast(@NonNull IAnyAtomicItem item); /** @@ -151,4 +152,14 @@ interface ICastExecutor { @NonNull ITEM cast(@NonNull IAnyAtomicItem item); } + + /** + * Construct a new instance of this type using the provided value. + * + * @param value + * the "wrapped" value of the type + * @return the new item + */ + @NonNull + I newItem(@NonNull Object value); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/IItemType.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/IItemType.java index 6798f5552..9f72261d3 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/IItemType.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/IItemType.java @@ -322,6 +322,27 @@ default boolean isInstance(IItem item) { return getItemClass().isInstance(item); } + /** + * Test if the provided item matches this item type. + * + * @param item + * the item to test + * @return the item if the test passes + * @throw InvalidTypeMetapathException if the test fails because the item is not + * the required type + */ + @NonNull + default T test(@Nullable T item) { + if (item != null && isInstance(item)) { + return item; + } + throw new InvalidTypeMetapathException( + null, + String.format("The item '%s' is not a '%s'", + item == null ? "null" : item.toSignature(), + toSignature())); + } + /** * Get the item Java class associated with this item type. * diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/ISequenceType.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/ISequenceType.java index a68c6798d..d32d19b7b 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/ISequenceType.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/ISequenceType.java @@ -6,6 +6,8 @@ package gov.nist.secauto.metaschema.core.metapath.type; import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue; +import gov.nist.secauto.metaschema.core.metapath.item.IItem; +import gov.nist.secauto.metaschema.core.metapath.item.ISequence; import gov.nist.secauto.metaschema.core.metapath.type.impl.SequenceTypeImpl; import edu.umd.cs.findbugs.annotations.NonNull; @@ -86,4 +88,47 @@ static ISequenceType of( * type or {@code false} otherwise */ boolean matches(@NonNull ICollectionValue value); + + /** + * Test if the provided sequence matches this sequence type. + * + * @param sequence + * the sequence to test + * @return the sequence if the test passes + * @throw InvalidTypeMetapathException if the test fails because the sequence is + * not the required type + */ + @NonNull + default ISequence test(@NonNull ISequence sequence) { + if (matches(sequence)) { + return sequence; + } + throw new InvalidTypeMetapathException( + null, + String.format("The argument '%s' is not a '%s'", + sequence.toSignature(), + toSignature())); + } + + /** + * Test if the provided item matches this sequence type. + * + * @param item + * the item to test + * @return the item if the test passes + * @throw InvalidTypeMetapathException if the test fails because the item is not + * the required type + */ + @NonNull + default T test(@NonNull T item) { + ISequence sequence = ISequence.of(item); + if (matches(sequence)) { + return item; + } + throw new InvalidTypeMetapathException( + null, + String.format("The item '%s' is not a '%s'", + item.toSignature(), + toSignature())); + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/impl/NonAdapterAtomicItemType.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/impl/NonAdapterAtomicItemType.java index 6761dfc0d..c63b04f3a 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/impl/NonAdapterAtomicItemType.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/type/impl/NonAdapterAtomicItemType.java @@ -79,4 +79,9 @@ public boolean equals(Object obj) { return Objects.equals(getItemClass(), other.getItemClass()) && Objects.equals(getQName(), other.getQName()); } + + @Override + public T newItem(Object value) { + throw new UnsupportedOperationException("Not a conrete atomic type."); + } } diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/path/FlagTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/path/FlagTest.java index a56b4c562..b6aababd3 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/path/FlagTest.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/path/FlagTest.java @@ -57,7 +57,7 @@ void testFlagWithName() { } }); - Flag expr = new Flag(new NameTest(flagName)); + Flag expr = new Flag(new NameNodeTest(flagName)); ISequence result = expr.accept(dynamicContext, ISequence.of(focusItem)); assertEquals(ISequence.of(flagNode), result, "Sequence does not match"); diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/path/StepTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/path/StepTest.java index 35e47d9ab..6018d1a0d 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/path/StepTest.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/path/StepTest.java @@ -333,11 +333,11 @@ void testAncestorAxis() { .evaluate(nodeB, dynamicContext); Assertions.assertThat(actual.getValue()).isEqualTo(List.of( + document, + document.getRootAssemblyNodeItem(), document.getRootAssemblyNodeItem() .getModelItemsByName(IEnhancedQName.of(NS, "node-2")) - .iterator().next(), - document.getRootAssemblyNodeItem(), - document)); + .iterator().next())); } @Test @@ -354,12 +354,12 @@ void testAncestorOrSelfAxis() { .evaluate(nodeB, dynamicContext); Assertions.assertThat(actual.getValue()).isEqualTo(List.of( - nodeB, + document, + document.getRootAssemblyNodeItem(), document.getRootAssemblyNodeItem() .getModelItemsByName(IEnhancedQName.of(NS, "node-2")) .iterator().next(), - document.getRootAssemblyNodeItem(), - document)); + nodeB)); } @Test diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnHasChildrenTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnHasChildrenTest.java new file mode 100644 index 000000000..ad89574e0 --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnHasChildrenTest.java @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.DynamicMetapathException; +import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase; +import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression; +import gov.nist.secauto.metaschema.core.metapath.MetapathException; +import gov.nist.secauto.metaschema.core.metapath.function.library.impl.MockedDocumentGenerator; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; +import gov.nist.secauto.metaschema.core.metapath.type.TypeMetapathException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; + +class FnHasChildrenTest + extends ExpressionTestBase { + + private static Stream provideValues() { // NOPMD - false positive + return Stream.of( + Arguments.of( + true, + "has-children()"), + Arguments.of( + true, + "has-children(.)"), + Arguments.of( + true, + "has-children(/root)"), + Arguments.of( + false, + "has-children(/root/assembly)"), + Arguments.of( + false, + "has-children(/root/assembly/@assembly-flag)"), + Arguments.of( + false, + "has-children(/root/field)"), + Arguments.of( + false, + "has-children(/root/field/@field-flag)")); + } + + @ParameterizedTest + @MethodSource("provideValues") + void test(boolean expected, @NonNull String metapath) { + DynamicContext dynamicContext = newDynamicContext(); + + INodeItem node = MockedDocumentGenerator.generateDocumentNodeItem(getContext()); + Boolean result = IMetapathExpression.compile(metapath, dynamicContext.getStaticContext()) + .evaluateAs(node, IMetapathExpression.ResultType.BOOLEAN, dynamicContext); + assertEquals(expected, result); + } + + @Test + void testContextAbsent() { + DynamicContext dynamicContext = newDynamicContext(); + + MetapathException ex = assertThrows(MetapathException.class, () -> { + IMetapathExpression.compile("has-children()", dynamicContext.getStaticContext()) + .evaluateAs(null, IMetapathExpression.ResultType.ITEM, dynamicContext); + }); + Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null; + + assertAll( + () -> assertEquals(DynamicMetapathException.class, cause == null + ? null + : cause.getClass()), + () -> assertEquals(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT, cause instanceof DynamicMetapathException + ? ((DynamicMetapathException) cause).getCode() + : null)); + } + + @Test + void testNotANode() { + DynamicContext dynamicContext = newDynamicContext(); + + MetapathException ex = assertThrows(MetapathException.class, () -> { + IMetapathExpression.compile("has-children()", dynamicContext.getStaticContext()) + .evaluateAs(IStringItem.valueOf("test"), IMetapathExpression.ResultType.ITEM, dynamicContext); + }); + Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null; + + assertAll( + () -> assertEquals(InvalidTypeMetapathException.class, cause == null + ? null + : cause.getClass()), + () -> assertEquals(TypeMetapathException.INVALID_TYPE_ERROR, cause instanceof TypeMetapathException + ? ((TypeMetapathException) cause).getCode() + : null)); + } +} diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnInnermostTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnInnermostTest.java new file mode 100644 index 000000000..17746b27d --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnInnermostTest.java @@ -0,0 +1,95 @@ +/* +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase; +import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression; +import gov.nist.secauto.metaschema.core.metapath.MetapathException; +import gov.nist.secauto.metaschema.core.metapath.function.library.impl.MockedDocumentGenerator; +import gov.nist.secauto.metaschema.core.metapath.item.ISequence; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; +import gov.nist.secauto.metaschema.core.metapath.type.TypeMetapathException; +import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; + +class FnInnermostTest + extends ExpressionTestBase { + + private static Stream provideValues() { // NOPMD - false positive + return Stream.of( + Arguments.of( + "(/root/assembly/@assembly-flag,/root/field/@field-flag)", + "(.,/root,/root/assembly,/root/assembly/@assembly-flag,/root/field,/root/field/@field-flag)"), + Arguments.of( + "(/root/assembly,/root/field)", + "(.,/root,/root/assembly,/root/field)"), + Arguments.of( + "(/root/assembly/@assembly-flag,/root/field/@field-flag)", + "(.,/root,/root/assembly,/root/assembly/@assembly-flag,/root/field,/root/field/@field-flag," + + ".,/root,/root/assembly,/root/assembly/@assembly-flag,/root/field,/root/field/@field-flag)")); + } + + @ParameterizedTest + @MethodSource("provideValues") + void test(@NonNull String expectedValueMetapath, @NonNull String actualValuesMetapath) { + DynamicContext dynamicContext = newDynamicContext(); + INodeItem node = MockedDocumentGenerator.generateDocumentNodeItem(getContext()); + + ISequence expected + = IMetapathExpression.compile(expectedValueMetapath, dynamicContext.getStaticContext()) + .evaluate(node, dynamicContext); + + ISequence actual + = IMetapathExpression.compile("innermost(" + actualValuesMetapath + ")", dynamicContext.getStaticContext()) + .evaluate(node, dynamicContext); + + // Test the expected values against the alternate implementation from the spec + ISequence values + = IMetapathExpression.compile(expectedValueMetapath, dynamicContext.getStaticContext()) + .evaluate(node, dynamicContext); + ISequence alternate + = IMetapathExpression.compile("$nodes except $nodes/ancestor::node()", dynamicContext.getStaticContext()) + .evaluate(null, dynamicContext.subContext().bindVariableValue(IEnhancedQName.of("nodes"), values)); + + assertEquals(expected, actual); + assertEquals(expected, alternate); + } + + @Test + void testNotANode() { + DynamicContext dynamicContext = newDynamicContext(); + + MetapathException ex = assertThrows(MetapathException.class, () -> { + IMetapathExpression.compile("innermost('test')", dynamicContext.getStaticContext()) + .evaluateAs(IStringItem.valueOf("test"), IMetapathExpression.ResultType.ITEM, dynamicContext); + }); + Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null; + + assertAll( + () -> assertEquals(InvalidTypeMetapathException.class, cause == null + ? null + : cause.getClass()), + () -> assertEquals(TypeMetapathException.INVALID_TYPE_ERROR, cause instanceof TypeMetapathException + ? ((TypeMetapathException) cause).getCode() + : null)); + } +} diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnLocalNameTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnLocalNameTest.java new file mode 100644 index 000000000..6a0647477 --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnLocalNameTest.java @@ -0,0 +1,117 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +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 static org.junit.jupiter.api.Assertions.assertThrows; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.DynamicMetapathException; +import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase; +import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression; +import gov.nist.secauto.metaschema.core.metapath.MetapathException; +import gov.nist.secauto.metaschema.core.metapath.function.library.impl.MockedDocumentGenerator; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; +import gov.nist.secauto.metaschema.core.metapath.type.TypeMetapathException; +import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +class FnLocalNameTest + extends ExpressionTestBase { + + private static Stream provideValues() { // NOPMD - false positive + return Stream.of( + Arguments.of( + null, + "local-name()"), + Arguments.of( + null, + "local-name(.)"), + Arguments.of( + MockedDocumentGenerator.ROOT_QNAME, + "local-name(/root)"), + Arguments.of( + MockedDocumentGenerator.ASSEMBLY_QNAME, + "local-name(/root/assembly)"), + Arguments.of( + MockedDocumentGenerator.ASSEMBLY_FLAG_QNAME, + "local-name(/root/assembly/@assembly-flag)"), + Arguments.of( + MockedDocumentGenerator.FIELD_QNAME, + "local-name(/root/field)"), + Arguments.of( + MockedDocumentGenerator.FIELD_FLAG_QNAME, + "local-name(/root/field/@field-flag)")); + } + + @ParameterizedTest + @MethodSource("provideValues") + void test(@Nullable IEnhancedQName expected, @NonNull String metapath) { + DynamicContext dynamicContext = newDynamicContext(); + + IStringItem result = IMetapathExpression.compile(metapath, dynamicContext.getStaticContext()) + .evaluateAs( + MockedDocumentGenerator.generateDocumentNodeItem(getContext()), + IMetapathExpression.ResultType.ITEM, + dynamicContext); + assertNotNull(result); + assertEquals( + expected == null + ? "" + : expected.getLocalName(), + result.asString()); + } + + @Test + void testContextAbsent() { + DynamicContext dynamicContext = newDynamicContext(); + + MetapathException ex = assertThrows(MetapathException.class, () -> { + IMetapathExpression.compile("local-name()", dynamicContext.getStaticContext()) + .evaluateAs(null, IMetapathExpression.ResultType.ITEM, dynamicContext); + }); + Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null; + + assertAll( + () -> assertEquals(DynamicMetapathException.class, cause == null + ? null + : cause.getClass()), + () -> assertEquals(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT, cause instanceof DynamicMetapathException + ? ((DynamicMetapathException) cause).getCode() + : null)); + } + + @Test + void testNotANode() { + DynamicContext dynamicContext = newDynamicContext(); + + MetapathException ex = assertThrows(MetapathException.class, () -> { + IMetapathExpression.compile("local-name()", dynamicContext.getStaticContext()) + .evaluateAs(IStringItem.valueOf("test"), IMetapathExpression.ResultType.ITEM, dynamicContext); + }); + Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null; + + assertAll( + () -> assertEquals(InvalidTypeMetapathException.class, cause == null + ? null + : cause.getClass()), + () -> assertEquals(TypeMetapathException.INVALID_TYPE_ERROR, cause instanceof TypeMetapathException + ? ((TypeMetapathException) cause).getCode() + : null)); + } +} diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnNameTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnNameTest.java new file mode 100644 index 000000000..5b9a376d9 --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnNameTest.java @@ -0,0 +1,116 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +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 static org.junit.jupiter.api.Assertions.assertThrows; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.DynamicMetapathException; +import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase; +import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression; +import gov.nist.secauto.metaschema.core.metapath.MetapathException; +import gov.nist.secauto.metaschema.core.metapath.function.library.impl.MockedDocumentGenerator; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; +import gov.nist.secauto.metaschema.core.metapath.type.TypeMetapathException; +import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +class FnNameTest + extends ExpressionTestBase { + private static Stream provideValues() { // NOPMD - false positive + return Stream.of( + Arguments.of( + null, + "name()"), + Arguments.of( + null, + "name(.)"), + Arguments.of( + MockedDocumentGenerator.ROOT_QNAME, + "name(/root)"), + Arguments.of( + MockedDocumentGenerator.ASSEMBLY_QNAME, + "name(/root/assembly)"), + Arguments.of( + MockedDocumentGenerator.ASSEMBLY_FLAG_QNAME, + "name(/root/assembly/@assembly-flag)"), + Arguments.of( + MockedDocumentGenerator.FIELD_QNAME, + "name(/root/field)"), + Arguments.of( + MockedDocumentGenerator.FIELD_FLAG_QNAME, + "name(/root/field/@field-flag)")); + } + + @ParameterizedTest + @MethodSource("provideValues") + void test(@Nullable IEnhancedQName expected, @NonNull String metapath) { + DynamicContext dynamicContext = newDynamicContext(); + + IStringItem result = IMetapathExpression.compile(metapath, dynamicContext.getStaticContext()) + .evaluateAs( + MockedDocumentGenerator.generateDocumentNodeItem(getContext()), + IMetapathExpression.ResultType.ITEM, + dynamicContext); + assertNotNull(result); + assertEquals( + expected == null + ? "" + : expected.toEQName(dynamicContext.getStaticContext()), + result.asString()); + } + + @Test + void testContextAbsent() { + DynamicContext dynamicContext = newDynamicContext(); + + MetapathException ex = assertThrows(MetapathException.class, () -> { + IMetapathExpression.compile("name()", dynamicContext.getStaticContext()) + .evaluateAs(null, IMetapathExpression.ResultType.ITEM, dynamicContext); + }); + Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null; + + assertAll( + () -> assertEquals(DynamicMetapathException.class, cause == null + ? null + : cause.getClass()), + () -> assertEquals(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT, cause instanceof DynamicMetapathException + ? ((DynamicMetapathException) cause).getCode() + : null)); + } + + @Test + void testNotANode() { + DynamicContext dynamicContext = newDynamicContext(); + + MetapathException ex = assertThrows(MetapathException.class, () -> { + IMetapathExpression.compile("name()", dynamicContext.getStaticContext()) + .evaluateAs(IStringItem.valueOf("test"), IMetapathExpression.ResultType.ITEM, dynamicContext); + }); + Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null; + + assertAll( + () -> assertEquals(InvalidTypeMetapathException.class, cause == null + ? null + : cause.getClass()), + () -> assertEquals(TypeMetapathException.INVALID_TYPE_ERROR, cause instanceof TypeMetapathException + ? ((TypeMetapathException) cause).getCode() + : null)); + } +} diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnNamespaceUriTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnNamespaceUriTest.java new file mode 100644 index 000000000..632738974 --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnNamespaceUriTest.java @@ -0,0 +1,117 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +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 static org.junit.jupiter.api.Assertions.assertThrows; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.DynamicMetapathException; +import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase; +import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression; +import gov.nist.secauto.metaschema.core.metapath.MetapathException; +import gov.nist.secauto.metaschema.core.metapath.function.library.impl.MockedDocumentGenerator; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; +import gov.nist.secauto.metaschema.core.metapath.type.TypeMetapathException; +import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +class FnNamespaceUriTest + extends ExpressionTestBase { + + private static Stream provideValues() { // NOPMD - false positive + return Stream.of( + Arguments.of( + null, + "namespace-uri()"), + Arguments.of( + null, + "namespace-uri(.)"), + Arguments.of( + MockedDocumentGenerator.ROOT_QNAME, + "namespace-uri(/root)"), + Arguments.of( + MockedDocumentGenerator.ASSEMBLY_QNAME, + "namespace-uri(/root/assembly)"), + Arguments.of( + MockedDocumentGenerator.ASSEMBLY_FLAG_QNAME, + "namespace-uri(/root/assembly/@assembly-flag)"), + Arguments.of( + MockedDocumentGenerator.FIELD_QNAME, + "namespace-uri(/root/field)"), + Arguments.of( + MockedDocumentGenerator.FIELD_FLAG_QNAME, + "namespace-uri(/root/field/@field-flag)")); + } + + @ParameterizedTest + @MethodSource("provideValues") + void test(@Nullable IEnhancedQName expected, @NonNull String metapath) { + DynamicContext dynamicContext = newDynamicContext(); + + IStringItem result = IMetapathExpression.compile(metapath, dynamicContext.getStaticContext()) + .evaluateAs( + MockedDocumentGenerator.generateDocumentNodeItem(getContext()), + IMetapathExpression.ResultType.ITEM, + dynamicContext); + assertNotNull(result); + assertEquals( + expected == null + ? "" + : expected.getNamespace(), + result.asString()); + } + + @Test + void testContextAbsent() { + DynamicContext dynamicContext = newDynamicContext(); + + MetapathException ex = assertThrows(MetapathException.class, () -> { + IMetapathExpression.compile("namespace-uri()", dynamicContext.getStaticContext()) + .evaluateAs(null, IMetapathExpression.ResultType.ITEM, dynamicContext); + }); + Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null; + + assertAll( + () -> assertEquals(DynamicMetapathException.class, cause == null + ? null + : cause.getClass()), + () -> assertEquals(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT, cause instanceof DynamicMetapathException + ? ((DynamicMetapathException) cause).getCode() + : null)); + } + + @Test + void testNotANode() { + DynamicContext dynamicContext = newDynamicContext(); + + MetapathException ex = assertThrows(MetapathException.class, () -> { + IMetapathExpression.compile("namespace-uri()", dynamicContext.getStaticContext()) + .evaluateAs(IStringItem.valueOf("test"), IMetapathExpression.ResultType.ITEM, dynamicContext); + }); + Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null; + + assertAll( + () -> assertEquals(InvalidTypeMetapathException.class, cause == null + ? null + : cause.getClass()), + () -> assertEquals(TypeMetapathException.INVALID_TYPE_ERROR, cause instanceof TypeMetapathException + ? ((TypeMetapathException) cause).getCode() + : null)); + } +} diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnOutermostTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnOutermostTest.java new file mode 100644 index 000000000..e54963f98 --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnOutermostTest.java @@ -0,0 +1,106 @@ +/* +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase; +import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression; +import gov.nist.secauto.metaschema.core.metapath.MetapathException; +import gov.nist.secauto.metaschema.core.metapath.function.library.impl.MockedDocumentGenerator; +import gov.nist.secauto.metaschema.core.metapath.item.ISequence; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; +import gov.nist.secauto.metaschema.core.metapath.type.TypeMetapathException; +import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; + +class FnOutermostTest + extends ExpressionTestBase { + + private static Stream provideValues() { // NOPMD - false positive + return Stream.of( + // only document matches + Arguments.of( + "(.)", + "(.,/root,/root/assembly,/root/assembly/@assembly-flag,/root/field,/root/field/@field-flag)"), + // parents not present + Arguments.of( + "(/root/assembly,/root/field)", + "(/root/assembly,/root/field)"), + // parents not present + Arguments.of( + "(/root/assembly,/root/field/@field-flag)", + "(/root/assembly,/root/assembly/@assembly-flag,/root/field/@field-flag)"), + // duplicates + Arguments.of( + "(.)", + "(/root/assembly,/root/assembly/@assembly-flag,/root/field,/root/field/@field-flag," + + ".,/root,/root/assembly,/root/assembly/@assembly-flag,/root/field,/root/field/@field-flag)")); + } + + @ParameterizedTest + @MethodSource("provideValues") + void test(@NonNull String expectedValueMetapath, @NonNull String actualValuesMetapath) { + DynamicContext dynamicContext = newDynamicContext(); + INodeItem node = MockedDocumentGenerator.generateDocumentNodeItem(getContext()); + + ISequence expected + = IMetapathExpression.compile(expectedValueMetapath, dynamicContext.getStaticContext()) + .evaluate(node, dynamicContext); + + ISequence actual + = IMetapathExpression.compile("outermost(" + actualValuesMetapath + ")", dynamicContext.getStaticContext()) + .evaluate(node, dynamicContext); + + // Test the expected values against the alternate implementation from the spec + ISequence values + = IMetapathExpression.compile(expectedValueMetapath, dynamicContext.getStaticContext()) + .evaluate(node, dynamicContext); + // ensure the values are list backed + values.getValue(); + + ISequence alternate + = IMetapathExpression + .compile("$nodes[not(ancestor::node() intersect $nodes)]/.", dynamicContext.getStaticContext()) + .evaluate(null, dynamicContext.subContext().bindVariableValue(IEnhancedQName.of("nodes"), values)); + + assertEquals(expected, actual); + assertEquals(expected, alternate); + } + + @Test + void testNotANode() { + DynamicContext dynamicContext = newDynamicContext(); + + MetapathException ex = assertThrows(MetapathException.class, () -> { + IMetapathExpression.compile("outermost('test')", dynamicContext.getStaticContext()) + .evaluateAs(IStringItem.valueOf("test"), IMetapathExpression.ResultType.ITEM, dynamicContext); + }); + Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null; + + assertAll( + () -> assertEquals(InvalidTypeMetapathException.class, cause == null + ? null + : cause.getClass()), + () -> assertEquals(TypeMetapathException.INVALID_TYPE_ERROR, cause instanceof TypeMetapathException + ? ((TypeMetapathException) cause).getCode() + : null)); + } +} diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnRootTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnRootTest.java new file mode 100644 index 000000000..e8d4c330f --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnRootTest.java @@ -0,0 +1,105 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.DynamicMetapathException; +import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase; +import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression; +import gov.nist.secauto.metaschema.core.metapath.MetapathException; +import gov.nist.secauto.metaschema.core.metapath.function.library.impl.MockedDocumentGenerator; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; +import gov.nist.secauto.metaschema.core.metapath.type.TypeMetapathException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; + +class FnRootTest + extends ExpressionTestBase { + + private static Stream provideValues() { // NOPMD - false positive + return Stream.of( + Arguments.of( + "root()"), + Arguments.of( + "root(.)"), + Arguments.of( + "root(/root)"), + Arguments.of( + "root(/root/assembly)"), + Arguments.of( + "root(/root/assembly/@assembly-flag)"), + Arguments.of( + "root(/root/field)"), + Arguments.of( + "root(/root/field/@field-flag)")); + } + + @ParameterizedTest + @MethodSource("provideValues") + void test(@NonNull String metapath) { + DynamicContext dynamicContext = newDynamicContext(); + + INodeItem root = MockedDocumentGenerator.generateDocumentNodeItem(getContext()); + INodeItem result = IMetapathExpression.compile(metapath, dynamicContext.getStaticContext()) + .evaluateAs(root, IMetapathExpression.ResultType.ITEM, dynamicContext); + INodeItem rootResult + = IMetapathExpression.compile("ancestor-or-self::node()[1]", dynamicContext.getStaticContext()) + .evaluateAs(root, IMetapathExpression.ResultType.ITEM, dynamicContext); + assertEquals(root, result); + assertEquals(rootResult, result); + } + + @Test + void testContextAbsent() { + DynamicContext dynamicContext = newDynamicContext(); + + MetapathException ex = assertThrows(MetapathException.class, () -> { + IMetapathExpression.compile("root()", dynamicContext.getStaticContext()) + .evaluateAs(null, IMetapathExpression.ResultType.ITEM, dynamicContext); + }); + Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null; + + assertAll( + () -> assertEquals(DynamicMetapathException.class, cause == null + ? null + : cause.getClass()), + () -> assertEquals(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT, cause instanceof DynamicMetapathException + ? ((DynamicMetapathException) cause).getCode() + : null)); + } + + @Test + void testNotANode() { + DynamicContext dynamicContext = newDynamicContext(); + + MetapathException ex = assertThrows(MetapathException.class, () -> { + IMetapathExpression.compile("root()", dynamicContext.getStaticContext()) + .evaluateAs(IStringItem.valueOf("test"), IMetapathExpression.ResultType.ITEM, dynamicContext); + }); + Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null; + + assertAll( + () -> assertEquals(InvalidTypeMetapathException.class, cause == null + ? null + : cause.getClass()), + () -> assertEquals(TypeMetapathException.INVALID_TYPE_ERROR, cause instanceof TypeMetapathException + ? ((TypeMetapathException) cause).getCode() + : null)); + } +} diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/impl/MockedDocumentGenerator.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/impl/MockedDocumentGenerator.java new file mode 100644 index 000000000..3b1b39aa4 --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/impl/MockedDocumentGenerator.java @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library.impl; + +import gov.nist.secauto.metaschema.core.mdm.IDMAssemblyNodeItem; +import gov.nist.secauto.metaschema.core.mdm.IDMDocumentNodeItem; +import gov.nist.secauto.metaschema.core.mdm.IDMFieldNodeItem; +import gov.nist.secauto.metaschema.core.mdm.IDMRootAssemblyNodeItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition; +import gov.nist.secauto.metaschema.core.model.IAssemblyInstance; +import gov.nist.secauto.metaschema.core.model.IFieldInstance; +import gov.nist.secauto.metaschema.core.model.IFlagInstance; +import gov.nist.secauto.metaschema.core.model.IResourceLocation; +import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; +import gov.nist.secauto.metaschema.core.testing.MockedModelTestSupport; + +import org.jmock.Mockery; + +import java.net.URI; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public class MockedDocumentGenerator { + @NonNull + public static final String NS = "http://example.com/ns"; + @NonNull + public static final IEnhancedQName ROOT_QNAME = IEnhancedQName.of(NS, "root"); + @NonNull + public static final IEnhancedQName ASSEMBLY_QNAME = IEnhancedQName.of(NS, "assembly"); + @NonNull + public static final IEnhancedQName FIELD_QNAME = IEnhancedQName.of(NS, "field"); + @NonNull + public static final IEnhancedQName ASSEMBLY_FLAG_QNAME = IEnhancedQName.of("assembly-flag"); + @NonNull + public static final IEnhancedQName FIELD_FLAG_QNAME = IEnhancedQName.of("field-flag"); + + public static IDMDocumentNodeItem generateDocumentNodeItem(@NonNull Mockery context) { + MockedModelTestSupport mocking = new MockedModelTestSupport(context); + IResourceLocation resourceLocation = mocking.mock(IResourceLocation.class); + + IAssemblyDefinition rootDefinition = mocking.assembly().qname(ROOT_QNAME).rootQName(ROOT_QNAME).toDefinition(); + IAssemblyInstance assemblyInstance = mocking.assembly().qname(ASSEMBLY_QNAME).toInstance(rootDefinition); + IFlagInstance assemblyFlag = mocking.flag().qname(ASSEMBLY_FLAG_QNAME).toInstance(assemblyInstance.getDefinition()); + IFieldInstance fieldInstance = mocking.field().qname(FIELD_QNAME).toInstance(rootDefinition); + IFlagInstance fieldFlag = mocking.flag().qname(FIELD_FLAG_QNAME).toInstance(fieldInstance.getDefinition()); + + IDMDocumentNodeItem document = IDMDocumentNodeItem.newInstance( + URI.create("https://example.com/resource"), + resourceLocation, + rootDefinition, + resourceLocation); + IDMRootAssemblyNodeItem root = document.getRootAssemblyNodeItem(); + IDMAssemblyNodeItem assembly = root.newAssembly(assemblyInstance, resourceLocation); + assembly.newFlag(assemblyFlag, resourceLocation, IStringItem.valueOf("assembly-flag")); + IDMFieldNodeItem field = root.newField(fieldInstance, resourceLocation, IStringItem.valueOf("field")); + field.newFlag(fieldFlag, resourceLocation, IStringItem.valueOf("field-flag")); + + return document; + } + + private MockedDocumentGenerator() { + // disable construction + } + +} 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 85c758a59..67c2a47cf 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 @@ -22,6 +22,12 @@ import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A base class for Metaschema module-based model builders. + * + * @param + * the Java type of this builder + */ public abstract class AbstractModelBuilder> extends MockFactory { @@ -52,7 +58,7 @@ public T reset() { } /** - * Apply the provided namespace to use for names built using this builder. + * Apply the provided namespace for use by this builder. * * @param name * the namespace to use @@ -66,7 +72,7 @@ public T namespace(@NonNull String name) { } /** - * Apply the provided namespace to use for names built using this builder. + * Apply the provided namespace for use by this builder. * * @param name * the namespace to use @@ -75,12 +81,11 @@ public T namespace(@NonNull String name) { @SuppressWarnings("unchecked") @NonNull public T namespace(@NonNull URI name) { - this.namespace = name.toASCIIString(); - return (T) this; + return namespace(name.toASCIIString()); } /** - * Apply the provided names to use for names built using this builder. + * Apply the provided name for use by this builder. * * @param name * the name to use @@ -93,6 +98,21 @@ public T name(@NonNull String name) { return (T) this; } + /** + * Apply the provided qualified name for use by this builder. + * + * @param qname + * the qualified name to use + * @return this builder + */ + @SuppressWarnings("unchecked") + @NonNull + public T qname(@NonNull IEnhancedQName qname) { + this.name = qname.getLocalName(); + this.namespace = qname.getNamespace(); + return (T) this; + } + /** * Validate the data provided to this builder to ensure correct and required * information is provided. @@ -111,6 +131,12 @@ protected void applyDefinition(@NonNull IDefinition definition) { applyModelElement(definition); applyNamed(definition); applyAttributable(definition); + getContext().checking(new Expectations() { + { + allowing(definition).getDefinitionQName(); + will(returnValue(IEnhancedQName.of(ObjectUtils.notNull(namespace), ObjectUtils.notNull(name)))); + } + }); } /** 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 f9aaf19e1..3f8da001d 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 @@ -11,10 +11,12 @@ import gov.nist.secauto.metaschema.core.model.INamedModelInstanceAbsolute; import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; import gov.nist.secauto.metaschema.core.util.CollectionUtil; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; import org.jmock.Expectations; import org.jmock.Mockery; +import java.net.URI; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -27,6 +29,8 @@ public final class AssemblyBuilder extends AbstractModelBuilder implements IModelInstanceBuilder { + private String rootNamespace = ""; + private String rootName; private List flags; private List modelInstances; @@ -53,6 +57,58 @@ public AssemblyBuilder reset() { return this; } + /** + * Apply the provided root namespace for use by this builder. + * + * @param name + * the namespace to use + * @return this builder + */ + @NonNull + public AssemblyBuilder rootNamespace(@NonNull String name) { + this.rootNamespace = name; + return this; + } + + /** + * Apply the provided root namespace for use by this builder. + * + * @param name + * the namespace to use + * @return this builder + */ + @NonNull + public AssemblyBuilder rootNamespace(@NonNull URI name) { + return rootNamespace(ObjectUtils.notNull(name.toASCIIString())); + } + + /** + * Apply the provided root name for use by this builder. + * + * @param name + * the name to use + * @return this builder + */ + @NonNull + public AssemblyBuilder rootName(@NonNull String name) { + this.rootName = name; + return this; + } + + /** + * Apply the provided root qualified name for use by this builder. + * + * @param qname + * the qualified name to use + * @return this builder + */ + @NonNull + public AssemblyBuilder rootQName(@NonNull IEnhancedQName qname) { + this.rootName = qname.getLocalName(); + this.rootNamespace = qname.getNamespace(); + return this; + } + /** * Use the provided flag instances for built fields. * @@ -132,6 +188,11 @@ public IAssemblyDefinition toDefinition() { getContext().checking(new Expectations() { { + if (rootName != null) { + allowing(retval).getRootQName(); + will(returnValue(IEnhancedQName.of(ObjectUtils.notNull(rootNamespace), ObjectUtils.notNull(rootName)))); + } + allowing(retval).getFlagInstances(); will(returnValue(flags.values())); flags.forEach((key, value) -> { diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/testing/IModuleMockFactory.java b/core/src/test/java/gov/nist/secauto/metaschema/core/testing/IModuleMockFactory.java new file mode 100644 index 000000000..fc6a9e624 --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/testing/IModuleMockFactory.java @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.testing; + +import org.jmock.Mockery; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public interface IModuleMockFactory extends IMockFactory { + @Override + @NonNull + Mockery getContext(); + + /** + * Get a new flag builder. + * + * @return the builder + */ + @NonNull + default FlagBuilder flag() { + return FlagBuilder.builder(getContext()); + } + + /** + * Get a new field builder. + * + * @return the builder + */ + @NonNull + default FieldBuilder field() { + return FieldBuilder.builder(getContext()); + } + + /** + * Get a new assembly builder. + * + * @return the builder + */ + @NonNull + default AssemblyBuilder assembly() { + return AssemblyBuilder.builder(getContext()); + } +} 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 72699a074..96d17b74b 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 @@ -11,39 +11,24 @@ import edu.umd.cs.findbugs.annotations.NonNull; -public class MockedModelTestSupport implements IMockFactory { +public class MockedModelTestSupport implements IModuleMockFactory { @RegisterExtension @NonNull - Mockery context = new JUnit5Mockery(); + Mockery context; /** - * Get a new flag builder. - * - * @return the builder + * Construct a new model mock factory using the default JUnit-based mocking + * context. */ - @NonNull - protected FlagBuilder flag() { - return FlagBuilder.builder(context); + public MockedModelTestSupport() { + this(new JUnit5Mockery()); } /** - * Get a new field builder. - * - * @return the builder + * Construct a new model mock factory using the provided mocking context. */ - @NonNull - protected FieldBuilder field() { - return FieldBuilder.builder(context); - } - - /** - * Get a new assembly builder. - * - * @return the builder - */ - @NonNull - protected AssemblyBuilder assembly() { - return AssemblyBuilder.builder(context); + public MockedModelTestSupport(@NonNull Mockery context) { + this.context = context; } @Override diff --git a/databind/src/test/java/gov/nist/secauto/metaschema/databind/codegen/GenerationTest.java b/databind/src/test/java/gov/nist/secauto/metaschema/databind/codegen/GenerationTest.java index 49d73f25b..07be733c8 100644 --- a/databind/src/test/java/gov/nist/secauto/metaschema/databind/codegen/GenerationTest.java +++ b/databind/src/test/java/gov/nist/secauto/metaschema/databind/codegen/GenerationTest.java @@ -30,7 +30,7 @@ void testOscalBindingModuleLoader() throws MetaschemaException, IOException { IBindingModuleLoader loader = bindingContext.newModuleLoader(); loader.allowEntityResolution(); IBindingMetaschemaModule module = loader.load(ObjectUtils.notNull(URI.create( - "https://raw.githubusercontent.com/usnistgov/OSCAL/refs/tags/v1.1.2/src/metaschema/oscal_complete_metaschema.xml"))); + "https://raw.githubusercontent.com/usnistgov/OSCAL/refs/tags/v1.1.3/src/metaschema/oscal_complete_metaschema.xml"))); IBoundModule registeredModule = bindingContext.registerModule(module); assertAll( diff --git a/databind/src/test/java/gov/nist/secauto/metaschema/databind/io/MetaschemaModuleMetaschemaTest.java b/databind/src/test/java/gov/nist/secauto/metaschema/databind/io/MetaschemaModuleMetaschemaTest.java index 490b11e26..3711c1be8 100644 --- a/databind/src/test/java/gov/nist/secauto/metaschema/databind/io/MetaschemaModuleMetaschemaTest.java +++ b/databind/src/test/java/gov/nist/secauto/metaschema/databind/io/MetaschemaModuleMetaschemaTest.java @@ -98,7 +98,7 @@ void testOscalBindingModuleLoader() throws MetaschemaException, IOException { IBindingModuleLoader loader = newBindingContext().newModuleLoader(); loader.allowEntityResolution(); IBindingMetaschemaModule module = loader.load(ObjectUtils.notNull(URI.create( - "https://raw.githubusercontent.com/usnistgov/OSCAL/refs/tags/v1.1.2/src/metaschema/oscal_complete_metaschema.xml"))); + "https://raw.githubusercontent.com/usnistgov/OSCAL/refs/tags/v1.1.3/src/metaschema/oscal_complete_metaschema.xml"))); assertNotNull(module); } @@ -108,7 +108,7 @@ void testOscalXmlModuleLoader() throws MetaschemaException, IOException { // loader.allowEntityResolution(); IXmlMetaschemaModule module = loader.load(ObjectUtils.notNull(URI.create( - "https://raw.githubusercontent.com/usnistgov/OSCAL/refs/tags/v1.1.2/src/metaschema/oscal_complete_metaschema.xml"))); + "https://raw.githubusercontent.com/usnistgov/OSCAL/refs/tags/v1.1.3/src/metaschema/oscal_complete_metaschema.xml"))); assertNotNull(module); } } diff --git a/schemagen/src/test/java/gov/nist/secauto/metaschema/schemagen/JsonSuiteTest.java b/schemagen/src/test/java/gov/nist/secauto/metaschema/schemagen/JsonSuiteTest.java index 3897d79f5..95932b5ae 100644 --- a/schemagen/src/test/java/gov/nist/secauto/metaschema/schemagen/JsonSuiteTest.java +++ b/schemagen/src/test/java/gov/nist/secauto/metaschema/schemagen/JsonSuiteTest.java @@ -119,7 +119,7 @@ void testOscalComplete() throws IOException, MetaschemaException { // NOPMD - de loader.allowEntityResolution(); IModule module = loader.load(new URL( - "https://raw.githubusercontent.com/usnistgov/OSCAL/refs/tags/v1.1.2/src/metaschema/oscal_complete_metaschema.xml")); + "https://raw.githubusercontent.com/usnistgov/OSCAL/refs/tags/v1.1.3/src/metaschema/oscal_complete_metaschema.xml")); ISchemaGenerator schemaGenerator = new JsonSchemaGenerator(); IMutableConfiguration> features = new DefaultConfiguration<>();