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