Skip to content

Commit befb71a

Browse files
Added basic support for array construction. Cleaned up ISequence implementations, moving getFirstItem to be a method member.
1 parent e75c797 commit befb71a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+458
-169
lines changed

core/src/main/java/gov/nist/secauto/metaschema/core/metapath/ISequence.java

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
* the Java type of the items in a sequence
6262
*/
6363
@SuppressWarnings("PMD.ShortMethodName")
64-
public interface ISequence<ITEM extends IItem> extends List<ITEM>, IArrayMember {
64+
public interface ISequence<ITEM extends IItem> extends List<ITEM>, IArrayMember, IStringValued {
6565
/**
6666
* Get an empty sequence.
6767
*
@@ -97,6 +97,63 @@ default Iterator<ITEM> iterator() {
9797
@NonNull
9898
Stream<ITEM> stream();
9999

100+
/**
101+
* Retrieves the first item in a sequence. If the sequence is empty, a
102+
* {@code null} result is returned. If requireSingleton is {@code true} and the
103+
* sequence contains more than one item, a {@link TypeMetapathException} is
104+
* thrown.
105+
*
106+
* @param <T>
107+
* the item type to return derived from the provided sequence
108+
* @param items
109+
* the sequence to retrieve the first item from
110+
* @param requireSingleton
111+
* if {@code true} then a {@link TypeMetapathException} is thrown if
112+
* the sequence contains more than one item
113+
* @return {@code null} if the sequence is empty, or the item otherwise
114+
* @throws TypeMetapathException
115+
* if the sequence contains more than one item and requireSingleton is
116+
* {@code true}
117+
*/
118+
static <T extends IItem> T getFirstItem(@NonNull ISequence<T> items, boolean requireSingleton) {
119+
return getFirstItem(items.stream(), requireSingleton);
120+
}
121+
122+
/**
123+
* Retrieves the first item in a sequence. If the sequence is empty, a
124+
* {@code null} result is returned. If requireSingleton is {@code true} and the
125+
* sequence contains more than one item, a {@link TypeMetapathException} is
126+
* thrown.
127+
*
128+
* @param <T>
129+
* the item type to return derived from the provided sequence
130+
* @param items
131+
* the sequence to retrieve the first item from
132+
* @param requireSingleton
133+
* if {@code true} then a {@link TypeMetapathException} is thrown if
134+
* the sequence contains more than one item
135+
* @return {@code null} if the sequence is empty, or the item otherwise
136+
* @throws TypeMetapathException
137+
* if the sequence contains more than one item and requireSingleton is
138+
* {@code true}
139+
*/
140+
static <T extends IItem> T getFirstItem(@NonNull Stream<T> items, boolean requireSingleton) {
141+
return items.limit(2)
142+
.reduce((t, u) -> {
143+
if (requireSingleton) {
144+
throw new InvalidTypeMetapathException(
145+
null,
146+
String.format("sequence expected to contain only one item, but found multiple"));
147+
}
148+
return t;
149+
}).orElse(null);
150+
}
151+
152+
@Nullable
153+
default ITEM getFirstItem(boolean requireSingleton) {
154+
return getFirstItem(this, requireSingleton);
155+
}
156+
100157
/**
101158
* Get a stream guaranteed to be backed by a list.
102159
*
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Portions of this software was developed by employees of the National Institute
3+
* of Standards and Technology (NIST), an agency of the Federal Government and is
4+
* being made available as a public service. Pursuant to title 17 United States
5+
* Code Section 105, works of NIST employees are not subject to copyright
6+
* protection in the United States. This software may be subject to foreign
7+
* copyright. Permission in the United States and in foreign countries, to the
8+
* extent that NIST may hold copyright, to use, copy, modify, create derivative
9+
* works, and distribute this software and its documentation without fee is hereby
10+
* granted on a non-exclusive basis, provided that this notice and disclaimer
11+
* of warranty appears in all copies.
12+
*
13+
* THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14+
* EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15+
* THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17+
* INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18+
* SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT
19+
* SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20+
* INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21+
* OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22+
* CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23+
* PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24+
* OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25+
*/
26+
27+
package gov.nist.secauto.metaschema.core.metapath;
28+
29+
import edu.umd.cs.findbugs.annotations.NonNull;
30+
31+
public interface IStringValued {
32+
/**
33+
* Get the string value.
34+
*
35+
* @return the string value
36+
*/
37+
@NonNull
38+
String asString();
39+
}

core/src/main/java/gov/nist/secauto/metaschema/core/metapath/MetapathConstants.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public final class MetapathConstants {
5252
public static final URI NS_METAPATH_FUNCTIONS_MATH = ObjectUtils.requireNonNull(
5353
URI.create("http://csrc.nist.gov/ns/metaschema/metapath-functions/math"));
5454
@NonNull
55+
public static final URI NS_METAPATH_FUNCTIONS_ARRAY = ObjectUtils.requireNonNull(
56+
URI.create("http://csrc.nist.gov/ns/metaschema/metapath-functions/array"));
57+
@NonNull
5558
public static final URI NS_METAPATH_FUNCTIONS_EXTENDED = NS_METAPATH;
5659

5760
@NonNull
@@ -62,9 +65,10 @@ public final class MetapathConstants {
6265
public static final String PREFIX_XPATH_FUNCTIONS = "mp";
6366
@NonNull
6467
public static final String PREFIX_XPATH_FUNCTIONS_MATH = "math";
68+
@NonNull
69+
public static final String PREFIX_XPATH_FUNCTIONS_ARRAY = "array";
6570

6671
private MetapathConstants() {
6772
// disable construction
6873
}
69-
7074
}

core/src/main/java/gov/nist/secauto/metaschema/core/metapath/MetapathExpression.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ protected <T> T toResultType(@NonNull ISequence<?> sequence, @NonNull ResultType
346346
result = FnBoolean.fnBoolean(sequence).toBoolean();
347347
break;
348348
case NODE:
349-
result = FunctionUtils.getFirstItem(sequence, true);
349+
result = sequence.getFirstItem(true);
350350
break;
351351
case NUMBER:
352352
INumericItem numeric = FunctionUtils.toNumeric(sequence, true);
@@ -356,8 +356,8 @@ protected <T> T toResultType(@NonNull ISequence<?> sequence, @NonNull ResultType
356356
result = sequence;
357357
break;
358358
case STRING:
359-
IItem item = FunctionUtils.getFirstItem(sequence, true);
360-
result = item == null ? "" : FnData.fnDataItem(item).asString();
359+
IAnyAtomicItem item = FnData.fnData(sequence).getFirstItem(true);
360+
result = item == null ? "" : item.asString();
361361
break;
362362
default:
363363
throw new InvalidTypeMetapathException(null, String.format("unsupported result type '%s'", resultType.name()));

core/src/main/java/gov/nist/secauto/metaschema/core/metapath/StaticContext.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ public final class StaticContext {
6262
knownNamespaces.put(
6363
MetapathConstants.PREFIX_XPATH_FUNCTIONS_MATH,
6464
MetapathConstants.NS_METAPATH_FUNCTIONS_MATH);
65+
knownNamespaces.put(
66+
MetapathConstants.PREFIX_XPATH_FUNCTIONS_ARRAY,
67+
MetapathConstants.NS_METAPATH_FUNCTIONS_ARRAY);
6568
WELL_KNOWN_NAMESPACES = CollectionUtil.unmodifiableMap(knownNamespaces);
6669
}
6770

core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractExpression.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@
2828

2929
import gov.nist.secauto.metaschema.core.metapath.ISequence;
3030
import gov.nist.secauto.metaschema.core.metapath.TypeMetapathException;
31-
import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils;
3231
import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
33-
import gov.nist.secauto.metaschema.core.metapath.item.IItem;
3432
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
3533

3634
import edu.umd.cs.findbugs.annotations.NonNull;
@@ -54,9 +52,7 @@ public abstract class AbstractExpression implements IExpression {
5452
@Nullable
5553
public static IAnyAtomicItem getFirstDataItem(@NonNull ISequence<?> sequence,
5654
boolean requireSingleton) {
57-
IItem item = FunctionUtils.getFirstItem(sequence, requireSingleton);
58-
59-
return item == null ? null : FnData.fnDataItem(item);
55+
return FnData.fnData(sequence).getFirstItem(requireSingleton);
6056
}
6157

6258
@Override

core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/BuildCSTVisitor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,11 @@ protected IExpression handleLet(LetexprContext context) {
266266

267267
@Override
268268
protected IExpression handleArrayConstructor(SquarearrayconstructorContext context) {
269+
if (context.getChildCount() == 2) {
270+
// empty
271+
return new ArrayMembers(CollectionUtil.emptyList());
272+
}
273+
269274
return nAiryToCollection(context, 1, 2,
270275
(ctx, idx) -> {
271276
int pos = (idx - 1) / 2;

core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/DefaultFunction.java

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
3636
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem;
3737
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
38+
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
3839

3940
import java.util.ArrayList;
4041
import java.util.Collections;
@@ -43,6 +44,7 @@
4344
import java.util.List;
4445
import java.util.Objects;
4546
import java.util.Set;
47+
import java.util.stream.Stream;
4648

4749
import edu.umd.cs.findbugs.annotations.NonNull;
4850
import edu.umd.cs.findbugs.annotations.Nullable;
@@ -205,7 +207,7 @@ public static List<ISequence<?>> convertArguments(
205207
String.format("a sequence of one expected, but found '%d'", size));
206208
}
207209

208-
IItem item = FunctionUtils.getFirstItem(parameter, true);
210+
IItem item = parameter.getFirstItem(true);
209211
parameter = item == null ? ISequence.empty() : ISequence.of(item);
210212
break;
211213
}
@@ -216,7 +218,7 @@ public static List<ISequence<?>> convertArguments(
216218
String.format("a sequence of zero or one expected, but found '%d'", size));
217219
}
218220

219-
IItem item = FunctionUtils.getFirstItem(parameter, false);
221+
IItem item = parameter.getFirstItem(false);
220222
parameter = item == null ? ISequence.empty() : ISequence.of(item);
221223
break;
222224
}
@@ -279,35 +281,34 @@ protected static ISequence<?> convertSequence(@NonNull IArgument argument, @NonN
279281
ISequenceType requiredSequenceType = argument.getSequenceType();
280282
Class<? extends IItem> requiredSequenceTypeClass = requiredSequenceType.getType();
281283

282-
List<IItem> result = new ArrayList<>(sequence.size());
284+
Stream<? extends IItem> stream = sequence.safeStream();
283285

284-
boolean atomize = IAnyAtomicItem.class.isAssignableFrom(requiredSequenceTypeClass);
286+
if (IAnyAtomicItem.class.isAssignableFrom(requiredSequenceTypeClass)) {
287+
Stream<? extends IAnyAtomicItem> atomicStream = stream.flatMap(item -> FnData.atomize(item));
285288

286-
for (IItem item : sequence.getValue()) {
287-
assert item != null;
288-
if (atomize) {
289-
item = FnData.fnDataItem(item); // NOPMD - intentional
290-
291-
// if (IUntypedAtomicItem.class.isInstance(item)) { // NOPMD
292-
// // TODO: apply cast to atomic type
293-
// }
289+
// if (IUntypedAtomicItem.class.isInstance(item)) { // NOPMD
290+
// // TODO: apply cast to atomic type
291+
// }
294292

293+
if (IStringItem.class.equals(requiredSequenceTypeClass)) {
295294
// promote URIs to strings if a string is required
296-
if (IStringItem.class.equals(requiredSequenceTypeClass) && IAnyUriItem.class.isInstance(item)) {
297-
item = IStringItem.cast((IAnyUriItem) item); // NOPMD - intentional
298-
}
295+
atomicStream = atomicStream.map(item -> IAnyUriItem.class.isInstance(item) ? IStringItem.cast(item) : item);
299296
}
300297

301-
// item = requiredSequenceType.
298+
stream = atomicStream;
299+
}
300+
301+
stream = stream.peek(item -> {
302302
if (!requiredSequenceTypeClass.isInstance(item)) {
303303
throw new InvalidTypeMetapathException(
304304
item,
305-
String.format("The type '%s' is not a subtype of '%s'", item.getClass().getName(),
305+
String.format("The type '%s' is not a subtype of '%s'",
306+
item.getClass().getName(),
306307
requiredSequenceTypeClass.getName()));
307308
}
308-
result.add(item);
309-
}
310-
retval = ISequence.of(result);
309+
});
310+
311+
retval = ISequence.of(stream);
311312
}
312313
return retval;
313314
}
@@ -320,7 +321,7 @@ public ISequence<?> execute(
320321
try {
321322
List<ISequence<?>> convertedArguments = convertArguments(this, arguments);
322323

323-
IItem contextItem = isFocusDepenent() ? FunctionUtils.requireFirstItem(focus, true) : null;
324+
IItem contextItem = isFocusDepenent() ? ObjectUtils.requireNonNull(focus.getFirstItem(true)) : null;
324325

325326
CallingContext callingContext = null;
326327
ISequence<?> result = null;

core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/FunctionUtils.java

Lines changed: 3 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -112,74 +112,6 @@ public static long asLong(@NonNull BigInteger value) {
112112
return value.longValueExact();
113113
}
114114

115-
/**
116-
* Retrieves the first item in a sequence. If the sequence is empty, a
117-
* {@link TypeMetapathException} exception is thrown. If requireSingleton is
118-
* {@code true} and the sequence contains more than one item, a
119-
* {@link TypeMetapathException} is thrown.
120-
*
121-
* @param <ITEM>
122-
* the item type to return derived from the provided sequence
123-
* @param sequence
124-
* the sequence to retrieve the first item from
125-
* @param requireSingleton
126-
* if {@code true} then a {@link TypeMetapathException} is thrown if
127-
* the sequence contains more than one item
128-
* @return {@code null} if the sequence is empty, or the item otherwise
129-
* @throws TypeMetapathException
130-
* if the sequence is empty, or contains more than one item and
131-
* requireSingleton is {@code true}
132-
*/
133-
@NonNull
134-
public static <ITEM extends IItem> ITEM requireFirstItem(@NonNull ISequence<ITEM> sequence,
135-
boolean requireSingleton) {
136-
if (sequence.isEmpty()) {
137-
throw new InvalidTypeMetapathException(
138-
null,
139-
"Expected a non-empty sequence, but sequence was empty.");
140-
}
141-
List<ITEM> items = sequence.getValue();
142-
if (requireSingleton && items.size() != 1) {
143-
throw new InvalidTypeMetapathException(
144-
null,
145-
String.format("sequence expected to contain one item, but found '%d'", items.size()));
146-
}
147-
return ObjectUtils.notNull(items.iterator().next());
148-
}
149-
150-
/**
151-
* Retrieves the first item in a sequence. If the sequence is empty, a
152-
* {@code null} result is returned. If requireSingleton is {@code true} and the
153-
* sequence contains more than one item, a {@link TypeMetapathException} is
154-
* thrown.
155-
*
156-
* @param <ITEM>
157-
* the item type to return derived from the provided sequence
158-
* @param sequence
159-
* the sequence to retrieve the first item from
160-
* @param requireSingleton
161-
* if {@code true} then a {@link TypeMetapathException} is thrown if
162-
* the sequence contains more than one item
163-
* @return {@code null} if the sequence is empty, or the item otherwise
164-
* @throws TypeMetapathException
165-
* if the sequence contains more than one item and requireSingleton is
166-
* {@code true}
167-
*/
168-
@Nullable
169-
public static <ITEM extends IItem> ITEM getFirstItem(@NonNull ISequence<ITEM> sequence, boolean requireSingleton) {
170-
@Nullable ITEM retval = null;
171-
if (!sequence.isEmpty()) {
172-
List<ITEM> items = sequence.getValue();
173-
if (requireSingleton && items.size() != 1) {
174-
throw new InvalidTypeMetapathException(
175-
null,
176-
String.format("sequence expected to contain one item, but found '%d'", items.size()));
177-
}
178-
retval = items.iterator().next();
179-
}
180-
return retval;
181-
}
182-
183115
/**
184116
* Gets the first item of the provided sequence as a {@link INumericItem} value.
185117
* If the sequence is empty, then a {@code null} value is returned.
@@ -198,7 +130,7 @@ public static <ITEM extends IItem> ITEM getFirstItem(@NonNull ISequence<ITEM> se
198130
*/
199131
@Nullable
200132
public static INumericItem toNumeric(@NonNull ISequence<?> sequence, boolean requireSingleton) {
201-
IItem item = getFirstItem(sequence, requireSingleton);
133+
IItem item = sequence.getFirstItem(requireSingleton);
202134
return item == null ? null : toNumeric(item);
203135
}
204136

@@ -215,7 +147,7 @@ public static INumericItem toNumeric(@NonNull ISequence<?> sequence, boolean req
215147
@NonNull
216148
public static INumericItem toNumeric(@NonNull IItem item) {
217149
// atomize
218-
IAnyAtomicItem atomicItem = FnData.fnDataItem(item);
150+
IAnyAtomicItem atomicItem = ISequence.getFirstItem(FnData.atomize(item), true);
219151
return toNumeric(atomicItem);
220152
}
221153

@@ -229,7 +161,7 @@ public static INumericItem toNumeric(@NonNull IItem item) {
229161
* if the item cannot be cast to a numeric value
230162
*/
231163
@NonNull
232-
public static INumericItem toNumeric(@NonNull IAnyAtomicItem item) {
164+
public static INumericItem toNumeric(@Nullable IAnyAtomicItem item) {
233165
try {
234166
return IDecimalItem.cast(item);
235167
} catch (InvalidValueForCastFunctionException ex) {

0 commit comments

Comments
 (0)