1
2
3
4
5
6 package gov.nist.secauto.metaschema.databind.codegen.impl;
7
8 import com.squareup.javapoet.AnnotationSpec;
9 import com.squareup.javapoet.AnnotationSpec.Builder;
10
11 import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
12 import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
13 import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
14 import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
15 import gov.nist.secauto.metaschema.core.metapath.ISequence;
16 import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem;
17 import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem;
18 import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItemFactory;
19 import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
20 import gov.nist.secauto.metaschema.core.model.IFlagDefinition;
21 import gov.nist.secauto.metaschema.core.model.IModelDefinition;
22 import gov.nist.secauto.metaschema.core.model.INamedInstance;
23 import gov.nist.secauto.metaschema.core.model.INamedModelInstanceAbsolute;
24 import gov.nist.secauto.metaschema.core.model.constraint.IAllowedValue;
25 import gov.nist.secauto.metaschema.core.model.constraint.IAllowedValuesConstraint;
26 import gov.nist.secauto.metaschema.core.model.constraint.ICardinalityConstraint;
27 import gov.nist.secauto.metaschema.core.model.constraint.IConstraint;
28 import gov.nist.secauto.metaschema.core.model.constraint.IExpectConstraint;
29 import gov.nist.secauto.metaschema.core.model.constraint.IIndexConstraint;
30 import gov.nist.secauto.metaschema.core.model.constraint.IIndexHasKeyConstraint;
31 import gov.nist.secauto.metaschema.core.model.constraint.IKeyField;
32 import gov.nist.secauto.metaschema.core.model.constraint.ILet;
33 import gov.nist.secauto.metaschema.core.model.constraint.IMatchesConstraint;
34 import gov.nist.secauto.metaschema.core.model.constraint.IUniqueConstraint;
35 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
36 import gov.nist.secauto.metaschema.databind.model.annotations.AllowedValue;
37 import gov.nist.secauto.metaschema.databind.model.annotations.AllowedValues;
38 import gov.nist.secauto.metaschema.databind.model.annotations.AssemblyConstraints;
39 import gov.nist.secauto.metaschema.databind.model.annotations.Expect;
40 import gov.nist.secauto.metaschema.databind.model.annotations.HasCardinality;
41 import gov.nist.secauto.metaschema.databind.model.annotations.Index;
42 import gov.nist.secauto.metaschema.databind.model.annotations.IndexHasKey;
43 import gov.nist.secauto.metaschema.databind.model.annotations.IsUnique;
44 import gov.nist.secauto.metaschema.databind.model.annotations.KeyField;
45 import gov.nist.secauto.metaschema.databind.model.annotations.Let;
46 import gov.nist.secauto.metaschema.databind.model.annotations.Matches;
47 import gov.nist.secauto.metaschema.databind.model.annotations.ValueConstraints;
48
49 import org.apache.logging.log4j.LogBuilder;
50 import org.apache.logging.log4j.LogManager;
51 import org.apache.logging.log4j.Logger;
52
53 import java.lang.reflect.Method;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.regex.Pattern;
57
58 import javax.xml.namespace.QName;
59
60 import edu.umd.cs.findbugs.annotations.NonNull;
61
62
63
64
65 @SuppressWarnings({
66 "PMD.GodClass", "PMD.CouplingBetweenObjects"
67 })
68 public final class AnnotationGenerator {
69 private static final Logger LOGGER = LogManager.getLogger(AnnotationGenerator.class);
70
71 private AnnotationGenerator() {
72
73 }
74
75
76
77
78
79
80
81
82
83
84
85 public static Object getDefaultValue(Class<?> annotation, String member) {
86 Method method;
87 try {
88 method = annotation.getDeclaredMethod(member);
89 } catch (NoSuchMethodException ex) {
90 throw new IllegalArgumentException(ex);
91 }
92 Object retval;
93 try {
94 retval = method.getDefaultValue();
95 } catch (TypeNotPresentException ex) {
96 retval = null;
97 }
98 return retval;
99 }
100
101 private static void buildConstraint(Class<?> annotationType, AnnotationSpec.Builder annotation,
102 IConstraint constraint) {
103 String id = constraint.getId();
104 if (id != null) {
105 annotation.addMember("id", "$S", id);
106 }
107
108 String formalName = constraint.getFormalName();
109 if (formalName != null) {
110 annotation.addMember("formalName", "$S", formalName);
111 }
112
113 MarkupLine description = constraint.getDescription();
114 if (description != null) {
115 annotation.addMember("description", "$S", description.toMarkdown());
116 }
117
118 annotation.addMember("level", "$T.$L", IConstraint.Level.class, constraint.getLevel());
119
120 String target = constraint.getTarget();
121 if (!target.equals(getDefaultValue(annotationType, "target"))) {
122 annotation.addMember("target", "$S", target);
123 }
124 }
125
126 public static void buildValueConstraints(
127 @NonNull AnnotationSpec.Builder builder,
128 @NonNull IFlagDefinition definition) {
129
130 Map<QName, ? extends ILet> lets = definition.getLetExpressions();
131 if (!lets.isEmpty() || !definition.getConstraints().isEmpty()) {
132 AnnotationSpec.Builder annotation = AnnotationSpec.builder(ValueConstraints.class);
133 assert annotation != null;
134
135 applyLetAssignments(annotation, lets);
136 applyAllowedValuesConstraints(annotation, definition.getAllowedValuesConstraints());
137 applyIndexHasKeyConstraints(annotation, definition.getIndexHasKeyConstraints());
138 applyMatchesConstraints(annotation, definition.getMatchesConstraints());
139 applyExpectConstraints(annotation, definition.getExpectConstraints());
140
141 builder.addMember("valueConstraints", "$L", annotation.build());
142 }
143 }
144
145 public static void buildValueConstraints(
146 @NonNull AnnotationSpec.Builder builder,
147 @NonNull IModelDefinition definition) {
148
149 Map<QName, ? extends ILet> lets = definition.getLetExpressions();
150 List<? extends IAllowedValuesConstraint> allowedValues = definition.getAllowedValuesConstraints();
151 List<? extends IIndexHasKeyConstraint> indexHasKey = definition.getIndexHasKeyConstraints();
152 List<? extends IMatchesConstraint> matches = definition.getMatchesConstraints();
153 List<? extends IExpectConstraint> expects = definition.getExpectConstraints();
154
155 if (!lets.isEmpty() || !allowedValues.isEmpty() || !indexHasKey.isEmpty() || !matches.isEmpty()
156 || !expects.isEmpty()) {
157 AnnotationSpec.Builder annotation = AnnotationSpec.builder(ValueConstraints.class);
158 assert annotation != null;
159
160 applyLetAssignments(annotation, lets);
161 applyAllowedValuesConstraints(annotation, allowedValues);
162 applyIndexHasKeyConstraints(annotation, indexHasKey);
163 applyMatchesConstraints(annotation, matches);
164 applyExpectConstraints(annotation, expects);
165
166 builder.addMember("valueConstraints", "$L", annotation.build());
167 }
168 }
169
170 public static void buildAssemblyConstraints(
171 @NonNull AnnotationSpec.Builder builder,
172 @NonNull IAssemblyDefinition definition) {
173
174 List<? extends IIndexConstraint> index = definition.getIndexConstraints();
175 List<? extends IUniqueConstraint> unique = definition.getUniqueConstraints();
176 List<? extends ICardinalityConstraint> cardinality = definition.getHasCardinalityConstraints();
177
178 if (!index.isEmpty() || !unique.isEmpty() || !cardinality.isEmpty()) {
179 AnnotationSpec.Builder annotation = ObjectUtils.notNull(AnnotationSpec.builder(AssemblyConstraints.class));
180
181 applyIndexConstraints(annotation, index);
182 applyUniqueConstraints(annotation, unique);
183 applyHasCardinalityConstraints(definition, annotation, cardinality);
184
185 builder.addMember("modelConstraints", "$L", annotation.build());
186 }
187 }
188
189 private static void applyLetAssignments(
190 @NonNull AnnotationSpec.Builder annotation,
191 @NonNull Map<QName, ? extends ILet> lets) {
192 for (ILet let : lets.values()) {
193 AnnotationSpec.Builder letAnnotation = AnnotationSpec.builder(Let.class);
194 letAnnotation.addMember("name", "$S", let.getName());
195 letAnnotation.addMember("target", "$S", let.getValueExpression().getPath());
196
197
198
199
200
201
202
203 annotation.addMember("lets", "$L", letAnnotation.build());
204 }
205 }
206
207 private static void applyAllowedValuesConstraints(
208 @NonNull AnnotationSpec.Builder annotation,
209 @NonNull List<? extends IAllowedValuesConstraint> constraints) {
210 for (IAllowedValuesConstraint constraint : constraints) {
211 AnnotationSpec.Builder constraintAnnotation = AnnotationSpec.builder(AllowedValues.class);
212 buildConstraint(AllowedValues.class, constraintAnnotation, constraint);
213
214 boolean isAllowedOther = constraint.isAllowedOther();
215 if (isAllowedOther != (boolean) getDefaultValue(AllowedValues.class, "allowOthers")) {
216 constraintAnnotation.addMember("allowOthers", "$L", isAllowedOther);
217 }
218
219 for (IAllowedValue value : constraint.getAllowedValues().values()) {
220 AnnotationSpec.Builder valueAnnotation = AnnotationSpec.builder(AllowedValue.class);
221
222 valueAnnotation.addMember("value", "$S", value.getValue());
223 valueAnnotation.addMember("description", "$S", value.getDescription().toMarkdown());
224
225 constraintAnnotation.addMember("values", "$L", valueAnnotation.build());
226 }
227
228 MarkupMultiline remarks = constraint.getRemarks();
229 if (remarks != null) {
230 constraintAnnotation.addMember("remarks", "$S", remarks.toMarkdown());
231 }
232 annotation.addMember("allowedValues", "$L", constraintAnnotation.build());
233 }
234 }
235
236 private static void applyIndexHasKeyConstraints(
237 @NonNull AnnotationSpec.Builder annotation,
238 @NonNull List<? extends IIndexHasKeyConstraint> constraints) {
239 for (IIndexHasKeyConstraint constraint : constraints) {
240 AnnotationSpec.Builder constraintAnnotation = AnnotationSpec.builder(IndexHasKey.class);
241 buildConstraint(IndexHasKey.class, constraintAnnotation, constraint);
242
243 constraintAnnotation.addMember("indexName", "$S", constraint.getIndexName());
244
245 buildKeyFields(constraintAnnotation, constraint.getKeyFields());
246
247 MarkupMultiline remarks = constraint.getRemarks();
248 if (remarks != null) {
249 constraintAnnotation.addMember("remarks", "$S", remarks.toMarkdown());
250 }
251
252 annotation.addMember("indexHasKey", "$L", constraintAnnotation.build());
253 }
254 }
255
256 private static void buildKeyFields(
257 @NonNull Builder constraintAnnotation,
258 @NonNull List<? extends IKeyField> keyFields) {
259 for (IKeyField key : keyFields) {
260 AnnotationSpec.Builder keyAnnotation = AnnotationSpec.builder(KeyField.class);
261
262 String target = key.getTarget();
263 if (!target.equals(getDefaultValue(KeyField.class, "target"))) {
264 keyAnnotation.addMember("target", "$S", target);
265 }
266
267 Pattern pattern = key.getPattern();
268 if (pattern != null) {
269 keyAnnotation.addMember("pattern", "$S", pattern.pattern());
270 }
271
272 MarkupMultiline remarks = key.getRemarks();
273 if (remarks != null) {
274 keyAnnotation.addMember("remarks", "$S", remarks.toMarkdown());
275 }
276
277 constraintAnnotation.addMember("keyFields", "$L", keyAnnotation.build());
278 }
279 }
280
281 private static void applyMatchesConstraints(
282 @NonNull AnnotationSpec.Builder annotation,
283 @NonNull List<? extends IMatchesConstraint> constraints) {
284 for (IMatchesConstraint constraint : constraints) {
285 AnnotationSpec.Builder constraintAnnotation = AnnotationSpec.builder(Matches.class);
286 buildConstraint(Matches.class, constraintAnnotation, constraint);
287
288 Pattern pattern = constraint.getPattern();
289 if (pattern != null) {
290 constraintAnnotation.addMember("pattern", "$S", pattern.pattern());
291 }
292
293 IDataTypeAdapter<?> dataType = constraint.getDataType();
294 if (dataType != null) {
295 constraintAnnotation.addMember("typeAdapter", "$T.class", dataType.getClass());
296 }
297
298 MarkupMultiline remarks = constraint.getRemarks();
299 if (remarks != null) {
300 constraintAnnotation.addMember("remarks", "$S", remarks.toMarkdown());
301 }
302 annotation.addMember("matches", "$L", constraintAnnotation.build());
303 }
304 }
305
306 private static void applyExpectConstraints(
307 @NonNull AnnotationSpec.Builder annotation,
308 @NonNull List<? extends IExpectConstraint> constraints) {
309 for (IExpectConstraint constraint : constraints) {
310 AnnotationSpec.Builder constraintAnnotation = AnnotationSpec.builder(Expect.class);
311
312 buildConstraint(Expect.class, constraintAnnotation, constraint);
313
314 constraintAnnotation.addMember("test", "$S", constraint.getTest());
315
316 if (constraint.getMessage() != null) {
317 constraintAnnotation.addMember("message", "$S", constraint.getMessage());
318 }
319
320 MarkupMultiline remarks = constraint.getRemarks();
321 if (remarks != null) {
322 constraintAnnotation.addMember("remarks", "$S", remarks.toMarkdown());
323 }
324
325 annotation.addMember("expect", "$L", constraintAnnotation.build());
326 }
327 }
328
329 private static void applyIndexConstraints(
330 @NonNull AnnotationSpec.Builder annotation,
331 @NonNull List<? extends IIndexConstraint> constraints) {
332 for (IIndexConstraint constraint : constraints) {
333 AnnotationSpec.Builder constraintAnnotation = AnnotationSpec.builder(Index.class);
334
335 buildConstraint(Index.class, constraintAnnotation, constraint);
336
337 constraintAnnotation.addMember("name", "$S", constraint.getName());
338
339 buildKeyFields(constraintAnnotation, constraint.getKeyFields());
340
341 MarkupMultiline remarks = constraint.getRemarks();
342 if (remarks != null) {
343 constraintAnnotation.addMember("remarks", "$S", remarks.toMarkdown());
344 }
345
346 annotation.addMember("index", "$L", constraintAnnotation.build());
347 }
348 }
349
350 private static void applyUniqueConstraints(
351 @NonNull AnnotationSpec.Builder annotation,
352 @NonNull List<? extends IUniqueConstraint> constraints) {
353 for (IUniqueConstraint constraint : constraints) {
354 AnnotationSpec.Builder constraintAnnotation = ObjectUtils.notNull(AnnotationSpec.builder(IsUnique.class));
355
356 buildConstraint(IsUnique.class, constraintAnnotation, constraint);
357
358 buildKeyFields(constraintAnnotation, constraint.getKeyFields());
359
360 MarkupMultiline remarks = constraint.getRemarks();
361 if (remarks != null) {
362 constraintAnnotation.addMember("remarks", "$S", remarks.toMarkdown());
363 }
364
365 annotation.addMember("unique", "$L", constraintAnnotation.build());
366 }
367 }
368
369 @SuppressWarnings({
370 "PMD.GuardLogStatement"
371 })
372 private static void checkCardinalities(
373 @NonNull IAssemblyDefinition definition,
374 @NonNull ICardinalityConstraint constraint,
375 @NonNull ISequence<? extends IDefinitionNodeItem<?, ?>> instanceSet,
376 @NonNull LogBuilder logBuilder) {
377
378 LogBuilder warn = LOGGER.atWarn();
379 for (IDefinitionNodeItem<?, ?> item : instanceSet.getValue()) {
380 INamedInstance instance = item.getInstance();
381 if (instance instanceof INamedModelInstanceAbsolute) {
382 INamedModelInstanceAbsolute modelInstance = (INamedModelInstanceAbsolute) instance;
383
384 checkMinOccurs(definition, constraint, modelInstance, logBuilder);
385 checkMaxOccurs(definition, constraint, modelInstance, logBuilder);
386 } else {
387 warn.log(String.format(
388 "Definition '%s' has min-occurs=%d cardinality constraint targeting '%s' that is not a model instance",
389 definition.getName(), constraint.getMinOccurs(), constraint.getTarget()));
390 }
391 }
392 }
393
394 @SuppressWarnings({
395 "PMD.GuardLogStatement"
396 })
397 private static void checkMinOccurs(
398 @NonNull IAssemblyDefinition definition,
399 @NonNull ICardinalityConstraint constraint,
400 @NonNull INamedModelInstanceAbsolute modelInstance,
401 @NonNull LogBuilder logBuilder) {
402 Integer minOccurs = constraint.getMinOccurs();
403 if (minOccurs != null) {
404 if (minOccurs == modelInstance.getMinOccurs()) {
405 logBuilder.log(String.format(
406 "Definition '%s' has min-occurs=%d cardinality constraint targeting '%s' that is redundant with a"
407 + " targeted instance named '%s' that requires min-occurs=%d",
408 definition.getName(), minOccurs, constraint.getTarget(),
409 modelInstance.getName(),
410 modelInstance.getMinOccurs()));
411 } else if (minOccurs < modelInstance.getMinOccurs()) {
412 logBuilder.log(String.format(
413 "Definition '%s' has min-occurs=%d cardinality constraint targeting '%s' that conflicts with a"
414 + " targeted instance named '%s' that requires min-occurs=%d",
415 definition.getName(), minOccurs, constraint.getTarget(),
416 modelInstance.getName(),
417 modelInstance.getMinOccurs()));
418 }
419 }
420 }
421
422 @SuppressWarnings({
423 "PMD.GuardLogStatement"
424 })
425 private static void checkMaxOccurs(
426 @NonNull IAssemblyDefinition definition,
427 @NonNull ICardinalityConstraint constraint,
428 @NonNull INamedModelInstanceAbsolute modelInstance,
429 @NonNull LogBuilder logBuilder) {
430 Integer maxOccurs = constraint.getMaxOccurs();
431 if (maxOccurs != null) {
432 if (maxOccurs == modelInstance.getMaxOccurs()) {
433 logBuilder.log(String.format(
434 "Definition '%s' has max-occurs=%d cardinality constraint targeting '%s' that is redundant with a"
435 + " targeted instance named '%s' that requires max-occurs=%d",
436 definition.getName(), maxOccurs, constraint.getTarget(),
437 modelInstance.getName(),
438 modelInstance.getMaxOccurs()));
439 } else if (maxOccurs < modelInstance.getMaxOccurs()) {
440 logBuilder.log(String.format(
441 "Definition '%s' has max-occurs=%d cardinality constraint targeting '%s' that conflicts with a"
442 + " targeted instance named '%s' that requires max-occurs=%d",
443 definition.getName(), maxOccurs, constraint.getTarget(),
444 modelInstance.getName(),
445 modelInstance.getMaxOccurs()));
446 }
447 }
448 }
449
450 private static void applyHasCardinalityConstraints(
451 @NonNull IAssemblyDefinition definition,
452 @NonNull AnnotationSpec.Builder annotation,
453 @NonNull List<? extends ICardinalityConstraint> constraints) {
454
455 DynamicContext dynamicContext = new DynamicContext();
456 dynamicContext.disablePredicateEvaluation();
457
458 for (ICardinalityConstraint constraint : constraints) {
459
460 IAssemblyNodeItem definitionNodeItem
461 = INodeItemFactory.instance().newAssemblyNodeItem(definition);
462
463 ISequence<? extends IDefinitionNodeItem<?, ?>> instanceSet
464 = constraint.matchTargets(definitionNodeItem, dynamicContext);
465
466 if (LOGGER.isWarnEnabled()) {
467 checkCardinalities(definition, constraint, instanceSet, ObjectUtils.notNull(LOGGER.atWarn()));
468 }
469
470 AnnotationSpec.Builder constraintAnnotation = AnnotationSpec.builder(HasCardinality.class);
471
472 buildConstraint(HasCardinality.class, constraintAnnotation, constraint);
473
474 Integer minOccurs = constraint.getMinOccurs();
475 if (minOccurs != null && !minOccurs.equals(getDefaultValue(HasCardinality.class, "minOccurs"))) {
476 constraintAnnotation.addMember("minOccurs", "$L", minOccurs);
477 }
478
479 Integer maxOccurs = constraint.getMaxOccurs();
480 if (maxOccurs != null && !maxOccurs.equals(getDefaultValue(HasCardinality.class, "maxOccurs"))) {
481 constraintAnnotation.addMember("maxOccurs", "$L", maxOccurs);
482 }
483
484 annotation.addMember("cardinality", "$L", constraintAnnotation.build());
485
486 MarkupMultiline remarks = constraint.getRemarks();
487 if (remarks != null) {
488 constraintAnnotation.addMember("remarks", "$S", remarks.toMarkdown());
489 }
490 }
491 }
492 }