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