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.IDataTypeAdapter;
17 import dev.metaschema.core.datatype.markup.MarkupLine;
18 import dev.metaschema.core.datatype.markup.MarkupMultiline;
19 import dev.metaschema.core.model.IAttributable;
20 import dev.metaschema.core.model.IBoundObject;
21 import dev.metaschema.core.model.ISource;
22 import dev.metaschema.core.model.constraint.AssemblyConstraintSet;
23 import dev.metaschema.core.model.constraint.IModelConstrained;
24 import dev.metaschema.core.model.constraint.IValueConstrained;
25 import dev.metaschema.core.util.CollectionUtil;
26 import dev.metaschema.core.util.ObjectUtils;
27 import dev.metaschema.databind.IBindingContext;
28 import dev.metaschema.databind.io.BindingException;
29 import dev.metaschema.databind.model.IBoundDefinitionModelFieldComplex;
30 import dev.metaschema.databind.model.IBoundFieldValue;
31 import dev.metaschema.databind.model.IBoundInstanceFlag;
32 import dev.metaschema.databind.model.IBoundModule;
33 import dev.metaschema.databind.model.IBoundProperty;
34 import dev.metaschema.databind.model.annotations.BoundFieldValue;
35 import dev.metaschema.databind.model.annotations.Ignore;
36 import dev.metaschema.databind.model.annotations.MetaschemaField;
37 import dev.metaschema.databind.model.annotations.ModelUtil;
38 import dev.metaschema.databind.model.annotations.ValueConstraints;
39 import edu.umd.cs.findbugs.annotations.NonNull;
40 import edu.umd.cs.findbugs.annotations.Nullable;
41 import nl.talsmasoftware.lazy4j.Lazy;
42
43
44
45
46 @SuppressWarnings("PMD.CouplingBetweenObjects")
47 public final class DefinitionField
48 extends AbstractBoundDefinitionModelComplex<MetaschemaField>
49 implements IBoundDefinitionModelFieldComplex {
50 @NonNull
51 private final FieldValue fieldValue;
52 @Nullable
53 private IBoundInstanceFlag jsonValueKeyFlagInstance;
54 @NonNull
55 private final Lazy<FlagContainerSupport> flagContainer;
56 @NonNull
57 private final Lazy<IValueConstrained> constraints;
58 @NonNull
59 private final Lazy<Map<String, IBoundProperty<?>>> jsonProperties;
60 @NonNull
61 private final Lazy<Map<IAttributable.Key, Set<String>>> properties;
62
63
64
65
66
67
68
69
70 @Nullable
71 private static Field getFieldValueField(Class<?> clazz) {
72 Field[] fields = clazz.getDeclaredFields();
73
74 Field retval = null;
75 for (Field field : fields) {
76 if (!field.isAnnotationPresent(BoundFieldValue.class) || field.isAnnotationPresent(Ignore.class)) {
77
78 continue;
79 }
80 retval = field;
81 }
82
83 if (retval == null) {
84 Class<?> superClass = clazz.getSuperclass();
85 if (superClass != null) {
86
87 retval = getFieldValueField(superClass);
88 }
89 }
90 return retval;
91 }
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 @NonNull
108 public static DefinitionField newInstance(
109 @NonNull Class<? extends IBoundObject> clazz,
110 @NonNull MetaschemaField annotation,
111 @NonNull IBoundModule module,
112 @NonNull IBindingContext bindingContext) {
113 return new DefinitionField(clazz, annotation, module, bindingContext);
114 }
115
116 private DefinitionField(
117 @NonNull Class<? extends IBoundObject> clazz,
118 @NonNull MetaschemaField annotation,
119 @NonNull IBoundModule module,
120 @NonNull IBindingContext bindingContext) {
121 super(clazz, annotation, module, bindingContext);
122 Field field = getFieldValueField(getBoundClass());
123 if (field == null) {
124 throw new IllegalArgumentException(
125 String.format("Class '%s' is missing the '%s' annotation on one of its fields.",
126 clazz.getName(),
127 BoundFieldValue.class.getName()));
128 }
129 FieldSupport.bindField(field);
130 this.fieldValue = new FieldValue(field, BoundFieldValue.class, bindingContext);
131 this.flagContainer = ObjectUtils.notNull(Lazy.of(() -> new FlagContainerSupport(this, this::handleFlagInstance)));
132
133 ISource source = module.getSource();
134
135 this.constraints = ObjectUtils.notNull(Lazy.of(() -> {
136 IModelConstrained retval = new AssemblyConstraintSet(source);
137 ValueConstraints valueAnnotation = getAnnotation().valueConstraints();
138 ConstraintSupport.parse(valueAnnotation, source, retval);
139 return retval;
140 }));
141 this.jsonProperties = ObjectUtils.notNull(Lazy.of(() -> {
142 IBoundInstanceFlag jsonValueKey = getJsonValueKeyFlagInstance();
143 Predicate<IBoundInstanceFlag> flagFilter = jsonValueKey == null ? null : flag -> !flag.equals(jsonValueKey);
144 return getJsonProperties(flagFilter);
145 }));
146 this.properties = ObjectUtils.notNull(
147 Lazy.of(() -> CollectionUtil.unmodifiableMap(ObjectUtils.notNull(
148 Arrays.stream(annotation.properties())
149 .map(ModelUtil::toPropertyEntry)
150 .collect(
151 Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2, LinkedHashMap::new))))));
152 }
153
154
155
156
157
158
159
160 protected void handleFlagInstance(@NonNull IBoundInstanceFlag instance) {
161 if (instance.isJsonValueKey()) {
162 this.jsonValueKeyFlagInstance = instance;
163 }
164 }
165
166 @Override
167 @NonNull
168 public FieldValue getFieldValue() {
169 return fieldValue;
170 }
171
172 @Override
173 public IBoundInstanceFlag getJsonValueKeyFlagInstance() {
174
175 getFlagContainer();
176 return jsonValueKeyFlagInstance;
177 }
178
179 @Override
180 protected void deepCopyItemInternal(IBoundObject fromObject, IBoundObject toObject) throws BindingException {
181
182 super.deepCopyItemInternal(fromObject, toObject);
183
184 getFieldValue().deepCopy(fromObject, toObject);
185 }
186
187
188
189
190
191 @Override
192 @SuppressWarnings("null")
193 @NonNull
194 public FlagContainerSupport getFlagContainer() {
195 return flagContainer.get();
196 }
197
198 @Override
199 @NonNull
200 public IValueConstrained getConstraintSupport() {
201 return ObjectUtils.notNull(constraints.get());
202 }
203
204 @Override
205 public Map<String, IBoundProperty<?>> getJsonProperties() {
206 return ObjectUtils.notNull(jsonProperties.get());
207 }
208
209 @Override
210 @Nullable
211 public String getFormalName() {
212 return ModelUtil.resolveNoneOrValue(getAnnotation().formalName());
213 }
214
215 @Override
216 @Nullable
217 public MarkupLine getDescription() {
218 return ModelUtil.resolveToMarkupLine(getAnnotation().description());
219 }
220
221 @Override
222 @NonNull
223 public String getName() {
224 return getAnnotation().name();
225 }
226
227 @Override
228 public Map<Key, Set<String>> getProperties() {
229 return ObjectUtils.notNull(properties.get());
230 }
231
232 @Override
233 @Nullable
234 public Integer getIndex() {
235 return ModelUtil.resolveDefaultInteger(getAnnotation().index());
236 }
237
238 @Override
239 @Nullable
240 public MarkupMultiline getRemarks() {
241 return ModelUtil.resolveToMarkupMultiline(getAnnotation().description());
242 }
243
244
245
246
247 protected class FieldValue
248 implements IBoundFieldValue {
249 @NonNull
250 private final Field javaField;
251 @NonNull
252 private final BoundFieldValue annotation;
253 @NonNull
254 private final IDataTypeAdapter<?> javaTypeAdapter;
255 @Nullable
256 private final Object defaultValue;
257
258
259
260
261
262
263
264
265
266
267
268 protected FieldValue(
269 @NonNull Field javaField,
270 @NonNull Class<BoundFieldValue> annotationClass,
271 @NonNull IBindingContext bindingContext) {
272 FieldSupport.bindField(javaField);
273 this.javaField = javaField;
274 this.annotation = ModelUtil.getAnnotation(javaField, annotationClass);
275 this.javaTypeAdapter = ModelUtil.getDataTypeAdapter(
276 this.annotation.typeAdapter(),
277 bindingContext);
278 this.defaultValue = ModelUtil.resolveDefaultValue(this.annotation.defaultValue(), this.javaTypeAdapter);
279 }
280
281
282
283
284
285
286 @Override
287 @NonNull
288 public Field getField() {
289 return javaField;
290 }
291
292
293
294
295
296
297 @NonNull
298 public BoundFieldValue getAnnotation() {
299 return annotation;
300 }
301
302 @Override
303 public IBoundDefinitionModelFieldComplex getParentFieldDefinition() {
304 return DefinitionField.this;
305 }
306
307 @Override
308 public String getJsonValueKeyName() {
309 String name = ModelUtil.resolveNoneOrValue(getAnnotation().valueKeyName());
310 return name == null ? getJavaTypeAdapter().getDefaultJsonValueKey() : name;
311 }
312
313 @Override
314 public String getJsonValueKeyFlagName() {
315 return ModelUtil.resolveNoneOrValue(getAnnotation().valueKeyName());
316 }
317
318 @Override
319 public Object getDefaultValue() {
320 return defaultValue;
321 }
322
323 @Override
324 public IDataTypeAdapter<?> getJavaTypeAdapter() {
325 return javaTypeAdapter;
326 }
327
328 @Override
329 public Object getEffectiveDefaultValue() {
330 return getDefaultValue();
331 }
332
333 @Override
334 public String getJsonName() {
335 return getEffectiveJsonValueKeyName();
336 }
337 }
338
339
340
341 }