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 @Override
87 public Iterable<ParameterWrapper> getDefaultInputParameters()
88 {
89 return EvalWrapper.this.defaultParamaters;
90 }
91
92 @Override
93 public Iterable<ParameterWrapper> getScenarioInputParameters()
94 {
95 return scenario == null ? null : EvalWrapper.this.scenarioParameters.get(scenario.getScenarioNode());
96 }
97 });
98 }
99 catch (RuntimeException ex)
100 {
101 this.dirty = false;
102 this.listeners.forEach((listener) -> listener.evalChanged());
103 throw ex;
104 }
105 this.dirty = false;
106 this.listeners.forEach((listener) -> listener.evalChanged());
107 }
108 return this.eval;
109 }
110
111
112
113
114
115 public Eval getLastValidEval()
116 {
117 if (this.eval == null)
118 {
119 return new Eval();
120 }
121 return this.eval;
122 }
123
124 @Override
125 public void notifyCreated(final XsdTreeNode node)
126 {
127 if (node.getPathString().equals(XsdPaths.SCENARIO))
128 {
129 this.scenarioParameters.put(node, new ArrayList<>());
130 setDirty();
131 }
132 else if (node.getPathString().equals(XsdPaths.INPUT_PARAMETERS)
133 || node.getPathString().equals(XsdPaths.DEFAULT_INPUT_PARAMETERS))
134 {
135 node.addListener(this, XsdTreeNode.ACTIVATION_CHANGED);
136 }
137 else if ((node.getPathString().startsWith(XsdPaths.INPUT_PARAMETERS + ".")
138 || node.getPathString().startsWith(XsdPaths.DEFAULT_INPUT_PARAMETERS + "."))
139 && !node.getNodeName().equals("xsd:choice"))
140 {
141 node.addListener(this, XsdTreeNode.ATTRIBUTE_CHANGED);
142 node.addListener(this, XsdTreeNode.VALUE_CHANGED);
143 node.addListener(this, XsdTreeNode.ACTIVATION_CHANGED);
144 registerParameter(node);
145 setDirty();
146 }
147 }
148
149 @Override
150 public void notifyRemoved(final XsdTreeNode node)
151 {
152 if (node.getPathString().equals(XsdPaths.SCENARIO))
153 {
154 this.scenarioParameters.remove(node);
155 setDirty();
156 }
157 else if (node.getPathString().equals(XsdPaths.INPUT_PARAMETERS)
158 || node.getPathString().equals(XsdPaths.DEFAULT_INPUT_PARAMETERS))
159 {
160 node.removeListener(this, XsdTreeNode.ACTIVATION_CHANGED);
161 }
162 else if (node.getPathString().startsWith(XsdPaths.DEFAULT_INPUT_PARAMETERS + "."))
163 {
164 this.defaultParamaters.remove(this.parameterMap.remove(node));
165 node.removeListener(this, XsdTreeNode.ATTRIBUTE_CHANGED);
166 node.removeListener(this, XsdTreeNode.VALUE_CHANGED);
167 setDirty();
168 }
169 else if (node.getPathString().startsWith(XsdPaths.INPUT_PARAMETERS + "."))
170 {
171 this.scenarioParameters.forEach((s, list) -> list.remove(this.parameterMap.remove(node)));
172 node.removeListener(this, XsdTreeNode.ATTRIBUTE_CHANGED);
173 node.removeListener(this, XsdTreeNode.VALUE_CHANGED);
174 setDirty();
175 }
176 }
177
178 @Override
179 public void notify(final Event event) throws RemoteException
180 {
181 if (event.getType().equals(XsdTreeNode.ATTRIBUTE_CHANGED) || event.getType().equals(XsdTreeNode.VALUE_CHANGED))
182 {
183 XsdTreeNode node = (XsdTreeNode) ((Object[]) event.getContent())[0];
184 registerParameter(node);
185 setDirty();
186 }
187 else if (event.getType().equals(XsdTreeNode.ACTIVATION_CHANGED))
188 {
189 Object[] content = (Object[]) event.getContent();
190 XsdTreeNode node = (XsdTreeNode) content[0];
191 boolean activated = (boolean) content[1];
192 if (node.getPathString().equals(XsdPaths.INPUT_PARAMETERS)
193 || node.getPathString().equals(XsdPaths.DEFAULT_INPUT_PARAMETERS))
194 {
195 if (activated)
196 {
197 node.getChildren().forEach((child) -> notifyCreated(child));
198 }
199 else
200 {
201 node.getChildren().forEach((child) -> notifyRemoved(child));
202 }
203 }
204 else if ((node.getPathString().startsWith(XsdPaths.INPUT_PARAMETERS + ".")
205 || node.getPathString().startsWith(XsdPaths.DEFAULT_INPUT_PARAMETERS + "."))
206 && !node.getNodeName().equals("xsd:choice"))
207 {
208 if (activated)
209 {
210 notifyCreated(node);
211 }
212 else
213 {
214 notifyRemoved(node);
215 }
216 }
217 }
218 else
219 {
220 super.notify(event);
221 }
222 }
223
224
225
226
227
228 private void registerParameter(final XsdTreeNode node)
229 {
230 if (node.getPathString().startsWith(XsdPaths.DEFAULT_INPUT_PARAMETERS + "."))
231 {
232 this.defaultParamaters.remove(this.parameterMap.remove(node));
233 if (node.isValid())
234 {
235 ParameterWrapper parameter = wrap(node);
236 this.parameterMap.put(node, parameter);
237 this.defaultParamaters.add(parameter);
238 }
239 }
240 else if (node.getPathString().startsWith(XsdPaths.INPUT_PARAMETERS + "."))
241 {
242 this.scenarioParameters.forEach((s, list) -> list.remove(this.parameterMap.remove(node)));
243 if (node.isValid())
244 {
245 ParameterWrapper parameter = wrap(node);
246 this.parameterMap.put(node, parameter);
247 XsdTreeNode scenarioNode = node.getParent().getParent();
248 if (scenarioNode != null)
249 {
250 this.scenarioParameters.get(scenarioNode).add(parameter);
251 }
252 }
253 }
254 }
255
256
257
258
259 public void setDirty()
260 {
261 this.dirty = true;
262 this.listeners.forEach((listener) -> listener.evalChanged());
263 }
264
265
266
267
268
269 public void addListener(final EvalListener listener)
270 {
271 this.listeners.add(listener);
272 }
273
274
275
276
277
278 public void removeListener(final EvalListener listener)
279 {
280 this.listeners.remove(listener);
281 }
282
283
284
285
286
287
288 private ParameterWrapper wrap(final XsdTreeNode node)
289 {
290 try
291 {
292 Class<?> clazz = Class.forName(String.format(ADAPTER_MASK, node.getNodeName()));
293 Constructor<?> constructor = ClassUtil.resolveConstructor(clazz, new Object[0]);
294 ExpressionAdapter<?, ?> adapter = (ExpressionAdapter<?, ?>) constructor.newInstance();
295 return new ParameterWrapper(node.getId(), adapter.unmarshal(node.getValue()));
296 }
297 catch (Exception e)
298 {
299 throw new RuntimeException("Unable to wrap node " + node + " as a parameter for Eval.");
300 }
301 }
302
303
304
305
306
307
308
309
310
311
312 @FunctionalInterface
313 public interface EvalListener
314 {
315
316
317
318 public void evalChanged();
319 }
320
321 }