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