1 package org.opentrafficsim.editor.decoration.validation;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.LinkedHashMap;
6 import java.util.LinkedHashSet;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Map.Entry;
10 import java.util.Set;
11
12 import org.djutils.exceptions.Throw;
13 import org.opentrafficsim.editor.DocumentReader;
14 import org.opentrafficsim.editor.XsdTreeNode;
15 import org.w3c.dom.Node;
16
17
18
19
20
21
22
23
24
25
26 public class KeyrefValidator extends XPathValidator implements CoupledValidator
27 {
28
29
30 private final KeyValidator refer;
31
32
33 private final Set<XsdTreeNode> valueValidating = new LinkedHashSet<>();
34
35
36 private final Map<String, Set<XsdTreeNode>> attributeValidating = new LinkedHashMap<>();
37
38
39 private final Map<XsdTreeNode, XsdTreeNode> coupledKeyrefNodes = new LinkedHashMap<>();
40
41
42
43
44
45
46
47 public KeyrefValidator(final Node keyNode, final String keyPath, final KeyValidator refer)
48 {
49 super(keyNode, keyPath);
50 Throw.when(!keyNode.getNodeName().equals("xsd:keyref"), IllegalArgumentException.class,
51 "The given node is not an xsd:keyref node.");
52 Throw.whenNull(refer, "Refer validator may not be null.");
53 String referName = DocumentReader.getAttribute(keyNode, "refer").replace("ots:", "");
54 Throw.when(!referName.equals(refer.getKeyName()), IllegalArgumentException.class,
55 "The key node refers to key/unique %s, but the provided refer validator has name %s.", referName,
56 refer.getKeyName());
57 this.refer = refer;
58 refer.addListeningKeyrefValidator(this);
59 }
60
61
62 @Override
63 public void addNode(final XsdTreeNode node)
64 {
65 for (int fieldIndex = 0; fieldIndex < this.fields.size(); fieldIndex++)
66 {
67 Field field = this.fields.get(fieldIndex);
68 int pathIndex = field.getValidPathIndex(node);
69 if (pathIndex >= 0)
70 {
71 String path = field.getFieldPath(pathIndex);
72 int attr = path.indexOf("@");
73 if (attr < 0)
74 {
75 node.addValueValidator(this, field);
76 this.valueValidating.add(node);
77 }
78 else
79 {
80 String attribute = path.substring(attr + 1);
81 node.addAttributeValidator(attribute, this, field);
82 this.attributeValidating.computeIfAbsent(attribute, (n) -> new LinkedHashSet<>()).add(node);
83 }
84 }
85 }
86 }
87
88
89 @Override
90 public void removeNode(final XsdTreeNode node)
91 {
92 this.valueValidating.remove(node);
93 this.attributeValidating.values().forEach((s) -> s.remove(node));
94 this.coupledKeyrefNodes.remove(node);
95 }
96
97
98 @Override
99 public String validate(final XsdTreeNode node)
100 {
101 if (node.getParent() == null)
102 {
103 return null;
104 }
105 List<String> values = gatherFieldValues(node);
106 if (values.stream().allMatch((v) -> v == null))
107 {
108 return null;
109 }
110
111 Map<XsdTreeNode, List<String>> valueMap = this.refer.getAllValueSets(node);
112 XsdTreeNode matched = null;
113 for (Entry<XsdTreeNode, List<String>> entry : valueMap.entrySet())
114 {
115 if (matchingKeyref(entry.getValue(), values))
116 {
117 if (matched != null)
118 {
119
120 this.coupledKeyrefNodes.remove(node);
121 return null;
122 }
123 matched = entry.getKey();
124 }
125 }
126 if (matched != null)
127 {
128 this.coupledKeyrefNodes.put(node, matched);
129 return null;
130 }
131
132 this.coupledKeyrefNodes.remove(node);
133 String[] types = this.refer.getSelectorTypeString();
134 String typeString = user(types.length == 1 ? types[0] : Arrays.asList(types).toString());
135 if (values.size() == 1)
136 {
137 String value = values.get(0);
138 String name = user(this.fields.get(0).getFullFieldName());
139 return "Value " + value + " for " + name + " does not refer to a known and unique " + typeString + " within "
140 + this.keyPath + ".";
141 }
142 values.removeIf((value) -> value != null && value.startsWith("{") && value.endsWith("}"));
143 return "Values " + values + " do not refer to a known and unique " + typeString + " within " + this.keyPath + ".";
144 }
145
146
147
148
149
150
151
152
153
154 private boolean matchingKeyref(final List<String> keyValues, final List<String> keyrefValues)
155 {
156 for (int i = 0; i < keyValues.size(); i++)
157 {
158 if (keyrefValues.get(i) != null && !keyrefValues.get(i).equals(keyValues.get(i)))
159 {
160 return false;
161 }
162 }
163 return true;
164 }
165
166
167 @Override
168 public List<String> getOptions(final XsdTreeNode node, final Object field)
169 {
170
171
172
173
174
175
176 XsdTreeNode contextKeyref = getContext(node);
177 XsdTreeNode contextKey = this.refer.getContext(node);
178 boolean uniqueScope = contextKeyref.equals(contextKey);
179 if (!uniqueScope)
180 {
181 List<XsdTreeNode> contextKeyPath = contextKey.getPath();
182 if (contextKeyPath.contains(contextKeyref))
183 {
184 contextKeyPath.removeAll(contextKeyref.getPath());
185 Set<XsdTreeNode> containedKeyScopes = new LinkedHashSet<>();
186 gatherScopes(contextKeyref, contextKeyPath, containedKeyScopes);
187 uniqueScope = containedKeyScopes.size() == 1;
188 }
189 }
190 if (!uniqueScope)
191 {
192 return null;
193 }
194 Map<XsdTreeNode, List<String>> values = this.refer.getAllValueSets(node);
195 List<String> result = new ArrayList<>(values.size());
196 int index = this.fields.indexOf(field);
197 values.forEach((n, list) -> result.add(list.get(index)));
198 result.removeIf((v) -> v == null || v.isEmpty());
199 return result;
200 }
201
202
203
204
205
206
207
208 private void gatherScopes(final XsdTreeNode node, final List<XsdTreeNode> remainingPath, final Set<XsdTreeNode> set)
209 {
210 String path = node.getPathString() + "." + remainingPath.get(0);
211 for (XsdTreeNode child : node.getChildren())
212 {
213 if (child.getPathString().equals(path))
214 {
215 if (remainingPath.size() == 1)
216 {
217 set.add(child);
218 }
219 else
220 {
221 gatherScopes(child, remainingPath.subList(1, remainingPath.size()), set);
222 }
223 }
224 }
225 }
226
227
228 @Override
229 public XsdTreeNode getCoupledKeyrefNode(final XsdTreeNode node)
230 {
231 return this.coupledKeyrefNodes.get(node);
232 }
233
234
235
236
237
238
239
240 public void updateFieldValue(final XsdTreeNode node, final int fieldIndex, final String newValue)
241 {
242 for (Entry<XsdTreeNode, XsdTreeNode> entry : this.coupledKeyrefNodes.entrySet())
243 {
244 if (entry.getValue().equals(node))
245 {
246 if (this.valueValidating.contains(entry.getKey()))
247 {
248 CoupledValidator.setValueIfNotNull(entry.getKey(), newValue);
249 }
250 else
251 {
252 for (Entry<String, Set<XsdTreeNode>> attrEntry : this.attributeValidating.entrySet())
253 {
254 if (attrEntry.getValue().contains(entry.getKey()))
255 {
256 int index = this.fields.get(fieldIndex).getValidPathIndex(entry.getKey());
257 if (index >= 0)
258 {
259 String field = this.fields.get(fieldIndex).getFieldPath(index);
260 int attr = field.indexOf("@");
261 String attribute = field.substring(attr + 1);
262 CoupledValidator.setAttributeIfNotNull(entry.getKey(), attribute, newValue);
263 }
264 }
265 }
266 }
267 }
268 }
269 }
270
271
272
273
274
275 public void invalidateNodes()
276 {
277 for (XsdTreeNode node : this.valueValidating)
278 {
279 node.invalidate();
280 }
281 for (Set<XsdTreeNode> set : this.attributeValidating.values())
282 {
283 for (XsdTreeNode node : set)
284 {
285 node.invalidate();
286 }
287 }
288 }
289
290 }