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