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