1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.model.impl;
7   
8   import gov.nist.secauto.metaschema.core.datatype.DataTypeService;
9   import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
10  import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
11  import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
12  import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression;
13  import gov.nist.secauto.metaschema.core.model.ISource;
14  import gov.nist.secauto.metaschema.core.model.constraint.AbstractConfigurableMessageConstraintBuilder;
15  import gov.nist.secauto.metaschema.core.model.constraint.AbstractConstraintBuilder;
16  import gov.nist.secauto.metaschema.core.model.constraint.AbstractKeyConstraintBuilder;
17  import gov.nist.secauto.metaschema.core.model.constraint.IAllowedValue;
18  import gov.nist.secauto.metaschema.core.model.constraint.IAllowedValuesConstraint;
19  import gov.nist.secauto.metaschema.core.model.constraint.ICardinalityConstraint;
20  import gov.nist.secauto.metaschema.core.model.constraint.IConstraint;
21  import gov.nist.secauto.metaschema.core.model.constraint.IExpectConstraint;
22  import gov.nist.secauto.metaschema.core.model.constraint.IIndexConstraint;
23  import gov.nist.secauto.metaschema.core.model.constraint.IIndexHasKeyConstraint;
24  import gov.nist.secauto.metaschema.core.model.constraint.IKeyField;
25  import gov.nist.secauto.metaschema.core.model.constraint.ILet;
26  import gov.nist.secauto.metaschema.core.model.constraint.IMatchesConstraint;
27  import gov.nist.secauto.metaschema.core.model.constraint.IUniqueConstraint;
28  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
29  import gov.nist.secauto.metaschema.databind.model.annotations.AllowedValue;
30  import gov.nist.secauto.metaschema.databind.model.annotations.AllowedValues;
31  import gov.nist.secauto.metaschema.databind.model.annotations.Expect;
32  import gov.nist.secauto.metaschema.databind.model.annotations.HasCardinality;
33  import gov.nist.secauto.metaschema.databind.model.annotations.Index;
34  import gov.nist.secauto.metaschema.databind.model.annotations.IndexHasKey;
35  import gov.nist.secauto.metaschema.databind.model.annotations.IsUnique;
36  import gov.nist.secauto.metaschema.databind.model.annotations.KeyField;
37  import gov.nist.secauto.metaschema.databind.model.annotations.Let;
38  import gov.nist.secauto.metaschema.databind.model.annotations.Matches;
39  import gov.nist.secauto.metaschema.databind.model.annotations.ModelUtil;
40  import gov.nist.secauto.metaschema.databind.model.annotations.NullJavaTypeAdapter;
41  import gov.nist.secauto.metaschema.databind.model.annotations.Property;
42  
43  import java.util.Arrays;
44  import java.util.regex.Pattern;
45  
46  import edu.umd.cs.findbugs.annotations.NonNull;
47  import edu.umd.cs.findbugs.annotations.Nullable;
48  
49  @SuppressWarnings("PMD.CouplingBetweenObjects")
50  final class ConstraintFactory {
51    private ConstraintFactory() {
52      // disable
53    }
54  
55    static MarkupMultiline toRemarks(@NonNull String remarks) {
56      return remarks.isBlank() ? null : MarkupMultiline.fromMarkdown(remarks);
57    }
58  
59    @NonNull
60    static IMetapathExpression metapath(
61        @NonNull String metapath,
62        @NonNull ISource source) {
63      return metapath.isBlank()
64          ? IConstraint.defaultTarget()
65          : IMetapathExpression.lazyCompile(metapath, source.getStaticContext());
66    }
67  
68    @NonNull
69    static <T extends AbstractConstraintBuilder<T, ?>> T applyId(@NonNull T builder, @NonNull String id) {
70      if (!id.isBlank()) {
71        builder.identifier(id);
72      }
73      return builder;
74    }
75  
76    @NonNull
77    static <T extends AbstractConstraintBuilder<T, ?>> T applyFormalName(@NonNull T builder, @NonNull String name) {
78      if (!name.isBlank()) {
79        builder.formalName(name);
80      }
81      return builder;
82    }
83  
84    @NonNull
85    static <T extends AbstractConstraintBuilder<T, ?>> T applyDescription(@NonNull T builder, @NonNull String value) {
86      if (!value.isBlank()) {
87        builder.description(MarkupLine.fromMarkdown(value));
88      }
89      return builder;
90    }
91  
92    @NonNull
93    static <T extends AbstractConstraintBuilder<T, ?>> T applyTarget(
94        @NonNull T builder,
95        @NonNull IMetapathExpression expression) {
96      builder.target(expression);
97      return builder;
98    }
99  
100   @NonNull
101   static <T extends AbstractConstraintBuilder<T, ?>> T applyProperties(
102       @NonNull T builder,
103       @Nullable Property... properties) {
104     if (properties != null) {
105       Arrays.stream(properties)
106           .map(ModelUtil::toPropertyEntry)
107           .forEachOrdered(entry -> builder.property(
108               ObjectUtils.notNull(entry.getKey()),
109               ObjectUtils.notNull(entry.getValue())));
110     }
111     return builder;
112   }
113 
114   static <T extends AbstractConfigurableMessageConstraintBuilder<T, ?>> T applyMessage(@NonNull T builder,
115       @Nullable String message) {
116     if (message != null && !message.isBlank()) {
117       builder.message(message);
118     }
119     return builder;
120   }
121 
122   static <T extends AbstractConstraintBuilder<T, ?>> T applyRemarks(@NonNull T builder, @NonNull String remarks) {
123     if (!remarks.isBlank()) {
124       builder.remarks(MarkupMultiline.fromMarkdown(remarks));
125     }
126     return builder;
127   }
128 
129   @SuppressWarnings("PMD.NullAssignment")
130   @NonNull
131   static IAllowedValuesConstraint.Builder applyAllowedValues(
132       @NonNull IAllowedValuesConstraint.Builder builder,
133       @NonNull AllowedValues constraint) {
134     for (AllowedValue value : constraint.values()) {
135       String deprecatedVersion = value.deprecatedVersion();
136       if (deprecatedVersion.isBlank()) {
137         deprecatedVersion = null;
138       }
139 
140       IAllowedValue allowedValue = IAllowedValue.of(
141           value.value(),
142           MarkupLine.fromMarkdown(value.description()),
143           deprecatedVersion);
144       builder.allowedValue(allowedValue);
145     }
146     return builder;
147   }
148 
149   @Nullable
150   static Pattern toPattern(@NonNull String pattern) {
151     return pattern.isBlank() ? null : Pattern.compile(pattern);
152   }
153 
154   @Nullable
155   static String toMessage(@NonNull String message) {
156     return message.isBlank() ? null : message;
157   }
158 
159   @Nullable
160   static IDataTypeAdapter<?> toDataType(@NonNull Class<? extends IDataTypeAdapter<?>> adapterClass) {
161     return adapterClass.isAssignableFrom(NullJavaTypeAdapter.class) ? null
162         : DataTypeService.instance().getDataTypeByAdapterClass(adapterClass);
163   }
164 
165   @NonNull
166   static IAllowedValuesConstraint newAllowedValuesConstraint(
167       @NonNull AllowedValues constraint,
168       @NonNull ISource source) {
169     IAllowedValuesConstraint.Builder builder = IAllowedValuesConstraint.builder();
170     applyId(builder, constraint.id());
171     applyFormalName(builder, constraint.formalName());
172     applyDescription(builder, constraint.description());
173     builder
174         .source(source)
175         .level(constraint.level());
176     applyTarget(builder, metapath(constraint.target(), source));
177     applyProperties(builder, constraint.properties());
178     applyRemarks(builder, constraint.remarks());
179 
180     applyAllowedValues(builder, constraint);
181     builder.allowsOther(constraint.allowOthers());
182     builder.extensible(constraint.extensible());
183 
184     return builder.build();
185   }
186 
187   @NonNull
188   static IMatchesConstraint newMatchesConstraint(Matches constraint, @NonNull ISource source) {
189     IMatchesConstraint.Builder builder = IMatchesConstraint.builder();
190     applyId(builder, constraint.id());
191     applyFormalName(builder, constraint.formalName());
192     applyDescription(builder, constraint.description());
193     builder
194         .source(source)
195         .level(constraint.level());
196     applyTarget(builder, metapath(constraint.target(), source));
197     applyProperties(builder, constraint.properties());
198     applyMessage(builder, constraint.message());
199     applyRemarks(builder, constraint.remarks());
200 
201     Pattern pattern = toPattern(constraint.pattern());
202     if (pattern != null) {
203       builder.regex(pattern);
204     }
205 
206     IDataTypeAdapter<?> dataType = toDataType(constraint.typeAdapter());
207     if (dataType != null) {
208       builder.datatype(dataType);
209     }
210 
211     return builder.build();
212   }
213 
214   @NonNull
215   static <T extends AbstractKeyConstraintBuilder<T, ?>> T applyKeyFields(
216       @NonNull T builder,
217       @NonNull ISource source,
218       @NonNull KeyField... keyFields) {
219     for (KeyField keyField : keyFields) {
220       @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") // ok
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   @Nullable
309   static Integer toCardinality(int value) {
310     return value < 0 ? null : value;
311   }
312 
313   @NonNull
314   static ICardinalityConstraint newCardinalityConstraint(@NonNull HasCardinality constraint,
315       @NonNull ISource source) {
316     ICardinalityConstraint.Builder builder = ICardinalityConstraint.builder();
317     applyId(builder, constraint.id());
318     applyFormalName(builder, constraint.formalName());
319     applyDescription(builder, constraint.description());
320     builder
321         .source(source)
322         .level(constraint.level());
323     applyTarget(builder, metapath(constraint.target(), source));
324     applyProperties(builder, constraint.properties());
325     applyMessage(builder, constraint.message());
326     applyRemarks(builder, constraint.remarks());
327 
328     Integer min = toCardinality(constraint.minOccurs());
329     if (min != null) {
330       builder.minOccurs(min);
331     }
332     Integer max = toCardinality(constraint.maxOccurs());
333     if (max != null) {
334       builder.maxOccurs(max);
335     }
336 
337     return builder.build();
338   }
339 
340   @NonNull
341   static ILet newLetExpression(@NonNull Let annotation, @NonNull ISource source) {
342     String remarkMarkdown = annotation.remarks();
343     MarkupMultiline remarks = remarkMarkdown.isBlank()
344         ? null
345         : MarkupMultiline.fromMarkdown(remarkMarkdown);
346     return ILet.of(
347         source.getStaticContext().parseVariableName(annotation.name()),
348         IMetapathExpression.lazyCompile(annotation.target(), source.getStaticContext()),
349         source,
350         remarks);
351   }
352 }