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