1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package gov.nist.secauto.metaschema.databind.model.info;
28
29 import com.fasterxml.jackson.core.JsonParser;
30 import com.fasterxml.jackson.core.JsonToken;
31
32 import gov.nist.secauto.metaschema.core.model.util.JsonUtil;
33 import gov.nist.secauto.metaschema.core.model.util.XmlEventUtil;
34 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
35 import gov.nist.secauto.metaschema.databind.io.BindingException;
36 import gov.nist.secauto.metaschema.databind.io.json.IJsonParsingContext;
37 import gov.nist.secauto.metaschema.databind.io.json.IJsonWritingContext;
38 import gov.nist.secauto.metaschema.databind.io.xml.IXmlParsingContext;
39 import gov.nist.secauto.metaschema.databind.io.xml.IXmlWritingContext;
40 import gov.nist.secauto.metaschema.databind.model.IBoundFlagInstance;
41 import gov.nist.secauto.metaschema.databind.model.IBoundNamedModelInstance;
42 import gov.nist.secauto.metaschema.databind.model.IClassBinding;
43
44 import org.codehaus.stax2.XMLEventReader2;
45
46 import java.io.IOException;
47 import java.lang.reflect.ParameterizedType;
48 import java.util.Collection;
49 import java.util.LinkedHashMap;
50 import java.util.List;
51 import java.util.Map;
52
53 import javax.xml.namespace.QName;
54 import javax.xml.stream.XMLStreamException;
55 import javax.xml.stream.events.StartElement;
56 import javax.xml.stream.events.XMLEvent;
57
58 import edu.umd.cs.findbugs.annotations.NonNull;
59 import edu.umd.cs.findbugs.annotations.Nullable;
60 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
61
62 class MapPropertyInfo
63 extends AbstractModelPropertyInfo {
64
65 @SuppressWarnings("null")
66 @Override
67 public Collection<?> getItemsFromValue(Object value) {
68 return value == null ? List.of() : ((Map<?, ?>) value).values();
69 }
70
71 @Override
72 public int getItemCount(Object value) {
73 return value == null ? 0 : ((Map<?, ?>) value).size();
74 }
75
76 public MapPropertyInfo(
77 @NonNull IBoundNamedModelInstance property) {
78 super(property);
79 }
80
81 @Override
82 public boolean isJsonKeyRequired() {
83 return true;
84 }
85
86 @SuppressWarnings("null")
87 @NonNull
88 public Class<?> getKeyType() {
89 ParameterizedType actualType = (ParameterizedType) getProperty().getType();
90
91 return (Class<?>) actualType.getActualTypeArguments()[0];
92 }
93
94 @Override
95 public Class<?> getItemType() {
96 return getValueType();
97 }
98
99 @SuppressWarnings("null")
100 @NonNull
101 public Class<?> getValueType() {
102 ParameterizedType actualType = (ParameterizedType) getProperty().getType();
103
104 return (Class<?>) actualType.getActualTypeArguments()[1];
105 }
106
107 @Override
108 public IPropertyCollector newPropertyCollector() {
109 return new MapPropertyCollector();
110 }
111
112 @Override
113 public void readValues(IPropertyCollector collector, Object parentInstance, IJsonParsingContext context)
114 throws IOException {
115 @SuppressWarnings("resource")
116 JsonParser jsonParser = context.getReader();
117
118
119
120 JsonUtil.assertAndAdvance(jsonParser, JsonToken.START_OBJECT);
121
122
123 while (!JsonToken.END_OBJECT.equals(jsonParser.currentToken())) {
124
125
126 JsonUtil.assertCurrent(jsonParser, JsonToken.FIELD_NAME);
127
128 Object value = getProperty().getDataTypeHandler().readItem(parentInstance, context);
129 collector.add(value);
130
131
132
133
134 JsonUtil.assertCurrent(jsonParser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT);
135 }
136
137
138 JsonUtil.assertAndAdvance(jsonParser, JsonToken.END_OBJECT);
139 }
140
141 @Override
142 public boolean readValues(IPropertyCollector collector, Object parentInstance, StartElement start,
143 IXmlParsingContext context) throws IOException, XMLStreamException {
144 QName qname = getProperty().getXmlQName();
145 XMLEventReader2 eventReader = context.getReader();
146
147
148 XmlEventUtil.skipWhitespace(eventReader);
149
150 boolean handled = false;
151 XMLEvent event;
152 while ((event = eventReader.peek()).isStartElement() && qname.equals(event.asStartElement().getName())) {
153
154
155 Object value = context.readModelInstanceValue(getProperty(), parentInstance, start);
156 if (value != null) {
157 collector.add(value);
158 handled = true;
159 }
160
161
162 XmlEventUtil.skipWhitespace(eventReader);
163 }
164
165 return handled;
166 }
167
168 @Override
169 public void writeValues(Object value, QName parentName, IXmlWritingContext context)
170 throws XMLStreamException, IOException {
171 IBoundNamedModelInstance property = getProperty();
172 @SuppressWarnings("unchecked") Map<String, ? extends Object> items = (Map<String, ? extends Object>) value;
173 for (Object item : items.values()) {
174 context.writeInstanceValue(property, ObjectUtils.notNull(item), parentName);
175 }
176 }
177
178 @Override
179 public void writeValues(Object parentInstance, IJsonWritingContext context) throws IOException {
180 for (Object targetObject : getItemsFromParentInstance(parentInstance)) {
181 assert targetObject != null;
182 getProperty().getDataTypeHandler().writeItem(targetObject, context);
183 }
184 }
185
186 @Override
187 public boolean isValueSet(Object parentInstance) throws IOException {
188 Collection<? extends Object> items = getItemsFromParentInstance(parentInstance);
189 return !items.isEmpty();
190 }
191
192 @Override
193 public void copy(@NonNull Object fromInstance, @NonNull Object toInstance, @NonNull IPropertyCollector collector)
194 throws BindingException {
195 IBoundNamedModelInstance property = getProperty();
196
197 for (Object item : getItemsFromParentInstance(fromInstance)) {
198 collector.add(property.copyItem(ObjectUtils.requireNonNull(item), toInstance));
199 }
200 }
201
202 public class MapPropertyCollector implements IPropertyCollector {
203 @NonNull
204 private final Map<String, Object> map = new LinkedHashMap<>();
205 @Nullable
206 private final IBoundFlagInstance jsonKey;
207
208 protected MapPropertyCollector() {
209 IClassBinding classBinding = getProperty().getDataTypeHandler().getClassBinding();
210 this.jsonKey = classBinding == null ? null : classBinding.getJsonKeyFlagInstance();
211 if (this.jsonKey == null) {
212 throw new IllegalStateException("No JSON key found");
213 }
214 }
215
216 protected IBoundFlagInstance getJsonKey() {
217 return jsonKey;
218 }
219
220 @Override
221 public void add(Object item) {
222 assert item != null;
223
224
225 String key = getJsonKey().getValue(item).toString();
226 map.put(key, item);
227 }
228
229 @Override
230 public void addAll(Collection<?> items) {
231 for (Object item : items) {
232 add(ObjectUtils.requireNonNull(item));
233 }
234 }
235
236 @NonNull
237 @Override
238 @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "this is a data holder")
239 public Map<String, Object> getValue() {
240 return map;
241 }
242 }
243 }