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