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