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