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