1
2
3
4
5
6 package gov.nist.secauto.metaschema.databind.model.impl;
7
8 import gov.nist.secauto.metaschema.core.model.AbstractChoiceGroupInstance;
9 import gov.nist.secauto.metaschema.core.model.IBoundObject;
10 import gov.nist.secauto.metaschema.core.model.IContainerModelSupport;
11 import gov.nist.secauto.metaschema.core.util.CollectionUtil;
12 import gov.nist.secauto.metaschema.core.util.CustomCollectors;
13 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
14 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
15 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceFlag;
16 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelChoiceGroup;
17 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedAssembly;
18 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedField;
19 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedNamed;
20 import gov.nist.secauto.metaschema.databind.model.IBoundModule;
21 import gov.nist.secauto.metaschema.databind.model.IGroupAs;
22 import gov.nist.secauto.metaschema.databind.model.annotations.BoundChoiceGroup;
23 import gov.nist.secauto.metaschema.databind.model.annotations.BoundGroupedAssembly;
24 import gov.nist.secauto.metaschema.databind.model.annotations.BoundGroupedField;
25 import gov.nist.secauto.metaschema.databind.model.annotations.GroupAs;
26 import gov.nist.secauto.metaschema.databind.model.annotations.ModelUtil;
27 import gov.nist.secauto.metaschema.databind.model.info.IModelInstanceCollectionInfo;
28
29 import java.lang.reflect.Field;
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.LinkedHashMap;
34 import java.util.Map;
35 import java.util.Map.Entry;
36 import java.util.function.Function;
37 import java.util.stream.Collectors;
38 import java.util.stream.Stream;
39
40 import javax.xml.namespace.QName;
41
42 import edu.umd.cs.findbugs.annotations.NonNull;
43 import edu.umd.cs.findbugs.annotations.Nullable;
44 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
45 import nl.talsmasoftware.lazy4j.Lazy;
46
47
48
49
50 public final class InstanceModelChoiceGroup
51 extends AbstractChoiceGroupInstance<
52 IBoundDefinitionModelAssembly,
53 IBoundInstanceModelGroupedNamed,
54 IBoundInstanceModelGroupedField,
55 IBoundInstanceModelGroupedAssembly>
56
57 implements IBoundInstanceModelChoiceGroup,
58 IFeatureBoundContainerModelChoiceGroup, IFeatureInstanceModelGroupAs<IBoundObject> {
59 @NonNull
60 private final Field javaField;
61 @NonNull
62 private final BoundChoiceGroup annotation;
63 @NonNull
64 private final Lazy<IModelInstanceCollectionInfo<IBoundObject>> collectionInfo;
65 @NonNull
66 private final IGroupAs groupAs;
67 @NonNull
68 private final Lazy<ChoiceGroupModelContainerSupport> modelContainer;
69 @NonNull
70 private final Lazy<Map<Class<?>, IBoundInstanceModelGroupedNamed>> classToInstanceMap;
71 @NonNull
72 private final Lazy<Map<QName, IBoundInstanceModelGroupedNamed>> qnameToInstanceMap;
73 @NonNull
74 private final Lazy<Map<String, IBoundInstanceModelGroupedNamed>> discriminatorToInstanceMap;
75
76
77
78
79
80
81
82
83
84
85 @NonNull
86 public static InstanceModelChoiceGroup newInstance(
87 @NonNull Field javaField,
88 @NonNull IBoundDefinitionModelAssembly parent) {
89 BoundChoiceGroup annotation = ModelUtil.getAnnotation(javaField, BoundChoiceGroup.class);
90 IGroupAs groupAs = ModelUtil.resolveDefaultGroupAs(annotation.groupAs(), parent.getContainingModule());
91 if (annotation.maxOccurs() == -1 || annotation.maxOccurs() > 1) {
92 if (IGroupAs.SINGLETON_GROUP_AS.equals(groupAs)) {
93 throw new IllegalStateException(String.format("Field '%s' on class '%s' is missing the '%s' annotation.",
94 javaField.getName(),
95 parent.getBoundClass().getName(),
96 GroupAs.class.getName()));
97 }
98 } else if (!IGroupAs.SINGLETON_GROUP_AS.equals(groupAs)) {
99
100 throw new IllegalStateException(
101 String.format(
102 "Field '%s' on class '%s' has the '%s' annotation, but maxOccurs=1. A groupAs must not be specfied.",
103 javaField.getName(),
104 parent.getBoundClass().getName(),
105 GroupAs.class.getName()));
106 }
107 return new InstanceModelChoiceGroup(javaField, annotation, groupAs, parent);
108 }
109
110 @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
111 private InstanceModelChoiceGroup(
112 @NonNull Field javaField,
113 @NonNull BoundChoiceGroup annotation,
114 @NonNull IGroupAs groupAs,
115 @NonNull IBoundDefinitionModelAssembly parent) {
116 super(parent);
117 this.javaField = javaField;
118 this.annotation = annotation;
119 this.groupAs = groupAs;
120 this.collectionInfo = ObjectUtils.notNull(Lazy.lazy(() -> IModelInstanceCollectionInfo.of(this)));
121 this.modelContainer = ObjectUtils.notNull(Lazy.lazy(() -> new ChoiceGroupModelContainerSupport(
122 this.annotation.assemblies(),
123 this.annotation.fields(),
124 this)));
125 this.classToInstanceMap = ObjectUtils.notNull(Lazy.lazy(() -> Collections.unmodifiableMap(
126 getNamedModelInstances().stream()
127 .map(instance -> instance)
128 .collect(Collectors.toMap(
129 item -> item.getDefinition().getBoundClass(),
130 CustomCollectors.identity())))));
131 this.qnameToInstanceMap = ObjectUtils.notNull(Lazy.lazy(() -> Collections.unmodifiableMap(
132 getNamedModelInstances().stream()
133 .collect(Collectors.toMap(
134 IBoundInstanceModelGroupedNamed::getXmlQName,
135 CustomCollectors.identity())))));
136 this.discriminatorToInstanceMap = ObjectUtils.notNull(Lazy.lazy(() -> Collections.unmodifiableMap(
137 getNamedModelInstances().stream()
138 .collect(Collectors.toMap(
139 IBoundInstanceModelGroupedNamed::getEffectiveDisciminatorValue,
140 CustomCollectors.identity())))));
141 }
142
143
144
145
146
147 @Override
148 public Field getField() {
149 return javaField;
150 }
151
152
153
154
155
156
157 @NonNull
158 public BoundChoiceGroup getAnnotation() {
159 return annotation;
160 }
161
162 @SuppressWarnings("null")
163 @Override
164 public IModelInstanceCollectionInfo<IBoundObject> getCollectionInfo() {
165 return collectionInfo.get();
166 }
167
168
169
170
171
172
173
174 @SuppressWarnings("null")
175 @NonNull
176 protected Map<QName, IBoundInstanceModelGroupedNamed> getQNameToInstanceMap() {
177 return qnameToInstanceMap.get();
178 }
179
180
181
182
183
184
185 @SuppressWarnings("null")
186 @NonNull
187 protected Map<Class<?>, IBoundInstanceModelGroupedNamed> getClassToInstanceMap() {
188 return classToInstanceMap.get();
189 }
190
191
192
193
194
195
196
197 @SuppressWarnings("null")
198 @NonNull
199 protected Map<String, IBoundInstanceModelGroupedNamed> getDiscriminatorToInstanceMap() {
200 return discriminatorToInstanceMap.get();
201 }
202
203 @Override
204 @Nullable
205 public IBoundInstanceModelGroupedNamed getGroupedModelInstance(@NonNull Class<?> clazz) {
206 return getClassToInstanceMap().get(clazz);
207 }
208
209 @Override
210 @Nullable
211 public IBoundInstanceModelGroupedNamed getGroupedModelInstance(@NonNull QName name) {
212 return getQNameToInstanceMap().get(name);
213 }
214
215 @Override
216 public IBoundInstanceModelGroupedNamed getGroupedModelInstance(String discriminator) {
217 return getDiscriminatorToInstanceMap().get(discriminator);
218 }
219
220 @Override
221 public IGroupAs getGroupAs() {
222 return groupAs;
223 }
224
225 @SuppressWarnings("null")
226 @Override
227 public ChoiceGroupModelContainerSupport getModelContainer() {
228 return modelContainer.get();
229 }
230
231 @Override
232 public IBoundDefinitionModelAssembly getOwningDefinition() {
233 return getParentContainer();
234 }
235
236 @Override
237 public IBoundModule getContainingModule() {
238 return getOwningDefinition().getContainingModule();
239 }
240
241 @Override
242 public int getMinOccurs() {
243 return getAnnotation().minOccurs();
244 }
245
246 @Override
247 public int getMaxOccurs() {
248 return getAnnotation().maxOccurs();
249 }
250
251 @Override
252 public String getJsonDiscriminatorProperty() {
253 return getAnnotation().discriminator();
254 }
255
256 @Override
257 public String getJsonKeyFlagInstanceName() {
258 return getAnnotation().jsonKey();
259 }
260
261 @Override
262 public IBoundInstanceFlag getItemJsonKey(Object item) {
263 String jsonKeyFlagName = getJsonKeyFlagInstanceName();
264 IBoundInstanceFlag retval = null;
265
266 if (jsonKeyFlagName != null) {
267 Class<?> clazz = item.getClass();
268
269 IBoundInstanceModelGroupedNamed itemInstance = getClassToInstanceMap().get(clazz);
270 retval = itemInstance.getDefinition().getFlagInstanceByName(
271 new QName(itemInstance.getXmlNamespace(), jsonKeyFlagName));
272 }
273 return retval;
274 }
275
276 private static class ChoiceGroupModelContainerSupport
277 implements IContainerModelSupport<
278 IBoundInstanceModelGroupedNamed,
279 IBoundInstanceModelGroupedNamed,
280 IBoundInstanceModelGroupedField,
281 IBoundInstanceModelGroupedAssembly> {
282
283 @NonNull
284 private final Map<QName, IBoundInstanceModelGroupedNamed> namedModelInstances;
285 @NonNull
286 private final Map<QName, IBoundInstanceModelGroupedField> fieldInstances;
287 @NonNull
288 private final Map<QName, IBoundInstanceModelGroupedAssembly> assemblyInstances;
289
290 public ChoiceGroupModelContainerSupport(
291 @NonNull BoundGroupedAssembly[] assemblies,
292 @NonNull BoundGroupedField[] fields,
293 @NonNull IBoundInstanceModelChoiceGroup container) {
294 this.assemblyInstances = CollectionUtil.unmodifiableMap(ObjectUtils.notNull(Arrays.stream(assemblies)
295 .map(instance -> {
296 assert instance != null;
297 return IBoundInstanceModelGroupedAssembly.newInstance(instance,
298 container);
299 })
300 .collect(Collectors.toMap(
301 IBoundInstanceModelGroupedAssembly::getXmlQName,
302 Function.identity(),
303 CustomCollectors.useLastMapper(),
304 LinkedHashMap::new))));
305 this.fieldInstances = CollectionUtil.unmodifiableMap(ObjectUtils.notNull(Arrays.stream(fields)
306 .map(instance -> {
307 assert instance != null;
308 return IBoundInstanceModelGroupedField.newInstance(instance, container);
309 })
310 .collect(Collectors.toMap(
311 IBoundInstanceModelGroupedField::getXmlQName,
312 Function.identity(),
313 CustomCollectors.useLastMapper(),
314 LinkedHashMap::new))));
315 this.namedModelInstances = CollectionUtil.unmodifiableMap(ObjectUtils.notNull(Stream.concat(
316 this.assemblyInstances.entrySet().stream(),
317 this.fieldInstances.entrySet().stream())
318 .collect(Collectors.toMap(
319 Entry::getKey,
320 Entry::getValue,
321 CustomCollectors.useLastMapper(),
322 LinkedHashMap::new))));
323 }
324
325 @SuppressWarnings("null")
326 @Override
327 public Collection<IBoundInstanceModelGroupedNamed> getModelInstances() {
328 return namedModelInstances.values();
329 }
330
331 @Override
332 public Map<QName, IBoundInstanceModelGroupedNamed> getNamedModelInstanceMap() {
333 return namedModelInstances;
334 }
335
336 @Override
337 public Map<QName, IBoundInstanceModelGroupedField> getFieldInstanceMap() {
338 return fieldInstances;
339 }
340
341 @Override
342 public Map<QName, IBoundInstanceModelGroupedAssembly> getAssemblyInstanceMap() {
343 return assemblyInstances;
344 }
345 }
346 }