1
2
3
4
5
6 package dev.metaschema.databind.model.impl;
7
8 import java.util.Arrays;
9 import java.util.regex.Pattern;
10
11 import dev.metaschema.core.datatype.DataTypeService;
12 import dev.metaschema.core.datatype.IDataTypeAdapter;
13 import dev.metaschema.core.datatype.markup.MarkupLine;
14 import dev.metaschema.core.datatype.markup.MarkupMultiline;
15 import dev.metaschema.core.metapath.IMetapathExpression;
16 import dev.metaschema.core.model.ISource;
17 import dev.metaschema.core.model.constraint.AbstractConfigurableMessageConstraintBuilder;
18 import dev.metaschema.core.model.constraint.AbstractConstraintBuilder;
19 import dev.metaschema.core.model.constraint.AbstractKeyConstraintBuilder;
20 import dev.metaschema.core.model.constraint.IAllowedValue;
21 import dev.metaschema.core.model.constraint.IAllowedValuesConstraint;
22 import dev.metaschema.core.model.constraint.ICardinalityConstraint;
23 import dev.metaschema.core.model.constraint.IConstraint;
24 import dev.metaschema.core.model.constraint.IExpectConstraint;
25 import dev.metaschema.core.model.constraint.IIndexConstraint;
26 import dev.metaschema.core.model.constraint.IIndexHasKeyConstraint;
27 import dev.metaschema.core.model.constraint.IKeyField;
28 import dev.metaschema.core.model.constraint.ILet;
29 import dev.metaschema.core.model.constraint.IMatchesConstraint;
30 import dev.metaschema.core.model.constraint.IReportConstraint;
31 import dev.metaschema.core.model.constraint.IUniqueConstraint;
32 import dev.metaschema.core.util.ObjectUtils;
33 import dev.metaschema.databind.model.annotations.AllowedValue;
34 import dev.metaschema.databind.model.annotations.AllowedValues;
35 import dev.metaschema.databind.model.annotations.Expect;
36 import dev.metaschema.databind.model.annotations.HasCardinality;
37 import dev.metaschema.databind.model.annotations.Index;
38 import dev.metaschema.databind.model.annotations.IndexHasKey;
39 import dev.metaschema.databind.model.annotations.IsUnique;
40 import dev.metaschema.databind.model.annotations.KeyField;
41 import dev.metaschema.databind.model.annotations.Let;
42 import dev.metaschema.databind.model.annotations.Matches;
43 import dev.metaschema.databind.model.annotations.ModelUtil;
44 import dev.metaschema.databind.model.annotations.NullJavaTypeAdapter;
45 import dev.metaschema.databind.model.annotations.Property;
46 import dev.metaschema.databind.model.annotations.Report;
47 import edu.umd.cs.findbugs.annotations.NonNull;
48 import edu.umd.cs.findbugs.annotations.Nullable;
49
50 @SuppressWarnings("PMD.CouplingBetweenObjects")
51 final class ConstraintFactory {
52 private ConstraintFactory() {
53
54 }
55
56 static MarkupMultiline toRemarks(@NonNull String remarks) {
57 return remarks.isBlank() ? null : MarkupMultiline.fromMarkdown(remarks);
58 }
59
60 @NonNull
61 static IMetapathExpression metapath(
62 @NonNull String metapath,
63 @NonNull ISource source) {
64 return metapath.isBlank()
65 ? IConstraint.defaultTarget()
66 : IMetapathExpression.lazyCompile(metapath, source.getStaticContext());
67 }
68
69 @NonNull
70 static <T extends AbstractConstraintBuilder<T, ?>> T applyId(@NonNull T builder, @NonNull String id) {
71 if (!id.isBlank()) {
72 builder.identifier(id);
73 }
74 return builder;
75 }
76
77 @NonNull
78 static <T extends AbstractConstraintBuilder<T, ?>> T applyFormalName(@NonNull T builder, @NonNull String name) {
79 if (!name.isBlank()) {
80 builder.formalName(name);
81 }
82 return builder;
83 }
84
85 @NonNull
86 static <T extends AbstractConstraintBuilder<T, ?>> T applyDescription(@NonNull T builder, @NonNull String value) {
87 if (!value.isBlank()) {
88 builder.description(MarkupLine.fromMarkdown(value));
89 }
90 return builder;
91 }
92
93 @NonNull
94 static <T extends AbstractConstraintBuilder<T, ?>> T applyTarget(
95 @NonNull T builder,
96 @NonNull IMetapathExpression expression) {
97 builder.target(expression);
98 return builder;
99 }
100
101 @NonNull
102 static <T extends AbstractConstraintBuilder<T, ?>> T applyProperties(
103 @NonNull T builder,
104 @Nullable Property... properties) {
105 if (properties != null) {
106 Arrays.stream(properties)
107 .map(ModelUtil::toPropertyEntry)
108 .forEachOrdered(entry -> builder.property(
109 ObjectUtils.notNull(entry.getKey()),
110 ObjectUtils.notNull(entry.getValue())));
111 }
112 return builder;
113 }
114
115 static <T extends AbstractConfigurableMessageConstraintBuilder<T, ?>> T applyMessage(@NonNull T builder,
116 @Nullable String message) {
117 if (message != null && !message.isBlank()) {
118 builder.message(message);
119 }
120 return builder;
121 }
122
123 static <T extends AbstractConstraintBuilder<T, ?>> T applyRemarks(@NonNull T builder, @NonNull String remarks) {
124 if (!remarks.isBlank()) {
125 builder.remarks(MarkupMultiline.fromMarkdown(remarks));
126 }
127 return builder;
128 }
129
130 @SuppressWarnings("PMD.NullAssignment")
131 @NonNull
132 static IAllowedValuesConstraint.Builder applyAllowedValues(
133 @NonNull IAllowedValuesConstraint.Builder builder,
134 @NonNull AllowedValues constraint) {
135 for (AllowedValue value : constraint.values()) {
136 String deprecatedVersion = value.deprecatedVersion();
137 if (deprecatedVersion.isBlank()) {
138 deprecatedVersion = null;
139 }
140
141 IAllowedValue allowedValue = IAllowedValue.of(
142 value.value(),
143 MarkupLine.fromMarkdown(value.description()),
144 deprecatedVersion);
145 builder.allowedValue(allowedValue);
146 }
147 return builder;
148 }
149
150 @Nullable
151 static Pattern toPattern(@NonNull String pattern) {
152 return pattern.isBlank() ? null : Pattern.compile(pattern);
153 }
154
155 @Nullable
156 static String toMessage(@NonNull String message) {
157 return message.isBlank() ? null : message;
158 }
159
160 @Nullable
161 static IDataTypeAdapter<?> toDataType(@NonNull Class<? extends IDataTypeAdapter<?>> adapterClass) {
162 return adapterClass.isAssignableFrom(NullJavaTypeAdapter.class) ? null
163 : DataTypeService.instance().getDataTypeByAdapterClass(adapterClass);
164 }
165
166 @NonNull
167 static IAllowedValuesConstraint newAllowedValuesConstraint(
168 @NonNull AllowedValues constraint,
169 @NonNull ISource source) {
170 IAllowedValuesConstraint.Builder builder = IAllowedValuesConstraint.builder();
171 applyId(builder, constraint.id());
172 applyFormalName(builder, constraint.formalName());
173 applyDescription(builder, constraint.description());
174 builder
175 .source(source)
176 .level(constraint.level());
177 applyTarget(builder, metapath(constraint.target(), source));
178 applyProperties(builder, constraint.properties());
179 applyRemarks(builder, constraint.remarks());
180
181 applyAllowedValues(builder, constraint);
182 builder.allowsOther(constraint.allowOthers());
183 builder.extensible(constraint.extensible());
184
185 return builder.build();
186 }
187
188 @NonNull
189 static IMatchesConstraint newMatchesConstraint(Matches constraint, @NonNull ISource source) {
190 IMatchesConstraint.Builder builder = IMatchesConstraint.builder();
191 applyId(builder, constraint.id());
192 applyFormalName(builder, constraint.formalName());
193 applyDescription(builder, constraint.description());
194 builder
195 .source(source)
196 .level(constraint.level());
197 applyTarget(builder, metapath(constraint.target(), source));
198 applyProperties(builder, constraint.properties());
199 applyMessage(builder, constraint.message());
200 applyRemarks(builder, constraint.remarks());
201
202 Pattern pattern = toPattern(constraint.pattern());
203 if (pattern != null) {
204 builder.regex(pattern);
205 }
206
207 IDataTypeAdapter<?> dataType = toDataType(constraint.typeAdapter());
208 if (dataType != null) {
209 builder.datatype(dataType);
210 }
211
212 return builder.build();
213 }
214
215 @NonNull
216 static <T extends AbstractKeyConstraintBuilder<T, ?>> T applyKeyFields(
217 @NonNull T builder,
218 @NonNull ISource source,
219 @NonNull KeyField... keyFields) {
220 for (KeyField keyField : keyFields) {
221 IKeyField field = IKeyField.of(
222 metapath(keyField.target(), source),
223 toPattern(keyField.pattern()),
224 toRemarks(keyField.remarks()));
225 builder.keyField(field);
226 }
227 return builder;
228 }
229
230 @NonNull
231 static IUniqueConstraint newUniqueConstraint(@NonNull IsUnique constraint, @NonNull ISource source) {
232 IUniqueConstraint.Builder builder = IUniqueConstraint.builder();
233 applyId(builder, constraint.id());
234 applyFormalName(builder, constraint.formalName());
235 applyDescription(builder, constraint.description());
236 builder
237 .source(source)
238 .level(constraint.level());
239 applyTarget(builder, metapath(constraint.target(), source));
240 applyProperties(builder, constraint.properties());
241 applyMessage(builder, constraint.message());
242 applyRemarks(builder, constraint.remarks());
243
244 applyKeyFields(builder, source, constraint.keyFields());
245
246 return builder.build();
247 }
248
249 @NonNull
250 static IIndexConstraint newIndexConstraint(@NonNull Index constraint, @NonNull ISource source) {
251 IIndexConstraint.Builder builder = IIndexConstraint.builder(constraint.name());
252 applyId(builder, constraint.id());
253 applyFormalName(builder, constraint.formalName());
254 applyDescription(builder, constraint.description());
255 builder
256 .source(source)
257 .level(constraint.level());
258 applyTarget(builder, metapath(constraint.target(), source));
259 applyProperties(builder, constraint.properties());
260 applyMessage(builder, constraint.message());
261 applyRemarks(builder, constraint.remarks());
262
263 applyKeyFields(builder, source, constraint.keyFields());
264
265 return builder.build();
266 }
267
268 @NonNull
269 static IIndexHasKeyConstraint newIndexHasKeyConstraint(
270 @NonNull IndexHasKey constraint,
271 @NonNull ISource source) {
272 IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(constraint.indexName());
273 applyId(builder, constraint.id());
274 applyFormalName(builder, constraint.formalName());
275 applyDescription(builder, constraint.description());
276 builder
277 .source(source)
278 .level(constraint.level());
279 applyTarget(builder, metapath(constraint.target(), source));
280 applyProperties(builder, constraint.properties());
281 applyMessage(builder, constraint.message());
282 applyRemarks(builder, constraint.remarks());
283
284 applyKeyFields(builder, source, constraint.keyFields());
285
286 return builder.build();
287 }
288
289 @NonNull
290 static IExpectConstraint newExpectConstraint(@NonNull Expect constraint, @NonNull ISource source) {
291 IExpectConstraint.Builder builder = IExpectConstraint.builder();
292 applyId(builder, constraint.id());
293 applyFormalName(builder, constraint.formalName());
294 applyDescription(builder, constraint.description());
295 builder
296 .source(source)
297 .level(constraint.level());
298 applyTarget(builder, metapath(constraint.target(), source));
299 applyProperties(builder, constraint.properties());
300 applyMessage(builder, constraint.message());
301 applyRemarks(builder, constraint.remarks());
302
303 builder.test(metapath(constraint.test(), source));
304
305 return builder.build();
306 }
307
308
309
310
311
312
313
314
315
316
317
318
319
320 @NonNull
321 static IReportConstraint newReportConstraint(@NonNull Report constraint, @NonNull ISource source) {
322 IReportConstraint.Builder builder = IReportConstraint.builder();
323 applyId(builder, constraint.id());
324 applyFormalName(builder, constraint.formalName());
325 applyDescription(builder, constraint.description());
326 builder
327 .source(source)
328 .level(constraint.level());
329 applyTarget(builder, metapath(constraint.target(), source));
330 applyProperties(builder, constraint.properties());
331 applyMessage(builder, constraint.message());
332 applyRemarks(builder, constraint.remarks());
333
334 builder.test(metapath(constraint.test(), source));
335
336 return builder.build();
337 }
338
339 @Nullable
340 static Integer toCardinality(int value) {
341 return value < 0 ? null : value;
342 }
343
344 @NonNull
345 static ICardinalityConstraint newCardinalityConstraint(@NonNull HasCardinality constraint,
346 @NonNull ISource source) {
347 ICardinalityConstraint.Builder builder = ICardinalityConstraint.builder();
348 applyId(builder, constraint.id());
349 applyFormalName(builder, constraint.formalName());
350 applyDescription(builder, constraint.description());
351 builder
352 .source(source)
353 .level(constraint.level());
354 applyTarget(builder, metapath(constraint.target(), source));
355 applyProperties(builder, constraint.properties());
356 applyMessage(builder, constraint.message());
357 applyRemarks(builder, constraint.remarks());
358
359 Integer min = toCardinality(constraint.minOccurs());
360 if (min != null) {
361 builder.minOccurs(min);
362 }
363 Integer max = toCardinality(constraint.maxOccurs());
364 if (max != null) {
365 builder.maxOccurs(max);
366 }
367
368 return builder.build();
369 }
370
371 @NonNull
372 static ILet newLetExpression(@NonNull Let annotation, @NonNull ISource source) {
373 String remarkMarkdown = annotation.remarks();
374 MarkupMultiline remarks = remarkMarkdown.isBlank()
375 ? null
376 : MarkupMultiline.fromMarkdown(remarkMarkdown);
377 return ILet.of(
378 source.getStaticContext().parseVariableName(annotation.name()),
379 IMetapathExpression.lazyCompile(annotation.target(), source.getStaticContext()),
380 source,
381 remarks);
382 }
383 }