View Javadoc
1   package org.opentrafficsim.core.network;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.List;
6   
7   import org.opentrafficsim.core.network.lane.CrossSectionLink;
8   
9   /**
10   * A Route consists of a list of Nodes. The last visited node is kept. Code can ask what the next node is, and can
11   * indicate the next node to visit. Routes can be expanded (e.g. for node expansion), collapsed (e.g. to use a macro
12   * model for a part of the route) or changed (e.g. to avoid congestion). Changing is done by adding and/or removing
13   * nodes of the node list. When the last visited node of the route is deleted, however, it is impossible to follow the
14   * route any further, which will result in a <code>NetworkException</code>.
15   * <p>
16   * Copyright (c) 2013-2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights
17   * reserved. <br>
18   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
19   * <p>
20   * @version Jan 1, 2015 <br>
21   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
22   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a> FIXME: PK If this class is parameterized with
23   *         the detailed Node type, no casts are needed when calling getNode(), visitNextNode, etc. and it will be
24   *         impossible to insert Nodes of mixed types in the same Route.
25   */
26  public class Route implements Serializable
27  {
28      /** */
29      private static final long serialVersionUID = 20150101L;
30  
31      /** The nodes of the route. */
32      private final List<Node<?, ?>> nodes;
33  
34      /** last visited node on the route. */
35      private int lastNode = -1;
36  
37      /**
38       * Create an empty route.
39       */
40      public Route()
41      {
42          this.nodes = new ArrayList<Node<?, ?>>();
43      }
44  
45      /**
46       * Create a route based on an initial list of nodes.
47       * @param nodes the initial list of nodes.
48       */
49      public Route(final List<Node<?, ?>> nodes)
50      {
51          // FIXME PK: Either make a defensive copy, or document that this constructor does not make a defensive copy
52          this.nodes = nodes;
53      }
54  
55      /**
56       * Add a node to the end of the node list.
57       * @param node the node to add.
58       * @return whether the add was successful.
59       * @throws NetworkException when node could not be added.
60       */
61      public final boolean addNode(final Node<?, ?> node) throws NetworkException
62      {
63          try
64          {
65              return this.nodes.add(node);
66          }
67          catch (RuntimeException e)
68          {
69              throw new NetworkException("Route.addNode(Node) could not be executed", e);
70          }
71      }
72  
73      /**
74       * Add a node at a specific location.
75       * @param i the location to put the node (0-based).
76       * @param node the node to add.
77       * @throws NetworkException when i<0 or i>=nodes.size(). Also thrown when another error occurs.
78       */
79      public final void addNode(final int i, final Node<?, ?> node) throws NetworkException
80      {
81          try
82          {
83              this.nodes.add(i, node);
84          }
85          catch (IndexOutOfBoundsException e)
86          {
87              throw new NetworkException("Route.addNode(i, Node) was called where i<0 or i>=nodes.size()");
88          }
89          catch (RuntimeException e)
90          {
91              throw new NetworkException("Route.addNode(i, Node) could not be executed", e);
92          }
93          // Do this AFTER executing the insert operation (otherwise we could/would increment lastNode and then fail to
94          // insert the node).
95          if (i <= this.lastNode)
96          {
97              // quite useless, as we have already done that part of the route, but we have to keep consistency!
98              this.lastNode++;
99          }
100     }
101 
102     /**
103      * Remove a node from a specific location.
104      * @param i the location to remove the node from (0-based).
105      * @return the removed node.
106      * @throws NetworkException when i is equal to the last visited node because the next link on the route cannot be
107      *             computed anymore. Also thrown when another error occurs.
108      */
109     public final Node<?, ?> removeNode(final int i) throws NetworkException
110     {
111         Node<?, ?> result = null;
112         if (i == this.lastNode)
113         {
114             throw new NetworkException("Route.removeNode(i) was called where i was equal to the last visited node");
115         }
116         try
117         {
118             result = this.nodes.remove(i);
119         }
120         catch (RuntimeException e)
121         {
122             throw new NetworkException("Route.removeNode(i, Node) could not be executed", e);
123         }
124         // Do this AFTER the removal operation has succeeded.
125         if (i < this.lastNode)
126         {
127             // quite useless, as we have already done that part of the route, but we have to keep consistency!
128             this.lastNode--;
129         }
130         return result;
131     }
132 
133     /**
134      * Return a node at a specific location.
135      * @param i the location to get the node from (0-based).
136      * @return the retrieved node.
137      * @throws NetworkException when i<0 or i>=nodes.size().
138      */
139     public final Node<?, ?> getNode(final int i) throws NetworkException
140     {
141         try
142         {
143             return this.nodes.get(i);
144         }
145         catch (IndexOutOfBoundsException e)
146         {
147             throw new NetworkException("Route.getNode(i) was called where i<0 or i>=nodes.size()");
148         }
149     }
150 
151     /**
152      * @return the first node of the route.
153      * @throws NetworkException when the list has no nodes.
154      */
155     public final Node<?, ?> originNode() throws NetworkException
156     {
157         if (this.nodes.size() == 0)
158         {
159             throw new NetworkException("Route.getOrigin() called, but node list has no nodes");
160         }
161         return getNode(0);
162     }
163 
164     /**
165      * @return the number of nodes in the list. If the list contains more than Integer.MAX_VALUE elements, returns
166      *         Integer.MAX_VALUE.
167      */
168     public final int size()
169     {
170         return this.nodes.size();
171     }
172 
173     /**
174      * @return the last node of the route.
175      * @throws NetworkException when the list has no nodes.
176      */
177     public final Node<?, ?> destinationNode() throws NetworkException
178     {
179         if (this.nodes.size() == 0)
180         {
181             throw new NetworkException("Route.getDestination() called, but node list has no nodes");
182         }
183         return getNode(size() - 1);
184     }
185 
186     /**
187      * @return the last visited node of the route, and null when no nodes have been visited yet.
188      * @throws NetworkException when the index is out of bounds (should never happen).
189      */
190     public final Node<?, ?> lastVisitedNode() throws NetworkException
191     {
192         if (this.lastNode == -1)
193         {
194             return null;
195         }
196         return getNode(this.lastNode);
197     }
198 
199     /**
200      * This method does <b>not</b> advance the route pointer.
201      * @return the next node of the route to visit, and null when we already reached the destination.
202      * @throws NetworkException when the index is out of bounds (should never happen).
203      */
204     public final Node<?, ?> nextNodeToVisit() throws NetworkException
205     {
206         if (this.lastNode >= size() - 1)
207         {
208             return null;
209         }
210         return getNode(this.lastNode + 1);
211     }
212 
213     /**
214      * This method <b>does</b> advance the route pointer (if possible).
215      * @return the next node of the route to visit, and null when we already reached the destination.
216      * @throws NetworkException when the index is out of bounds (should never happen).
217      */
218     public final Node<?, ?> visitNextNode() throws NetworkException
219     {
220         if (this.lastNode >= size() - 1)
221         {
222             return null;
223         }
224         this.lastNode++;
225         return getNode(this.lastNode);
226     }
227 
228     /**
229      * FIXME: this fails if this route contains a Node more than once.
230      * @param link the link to check in the route.
231      * @return whether the link is part of the route or not.
232      */
233     public final boolean containsLink(final CrossSectionLink<?, ?> link)
234     {
235         Node<?, ?> sn = link.getStartNode();
236         Node<?, ?> en = link.getEndNode();
237         if (this.nodes.contains(sn) && this.nodes.contains(en))
238         {
239             return this.nodes.indexOf(en) - this.nodes.indexOf(sn) == 1;
240         }
241         return false;
242     }
243 
244     /**
245      * Return the index of a Node in this Route, or -1 if this Route does not contain the specified Node. FIXME: this
246      * fails if this route contains a Node more than once.
247      * @param node Node&lt;?, ?&gt;; the Node to find
248      * @return int;
249      */
250     public final int indexOf(final Node<?, ?> node)
251     {
252         return this.nodes.indexOf(node);
253     }
254 
255     /** {@inheritDoc} */
256     @SuppressWarnings("checkstyle:designforextension")
257     public String toString()
258     {
259         StringBuilder result = new StringBuilder();
260         final String currentLocationMark = "<>";
261         result.append("Route: [");
262         String separator = "";
263         if (this.lastNode < 0)
264         {
265             result.append(currentLocationMark);
266         }
267         for (int index = 0; index < this.nodes.size(); index++)
268         {
269             Node<?, ?> node = this.nodes.get(index);
270             result.append(separator + node);
271             if (index == this.lastNode)
272             {
273                 result.append(" " + currentLocationMark); // Indicate current position in the route
274             }
275             separator = ", ";
276         }
277         result.append("]");
278         return result.toString();
279     }
280 
281 }