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 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
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
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
708
709
710
711
712
713
714
715
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
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, this, "evalExprs", null);
749 }
750
751
752
753
754
755
756 private int evalExpressionsOnce() throws TrafficControlException
757 {
758 for (Variable variable : this.variables.values())
759 {
760 variable.clearChangedFlag();
761 }
762 int changeCount = 0;
763 for (Object[] rule : this.tokenisedRules)
764 {
765 if (evalRule(rule))
766 {
767 changeCount++;
768 }
769 }
770 return changeCount;
771 }
772
773
774
775
776
777
778
779 private boolean evalRule(final Object[] rule) throws TrafficControlException
780 {
781 boolean result = false;
782 Token ruleType = (Token) rule[0];
783 Variable destination = (Variable) rule[1];
784 if (destination.isTimer())
785 {
786 if (destination.getFlags().contains(Flags.TIMEREXPIRED))
787 {
788 destination.clearFlag(Flags.TIMEREXPIRED);
789 destination.setFlag(Flags.END);
790 }
791 else if (destination.getFlags().contains(Flags.START) || destination.getFlags().contains(Flags.END))
792 {
793 destination.clearFlag(Flags.START);
794 destination.clearFlag(Flags.END);
795 destination.setFlag(Flags.CHANGED);
796 }
797 }
798 else
799 {
800
801 if (Token.START_RULE == ruleType)
802 {
803 destination.clearFlag(Flags.START);
804 }
805 else if (Token.END_RULE == ruleType)
806 {
807 destination.clearFlag(Flags.END);
808 }
809 else
810 {
811 destination.clearFlag(Flags.START);
812 destination.clearFlag(Flags.END);
813 }
814 }
815
816 int currentValue = destination.getValue();
817 if (Token.START_RULE == ruleType && currentValue != 0 || Token.END == ruleType && currentValue == 0
818 || Token.INIT_TIMER == ruleType && currentValue != 0)
819 {
820 return false;
821 }
822 this.currentRule = rule;
823 this.currentToken = 2;
824 this.stack.clear();
825 evalExpr(0);
826 if (this.currentToken < this.currentRule.length && Token.CLOSE_PAREN == this.currentRule[this.currentToken])
827 {
828 throw new TrafficControlException("Too many closing parentheses");
829 }
830 int resultValue = pop();
831 if (Token.END_RULE == ruleType)
832 {
833
834 if (0 == resultValue)
835 {
836 resultValue = destination.getValue();
837 }
838 else
839 {
840 resultValue = 0;
841 }
842 }
843 if (resultValue != 0 && destination.getValue() == 0)
844 {
845 destination.setFlag(Flags.START);
846 }
847 else if (resultValue == 0 && destination.getValue() != 0)
848 {
849 destination.setFlag(Flags.END);
850 }
851 if (destination.isTimer())
852 {
853 if (resultValue != 0 && Token.END_RULE != ruleType)
854 {
855 if (0 == destination.getValue())
856 {
857 result = true;
858 }
859 int timerValue10 = destination.getTimerMax();
860 if (timerValue10 < 1)
861 {
862
863 timerValue10 = 1;
864 }
865 result = destination.setValue(timerValue10, this.currentTime10, new CausePrinter(rule), this);
866 }
867 else if (0 == resultValue && Token.END_RULE == ruleType && destination.getValue() != 0)
868 {
869 result = destination.setValue(0, this.currentTime10, new CausePrinter(rule), this);
870 }
871 }
872 else if (destination.getValue() != resultValue)
873 {
874 result = destination.setValue(resultValue, this.currentTime10, new CausePrinter(rule), this);
875 if (destination.isOutput())
876 {
877 fireTimedEvent(TRAFFIC_LIGHT_CHANGED, new Object[] {getId(), destination.getStream(), destination.getColor()},
878 getSimulator().getSimulatorAbsTime());
879 }
880 if (destination.isConflictGroup() && resultValue != 0)
881 {
882 int conflictGroupRank = destination.conflictGroupRank();
883 StringBuilder conflictGroupList = new StringBuilder();
884 for (Short stream : this.conflictGroups.get(conflictGroupRank))
885 {
886 if (conflictGroupList.length() > 0)
887 {
888 conflictGroupList.append(" ");
889 }
890 conflictGroupList.append(String.format("%02d", stream));
891 }
892 fireTimedEvent(TRAFFICCONTROL_CONFLICT_GROUP_CHANGED,
893 new Object[] {getId(), this.currentConflictGroup, conflictGroupList.toString()},
894 getSimulator().getSimulatorTime());
895
896
897 this.currentConflictGroup = conflictGroupList.toString();
898 }
899 }
900 return result;
901 }
902
903
904 private static final int BIND_RELATIONAL_OPERATOR = 1;
905
906
907 private static final int BIND_ADDITION = 2;
908
909
910 private static final int BIND_MULTIPLY = 3;
911
912
913 private static final int BIND_UNARY_MINUS = 4;
914
915
916
917
918
919
920
921
922
923
924
925
926 private void evalExpr(final int bindingStrength) throws TrafficControlException
927 {
928 if (this.currentToken >= this.currentRule.length)
929 {
930 throw new TrafficControlException("Missing operand at end of expression " + printRule(this.currentRule, false));
931 }
932 Token token = (Token) this.currentRule[this.currentToken++];
933 Object nextToken = null;
934 if (this.currentToken < this.currentRule.length)
935 {
936 nextToken = this.currentRule[this.currentToken];
937 }
938 switch (token)
939 {
940 case UNARY_MINUS:
941 if (Token.OPEN_PAREN != nextToken && Token.VARIABLE != nextToken && Token.NEG_VARIABLE != nextToken
942 && Token.CONSTANT != nextToken && Token.START != nextToken && Token.END != nextToken)
943 {
944 throw new TrafficControlException("Operand expected after unary minus");
945 }
946 evalExpr(BIND_UNARY_MINUS);
947 push(-pop());
948 break;
949
950 case OPEN_PAREN:
951 evalExpr(0);
952 if (Token.CLOSE_PAREN != this.currentRule[this.currentToken])
953 {
954 throw new TrafficControlException("Missing closing parenthesis");
955 }
956 this.currentToken++;
957 break;
958
959 case START:
960 if (Token.VARIABLE != nextToken || this.currentToken >= this.currentRule.length - 1)
961 {
962 throw new TrafficControlException("Missing variable after S");
963 }
964 nextToken = this.currentRule[++this.currentToken];
965 if (!(nextToken instanceof Variable))
966 {
967 throw new TrafficControlException("Missing variable after S");
968 }
969 push(((Variable) nextToken).getFlags().contains(Flags.START) ? 1 : 0);
970 this.currentToken++;
971 break;
972
973 case END:
974 if (Token.VARIABLE != nextToken || this.currentToken >= this.currentRule.length - 1)
975 {
976 throw new TrafficControlException("Missing variable after E");
977 }
978 nextToken = this.currentRule[++this.currentToken];
979 if (!(nextToken instanceof Variable))
980 {
981 throw new TrafficControlException("Missing variable after E");
982 }
983 push(((Variable) nextToken).getFlags().contains(Flags.END) ? 1 : 0);
984 this.currentToken++;
985 break;
986
987 case VARIABLE:
988 {
989 Variable operand = (Variable) nextToken;
990 if (operand.isTimer())
991 {
992 push(operand.getValue() == 0 ? 0 : 1);
993 }
994 else
995 {
996 push(operand.getValue());
997 }
998 this.currentToken++;
999 break;
1000 }
1001
1002 case CONSTANT:
1003 push((Integer) nextToken);
1004 this.currentToken++;
1005 break;
1006
1007 case NEG_VARIABLE:
1008 Variable operand = (Variable) nextToken;
1009 push(operand.getValue() == 0 ? 1 : 0);
1010 this.currentToken++;
1011 break;
1012
1013 default:
1014 throw new TrafficControlException("Operand missing");
1015 }
1016 evalRHS(bindingStrength);
1017 }
1018
1019
1020
1021
1022
1023
1024 private void evalRHS(final int bindingStrength) throws TrafficControlException
1025 {
1026 while (true)
1027 {
1028 if (this.currentToken >= this.currentRule.length)
1029 {
1030 return;
1031 }
1032 Token token = (Token) this.currentRule[this.currentToken];
1033 switch (token)
1034 {
1035 case CLOSE_PAREN:
1036 return;
1037
1038 case TIMES:
1039 if (BIND_MULTIPLY <= bindingStrength)
1040 {
1041 return;
1042 }
1043
1044
1045
1046
1047
1048 this.currentToken++;
1049 evalExpr(BIND_MULTIPLY);
1050 push(pop() * pop() == 0 ? 0 : 1);
1051 break;
1052
1053 case EQ:
1054 case NOTEQ:
1055 case LE:
1056 case LEEQ:
1057 case GT:
1058 case GTEQ:
1059 if (BIND_RELATIONAL_OPERATOR <= bindingStrength)
1060 {
1061 return;
1062 }
1063
1064
1065
1066
1067
1068 this.currentToken++;
1069 evalExpr(BIND_RELATIONAL_OPERATOR);
1070 switch (token)
1071 {
1072 case EQ:
1073 push(pop() == pop() ? 1 : 0);
1074 break;
1075
1076 case NOTEQ:
1077 push(pop() != pop() ? 1 : 0);
1078 break;
1079
1080 case GT:
1081 push(pop() < pop() ? 1 : 0);
1082 break;
1083
1084 case GTEQ:
1085 push(pop() <= pop() ? 1 : 0);
1086 break;
1087
1088 case LE:
1089 push(pop() > pop() ? 1 : 0);
1090 break;
1091
1092 case LEEQ:
1093 push(pop() >= pop() ? 1 : 0);
1094 break;
1095
1096 default:
1097 throw new TrafficControlException("Bad relational operator");
1098 }
1099 break;
1100
1101 case PLUS:
1102 if (BIND_ADDITION <= bindingStrength)
1103 {
1104 return;
1105 }
1106
1107
1108
1109
1110
1111 this.currentToken++;
1112 evalExpr(BIND_ADDITION);
1113 push(pop() + pop() == 0 ? 0 : 1);
1114 break;
1115
1116 case MINUS:
1117 if (BIND_ADDITION <= bindingStrength)
1118 {
1119 return;
1120 }
1121
1122
1123
1124
1125
1126 this.currentToken++;
1127 evalExpr(BIND_ADDITION);
1128 push(-pop() + pop());
1129 break;
1130
1131 default:
1132 throw new TrafficControlException("Missing binary operator");
1133 }
1134 }
1135 }
1136
1137
1138
1139
1140
1141 private void push(final int value)
1142 {
1143 this.stack.add(value);
1144 }
1145
1146
1147
1148
1149
1150
1151 private int pop() throws TrafficControlException
1152 {
1153 if (this.stack.size() < 1)
1154 {
1155 throw new TrafficControlException("Stack empty");
1156 }
1157 return this.stack.remove(this.stack.size() - 1);
1158 }
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168 static String printRule(final Object[] tokens, final boolean printValues) throws TrafficControlException
1169 {
1170 EnumSet<PrintFlags> variableFlags = EnumSet.of(PrintFlags.ID);
1171 if (printValues)
1172 {
1173 variableFlags.add(PrintFlags.VALUE);
1174 }
1175 EnumSet<PrintFlags> negatedVariableFlags = EnumSet.copyOf(variableFlags);
1176 negatedVariableFlags.add(PrintFlags.NEGATED);
1177 StringBuilder result = new StringBuilder();
1178 for (int inPos = 0; inPos < tokens.length; inPos++)
1179 {
1180 Object token = tokens[inPos];
1181 if (token instanceof Token)
1182 {
1183 switch ((Token) token)
1184 {
1185 case EQUALS_RULE:
1186 result.append(((Variable) tokens[++inPos]).toString(variableFlags));
1187 result.append("=");
1188 break;
1189
1190 case NEG_EQUALS_RULE:
1191 result.append(((Variable) tokens[++inPos]).toString(negatedVariableFlags));
1192 result.append("=");
1193 break;
1194
1195 case START_RULE:
1196 result.append(((Variable) tokens[++inPos]).toString(variableFlags));
1197 result.append(".=");
1198 break;
1199
1200 case END_RULE:
1201 result.append(((Variable) tokens[++inPos]).toString(variableFlags));
1202 result.append("N.=");
1203 break;
1204
1205 case INIT_TIMER:
1206 result.append(((Variable) tokens[++inPos]).toString(EnumSet.of(PrintFlags.ID, PrintFlags.INITTIMER)));
1207 result.append(".=");
1208 break;
1209
1210 case REINIT_TIMER:
1211 result.append(((Variable) tokens[++inPos]).toString(EnumSet.of(PrintFlags.ID, PrintFlags.REINITTIMER)));
1212 result.append(".=");
1213 break;
1214
1215 case START:
1216 result.append("S");
1217 break;
1218
1219 case END:
1220 result.append("E");
1221 break;
1222
1223 case VARIABLE:
1224 result.append(((Variable) tokens[++inPos]).toString(variableFlags));
1225 break;
1226
1227 case NEG_VARIABLE:
1228 result.append(((Variable) tokens[++inPos]).toString(variableFlags));
1229 result.append("N");
1230 break;
1231
1232 case CONSTANT:
1233 result.append(tokens[++inPos]).toString();
1234 break;
1235
1236 case UNARY_MINUS:
1237 case MINUS:
1238 result.append("-");
1239 break;
1240
1241 case PLUS:
1242 result.append("+");
1243 break;
1244
1245 case TIMES:
1246 result.append(".");
1247 break;
1248
1249 case EQ:
1250 result.append("=");
1251 break;
1252
1253 case NOTEQ:
1254 result.append("<>");
1255 break;
1256
1257 case GT:
1258 result.append(">");
1259 break;
1260
1261 case GTEQ:
1262 result.append(">=");
1263 break;
1264
1265 case LE:
1266 result.append("<");
1267 break;
1268
1269 case LEEQ:
1270 result.append("<=");
1271 break;
1272
1273 case OPEN_PAREN:
1274 result.append("(");
1275 break;
1276
1277 case CLOSE_PAREN:
1278 result.append(")");
1279 break;
1280
1281 default:
1282 System.out.println(
1283 "<<<ERROR>>> encountered a non-Token object: " + token + " after " + result.toString());
1284 throw new TrafficControlException("Unknown token");
1285 }
1286 }
1287 else
1288 {
1289 System.out.println("<<<ERROR>>> encountered a non-Token object: " + token + " after " + 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) throws RemoteException
1801 {
1802 System.out.println("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 System.err.println("TrafCOD controller " + getId() + " received event with bad payload (" + 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 System.err.println("TrafCOD controller " + getId() + " received event with bad payload (" + 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 System.err.println("Received trace notification for nonexistent variable (name=\"" + name
1829 + "\", stream=" + 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 Container getDisplayContainer()
1881 {
1882 return 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 static final long serialVersionUID = 20200313L;
2039
2040
2041 private final TrafCod trafCOD;
2042
2043
2044 private EnumSet<Flags> flags = EnumSet.noneOf(Flags.class);
2045
2046
2047 private int value;
2048
2049
2050 private int timerMax10;
2051
2052
2053 private TrafficLightColor color;
2054
2055
2056 private final String name;
2057
2058
2059 private final short stream;
2060
2061
2062 private int refCount;
2063
2064
2065 private int updateTime10;
2066
2067
2068 private String startSource;
2069
2070
2071 private String endSource;
2072
2073
2074 private Set<TrafficLight> trafficLights;
2075
2076
2077 private static String rowLetters = "ABCDXYZUVW";
2078
2079
2080
2081
2082
2083 public int getRefCount()
2084 {
2085 return this.refCount;
2086 }
2087
2088
2089
2090
2091
2092 public Set<TrafficLight> getTrafficLights()
2093 {
2094 return this.trafficLights;
2095 }
2096
2097
2098
2099
2100
2101
2102
2103 Variable(final String name, final short stream, final TrafCod trafCOD)
2104 {
2105 this.name = name.toUpperCase(Locale.US);
2106 this.stream = stream;
2107 this.trafCOD = trafCOD;
2108 if (this.name.startsWith("T"))
2109 {
2110 this.flags.add(Flags.IS_TIMER);
2111 }
2112 if (this.name.length() == 2 && this.name.startsWith("D") && Character.isDigit(this.name.charAt(1)))
2113 {
2114 this.flags.add(Flags.IS_DETECTOR);
2115 }
2116 if (TrafficController.NO_STREAM == stream && this.name.startsWith("MR") && this.name.length() == 3
2117 && rowLetters.indexOf(this.name.charAt(2)) >= 0)
2118 {
2119 this.flags.add(Flags.CONFLICT_GROUP);
2120 }
2121 }
2122
2123
2124
2125
2126
2127 public String getName()
2128 {
2129 return this.name;
2130 }
2131
2132
2133
2134
2135
2136
2137 public void subscribeToDetector(final TrafficLightDetector sensor) throws TrafficControlException
2138 {
2139 if (!isDetector())
2140 {
2141 throw new TrafficControlException("Cannot subscribe a non-detector to a TrafficLightSensor");
2142 }
2143 sensor.addListener(this, TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_ENTRY_EVENT);
2144 sensor.addListener(this, TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_EXIT_EVENT);
2145 }
2146
2147
2148
2149
2150 public void initialize()
2151 {
2152 if (this.flags.contains(Flags.INITED))
2153 {
2154 if (isTimer())
2155 {
2156 setValue(this.timerMax10, 0, new CausePrinter("Timer initialization rule"), this.trafCOD);
2157 }
2158 else
2159 {
2160 setValue(1, 0, new CausePrinter("Variable initialization rule"), this.trafCOD);
2161 }
2162 }
2163 }
2164
2165
2166
2167
2168
2169
2170
2171 public boolean decrementTimer(final int timeStamp10) throws TrafficControlException
2172 {
2173 if (!isTimer())
2174 {
2175 throw new TrafficControlException("Variable " + this + " is not a timer");
2176 }
2177 if (this.value <= 0)
2178 {
2179 return false;
2180 }
2181 if (0 == --this.value)
2182 {
2183 this.flags.add(Flags.CHANGED);
2184 this.flags.add(Flags.END);
2185 this.value = 0;
2186 this.updateTime10 = timeStamp10;
2187 if (this.flags.contains(Flags.TRACED))
2188 {
2189 System.out.println("Timer " + toString() + " expired");
2190 }
2191 return true;
2192 }
2193 return false;
2194 }
2195
2196
2197
2198
2199
2200
2201 public TrafficLightColor getColor() throws TrafficControlException
2202 {
2203 if (!this.flags.contains(Flags.IS_OUTPUT))
2204 {
2205 throw new TrafficControlException("Stream " + this.toString() + "is not an output");
2206 }
2207 return this.color;
2208 }
2209
2210
2211
2212
2213
2214 public boolean isOutput()
2215 {
2216 return this.flags.contains(Flags.IS_OUTPUT);
2217 }
2218
2219
2220
2221
2222
2223 public boolean isConflictGroup()
2224 {
2225 return this.flags.contains(Flags.CONFLICT_GROUP);
2226 }
2227
2228
2229
2230
2231
2232
2233 public int conflictGroupRank() throws TrafficControlException
2234 {
2235 if (!isConflictGroup())
2236 {
2237 throw new TrafficControlException("Variable " + this + " is not a conflict group identifier");
2238 }
2239 return rowLetters.indexOf(this.name.charAt(2));
2240 }
2241
2242
2243
2244
2245
2246 public boolean isDetector()
2247 {
2248 return this.flags.contains(Flags.IS_DETECTOR);
2249 }
2250
2251
2252
2253
2254
2255
2256
2257
2258 public boolean setValue(final int newValue, final int timeStamp10, final CausePrinter cause,
2259 final TrafCod trafCODController)
2260 {
2261 boolean result = false;
2262 if (this.value != newValue)
2263 {
2264 this.updateTime10 = timeStamp10;
2265 setFlag(Flags.CHANGED);
2266 if (0 == newValue)
2267 {
2268 setFlag(Flags.END);
2269 result = true;
2270 }
2271 else if (!isTimer() || 0 == this.value)
2272 {
2273 setFlag(Flags.START);
2274 result = true;
2275 }
2276 if (isOutput() && newValue != 0)
2277 {
2278 for (TrafficLight trafficLight : this.trafficLights)
2279 {
2280 trafficLight.setTrafficLightColor(this.color);
2281 }
2282 }
2283 }
2284 if (this.flags.contains(Flags.TRACED))
2285 {
2286
2287
2288 trafCODController.fireTrafCODEvent(TrafficController.TRAFFICCONTROL_TRACED_VARIABLE_UPDATED,
2289 new Object[] {trafCODController.getId(), toString(EnumSet.of(PrintFlags.ID)), this.stream, this.value,
2290 newValue, cause.toString()});
2291 }
2292 this.value = newValue;
2293 return result;
2294 }
2295
2296
2297
2298
2299
2300
2301
2302 public void cloneState(final Variable fromVariable, final Network newNetwork) throws NetworkException
2303 {
2304 this.value = fromVariable.value;
2305 this.flags = EnumSet.copyOf(fromVariable.flags);
2306 this.updateTime10 = fromVariable.updateTime10;
2307 if (fromVariable.isOutput())
2308 {
2309 for (TrafficLight tl : fromVariable.trafficLights)
2310 {
2311 LocatedObject clonedTrafficLight = newNetwork.getObjectMap().get(tl.getId());
2312 if (null != clonedTrafficLight)
2313 {
2314 throw new NetworkException("newNetwork does not contain a clone of traffic light " + tl.getId());
2315 }
2316 if (clonedTrafficLight instanceof TrafficLight)
2317 {
2318 throw new NetworkException(
2319 "newNetwork contains an object with name " + tl.getId() + " but this object is not a TrafficLight");
2320 }
2321 this.trafficLights.add((TrafficLight) clonedTrafficLight);
2322 }
2323 }
2324 if (isOutput())
2325 {
2326 for (TrafficLight trafficLight : this.trafficLights)
2327 {
2328 trafficLight.setTrafficLightColor(this.color);
2329 }
2330 }
2331 }
2332
2333
2334
2335
2336
2337
2338 public int getTimerMax() throws TrafficControlException
2339 {
2340 if (!this.isTimer())
2341 {
2342 throw new TrafficControlException("This is not a timer");
2343 }
2344 return this.timerMax10;
2345 }
2346
2347
2348
2349
2350
2351 public int getValue()
2352 {
2353 return this.value;
2354 }
2355
2356
2357
2358
2359
2360 public void setFlag(final Flags flag)
2361 {
2362 this.flags.add(flag);
2363 }
2364
2365
2366
2367
2368
2369 public void clearFlag(final Flags flag)
2370 {
2371 this.flags.remove(flag);
2372 }
2373
2374
2375
2376
2377
2378 public boolean isTimer()
2379 {
2380 return this.flags.contains(Flags.IS_TIMER);
2381 }
2382
2383
2384
2385
2386 public void clearChangedFlag()
2387 {
2388 this.flags.remove(Flags.CHANGED);
2389 }
2390
2391
2392
2393
2394
2395 public void incrementReferenceCount()
2396 {
2397 this.refCount++;
2398 }
2399
2400
2401
2402
2403
2404 public EnumSet<Flags> getFlags()
2405 {
2406 return EnumSet.copyOf(this.flags);
2407 }
2408
2409
2410
2411
2412
2413
2414 public void setOutput(final int colorValue) throws TrafficControlException
2415 {
2416 if (null != this.color)
2417 {
2418 throw new TrafficControlException("setOutput has already been called for " + this);
2419 }
2420 if (null == this.trafficLights)
2421 {
2422 this.trafficLights = new LinkedHashSet<>();
2423 }
2424
2425 TrafficLightColor newColor;
2426 switch (colorValue)
2427 {
2428 case 'R':
2429 newColor = TrafficLightColor.RED;
2430 break;
2431 case 'G':
2432 newColor = TrafficLightColor.GREEN;
2433 break;
2434 case 'Y':
2435 newColor = TrafficLightColor.YELLOW;
2436 break;
2437 default:
2438 throw new TrafficControlException("Bad color value: " + colorValue);
2439 }
2440 this.color = newColor;
2441 this.flags.add(Flags.IS_OUTPUT);
2442 }
2443
2444
2445
2446
2447
2448
2449 public void addOutput(final TrafficLight trafficLight) throws TrafficControlException
2450 {
2451 if (!this.isOutput())
2452 {
2453 throw new TrafficControlException("Cannot add an output to an non-output variable");
2454 }
2455 this.trafficLights.add(trafficLight);
2456 }
2457
2458
2459
2460
2461
2462
2463 public void setTimerMax(final int value10) throws TrafficControlException
2464 {
2465 if (!this.flags.contains(Flags.IS_TIMER))
2466 {
2467 throw new TrafficControlException(
2468 "Cannot set maximum timer value of " + this.toString() + " because this is not a timer");
2469 }
2470 this.timerMax10 = value10;
2471 }
2472
2473
2474
2475
2476
2477 public String getStartSource()
2478 {
2479 return this.startSource;
2480 }
2481
2482
2483
2484
2485
2486
2487 public void setStartSource(final String startSource) throws TrafficControlException
2488 {
2489 if (null != this.startSource)
2490 {
2491 throw new TrafficControlException("Conflicting rules: " + this.startSource + " vs " + startSource);
2492 }
2493 this.startSource = startSource;
2494 this.flags.add(Flags.HAS_START_RULE);
2495 }
2496
2497
2498
2499
2500
2501 public String getEndSource()
2502 {
2503 return this.endSource;
2504 }
2505
2506
2507
2508
2509
2510
2511 public void setEndSource(final String endSource) throws TrafficControlException
2512 {
2513 if (null != this.endSource)
2514 {
2515 throw new TrafficControlException("Conflicting rules: " + this.startSource + " vs " + endSource);
2516 }
2517 this.endSource = endSource;
2518 this.flags.add(Flags.HAS_END_RULE);
2519 }
2520
2521
2522
2523
2524
2525 public short getStream()
2526 {
2527 return this.stream;
2528 }
2529
2530 @Override
2531 public String toString()
2532 {
2533 return "Variable [" + toString(EnumSet.of(PrintFlags.ID, PrintFlags.VALUE, PrintFlags.FLAGS)) + "]";
2534 }
2535
2536
2537
2538
2539
2540
2541 public String toString(final EnumSet<PrintFlags> printFlags)
2542 {
2543 StringBuilder result = new StringBuilder();
2544 if (printFlags.contains(PrintFlags.ID))
2545 {
2546 if (this.flags.contains(Flags.IS_DETECTOR))
2547 {
2548 result.append("D");
2549 }
2550 else if (isTimer() && printFlags.contains(PrintFlags.INITTIMER))
2551 {
2552 result.append("I");
2553 result.append(this.name);
2554 }
2555 else if (isTimer() && printFlags.contains(PrintFlags.REINITTIMER))
2556 {
2557 result.append("RI");
2558 result.append(this.name);
2559 }
2560 else
2561 {
2562 result.append(this.name);
2563 }
2564 if (this.stream > 0)
2565 {
2566
2567 int pos;
2568 for (pos = 0; pos < result.length(); pos++)
2569 {
2570 if (Character.isDigit(result.charAt(pos)))
2571 {
2572 break;
2573 }
2574 }
2575 result.insert(pos, String.format("%02d", this.stream));
2576 }
2577 if (this.flags.contains(Flags.IS_DETECTOR))
2578 {
2579 result.append(this.name.substring(1));
2580 }
2581 if (printFlags.contains(PrintFlags.NEGATED))
2582 {
2583 result.append("N");
2584 }
2585 }
2586 int printValue = Integer.MIN_VALUE;
2587 if (printFlags.contains(PrintFlags.VALUE))
2588 {
2589 if (printFlags.contains(PrintFlags.NEGATED))
2590 {
2591 printValue = 0 == this.value ? 1 : 0;
2592 }
2593 else
2594 {
2595 printValue = this.value;
2596 }
2597 if (printFlags.contains(PrintFlags.S))
2598 {
2599 if (this.flags.contains(Flags.START))
2600 {
2601 printValue = 1;
2602 }
2603 else
2604 {
2605 printValue = 0;
2606 }
2607 }
2608 if (printFlags.contains(PrintFlags.E))
2609 {
2610 if (this.flags.contains(Flags.END))
2611 {
2612 printValue = 1;
2613 }
2614 else
2615 {
2616 printValue = 0;
2617 }
2618 }
2619 }
2620 if (printFlags.contains(PrintFlags.VALUE) || printFlags.contains(PrintFlags.S) || printFlags.contains(PrintFlags.E)
2621 || printFlags.contains(PrintFlags.FLAGS))
2622 {
2623 result.append("<");
2624 if (printFlags.contains(PrintFlags.VALUE) || printFlags.contains(PrintFlags.S) || printFlags.contains(PrintFlags.E))
2625 {
2626 result.append(printValue);
2627 }
2628 if (printFlags.contains(PrintFlags.FLAGS))
2629 {
2630 if (this.flags.contains(Flags.START))
2631 {
2632 result.append("S");
2633 }
2634 if (this.flags.contains(Flags.END))
2635 {
2636 result.append("E");
2637 }
2638 }
2639 result.append(">");
2640 }
2641 if (printFlags.contains(PrintFlags.MODIFY_TIME))
2642 {
2643 result.append(String.format(" (%d.%d)", this.updateTime10 / 10, this.updateTime10 % 10));
2644 }
2645 return result.toString();
2646 }
2647
2648 @Override
2649 public void notify(final Event event) throws RemoteException
2650 {
2651 if (event.getType().equals(TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_ENTRY_EVENT))
2652 {
2653 setValue(1, this.updateTime10, new CausePrinter("Detector became occupied"), this.trafCOD);
2654 }
2655 else if (event.getType().equals(TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_EXIT_EVENT))
2656 {
2657 setValue(0, this.updateTime10, new CausePrinter("Detector became unoccupied"), this.trafCOD);
2658 }
2659 }
2660
2661 }
2662
2663
2664
2665
2666
2667 class CausePrinter
2668 {
2669
2670 private final Object cause;
2671
2672
2673
2674
2675
2676 CausePrinter(final Object cause)
2677 {
2678 this.cause = cause;
2679 }
2680
2681 @Override
2682 public String toString()
2683 {
2684 if (this.cause instanceof String)
2685 {
2686 return (String) this.cause;
2687 }
2688 else if (this.cause instanceof Object[])
2689 {
2690 try
2691 {
2692 return TrafCod.printRule((Object[]) this.cause, true);
2693 }
2694 catch (TrafficControlException exception)
2695 {
2696 exception.printStackTrace();
2697 return ("printRule failed");
2698 }
2699 }
2700 return this.cause.toString();
2701 }
2702 }
2703
2704
2705
2706
2707 enum PrintFlags
2708 {
2709
2710 ID,
2711
2712 VALUE,
2713
2714 INITTIMER,
2715
2716 REINITTIMER,
2717
2718 S,
2719
2720 E,
2721
2722 NEGATED,
2723
2724 FLAGS,
2725
2726 MODIFY_TIME,
2727 }
2728
2729
2730
2731
2732 enum Flags
2733 {
2734
2735 START,
2736
2737 END,
2738
2739 TIMEREXPIRED,
2740
2741 CHANGED,
2742
2743 IS_TIMER,
2744
2745 IS_DETECTOR,
2746
2747 HAS_START_RULE,
2748
2749 HAS_END_RULE,
2750
2751 IS_OUTPUT,
2752
2753 INITED,
2754
2755 TRACED,
2756
2757 CONFLICT_GROUP,
2758 }