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.LinkedHashMap;
11 import java.util.Map;
12 import java.util.Set;
13 import java.util.function.Predicate;
14 import java.util.stream.Collectors;
15
16 import dev.metaschema.core.datatype.markup.MarkupLine;
17 import dev.metaschema.core.datatype.markup.MarkupMultiline;
18 import dev.metaschema.core.model.AbstractFieldInstance;
19 import dev.metaschema.core.model.IAttributable;
20 import dev.metaschema.core.model.IBoundObject;
21 import dev.metaschema.core.util.CollectionUtil;
22 import dev.metaschema.core.util.ObjectUtils;
23 import dev.metaschema.databind.model.IBoundDefinitionModelAssembly;
24 import dev.metaschema.databind.model.IBoundDefinitionModelFieldComplex;
25 import dev.metaschema.databind.model.IBoundFieldValue;
26 import dev.metaschema.databind.model.IBoundInstanceFlag;
27 import dev.metaschema.databind.model.IBoundInstanceModelFieldComplex;
28 import dev.metaschema.databind.model.IBoundModule;
29 import dev.metaschema.databind.model.IBoundProperty;
30 import dev.metaschema.databind.model.IGroupAs;
31 import dev.metaschema.databind.model.annotations.BoundField;
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.SuppressFBWarnings;
37 import nl.talsmasoftware.lazy4j.Lazy;
38
39
40
41
42
43
44
45
46
47
48
49 public final class InstanceModelFieldComplex
50 extends AbstractFieldInstance<
51 IBoundDefinitionModelAssembly,
52 IBoundDefinitionModelFieldComplex,
53 IBoundInstanceModelFieldComplex,
54 IBoundDefinitionModelAssembly>
55 implements IBoundInstanceModelFieldComplex, IFeatureInstanceModelGroupAs {
56 @NonNull
57 private final Field javaField;
58 @NonNull
59 private final BoundField annotation;
60 @NonNull
61 private final Lazy<IModelInstanceCollectionInfo<IBoundObject>> collectionInfo;
62 @NonNull
63 private final IGroupAs groupAs;
64 @NonNull
65 private final DefinitionField definition;
66 @NonNull
67 private final Lazy<Object> defaultValue;
68 @NonNull
69 private final Lazy<Map<String, IBoundProperty<?>>> jsonProperties;
70 @NonNull
71 private final Lazy<Map<IAttributable.Key, Set<String>>> properties;
72
73
74
75
76
77
78
79
80
81
82
83
84 @NonNull
85 public static InstanceModelFieldComplex newInstance(
86 @NonNull Field javaField,
87 @NonNull DefinitionField definition,
88 @NonNull IBoundDefinitionModelAssembly parent) {
89 BoundField annotation = ModelUtil.getAnnotation(javaField, BoundField.class);
90 if (!annotation.inXmlWrapped()) {
91 if (definition.hasChildren()) {
92 throw new IllegalStateException(
93 String.format("Field '%s' on class '%s' is requested to be unwrapped, but it has flags preventing this.",
94 javaField.getName(),
95 parent.getBoundClass().getName()));
96 }
97 if (!definition.getJavaTypeAdapter().isUnrappedValueAllowedInXml()) {
98 throw new IllegalStateException(
99 String.format(
100 "Field '%s' on class '%s' is requested to be unwrapped, but its data type '%s' does not allow this.",
101 javaField.getName(),
102 parent.getBoundClass().getName(),
103 definition.getJavaTypeAdapter().getPreferredName()));
104 }
105 }
106
107 IGroupAs groupAs = ModelUtil.resolveDefaultGroupAs(
108 annotation.groupAs(),
109 parent.getContainingModule());
110 if (annotation.maxOccurs() == -1 || annotation.maxOccurs() > 1) {
111 if (IGroupAs.SINGLETON_GROUP_AS.equals(groupAs)) {
112 throw new IllegalStateException(String.format("Field '%s' on class '%s' is missing the '%s' annotation.",
113 javaField.getName(),
114 javaField.getDeclaringClass().getName(),
115 GroupAs.class.getName()));
116 }
117 } else if (!IGroupAs.SINGLETON_GROUP_AS.equals(groupAs)) {
118
119 throw new IllegalStateException(
120 String.format(
121 "Field '%s' on class '%s' has the '%s' annotation, but maxOccurs=1. A groupAs must not be specfied.",
122 javaField.getName(),
123 javaField.getDeclaringClass().getName(),
124 GroupAs.class.getName()));
125 }
126 return new InstanceModelFieldComplex(javaField, annotation, groupAs, definition, parent);
127 }
128
129
130
131
132
133
134
135
136
137
138
139
140 @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
141 private InstanceModelFieldComplex(
142 @NonNull Field javaField,
143 @NonNull BoundField annotation,
144 @NonNull IGroupAs groupAs,
145 @NonNull DefinitionField definition,
146 @NonNull IBoundDefinitionModelAssembly parent) {
147 super(parent);
148 FieldSupport.bindField(javaField);
149 this.javaField = javaField;
150 this.annotation = annotation;
151 this.collectionInfo = ObjectUtils.notNull(Lazy.of(() -> IModelInstanceCollectionInfo.of(this)));
152 this.groupAs = groupAs;
153 this.definition = definition;
154 this.defaultValue = ObjectUtils.notNull(Lazy.of(() -> {
155 Object retval = null;
156 if (getMaxOccurs() == 1) {
157 IBoundFieldValue fieldValue = definition.getFieldValue();
158
159 Object fieldValueDefault = fieldValue.getDefaultValue();
160 if (fieldValueDefault != null) {
161 retval = newInstance(null);
162 fieldValue.setValue(retval, fieldValueDefault);
163
164 for (IBoundInstanceFlag flag : definition.getFlagInstances()) {
165 assert flag != null;
166
167 Object flagDefault = flag.getResolvedDefaultValue();
168 if (flagDefault != null) {
169 flag.setValue(retval, flagDefault);
170 }
171 }
172 }
173 }
174 return retval;
175 }));
176 this.jsonProperties = ObjectUtils.notNull(Lazy.of(() -> {
177 Predicate<IBoundInstanceFlag> flagFilter = null;
178 IBoundInstanceFlag jsonKey = getEffectiveJsonKey();
179 if (jsonKey != null) {
180 flagFilter = flag -> !jsonKey.equals(flag);
181 }
182 return definition.getJsonProperties(flagFilter);
183 }));
184 this.properties = ObjectUtils.notNull(
185 Lazy.of(() -> CollectionUtil.unmodifiableMap(ObjectUtils.notNull(
186 Arrays.stream(annotation.properties())
187 .map(ModelUtil::toPropertyEntry)
188 .collect(
189 Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2, LinkedHashMap::new))))));
190 }
191
192
193
194
195
196 @Override
197 public Field getField() {
198 return javaField;
199 }
200
201
202
203
204
205
206 @NonNull
207 public BoundField getAnnotation() {
208 return annotation;
209 }
210
211 @SuppressWarnings("null")
212 @Override
213 public IModelInstanceCollectionInfo<IBoundObject> getCollectionInfo() {
214 return collectionInfo.get();
215 }
216
217 @Override
218 public DefinitionField getDefinition() {
219 return definition;
220 }
221
222 @Override
223 public IBoundModule getContainingModule() {
224 return getContainingDefinition().getContainingModule();
225 }
226
227 @Override
228 public Object getDefaultValue() {
229 return defaultValue.get();
230 }
231
232 @Override
233 public Map<String, IBoundProperty<?>> getJsonProperties() {
234 return ObjectUtils.notNull(jsonProperties.get());
235 }
236
237 @Override
238 public IGroupAs getGroupAs() {
239 return groupAs;
240 }
241
242 @Override
243 public String getFormalName() {
244 return ModelUtil.resolveNoneOrValue(getAnnotation().formalName());
245 }
246
247 @Override
248 public MarkupLine getDescription() {
249 return ModelUtil.resolveToMarkupLine(getAnnotation().description());
250 }
251
252 @Override
253 public String getUseName() {
254 return ModelUtil.resolveNoneOrValue(getAnnotation().useName());
255 }
256
257 @Override
258 public Integer getUseIndex() {
259 int value = getAnnotation().useIndex();
260 return value == Integer.MIN_VALUE ? null : value;
261 }
262
263 @Override
264 public boolean isInXmlWrapped() {
265 return getAnnotation().inXmlWrapped();
266 }
267
268 @Override
269 public int getMinOccurs() {
270 return getAnnotation().minOccurs();
271 }
272
273 @Override
274 public int getMaxOccurs() {
275 return getAnnotation().maxOccurs();
276 }
277
278 @Override
279 public Map<Key, Set<String>> getProperties() {
280 return ObjectUtils.notNull(properties.get());
281 }
282
283 @Override
284 public MarkupMultiline getRemarks() {
285 return ModelUtil.resolveToMarkupMultiline(getAnnotation().remarks());
286 }
287 }