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