1 package org.opentrafficsim.editor;
2
3 import java.lang.reflect.Constructor;
4 import java.util.ArrayList;
5 import java.util.LinkedHashMap;
6 import java.util.LinkedHashSet;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Objects;
10 import java.util.Set;
11
12 import org.djutils.eval.Eval;
13 import org.djutils.event.Event;
14 import org.djutils.event.reference.ReferenceType;
15 import org.djutils.reflection.ClassUtil;
16 import org.opentrafficsim.base.logger.Logger;
17 import org.opentrafficsim.editor.decoration.AbstractNodeDecoratorRemove;
18 import org.opentrafficsim.road.network.factory.xml.CircularDependencyException;
19 import org.opentrafficsim.road.network.factory.xml.OtsEval;
20 import org.opentrafficsim.road.network.factory.xml.parser.ScenarioParser;
21 import org.opentrafficsim.road.network.factory.xml.parser.ScenarioParser.ParameterWrapper;
22 import org.opentrafficsim.road.network.factory.xml.parser.ScenarioParser.ScenariosWrapper;
23 import org.opentrafficsim.xml.bindings.ExpressionAdapter;
24
25
26
27
28
29
30
31
32
33
34
35 public class EvalWrapper extends AbstractNodeDecoratorRemove
36 {
37
38
39 private static final String ADAPTER_MASK = "org.opentrafficsim.xml.bindings.%sAdapter";
40
41
42 private boolean dirty = true;
43
44
45 private ScenarioWrapper lastScenario;
46
47
48 private Eval eval;
49
50
51 private boolean ignoreEval = false;
52
53
54 private final List<ParameterWrapper> defaultParamaters = new ArrayList<>();
55
56
57 private final Map<XsdTreeNode, List<ParameterWrapper>> scenarioParameters = new LinkedHashMap<>();
58
59
60 private final Map<XsdTreeNode, ParameterWrapper> parameterMap = new LinkedHashMap<>();
61
62
63 private final Set<EvalListener> listeners = new LinkedHashSet<>();
64
65
66 private final OtsEditor editor;
67
68
69
70
71
72 public EvalWrapper(final OtsEditor editor)
73 {
74 super(editor, (n) -> true);
75 this.editor = editor;
76 }
77
78
79
80
81
82
83 public Eval getEval(final ScenarioWrapper scenario)
84 {
85 boolean becomesDirty = this.dirty || !Objects.equals(this.lastScenario, scenario);
86 if (becomesDirty)
87 {
88 this.lastScenario = scenario;
89 try
90 {
91 this.eval = ScenarioParser.parseInputParameters(new ScenariosWrapper()
92 {
93 @Override
94 public Iterable<ParameterWrapper> getDefaultInputParameters()
95 {
96 return EvalWrapper.this.defaultParamaters;
97 }
98
99 @Override
100 public Iterable<ParameterWrapper> getScenarioInputParameters()
101 {
102 return scenario == null ? null : EvalWrapper.this.scenarioParameters.get(scenario.scenarioNode());
103 }
104 });
105 }
106 catch (CircularDependencyException ex)
107 {
108 throw ex;
109 }
110 catch (RuntimeException ex)
111 {
112 this.editor.dialogs().showInvalidExpression(ex.getMessage());
113 return null;
114 }
115 this.dirty = false;
116 this.listeners.forEach((listener) -> listener.evalChanged());
117 }
118 return this.eval;
119 }
120
121
122
123
124
125 public Eval getLastValidEval()
126 {
127 if (this.eval == null)
128 {
129 return new OtsEval();
130 }
131 return this.eval;
132 }
133
134 @Override
135 public void notifyCreated(final XsdTreeNode node)
136 {
137 if (node.getPathString().equals(XsdPaths.SCENARIO))
138 {
139 this.scenarioParameters.put(node, new ArrayList<>());
140 setDirty();
141 }
142 else if (node.getPathString().equals(XsdPaths.INPUT_PARAMETERS)
143 || node.getPathString().equals(XsdPaths.DEFAULT_INPUT_PARAMETERS))
144 {
145 node.addListener(this, XsdTreeNode.ACTIVATION_CHANGED, ReferenceType.WEAK);
146 }
147 else if ((node.getPathString().startsWith(XsdPaths.INPUT_PARAMETERS + ".")
148 || node.getPathString().startsWith(XsdPaths.DEFAULT_INPUT_PARAMETERS + "."))
149 && !node.getNodeName().equals("xsd:choice"))
150 {
151 node.addListener(this, XsdTreeNode.ATTRIBUTE_CHANGED, ReferenceType.WEAK);
152 node.addListener(this, XsdTreeNode.VALUE_CHANGED, ReferenceType.WEAK);
153 node.addListener(this, XsdTreeNode.ACTIVATION_CHANGED, ReferenceType.WEAK);
154 registerParameter(node);
155 setDirty();
156 }
157 }
158
159 @Override
160 public void notifyRemoved(final XsdTreeNode node)
161 {
162 if (node.getPathString().equals(XsdPaths.SCENARIO))
163 {
164 this.scenarioParameters.remove(node);
165 setDirty();
166 }
167 else if (node.getPathString().equals(XsdPaths.INPUT_PARAMETERS)
168 || node.getPathString().equals(XsdPaths.DEFAULT_INPUT_PARAMETERS))
169 {
170 node.removeListener(this, XsdTreeNode.ACTIVATION_CHANGED);
171 }
172 else if (node.getPathString().startsWith(XsdPaths.DEFAULT_INPUT_PARAMETERS + "."))
173 {
174 this.defaultParamaters.remove(this.parameterMap.remove(node));
175 node.removeListener(this, XsdTreeNode.ATTRIBUTE_CHANGED);
176 node.removeListener(this, XsdTreeNode.VALUE_CHANGED);
177 setDirty();
178 }
179 else if (node.getPathString().startsWith(XsdPaths.INPUT_PARAMETERS + "."))
180 {
181 this.scenarioParameters.forEach((s, list) -> list.remove(this.parameterMap.remove(node)));
182 node.removeListener(this, XsdTreeNode.ATTRIBUTE_CHANGED);
183 node.removeListener(this, XsdTreeNode.VALUE_CHANGED);
184 setDirty();
185 }
186 }
187
188 @Override
189 public void notify(final Event event)
190 {
191 if (event.getType().equals(XsdTreeNode.ATTRIBUTE_CHANGED) || event.getType().equals(XsdTreeNode.VALUE_CHANGED))
192 {
193 XsdTreeNode node = (XsdTreeNode) ((Object[]) event.getContent())[0];
194 registerParameter(node);
195 setDirty();
196 }
197 else if (event.getType().equals(XsdTreeNode.ACTIVATION_CHANGED))
198 {
199 Object[] content = (Object[]) event.getContent();
200 XsdTreeNode node = (XsdTreeNode) content[0];
201 boolean activated = (boolean) content[1];
202 if (node.getPathString().equals(XsdPaths.INPUT_PARAMETERS)
203 || node.getPathString().equals(XsdPaths.DEFAULT_INPUT_PARAMETERS))
204 {
205
206 if (activated)
207 {
208 node.getChildren().forEach((child) -> notifyCreated(child));
209 }
210 else
211 {
212 node.getChildren().forEach((child) -> notifyRemoved(child));
213 }
214 }
215 else if ((node.getPathString().startsWith(XsdPaths.INPUT_PARAMETERS + ".")
216 || node.getPathString().startsWith(XsdPaths.DEFAULT_INPUT_PARAMETERS + "."))
217 && !node.getNodeName().equals("xsd:choice"))
218 {
219 if (activated)
220 {
221 notifyCreated(node);
222 }
223 else
224 {
225 notifyRemoved(node);
226 }
227 }
228 }
229 else
230 {
231 super.notify(event);
232 }
233 }
234
235
236
237
238
239 private void registerParameter(final XsdTreeNode node)
240 {
241 if (node.getPathString().startsWith(XsdPaths.DEFAULT_INPUT_PARAMETERS + "."))
242 {
243 this.defaultParamaters.remove(this.parameterMap.remove(node));
244 if (node.isValid())
245 {
246 ParameterWrapper parameter = wrap(node);
247 if (parameter != null)
248 {
249 this.parameterMap.put(node, parameter);
250 this.defaultParamaters.add(parameter);
251 }
252 }
253 }
254 else if (node.getPathString().startsWith(XsdPaths.INPUT_PARAMETERS + "."))
255 {
256 this.scenarioParameters.forEach((s, list) -> list.remove(this.parameterMap.remove(node)));
257 if (node.isValid())
258 {
259 ParameterWrapper parameter = wrap(node);
260 this.parameterMap.put(node, parameter);
261 XsdTreeNode scenarioNode = node.getParent().getParent();
262 if (scenarioNode != null)
263 {
264 this.scenarioParameters.get(scenarioNode).add(parameter);
265 }
266 }
267 }
268 }
269
270
271
272
273
274 public void setIgnoreEval(final boolean ignoreEval)
275 {
276 if (this.ignoreEval && !ignoreEval)
277 {
278 this.ignoreEval = false;
279 setDirty();
280 return;
281 }
282 this.ignoreEval = ignoreEval;
283 }
284
285
286
287
288 public void setDirty()
289 {
290 if (this.ignoreEval)
291 {
292 return;
293 }
294 this.dirty = true;
295 this.listeners.forEach((listener) -> listener.evalChanged());
296 }
297
298
299
300
301
302 public void addListener(final EvalListener listener)
303 {
304 this.listeners.add(listener);
305 }
306
307
308
309
310
311 public void removeListener(final EvalListener listener)
312 {
313 this.listeners.remove(listener);
314 }
315
316
317
318
319
320
321 private ParameterWrapper wrap(final XsdTreeNode node)
322 {
323 try
324 {
325 Class<?> clazz = Class.forName(String.format(ADAPTER_MASK, node.getNodeName()));
326 Constructor<?> constructor = ClassUtil.resolveConstructor(clazz, new Object[0]);
327 ExpressionAdapter<?, ?> adapter = (ExpressionAdapter<?, ?>) constructor.newInstance();
328 return new ParameterWrapper(node.getId(), adapter.unmarshal(node.getValueOrDefault()));
329 }
330 catch (Exception e)
331 {
332 Logger.ots().trace("Unable to wrap node {} as a parameter for Eval.", node);
333 return null;
334 }
335 }
336
337
338
339
340
341
342
343
344
345
346 @FunctionalInterface
347 public interface EvalListener
348 {
349
350
351
352 void evalChanged();
353 }
354
355 }