View Javadoc
1   package org.opentrafficsim.trafficcontrol.trafcod;
2   
3   import java.io.BufferedReader;
4   import java.io.InputStreamReader;
5   import java.net.URL;
6   import java.rmi.RemoteException;
7   import java.util.ArrayList;
8   import java.util.EnumSet;
9   import java.util.HashMap;
10  import java.util.HashSet;
11  import java.util.List;
12  import java.util.Locale;
13  import java.util.Map;
14  import java.util.Set;
15  
16  import nl.tudelft.simulation.dsol.SimRuntimeException;
17  import nl.tudelft.simulation.dsol.simulators.DEVSSimulator;
18  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
19  import nl.tudelft.simulation.event.EventProducer;
20  import nl.tudelft.simulation.event.EventType;
21  import nl.tudelft.simulation.language.Throw;
22  
23  import org.djunits.unit.LengthUnit;
24  import org.djunits.unit.SpeedUnit;
25  import org.djunits.unit.TimeUnit;
26  import org.djunits.value.formatter.EngineeringFormatter;
27  import org.djunits.value.vdouble.scalar.Duration;
28  import org.djunits.value.vdouble.scalar.Length;
29  import org.djunits.value.vdouble.scalar.Speed;
30  import org.djunits.value.vdouble.scalar.Time;
31  import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
32  import org.opentrafficsim.core.dsol.OTSModelInterface;
33  import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
34  import org.opentrafficsim.core.geometry.OTSPoint3D;
35  import org.opentrafficsim.core.gtu.GTUType;
36  import org.opentrafficsim.core.network.Link;
37  import org.opentrafficsim.core.network.LinkType;
38  import org.opentrafficsim.core.network.LongitudinalDirectionality;
39  import org.opentrafficsim.core.network.Network;
40  import org.opentrafficsim.core.network.Node;
41  import org.opentrafficsim.core.network.OTSLink;
42  import org.opentrafficsim.core.network.OTSNetwork;
43  import org.opentrafficsim.core.network.OTSNode;
44  import org.opentrafficsim.road.network.lane.CrossSectionLink;
45  import org.opentrafficsim.road.network.lane.Lane;
46  import org.opentrafficsim.road.network.lane.LaneType;
47  import org.opentrafficsim.road.network.lane.changing.OvertakingConditions;
48  import org.opentrafficsim.road.network.lane.object.sensor.Sensor;
49  import org.opentrafficsim.road.network.lane.object.sensor.TrafficLightSensor;
50  import org.opentrafficsim.road.network.lane.object.trafficlight.SimpleTrafficLight;
51  import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight;
52  import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLightColor;
53  import org.opentrafficsim.simulationengine.SimpleSimulator;
54  import org.opentrafficsim.trafficcontrol.TrafficController;
55  
56  /**
57   * TrafCOD evaluator.
58   * <p>
59   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
60   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
61   * <p>
62   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Oct 5, 2016 <br>
63   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
64   */
65  public class TrafCOD extends EventProducer implements TrafficController
66  {
67      /** */
68      private static final long serialVersionUID = 20161014L;
69  
70      /** Name of this TrafCod controller. */
71      final String controllerName;
72  
73      /** Version of the supported TrafCOD files. */
74      final static int TRAFCOD_VERSION = 100;
75  
76      /** The evaluation interval of TrafCOD. */
77      final static Duration EVALUATION_INTERVAL = new Duration(0.1, TimeUnit.SECOND);
78  
79      /** Text leading up to the TrafCOD version number. */
80      private final static String VERSION_PREFIX = "trafcod-version=";
81  
82      /** Text on line before the sequence line. */
83      private final static String SEQUENCE_KEY = "Sequence";
84  
85      /** Text leading up to the control program structure. */
86      private final static String STRUCTURE_PREFIX = "Structure:";
87  
88      /** The original rules. */
89      final List<String> trafcodRules = new ArrayList<>();
90  
91      /** The tokenised rules. */
92      final List<Object[]> tokenisedRules = new ArrayList<>();
93  
94      /** The TrafCOD variables. */
95      final Map<String, Variable> variables = new HashMap<>();
96  
97      /** The detectors. */
98      final Map<String, Variable> detectors = new HashMap<>();
99  
100     /** Comment starter in TrafCOD. */
101     final static String COMMENT_START = "#";
102 
103     /** Prefix for initialization rules. */
104     private final static String INIT_PREFIX = "%init ";
105 
106     /** Prefix for time initializer rules. */
107     private final static String TIME_PREFIX = "%time ";
108 
109     /** Prefix for export rules. */
110     private final static String EXPORT_PREFIX = "%export ";
111 
112     /** Sequence information; number of conflict groups. */
113     private int conflictGroups = -1;
114 
115     /** Sequence information; size of conflict group. */
116     private int conflictGroupSize = -1;
117 
118     /** Chosen structure number (as assigned by VRIGen). */
119     private int structureNumber = -1;
120 
121     /** The conflict groups in order that they will be served. */
122     private List<List<Short>> conflictgroups = new ArrayList<>();
123 
124     /** Maximum number of evaluation loops. */
125     private int maxLoopCount = 10;
126 
127     /** Position in current expression. */
128     private int currentToken;
129 
130     /** The expression evaluation stack. */
131     private List<Integer> stack = new ArrayList<Integer>();
132 
133     /** Rule that is currently being evaluated. */
134     private Object[] currentRule;
135 
136     /** The current time in units of 0.1 s */
137     private int currentTime10 = 0;
138 
139     /** Event that is fired whenever a traffic light changes state. */
140     public static final EventType TRAFFIC_LIGHT_CHANGED = new EventType("TrafficLightChanged");
141 
142     /** The simulation engine. */
143     private final DEVSSimulator<Time, Duration, OTSSimTimeDouble> simulator;
144 
145     /**
146      * @param controllerName String; name of this traffic light controller
147      * @param trafCodURL String; the URL of the TrafCOD rules
148      * @param trafficLights Set&lt;TrafficLight&gt;; the traffic lights. The ids of the traffic lights must end with two digits
149      *            that match the stream numbers as used in the traffic control program
150      * @param sensors Set&lt;Sensor&gt;; the traffic sensors. The ids of the traffic sensors must end with three digits; the
151      *            first two of those must match the stream and sensor numbers used in the traffic control program
152      * @param simulator DEVSSimulator&lt;Time, Duration, OTSSimTimeDouble&gt;; the simulation engine
153      * @throws Exception when a rule cannot be parsed
154      */
155     public TrafCOD(String controllerName, final String trafCodURL, final Set<TrafficLight> trafficLights,
156             final Set<Sensor> sensors, final DEVSSimulator<Time, Duration, OTSSimTimeDouble> simulator) throws Exception
157     {
158         Throw.whenNull(trafCodURL, "trafCodURL may not be null");
159         Throw.whenNull(controllerName, "controllerName may not be null");
160         Throw.whenNull(trafficLights, "trafficLights may not be null");
161         Throw.whenNull(simulator, "simulator may not be null");
162         this.simulator = simulator;
163         this.controllerName = controllerName;
164         BufferedReader in = new BufferedReader(new InputStreamReader(new URL(trafCodURL).openStream()));
165         String inputLine;
166         int lineno = 0;
167         while ((inputLine = in.readLine()) != null)
168         {
169             ++lineno;
170             System.out.println(lineno + ":\t" + inputLine);
171             String trimmedLine = inputLine.trim();
172             if (trimmedLine.length() == 0)
173             {
174                 continue;
175             }
176             String locationDescription = trafCodURL + "(" + lineno + ") ";
177             if (trimmedLine.startsWith(COMMENT_START))
178             {
179                 String commentStripped = trimmedLine.substring(1).trim();
180                 if (stringBeginsWithIgnoreCase(VERSION_PREFIX, commentStripped))
181                 {
182                     String versionString = commentStripped.substring(VERSION_PREFIX.length());
183                     try
184                     {
185                         int observedVersion = Integer.parseInt(versionString);
186                         if (TRAFCOD_VERSION != observedVersion)
187                         {
188                             throw new Exception("Wrong TrafCOD version (expected " + TRAFCOD_VERSION + ", got "
189                                     + observedVersion + ")");
190                         }
191                     }
192                     catch (NumberFormatException nfe)
193                     {
194                         nfe.printStackTrace();
195                         throw new Exception("Could not parse TrafCOD version (got \"" + versionString + ")");
196                     }
197                 }
198                 else if (stringBeginsWithIgnoreCase(SEQUENCE_KEY, commentStripped))
199                 {
200                     while (trimmedLine.startsWith(COMMENT_START))
201                     {
202                         inputLine = in.readLine();
203                         if (null == inputLine)
204                         {
205                             throw new Exception("Unexpected EOF (reading sequence key at " + locationDescription + ")");
206                         }
207                         ++lineno;
208                         trimmedLine = inputLine.trim();
209                     }
210                     String[] fields = inputLine.split("\t");
211                     if (fields.length != 2)
212                     {
213                         throw new Exception("Wrong number of fields in Sequence information");
214                     }
215                     try
216                     {
217                         this.conflictGroups = Integer.parseInt(fields[0]);
218                         this.conflictGroupSize = Integer.parseInt(fields[1]);
219                     }
220                     catch (NumberFormatException nfe)
221                     {
222                         nfe.printStackTrace();
223                         throw new Exception("Bad number of conflict groups or bad conflict group size");
224                     }
225                 }
226                 else if (stringBeginsWithIgnoreCase(STRUCTURE_PREFIX, commentStripped))
227                 {
228                     String structureNumberString = commentStripped.substring(STRUCTURE_PREFIX.length()).trim();
229                     try
230                     {
231                         this.setStructureNumber(Integer.parseInt(structureNumberString));
232                     }
233                     catch (NumberFormatException nfe)
234                     {
235                         nfe.printStackTrace();
236                         throw new Exception("Bad structure number (got \"" + structureNumberString + "\" at "
237                                 + locationDescription + ")");
238                     }
239                     for (int conflictMemberLine = 0; conflictMemberLine < this.conflictGroups; conflictMemberLine++)
240                     {
241                         while (trimmedLine.startsWith(COMMENT_START))
242                         {
243                             inputLine = in.readLine();
244                             if (null == inputLine)
245                             {
246                                 throw new Exception("Unexpected EOF (reading sequence key at " + locationDescription + ")");
247                             }
248                             ++lineno;
249                             trimmedLine = inputLine.trim();
250                         }
251                         String[] fields = inputLine.split("\t");
252                         if (fields.length != this.conflictGroupSize)
253                         {
254                             throw new Exception("Wrong number of conflict groups in Structure information");
255                         }
256                         List<Short> row = new ArrayList<>(this.conflictGroupSize);
257                         for (int col = 0; col < this.conflictGroupSize; col++)
258                         {
259                             try
260                             {
261                                 Short stream = Short.parseShort(fields[col]);
262                                 row.add(stream);
263                             }
264                             catch (NumberFormatException nfe)
265                             {
266                                 nfe.printStackTrace();
267                                 throw new Exception("Wrong number of streams in conflict group " + trimmedLine);
268                             }
269                         }
270                     }
271                 }
272                 continue;
273             }
274             if (stringBeginsWithIgnoreCase(INIT_PREFIX, trimmedLine))
275             {
276                 String varNameAndInitialValue = trimmedLine.substring(INIT_PREFIX.length()).trim().replaceAll("[ \t]+", " ");
277                 String[] fields = varNameAndInitialValue.split(" ");
278                 NameAndStream nameAndStream = new NameAndStream(fields[0], locationDescription);
279                 installVariable(nameAndStream.getName(), nameAndStream.getStream(), EnumSet.noneOf(Flags.class),
280                         locationDescription);
281                 // The supplied initial value is ignored (in this version of the TrafCOD interpreter)!
282                 continue;
283             }
284             if (stringBeginsWithIgnoreCase(TIME_PREFIX, trimmedLine))
285             {
286                 String timerNameAndMaximumValue = trimmedLine.substring(INIT_PREFIX.length()).trim().replaceAll("[ \t]+", " ");
287                 String[] fields = timerNameAndMaximumValue.split(" ");
288                 NameAndStream nameAndStream = new NameAndStream(fields[0], locationDescription);
289                 Variable variable =
290                         installVariable(nameAndStream.getName(), nameAndStream.getStream(), EnumSet.noneOf(Flags.class),
291                                 locationDescription);
292                 int value10 = Integer.parseInt(fields[1]);
293                 variable.setTimerMax(value10);
294                 continue;
295             }
296             if (stringBeginsWithIgnoreCase(EXPORT_PREFIX, trimmedLine))
297             {
298                 String varNameAndOutputValue = trimmedLine.substring(EXPORT_PREFIX.length()).trim().replaceAll("[ \t]+", " ");
299                 String[] fields = varNameAndOutputValue.split(" ");
300                 NameAndStream nameAndStream = new NameAndStream(fields[0], locationDescription);
301                 Variable variable =
302                         installVariable(nameAndStream.getName(), nameAndStream.getStream(), EnumSet.noneOf(Flags.class),
303                                 locationDescription);
304                 int value = Integer.parseInt(fields[1]);
305                 // TODO create the set of traffic lights of this stream only once (not repeat for each possible color)
306                 Set<TrafficLight> trafficLightsOfStream = new HashSet<>();
307                 for (TrafficLight trafficLight : trafficLights)
308                 {
309                     String id = trafficLight.getId();
310                     if (id.length() < 2)
311                     {
312                         throw new Exception("Id of traffic light " + trafficLight + " does not end on two digits");
313                     }
314                     String streamLetters = id.substring(id.length() - 2);
315                     if (!Character.isDigit(streamLetters.charAt(0)) || !Character.isDigit(streamLetters.charAt(1)))
316                     {
317                         throw new Exception("Id of traffic light " + trafficLight + " does not end on two digits");
318                     }
319                     int stream = Integer.parseInt(streamLetters);
320                     if (variable.getStream() == stream)
321                     {
322                         trafficLightsOfStream.add(trafficLight);
323                     }
324                 }
325                 if (trafficLightsOfStream.size() == 0)
326                 {
327                     throw new Exception("No traffic light provided that matches stream " + variable.getStream());
328                 }
329                 variable.setOutput(value, trafficLightsOfStream);
330                 continue;
331             }
332             this.trafcodRules.add(trimmedLine);
333             Object[] tokenisedRule = parse(trimmedLine, locationDescription);
334             if (null != tokenisedRule)
335             {
336                 this.tokenisedRules.add(tokenisedRule);
337                 // for (Object o : tokenisedRule)
338                 // {
339                 // System.out.print(o + " ");
340                 // }
341                 // System.out.println("");
342                 String untokenised = printRule(tokenisedRule, false);
343                 System.out.println(untokenised);
344             }
345         }
346         in.close();
347         for (Variable variable : this.variables.values())
348         {
349             if (variable.isDetector())
350             {
351                 String detectorName = variable.selectedFieldsToString(EnumSet.of(PrintFlags.ID));
352                 int detectorNumber = variable.getStream() * 10 + detectorName.charAt(detectorName.length() - 1) - '0';
353 
354             }
355         }
356         System.out.println("Installed " + this.variables.size() + " variables");
357         for (String key : this.variables.keySet())
358         {
359             System.out.println(key
360                     + ":\t"
361                     + this.variables.get(key).selectedFieldsToString(
362                             EnumSet.of(PrintFlags.ID, PrintFlags.VALUE, PrintFlags.INITTIMER, PrintFlags.REINITTIMER,
363                                     PrintFlags.S, PrintFlags.E)));
364         }
365         this.simulator.scheduleEventNow(this, this, "evalExprs", null);
366     }
367 
368     /**
369      * Evaluate all expressions until no more changes occur.
370      * @throws Exception when evaluation of a rule fails
371      */
372     @SuppressWarnings("unused")
373     private void evalExprs() throws Exception
374     {
375         System.out.println("TrafCOD: time is " + EngineeringFormatter.format(this.simulator.getSimulatorTime().get().si));
376         for (int loop = 0; loop < this.maxLoopCount; loop++)
377         {
378             if (evalExpressionsOnce() == 0)
379             {
380                 break;
381             }
382         }
383         this.simulator.scheduleEventRel(EVALUATION_INTERVAL, this, this, "evalExprs", null);
384     }
385 
386     /**
387      * Evaluate all expressions and return the number of changed variables.
388      * @return int; the number of changed variables
389      * @throws Exception when evaluation of a rule fails
390      */
391     private int evalExpressionsOnce() throws Exception
392     {
393         for (Variable variable : this.variables.values())
394         {
395             variable.clearChangedFlag();
396         }
397         int changeCount = 0;
398         for (Object[] rule : this.tokenisedRules)
399         {
400             System.out.println("Evaluating rule " + printRule(rule, true));
401             for (Object o : rule)
402             {
403                 System.out.print(o + " ");
404             }
405             System.out.println("");
406 
407             if (evalRule(rule))
408             {
409                 changeCount++;
410             }
411         }
412         return changeCount;
413     }
414 
415     /**
416      * Evaluate a rule.
417      * @param rule Object[]; the tokenised rule
418      * @return boolean; true if the variable that is affected by the rule has changed; false if no variable was changed
419      * @throws Exception when evaluation of the rule fails
420      */
421     private boolean evalRule(final Object[] rule) throws Exception
422     {
423         boolean result = false;
424         Token ruleType = (Token) rule[0];
425         Variable destination = (Variable) rule[1];
426         if (destination.isTimer())
427         {
428             if (destination.getFlags().contains(Flags.TIMEREXPIRED))
429             {
430                 destination.clearFlag(Flags.TIMEREXPIRED);
431                 destination.setFlag(Flags.END);
432             }
433             else if (destination.getFlags().contains(Flags.START) || destination.getFlags().contains(Flags.END))
434             {
435                 destination.clearFlag(Flags.START);
436                 destination.clearFlag(Flags.END);
437                 destination.setFlag(Flags.CHANGED);
438             }
439         }
440         else
441         {
442             // Normal Variable or detector
443             if (Token.START_RULE == ruleType)
444             {
445                 destination.clearFlag(Flags.START);
446             }
447             else if (Token.END_RULE == ruleType)
448             {
449                 destination.clearFlag(Flags.END);
450             }
451             else
452             {
453                 destination.clearFlag(Flags.START);
454                 destination.clearFlag(Flags.END);
455             }
456         }
457 
458         int currentValue = destination.getValue();
459         if (Token.START_RULE == ruleType && currentValue != 0 || Token.END == ruleType && currentValue == 0
460                 || Token.INIT_TIMER == ruleType && currentValue != 0)
461         {
462             return false; // Value cannot change from zero to nonzero or vice versa due to evaluating the expression
463         }
464         this.currentRule = rule;
465         this.currentToken = 2; // Point to first token of the RHS
466         this.stack.clear();
467         evalExpr(0);
468         if (this.currentToken < this.currentRule.length && Token.CLOSE_PAREN == this.currentRule[this.currentToken])
469         {
470             throw new Exception("Too man closing parentheses");
471         }
472         int resultValue = pop();
473         if (Token.END_RULE == ruleType)
474         {
475             // Invert the result
476             if (0 == resultValue)
477             {
478                 resultValue = destination.getValue(); // preserve the current value
479             }
480             else
481             {
482                 resultValue = 0;
483             }
484         }
485         if (destination.isTimer())
486         {
487             if (resultValue != 0 && Token.END_RULE == ruleType)
488             {
489                 if (destination.getValue() == 0)
490                 {
491                     result = true;
492                 }
493                 // TODO Handle traced streams
494                 int timerValue10 = destination.getTimerMax();
495                 if (timerValue10 < 1)
496                 {
497                     // Cheat
498                     timerValue10 = 1;
499                 }
500                 destination.setValue(timerValue10, this.currentTime10);
501             }
502             else if (0 == resultValue && Token.END_RULE == ruleType && destination.getValue() != 0)
503             {
504                 result = true;
505                 destination.setValue(0, this.currentTime10);
506             }
507             // TODO handle tracing
508         }
509         else if (destination.getValue() != resultValue)
510         {
511             result = true;
512             destination.setValue(resultValue, this.currentTime10);
513             if (destination.isOutput())
514             {
515                 fireEvent(TRAFFIC_LIGHT_CHANGED,
516                         new Object[] { this.controllerName, destination.getStartSource(), destination.getColor() });
517             }
518             // TODO handle tracing
519         }
520         // TODO handle tracing
521         return result;
522     }
523 
524     /** Binding strength of relational operators. */
525     private static final int BIND_RELATIONAL_OPERATOR = 1;
526 
527     /** Binding strength of addition and subtraction. */
528     private static final int BIND_ADDITION = 2;
529 
530     /** Binding strength of multiplication and division. */
531     private static final int BIND_MULTIPLY = 3;
532 
533     /** Binding strength of unary minus. */
534     private static int BIND_UNARY_MINUS = 4;
535 
536     /**
537      * Evaluate an expression. <br>
538      * The methods evalExpr and evalRHS together evaluate an expression. This is done using recursion and a stack. The argument
539      * bindingStrength that is passed around is the binding strength of the last preceding pending operator. if a
540      * binary operator with the same or a lower strength is encountered, the pending operator must be applied first. On the
541      * other hand of a binary operator with higher binding strength is encountered, that operator takes precedence over the
542      * pending operator. To evaluate an expression, call evalExpr with a bindingStrength value of 0.
543      * @param bindingStrength int; the binding strength of a not yet applied binary operator (higher value must be applied
544      *            first)
545      * @throws Exception when the expression is not valid
546      */
547     private void evalExpr(final int bindingStrength) throws Exception
548     {
549         if (this.currentToken >= this.currentRule.length)
550         {
551             throw new Exception("Missing operand at end of expression " + printRule(this.currentRule, false));
552         }
553         Token token = (Token) this.currentRule[this.currentToken++];
554         Object nextToken = null;
555         if (this.currentToken < this.currentRule.length)
556         {
557             nextToken = this.currentRule[this.currentToken];
558         }
559         switch (token)
560         {
561             case UNARY_MINUS:
562                 if (Token.OPEN_PAREN != nextToken && Token.VARIABLE != nextToken && Token.NEG_VARIABLE != nextToken
563                         && Token.CONSTANT != nextToken && Token.START != nextToken && Token.END != nextToken)
564                 {
565                     throw new Exception("Operand expected after unary minus");
566                 }
567                 evalExpr(BIND_UNARY_MINUS);
568                 push(-pop());
569                 break;
570 
571             case OPEN_PAREN:
572                 evalExpr(0);
573                 if (Token.CLOSE_PAREN != this.currentRule[this.currentToken])
574                 {
575                     throw new Exception("Missing closing parenthesis");
576                 }
577                 this.currentToken++;
578                 break;
579 
580             case START:
581                 if (Token.VARIABLE != nextToken || this.currentToken >= this.currentRule.length - 1)
582                 {
583                     throw new Exception("Missing variable after S");
584                 }
585                 nextToken = this.currentRule[++this.currentToken];
586                 if (!(nextToken instanceof Variable))
587                 {
588                     throw new Exception("Missing variable after S");
589                 }
590                 push(((Variable) nextToken).getFlags().contains(Flags.START) ? 1 : 0);
591                 this.currentToken++;
592                 break;
593 
594             case END:
595                 if (Token.VARIABLE != nextToken || this.currentToken >= this.currentRule.length - 1)
596                 {
597                     throw new Exception("Missing variable after E");
598                 }
599                 nextToken = this.currentRule[++this.currentToken];
600                 if (!(nextToken instanceof Variable))
601                 {
602                     throw new Exception("Missing variable after E");
603                 }
604                 push(((Variable) nextToken).getFlags().contains(Flags.END) ? 1 : 0);
605                 this.currentToken++;
606                 break;
607 
608             case VARIABLE:
609             {
610                 Variable operand = (Variable) nextToken;
611                 if (operand.isTimer())
612                 {
613                     push(operand.getValue() == 0 ? 0 : 1);
614                 }
615                 else
616                 {
617                     push(operand.getValue());
618                 }
619                 this.currentToken++;
620                 break;
621             }
622 
623             case CONSTANT:
624                 push((Integer) nextToken);
625                 this.currentToken++;
626                 break;
627 
628             case NEG_VARIABLE:
629                 push(-((Variable) nextToken).getValue());
630                 this.currentToken++;
631                 break;
632 
633             default:
634                 throw new Exception("Operand missing");
635         }
636         evalRHS(bindingStrength);
637     }
638 
639     /**
640      * Evaluate the right-hand-side of an expression.
641      * @param bindingStrength int; the binding strength of the most recent, not yet applied, binary operator
642      * @throws Exception
643      */
644     private void evalRHS(final int bindingStrength) throws Exception
645     {
646         while (true)
647         {
648             if (this.currentToken >= this.currentRule.length)
649             {
650                 return;
651             }
652             Token token = (Token) this.currentRule[this.currentToken];
653             switch (token)
654             {
655                 case CLOSE_PAREN:
656                     return;
657 
658                 case TIMES:
659                     if (BIND_MULTIPLY <= bindingStrength)
660                     {
661                         return; // apply pending operator now
662                     }
663                     /*-
664                      * apply pending operator later 
665                      * 1: evaluate the RHS operand. 
666                      * 2: multiply the top-most two operands on the
667                      * stack and push the result on the stack.
668                      */
669                     this.currentToken++;
670                     evalExpr(BIND_MULTIPLY);
671                     push(pop() * pop() == 0 ? 0 : 1);
672                     break;
673 
674                 case EQ:
675                 case NOTEQ:
676                 case LE:
677                 case LEEQ:
678                 case GT:
679                 case GTEQ:
680                     if (BIND_RELATIONAL_OPERATOR <= bindingStrength)
681                     {
682                         return; // apply pending operator now
683                     }
684                     /*-
685                      * apply pending operator later 
686                      * 1: evaluate the RHS operand. 
687                      * 2: compare the top-most two operands on the
688                      * stack and push the result on the stack.
689                      */
690                     this.currentToken++;
691                     evalExpr(BIND_RELATIONAL_OPERATOR);
692                     switch (token)
693                     {
694                         case EQ:
695                             push(pop() == pop() ? 1 : 0);
696                             break;
697 
698                         case NOTEQ:
699                             push(pop() != pop() ? 1 : 0);
700                             break;
701 
702                         case GT:
703                             push(pop() < pop() ? 1 : 0);
704                             break;
705 
706                         case GTEQ:
707                             push(pop() <= pop() ? 1 : 0);
708                             break;
709 
710                         case LE:
711                             push(pop() > pop() ? 1 : 0);
712                             break;
713 
714                         case LEEQ:
715                             push(pop() >= pop() ? 1 : 0);
716                             break;
717 
718                         default:
719                             throw new Exception("Bad relational operator");
720                     }
721                     break;
722 
723                 case PLUS:
724                     if (BIND_ADDITION <= bindingStrength)
725                     {
726                         return; // apply pending operator now
727                     }
728                     /*-
729                      * apply pending operator later 
730                      * 1: evaluate the RHS operand. 
731                      * 2: add (OR) the top-most two operands on the
732                      * stack and push the result on the stack.
733                      */
734                     this.currentToken++;
735                     evalExpr(BIND_ADDITION);
736                     push(pop() + pop() == 0 ? 0 : 1);
737                     break;
738 
739                 case MINUS:
740                     if (BIND_ADDITION <= bindingStrength)
741                     {
742                         return; // apply pending operator now
743                     }
744                     /*-
745                      * apply pending operator later 
746                      * 1: evaluate the RHS operand. 
747                      * 2: subtract the top-most two operands on the
748                      * stack and push the result on the stack.
749                      */
750                     this.currentToken++;
751                     evalExpr(BIND_ADDITION);
752                     push(-pop() + pop());
753                     break;
754 
755                 default:
756                     throw new Exception("Missing binary operator");
757             }
758         }
759     }
760 
761     /**
762      * Push a value on the evaluation stack.
763      * @param value int; the value to push on the evaluation stack
764      */
765     private void push(final int value)
766     {
767         this.stack.add(value);
768     }
769 
770     /**
771      * Remove the last not-yet-removed value from the evaluation stack and return it.
772      * @return int; the last non-yet-removed value on the evaluation stack
773      * @throws Exception when the stack is empty
774      */
775     private int pop() throws Exception
776     {
777         if (this.stack.size() < 1)
778         {
779             throw new Exception("Stack empty");
780         }
781         return this.stack.remove(this.stack.size() - 1);
782     }
783 
784     /**
785      * Print a tokenised rule.
786      * @param tokens Object[]; the tokens
787      * @param printValues boolean; if true; print the values of all encountered variable; if false; do not print the values of
788      *            all encountered variable
789      * @return String; a textual approximation of the original rule
790      * @throws Exception when tokens does not match the expected grammar
791      */
792     private String printRule(Object[] tokens, final boolean printValues) throws Exception
793     {
794         StringBuilder result = new StringBuilder();
795         for (int inPos = 0; inPos < tokens.length; inPos++)
796         {
797             Object token = tokens[inPos];
798             if (token instanceof Token)
799             {
800                 switch ((Token) token)
801                 {
802                     case EQUALS_RULE:
803                         result.append(((Variable) tokens[++inPos]).selectedFieldsToString(EnumSet.of(PrintFlags.ID)));
804                         result.append("=");
805                         break;
806                     case NEG_EQUALS_RULE:
807                         result.append(((Variable) tokens[++inPos]).selectedFieldsToString(EnumSet.of(PrintFlags.ID,
808                                 PrintFlags.NEGATED)));
809                         result.append("=");
810                         break;
811                     case START_RULE:
812                         result.append(((Variable) tokens[++inPos]).selectedFieldsToString(EnumSet.of(PrintFlags.ID)));
813                         result.append(".=");
814                         break;
815                     case END_RULE:
816                         result.append(((Variable) tokens[++inPos]).selectedFieldsToString(EnumSet.of(PrintFlags.ID,
817                                 PrintFlags.NEGATED)));
818                         result.append(".=");
819                         break;
820                     case INIT_TIMER:
821                         result.append(((Variable) tokens[++inPos]).selectedFieldsToString(EnumSet.of(PrintFlags.ID,
822                                 PrintFlags.INITTIMER)));
823                         result.append(".=");
824                         break;
825                     case REINIT_TIMER:
826                         result.append(((Variable) tokens[++inPos]).selectedFieldsToString(EnumSet.of(PrintFlags.ID,
827                                 PrintFlags.REINITTIMER)));
828                         result.append(".=");
829                         break;
830                     case START:
831                         result.append("S");
832                         break;
833                     case END:
834                         result.append("E");
835                         break;
836                     case VARIABLE:
837                         result.append(((Variable) tokens[++inPos]).selectedFieldsToString(EnumSet.of(PrintFlags.ID)));
838                         break;
839                     case NEG_VARIABLE:
840                         result.append(((Variable) tokens[++inPos]).selectedFieldsToString(EnumSet.of(PrintFlags.ID,
841                                 PrintFlags.NEGATED)));
842                         break;
843                     case CONSTANT:
844                         result.append(tokens[++inPos]).toString();
845                         break;
846                     case UNARY_MINUS:
847                     case MINUS:
848                         result.append("-");
849                         break;
850                     case PLUS:
851                         result.append("+");
852                         break;
853                     case TIMES:
854                         result.append(".");
855                         break;
856                     case EQ:
857                         result.append("=");
858                         break;
859                     case NOTEQ:
860                         result.append("<>");
861                         break;
862                     case GT:
863                         result.append(">");
864                         break;
865                     case GTEQ:
866                         result.append(">=");
867                         break;
868                     case LE:
869                         result.append("<");
870                         break;
871                     case LEEQ:
872                         result.append("<=");
873                         break;
874                     case OPEN_PAREN:
875                         result.append("(");
876                         break;
877                     case CLOSE_PAREN:
878                         result.append(")");
879                         break;
880                     default:
881                         System.out.println("<<<ERROR>>> encountered a non-Token object: " + token + " after "
882                                 + result.toString());
883                         throw new Exception("Unknown token");
884 
885                 }
886             }
887             else
888             {
889                 System.out.println("<<<ERROR>>> encountered a non-Token object: " + token + " after " + result.toString());
890                 throw new Exception("Not a token");
891             }
892         }
893         return result.toString();
894     }
895 
896     /**
897      * States of the rule parser.
898      * <p>
899      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
900      */
901     enum ParserState
902     {
903         /** Looking for the left hand side of an assignment. */
904         FIND_LHS,
905         /** Looking for an assignment operator. */
906         FIND_ASSIGN,
907         /** Looking for the right hand side of an assignment. */
908         FIND_RHS,
909         /** Looking for an optional unary minus. */
910         MAY_UMINUS,
911         /** Looking for an expression. */
912         FIND_EXPR,
913     }
914 
915     /**
916      * Types of TrafCOD tokens.
917      * <p>
918      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
919      */
920     enum Token
921     {
922         /** Equals rule. */
923         EQUALS_RULE,
924         /** Not equals rule. */
925         NEG_EQUALS_RULE,
926         /** Assignment rule. */
927         ASSIGNMENT,
928         /** Start rule. */
929         START_RULE,
930         /** End rule. */
931         END_RULE,
932         /** Timer initialize rule. */
933         INIT_TIMER,
934         /** Timer re-initialize rule. */
935         REINIT_TIMER,
936         /** Unary minus operator. */
937         UNARY_MINUS,
938         /** Less than or equal to (&lt;=). */
939         LEEQ,
940         /** Not equal to (!=). */
941         NOTEQ,
942         /** Less than (&lt;). */
943         LE,
944         /** Greater than or equal to (&gt;=). */
945         GTEQ,
946         /** Greater than (&gt;). */
947         GT,
948         /** Equals to (=). */
949         EQ,
950         /** True if following variable has just started. */
951         START,
952         /** True if following variable has just ended. */
953         END,
954         /** Variable follows. */
955         VARIABLE,
956         /** Variable that follows must be logically negated. */
957         NEG_VARIABLE,
958         /** Integer follows. */
959         CONSTANT,
960         /** Addition operator. */
961         PLUS,
962         /** Subtraction operator. */
963         MINUS,
964         /** Multiplication operator. */
965         TIMES,
966         /** Opening parenthesis. */
967         OPEN_PAREN,
968         /** Closing parenthesis. */
969         CLOSE_PAREN,
970     }
971 
972     /**
973      * Parse one TrafCOD rule.
974      * @param rawRule String; the TrafCOD rule
975      * @param locationDescription String; description of the location (file, line) where the rule was found
976      * @return Object[]; array filled with the tokenised rule
977      * @throws Exception when the rule is not a valid TrafCOD rule
978      */
979     private Object[] parse(final String rawRule, final String locationDescription) throws Exception
980     {
981         if (rawRule.length() == 0)
982         {
983             throw new Exception("empty rule at " + locationDescription);
984         }
985         ParserState state = ParserState.FIND_LHS;
986         String rule = rawRule.toUpperCase(Locale.US);
987         Token ruleType = Token.ASSIGNMENT;
988         int inPos = 0;
989         NameAndStream lhsNameAndStream = null;
990         List<Object> tokens = new ArrayList<>();
991         while (inPos < rule.length())
992         {
993             char character = rule.charAt(inPos);
994             if (Character.isWhitespace(character))
995             {
996                 inPos++;
997                 continue;
998             }
999             switch (state)
1000             {
1001                 case FIND_LHS:
1002                 {
1003                     if ('S' == character)
1004                     {
1005                         ruleType = Token.START_RULE;
1006                         inPos++;
1007                         lhsNameAndStream = new NameAndStream(rule.substring(inPos), locationDescription);
1008                         inPos += lhsNameAndStream.getNumberOfChars();
1009                     }
1010                     else if ('E' == character)
1011                     {
1012                         ruleType = Token.END_RULE;
1013                         inPos++;
1014                         lhsNameAndStream = new NameAndStream(rule.substring(inPos), locationDescription);
1015                         inPos += lhsNameAndStream.getNumberOfChars();
1016                     }
1017                     else if ('I' == character && 'T' == rule.charAt(inPos + 1))
1018                     {
1019                         ruleType = Token.INIT_TIMER;
1020                         inPos++; // The 'T' is part of the name of the time; do not consume it
1021                         lhsNameAndStream = new NameAndStream(rule.substring(inPos), locationDescription);
1022                         inPos += lhsNameAndStream.getNumberOfChars();
1023                     }
1024                     else if ('R' == character && 'I' == rule.charAt(inPos + 1) && 'T' == rule.charAt(inPos + 2))
1025                     {
1026                         ruleType = Token.REINIT_TIMER;
1027                         inPos += 2; // The 'T' is part of the name of the time; do not consume it
1028                         lhsNameAndStream = new NameAndStream(rule.substring(inPos), locationDescription);
1029                         inPos += lhsNameAndStream.getNumberOfChars();
1030                     }
1031                     else if ('T' == character && rule.indexOf('=') >= 0
1032                             && (rule.indexOf('N') < 0 || rule.indexOf('N') > rule.indexOf('=')))
1033                     {
1034                         throw new Exception("Bad time initialization at " + locationDescription);
1035                     }
1036                     else
1037                     {
1038                         ruleType = Token.EQUALS_RULE;
1039                         lhsNameAndStream = new NameAndStream(rule.substring(inPos), locationDescription);
1040                         inPos += lhsNameAndStream.getNumberOfChars();
1041                         if (lhsNameAndStream.isNegated())
1042                         {
1043                             ruleType = Token.NEG_EQUALS_RULE;
1044                         }
1045                     }
1046                     state = ParserState.FIND_ASSIGN;
1047                     break;
1048                 }
1049                 case FIND_ASSIGN:
1050                 {
1051                     if ('.' == character && '=' == rule.charAt(inPos + 1))
1052                     {
1053                         if (Token.EQUALS_RULE == ruleType)
1054                         {
1055                             ruleType = Token.START_RULE;
1056                         }
1057                         else if (Token.NEG_EQUALS_RULE == ruleType)
1058                         {
1059                             ruleType = Token.END_RULE;
1060                         }
1061                         inPos += 2;
1062                     }
1063                     else if ('=' == character)
1064                     {
1065                         if (Token.START_RULE == ruleType || Token.END_RULE == ruleType || Token.INIT_TIMER == ruleType
1066                                 || Token.REINIT_TIMER == ruleType)
1067                         {
1068                             throw new Exception("Bad assignment at " + locationDescription);
1069                         }
1070                         inPos++;
1071                     }
1072                     tokens.add(ruleType);
1073                     EnumSet<Flags> lhsFlags = EnumSet.noneOf(Flags.class);
1074                     if (Token.START_RULE == ruleType || Token.EQUALS_RULE == ruleType || Token.NEG_EQUALS_RULE == ruleType
1075                             || Token.INIT_TIMER == ruleType || Token.REINIT_TIMER == ruleType)
1076                     {
1077                         lhsFlags.add(Flags.HAS_START_RULE);
1078                     }
1079                     if (Token.END_RULE == ruleType || Token.EQUALS_RULE == ruleType || Token.NEG_EQUALS_RULE == ruleType)
1080                     {
1081                         lhsFlags.add(Flags.HAS_END_RULE);
1082                     }
1083                     Variable lhsVariable =
1084                             installVariable(lhsNameAndStream.getName(), lhsNameAndStream.getStream(), lhsFlags,
1085                                     locationDescription);
1086                     tokens.add(lhsVariable);
1087                     state = ParserState.MAY_UMINUS;
1088                     break;
1089                 }
1090                 case MAY_UMINUS:
1091                     if ('-' == character)
1092                     {
1093                         tokens.add(Token.UNARY_MINUS);
1094                         inPos++;
1095                     }
1096                     state = ParserState.FIND_EXPR;
1097                     break;
1098 
1099                 case FIND_EXPR:
1100                 {
1101                     if (Character.isDigit(character))
1102                     {
1103                         int constValue = 0;
1104                         while (inPos < rule.length() && Character.isDigit(rule.charAt(inPos)))
1105                         {
1106                             int digit = rule.charAt(inPos) - '0';
1107                             if (constValue >= (Integer.MAX_VALUE - digit) / 10)
1108                             {
1109                                 throw new Exception("Number too large at " + locationDescription);
1110                             }
1111                             constValue = 10 * constValue + digit;
1112                             inPos++;
1113                         }
1114                         tokens.add(Token.CONSTANT);
1115                         tokens.add(new Integer(constValue));
1116                     }
1117                     if (inPos >= rule.length())
1118                     {
1119                         return tokens.toArray();
1120                     }
1121                     character = rule.charAt(inPos);
1122                     switch (character)
1123                     {
1124                         case '+':
1125                             tokens.add(Token.PLUS);
1126                             inPos++;
1127                             break;
1128                         case '-':
1129                             tokens.add(Token.MINUS);
1130                             inPos++;
1131                             break;
1132                         case '.':
1133                             tokens.add(Token.TIMES);
1134                             inPos++;
1135                             break;
1136                         case ')':
1137                             tokens.add(Token.CLOSE_PAREN);
1138                             inPos++;
1139                             break;
1140 
1141                         case '<':
1142                         {
1143                             Character nextChar = rule.charAt(++inPos);
1144                             if ('=' == nextChar)
1145                             {
1146                                 tokens.add(Token.LEEQ);
1147                                 inPos++;
1148                             }
1149                             else if ('>' == nextChar)
1150                             {
1151                                 tokens.add(Token.NOTEQ);
1152                                 inPos++;
1153                             }
1154                             else
1155                             {
1156                                 tokens.add(Token.LE);
1157                             }
1158                             break;
1159                         }
1160                         case '>':
1161                         {
1162                             Character nextChar = rule.charAt(++inPos);
1163                             if ('=' == nextChar)
1164                             {
1165                                 tokens.add(Token.GTEQ);
1166                                 inPos++;
1167                             }
1168                             else if ('<' == nextChar)
1169                             {
1170                                 tokens.add(Token.NOTEQ);
1171                                 inPos++;
1172                             }
1173                             else
1174                             {
1175                                 tokens.add(Token.GT);
1176                             }
1177                             break;
1178                         }
1179                         case '=':
1180                         {
1181                             Character nextChar = rule.charAt(++inPos);
1182                             if ('<' == nextChar)
1183                             {
1184                                 tokens.add(Token.LEEQ);
1185                                 inPos++;
1186                             }
1187                             else if ('>' == nextChar)
1188                             {
1189                                 tokens.add(Token.GTEQ);
1190                                 inPos++;
1191                             }
1192                             else
1193                             {
1194                                 tokens.add(Token.EQ);
1195                             }
1196                             break;
1197                         }
1198                         case '(':
1199                         {
1200                             inPos++;
1201                             tokens.add(Token.OPEN_PAREN);
1202                             state = ParserState.MAY_UMINUS;
1203                             break;
1204                         }
1205                         default:
1206                         {
1207                             if ('S' == character)
1208                             {
1209                                 tokens.add(Token.START);
1210                                 inPos++;
1211                             }
1212                             else if ('E' == character)
1213                             {
1214                                 tokens.add(Token.END);
1215                                 inPos++;
1216                             }
1217                             NameAndStream nas = new NameAndStream(rule.substring(inPos), locationDescription);
1218                             inPos += nas.getNumberOfChars();
1219                             if (nas.isNegated())
1220                             {
1221                                 tokens.add(Token.NEG_VARIABLE);
1222                             }
1223                             else
1224                             {
1225                                 tokens.add(Token.VARIABLE);
1226                             }
1227                             Variable variable =
1228                                     installVariable(nas.getName(), nas.getStream(), EnumSet.noneOf(Flags.class),
1229                                             locationDescription);
1230                             variable.incrementReferenceCount();
1231                             tokens.add(variable);
1232                         }
1233                     }
1234                     break;
1235                 }
1236                 default:
1237                     throw new Exception("Error: bad switch; case " + state + " should not happen");
1238             }
1239         }
1240         return tokens.toArray();
1241 
1242     }
1243 
1244     /**
1245      * Check if a String begins with the text of a supplied String (ignoring case).
1246      * @param sought String; the sought pattern (NOT a regular expression)
1247      * @param supplied String; the String that might start with the sought string
1248      * @return boolean; true if the supplied String begins with the sought String (case insensitive)
1249      */
1250     private boolean stringBeginsWithIgnoreCase(final String sought, final String supplied)
1251     {
1252         if (sought.length() > supplied.length())
1253         {
1254             return false;
1255         }
1256         return (sought.equalsIgnoreCase(supplied.substring(0, sought.length())));
1257     }
1258 
1259     /**
1260      * Generate the key for a variable name and stream for use in this.variables.
1261      * @param name String; name of the variable
1262      * @param stream short; stream of the variable
1263      * @return String
1264      */
1265     private String variableKey(final String name, final short stream)
1266     {
1267         if (name.contains("\t"))
1268         {
1269             System.out.println("Whoops");
1270         }
1271         return String.format("%s%02d", name, stream);
1272     }
1273 
1274     /**
1275      * Lookup or create a new Variable.
1276      * @param name String; name of the variable
1277      * @param stream short; stream number of the variable
1278      * @param flags EnumSet&lt;Flags&gt;; some (possibly empty) combination of Flags.HAS_START_RULE and Flags.HAS_END_RULE; no
1279      *            other flags are allowed
1280      * @param location String; description of the location in the TrafCOD file that triggered the call to this method
1281      * @return Variable; the new (or already existing) variable
1282      * @throws Exception if the variable already exists and already has (one of) the specified flag(s)
1283      */
1284     private Variable installVariable(String name, short stream, EnumSet<Flags> flags, String location) throws Exception
1285     {
1286         EnumSet<Flags> forbidden = EnumSet.complementOf(EnumSet.of(Flags.HAS_START_RULE, Flags.HAS_END_RULE));
1287         EnumSet<Flags> badFlags = EnumSet.copyOf(forbidden);
1288         badFlags.retainAll(flags);
1289         if (badFlags.size() > 0)
1290         {
1291             throw new Exception("installVariable was called with wrong flag(s): " + badFlags);
1292         }
1293         String key = variableKey(name, stream);
1294         Variable variable = this.variables.get(key);
1295         if (null == variable)
1296         {
1297             // Create and install a new variable
1298             variable = new Variable(name, stream);
1299             this.variables.put(key, variable);
1300             if (variable.isDetector())
1301             {
1302                 this.detectors.put(key, variable);
1303             }
1304         }
1305         if (flags.contains(Flags.HAS_START_RULE))
1306         {
1307             variable.setStartSource(location);
1308         }
1309         if (flags.contains(Flags.HAS_END_RULE))
1310         {
1311             variable.setEndSource(location);
1312         }
1313         return variable;
1314     }
1315 
1316     /**
1317      * Test code
1318      * @param args String; the command line arguments (not used)
1319      * @throws Exception when network cannot be created
1320      */
1321     public static void main(final String[] args) throws Exception
1322     {
1323         OTSModelInterface model = new OTSModelInterface()
1324         {
1325             /** */
1326             private static final long serialVersionUID = 20161020L;
1327 
1328             /** The TrafCOD evaluator. */
1329             private TrafCOD trafCOD;
1330 
1331             @Override
1332             public void constructModel(SimulatorInterface<Time, Duration, OTSSimTimeDouble> theSimulator)
1333                     throws SimRuntimeException, RemoteException
1334             {
1335                 try
1336                 {
1337                     Network network = new OTSNetwork("TrafCOD test network");
1338                     Node nodeX = new OTSNode(network, "Crossing", new OTSPoint3D(0, 0, 0));
1339                     Node nodeS = new OTSNode(network, "South", new OTSPoint3D(0, -100, 0));
1340                     Node nodeE = new OTSNode(network, "East", new OTSPoint3D(100, 0, 0));
1341                     Node nodeN = new OTSNode(network, "North", new OTSPoint3D(0, 100, 0));
1342                     Node nodeW = new OTSNode(network, "West", new OTSPoint3D(-100, 0, 0));
1343                     Map<GTUType, LongitudinalDirectionality> directionalityMap = new HashMap<>();
1344                     Link linkSX = new OTSLink(network, "LinkSX", nodeS, nodeX, LinkType.ALL, null, directionalityMap);
1345                     Link linkXN = new OTSLink(network, "LinkSX", nodeX, nodeN, LinkType.ALL, null, directionalityMap);
1346                     Link linkWX = new OTSLink(network, "LinkSX", nodeW, nodeX, LinkType.ALL, null, directionalityMap);
1347                     Link linkXE = new OTSLink(network, "LinkSX", nodeX, nodeE, LinkType.ALL, null, directionalityMap);
1348                     Length laneWidth = new Length(3, LengthUnit.METER);
1349                     Speed speedLimit = new Speed(50, SpeedUnit.KM_PER_HOUR);
1350                     Lane laneSX =
1351                             new Lane((CrossSectionLink) linkSX, "laneSX", Length.ZERO, laneWidth, LaneType.ALL,
1352                                     LongitudinalDirectionality.DIR_PLUS, speedLimit, new OvertakingConditions.None());
1353                     TrafficLightSensor d082 =
1354                             new TrafficLightSensor("D082", laneSX, new Length(50, LengthUnit.METER), new Length(20,
1355                                     LengthUnit.METER), (OTSDEVSSimulatorInterface) theSimulator);
1356                     Set<TrafficLight> trafficLights = new HashSet<>();
1357                     trafficLights.add(new SimpleTrafficLight("TL08", null, null, (OTSDEVSSimulatorInterface) theSimulator));
1358                     trafficLights.add(new SimpleTrafficLight("TL11", null, null, (OTSDEVSSimulatorInterface) theSimulator));
1359                     this.trafCOD =
1360                             new TrafCOD("Simple TrafCOD controller", "file:///d:/cppb/trafcod/otsim/simpel.tfc", trafficLights,
1361                                     null, (DEVSSimulator<Time, Duration, OTSSimTimeDouble>) theSimulator);
1362                 }
1363                 catch (Exception exception)
1364                 {
1365                     exception.printStackTrace();
1366                 }
1367             }
1368 
1369             @Override
1370             public SimulatorInterface<Time, Duration, OTSSimTimeDouble> getSimulator() throws RemoteException
1371             {
1372                 return this.trafCOD.getSimulator();
1373             }
1374 
1375         };
1376         SimpleSimulator testSimulator = new SimpleSimulator(Time.ZERO, Duration.ZERO, new Duration(1, TimeUnit.HOUR), model);
1377         testSimulator.runUpToAndIncluding(new Time(1, TimeUnit.HOUR));
1378 
1379     }
1380 
1381     /**
1382      * Retrieve the simulator.
1383      * @return SimulatorInterface&lt;Time, Duration, OTSSimTimeDouble&gt;
1384      */
1385     protected SimulatorInterface<Time, Duration, OTSSimTimeDouble> getSimulator()
1386     {
1387         return this.simulator;
1388     }
1389 
1390     /**
1391      * Retrieve the structure number.
1392      * @return int; the structureNumber
1393      */
1394     public int getStructureNumber()
1395     {
1396         return this.structureNumber;
1397     }
1398 
1399     /**
1400      * Set the structure number.
1401      * @param structureNumber int; the new structureNumber.
1402      */
1403     public void setStructureNumber(int structureNumber)
1404     {
1405         this.structureNumber = structureNumber;
1406     }
1407 
1408     /** {@inheritDoc} */
1409     @Override
1410     public void updateDetector(String detectorId, boolean detectingGTU)
1411     {
1412         Variable detector = this.detectors.get(detectorId);
1413         detector.setValue(detectingGTU ? 1 : 0, this.currentTime10);
1414     }
1415 
1416 }
1417 
1418 /**
1419  * Store a variable name, stream and isTimer status.
1420  * <p>
1421  * @version $Revision$, $LastChangedDate$, by $Author$, initial version Oct 6, 2016 <br>
1422  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
1423  */
1424 class NameAndStream
1425 {
1426     /** The name. */
1427     private final String name;
1428 
1429     /** The stream number. */
1430     private short stream = -1;
1431 
1432     /** Number characters parsed. */
1433     private int numberOfChars = 0;
1434 
1435     /** Was a letter N consumed while parsing the name?. */
1436     private boolean negated = false;
1437 
1438     /**
1439      * Parse a name and stream.
1440      * @param text String; the name and stream
1441      * @param locationDescription String; description of the location in the input file
1442      * @throws Exception when text is not a valid TrafCOD variable name
1443      */
1444     public NameAndStream(final String text, final String locationDescription) throws Exception
1445     {
1446         int pos = 0;
1447         while (pos < text.length() && Character.isWhitespace(text.charAt(pos)))
1448         {
1449             pos++;
1450         }
1451         while (pos < text.length())
1452         {
1453             char character = text.charAt(pos);
1454             if (!Character.isLetterOrDigit(character))
1455             {
1456                 break;
1457             }
1458             // if (Character.isWhitespace(character) || '.' == character || '=' == character)
1459             // {
1460             // break;
1461             // }
1462             pos++;
1463         }
1464         this.numberOfChars = pos;
1465         String trimmed = text.substring(0, pos).replaceAll(" ", "");
1466         if (trimmed.length() == 0)
1467         {
1468             throw new Exception("missing variable at " + locationDescription);
1469         }
1470         if (trimmed.matches("^D([Nn]?\\d\\d\\d)|(\\d\\d\\d[Nn])"))
1471         {
1472             // Handle a detector
1473             if (trimmed.charAt(1) == 'N' || trimmed.charAt(1) == 'n')
1474             {
1475                 // Move the 'N' to the end
1476                 trimmed = "D" + trimmed.substring(1, 3) + "N" + trimmed.substring(5);
1477             }
1478             this.name = "D" + trimmed.charAt(3);
1479             this.stream = (short) (10 * (trimmed.charAt(1) - '0') + trimmed.charAt(2) - '0');
1480             return;
1481         }
1482         // else if (trimmed.matches("^T"))
1483         // {
1484         // trimmed = trimmed.substring(1);
1485         // }
1486         StringBuilder nameBuilder = new StringBuilder();
1487         for (pos = 0; pos < trimmed.length(); pos++)
1488         {
1489             char nextChar = trimmed.charAt(pos);
1490             if (pos < trimmed.length() - 1 && Character.isDigit(nextChar) && Character.isDigit(trimmed.charAt(pos + 1))
1491                     && -1 == this.stream)
1492             {
1493                 if (0 == pos || (1 == pos && trimmed.startsWith("N")))
1494                 {
1495                     throw new Exception("Bad variable name: " + trimmed + " at " + locationDescription);
1496                 }
1497                 if (trimmed.charAt(pos - 1) == 'N')
1498                 {
1499                     // Previous N was NOT part of the name
1500                     nameBuilder.deleteCharAt(nameBuilder.length() - 1);
1501                     // Move the 'N' after the digits
1502                     trimmed =
1503                             trimmed.substring(0, pos - 2) + trimmed.substring(pos, pos + 2) + "N" + trimmed.substring(pos + 2);
1504                     pos -= 2;
1505                 }
1506                 this.stream = (short) (10 * (trimmed.charAt(pos) - '0') + trimmed.charAt(pos + 1) - '0');
1507                 pos++;
1508             }
1509             else
1510             {
1511                 nameBuilder.append(nextChar);
1512             }
1513         }
1514         if (trimmed.endsWith("N"))
1515         {
1516             nameBuilder.deleteCharAt(nameBuilder.length() - 1);
1517             this.negated = true;
1518         }
1519         this.name = nameBuilder.toString();
1520     }
1521 
1522     /**
1523      * Was a negation operator ('N') embedded in the name?
1524      * @return boolean
1525      */
1526     public boolean isNegated()
1527     {
1528         return this.negated;
1529     }
1530 
1531     /**
1532      * Retrieve the stream number.
1533      * @return short; the stream number
1534      */
1535     public short getStream()
1536     {
1537         return this.stream;
1538     }
1539 
1540     /**
1541      * Retrieve the name.
1542      * @return String; the name (without the stream number)
1543      */
1544     public String getName()
1545     {
1546         return this.name;
1547     }
1548 
1549     /**
1550      * Retrieve the number of characters consumed from the input.
1551      * @return int; the number of characters consumed from the input
1552      */
1553     public int getNumberOfChars()
1554     {
1555         return this.numberOfChars;
1556     }
1557 
1558     /** {@inheritDoc} */
1559     @Override
1560     public String toString()
1561     {
1562         return "NameAndStream [name=" + this.name + ", stream=" + this.stream + ", numberOfChars=" + this.numberOfChars
1563                 + ", negated=" + this.negated + "]";
1564     }
1565 
1566 }
1567 
1568 /**
1569  * Storage for a TrafCOD variable.
1570  * <p>
1571  * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
1572  * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
1573  * <p>
1574  * @version $Revision$, $LastChangedDate$, by $Author$, initial version Oct 5, 2016 <br>
1575  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
1576  */
1577 class Variable
1578 {
1579     /** Flags. */
1580     EnumSet<Flags> flags = EnumSet.noneOf(Flags.class);
1581 
1582     /** The current value. */
1583     int value;
1584 
1585     /** Limit value (if this is a timer variable). */
1586     int timerMax10;
1587 
1588     /** Output color (if this is an export variable). */
1589     TrafficLightColor color;
1590 
1591     /** Name of this variable (without the traffic stream). */
1592     final String name;
1593 
1594     /** Position in the debugging list. */
1595     char listPos;
1596 
1597     /** Traffic stream number */
1598     final short stream;
1599 
1600     /** Number of rules that refer to this variable. */
1601     int refCount;
1602 
1603     /** Time of last update in tenth of second. */
1604     int updateTime10;
1605 
1606     /** Source of start rule. */
1607     String startSource;
1608 
1609     /** Source of end rule. */
1610     String endSource;
1611 
1612     /** The traffic light (only set if this Variable is an output(. */
1613     private Set<TrafficLight> trafficLights;
1614 
1615     /**
1616      * Construct a new Variable.
1617      * @param name String; name of the new variable (without the stream number)
1618      * @param stream short; stream number to which the new Variable is associated
1619      */
1620     public Variable(final String name, final short stream)
1621     {
1622         this.name = name;
1623         this.stream = stream;
1624         if (name.startsWith("T"))
1625         {
1626             this.flags.add(Flags.IS_TIMER);
1627         }
1628         if (this.name.length() == 2 && this.name.startsWith("D") && Character.isDigit(this.name.charAt(1)))
1629         {
1630             this.flags.add(Flags.IS_DETECTOR);
1631         }
1632     }
1633 
1634     /**
1635      * Retrieve the color for an output Variable.
1636      * @return int; the color code for this Variable
1637      * @throws Exception if this Variable is not an output
1638      */
1639     public TrafficLightColor getColor() throws Exception
1640     {
1641         if (!this.flags.contains(Flags.IS_OUTPUT))
1642         {
1643             throw new Exception("Stream is not an output");
1644         }
1645         return this.color;
1646     }
1647 
1648     /**
1649      * Report whether a change in this variable must be published.
1650      * @return boolean; true if this Variable is an output; false if this Variable is not an output
1651      */
1652     public boolean isOutput()
1653     {
1654         return this.flags.contains(Flags.IS_OUTPUT);
1655     }
1656 
1657     /**
1658      * Report if this Variable is a detector.
1659      * @return boolean; true if this Variable is a detector; false if this Variable is not a detector
1660      */
1661     public boolean isDetector()
1662     {
1663         return this.name.startsWith("D");
1664     }
1665 
1666     /**
1667      * @param newValue int; the new value of this Variable
1668      * @param timeStamp10 int; the time stamp of this update
1669      */
1670     public void setValue(int newValue, int timeStamp10)
1671     {
1672         if (this.value != newValue)
1673         {
1674             this.updateTime10 = timeStamp10;
1675             setFlag(Flags.CHANGED);
1676             if (0 == newValue)
1677             {
1678                 setFlag(Flags.END);
1679             }
1680             else
1681             {
1682                 setFlag(Flags.START);
1683             }
1684             if (isOutput())
1685             {
1686                 for (TrafficLight trafficLight : this.trafficLights)
1687                 {
1688                     trafficLight.setTrafficLightColor(this.color);
1689                 }
1690             }
1691         }
1692         this.value = newValue;
1693     }
1694 
1695     /**
1696      * Retrieve the start value of this timer in units of 0.1 seconds (1 second is represented by the value 10).
1697      * @return int; the timerMax10 value
1698      * @throws Exception
1699      */
1700     public int getTimerMax() throws Exception
1701     {
1702         if (!this.isTimer())
1703         {
1704             throw new Exception("This is not a timer");
1705         }
1706         return this.timerMax10;
1707     }
1708 
1709     /**
1710      * Retrieve the current value of this Variable.
1711      * @return int; the value of this Variable
1712      */
1713     public int getValue()
1714     {
1715         return this.value;
1716     }
1717 
1718     /**
1719      * Set one flag.
1720      * @param flag Flags
1721      */
1722     public void setFlag(final Flags flag)
1723     {
1724         this.flags.add(flag);
1725     }
1726 
1727     /**
1728      * Clear one flag.
1729      * @param flag Flags; the flag to clear
1730      */
1731     public void clearFlag(final Flags flag)
1732     {
1733         this.flags.remove(flag);
1734     }
1735 
1736     /**
1737      * Report whether this Variable is a timer.
1738      * @return boolean; true if this Variable is a timer; false if this variable is not a timer
1739      */
1740     public boolean isTimer()
1741     {
1742         return this.flags.contains(Flags.IS_TIMER);
1743     }
1744 
1745     /**
1746      * Clear the CHANGED flag of this Variable.
1747      */
1748     public void clearChangedFlag()
1749     {
1750         this.flags.remove(Flags.CHANGED);
1751     }
1752 
1753     /**
1754      * Increment the reference counter of this variable. The reference counter counts the number of rules where this variable
1755      * occurs on the right hand side of the assignment operator.
1756      */
1757     public void incrementReferenceCount()
1758     {
1759         this.refCount++;
1760     }
1761 
1762     /**
1763      * Return a safe copy of the flags.
1764      * @return EnumSet&lt;Flags&gt;
1765      */
1766     public EnumSet<Flags> getFlags()
1767     {
1768         return EnumSet.copyOf(this.flags);
1769     }
1770 
1771     /**
1772      * Set a flag of this Variable.
1773      * @param flag Flags; the flag to set
1774      */
1775     public void addFlag(final Flags flag)
1776     {
1777         this.flags.add(flag);
1778     }
1779 
1780     /**
1781      * Make this variable an output variable and set the output value.
1782      * @param colorValue int; the output value
1783      * @param trafficLights Set&lt;TrafficLight&gt;; the traffic light that must be update when this output becomes active
1784      * @throws Exception when the colorValue is invalid
1785      */
1786     public void setOutput(int colorValue, Set<TrafficLight> trafficLights) throws Exception
1787     {
1788         TrafficLightColor newColor;
1789         switch (colorValue)
1790         {
1791             case 'R':
1792                 newColor = TrafficLightColor.RED;
1793                 break;
1794             case 'G':
1795                 newColor = TrafficLightColor.GREEN;
1796                 break;
1797             case 'Y':
1798                 newColor = TrafficLightColor.YELLOW;
1799                 break;
1800             default:
1801                 throw new Exception("Bad color value: " + colorValue);
1802         }
1803 
1804         this.color = newColor;
1805         this.flags.add(Flags.IS_OUTPUT);
1806         this.trafficLights = trafficLights;
1807     }
1808 
1809     /**
1810      * Set the maximum time of this timer.
1811      * @param value10 int; the maximum time in 0.1 s
1812      * @throws Exception when this Variable is not a timer
1813      */
1814     public void setTimerMax(int value10) throws Exception
1815     {
1816         if (!this.flags.contains(Flags.IS_TIMER))
1817         {
1818             throw new Exception("Cannot set maximum timer value of " + selectedFieldsToString(EnumSet.of(PrintFlags.ID)));
1819         }
1820         this.timerMax10 = value10;
1821     }
1822 
1823     /**
1824      * Describe the rule that starts this variable.
1825      * @return String
1826      */
1827     public String getStartSource()
1828     {
1829         return this.startSource;
1830     }
1831 
1832     /**
1833      * Set the description of the rule that starts this variable.
1834      * @param startSource String; description of the rule that starts this variable
1835      * @throws Exception when a start source has already been set
1836      */
1837     public void setStartSource(String startSource) throws Exception
1838     {
1839         if (null != this.startSource)
1840         {
1841             throw new Exception("Conflicting rules: " + this.startSource + " vs " + startSource);
1842         }
1843         this.startSource = startSource;
1844         this.flags.add(Flags.HAS_START_RULE);
1845     }
1846 
1847     /**
1848      * Describe the rule that ends this variable.
1849      * @return String
1850      */
1851     public String getEndSource()
1852     {
1853         return this.endSource;
1854     }
1855 
1856     /**
1857      * Set the description of the rule that ends this variable.
1858      * @param endSource String; description of the rule that ends this variable
1859      * @throws Exception when an end source has already been set
1860      */
1861     public void setEndSource(String endSource) throws Exception
1862     {
1863         if (null != this.endSource)
1864         {
1865             throw new Exception("Conflicting rules: " + this.startSource + " vs " + endSource);
1866         }
1867         this.endSource = endSource;
1868         this.flags.add(Flags.HAS_END_RULE);
1869     }
1870 
1871     /**
1872      * Retrieve the stream to which this variable belongs.
1873      * @return short; the stream to which this variable belongs
1874      */
1875     public short getStream()
1876     {
1877         return this.stream;
1878     }
1879 
1880     /** {@inheritDoc} */
1881     @Override
1882     public String toString()
1883     {
1884         return "Variable [" + selectedFieldsToString(EnumSet.of(PrintFlags.ID, PrintFlags.VALUE)) + "]";
1885     }
1886 
1887     /**
1888      * Print selected fields.
1889      * @param printFlags EnumSet&lt;PrintFlags&gt;; the set of fields to print
1890      * @return String
1891      */
1892     public String selectedFieldsToString(EnumSet<PrintFlags> printFlags)
1893     {
1894         StringBuilder result = new StringBuilder();
1895         if (printFlags.contains(PrintFlags.ID))
1896         {
1897             if (this.flags.contains(Flags.IS_DETECTOR))
1898             {
1899                 result.append("D");
1900             }
1901             else if (printFlags.contains(PrintFlags.INITTIMER))
1902             {
1903                 result.append("I");
1904                 result.append(this.name);
1905             }
1906             else if (printFlags.contains(PrintFlags.REINITTIMER))
1907             {
1908                 result.append("RI");
1909                 result.append(this.name);
1910             }
1911             else
1912             {
1913                 result.append(this.name);
1914             }
1915             if (this.stream > 0)
1916             {
1917                 // Insert the stream BEFORE the first digit in the name (if any); otherwise append
1918                 int pos;
1919                 for (pos = 0; pos < result.length(); pos++)
1920                 {
1921                     if (Character.isDigit(result.charAt(pos)))
1922                     {
1923                         break;
1924                     }
1925                 }
1926                 result.insert(pos, String.format("%02d", this.stream));
1927             }
1928             if (this.flags.contains(Flags.IS_DETECTOR))
1929             {
1930                 result.append(this.name.substring(1));
1931             }
1932             if (printFlags.contains(PrintFlags.NEGATED))
1933             {
1934                 result.append("N");
1935             }
1936         }
1937         int printValue = Integer.MIN_VALUE; // That should stand out if not changed by the code below this line.
1938         if (printFlags.contains(PrintFlags.VALUE))
1939         {
1940             if (printFlags.contains(PrintFlags.NEGATED))
1941             {
1942                 printValue = 0 == this.value ? 1 : 0;
1943             }
1944             else
1945             {
1946                 printValue = this.value;
1947             }
1948             if (printFlags.contains(PrintFlags.S))
1949             {
1950                 if (this.flags.contains(Flags.START))
1951                 {
1952                     printValue = 1;
1953                 }
1954                 else
1955                 {
1956                     printValue = 0;
1957                 }
1958             }
1959             if (printFlags.contains(PrintFlags.E))
1960             {
1961                 if (this.flags.contains(Flags.END))
1962                 {
1963                     printValue = 1;
1964                 }
1965                 else
1966                 {
1967                     printValue = 0;
1968                 }
1969             }
1970         }
1971         if (printFlags.contains(PrintFlags.VALUE) || printFlags.contains(PrintFlags.S) || printFlags.contains(PrintFlags.E)
1972                 || printFlags.contains(PrintFlags.FLAGS))
1973         {
1974             result.append("<");
1975             if (printFlags.contains(PrintFlags.VALUE) || printFlags.contains(PrintFlags.S) || printFlags.contains(PrintFlags.E))
1976             {
1977                 result.append(printValue);
1978             }
1979             if (printFlags.contains(PrintFlags.FLAGS))
1980             {
1981                 if (this.flags.contains(Flags.START))
1982                 {
1983                     result.append("S");
1984                 }
1985                 if (this.flags.contains(Flags.END))
1986                 {
1987                     result.append("E");
1988                 }
1989             }
1990             result.append(">");
1991         }
1992         if (printFlags.contains(PrintFlags.MODIFY_TIME))
1993         {
1994             result.append(String.format(" (%d.%d)", this.updateTime10 / 10, this.updateTime10 % 10));
1995         }
1996         return result.toString();
1997     }
1998 
1999 }
2000 
2001 /**
2002  * Flags for toString method of a Variable.
2003  * <p>
2004  * @version $Revision$, $LastChangedDate$, by $Author$, initial version Oct 6, 2016 <br>
2005  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
2006  */
2007 enum PrintFlags
2008 {
2009     /** The name and stream of the Variable. */
2010     ID,
2011     /** The current Variable. */
2012     VALUE,
2013     /** Print "I" before the name (to indicate that a timer is initialized). */
2014     INITTIMER,
2015     /** Print "RI" before the name (to indicate that a timer is re-initialized). */
2016     REINITTIMER,
2017     /** Print "1" if just set, else print "0". */
2018     S,
2019     /** Print "1" if just reset, else print "0". */
2020     E,
2021     /** Print the negated Variable. */
2022     NEGATED,
2023     /** Print the flags of the Variable. */
2024     FLAGS,
2025     /** Print the time of last modification of the Variable. */
2026     MODIFY_TIME,
2027 }
2028 
2029 /**
2030  * Flags of a TrafCOD variable.
2031  * <p>
2032  * @version $Revision$, $LastChangedDate$, by $Author$, initial version Oct 6, 2016 <br>
2033  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
2034  */
2035 enum Flags
2036 {
2037     /** Variable becomes active. */
2038     START,
2039     /** Variable becomes inactive. */
2040     END,
2041     /** Timer has just expired. */
2042     TIMEREXPIRED,
2043     /** Variable has just changed value. */
2044     CHANGED,
2045     /** Variable is a timer. */
2046     IS_TIMER,
2047     /** Variable is a detector. */
2048     IS_DETECTOR,
2049     /** Variable has a start rule. */
2050     HAS_START_RULE,
2051     /** Variable has an end rule. */
2052     HAS_END_RULE,
2053     /** Variable is an output. */
2054     IS_OUTPUT,
2055     /** Variable must be initialized to 1 at start of control program. */
2056     INITED,
2057 }