001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.oscal.lib.metapath.function.library; 007 008import java.net.URI; 009import java.util.List; 010 011import dev.metaschema.core.metapath.DynamicContext; 012import dev.metaschema.core.metapath.MetapathConstants; 013import dev.metaschema.core.metapath.function.FunctionUtils; 014import dev.metaschema.core.metapath.function.IArgument; 015import dev.metaschema.core.metapath.function.IFunction; 016import dev.metaschema.core.metapath.function.InvalidTypeFunctionException; 017import dev.metaschema.core.metapath.item.IItem; 018import dev.metaschema.core.metapath.item.ISequence; 019import dev.metaschema.core.metapath.item.atomic.IAnyUriItem; 020import dev.metaschema.core.metapath.item.atomic.IBooleanItem; 021import dev.metaschema.core.metapath.item.atomic.IStringItem; 022import dev.metaschema.core.metapath.item.node.IAssemblyNodeItem; 023import dev.metaschema.core.metapath.item.node.IFieldNodeItem; 024import dev.metaschema.core.metapath.item.node.IFlagNodeItem; 025import dev.metaschema.core.metapath.item.node.IModelNodeItem; 026import dev.metaschema.core.metapath.type.InvalidTypeMetapathException; 027import dev.metaschema.core.model.IFlagInstance; 028import dev.metaschema.core.model.IModelDefinition; 029import dev.metaschema.core.qname.IEnhancedQName; 030import dev.metaschema.core.util.ObjectUtils; 031import dev.metaschema.oscal.lib.OscalModelConstants; 032import dev.metaschema.oscal.lib.model.metadata.IProperty; 033import edu.umd.cs.findbugs.annotations.NonNull; 034 035public final class HasOscalNamespace { 036 @NonNull 037 private static final IEnhancedQName NS_FLAG_QNAME = IEnhancedQName.of("ns"); 038 @NonNull 039 static final IFunction SIGNATURE_ONE_ARG = IFunction.builder() 040 .name("has-oscal-namespace") 041 .namespace(OscalModelConstants.NS_OSCAL) 042 .argument(IArgument.builder() 043 .name("namespace") 044 .type(IStringItem.type()) 045 .oneOrMore() 046 .build()) 047 .allowUnboundedArity(true) 048 .returnType(IBooleanItem.type()) 049 .focusDependent() 050 .contextIndependent() 051 .deterministic() 052 .returnOne() 053 .functionHandler(HasOscalNamespace::executeOneArg) 054 .build(); 055 056 @NonNull 057 static final IFunction SIGNATURE_TWO_ARGS = IFunction.builder() 058 .name("has-oscal-namespace") 059 .namespace(OscalModelConstants.NS_OSCAL) 060 .argument(IArgument.builder() 061 .name("propOrPart") 062 .type(IAssemblyNodeItem.type()) 063 .one() 064 .build()) 065 .argument(IArgument.builder() 066 .name("namespace") 067 .type(IStringItem.type()) 068 .oneOrMore() 069 .build()) 070 .allowUnboundedArity(true) 071 .focusIndependent() 072 .contextIndependent() 073 .deterministic() 074 .returnType(IBooleanItem.type()) 075 .returnOne() 076 .functionHandler(HasOscalNamespace::executeTwoArg) 077 .build(); 078 079 @NonNull 080 static final IFunction SIGNATURE_ONE_ARG_METAPATH = IFunction.builder() 081 .name("has-oscal-namespace") 082 .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) 083 .argument(IArgument.builder() 084 .name("namespace") 085 .type(IStringItem.type()) 086 .oneOrMore() 087 .build()) 088 .allowUnboundedArity(true) 089 .returnType(IBooleanItem.type()) 090 .focusDependent() 091 .contextIndependent() 092 .deterministic() 093 .returnOne() 094 .functionHandler(HasOscalNamespace::executeOneArg) 095 .build(); 096 097 @NonNull 098 static final IFunction SIGNATURE_TWO_ARGS_METAPATH = IFunction.builder() 099 .name("has-oscal-namespace") 100 .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) 101 .argument(IArgument.builder() 102 .name("propOrPart") 103 .type(IAssemblyNodeItem.type()) 104 .one() 105 .build()) 106 .argument(IArgument.builder() 107 .name("namespace") 108 .type(IStringItem.type()) 109 .oneOrMore() 110 .build()) 111 .allowUnboundedArity(true) 112 .focusIndependent() 113 .contextIndependent() 114 .deterministic() 115 .returnType(IBooleanItem.type()) 116 .returnOne() 117 .functionHandler(HasOscalNamespace::executeTwoArg) 118 .build(); 119 120 private HasOscalNamespace() { 121 // disable construction 122 } 123 124 @SuppressWarnings({ "unused", 125 "PMD.OnlyOneReturn" // readability 126 }) 127 @NonNull 128 public static ISequence<?> executeOneArg( 129 @NonNull IFunction function, 130 @NonNull List<ISequence<?>> arguments, 131 @NonNull DynamicContext dynamicContext, 132 IItem focus) { 133 assert arguments.size() == 1; 134 ISequence<? extends IStringItem> namespaceArgs = FunctionUtils.asType( 135 ObjectUtils.notNull(arguments.get(0))); 136 137 if (namespaceArgs.isEmpty()) { 138 return ISequence.empty(); 139 } 140 141 // Support both assembly and field nodes 142 IModelNodeItem<?, ?> node; 143 if (focus instanceof IAssemblyNodeItem) { 144 node = (IAssemblyNodeItem) focus; 145 } else if (focus instanceof IFieldNodeItem) { 146 node = (IFieldNodeItem) focus; 147 } else { 148 throw new InvalidTypeMetapathException( 149 focus, 150 String.format("Expected an assembly or field node, but got '%s'", 151 focus == null ? "null" : focus.getClass().getName())); 152 } 153 return ISequence.of(hasNamespace(node, namespaceArgs)); 154 } 155 156 @SuppressWarnings({ "unused", 157 "PMD.OnlyOneReturn" // readability 158 }) 159 @NonNull 160 public static ISequence<?> executeTwoArg( 161 @NonNull IFunction function, 162 @NonNull List<ISequence<?>> arguments, 163 @NonNull DynamicContext dynamicContext, 164 IItem focus) { 165 assert arguments.size() == 2; 166 167 ISequence<? extends IStringItem> namespaceArgs = FunctionUtils.asType( 168 ObjectUtils.notNull(arguments.get(1))); 169 if (namespaceArgs.isEmpty()) { 170 return ISequence.empty(); 171 } 172 173 ISequence<? extends IAssemblyNodeItem> nodeSequence = FunctionUtils.asType( 174 ObjectUtils.notNull(arguments.get(0))); 175 176 // always not null, since the first item is required 177 IAssemblyNodeItem node = FunctionUtils.asType(ObjectUtils.requireNonNull(nodeSequence.getFirstItem(true))); 178 return ISequence.of(hasNamespace(node, namespaceArgs)); 179 } 180 181 @SuppressWarnings("PMD.LinguisticNaming") // false positive 182 @NonNull 183 public static IBooleanItem hasNamespace( 184 @NonNull IModelNodeItem<?, ?> propOrPart, 185 @NonNull ISequence<? extends IStringItem> namespaces) { 186 Object propOrPartObject = propOrPart.getValue(); 187 if (propOrPartObject == null) { 188 throw new InvalidTypeFunctionException(InvalidTypeFunctionException.NODE_HAS_NO_TYPED_VALUE, propOrPart); 189 } 190 191 URI nodeNamespace = null; 192 // get the "ns" flag value 193 IFlagNodeItem ns = propOrPart.getFlagByName(NS_FLAG_QNAME); 194 if (ns == null) { 195 // check if the node actually has a "ns" flag 196 IModelDefinition definition = propOrPart.getDefinition(); 197 IFlagInstance flag = definition.getFlagInstanceByName(NS_FLAG_QNAME.getIndexPosition()); 198 if (flag == null) { 199 // Node doesn't support namespaces, so it can't match any namespace 200 return IBooleanItem.FALSE; 201 } 202 203 Object defaultValue = flag.getDefinition().getDefaultValue(); 204 if (defaultValue != null) { 205 nodeNamespace = IAnyUriItem.valueOf(ObjectUtils.notNull(defaultValue.toString())).asUri(); 206 } 207 } else { 208 nodeNamespace = IAnyUriItem.cast(ObjectUtils.notNull(ns.toAtomicItem())).asUri(); 209 } 210 211 String nodeNamespaceString = IProperty.normalizeNamespace(nodeNamespace).toString(); 212 return IBooleanItem.valueOf(namespaces.stream() 213 .map(node -> nodeNamespaceString.equals(node.asString())) 214 .anyMatch(bool -> bool)); 215 } 216}