1
2
3
4
5
6 package dev.metaschema.core.metapath.function.library;
7
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Set;
11 import java.util.stream.Collectors;
12 import java.util.stream.Stream;
13
14 import dev.metaschema.core.metapath.DynamicContext;
15 import dev.metaschema.core.metapath.MetapathConstants;
16 import dev.metaschema.core.metapath.function.ComparisonFunctions;
17 import dev.metaschema.core.metapath.function.FunctionUtils;
18 import dev.metaschema.core.metapath.function.IArgument;
19 import dev.metaschema.core.metapath.function.IFunction;
20 import dev.metaschema.core.metapath.function.InvalidArgumentFunctionException;
21 import dev.metaschema.core.metapath.item.IItem;
22 import dev.metaschema.core.metapath.item.ISequence;
23 import dev.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
24 import dev.metaschema.core.metapath.item.atomic.IAnyUriItem;
25 import dev.metaschema.core.metapath.item.atomic.IBase64BinaryItem;
26 import dev.metaschema.core.metapath.item.atomic.IBooleanItem;
27 import dev.metaschema.core.metapath.item.atomic.IDateItem;
28 import dev.metaschema.core.metapath.item.atomic.IDateTimeItem;
29 import dev.metaschema.core.metapath.item.atomic.IDecimalItem;
30 import dev.metaschema.core.metapath.item.atomic.IDurationItem;
31 import dev.metaschema.core.metapath.item.atomic.IStringItem;
32 import dev.metaschema.core.metapath.item.atomic.IUntypedAtomicItem;
33 import dev.metaschema.core.util.ObjectUtils;
34 import edu.umd.cs.findbugs.annotations.NonNull;
35 import edu.umd.cs.findbugs.annotations.Nullable;
36
37
38
39
40
41
42
43 public final class FnMinMax {
44 private static final String NAME_MIN = "min";
45 private static final String NAME_MAX = "max";
46
47
48
49
50
51 @NonNull
52 private static final Set<Class<? extends IAnyAtomicItem>> PRIMITIVE_ITEM_TYPES = ObjectUtils.notNull(Set.of(
53 IStringItem.class,
54 IBooleanItem.class,
55 IDecimalItem.class,
56 IDurationItem.class,
57 IDateTimeItem.class,
58 IDateItem.class,
59 IBase64BinaryItem.class,
60 IAnyUriItem.class));
61 @NonNull
62 static final IFunction SIGNATURE_MIN = IFunction.builder()
63 .name(NAME_MIN)
64 .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
65 .deterministic()
66 .contextIndependent()
67 .focusIndependent()
68 .argument(IArgument.builder()
69 .name("arg")
70 .type(IAnyAtomicItem.type())
71 .zeroOrMore()
72 .build())
73 .returnType(IAnyAtomicItem.type())
74 .returnZeroOrOne()
75 .functionHandler(FnMinMax::executeMin)
76 .build();
77
78 @NonNull
79 static final IFunction SIGNATURE_MAX = IFunction.builder()
80 .name(NAME_MAX)
81 .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
82 .deterministic()
83 .contextIndependent()
84 .focusIndependent()
85 .argument(IArgument.builder()
86 .name("arg")
87 .type(IAnyAtomicItem.type())
88 .zeroOrMore()
89 .build())
90 .returnType(IAnyAtomicItem.type())
91 .returnZeroOrOne()
92 .functionHandler(FnMinMax::executeMax)
93 .build();
94
95 private FnMinMax() {
96
97 }
98
99 @SuppressWarnings("unused")
100 @NonNull
101 private static ISequence<IAnyAtomicItem> executeMin(
102 @NonNull IFunction function,
103 @NonNull List<ISequence<?>> arguments,
104 @NonNull DynamicContext dynamicContext,
105 IItem focus) {
106 ISequence<? extends IAnyAtomicItem> sequence = FunctionUtils.asType(
107 ObjectUtils.requireNonNull(arguments.get(0)));
108
109 return ISequence.of(min(sequence));
110 }
111
112 @SuppressWarnings("unused")
113 @NonNull
114 private static ISequence<IAnyAtomicItem> executeMax(
115 @NonNull IFunction function,
116 @NonNull List<ISequence<?>> arguments,
117 @NonNull DynamicContext dynamicContext,
118 IItem focus) {
119 ISequence<? extends IAnyAtomicItem> sequence = FunctionUtils.asType(
120 ObjectUtils.requireNonNull(arguments.get(0)));
121
122 return ISequence.of(max(sequence));
123 }
124
125
126
127
128
129
130
131
132
133 @Nullable
134 public static IAnyAtomicItem min(@NonNull List<? extends IAnyAtomicItem> items) {
135
136 return normalize(items)
137 .reduce(null, (item1, item2) -> {
138
139 return item1 != null && ComparisonFunctions.valueCompairison(
140 item1,
141 ComparisonFunctions.Operator.LE,
142 ObjectUtils.notNull(item2),
143 new DynamicContext()).toBoolean()
144 ? item1
145 : item2;
146 });
147 }
148
149
150
151
152
153
154
155
156
157 @Nullable
158 public static IAnyAtomicItem max(@NonNull List<? extends IAnyAtomicItem> items) {
159
160 return normalize(items)
161 .reduce(null, (item1, item2) -> {
162
163 return item1 != null && ComparisonFunctions.valueCompairison(
164 item1,
165 ComparisonFunctions.Operator.GE,
166 ObjectUtils.notNull(item2),
167 new DynamicContext()).toBoolean()
168 ? item1
169 : item2;
170 });
171 }
172
173 private static Stream<? extends IAnyAtomicItem> normalize(
174 @NonNull List<? extends IAnyAtomicItem> items) {
175 if (items.isEmpty()) {
176 return Stream.empty();
177 }
178
179 if (items.size() == 1) {
180 return Stream.of(items.get(0));
181 }
182
183 List<? extends IAnyAtomicItem> resultingItems = convertUntypedItems(items);
184 Map<Class<? extends IAnyAtomicItem>, Integer> counts = countItemTypes(resultingItems);
185 return createNormalizedStream(resultingItems, counts);
186 }
187
188 @NonNull
189 private static List<? extends IAnyAtomicItem> convertUntypedItems(
190 @NonNull List<? extends IAnyAtomicItem> items) {
191 return ObjectUtils.notNull(items.stream()
192 .map(item -> item instanceof IUntypedAtomicItem ? IDecimalItem.cast(item) : item)
193 .collect(Collectors.toList()));
194 }
195
196 @SuppressWarnings("unchecked")
197 @NonNull
198 private static Map<Class<? extends IAnyAtomicItem>, Integer> countItemTypes(
199 @NonNull List<? extends IAnyAtomicItem> items) {
200 return ISequence.ofCollection((List<IAnyAtomicItem>) items).countTypes(PRIMITIVE_ITEM_TYPES);
201 }
202
203 @NonNull
204 private static Stream<? extends IAnyAtomicItem> createNormalizedStream(
205 @NonNull List<? extends IAnyAtomicItem> items,
206 @NonNull Map<Class<? extends IAnyAtomicItem>, Integer> counts) {
207
208
209 if (counts.size() == 1) {
210 return ObjectUtils.notNull(items.stream());
211 }
212
213
214 int size = items.size();
215 if (counts.size() > 1) {
216
217 if (counts.getOrDefault(IStringItem.class, 0) + counts.getOrDefault(IAnyUriItem.class, 0) == size) {
218 return ObjectUtils.notNull(items.stream().map(IAnyAtomicItem::asStringItem));
219 }
220
221
222 if (counts.getOrDefault(IDecimalItem.class, 0) == size) {
223 return ObjectUtils.notNull(items.stream().map(item -> (IDecimalItem) item));
224 }
225 }
226
227
228 @SuppressWarnings("unchecked")
229 List<IAnyAtomicItem> itemList = (List<IAnyAtomicItem>) items;
230 throw new InvalidArgumentFunctionException(
231 InvalidArgumentFunctionException.INVALID_ARGUMENT_TYPE,
232 String.format(
233 "Values must all be of a single atomic type. Found multiple types: [%s]",
234 ISequence.ofCollection(itemList).getItemTypes().stream()
235 .map(Class::getSimpleName)
236 .collect(Collectors.joining(", "))));
237 }
238 }