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