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