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