1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.oscal.lib.metapath.function.library;
7   
8   import java.net.URI;
9   import java.util.List;
10  
11  import dev.metaschema.core.metapath.DynamicContext;
12  import dev.metaschema.core.metapath.MetapathConstants;
13  import dev.metaschema.core.metapath.function.FunctionUtils;
14  import dev.metaschema.core.metapath.function.IArgument;
15  import dev.metaschema.core.metapath.function.IFunction;
16  import dev.metaschema.core.metapath.function.InvalidTypeFunctionException;
17  import dev.metaschema.core.metapath.item.IItem;
18  import dev.metaschema.core.metapath.item.ISequence;
19  import dev.metaschema.core.metapath.item.atomic.IAnyUriItem;
20  import dev.metaschema.core.metapath.item.atomic.IBooleanItem;
21  import dev.metaschema.core.metapath.item.atomic.IStringItem;
22  import dev.metaschema.core.metapath.item.node.IAssemblyNodeItem;
23  import dev.metaschema.core.metapath.item.node.IFieldNodeItem;
24  import dev.metaschema.core.metapath.item.node.IFlagNodeItem;
25  import dev.metaschema.core.metapath.item.node.IModelNodeItem;
26  import dev.metaschema.core.metapath.type.InvalidTypeMetapathException;
27  import dev.metaschema.core.model.IFlagInstance;
28  import dev.metaschema.core.model.IModelDefinition;
29  import dev.metaschema.core.qname.IEnhancedQName;
30  import dev.metaschema.core.util.ObjectUtils;
31  import dev.metaschema.oscal.lib.OscalModelConstants;
32  import dev.metaschema.oscal.lib.model.metadata.IProperty;
33  import edu.umd.cs.findbugs.annotations.NonNull;
34  
35  public final class HasOscalNamespace {
36    @NonNull
37    private static final IEnhancedQName NS_FLAG_QNAME = IEnhancedQName.of("ns");
38    @NonNull
39    static final IFunction SIGNATURE_ONE_ARG = IFunction.builder()
40        .name("has-oscal-namespace")
41        .namespace(OscalModelConstants.NS_OSCAL)
42        .argument(IArgument.builder()
43            .name("namespace")
44            .type(IStringItem.type())
45            .oneOrMore()
46            .build())
47        .allowUnboundedArity(true)
48        .returnType(IBooleanItem.type())
49        .focusDependent()
50        .contextIndependent()
51        .deterministic()
52        .returnOne()
53        .functionHandler(HasOscalNamespace::executeOneArg)
54        .build();
55  
56    @NonNull
57    static final IFunction SIGNATURE_TWO_ARGS = IFunction.builder()
58        .name("has-oscal-namespace")
59        .namespace(OscalModelConstants.NS_OSCAL)
60        .argument(IArgument.builder()
61            .name("propOrPart")
62            .type(IAssemblyNodeItem.type())
63            .one()
64            .build())
65        .argument(IArgument.builder()
66            .name("namespace")
67            .type(IStringItem.type())
68            .oneOrMore()
69            .build())
70        .allowUnboundedArity(true)
71        .focusIndependent()
72        .contextIndependent()
73        .deterministic()
74        .returnType(IBooleanItem.type())
75        .returnOne()
76        .functionHandler(HasOscalNamespace::executeTwoArg)
77        .build();
78  
79    @NonNull
80    static final IFunction SIGNATURE_ONE_ARG_METAPATH = IFunction.builder()
81        .name("has-oscal-namespace")
82        .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
83        .argument(IArgument.builder()
84            .name("namespace")
85            .type(IStringItem.type())
86            .oneOrMore()
87            .build())
88        .allowUnboundedArity(true)
89        .returnType(IBooleanItem.type())
90        .focusDependent()
91        .contextIndependent()
92        .deterministic()
93        .returnOne()
94        .functionHandler(HasOscalNamespace::executeOneArg)
95        .build();
96  
97    @NonNull
98    static final IFunction SIGNATURE_TWO_ARGS_METAPATH = IFunction.builder()
99        .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 }