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