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