1 package org.opentrafficsim.trafficcontrol.trafcod;
2
3 import java.awt.Container;
4 import java.awt.geom.Point2D;
5 import java.awt.image.BufferedImage;
6 import java.io.BufferedReader;
7 import java.io.IOException;
8 import java.io.InputStreamReader;
9 import java.net.URL;
10 import java.util.ArrayList;
11 import java.util.EnumSet;
12 import java.util.LinkedHashMap;
13 import java.util.LinkedHashSet;
14 import java.util.List;
15 import java.util.Locale;
16 import java.util.Map;
17 import java.util.Optional;
18 import java.util.Set;
19
20 import javax.swing.JPanel;
21
22 import org.djunits.unit.DurationUnit;
23 import org.djunits.value.formatter.EngineeringFormatter;
24 import org.djunits.value.vdouble.scalar.Duration;
25 import org.djutils.event.Event;
26 import org.djutils.event.EventListener;
27 import org.djutils.event.EventType;
28 import org.djutils.exceptions.Throw;
29 import org.djutils.exceptions.Try;
30 import org.djutils.immutablecollections.ImmutableCollection;
31 import org.opentrafficsim.base.logger.Logger;
32 import org.opentrafficsim.core.dsol.OtsModelInterface;
33 import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
34 import org.opentrafficsim.core.network.Network;
35 import org.opentrafficsim.core.network.NetworkException;
36 import org.opentrafficsim.core.object.LocatedObject;
37 import org.opentrafficsim.road.network.lane.object.detector.TrafficLightDetector;
38 import org.opentrafficsim.road.network.lane.object.detector.TrafficLightDetector.StartEndDetector;
39 import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight;
40 import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLightColor;
41 import org.opentrafficsim.trafficcontrol.AbstractTrafficController;
42 import org.opentrafficsim.trafficcontrol.ActuatedTrafficController;
43 import org.opentrafficsim.trafficcontrol.TrafficControlException;
44 import org.opentrafficsim.trafficcontrol.TrafficController;
45
46 import nl.tudelft.simulation.dsol.SimRuntimeException;
47
48
49
50
51
52
53
54
55
56
57 public class TrafCod extends AbstractTrafficController implements ActuatedTrafficController, EventListener
58 {
59
60 static final int TrafCod_VERSION = 100;
61
62
63 static final Duration EVALUATION_INTERVAL = new Duration(0.1, DurationUnit.SECOND);
64
65
66 private static final String VERSION_PREFIX = "trafcod-version=";
67
68
69 private static final String SEQUENCE_KEY = "Sequence";
70
71
72 private static final String STRUCTURE_PREFIX = "Structure:";
73
74
75 private final List<Object[]> tokenisedRules = new ArrayList<>();
76
77
78 private final Map<String, Variable> variables = new LinkedHashMap<>();
79
80
81 private final List<Variable> variablesInDefinitionOrder = new ArrayList<>();
82
83
84 private final Map<String, Variable> detectors = new LinkedHashMap<>();
85
86
87 static final String COMMENT_PREFIX = "#";
88
89
90 private static final String INIT_PREFIX = "%init ";
91
92
93 private static final String TIME_PREFIX = "%time ";
94
95
96 private static final String EXPORT_PREFIX = "%export ";
97
98
99 private int numberOfConflictGroups = -1;
100
101
102 private int conflictGroupSize = -1;
103
104
105 private int structureNumber = -1;
106
107
108 private List<List<Short>> conflictGroups = new ArrayList<List<Short>>();
109
110
111 private int maxLoopCount = 10;
112
113
114 private int currentToken;
115
116
117 private List<Integer> stack = new ArrayList<Integer>();
118
119
120 private Object[] currentRule;
121
122
123 private int currentTime10 = 0;
124
125
126 private final List<String> trafCODRules;
127
128
129 private final Container displayContainer = new JPanel();
130
131
132 private final BufferedImage displayBackground;
133
134
135 private final List<String> displayObjectLocations;
136
137
138 private TrafCodDisplay stateDisplay = null;
139
140
141 private final OtsSimulatorInterface simulator;
142
143
144 private String currentConflictGroup = "";
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159 public TrafCod(final String controllerName, final URL trafCodURL, final OtsSimulatorInterface simulator,
160 final Container display, final BufferedImage displayBackground, final List<String> displayObjectLocations)
161 throws TrafficControlException, SimRuntimeException, IOException
162 {
163 this(controllerName, loadTextFromURL(trafCodURL), simulator, displayBackground, displayObjectLocations);
164 }
165
166
167
168
169
170
171
172
173
174
175
176
177 public TrafCod(final String controllerName, final List<String> trafCODRules, final OtsSimulatorInterface simulator,
178 final BufferedImage displayBackground, final List<String> displayObjectLocations)
179 throws TrafficControlException, SimRuntimeException
180 {
181 super(controllerName, simulator);
182 Throw.whenNull(controllerName, "controllerName may not be null");
183 Throw.whenNull(simulator, "simulator may not be null");
184 this.simulator = simulator;
185 this.displayBackground = displayBackground;
186 this.displayObjectLocations = displayObjectLocations;
187 Throw.whenNull(trafCODRules, "trafCodRules may not be null");
188 this.trafCODRules = trafCODRules;
189 parseTrafCODRules();
190
191
192 for (Variable v : this.variablesInDefinitionOrder)
193 {
194 v.initialize();
195 double value = v.getValue();
196 if (v.isTimer())
197 {
198 value /= 10.0;
199 }
200 fireTimedEvent(TrafficController.TRAFFICCONTROL_VARIABLE_CREATED,
201 new Object[] {getId(), v.getName(), v.getStream(), value}, simulator.getSimulatorTime());
202 }
203 if (null != this.displayContainer && null != this.displayBackground && null != this.displayObjectLocations)
204 {
205 this.stateDisplay = new TrafCodDisplay(this.displayBackground);
206 this.displayContainer.add(this.stateDisplay);
207 try
208 {
209 addTrafCODDisplay(this.displayObjectLocations);
210 }
211 catch (IOException e)
212 {
213 e.printStackTrace();
214 }
215 }
216
217
218 this.simulator.scheduleEventRel(Duration.ZERO,
219 () -> Try.execute(() -> checkConsistency(), "Exception during TrafCod consistency check."));
220
221 this.simulator.scheduleEventRel(EVALUATION_INTERVAL,
222 () -> Try.execute(() -> evalExprs(), "Exception during TrafCod expression evaluation."));
223 }
224
225
226
227
228
229
230
231 public static List<String> loadTextFromURL(final URL url) throws IOException
232 {
233 List<String> result = new ArrayList<>();
234 BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
235 String inputLine;
236 while ((inputLine = in.readLine()) != null)
237 {
238 result.add(inputLine.trim());
239 }
240 return result;
241 }
242
243
244
245
246
247 private void parseTrafCODRules() throws TrafficControlException
248 {
249 for (int lineno = 0; lineno < this.trafCODRules.size(); lineno++)
250 {
251 String trimmedLine = this.trafCODRules.get(lineno);
252 Logger.ots().trace("{}:\t{}", lineno, trimmedLine);
253 if (trimmedLine.length() == 0)
254 {
255 continue;
256 }
257 String locationDescription = "TrafCOD rule" + "(" + lineno + ") ";
258 if (trimmedLine.startsWith(COMMENT_PREFIX))
259 {
260 String commentStripped = trimmedLine.substring(1).trim();
261 if (stringBeginsWithIgnoreCase(VERSION_PREFIX, commentStripped))
262 {
263 String versionString = commentStripped.substring(VERSION_PREFIX.length());
264 try
265 {
266 int observedVersion = Integer.parseInt(versionString);
267 if (TrafCod_VERSION != observedVersion)
268 {
269 throw new TrafficControlException(
270 "Wrong TrafCOD version (expected " + TrafCod_VERSION + ", got " + observedVersion + ")");
271 }
272 }
273 catch (NumberFormatException nfe)
274 {
275 nfe.printStackTrace();
276 throw new TrafficControlException("Could not parse TrafCOD version (got \"" + versionString + ")");
277 }
278 }
279 else if (stringBeginsWithIgnoreCase(SEQUENCE_KEY, commentStripped))
280 {
281 while (trimmedLine.startsWith(COMMENT_PREFIX))
282 {
283 if (++lineno >= this.trafCODRules.size())
284 {
285 throw new TrafficControlException(
286 "Unexpected EOF (reading sequence key at " + locationDescription + ")");
287 }
288 trimmedLine = this.trafCODRules.get(lineno);
289 }
290 String[] fields = trimmedLine.split("\\s");
291 Throw.when(fields.length != 2, TrafficControlException.class,
292 "Wrong number of fields in Sequence information line (%s)", trimmedLine);
293 try
294 {
295 this.numberOfConflictGroups = Integer.parseInt(fields[0]);
296 this.conflictGroupSize = Integer.parseInt(fields[1]);
297 }
298 catch (NumberFormatException nfe)
299 {
300 nfe.printStackTrace();
301 throw new TrafficControlException("Bad number of conflict groups or bad conflict group size");
302 }
303 }
304 else if (stringBeginsWithIgnoreCase(STRUCTURE_PREFIX, commentStripped))
305 {
306 String structureNumberString = commentStripped.substring(STRUCTURE_PREFIX.length()).trim();
307 try
308 {
309 this.structureNumber = Integer.parseInt(structureNumberString);
310 }
311 catch (NumberFormatException nfe)
312 {
313 nfe.printStackTrace();
314 throw new TrafficControlException(
315 "Bad structure number (got \"" + structureNumberString + "\" at " + locationDescription + ")");
316 }
317 for (int i = 0; i < this.conflictGroupSize; i++)
318 {
319 this.conflictGroups.add(new ArrayList<Short>());
320 }
321 for (int conflictMemberLine = 0; conflictMemberLine < this.numberOfConflictGroups; conflictMemberLine++)
322 {
323 if (++lineno >= this.trafCODRules.size())
324 {
325 throw new TrafficControlException(
326 "Unexpected EOF (reading conflict groups at " + locationDescription + ")");
327 }
328 trimmedLine = this.trafCODRules.get(lineno);
329 while (trimmedLine.startsWith(COMMENT_PREFIX))
330 {
331 if (++lineno >= this.trafCODRules.size())
332 {
333 throw new TrafficControlException(
334 "Unexpected EOF (reading conflict groups at " + locationDescription + ")");
335 }
336 trimmedLine = this.trafCODRules.get(lineno);
337 }
338 String[] fields = trimmedLine.split("\\s+");
339 if (fields.length != this.conflictGroupSize)
340 {
341 throw new TrafficControlException("Wrong number of conflict groups in Structure information");
342 }
343 for (int col = 0; col < this.conflictGroupSize; col++)
344 {
345 try
346 {
347 Short stream = Short.parseShort(fields[col]);
348 this.conflictGroups.get(col).add(stream);
349 }
350 catch (NumberFormatException nfe)
351 {
352 nfe.printStackTrace();
353 throw new TrafficControlException("Wrong number of streams in conflict group " + trimmedLine);
354 }
355 }
356 }
357 }
358 continue;
359 }
360 if (stringBeginsWithIgnoreCase(INIT_PREFIX, trimmedLine))
361 {
362 String varNameAndInitialValue = trimmedLine.substring(INIT_PREFIX.length()).trim().replaceAll("[ \t]+", " ");
363 String[] fields = varNameAndInitialValue.split(" ");
364 NameAndStream nameAndStream = new NameAndStream(fields[0], locationDescription);
365 installVariable(nameAndStream.getName(), nameAndStream.getStream(), EnumSet.noneOf(Flags.class),
366 locationDescription).setFlag(Flags.INITED);
367
368 continue;
369 }
370 if (stringBeginsWithIgnoreCase(TIME_PREFIX, trimmedLine))
371 {
372 String timerNameAndMaximumValue = trimmedLine.substring(INIT_PREFIX.length()).trim().replaceAll("[ \t]+", " ");
373 String[] fields = timerNameAndMaximumValue.split(" ");
374 NameAndStream nameAndStream = new NameAndStream(fields[0], locationDescription);
375 Variable variable = installVariable(nameAndStream.getName(), nameAndStream.getStream(),
376 EnumSet.noneOf(Flags.class), locationDescription);
377 int value10 = Integer.parseInt(fields[1]);
378 variable.setTimerMax(value10);
379 continue;
380 }
381 if (stringBeginsWithIgnoreCase(EXPORT_PREFIX, trimmedLine))
382 {
383 String varNameAndOutputValue = trimmedLine.substring(EXPORT_PREFIX.length()).trim().replaceAll("[ \t]+", " ");
384 String[] fields = varNameAndOutputValue.split(" ");
385 NameAndStream nameAndStream = new NameAndStream(fields[0], locationDescription);
386 Variable variable = installVariable(nameAndStream.getName(), nameAndStream.getStream(),
387 EnumSet.noneOf(Flags.class), locationDescription);
388 int value = Integer.parseInt(fields[1]);
389 variable.setOutput(value);
390 continue;
391 }
392 Object[] tokenisedRule = parse(trimmedLine, locationDescription);
393 if (null != tokenisedRule && tokenisedRule.length > 0)
394 {
395 this.tokenisedRules.add(tokenisedRule);
396 }
397 }
398 }
399
400
401
402
403
404
405 public void checkConsistency() throws SimRuntimeException, TrafficControlException
406 {
407 for (Variable v : this.variablesInDefinitionOrder)
408 {
409 if (0 == v.getRefCount() && (!v.isOutput()) && (!v.getName().matches("^RA.")))
410 {
411 Logger.ots().warn("Warning: {}{} is never referenced", v.getName(), v.getStream());
412 fireTimedEvent(TRAFFICCONTROL_CONTROLLER_WARNING,
413 new Object[] {getId(), v.toString(EnumSet.of(PrintFlags.ID)) + " is never referenced"},
414 this.simulator.getSimulatorTime());
415 }
416 if (!v.isDetector())
417 {
418 if (!v.getFlags().contains(Flags.HAS_START_RULE))
419 {
420 Logger.ots().warn("Warning: {}{} has no start rule", v.getName(), v.getStream());
421 fireTimedEvent(TRAFFICCONTROL_CONTROLLER_WARNING,
422 new Object[] {getId(), v.toString(EnumSet.of(PrintFlags.ID)) + " has no start rule"},
423 this.simulator.getSimulatorTime());
424 }
425 if ((!v.getFlags().contains(Flags.HAS_END_RULE)) && (!v.isTimer()))
426 {
427 Logger.ots().warn("Warning: {}{} has no end rule", v.getName(), v.getStream());
428 fireTimedEvent(TRAFFICCONTROL_CONTROLLER_WARNING,
429 new Object[] {getId(), v.toString(EnumSet.of(PrintFlags.ID)) + " has no end rule"},
430 this.simulator.getSimulatorTime());
431 }
432 }
433 }
434 Network network = null;
435 try
436 {
437 network = ((OtsModelInterface) this.simulator.getModel()).getNetwork();
438 }
439 catch (ClassCastException e)
440 {
441 throw new SimRuntimeException("Model is not an OtsModelInterface");
442 }
443 ImmutableCollection<TrafficLight> trafficLights = network.getObjectMap(TrafficLight.class).values();
444 Map<String, List<TrafficLight>> trafficLightMap = new LinkedHashMap<>();
445 for (TrafficLight tl : trafficLights)
446 {
447 String trafficLightName = tl.getId();
448 if (trafficLightName.startsWith(getId()))
449 {
450 trafficLightName = trafficLightName.substring(getId().length());
451 if (trafficLightName.startsWith("."))
452 {
453 trafficLightName = trafficLightName.substring(1);
454 }
455 }
456 if (trafficLightName.substring(trafficLightName.length() - 2).startsWith("."))
457 {
458 trafficLightName = trafficLightName.substring(0, trafficLightName.length() - 2);
459 }
460 List<TrafficLight> list = trafficLightMap.get(trafficLightName);
461 if (null == list)
462 {
463 list = new ArrayList<>();
464 trafficLightMap.put(trafficLightName, list);
465 }
466 list.add(tl);
467 }
468 Map<String, TrafficLightDetector> detectors = new LinkedHashMap<>();
469
470 for (StartEndDetector startEndDetector : network.getObjectMap(StartEndDetector.class).values())
471 {
472 TrafficLightDetector trafficLightSensor = startEndDetector.getParent();
473 detectors.put(trafficLightSensor.getId(), trafficLightSensor);
474 }
475 for (Variable variable : this.variables.values())
476 {
477 if (variable.isOutput())
478 {
479 if (variable.getValue() != 0)
480 {
481 for (TrafficLight trafficLight : variable.getTrafficLights())
482 {
483 trafficLight.setTrafficLightColor(variable.getColor());
484 }
485 }
486 int added = 0;
487 String name = String.format("%s%02d", variable.getName(), variable.getStream());
488 String digits = String.format("%02d", variable.getStream());
489 List<TrafficLight> matchingLights = trafficLightMap.get(digits);
490 if (null == matchingLights)
491 {
492 throw new TrafficControlException("No traffic light for stream " + digits + " found");
493 }
494 for (TrafficLight tl : matchingLights)
495 {
496 try
497 {
498 variable.addOutput(tl);
499 }
500 catch (TrafficControlException exception)
501 {
502
503 exception.printStackTrace();
504 throw new SimRuntimeException(exception);
505 }
506 if (variable.getValue() != 0)
507 {
508 tl.setTrafficLightColor(variable.getColor());
509 }
510 added++;
511 }
512 if (0 == added)
513 {
514 throw new TrafficControlException("No traffic light found that matches output " + name + " and " + getId());
515 }
516 }
517 else if (variable.isDetector())
518 {
519 String name = variable.getName();
520 String subNumber = name.substring(name.length() - 1);
521 name = name.substring(0, name.length() - 1);
522 name = String.format("%s%02d", name, variable.getStream());
523 String digits = String.format("%02d", variable.getStream());
524 TrafficLightDetector tls = detectors.get("D" + digits + subNumber);
525 if (null == tls)
526 {
527 throw new TrafficControlException(
528 "No sensor found that matches " + name + " subNumber " + subNumber + " and " + getId());
529 }
530 variable.subscribeToDetector(tls);
531 if (null != this.stateDisplay)
532 {
533
534 EventListener el =
535 this.stateDisplay.getDetectorImage(String.format("%02d.%s", variable.getStream(), subNumber))
536 .orElseThrow(() -> new TrafficControlException(
537 "Cannor find detector image matching variable " + variable));
538 Logger.ots().trace("creating subscriptions to sensor {}", tls);
539 tls.addListener(el, TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_ENTRY_EVENT);
540 tls.addListener(el, TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_EXIT_EVENT);
541 }
542 }
543 }
544 }
545
546
547
548
549
550
551
552
553 private void addTrafCODDisplay(final List<String> rules) throws TrafficControlException, IOException
554 {
555 boolean useFirstCoordinates = true;
556 int lineno = 0;
557 for (String line : rules)
558 {
559 lineno++;
560 String rule = line.trim();
561 if (rule.length() == 0)
562 {
563 continue;
564 }
565 String[] fields = rule.split("=");
566 if ("mapfile".contentEquals(fields[0]))
567 {
568 if (fields[1].matches("[Bb][Mm][Pp]|[Pp][Nn][Gg]$"))
569 {
570 useFirstCoordinates = false;
571 }
572 Logger.ots().trace("map file description is {}", line);
573
574 }
575 else if ("light".equals(fields[0]))
576 {
577
578 int streamNumber;
579 try
580 {
581 streamNumber = Integer.parseInt(fields[1].substring(0, 2));
582 }
583 catch (NumberFormatException nfe)
584 {
585 throw new TrafficControlException("Bad traffic light number in coordinates: " + rule);
586 }
587
588 TrafficLightImage tli =
589 new TrafficLightImage(this.stateDisplay, getCoordinates(fields[1].substring(3), useFirstCoordinates),
590 String.format("Traffic Light %02d", streamNumber));
591 for (Variable v : this.variablesInDefinitionOrder)
592 {
593 if (v.isOutput() && v.getStream() == streamNumber)
594 {
595
596
597 }
598 }
599 }
600 else if ("detector".equals(fields[0]))
601 {
602 int detectorStream;
603 int detectorSubNumber;
604 try
605 {
606 detectorStream = Integer.parseInt(fields[1].substring(0, 2));
607 detectorSubNumber = Integer.parseInt(fields[1].substring(3, 4));
608 }
609 catch (NumberFormatException nfe)
610 {
611 throw new TrafficControlException("Cannot parse detector number in coordinates " + rule);
612 }
613 String detectorName = String.format("D%02d%d", detectorStream, detectorSubNumber);
614 Variable detectorVariable = this.variables.get(detectorName);
615 if (null == detectorVariable)
616 {
617 throw new TrafficControlException(
618 "coordinates defines detector " + detectorName + " which does not exist in the TrafCOD program");
619 }
620
621 new DetectorImage(this.stateDisplay, getCoordinates(fields[1].substring(5), useFirstCoordinates),
622 String.format("%02d.%d", detectorStream, detectorSubNumber),
623 String.format("Detector %02d.%d", detectorStream, detectorSubNumber));
624 }
625 else
626 {
627 throw new TrafficControlException("Cannot parse coordinates line " + lineno + "in \"" + line + "\"");
628 }
629 }
630 }
631
632
633
634
635
636
637
638
639
640 private static Point2D getCoordinates(final String line, final boolean useFirstCoordinates) throws TrafficControlException
641 {
642 String work = line.replaceAll("[ ,\t]+", "\t").trim();
643 int x;
644 int y;
645 String[] fields = work.split("\t");
646 if (fields.length < (useFirstCoordinates ? 2 : 4))
647 {
648 throw new TrafficControlException("not enough fields in tfg line \"" + line + "\"");
649 }
650 try
651 {
652 x = Integer.parseInt(fields[useFirstCoordinates ? 0 : 2]);
653 y = Integer.parseInt(fields[useFirstCoordinates ? 1 : 3]);
654 }
655 catch (NumberFormatException nfe)
656 {
657 throw new TrafficControlException("Bad value in tfg line \"" + line + "\"");
658 }
659 return new Point2D.Double(x, y);
660 }
661
662
663
664
665
666
667 private int decrementTimers() throws TrafficControlException
668 {
669 Logger.ots().trace("Decrement running timers");
670 int changeCount = 0;
671 for (Variable v : this.variables.values())
672 {
673 if (v.isTimer() && v.getValue() > 0 && v.decrementTimer(this.currentTime10))
674 {
675 changeCount++;
676 }
677 }
678 return changeCount;
679 }
680
681
682
683
684 private void resetTimerFlags()
685 {
686 for (Variable v : this.variablesInDefinitionOrder)
687 {
688 if (v.isTimer())
689 {
690 v.clearChangedFlag();
691 v.clearFlag(Flags.START);
692 v.clearFlag(Flags.END);
693 }
694 }
695 }
696
697
698
699
700
701
702 @SuppressWarnings("unused")
703 private void evalExprs() throws TrafficControlException, SimRuntimeException
704 {
705 fireTimedEvent(TrafficController.TRAFFICCONTROL_CONTROLLER_EVALUATING, new Object[] {getId()},
706 this.simulator.getSimulatorTime());
707 Logger.ots().trace("evalExprs: time is {}", EngineeringFormatter.format(this.simulator.getSimulatorTime().si));
708
709
710
711
712
713
714
715 Logger.ots().trace("Sleep in evalExprs was interrupted");
716
717
718
719
720 decrementTimers();
721 this.currentTime10 = (int) (this.simulator.getSimulatorTime().si * 10);
722 int loop;
723 for (loop = 0; loop < this.maxLoopCount; loop++)
724 {
725 int changeCount = evalExpressionsOnce();
726 resetTimerFlags();
727 if (changeCount == 0)
728 {
729 break;
730 }
731 }
732 Logger.ots().trace("Executed {} iteration(s)", (loop + 1));
733 if (loop >= this.maxLoopCount)
734 {
735 StringBuffer warningMessage = new StringBuffer();
736 warningMessage.append(String
737 .format("Control program did not settle to a final state in %d iterations; oscillating variables:", loop));
738 for (Variable v : this.variablesInDefinitionOrder)
739 {
740 if (v.getFlags().contains(Flags.CHANGED))
741 {
742 warningMessage.append(String.format(" %s%02d", v.getName(), v.getStream()));
743 }
744 }
745 fireTimedEvent(TrafficController.TRAFFICCONTROL_CONTROLLER_WARNING,
746 new Object[] {getId(), warningMessage.toString()}, this.simulator.getSimulatorTime());
747 }
748 this.simulator.scheduleEventRel(EVALUATION_INTERVAL,
749 () -> Try.execute(() -> evalExprs(), "Exception evaluating expressions in TrafCod."));
750 }
751
752
753
754
755
756
757 private int evalExpressionsOnce() throws TrafficControlException
758 {
759 for (Variable variable : this.variables.values())
760 {
761 variable.clearChangedFlag();
762 }
763 int changeCount = 0;
764 for (Object[] rule : this.tokenisedRules)
765 {
766 if (evalRule(rule))
767 {
768 changeCount++;
769 }
770 }
771 return changeCount;
772 }
773
774
775
776
777
778
779
780 private boolean evalRule(final Object[] rule) throws TrafficControlException
781 {
782 boolean result = false;
783 Token ruleType = (Token) rule[0];
784 Variable destination = (Variable) rule[1];
785 if (destination.isTimer())
786 {
787 if (destination.getFlags().contains(Flags.TIMEREXPIRED))
788 {
789 destination.clearFlag(Flags.TIMEREXPIRED);
790 destination.setFlag(Flags.END);
791 }
792 else if (destination.getFlags().contains(Flags.START) || destination.getFlags().contains(Flags.END))
793 {
794 destination.clearFlag(Flags.START);
795 destination.clearFlag(Flags.END);
796 destination.setFlag(Flags.CHANGED);
797 }
798 }
799 else
800 {
801
802 if (Token.START_RULE == ruleType)
803 {
804 destination.clearFlag(Flags.START);
805 }
806 else if (Token.END_RULE == ruleType)
807 {
808 destination.clearFlag(Flags.END);
809 }
810 else
811 {
812 destination.clearFlag(Flags.START);
813 destination.clearFlag(Flags.END);
814 }
815 }
816
817 int currentValue = destination.getValue();
818 if (Token.START_RULE == ruleType && currentValue != 0 || Token.END == ruleType && currentValue == 0
819 || Token.INIT_TIMER == ruleType && currentValue != 0)
820 {
821 return false;
822 }
823 this.currentRule = rule;
824 this.currentToken = 2;
825 this.stack.clear();
826 evalExpr(0);
827 if (this.currentToken < this.currentRule.length && Token.CLOSE_PAREN == this.currentRule[this.currentToken])
828 {
829 throw new TrafficControlException("Too many closing parentheses");
830 }
831 int resultValue = pop();
832 if (Token.END_RULE == ruleType)
833 {
834
835 if (0 == resultValue)
836 {
837 resultValue = destination.getValue();
838 }
839 else
840 {
841 resultValue = 0;
842 }
843 }
844 if (resultValue != 0 && destination.getValue() == 0)
845 {
846 destination.setFlag(Flags.START);
847 }
848 else if (resultValue == 0 && destination.getValue() != 0)
849 {
850 destination.setFlag(Flags.END);
851 }
852 if (destination.isTimer())
853 {
854 if (resultValue != 0 && Token.END_RULE != ruleType)
855 {
856 if (0 == destination.getValue())
857 {
858 result = true;
859 }
860 int timerValue10 = destination.getTimerMax();
861 if (timerValue10 < 1)
862 {
863
864 timerValue10 = 1;
865 }
866 result = destination.setValue(timerValue10, this.currentTime10, new CausePrinter(rule), this);
867 }
868 else if (0 == resultValue && Token.END_RULE == ruleType && destination.getValue() != 0)
869 {
870 result = destination.setValue(0, this.currentTime10, new CausePrinter(rule), this);
871 }
872 }
873 else if (destination.getValue() != resultValue)
874 {
875 result = destination.setValue(resultValue, this.currentTime10, new CausePrinter(rule), this);
876 if (destination.isOutput())
877 {
878 fireTimedEvent(TRAFFIC_LIGHT_CHANGED, new Object[] {getId(), destination.getStream(), destination.getColor()},
879 getSimulator().getSimulatorTime());
880 }
881 if (destination.isConflictGroup() && resultValue != 0)
882 {
883 int conflictGroupRank = destination.conflictGroupRank();
884 StringBuilder conflictGroupList = new StringBuilder();
885 for (Short stream : this.conflictGroups.get(conflictGroupRank))
886 {
887 if (conflictGroupList.length() > 0)
888 {
889 conflictGroupList.append(" ");
890 }
891 conflictGroupList.append(String.format("%02d", stream));
892 }
893 fireTimedEvent(TRAFFICCONTROL_CONFLICT_GROUP_CHANGED,
894 new Object[] {getId(), this.currentConflictGroup, conflictGroupList.toString()},
895 getSimulator().getSimulatorTime());
896 Logger.ots().trace("Conflict group changed from {} to {}", this.currentConflictGroup,
897 conflictGroupList.toString());
898 this.currentConflictGroup = conflictGroupList.toString();
899 }
900 }
901 return result;
902 }
903
904
905 private static final int BIND_RELATIONAL_OPERATOR = 1;
906
907
908 private static final int BIND_ADDITION = 2;
909
910
911 private static final int BIND_MULTIPLY = 3;
912
913
914 private static final int BIND_UNARY_MINUS = 4;
915
916
917
918
919
920
921
922
923
924
925
926
927 private void evalExpr(final int bindingStrength) throws TrafficControlException
928 {
929 if (this.currentToken >= this.currentRule.length)
930 {
931 throw new TrafficControlException("Missing operand at end of expression " + printRule(this.currentRule, false));
932 }
933 Token token = (Token) this.currentRule[this.currentToken++];
934 Object nextToken = null;
935 if (this.currentToken < this.currentRule.length)
936 {
937 nextToken = this.currentRule[this.currentToken];
938 }
939 switch (token)
940 {
941 case UNARY_MINUS:
942 if (Token.OPEN_PAREN != nextToken && Token.VARIABLE != nextToken && Token.NEG_VARIABLE != nextToken
943 && Token.CONSTANT != nextToken && Token.START != nextToken && Token.END != nextToken)
944 {
945 throw new TrafficControlException("Operand expected after unary minus");
946 }
947 evalExpr(BIND_UNARY_MINUS);
948 push(-pop());
949 break;
950
951 case OPEN_PAREN:
952 evalExpr(0);
953 if (Token.CLOSE_PAREN != this.currentRule[this.currentToken])
954 {
955 throw new TrafficControlException("Missing closing parenthesis");
956 }
957 this.currentToken++;
958 break;
959
960 case START:
961 if (Token.VARIABLE != nextToken || this.currentToken >= this.currentRule.length - 1)
962 {
963 throw new TrafficControlException("Missing variable after S");
964 }
965 nextToken = this.currentRule[++this.currentToken];
966 if (!(nextToken instanceof Variable))
967 {
968 throw new TrafficControlException("Missing variable after S");
969 }
970 push(((Variable) nextToken).getFlags().contains(Flags.START) ? 1 : 0);
971 this.currentToken++;
972 break;
973
974 case END:
975 if (Token.VARIABLE != nextToken || this.currentToken >= this.currentRule.length - 1)
976 {
977 throw new TrafficControlException("Missing variable after E");
978 }
979 nextToken = this.currentRule[++this.currentToken];
980 if (!(nextToken instanceof Variable))
981 {
982 throw new TrafficControlException("Missing variable after E");
983 }
984 push(((Variable) nextToken).getFlags().contains(Flags.END) ? 1 : 0);
985 this.currentToken++;
986 break;
987
988 case VARIABLE:
989 {
990 Variable operand = (Variable) nextToken;
991 if (operand.isTimer())
992 {
993 push(operand.getValue() == 0 ? 0 : 1);
994 }
995 else
996 {
997 push(operand.getValue());
998 }
999 this.currentToken++;
1000 break;
1001 }
1002
1003 case CONSTANT:
1004 push((Integer) nextToken);
1005 this.currentToken++;
1006 break;
1007
1008 case NEG_VARIABLE:
1009 Variable operand = (Variable) nextToken;
1010 push(operand.getValue() == 0 ? 1 : 0);
1011 this.currentToken++;
1012 break;
1013
1014 default:
1015 throw new TrafficControlException("Operand missing");
1016 }
1017 evalRHS(bindingStrength);
1018 }
1019
1020
1021
1022
1023
1024
1025 private void evalRHS(final int bindingStrength) throws TrafficControlException
1026 {
1027 while (true)
1028 {
1029 if (this.currentToken >= this.currentRule.length)
1030 {
1031 return;
1032 }
1033 Token token = (Token) this.currentRule[this.currentToken];
1034 switch (token)
1035 {
1036 case CLOSE_PAREN:
1037 return;
1038
1039 case TIMES:
1040 if (BIND_MULTIPLY <= bindingStrength)
1041 {
1042 return;
1043 }
1044
1045
1046
1047
1048
1049 this.currentToken++;
1050 evalExpr(BIND_MULTIPLY);
1051 push(pop() * pop() == 0 ? 0 : 1);
1052 break;
1053
1054 case EQ:
1055 case NOTEQ:
1056 case LE:
1057 case LEEQ:
1058 case GT:
1059 case GTEQ:
1060 if (BIND_RELATIONAL_OPERATOR <= bindingStrength)
1061 {
1062 return;
1063 }
1064
1065
1066
1067
1068
1069 this.currentToken++;
1070 evalExpr(BIND_RELATIONAL_OPERATOR);
1071 switch (token)
1072 {
1073 case EQ:
1074 push(pop() == pop() ? 1 : 0);
1075 break;
1076
1077 case NOTEQ:
1078 push(pop() != pop() ? 1 : 0);
1079 break;
1080
1081 case GT:
1082 push(pop() < pop() ? 1 : 0);
1083 break;
1084
1085 case GTEQ:
1086 push(pop() <= pop() ? 1 : 0);
1087 break;
1088
1089 case LE:
1090 push(pop() > pop() ? 1 : 0);
1091 break;
1092
1093 case LEEQ:
1094 push(pop() >= pop() ? 1 : 0);
1095 break;
1096
1097 default:
1098 throw new TrafficControlException("Bad relational operator");
1099 }
1100 break;
1101
1102 case PLUS:
1103 if (BIND_ADDITION <= bindingStrength)
1104 {
1105 return;
1106 }
1107
1108
1109
1110
1111
1112 this.currentToken++;
1113 evalExpr(BIND_ADDITION);
1114 push(pop() + pop() == 0 ? 0 : 1);
1115 break;
1116
1117 case MINUS:
1118 if (BIND_ADDITION <= bindingStrength)
1119 {
1120 return;
1121 }
1122
1123
1124
1125
1126
1127 this.currentToken++;
1128 evalExpr(BIND_ADDITION);
1129 push(-pop() + pop());
1130 break;
1131
1132 default:
1133 throw new TrafficControlException("Missing binary operator");
1134 }
1135 }
1136 }
1137
1138
1139
1140
1141
1142 private void push(final int value)
1143 {
1144 this.stack.add(value);
1145 }
1146
1147
1148
1149
1150
1151
1152 private int pop() throws TrafficControlException
1153 {
1154 if (this.stack.size() < 1)
1155 {
1156 throw new TrafficControlException("Stack empty");
1157 }
1158 return this.stack.remove(this.stack.size() - 1);
1159 }
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169 static String printRule(final Object[] tokens, final boolean printValues) throws TrafficControlException
1170 {
1171 EnumSet<PrintFlags> variableFlags = EnumSet.of(PrintFlags.ID);
1172 if (printValues)
1173 {
1174 variableFlags.add(PrintFlags.VALUE);
1175 }
1176 EnumSet<PrintFlags> negatedVariableFlags = EnumSet.copyOf(variableFlags);
1177 negatedVariableFlags.add(PrintFlags.NEGATED);
1178 StringBuilder result = new StringBuilder();
1179 for (int inPos = 0; inPos < tokens.length; inPos++)
1180 {
1181 Object token = tokens[inPos];
1182 if (token instanceof Token)
1183 {
1184 switch ((Token) token)
1185 {
1186 case EQUALS_RULE:
1187 result.append(((Variable) tokens[++inPos]).toString(variableFlags));
1188 result.append("=");
1189 break;
1190
1191 case NEG_EQUALS_RULE:
1192 result.append(((Variable) tokens[++inPos]).toString(negatedVariableFlags));
1193 result.append("=");
1194 break;
1195
1196 case START_RULE:
1197 result.append(((Variable) tokens[++inPos]).toString(variableFlags));
1198 result.append(".=");
1199 break;
1200
1201 case END_RULE:
1202 result.append(((Variable) tokens[++inPos]).toString(variableFlags));
1203 result.append("N.=");
1204 break;
1205
1206 case INIT_TIMER:
1207 result.append(((Variable) tokens[++inPos]).toString(EnumSet.of(PrintFlags.ID, PrintFlags.INITTIMER)));
1208 result.append(".=");
1209 break;
1210
1211 case REINIT_TIMER:
1212 result.append(((Variable) tokens[++inPos]).toString(EnumSet.of(PrintFlags.ID, PrintFlags.REINITTIMER)));
1213 result.append(".=");
1214 break;
1215
1216 case START:
1217 result.append("S");
1218 break;
1219
1220 case END:
1221 result.append("E");
1222 break;
1223
1224 case VARIABLE:
1225 result.append(((Variable) tokens[++inPos]).toString(variableFlags));
1226 break;
1227
1228 case NEG_VARIABLE:
1229 result.append(((Variable) tokens[++inPos]).toString(variableFlags));
1230 result.append("N");
1231 break;
1232
1233 case CONSTANT:
1234 result.append(tokens[++inPos]).toString();
1235 break;
1236
1237 case UNARY_MINUS:
1238 case MINUS:
1239 result.append("-");
1240 break;
1241
1242 case PLUS:
1243 result.append("+");
1244 break;
1245
1246 case TIMES:
1247 result.append(".");
1248 break;
1249
1250 case EQ:
1251 result.append("=");
1252 break;
1253
1254 case NOTEQ:
1255 result.append("<>");
1256 break;
1257
1258 case GT:
1259 result.append(">");
1260 break;
1261
1262 case GTEQ:
1263 result.append(">=");
1264 break;
1265
1266 case LE:
1267 result.append("<");
1268 break;
1269
1270 case LEEQ:
1271 result.append("<=");
1272 break;
1273
1274 case OPEN_PAREN:
1275 result.append("(");
1276 break;
1277
1278 case CLOSE_PAREN:
1279 result.append(")");
1280 break;
1281
1282 default:
1283 Logger.ots().error("<<<ERROR>>> encountered a non-Token object: {} after {}", token, result.toString());
1284 throw new TrafficControlException("Unknown token");
1285 }
1286 }
1287 else
1288 {
1289 Logger.ots().error("<<<ERROR>>> encountered a non-Token object: {} after {}", token, result.toString());
1290 throw new TrafficControlException("Not a token");
1291 }
1292 }
1293 return result.toString();
1294 }
1295
1296
1297
1298
1299
1300 enum ParserState
1301 {
1302
1303 FIND_LHS,
1304
1305 FIND_ASSIGN,
1306
1307 FIND_RHS,
1308
1309 MAY_UMINUS,
1310
1311 FIND_EXPR,
1312 }
1313
1314
1315
1316
1317
1318 enum Token
1319 {
1320
1321 EQUALS_RULE,
1322
1323 NEG_EQUALS_RULE,
1324
1325 ASSIGNMENT,
1326
1327 START_RULE,
1328
1329 END_RULE,
1330
1331 INIT_TIMER,
1332
1333 REINIT_TIMER,
1334
1335 UNARY_MINUS,
1336
1337 LEEQ,
1338
1339 NOTEQ,
1340
1341 LE,
1342
1343 GTEQ,
1344
1345 GT,
1346
1347 EQ,
1348
1349 START,
1350
1351 END,
1352
1353 VARIABLE,
1354
1355 NEG_VARIABLE,
1356
1357 CONSTANT,
1358
1359 PLUS,
1360
1361 MINUS,
1362
1363 TIMES,
1364
1365 OPEN_PAREN,
1366
1367 CLOSE_PAREN,
1368 }
1369
1370
1371
1372
1373
1374
1375
1376
1377 private Object[] parse(final String rawRule, final String locationDescription) throws TrafficControlException
1378 {
1379 if (rawRule.length() == 0)
1380 {
1381 throw new TrafficControlException("empty rule at " + locationDescription);
1382 }
1383 ParserState state = ParserState.FIND_LHS;
1384 String rule = rawRule.toUpperCase(Locale.US);
1385 Token ruleType = Token.ASSIGNMENT;
1386 int inPos = 0;
1387 NameAndStream lhsNameAndStream = null;
1388 List<Object> tokens = new ArrayList<>();
1389 while (inPos < rule.length())
1390 {
1391 char character = rule.charAt(inPos);
1392 if (Character.isWhitespace(character))
1393 {
1394 inPos++;
1395 continue;
1396 }
1397 switch (state)
1398 {
1399 case FIND_LHS:
1400 {
1401 if ('S' == character)
1402 {
1403 ruleType = Token.START_RULE;
1404 inPos++;
1405 lhsNameAndStream = new NameAndStream(rule.substring(inPos), locationDescription);
1406 inPos += lhsNameAndStream.getNumberOfChars();
1407 }
1408 else if ('E' == character)
1409 {
1410 ruleType = Token.END_RULE;
1411 inPos++;
1412 lhsNameAndStream = new NameAndStream(rule.substring(inPos), locationDescription);
1413 inPos += lhsNameAndStream.getNumberOfChars();
1414 }
1415 else if ('I' == character && 'T' == rule.charAt(inPos + 1))
1416 {
1417 ruleType = Token.INIT_TIMER;
1418 inPos++;
1419 lhsNameAndStream = new NameAndStream(rule.substring(inPos), locationDescription);
1420 inPos += lhsNameAndStream.getNumberOfChars();
1421 }
1422 else if ('R' == character && 'I' == rule.charAt(inPos + 1) && 'T' == rule.charAt(inPos + 2))
1423 {
1424 ruleType = Token.REINIT_TIMER;
1425 inPos += 2;
1426 lhsNameAndStream = new NameAndStream(rule.substring(inPos), locationDescription);
1427 inPos += lhsNameAndStream.getNumberOfChars();
1428 }
1429 else if ('T' == character && rule.indexOf('=') >= 0
1430 && (rule.indexOf('N') < 0 || rule.indexOf('N') > rule.indexOf('=')))
1431 {
1432 throw new TrafficControlException("Bad time initialization at " + locationDescription);
1433 }
1434 else
1435 {
1436 ruleType = Token.EQUALS_RULE;
1437 lhsNameAndStream = new NameAndStream(rule.substring(inPos), locationDescription);
1438 inPos += lhsNameAndStream.getNumberOfChars();
1439 if (lhsNameAndStream.isNegated())
1440 {
1441 ruleType = Token.NEG_EQUALS_RULE;
1442 }
1443 }
1444 state = ParserState.FIND_ASSIGN;
1445 break;
1446 }
1447
1448 case FIND_ASSIGN:
1449 {
1450 if ('.' == character && '=' == rule.charAt(inPos + 1))
1451 {
1452 if (Token.EQUALS_RULE == ruleType)
1453 {
1454 ruleType = Token.START_RULE;
1455 }
1456 else if (Token.NEG_EQUALS_RULE == ruleType)
1457 {
1458 ruleType = Token.END_RULE;
1459 }
1460 inPos += 2;
1461 }
1462 else if ('=' == character)
1463 {
1464 if (Token.START_RULE == ruleType || Token.END_RULE == ruleType || Token.INIT_TIMER == ruleType
1465 || Token.REINIT_TIMER == ruleType)
1466 {
1467 throw new TrafficControlException("Bad assignment at " + locationDescription);
1468 }
1469 inPos++;
1470 }
1471 tokens.add(ruleType);
1472 EnumSet<Flags> lhsFlags = EnumSet.noneOf(Flags.class);
1473 if (Token.START_RULE == ruleType || Token.EQUALS_RULE == ruleType || Token.NEG_EQUALS_RULE == ruleType
1474 || Token.INIT_TIMER == ruleType || Token.REINIT_TIMER == ruleType)
1475 {
1476 lhsFlags.add(Flags.HAS_START_RULE);
1477 }
1478 if (Token.END_RULE == ruleType || Token.EQUALS_RULE == ruleType || Token.NEG_EQUALS_RULE == ruleType)
1479 {
1480 lhsFlags.add(Flags.HAS_END_RULE);
1481 }
1482 Variable lhsVariable = installVariable(lhsNameAndStream.getName(), lhsNameAndStream.getStream(), lhsFlags,
1483 locationDescription);
1484 tokens.add(lhsVariable);
1485 state = ParserState.MAY_UMINUS;
1486 break;
1487 }
1488
1489 case MAY_UMINUS:
1490 if ('-' == character)
1491 {
1492 tokens.add(Token.UNARY_MINUS);
1493 inPos++;
1494 }
1495 state = ParserState.FIND_EXPR;
1496 break;
1497
1498 case FIND_EXPR:
1499 {
1500 if (Character.isDigit(character))
1501 {
1502 int constValue = 0;
1503 while (inPos < rule.length() && Character.isDigit(rule.charAt(inPos)))
1504 {
1505 int digit = rule.charAt(inPos) - '0';
1506 if (constValue >= (Integer.MAX_VALUE - digit) / 10)
1507 {
1508 throw new TrafficControlException("Number too large at " + locationDescription);
1509 }
1510 constValue = 10 * constValue + digit;
1511 inPos++;
1512 }
1513 tokens.add(Token.CONSTANT);
1514 tokens.add(new Integer(constValue));
1515 }
1516 if (inPos >= rule.length())
1517 {
1518 return tokens.toArray();
1519 }
1520 character = rule.charAt(inPos);
1521 switch (character)
1522 {
1523 case '+':
1524 tokens.add(Token.PLUS);
1525 inPos++;
1526 break;
1527
1528 case '-':
1529 tokens.add(Token.MINUS);
1530 inPos++;
1531 break;
1532
1533 case '.':
1534 tokens.add(Token.TIMES);
1535 inPos++;
1536 break;
1537
1538 case ')':
1539 tokens.add(Token.CLOSE_PAREN);
1540 inPos++;
1541 break;
1542
1543 case '<':
1544 {
1545 Character nextChar = rule.charAt(++inPos);
1546 if ('=' == nextChar)
1547 {
1548 tokens.add(Token.LEEQ);
1549 inPos++;
1550 }
1551 else if ('>' == nextChar)
1552 {
1553 tokens.add(Token.NOTEQ);
1554 inPos++;
1555 }
1556 else
1557 {
1558 tokens.add(Token.LE);
1559 }
1560 break;
1561 }
1562
1563 case '>':
1564 {
1565 Character nextChar = rule.charAt(++inPos);
1566 if ('=' == nextChar)
1567 {
1568 tokens.add(Token.GTEQ);
1569 inPos++;
1570 }
1571 else if ('<' == nextChar)
1572 {
1573 tokens.add(Token.NOTEQ);
1574 inPos++;
1575 }
1576 else
1577 {
1578 tokens.add(Token.GT);
1579 }
1580 break;
1581 }
1582
1583 case '=':
1584 {
1585 Character nextChar = rule.charAt(++inPos);
1586 if ('<' == nextChar)
1587 {
1588 tokens.add(Token.LEEQ);
1589 inPos++;
1590 }
1591 else if ('>' == nextChar)
1592 {
1593 tokens.add(Token.GTEQ);
1594 inPos++;
1595 }
1596 else
1597 {
1598 tokens.add(Token.EQ);
1599 }
1600 break;
1601 }
1602
1603 case '(':
1604 {
1605 inPos++;
1606 tokens.add(Token.OPEN_PAREN);
1607 state = ParserState.MAY_UMINUS;
1608 break;
1609 }
1610
1611 default:
1612 {
1613 if ('S' == character)
1614 {
1615 tokens.add(Token.START);
1616 inPos++;
1617 }
1618 else if ('E' == character)
1619 {
1620 tokens.add(Token.END);
1621 inPos++;
1622 }
1623 NameAndStream nas = new NameAndStream(rule.substring(inPos), locationDescription);
1624 inPos += nas.getNumberOfChars();
1625 if (nas.isNegated())
1626 {
1627 tokens.add(Token.NEG_VARIABLE);
1628 }
1629 else
1630 {
1631 tokens.add(Token.VARIABLE);
1632 }
1633 Variable variable = installVariable(nas.getName(), nas.getStream(), EnumSet.noneOf(Flags.class),
1634 locationDescription);
1635 variable.incrementReferenceCount();
1636 tokens.add(variable);
1637 }
1638 }
1639 break;
1640 }
1641 default:
1642 throw new TrafficControlException("Error: bad switch; case " + state + " should not happen");
1643 }
1644 }
1645 return tokens.toArray();
1646 }
1647
1648
1649
1650
1651
1652
1653
1654 private boolean stringBeginsWithIgnoreCase(final String sought, final String supplied)
1655 {
1656 if (sought.length() > supplied.length())
1657 {
1658 return false;
1659 }
1660 return (sought.equalsIgnoreCase(supplied.substring(0, sought.length())));
1661 }
1662
1663
1664
1665
1666
1667
1668
1669 private String variableKey(final String name, final short stream)
1670 {
1671 if (name.startsWith("D"))
1672 {
1673 return String.format("D%02d%s", stream, name.substring(1));
1674 }
1675 return String.format("%s%02d", name.toUpperCase(Locale.US), stream);
1676 }
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687 private Variable installVariable(final String name, final short stream, final EnumSet<Flags> flags, final String location)
1688 throws TrafficControlException
1689 {
1690 EnumSet<Flags> forbidden = EnumSet.complementOf(EnumSet.of(Flags.HAS_START_RULE, Flags.HAS_END_RULE));
1691 EnumSet<Flags> badFlags = EnumSet.copyOf(forbidden);
1692 badFlags.retainAll(flags);
1693 if (badFlags.size() > 0)
1694 {
1695 throw new TrafficControlException("installVariable was called with wrong flag(s): " + badFlags);
1696 }
1697 String key = variableKey(name, stream);
1698 Variable variable = this.variables.get(key);
1699 if (null == variable)
1700 {
1701
1702 variable = new Variable(name, stream, this);
1703 this.variables.put(key, variable);
1704 this.variablesInDefinitionOrder.add(variable);
1705 if (variable.isDetector())
1706 {
1707 this.detectors.put(key, variable);
1708 }
1709 }
1710 if (flags.contains(Flags.HAS_START_RULE))
1711 {
1712 variable.setStartSource(location);
1713 }
1714 if (flags.contains(Flags.HAS_END_RULE))
1715 {
1716 variable.setEndSource(location);
1717 }
1718 return variable;
1719 }
1720
1721
1722
1723
1724
1725 public OtsSimulatorInterface getSimulator()
1726 {
1727 return this.simulator;
1728 }
1729
1730
1731
1732
1733
1734 public int getStructureNumber()
1735 {
1736 return this.structureNumber;
1737 }
1738
1739 @Override
1740 public void updateDetector(final String detectorId, final boolean detectingGTU)
1741 {
1742 Variable detector = this.detectors.get(detectorId);
1743 detector.setValue(detectingGTU ? 1 : 0, this.currentTime10,
1744 new CausePrinter(
1745 String.format("Detector %s becoming %s", detectorId, (detectingGTU ? "occupied" : "unoccupied"))),
1746 this);
1747 }
1748
1749
1750
1751
1752
1753
1754
1755
1756 public void traceVariablesOfStream(final int stream, final boolean trace)
1757 {
1758 for (Variable v : this.variablesInDefinitionOrder)
1759 {
1760 if (v.getStream() == stream)
1761 {
1762 if (trace)
1763 {
1764 v.setFlag(Flags.TRACED);
1765 }
1766 else
1767 {
1768 v.clearFlag(Flags.TRACED);
1769 }
1770 }
1771 }
1772 }
1773
1774
1775
1776
1777
1778
1779
1780
1781 public void traceVariable(final String variableName, final int stream, final boolean trace)
1782 {
1783 for (Variable v : this.variablesInDefinitionOrder)
1784 {
1785 if (v.getStream() == stream && variableName.equals(v.getName()))
1786 {
1787 if (trace)
1788 {
1789 v.setFlag(Flags.TRACED);
1790 }
1791 else
1792 {
1793 v.clearFlag(Flags.TRACED);
1794 }
1795 }
1796 }
1797 }
1798
1799 @Override
1800 public void notify(final Event event)
1801 {
1802 Logger.ots().info("TrafCOD: received an event");
1803 if (event.getType().equals(TrafficController.TRAFFICCONTROL_SET_TRACING))
1804 {
1805 Object content = event.getContent();
1806 if (!(content instanceof Object[]))
1807 {
1808 Logger.ots().error("TrafCOD controller {} received event with bad payload ({})", getId(), content);
1809 return;
1810 }
1811 Object[] fields = (Object[]) event.getContent();
1812 if (getId().equals(fields[0]))
1813 {
1814 if (fields.length < 4 || !(fields[1] instanceof String) || !(fields[2] instanceof Integer)
1815 || !(fields[3] instanceof Boolean))
1816 {
1817 Logger.ots().error("TrafCOD controller {} received event with bad payload ({})", getId(), content);
1818 return;
1819 }
1820 String name = (String) fields[1];
1821 int stream = (Integer) fields[2];
1822 boolean trace = (Boolean) fields[3];
1823 if (name.length() > 1)
1824 {
1825 Variable v = this.variables.get(variableKey(name, (short) stream));
1826 if (null == v)
1827 {
1828 Logger.ots().error("Received trace notification for nonexistent variable (name=\"{}\", stream={})",
1829 name, stream);
1830 }
1831 if (trace)
1832 {
1833 v.setFlag(Flags.TRACED);
1834 }
1835 else
1836 {
1837 v.clearFlag(Flags.TRACED);
1838 }
1839 }
1840 else
1841 {
1842 for (Variable v : this.variablesInDefinitionOrder)
1843 {
1844 if (v.getStream() == stream)
1845 {
1846 if (trace)
1847 {
1848 v.setFlag(Flags.TRACED);
1849 }
1850 else
1851 {
1852 v.clearFlag(Flags.TRACED);
1853 }
1854 }
1855 }
1856 }
1857 }
1858
1859 }
1860
1861 }
1862
1863
1864
1865
1866
1867
1868 void fireTrafCODEvent(final EventType eventType, final Object[] payload)
1869 {
1870 fireTimedEvent(eventType, payload, getSimulator().getSimulatorTime());
1871 }
1872
1873 @Override
1874 public String getFullId()
1875 {
1876 return getId();
1877 }
1878
1879 @Override
1880 public Optional<Container> getDisplayContainer()
1881 {
1882 return Optional.of(this.displayContainer);
1883 }
1884
1885 @Override
1886 public String toString()
1887 {
1888 return "TrafCOD [id=" + getId() + "]";
1889 }
1890
1891 }
1892
1893
1894
1895
1896 class NameAndStream
1897 {
1898
1899 private final String name;
1900
1901
1902 private short stream = TrafficController.NO_STREAM;
1903
1904
1905 private int numberOfChars = 0;
1906
1907
1908 private boolean negated = false;
1909
1910
1911
1912
1913
1914
1915
1916 NameAndStream(final String text, final String locationDescription) throws TrafficControlException
1917 {
1918 int pos = 0;
1919 while (pos < text.length() && Character.isWhitespace(text.charAt(pos)))
1920 {
1921 pos++;
1922 }
1923 while (pos < text.length())
1924 {
1925 char character = text.charAt(pos);
1926 if (!Character.isLetterOrDigit(character))
1927 {
1928 break;
1929 }
1930 pos++;
1931 }
1932 this.numberOfChars = pos;
1933 String trimmed = text.substring(0, pos).replaceAll(" ", "");
1934 if (trimmed.length() == 0)
1935 {
1936 throw new TrafficControlException("missing variable at " + locationDescription);
1937 }
1938 if (trimmed.matches("^D([Nn]?\\d\\d\\d)|(\\d\\d\\d[Nn])"))
1939 {
1940
1941 if (trimmed.charAt(1) == 'N' || trimmed.charAt(1) == 'n')
1942 {
1943
1944 trimmed = "D" + trimmed.substring(2, 5) + "N" + trimmed.substring(5);
1945 this.negated = true;
1946 }
1947 this.name = "D" + trimmed.charAt(3);
1948 this.stream = (short) (10 * (trimmed.charAt(1) - '0') + trimmed.charAt(2) - '0');
1949 return;
1950 }
1951 StringBuilder nameBuilder = new StringBuilder();
1952 for (pos = 0; pos < trimmed.length(); pos++)
1953 {
1954 char nextChar = trimmed.charAt(pos);
1955 if (pos < trimmed.length() - 1 && Character.isDigit(nextChar) && Character.isDigit(trimmed.charAt(pos + 1))
1956 && TrafficController.NO_STREAM == this.stream)
1957 {
1958 if (0 == pos || (1 == pos && trimmed.startsWith("N")))
1959 {
1960 throw new TrafficControlException("Bad variable name: " + trimmed + " at " + locationDescription);
1961 }
1962 if (trimmed.charAt(pos - 1) == 'N')
1963 {
1964
1965 nameBuilder.deleteCharAt(nameBuilder.length() - 1);
1966
1967 trimmed =
1968 trimmed.substring(0, pos - 1) + trimmed.substring(pos, pos + 2) + trimmed.substring(pos + 2) + "N";
1969 pos--;
1970 }
1971 this.stream = (short) (10 * (trimmed.charAt(pos) - '0') + trimmed.charAt(pos + 1) - '0');
1972 pos++;
1973 }
1974 else
1975 {
1976 nameBuilder.append(nextChar);
1977 }
1978 }
1979 if (trimmed.endsWith("N"))
1980 {
1981 nameBuilder.deleteCharAt(nameBuilder.length() - 1);
1982 this.negated = true;
1983 }
1984 this.name = nameBuilder.toString();
1985 }
1986
1987
1988
1989
1990
1991 public boolean isNegated()
1992 {
1993 return this.negated;
1994 }
1995
1996
1997
1998
1999
2000 public short getStream()
2001 {
2002 return this.stream;
2003 }
2004
2005
2006
2007
2008
2009 public String getName()
2010 {
2011 return this.name;
2012 }
2013
2014
2015
2016
2017
2018 public int getNumberOfChars()
2019 {
2020 return this.numberOfChars;
2021 }
2022
2023 @Override
2024 public String toString()
2025 {
2026 return "NameAndStream [name=" + this.name + ", stream=" + this.stream + ", numberOfChars=" + this.numberOfChars
2027 + ", negated=" + this.negated + "]";
2028 }
2029
2030 }
2031
2032
2033
2034
2035 class Variable implements EventListener
2036 {
2037
2038 private final TrafCod trafCOD;
2039
2040
2041 private EnumSet<Flags> flags = EnumSet.noneOf(Flags.class);
2042
2043
2044 private int value;
2045
2046
2047 private int timerMax10;
2048
2049
2050 private TrafficLightColor color;
2051
2052
2053 private final String name;
2054
2055
2056 private final short stream;
2057
2058
2059 private int refCount;
2060
2061
2062 private int updateTime10;
2063
2064
2065 private String startSource;
2066
2067
2068 private String endSource;
2069
2070
2071 private Set<TrafficLight> trafficLights = new LinkedHashSet<>();
2072
2073
2074 private static String rowLetters = "ABCDXYZUVW";
2075
2076
2077
2078
2079
2080 public int getRefCount()
2081 {
2082 return this.refCount;
2083 }
2084
2085
2086
2087
2088
2089 public Set<TrafficLight> getTrafficLights()
2090 {
2091 return this.trafficLights;
2092 }
2093
2094
2095
2096
2097
2098
2099
2100 Variable(final String name, final short stream, final TrafCod trafCOD)
2101 {
2102 this.name = name.toUpperCase(Locale.US);
2103 this.stream = stream;
2104 this.trafCOD = trafCOD;
2105 if (this.name.startsWith("T"))
2106 {
2107 this.flags.add(Flags.IS_TIMER);
2108 }
2109 if (this.name.length() == 2 && this.name.startsWith("D") && Character.isDigit(this.name.charAt(1)))
2110 {
2111 this.flags.add(Flags.IS_DETECTOR);
2112 }
2113 if (TrafficController.NO_STREAM == stream && this.name.startsWith("MR") && this.name.length() == 3
2114 && rowLetters.indexOf(this.name.charAt(2)) >= 0)
2115 {
2116 this.flags.add(Flags.CONFLICT_GROUP);
2117 }
2118 }
2119
2120
2121
2122
2123
2124 public String getName()
2125 {
2126 return this.name;
2127 }
2128
2129
2130
2131
2132
2133
2134 public void subscribeToDetector(final TrafficLightDetector sensor) throws TrafficControlException
2135 {
2136 if (!isDetector())
2137 {
2138 throw new TrafficControlException("Cannot subscribe a non-detector to a TrafficLightSensor");
2139 }
2140 sensor.addListener(this, TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_ENTRY_EVENT);
2141 sensor.addListener(this, TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_EXIT_EVENT);
2142 }
2143
2144
2145
2146
2147 public void initialize()
2148 {
2149 if (this.flags.contains(Flags.INITED))
2150 {
2151 if (isTimer())
2152 {
2153 setValue(this.timerMax10, 0, new CausePrinter("Timer initialization rule"), this.trafCOD);
2154 }
2155 else
2156 {
2157 setValue(1, 0, new CausePrinter("Variable initialization rule"), this.trafCOD);
2158 }
2159 }
2160 }
2161
2162
2163
2164
2165
2166
2167
2168 public boolean decrementTimer(final int timeStamp10) throws TrafficControlException
2169 {
2170 if (!isTimer())
2171 {
2172 throw new TrafficControlException("Variable " + this + " is not a timer");
2173 }
2174 if (this.value <= 0)
2175 {
2176 return false;
2177 }
2178 if (0 == --this.value)
2179 {
2180 this.flags.add(Flags.CHANGED);
2181 this.flags.add(Flags.END);
2182 this.value = 0;
2183 this.updateTime10 = timeStamp10;
2184 if (this.flags.contains(Flags.TRACED))
2185 {
2186 Logger.ots().info("Timer {} expired", toString());
2187 }
2188 return true;
2189 }
2190 return false;
2191 }
2192
2193
2194
2195
2196
2197
2198 public TrafficLightColor getColor() throws TrafficControlException
2199 {
2200 if (!this.flags.contains(Flags.IS_OUTPUT))
2201 {
2202 throw new TrafficControlException("Stream " + this.toString() + "is not an output");
2203 }
2204 return this.color;
2205 }
2206
2207
2208
2209
2210
2211 public boolean isOutput()
2212 {
2213 return this.flags.contains(Flags.IS_OUTPUT);
2214 }
2215
2216
2217
2218
2219
2220 public boolean isConflictGroup()
2221 {
2222 return this.flags.contains(Flags.CONFLICT_GROUP);
2223 }
2224
2225
2226
2227
2228
2229
2230 public int conflictGroupRank() throws TrafficControlException
2231 {
2232 if (!isConflictGroup())
2233 {
2234 throw new TrafficControlException("Variable " + this + " is not a conflict group identifier");
2235 }
2236 return rowLetters.indexOf(this.name.charAt(2));
2237 }
2238
2239
2240
2241
2242
2243 public boolean isDetector()
2244 {
2245 return this.flags.contains(Flags.IS_DETECTOR);
2246 }
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256 public boolean setValue(final int newValue, final int timeStamp10, final CausePrinter cause,
2257 final TrafCod trafCODController)
2258 {
2259 boolean result = false;
2260 if (this.value != newValue)
2261 {
2262 this.updateTime10 = timeStamp10;
2263 setFlag(Flags.CHANGED);
2264 if (0 == newValue)
2265 {
2266 setFlag(Flags.END);
2267 result = true;
2268 }
2269 else if (!isTimer() || 0 == this.value)
2270 {
2271 setFlag(Flags.START);
2272 result = true;
2273 }
2274 if (isOutput() && newValue != 0)
2275 {
2276 for (TrafficLight trafficLight : this.trafficLights)
2277 {
2278 trafficLight.setTrafficLightColor(this.color);
2279 }
2280 }
2281 }
2282 if (this.flags.contains(Flags.TRACED))
2283 {
2284 Logger.ots().trace("Variable {} {} changed from {} to {}", this.name, this.stream, this.value, newValue);
2285
2286 trafCODController.fireTrafCODEvent(TrafficController.TRAFFICCONTROL_TRACED_VARIABLE_UPDATED,
2287 new Object[] {trafCODController.getId(), toString(EnumSet.of(PrintFlags.ID)), this.stream, this.value,
2288 newValue, cause.toString()});
2289 }
2290 this.value = newValue;
2291 return result;
2292 }
2293
2294
2295
2296
2297
2298
2299
2300 public void cloneState(final Variable fromVariable, final Network newNetwork) throws NetworkException
2301 {
2302 this.value = fromVariable.value;
2303 this.flags = EnumSet.copyOf(fromVariable.flags);
2304 this.updateTime10 = fromVariable.updateTime10;
2305 if (fromVariable.isOutput())
2306 {
2307 for (TrafficLight tl : fromVariable.trafficLights)
2308 {
2309 LocatedObject clonedTrafficLight = newNetwork.getObjectMap().get(tl.getId());
2310 if (null != clonedTrafficLight)
2311 {
2312 throw new NetworkException("newNetwork does not contain a clone of traffic light " + tl.getId());
2313 }
2314 if (clonedTrafficLight instanceof TrafficLight)
2315 {
2316 throw new NetworkException(
2317 "newNetwork contains an object with name " + tl.getId() + " but this object is not a TrafficLight");
2318 }
2319 this.trafficLights.add((TrafficLight) clonedTrafficLight);
2320 }
2321 }
2322 if (isOutput())
2323 {
2324 for (TrafficLight trafficLight : this.trafficLights)
2325 {
2326 trafficLight.setTrafficLightColor(this.color);
2327 }
2328 }
2329 }
2330
2331
2332
2333
2334
2335
2336 public int getTimerMax() throws TrafficControlException
2337 {
2338 if (!this.isTimer())
2339 {
2340 throw new TrafficControlException("This is not a timer");
2341 }
2342 return this.timerMax10;
2343 }
2344
2345
2346
2347
2348
2349 public int getValue()
2350 {
2351 return this.value;
2352 }
2353
2354
2355
2356
2357
2358 public void setFlag(final Flags flag)
2359 {
2360 this.flags.add(flag);
2361 }
2362
2363
2364
2365
2366
2367 public void clearFlag(final Flags flag)
2368 {
2369 this.flags.remove(flag);
2370 }
2371
2372
2373
2374
2375
2376 public boolean isTimer()
2377 {
2378 return this.flags.contains(Flags.IS_TIMER);
2379 }
2380
2381
2382
2383
2384 public void clearChangedFlag()
2385 {
2386 this.flags.remove(Flags.CHANGED);
2387 }
2388
2389
2390
2391
2392
2393 public void incrementReferenceCount()
2394 {
2395 this.refCount++;
2396 }
2397
2398
2399
2400
2401
2402 public EnumSet<Flags> getFlags()
2403 {
2404 return EnumSet.copyOf(this.flags);
2405 }
2406
2407
2408
2409
2410
2411
2412 public void setOutput(final int colorValue) throws TrafficControlException
2413 {
2414 if (null != this.color)
2415 {
2416 throw new TrafficControlException("setOutput has already been called for " + this);
2417 }
2418 if (null == this.trafficLights)
2419 {
2420 this.trafficLights = new LinkedHashSet<>();
2421 }
2422
2423 TrafficLightColor newColor;
2424 switch (colorValue)
2425 {
2426 case 'R':
2427 newColor = TrafficLightColor.RED;
2428 break;
2429 case 'G':
2430 newColor = TrafficLightColor.GREEN;
2431 break;
2432 case 'Y':
2433 newColor = TrafficLightColor.YELLOW;
2434 break;
2435 default:
2436 throw new TrafficControlException("Bad color value: " + colorValue);
2437 }
2438 this.color = newColor;
2439 this.flags.add(Flags.IS_OUTPUT);
2440 }
2441
2442
2443
2444
2445
2446
2447 public void addOutput(final TrafficLight trafficLight) throws TrafficControlException
2448 {
2449 if (!this.isOutput())
2450 {
2451 throw new TrafficControlException("Cannot add an output to an non-output variable");
2452 }
2453 this.trafficLights.add(trafficLight);
2454 }
2455
2456
2457
2458
2459
2460
2461 public void setTimerMax(final int value10) throws TrafficControlException
2462 {
2463 if (!this.flags.contains(Flags.IS_TIMER))
2464 {
2465 throw new TrafficControlException(
2466 "Cannot set maximum timer value of " + this.toString() + " because this is not a timer");
2467 }
2468 this.timerMax10 = value10;
2469 }
2470
2471
2472
2473
2474
2475 public String getStartSource()
2476 {
2477 return this.startSource;
2478 }
2479
2480
2481
2482
2483
2484
2485 public void setStartSource(final String startSource) throws TrafficControlException
2486 {
2487 if (null != this.startSource)
2488 {
2489 throw new TrafficControlException("Conflicting rules: " + this.startSource + " vs " + startSource);
2490 }
2491 this.startSource = startSource;
2492 this.flags.add(Flags.HAS_START_RULE);
2493 }
2494
2495
2496
2497
2498
2499 public String getEndSource()
2500 {
2501 return this.endSource;
2502 }
2503
2504
2505
2506
2507
2508
2509 public void setEndSource(final String endSource) throws TrafficControlException
2510 {
2511 if (null != this.endSource)
2512 {
2513 throw new TrafficControlException("Conflicting rules: " + this.startSource + " vs " + endSource);
2514 }
2515 this.endSource = endSource;
2516 this.flags.add(Flags.HAS_END_RULE);
2517 }
2518
2519
2520
2521
2522
2523 public short getStream()
2524 {
2525 return this.stream;
2526 }
2527
2528 @Override
2529 public String toString()
2530 {
2531 return "Variable [" + toString(EnumSet.of(PrintFlags.ID, PrintFlags.VALUE, PrintFlags.FLAGS)) + "]";
2532 }
2533
2534
2535
2536
2537
2538
2539 public String toString(final EnumSet<PrintFlags> printFlags)
2540 {
2541 StringBuilder result = new StringBuilder();
2542 if (printFlags.contains(PrintFlags.ID))
2543 {
2544 if (this.flags.contains(Flags.IS_DETECTOR))
2545 {
2546 result.append("D");
2547 }
2548 else if (isTimer() && printFlags.contains(PrintFlags.INITTIMER))
2549 {
2550 result.append("I");
2551 result.append(this.name);
2552 }
2553 else if (isTimer() && printFlags.contains(PrintFlags.REINITTIMER))
2554 {
2555 result.append("RI");
2556 result.append(this.name);
2557 }
2558 else
2559 {
2560 result.append(this.name);
2561 }
2562 if (this.stream > 0)
2563 {
2564
2565 int pos;
2566 for (pos = 0; pos < result.length(); pos++)
2567 {
2568 if (Character.isDigit(result.charAt(pos)))
2569 {
2570 break;
2571 }
2572 }
2573 result.insert(pos, String.format("%02d", this.stream));
2574 }
2575 if (this.flags.contains(Flags.IS_DETECTOR))
2576 {
2577 result.append(this.name.substring(1));
2578 }
2579 if (printFlags.contains(PrintFlags.NEGATED))
2580 {
2581 result.append("N");
2582 }
2583 }
2584 int printValue = Integer.MIN_VALUE;
2585 if (printFlags.contains(PrintFlags.VALUE))
2586 {
2587 if (printFlags.contains(PrintFlags.NEGATED))
2588 {
2589 printValue = 0 == this.value ? 1 : 0;
2590 }
2591 else
2592 {
2593 printValue = this.value;
2594 }
2595 if (printFlags.contains(PrintFlags.S))
2596 {
2597 if (this.flags.contains(Flags.START))
2598 {
2599 printValue = 1;
2600 }
2601 else
2602 {
2603 printValue = 0;
2604 }
2605 }
2606 if (printFlags.contains(PrintFlags.E))
2607 {
2608 if (this.flags.contains(Flags.END))
2609 {
2610 printValue = 1;
2611 }
2612 else
2613 {
2614 printValue = 0;
2615 }
2616 }
2617 }
2618 if (printFlags.contains(PrintFlags.VALUE) || printFlags.contains(PrintFlags.S) || printFlags.contains(PrintFlags.E)
2619 || printFlags.contains(PrintFlags.FLAGS))
2620 {
2621 result.append("<");
2622 if (printFlags.contains(PrintFlags.VALUE) || printFlags.contains(PrintFlags.S) || printFlags.contains(PrintFlags.E))
2623 {
2624 result.append(printValue);
2625 }
2626 if (printFlags.contains(PrintFlags.FLAGS))
2627 {
2628 if (this.flags.contains(Flags.START))
2629 {
2630 result.append("S");
2631 }
2632 if (this.flags.contains(Flags.END))
2633 {
2634 result.append("E");
2635 }
2636 }
2637 result.append(">");
2638 }
2639 if (printFlags.contains(PrintFlags.MODIFY_TIME))
2640 {
2641 result.append(String.format(" (%d.%d)", this.updateTime10 / 10, this.updateTime10 % 10));
2642 }
2643 return result.toString();
2644 }
2645
2646 @Override
2647 public void notify(final Event event)
2648 {
2649 if (event.getType().equals(TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_ENTRY_EVENT))
2650 {
2651 setValue(1, this.updateTime10, new CausePrinter("Detector became occupied"), this.trafCOD);
2652 }
2653 else if (event.getType().equals(TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_EXIT_EVENT))
2654 {
2655 setValue(0, this.updateTime10, new CausePrinter("Detector became unoccupied"), this.trafCOD);
2656 }
2657 }
2658
2659 }
2660
2661
2662
2663
2664
2665 class CausePrinter
2666 {
2667
2668 private final Object cause;
2669
2670
2671
2672
2673
2674 CausePrinter(final Object cause)
2675 {
2676 this.cause = cause;
2677 }
2678
2679 @Override
2680 public String toString()
2681 {
2682 if (this.cause instanceof String)
2683 {
2684 return (String) this.cause;
2685 }
2686 else if (this.cause instanceof Object[])
2687 {
2688 try
2689 {
2690 return TrafCod.printRule((Object[]) this.cause, true);
2691 }
2692 catch (TrafficControlException exception)
2693 {
2694 exception.printStackTrace();
2695 return ("printRule failed");
2696 }
2697 }
2698 return this.cause.toString();
2699 }
2700 }
2701
2702
2703
2704
2705 enum PrintFlags
2706 {
2707
2708 ID,
2709
2710 VALUE,
2711
2712 INITTIMER,
2713
2714 REINITTIMER,
2715
2716 S,
2717
2718 E,
2719
2720 NEGATED,
2721
2722 FLAGS,
2723
2724 MODIFY_TIME,
2725 }
2726
2727
2728
2729
2730 enum Flags
2731 {
2732
2733 START,
2734
2735 END,
2736
2737 TIMEREXPIRED,
2738
2739 CHANGED,
2740
2741 IS_TIMER,
2742
2743 IS_DETECTOR,
2744
2745 HAS_START_RULE,
2746
2747 HAS_END_RULE,
2748
2749 IS_OUTPUT,
2750
2751 INITED,
2752
2753 TRACED,
2754
2755 CONFLICT_GROUP,
2756 }