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