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.IChoiceInstance;
9   import gov.nist.secauto.metaschema.core.model.IContainerModelAssemblySupport;
10  import gov.nist.secauto.metaschema.core.util.CollectionUtil;
11  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
12  import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
13  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModel;
14  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelAssembly;
15  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelChoiceGroup;
16  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelField;
17  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelNamed;
18  import gov.nist.secauto.metaschema.databind.model.annotations.BoundAssembly;
19  import gov.nist.secauto.metaschema.databind.model.annotations.BoundChoiceGroup;
20  import gov.nist.secauto.metaschema.databind.model.annotations.BoundField;
21  import gov.nist.secauto.metaschema.databind.model.annotations.Ignore;
22  
23  import java.lang.reflect.Field;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.LinkedHashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Objects;
30  import java.util.stream.Collectors;
31  import java.util.stream.Stream;
32  
33  import javax.xml.namespace.QName;
34  
35  import edu.umd.cs.findbugs.annotations.NonNull;
36  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
37  
38  class AssemblyModelContainerSupport
39      implements IContainerModelAssemblySupport<
40          IBoundInstanceModel<?>,
41          IBoundInstanceModelNamed<?>,
42          IBoundInstanceModelField<?>,
43          IBoundInstanceModelAssembly,
44          IChoiceInstance,
45          IBoundInstanceModelChoiceGroup> {
46    @NonNull
47    private final List<IBoundInstanceModel<?>> modelInstances;
48    @NonNull
49    private final Map<QName, IBoundInstanceModelNamed<?>> namedModelInstances;
50    @NonNull
51    private final Map<QName, IBoundInstanceModelField<?>> fieldInstances;
52    @NonNull
53    private final Map<QName, IBoundInstanceModelAssembly> assemblyInstances;
54    @NonNull
55    private final Map<String, IBoundInstanceModelChoiceGroup> choiceGroupInstances;
56  
57    @SuppressWarnings("PMD.UseConcurrentHashMap")
58    @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
59    public AssemblyModelContainerSupport(
60        @NonNull DefinitionAssembly containingDefinition) {
61      this.modelInstances = CollectionUtil.unmodifiableList(ObjectUtils.notNull(
62          getModelInstanceStream(containingDefinition, containingDefinition.getBoundClass())
63              .collect(Collectors.toUnmodifiableList())));
64  
65      Map<QName, IBoundInstanceModelNamed<?>> namedModelInstances = new LinkedHashMap<>();
66      Map<QName, IBoundInstanceModelField<?>> fieldInstances = new LinkedHashMap<>();
67      Map<QName, IBoundInstanceModelAssembly> assemblyInstances = new LinkedHashMap<>();
68      Map<String, IBoundInstanceModelChoiceGroup> choiceGroupInstances = new LinkedHashMap<>();
69      for (IBoundInstanceModel<?> instance : this.modelInstances) {
70        if (instance instanceof IBoundInstanceModelNamed) {
71          IBoundInstanceModelNamed<?> named = (IBoundInstanceModelNamed<?>) instance;
72          QName key = named.getXmlQName();
73          namedModelInstances.put(key, named);
74  
75          if (instance instanceof IBoundInstanceModelField) {
76            fieldInstances.put(key, (IBoundInstanceModelField<?>) named);
77          } else if (instance instanceof IBoundInstanceModelAssembly) {
78            assemblyInstances.put(key, (IBoundInstanceModelAssembly) named);
79          }
80        } else if (instance instanceof IBoundInstanceModelChoiceGroup) {
81          IBoundInstanceModelChoiceGroup choiceGroup = (IBoundInstanceModelChoiceGroup) instance;
82          String key = ObjectUtils.requireNonNull(choiceGroup.getGroupAsName());
83          choiceGroupInstances.put(key, choiceGroup);
84        }
85      }
86  
87      this.namedModelInstances = namedModelInstances.isEmpty()
88          ? CollectionUtil.emptyMap()
89          : CollectionUtil.unmodifiableMap(namedModelInstances);
90      this.fieldInstances = fieldInstances.isEmpty()
91          ? CollectionUtil.emptyMap()
92          : CollectionUtil.unmodifiableMap(fieldInstances);
93      this.assemblyInstances = assemblyInstances.isEmpty()
94          ? CollectionUtil.emptyMap()
95          : CollectionUtil.unmodifiableMap(assemblyInstances);
96      this.choiceGroupInstances = choiceGroupInstances.isEmpty()
97          ? CollectionUtil.emptyMap()
98          : CollectionUtil.unmodifiableMap(choiceGroupInstances);
99    }
100 
101   protected static IBoundInstanceModel<?> newBoundModelInstance(
102       @NonNull Field field,
103       @NonNull IBoundDefinitionModelAssembly definition) {
104     IBoundInstanceModel<?> retval = null;
105     if (field.isAnnotationPresent(BoundAssembly.class)) {
106       retval = IBoundInstanceModelAssembly.newInstance(field, definition);
107     } else if (field.isAnnotationPresent(BoundField.class)) {
108       retval = IBoundInstanceModelField.newInstance(field, definition);
109     } else if (field.isAnnotationPresent(BoundChoiceGroup.class)) {
110       retval = IBoundInstanceModelChoiceGroup.newInstance(field, definition);
111     }
112     return retval;
113   }
114 
115   @NonNull
116   protected static Stream<IBoundInstanceModel<?>> getModelInstanceStream(
117       @NonNull IBoundDefinitionModelAssembly definition,
118       @NonNull Class<?> clazz) {
119 
120     Stream<IBoundInstanceModel<?>> superInstances;
121     Class<?> superClass = clazz.getSuperclass();
122     if (superClass == null) {
123       superInstances = Stream.empty();
124     } else {
125       // get instances from superclass
126       superInstances = getModelInstanceStream(definition, superClass);
127     }
128 
129     return ObjectUtils.notNull(Stream.concat(superInstances, Arrays.stream(clazz.getDeclaredFields())
130         // skip this field, since it is ignored
131         .filter(field -> !field.isAnnotationPresent(Ignore.class))
132         // skip fields that aren't a Module field or assembly instance
133         .filter(field -> field.isAnnotationPresent(BoundField.class)
134             || field.isAnnotationPresent(BoundAssembly.class)
135             || field.isAnnotationPresent(BoundChoiceGroup.class))
136         .map(field -> {
137           assert field != null;
138 
139           IBoundInstanceModel<?> retval = newBoundModelInstance(field, definition);
140           if (retval == null) {
141             throw new IllegalStateException(
142                 String.format("The field '%s' on class '%s' is not bound", field.getName(), clazz.getName()));
143           }
144           return retval;
145         })
146         .filter(Objects::nonNull)));
147   }
148 
149   @Override
150   public Collection<IBoundInstanceModel<?>> getModelInstances() {
151     return modelInstances;
152   }
153 
154   @Override
155   public Map<QName, IBoundInstanceModelNamed<?>> getNamedModelInstanceMap() {
156     return namedModelInstances;
157   }
158 
159   @Override
160   public Map<QName, IBoundInstanceModelField<?>> getFieldInstanceMap() {
161     return fieldInstances;
162   }
163 
164   @Override
165   public Map<QName, IBoundInstanceModelAssembly> getAssemblyInstanceMap() {
166     return assemblyInstances;
167   }
168 
169   @Override
170   public List<IChoiceInstance> getChoiceInstances() {
171     // not supported
172     return CollectionUtil.emptyList();
173   }
174 
175   @Override
176   public Map<String, IBoundInstanceModelChoiceGroup> getChoiceGroupInstanceMap() {
177     return choiceGroupInstances;
178   }
179 }