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