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.Optional;
13 import java.util.Set;
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.AbstractInlineFieldDefinition;
20 import dev.metaschema.core.model.IAttributable;
21 import dev.metaschema.core.model.IModule;
22 import dev.metaschema.core.model.ISource;
23 import dev.metaschema.core.model.constraint.IValueConstrained;
24 import dev.metaschema.core.model.constraint.ValueConstraintSet;
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.model.IBoundDefinitionModelAssembly;
29 import dev.metaschema.databind.model.IBoundDefinitionModelField;
30 import dev.metaschema.databind.model.IBoundInstanceFlag;
31 import dev.metaschema.databind.model.IBoundInstanceModelFieldScalar;
32 import dev.metaschema.databind.model.IBoundModule;
33 import dev.metaschema.databind.model.IGroupAs;
34 import dev.metaschema.databind.model.annotations.BoundField;
35 import dev.metaschema.databind.model.annotations.GroupAs;
36 import dev.metaschema.databind.model.annotations.ModelUtil;
37 import dev.metaschema.databind.model.annotations.ValueConstraints;
38 import dev.metaschema.databind.model.info.IModelInstanceCollectionInfo;
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
47
48
49
50
51
52
53 public final class InstanceModelFieldScalar
54 extends AbstractInlineFieldDefinition<
55 IBoundDefinitionModelAssembly,
56 IBoundDefinitionModelField<Object>,
57 IBoundInstanceModelFieldScalar,
58 IBoundDefinitionModelAssembly,
59 IBoundInstanceFlag>
60 implements IBoundInstanceModelFieldScalar, IFeatureInstanceModelGroupAs {
61 @NonNull
62 private final Field javaField;
63 @NonNull
64 private final BoundField annotation;
65 @NonNull
66 private final Lazy<IModelInstanceCollectionInfo<Object>> collectionInfo;
67 @NonNull
68 private final IGroupAs groupAs;
69 @NonNull
70 private final IDataTypeAdapter<?> javaTypeAdapter;
71 @Nullable
72 private final Object defaultValue;
73 @NonNull
74 private final Lazy<IValueConstrained> constraints;
75 @NonNull
76 private final Lazy<Map<IAttributable.Key, Set<String>>> properties;
77
78
79
80
81
82
83
84
85
86
87 @NonNull
88 public static InstanceModelFieldScalar newInstance(
89 @NonNull Field javaField,
90 @NonNull IBoundDefinitionModelAssembly parent) {
91 BoundField annotation = ModelUtil.getAnnotation(javaField, BoundField.class);
92 IGroupAs groupAs = ModelUtil.resolveDefaultGroupAs(
93 annotation.groupAs(),
94 parent.getContainingModule());
95
96 if (annotation.maxOccurs() == -1 || annotation.maxOccurs() > 1) {
97 if (IGroupAs.SINGLETON_GROUP_AS.equals(groupAs)) {
98 throw new IllegalStateException(String.format("Field '%s' on class '%s' is missing the '%s' annotation.",
99 javaField.getName(),
100 javaField.getDeclaringClass().getName(),
101 GroupAs.class.getName()));
102 }
103 } else if (!IGroupAs.SINGLETON_GROUP_AS.equals(groupAs)) {
104
105 throw new IllegalStateException(
106 String.format(
107 "Field '%s' on class '%s' has the '%s' annotation, but maxOccurs=1. A groupAs must not be specfied.",
108 javaField.getName(),
109 javaField.getDeclaringClass().getName(),
110 GroupAs.class.getName()));
111 }
112
113 return new InstanceModelFieldScalar(
114 javaField,
115 annotation,
116 groupAs,
117 parent);
118 }
119
120 private InstanceModelFieldScalar(
121 @NonNull Field javaField,
122 @NonNull BoundField annotation,
123 @NonNull IGroupAs groupAs,
124 @NonNull IBoundDefinitionModelAssembly parent) {
125 super(parent);
126 FieldSupport.bindField(javaField);
127 this.javaField = javaField;
128 this.annotation = annotation;
129 this.collectionInfo = ObjectUtils.notNull(Lazy.of(() -> IModelInstanceCollectionInfo.of(this)));
130 this.groupAs = groupAs;
131 this.javaTypeAdapter = ModelUtil.getDataTypeAdapter(
132 annotation.typeAdapter(),
133 parent.getBindingContext());
134 this.defaultValue = ModelUtil.resolveDefaultValue(annotation.defaultValue(), this.javaTypeAdapter);
135
136 IModule module = getContainingModule();
137 ISource source = module.getSource();
138
139 this.constraints = ObjectUtils.notNull(Lazy.of(() -> {
140 IValueConstrained retval = new ValueConstraintSet(source);
141 ValueConstraints valueAnnotation = annotation.valueConstraints();
142 ConstraintSupport.parse(valueAnnotation, module.getSource(), retval);
143 return retval;
144 }));
145 this.properties = ObjectUtils.notNull(
146 Lazy.of(() -> CollectionUtil.unmodifiableMap(ObjectUtils.notNull(
147 Arrays.stream(annotation.properties())
148 .map(ModelUtil::toPropertyEntry)
149 .collect(
150 Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2, LinkedHashMap::new))))));
151 }
152
153
154
155
156
157 @Override
158 public IBindingContext getBindingContext() {
159 return getContainingDefinition().getBindingContext();
160 }
161
162 @Override
163 public IBoundModule getContainingModule() {
164 return getContainingDefinition().getContainingModule();
165 }
166
167 @Override
168 public Field getField() {
169 return javaField;
170 }
171
172
173
174
175
176
177 @NonNull
178 public BoundField getAnnotation() {
179 return annotation;
180 }
181
182 @SuppressWarnings("null")
183 @Override
184 public IModelInstanceCollectionInfo<Object> getCollectionInfo() {
185 return collectionInfo.get();
186 }
187
188 @SuppressWarnings("null")
189 @Override
190 @NonNull
191 public IValueConstrained getConstraintSupport() {
192 return constraints.get();
193 }
194
195 @Override
196 public IDataTypeAdapter<?> getJavaTypeAdapter() {
197 return javaTypeAdapter;
198 }
199
200 @Override
201 public Object getDefaultValue() {
202 return defaultValue;
203 }
204
205 @Override
206 public IGroupAs getGroupAs() {
207 return groupAs;
208 }
209
210 @Override
211 public String getFormalName() {
212 return ModelUtil.resolveNoneOrValue(getAnnotation().formalName());
213 }
214
215 @Override
216 public MarkupLine getDescription() {
217 return ModelUtil.resolveToMarkupLine(getAnnotation().description());
218 }
219
220 @Override
221 public Integer getUseIndex() {
222 int value = getAnnotation().useIndex();
223 return value == Integer.MIN_VALUE ? null : value;
224 }
225
226 @Override
227 public boolean isInXmlWrapped() {
228 return getAnnotation().inXmlWrapped();
229 }
230
231 @Override
232 public int getMinOccurs() {
233 return getAnnotation().minOccurs();
234 }
235
236 @Override
237 public int getMaxOccurs() {
238 return getAnnotation().maxOccurs();
239 }
240
241 @Override
242 public Map<Key, Set<String>> getProperties() {
243 return ObjectUtils.notNull(properties.get());
244 }
245
246 @Override
247 public MarkupMultiline getRemarks() {
248 return ModelUtil.resolveToMarkupMultiline(getAnnotation().remarks());
249 }
250
251 @Override
252 public String getName() {
253
254
255 return ObjectUtils.notNull(
256 Optional.ofNullable(ModelUtil.resolveNoneOrValue(getAnnotation().useName())).orElse(getField().getName()));
257 }
258
259
260
261
262 }