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