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.model.DefaultAssemblyModelBuilder;
9   import gov.nist.secauto.metaschema.core.model.IChoiceInstance;
10  import gov.nist.secauto.metaschema.core.model.IContainerModelAssemblySupport;
11  import gov.nist.secauto.metaschema.core.util.CollectionUtil;
12  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
13  import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
14  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModel;
15  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelAssembly;
16  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelChoiceGroup;
17  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelField;
18  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelNamed;
19  import gov.nist.secauto.metaschema.databind.model.annotations.BoundAssembly;
20  import gov.nist.secauto.metaschema.databind.model.annotations.BoundChoiceGroup;
21  import gov.nist.secauto.metaschema.databind.model.annotations.BoundField;
22  import gov.nist.secauto.metaschema.databind.model.annotations.Ignore;
23  
24  import java.lang.reflect.Field;
25  import java.util.Arrays;
26  import java.util.List;
27  import java.util.Objects;
28  import java.util.stream.Collectors;
29  import java.util.stream.Stream;
30  
31  import edu.umd.cs.findbugs.annotations.NonNull;
32  
33  final class AssemblyModelGenerator {
34  
35    @SuppressWarnings("PMD.ShortMethodName")
36    @NonNull
37    public static IContainerModelAssemblySupport<
38        IBoundInstanceModel<?>,
39        IBoundInstanceModelNamed<?>,
40        IBoundInstanceModelField<?>,
41        IBoundInstanceModelAssembly,
42        IChoiceInstance,
43        IBoundInstanceModelChoiceGroup> of(@NonNull DefinitionAssembly containingDefinition) {
44      DefaultAssemblyModelBuilder<IBoundInstanceModel<?>,
45          IBoundInstanceModelNamed<?>,
46          IBoundInstanceModelField<?>,
47          IBoundInstanceModelAssembly,
48          IChoiceInstance,
49          IBoundInstanceModelChoiceGroup> builder = new DefaultAssemblyModelBuilder<>();
50  
51      List<IBoundInstanceModel<?>> modelInstances = CollectionUtil.unmodifiableList(ObjectUtils.notNull(
52          getModelInstanceStream(containingDefinition, containingDefinition.getBoundClass())
53              .collect(Collectors.toUnmodifiableList())));
54  
55      for (IBoundInstanceModel<?> instance : modelInstances) {
56        if (instance instanceof IBoundInstanceModelNamed) {
57          IBoundInstanceModelNamed<?> named = (IBoundInstanceModelNamed<?>) instance;
58          if (instance instanceof IBoundInstanceModelField) {
59            builder.append((IBoundInstanceModelField<?>) named);
60          } else if (instance instanceof IBoundInstanceModelAssembly) {
61            builder.append((IBoundInstanceModelAssembly) named);
62          }
63        } else if (instance instanceof IBoundInstanceModelChoiceGroup) {
64          IBoundInstanceModelChoiceGroup choiceGroup = (IBoundInstanceModelChoiceGroup) instance;
65          builder.append(choiceGroup);
66        }
67      }
68      return builder.buildAssembly();
69    }
70  
71    private static IBoundInstanceModel<?> newBoundModelInstance(
72        @NonNull Field field,
73        @NonNull IBoundDefinitionModelAssembly definition) {
74      IBoundInstanceModel<?> retval = null;
75      if (field.isAnnotationPresent(BoundAssembly.class)) {
76        retval = IBoundInstanceModelAssembly.newInstance(field, definition);
77      } else if (field.isAnnotationPresent(BoundField.class)) {
78        retval = IBoundInstanceModelField.newInstance(field, definition);
79      } else if (field.isAnnotationPresent(BoundChoiceGroup.class)) {
80        retval = IBoundInstanceModelChoiceGroup.newInstance(field, definition);
81      }
82      return retval;
83    }
84  
85    @NonNull
86    private static Stream<IBoundInstanceModel<?>> getModelInstanceStream(
87        @NonNull IBoundDefinitionModelAssembly definition,
88        @NonNull Class<?> clazz) {
89  
90      Stream<IBoundInstanceModel<?>> superInstances;
91      Class<?> superClass = clazz.getSuperclass();
92      if (superClass == null) {
93        superInstances = Stream.empty();
94      } else {
95        // get instances from superclass
96        superInstances = getModelInstanceStream(definition, superClass);
97      }
98  
99      return ObjectUtils.notNull(Stream.concat(superInstances, Arrays.stream(clazz.getDeclaredFields())
100         // skip this field, since it is ignored
101         .filter(field -> !field.isAnnotationPresent(Ignore.class))
102         // skip fields that aren't a Module field or assembly instance
103         .filter(field -> field.isAnnotationPresent(BoundField.class)
104             || field.isAnnotationPresent(BoundAssembly.class)
105             || field.isAnnotationPresent(BoundChoiceGroup.class))
106         .map(field -> {
107           assert field != null;
108 
109           IBoundInstanceModel<?> retval = newBoundModelInstance(field, definition);
110           if (retval == null) {
111             throw new IllegalStateException(
112                 String.format("The field '%s' on class '%s' is not bound", field.getName(), clazz.getName()));
113           }
114           return retval;
115         })
116         .filter(Objects::nonNull)));
117   }
118 
119   private AssemblyModelGenerator() {
120     // disable construction
121   }
122 }