001package jmri.jmrit.operations.router;
002
003import java.io.PrintWriter;
004import java.text.MessageFormat;
005import java.util.*;
006
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import jmri.InstanceManager;
011import jmri.InstanceManagerAutoDefault;
012import jmri.jmrit.operations.locations.*;
013import jmri.jmrit.operations.rollingstock.RollingStock;
014import jmri.jmrit.operations.rollingstock.cars.Car;
015import jmri.jmrit.operations.setup.Setup;
016import jmri.jmrit.operations.trains.*;
017import jmri.jmrit.operations.trains.trainbuilder.TrainCommon;
018
019/**
020 * Router for car movement. This code attempts to find a way (a route) to move a
021 * car to its final destination through the use of two or more trains. First the
022 * code tries to move car using a single train. If that fails, attempts are made
023 * using two trains via a classification/interchange (C/I) tracks, then yard
024 * tracks if enabled. Next attempts are made using three or more trains using
025 * any combination of C/I and yard tracks. If that fails and routing via staging
026 * is enabled, the code tries two trains using staging tracks, then multiple
027 * trains using a combination of C/I, yards, and staging tracks. Currently the
028 * router is limited to seven trains.
029 *
030 * @author Daniel Boudreau Copyright (C) 2010, 2011, 2012, 2013, 2015, 2021,
031 *         2022, 2024
032 */
033public class Router extends TrainCommon implements InstanceManagerAutoDefault {
034
035    TrainManager tmanager = InstanceManager.getDefault(TrainManager.class);
036
037    protected final List<Track> _nextLocationTracks = new ArrayList<>();
038    protected final List<Track> _lastLocationTracks = new ArrayList<>();
039    private final List<Track> _otherLocationTracks = new ArrayList<>();
040
041    protected final List<Track> _next2ndLocationTracks = new ArrayList<>();
042    protected final List<Track> _next3rdLocationTracks = new ArrayList<>();
043    protected final List<Track> _next4thLocationTracks = new ArrayList<>();
044
045    protected final List<Train> _nextLocationTrains = new ArrayList<>();
046    protected final List<Train> _lastLocationTrains = new ArrayList<>();
047
048    protected Hashtable<String, Train> _listTrains = new Hashtable<>();
049
050    protected static final String STATUS_NOT_THIS_TRAIN = Bundle.getMessage("RouterTrain");
051    public static final String STATUS_NOT_THIS_TRAIN_PREFIX =
052            STATUS_NOT_THIS_TRAIN.substring(0, STATUS_NOT_THIS_TRAIN.indexOf('('));
053    protected static final String STATUS_NOT_ABLE = Bundle.getMessage("RouterNotAble");
054    protected static final String STATUS_ROUTER_DISABLED = Bundle.getMessage("RouterDisabled");
055
056    private String _status = "";
057    private Train _train = null;
058    PrintWriter _buildReport = null; // build report
059    Date _startTime; // when routing started
060
061    private static final String SEVEN = Setup.BUILD_REPORT_VERY_DETAILED;
062    private boolean _addtoReport = false;
063    private boolean _addtoReportVeryDetailed = false;
064
065    /**
066     * Returns the status of the router when using the setDestination() for a
067     * car.
068     *
069     * @return Track.OKAY, STATUS_NOT_THIS_TRAIN, STATUS_NOT_ABLE,
070     *         STATUS_ROUTER_DISABLED, or the destination track status is
071     *         there's an issue.
072     */
073    public String getStatus() {
074        return _status;
075    }
076
077    /**
078     * Determines if car can be routed to the destination track
079     * 
080     * @param car         the car being tested
081     * @param train       the first train servicing the car, can be null
082     * @param track       the destination track, can not be null
083     * @param buildReport the report, can be null
084     * @return true if the car can be routed to the track
085     */
086    public boolean isCarRouteable(Car car, Train train, Track track, PrintWriter buildReport) {
087        addLine(buildReport, SEVEN, Bundle.getMessage("RouterIsCarRoutable",
088                car.toString(), car.getLocationName(), car.getTrackName(), car.getLoadName(),
089                track.getLocation().getName(), track.getName()));
090        return isCarRouteable(car, train, track.getLocation(), track, buildReport);
091    }
092
093    public boolean isCarRouteable(Car car, Train train, Location destination, Track track, PrintWriter buildReport) {
094        Car c = car.copy();
095        c.setTrack(car.getTrack());
096        c.setFinalDestination(destination);
097        c.setFinalDestinationTrack(track);
098        boolean results = setDestination(c, train, buildReport);
099        c.setDestination(null, null); // clear router car destinations
100        c.setFinalDestinationTrack(null);
101        // transfer route path info
102        car.setRoutePath(c.getRoutePath());
103        return results;
104    }
105
106    /**
107     * Attempts to set the car's destination if a final destination exists. Only
108     * sets the car's destination if the train is part of the car's route.
109     *
110     * @param car         the car to route
111     * @param train       the first train to carry this car, can be null
112     * @param buildReport PrintWriter for build report, and can be null
113     * @return true if car can be routed.
114     */
115    public boolean setDestination(Car car, Train train, PrintWriter buildReport) {
116        if (car.getTrack() == null || car.getFinalDestination() == null) {
117            return false;
118        }
119        _startTime = new Date();
120        _status = Track.OKAY;
121        _train = train;
122        _buildReport = buildReport;
123        _addtoReport = Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_DETAILED) ||
124                Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED);
125        _addtoReportVeryDetailed = Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED);
126        log.debug("Car ({}) at location ({}, {}) final destination ({}, {}) car routing begins", car,
127                car.getLocationName(), car.getTrackName(), car.getFinalDestinationName(),
128                car.getFinalDestinationTrackName());
129        if (_train != null) {
130            log.debug("Routing using train ({})", train.getName());
131        }
132        // is car part of kernel?
133        if (car.getKernel() != null && !car.isLead()) {
134            return false;
135        }
136        // note clone car has the car's "final destination" as its destination
137        Car clone = clone(car);
138        // Note the following test doesn't check for car length which is what we
139        // want.
140        // Also ignores spur schedule since the car's destination is already
141        // set.
142        _status = clone.checkDestination(clone.getDestination(), clone.getDestinationTrack());
143        if (!_status.equals(Track.OKAY)) {
144            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCanNotDeliverCar",
145                    car.toString(), car.getFinalDestinationName(), car.getFinalDestinationTrackName(),
146                    _status, (car.getFinalDestinationTrack() == null ? Bundle.getMessage("RouterDestination")
147                            : car.getFinalDestinationTrack().getTrackTypeName())));
148            return false;
149        }
150        // check to see if car has a destination track or one is available
151        if (!checkForDestinationTrack(clone)) {
152            return false; // no destination track found
153        }
154        // check to see if car will move to destination using a single train
155        if (checkForSingleTrain(car, clone)) {
156            return true; // a single train can service this car
157        }
158        if (!Setup.isCarRoutingEnabled()) {
159            log.debug("Car ({}) final destination ({}) is not served directly by any train", car,
160                    car.getFinalDestinationName()); // NOI18N
161            _status = STATUS_ROUTER_DISABLED;
162            car.setFinalDestination(null);
163            car.setFinalDestinationTrack(null);
164            return false;
165        }
166        log.debug("Car ({}) final destination ({}) is not served by a single train", car,
167                car.getFinalDestinationName());
168        // was the request for a local move? Try multiple trains to move car
169        if (car.getLocationName().equals(car.getFinalDestinationName())) {
170            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCouldNotFindTrain",
171                    car.getLocationName(), car.getTrackName(), car.getFinalDestinationName(),
172                    car.getFinalDestinationTrackName()));
173        }
174        if (_addtoReport) {
175            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterBeginTwoTrain",
176                    car.toString(), car.getLocationName(), car.getFinalDestinationName()));
177        }
178
179        _nextLocationTracks.clear();
180        _next2ndLocationTracks.clear();
181        _next3rdLocationTracks.clear();
182        _next4thLocationTracks.clear();
183        _lastLocationTracks.clear();
184        _otherLocationTracks.clear();
185        _nextLocationTrains.clear();
186        _lastLocationTrains.clear();
187        _listTrains.clear();
188
189        // first try using 2 trains and an interchange track to route the car
190        if (setCarDestinationTwoTrainsInterchange(car)) {
191            if (car.getDestination() == null) {
192                log.debug(
193                        "Was able to find a route via classification/interchange track, but not using specified train" +
194                                " or car destination not set, try again using yard tracks"); // NOI18N
195                if (setCarDestinationTwoTrainsYard(car)) {
196                    log.debug("Was able to find route via yard ({}, {}) for car ({})", car.getDestinationName(),
197                            car.getDestinationTrackName(), car);
198                }
199            } else {
200                log.debug("Was able to find route via interchange ({}, {}) for car ({})", car.getDestinationName(),
201                        car.getDestinationTrackName(), car);
202            }
203            // now try 2 trains using a yard track
204        } else if (setCarDestinationTwoTrainsYard(car)) {
205            log.debug("Was able to find route via yard ({}, {}) for car ({}) using two trains",
206                    car.getDestinationName(), car.getDestinationTrackName(), car);
207            // now try 3 or more trains to route car, but not through staging
208        } else if (setCarDestinationMultipleTrains(car, false)) {
209            log.debug("Was able to find multiple train route for car ({})", car);
210            // now try 2 trains using a staging track to connect
211        } else if (setCarDestinationTwoTrainsStaging(car)) {
212            log.debug("Was able to find route via staging ({}, {}) for car ({}) using two trains",
213                    car.getDestinationName(), car.getDestinationTrackName(), car);
214            // now try 3 or more trains to route car, include staging if enabled
215        } else if (setCarDestinationMultipleTrains(car, true)) {
216            log.debug("Was able to find multiple train route for car ({}) through staging", car);
217        } else {
218            log.debug("Wasn't able to set route for car ({}) took {} mSec", car,
219                    new Date().getTime() - _startTime.getTime());
220            _status = STATUS_NOT_ABLE;
221            return false; // maybe next time
222        }
223        return true; // car's destination has been set
224    }
225
226    /*
227     * Checks to see if the car has a destination track, no destination track,
228     * searches for one. returns true if the car has a destination track or if
229     * there's one available.
230     */
231    private boolean checkForDestinationTrack(Car clone) {
232        if (clone.getDestination() != null && clone.getDestinationTrack() == null) {
233            // determine if there's a track that can service the car
234            String status = "";
235            for (Track track : clone.getDestination().getTracksList()) {
236                status = track.isRollingStockAccepted(clone);
237                if (status.equals(Track.OKAY) || status.startsWith(Track.LENGTH)) {
238                    log.debug("Track ({}) will accept car ({})", track.getName(), clone.toString());
239                    break;
240                }
241            }
242            if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
243                addLine(_buildReport, SEVEN, _status = Bundle.getMessage("RouterNoTracks",
244                        clone.getDestinationName(), clone.toString()));
245                return false;
246            }
247        }
248        return true;
249    }
250
251    /**
252     * Checks to see if a single train can transport car to its final
253     * destination. Special case if car is departing staging.
254     *
255     * @return true if single train can transport car to its final destination.
256     */
257    private boolean checkForSingleTrain(Car car, Car clone) {
258        boolean trainServicesCar = false; // true the specified train can service the car
259        Train testTrain = null;
260        if (_train != null) {
261            trainServicesCar = _train.isServiceable(_buildReport, clone);
262        }
263        if (trainServicesCar) {
264            testTrain = _train; // use the specified train
265            log.debug("Train ({}) can service car ({})", _train.getName(), car.toString());
266        } else if (_train != null && !_train.getServiceStatus().equals(Train.NONE)) {
267            // _train isn't able to service car
268            // determine if car was attempting to go to the train's termination staging
269            String trackName = car.getFinalDestinationTrackName();
270            if (car.getFinalDestinationTrack() == null &&
271                    car.getFinalDestinationName().equals(_train.getTrainTerminatesName()) &&
272                    _train.getTerminationTrack() != null) {
273                trackName = _train.getTerminationTrack().getName(); // use staging track
274            }
275            // report that train can't service car
276            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainCanNotDueTo", _train.getName(), car.toString(),
277                    car.getFinalDestinationName(), trackName, _train.getServiceStatus()));
278            if (!car.getTrack().isStaging() &&
279                    !_train.isServiceAllCarsWithFinalDestinationsEnabled()) {
280                _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{_train.getName()});
281                return true; // temporary issue with train moves, length, or destination track length
282            }
283        }
284        // Determines if specified train can service car out of staging.
285        // Note that the router code will try to route the car using
286        // two or more trains just to get the car out of staging.
287        if (car.getTrack().isStaging() && _train != null && !trainServicesCar) {
288            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainCanNotStaging",
289                    _train.getName(), car.toString(), car.getLocationName(),
290                    clone.getDestinationName(), clone.getDestinationTrackName()));
291            if (!_train.getServiceStatus().equals(Train.NONE)) {
292                addLine(_buildReport, SEVEN, _train.getServiceStatus());
293            }
294            addLine(_buildReport, SEVEN,
295                    Bundle.getMessage("RouterStagingTryRouting", car.toString(), clone.getLocationName(),
296                            clone.getDestinationName(), clone.getDestinationTrackName()));
297            // note that testTrain = null, return false
298        } else if (!trainServicesCar) {
299            List<Train> excludeTrains = new ArrayList<>(Arrays.asList(_train));
300            testTrain = tmanager.getTrainForCar(clone, excludeTrains, _buildReport);
301        }
302        // report that another train could transport the car
303        if (testTrain != null &&
304                _train != null &&
305                !trainServicesCar &&
306                _train.isServiceAllCarsWithFinalDestinationsEnabled()) {
307            // log.debug("Option to service all cars with a final destination is enabled");
308            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterOptionToCarry",
309                    _train.getName(), testTrain.getName(), car.toString(),
310                    clone.getDestinationName(), clone.getDestinationTrackName()));
311            testTrain = null; // return false
312        }
313        if (testTrain != null) {
314            return finishRouteUsingOneTrain(testTrain, car, clone);
315        }
316        return false;
317    }
318
319    /**
320     * A single train can service the car. Provide various messages to build
321     * report detailing which train can service the car. Also checks to see if
322     * the needs to go the alternate track or yard track if the car's final
323     * destination track is full. Returns false if car is stuck in staging. Sets
324     * the car's destination if specified _train is available
325     *
326     * @return true for all cases except if car is departing staging and is
327     *         stuck there.
328     */
329    private boolean finishRouteUsingOneTrain(Train testTrain, Car car, Car clone) {
330        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainCanTransport", testTrain.getName(), car.toString(),
331                car.getTrack().getTrackTypeName(), car.getLocationName(), car.getTrackName(),
332                clone.getDestinationName(), clone.getDestinationTrackName()));
333        showRoute(car, new ArrayList<>(Arrays.asList(testTrain)),
334                new ArrayList<>(Arrays.asList(car.getFinalDestinationTrack())));
335        // don't modify car if a train wasn't specified
336        if (_train == null) {
337            return true; // done, car can be routed
338        }
339        // now check to see if specified train can service car directly
340        else if (_train != testTrain) {
341            addLine(_buildReport, SEVEN, Bundle.getMessage("TrainDoesNotServiceCar", _train.getName(), car.toString(),
342                    clone.getDestinationName(), clone.getDestinationTrackName()));
343            _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{testTrain.getName()});
344            return true; // car can be routed, but not by this train!
345        }
346        _status = car.setDestination(clone.getDestination(), clone.getDestinationTrack());
347        if (_status.equals(Track.OKAY)) {
348            return true; // done, car has new destination
349        }
350        addLine(_buildReport, SEVEN,
351                Bundle.getMessage("RouterCanNotDeliverCar", car.toString(), clone.getDestinationName(),
352                        clone.getDestinationTrackName(), _status,
353                        (clone.getDestinationTrack() == null ? Bundle.getMessage("RouterDestination")
354                                : clone.getDestinationTrack().getTrackTypeName())));
355        // check to see if an alternative track was specified
356        if ((_status.startsWith(Track.LENGTH) || _status.startsWith(Track.SCHEDULE)) &&
357                clone.getDestinationTrack() != null &&
358                clone.getDestinationTrack().getAlternateTrack() != null &&
359                clone.getDestinationTrack().getAlternateTrack() != car.getTrack()) {
360            String status = car.setDestination(clone.getDestination(), clone.getDestinationTrack().getAlternateTrack());
361            if (status.equals(Track.OKAY)) {
362                if (_train.isServiceable(car)) {
363                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterSendCarToAlternative",
364                            car.toString(), clone.getDestinationTrack().getAlternateTrack().getName(),
365                            clone.getDestination().getName()));
366                    return true; // car is going to alternate track
367                }
368                addLine(_buildReport, SEVEN,
369                        Bundle.getMessage("RouterNotSendCarToAlternative", _train.getName(), car.toString(),
370                                clone.getDestinationTrack().getAlternateTrack().getName(),
371                                clone.getDestination().getName()));
372            } else {
373                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterAlternateFailed",
374                        clone.getDestinationTrack().getAlternateTrack().getName(), status));
375            }
376        } else if (clone.getDestinationTrack() != null &&
377                clone.getDestinationTrack().getAlternateTrack() != null &&
378                clone.getDestinationTrack().getAlternateTrack() == car.getTrack()) {
379            // state that car is spotted at the alternative track
380            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterAtAlternate",
381                    car.toString(), clone.getDestinationTrack().getAlternateTrack().getName(),
382                    clone.getLocationName(), clone.getDestinationTrackName()));
383        } else if (car.getLocation() == clone.getDestination()) {
384            // state that alternative and yard track options are not available
385            // if car is at final destination
386            addLine(_buildReport, SEVEN,
387                    Bundle.getMessage("RouterIgnoreAlternate", car.toString(), car.getLocationName()));
388        }
389        // check to see if spur was full, if so, forward to yard if possible
390        if (Setup.isForwardToYardEnabled() &&
391                _status.startsWith(Track.LENGTH) &&
392                car.getLocation() != clone.getDestination()) {
393            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterSpurFull",
394                    clone.getDestinationName(), clone.getDestinationTrackName(), clone.getDestinationName()));
395            Location dest = clone.getDestination();
396            List<Track> yards = dest.getTracksByMoves(Track.YARD);
397            log.debug("Found {} yard(s) at destination ({})", yards.size(), clone.getDestinationName());
398            for (Track track : yards) {
399                String status = car.setDestination(dest, track);
400                if (status.equals(Track.OKAY)) {
401                    if (!_train.isServiceable(car)) {
402                        log.debug("Train ({}) can not deliver car ({}) to yard ({})", _train.getName(), car,
403                                track.getName());
404                        continue;
405                    }
406                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterSendCarToYard",
407                            car.toString(), dest.getName(), track.getName(), dest.getName()));
408                    return true; // car is going to a yard
409                } else {
410                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCanNotUseYard",
411                            track.getLocation().getName(), track.getName(), status));
412                }
413            }
414            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNoYardTracks",
415                    dest.getName(), car.toString()));
416        }
417        car.setDestination(null, null);
418        if (car.getTrack().isStaging()) {
419            addLine(_buildReport, SEVEN,
420                    Bundle.getMessage("RouterStagingTryRouting", car.toString(), clone.getLocationName(),
421                            clone.getDestinationName(), clone.getDestinationTrackName()));
422            return false; // try 2 or more trains
423        }
424        return true; // able to route, but unable to set the car's destination
425    }
426
427    /**
428     * Sets a car's destination to an interchange track if two trains can route
429     * the car.
430     *
431     * @param car the car to be routed
432     * @return true if car's destination has been modified to an interchange.
433     *         False if an interchange track wasn't found that could service the
434     *         car's final destination.
435     */
436    private boolean setCarDestinationTwoTrainsInterchange(Car car) {
437        return setCarDestinationTwoTrains(car, Track.INTERCHANGE);
438    }
439
440    /**
441     * Sets a car's destination to a yard track if two trains can route the car.
442     *
443     * @param car the car to be routed
444     * @return true if car's destination has been modified to a yard. False if a
445     *         yard track wasn't found that could service the car's final
446     *         destination.
447     */
448    private boolean setCarDestinationTwoTrainsYard(Car car) {
449        if (Setup.isCarRoutingViaYardsEnabled()) {
450            return setCarDestinationTwoTrains(car, Track.YARD);
451        }
452        return false;
453    }
454
455    /**
456     * Sets a car's destination to a staging track if two trains can route the
457     * car.
458     *
459     * @param car the car to be routed
460     * @return true if car's destination has been modified to a staging track.
461     *         False if a staging track wasn't found that could service the
462     *         car's final destination.
463     */
464    private boolean setCarDestinationTwoTrainsStaging(Car car) {
465        if (Setup.isCarRoutingViaStagingEnabled()) {
466            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterAttemptStaging", car.toString(),
467                    car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
468            return setCarDestinationTwoTrains(car, Track.STAGING);
469        }
470        return false;
471    }
472
473    /*
474     * Note that this routine loads the last set of tracks and trains that can
475     * service the car to its final location. This routine attempts to find a
476     * "two" train route by cycling through various interchange, yard, and
477     * staging tracks searching for a second train that can pull the car from
478     * the track and deliver the car to the its destination. Then the program
479     * determines if the train being built or another train (first) can deliver
480     * the car to the track from its current location. If successful, a two
481     * train route was found, and returns true.
482     */
483    private boolean setCarDestinationTwoTrains(Car car, String trackType) {
484        Car testCar = clone(car); // reload
485        log.debug("Two train routing, find {} track for car ({}) final destination ({}, {})", trackType, car,
486                testCar.getDestinationName(), testCar.getDestinationTrackName());
487        if (_addtoReportVeryDetailed) {
488            addLine(_buildReport, SEVEN, BLANK_LINE);
489            addLine(_buildReport, SEVEN,
490                    Bundle.getMessage("RouterFindTrack", Track.getTrackTypeName(trackType), car.toString(),
491                            testCar.getDestinationName(), testCar.getDestinationTrackName()));
492        }
493        boolean foundRoute = false;
494        // now search for a yard or interchange that a train can pick up and
495        // deliver the car to its destination
496        List<Track> tracks = getTracks(car, testCar, trackType);
497        for (Track track : tracks) {
498            if (_addtoReportVeryDetailed) {
499                addLine(_buildReport, SEVEN, BLANK_LINE);
500                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterFoundTrack",
501                        Track.getTrackTypeName(trackType), track.getLocation().getName(),
502                        track.getName(), car.toString()));
503            }
504            // test to see if there's a train that can deliver the car to its
505            // final location
506            testCar.setTrack(track);
507            testCar.setDestination(car.getFinalDestination());
508            // note that destination track can be null
509            testCar.setDestinationTrack(car.getFinalDestinationTrack());
510            Train secondTrain = tmanager.getTrainForCar(testCar, _buildReport);
511            if (secondTrain == null) {
512                // maybe the train being built can service the car?
513                String specified = canSpecifiedTrainService(testCar);
514                if (specified.equals(NOT_NOW)) {
515                    secondTrain = _train;
516                } else {
517                    if (_addtoReportVeryDetailed) {
518                        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNotFindTrain",
519                                Track.getTrackTypeName(trackType), track.getLocation().getName(),
520                                track.getName(), testCar.getDestinationName(),
521                                testCar.getDestinationTrackName()));
522                    }
523                    continue;
524                }
525            }
526            if (_addtoReportVeryDetailed) {
527                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainCanTransport",
528                        secondTrain.getName(), car.toString(), testCar.getTrack().getTrackTypeName(),
529                        testCar.getLocationName(), testCar.getTrackName(), testCar.getDestinationName(),
530                        testCar.getDestinationTrackName()));
531            }
532            // Save the "last" tracks for later use if needed
533            _lastLocationTracks.add(track);
534            _lastLocationTrains.add(secondTrain);
535            // now try to forward car to this track
536            testCar.setTrack(car.getTrack()); // restore car origin
537            testCar.setDestination(track.getLocation());
538            testCar.setDestinationTrack(track);
539            // determine if car can be transported from current location to this
540            // interchange, yard, or staging track
541            // Now find a train that will transport the car to this track
542            Train firstTrain = null;
543            String specified = canSpecifiedTrainService(testCar);
544            if (specified.equals(YES)) {
545                firstTrain = _train;
546            } else if (specified.equals(NOT_NOW)) {
547                // found a two train route for this car, show the car's route
548                List<Train> trains = new ArrayList<>(Arrays.asList(_train, secondTrain));
549                tracks = new ArrayList<>(Arrays.asList(track, car.getFinalDestinationTrack()));
550                showRoute(car, trains, tracks);
551
552                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainCanNotDueTo",
553                        _train.getName(), car.toString(), track.getLocation().getName(), track.getName(),
554                        _train.getServiceStatus()));
555                foundRoute = true; // issue is route moves or train length
556            } else {
557                firstTrain = tmanager.getTrainForCar(testCar, _buildReport);
558            }
559            // check to see if a train or trains with the same route is delivering and pulling the car to an interchange track
560            if (firstTrain != null &&
561                    firstTrain.getRoute() == secondTrain.getRoute() &&
562                    track.isInterchange() &&
563                    track.getPickupOption().equals(Track.ANY)) {
564                if (_addtoReportVeryDetailed) {
565                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterSameInterchange", firstTrain.getName(),
566                            track.getLocation().getName(), track.getName()));
567                }
568                List<Train> excludeTrains = new ArrayList<>();
569                excludeTrains.add(firstTrain);
570                firstTrain = tmanager.getTrainForCar(testCar, excludeTrains, _buildReport);
571            }
572            if (firstTrain == null && _addtoReportVeryDetailed) {
573                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNotFindTrain",
574                        testCar.getTrack().getTrackTypeName(), testCar.getTrack().getLocation().getName(),
575                        testCar.getTrack().getName(),
576                        testCar.getDestinationName(), testCar.getDestinationTrackName()));
577            }
578            // Can the specified train carry this car out of staging?
579            if (_train != null && car.getTrack().isStaging() && !specified.equals(YES)) {
580                if (_addtoReport) {
581                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainCanNot",
582                            _train.getName(), car.toString(), car.getLocationName(),
583                            car.getTrackName(), track.getLocation().getName(), track.getName()));
584                }
585                continue; // can't use this train
586            }
587            // Is the option for the specified train carry this car?
588            if (firstTrain != null &&
589                    _train != null &&
590                    _train.isServiceAllCarsWithFinalDestinationsEnabled() &&
591                    !specified.equals(YES)) {
592                if (_addtoReport) {
593                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterOptionToCarry",
594                            _train.getName(), firstTrain.getName(), car.toString(),
595                            track.getLocation().getName(), track.getName()));
596                }
597                continue; // can't use this train
598            }
599            if (firstTrain != null) {
600                foundRoute = true; // found a route
601                if (_addtoReportVeryDetailed) {
602                    addLine(_buildReport, SEVEN,
603                            Bundle.getMessage("RouterTrainCanTransport", firstTrain.getName(), car.toString(),
604                                    testCar.getTrack().getTrackTypeName(),
605                                    testCar.getLocationName(), testCar.getTrackName(), testCar.getDestinationName(),
606                                    testCar.getDestinationTrackName()));
607                }
608                // found a two train route for this car, show the car's route
609                List<Train> trains = new ArrayList<>(Arrays.asList(firstTrain, secondTrain));
610                tracks = new ArrayList<>(Arrays.asList(track, car.getFinalDestinationTrack()));
611                showRoute(car, trains, tracks);
612
613                _status = car.checkDestination(track.getLocation(), track);
614                if (_status.startsWith(Track.LENGTH)) {
615                    // if the issue is length at the interim track, add message
616                    // to build report
617                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCanNotDeliverCar",
618                            car.toString(), track.getLocation().getName(), track.getName(),
619                            _status, track.getTrackTypeName()));
620                    continue;
621                }
622                if (_status.equals(Track.OKAY)) {
623                    // only set car's destination if specified train can service
624                    // car
625                    if (_train != null && _train != firstTrain) {
626                        addLine(_buildReport, SEVEN, Bundle.getMessage("TrainDoesNotServiceCar",
627                                _train.getName(), car.toString(), testCar.getDestinationName(),
628                                testCar.getDestinationTrackName()));
629                        _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{firstTrain.getName()});
630                        continue;// found a route but it doesn't start with the
631                                 // specified train
632                    }
633                    // is this the staging track assigned to the specified
634                    // train?
635                    if (track.isStaging() &&
636                            firstTrain.getTerminationTrack() != null &&
637                            firstTrain.getTerminationTrack() != track) {
638                        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainIntoStaging", firstTrain.getName(),
639                                firstTrain.getTerminationTrack().getLocation().getName(),
640                                firstTrain.getTerminationTrack().getName()));
641                        continue;
642                    }
643                    _status = car.setDestination(track.getLocation(), track);
644                    if (_addtoReport) {
645                        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainCanService",
646                                firstTrain.getName(), car.toString(), car.getLocationName(), car.getTrackName(),
647                                Track.getTrackTypeName(trackType), track.getLocation().getName(), track.getName()));
648                    }
649                    return true; // the specified train and another train can
650                                 // carry the car to its destination
651                }
652            }
653        }
654        if (foundRoute) {
655            if (_train != null) {
656                _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{_train.getName()});
657            } else {
658                _status = STATUS_NOT_ABLE;
659            }
660        }
661        return foundRoute;
662    }
663
664    /**
665     * This routine builds a set of tracks that could be used for routing. It
666     * also lists all of the tracks that can't be used.
667     * 
668     * @param car       The car being routed
669     * @param testCar   the test car
670     * @param trackType the type of track used for routing
671     * @return list of usable tracks
672     */
673    private List<Track> getTracks(Car car, Car testCar, String trackType) {
674        List<Track> inTracks = InstanceManager.getDefault(LocationManager.class).getTracksByMoves(trackType);
675        List<Track> tracks = new ArrayList<Track>();
676        for (Track track : inTracks) {
677            if (car.getTrack() == track || car.getFinalDestinationTrack() == track) {
678                continue; // don't use car's current track
679            }
680            // can't use staging if car's load can be modified
681            if (trackType.equals(Track.STAGING) && track.isModifyLoadsEnabled()) {
682                if (_addtoReportVeryDetailed) {
683                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterStagingExcluded",
684                            track.getLocation().getName(), track.getName()));
685                }
686                continue;
687            }
688            String status = track.isRollingStockAccepted(testCar);
689            if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
690                if (_addtoReportVeryDetailed) {
691                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCanNotDeliverCar",
692                            car.toString(), track.getLocation().getName(), track.getName(),
693                            status, track.getTrackTypeName()));
694                }
695                continue;
696            }
697            tracks.add(track);
698        }
699        return tracks;
700    }
701
702    /*
703     * Note that "last" set of location/tracks (_lastLocationTracks) was loaded
704     * by setCarDestinationTwoTrains. The following code builds two additional
705     * sets of location/tracks called "next" (_nextLocationTracks) and "other"
706     * (_otherLocationTracks). "next" is the next set of location/tracks that
707     * the car can reach by a single train. "last" is the last set of
708     * location/tracks that services the cars final destination. And "other" is
709     * the remaining sets of location/tracks that are not "next" or "last". The
710     * code then tries to connect the "next" and "last" location/track sets with
711     * a train that can service the car. If successful, that would be a three
712     * train route for the car. If not successful, the code than tries
713     * combinations of "next", "other" and "last" location/tracks to create a
714     * route for the car.
715     */
716    private boolean setCarDestinationMultipleTrains(Car car, boolean useStaging) {
717        if (useStaging && !Setup.isCarRoutingViaStagingEnabled())
718            return false; // routing via staging is disabled
719
720        if (_addtoReportVeryDetailed) {
721            addLine(_buildReport, SEVEN, BLANK_LINE);
722        }
723        if (_lastLocationTracks.isEmpty()) {
724            if (useStaging) {
725                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCouldNotFindStaging",
726                        car.getFinalDestinationName()));
727            } else {
728                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCouldNotFindLast",
729                        car.getFinalDestinationName()));
730            }
731            return false;
732        }
733
734        Car testCar = clone(car); // reload
735        // build the "next" and "other" location/tracks
736        List<Track> tracks;
737        if (!useStaging) {
738            // start with interchanges
739            tracks = InstanceManager.getDefault(LocationManager.class).getTracksByMoves(Track.INTERCHANGE);
740            loadTracksAndTrains(car, testCar, tracks);
741            // next load yards if enabled
742            if (Setup.isCarRoutingViaYardsEnabled()) {
743                tracks = InstanceManager.getDefault(LocationManager.class).getTracksByMoves(Track.YARD);
744                loadTracksAndTrains(car, testCar, tracks);
745            }
746        } else {
747            // add staging if requested
748            List<Track> stagingTracks =
749                    InstanceManager.getDefault(LocationManager.class).getTracksByMoves(Track.STAGING);
750            tracks = new ArrayList<Track>();
751            for (Track staging : stagingTracks) {
752                if (!staging.isModifyLoadsEnabled()) {
753                    tracks.add(staging);
754                }
755            }
756            loadTracksAndTrains(car, testCar, tracks);
757        }
758
759        if (_nextLocationTracks.isEmpty()) {
760            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCouldNotFindLoc",
761                    car.getLocationName()));
762            return false;
763        }
764
765        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTwoTrainsFailed", car));
766
767        if (_addtoReport) {
768            // tracks that could be the very next destination for the car
769            for (Track t : _nextLocationTracks) {
770                addLine(_buildReport, SEVEN,
771                        Bundle.getMessage("RouterNextTrack", t.getTrackTypeName(), t.getLocation().getName(),
772                                t.getName(), car, car.getLocationName(), car.getTrackName(),
773                                _nextLocationTrains.get(_nextLocationTracks.indexOf(t))));
774            }
775            // tracks that could be the next to last destination for the car
776            for (Track t : _lastLocationTracks) {
777                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterLastTrack",
778                        t.getTrackTypeName(), t.getLocation().getName(), t.getName(), car,
779                        car.getFinalDestinationName(), car.getFinalDestinationTrackName(),
780                        _lastLocationTrains.get(_lastLocationTracks.indexOf(t))));
781            }
782        }
783        if (_addtoReportVeryDetailed) {
784            // tracks that are not the next or the last list
785            for (Track t : _otherLocationTracks) {
786                addLine(_buildReport, SEVEN,
787                        Bundle.getMessage("RouterOtherTrack", t.getTrackTypeName(), t.getLocation().getName(),
788                                t.getName(), car));
789            }
790            addLine(_buildReport, SEVEN, BLANK_LINE);
791        }
792        boolean foundRoute = routeUsing3Trains(car);
793        if (!foundRoute) {
794            log.debug("Using 3 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName());
795            foundRoute = routeUsing4Trains(car);
796        }
797        if (!foundRoute) {
798            log.debug("Using 4 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName());
799            foundRoute = routeUsing5Trains(car);
800        }
801        if (!foundRoute) {
802            log.debug("Using 5 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName());
803            foundRoute = routeUsing6Trains(car);
804        }
805        if (!foundRoute) {
806            log.debug("Using 6 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName());
807            foundRoute = routeUsing7Trains(car);
808        }
809        if (!foundRoute) {
810            addLine(_buildReport, SEVEN,
811                    Bundle.getMessage("RouterNotAbleToRoute", car.toString(), car.getLocationName(),
812                            car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
813        }
814        return foundRoute;
815    }
816
817    private boolean routeUsing3Trains(Car car) {
818        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNTrains", "3", car.getFinalDestinationName(),
819                car.getFinalDestinationTrackName()));
820        Car testCar = clone(car); // reload
821        boolean foundRoute = false;
822        for (Track nlt : _nextLocationTracks) {
823            for (Track llt : _lastLocationTracks) {
824                // does a train service these two locations?
825                Train middleTrain =
826                        getTrainForCar(testCar, nlt, llt, _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)),
827                                _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
828                if (middleTrain != null) {
829                    log.debug("Found 3 train route, setting car destination ({}, {})", nlt.getLocation().getName(),
830                            nlt.getName());
831                    foundRoute = true;
832                    // show the car's route by building an ordered list of
833                    // trains and tracks
834                    List<Train> trains = new ArrayList<>(
835                            Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), middleTrain,
836                                    _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
837                    List<Track> tracks = new ArrayList<>(Arrays.asList(nlt, llt, car.getFinalDestinationTrack()));
838                    showRoute(car, trains, tracks);
839                    if (finshSettingRouteFor(car, nlt)) {
840                        return true; // done 3 train routing
841                    }
842                    break; // there was an issue with the first stop in the
843                           // route
844                }
845            }
846        }
847        return foundRoute;
848    }
849
850    private boolean routeUsing4Trains(Car car) {
851        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNTrains", "4", car.getFinalDestinationName(),
852                car.getFinalDestinationTrackName()));
853        Car testCar = clone(car); // reload
854        boolean foundRoute = false;
855        for (Track nlt : _nextLocationTracks) {
856            otherloop: for (Track mlt : _otherLocationTracks) {
857                Train middleTrain2 = getTrainForCar(testCar, nlt, mlt,
858                        _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null);
859                if (middleTrain2 == null) {
860                    continue;
861                }
862                // build a list of tracks that are reachable from the 1st
863                // interchange
864                if (!_next2ndLocationTracks.contains(mlt)) {
865                    _next2ndLocationTracks.add(mlt);
866                    if (_addtoReport) {
867                        addLine(_buildReport, SEVEN,
868                                Bundle.getMessage("RouterNextHop", mlt.getTrackTypeName(), mlt.getLocation().getName(),
869                                        mlt.getName(), car, nlt.getLocation().getName(), nlt.getName(),
870                                        middleTrain2.getName()));
871                    }
872                }
873                for (Track llt : _lastLocationTracks) {
874                    Train middleTrain3 = getTrainForCar(testCar, mlt, llt, middleTrain2,
875                            _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
876                    if (middleTrain3 == null) {
877                        continue;
878                    }
879                    log.debug("Found 4 train route, setting car destination ({}, {})", nlt.getLocation().getName(),
880                            nlt.getName());
881                    foundRoute = true;
882                    // show the car's route by building an ordered list of
883                    // trains and tracks
884                    List<Train> trains = new ArrayList<>(
885                            Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), middleTrain2,
886                                    middleTrain3, _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
887                    List<Track> tracks = new ArrayList<>(Arrays.asList(nlt, mlt, llt, car.getFinalDestinationTrack()));
888                    showRoute(car, trains, tracks);
889                    if (finshSettingRouteFor(car, nlt)) {
890                        return true; // done 4 train routing
891                    }
892                    break otherloop; // there was an issue with the first
893                                     // stop in the route
894                }
895            }
896        }
897        return foundRoute;
898    }
899
900    private boolean routeUsing5Trains(Car car) {
901        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNTrains", "5", car.getFinalDestinationName(),
902                car.getFinalDestinationTrackName()));
903        Car testCar = clone(car); // reload
904        boolean foundRoute = false;
905        for (Track nlt : _nextLocationTracks) {
906            otherloop: for (Track mlt1 : _next2ndLocationTracks) {
907                Train middleTrain2 = getTrainForCar(testCar, nlt, mlt1,
908                        _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null);
909                if (middleTrain2 == null) {
910                    continue;
911                }
912                for (Track mlt2 : _otherLocationTracks) {
913                    if (_next2ndLocationTracks.contains(mlt2)) {
914                        continue;
915                    }
916                    Train middleTrain3 = getTrainForCar(testCar, mlt1, mlt2, middleTrain2, null);
917                    if (middleTrain3 == null) {
918                        continue;
919                    }
920                    // build a list of tracks that are reachable from the 2nd
921                    // interchange
922                    if (!_next3rdLocationTracks.contains(mlt2)) {
923                        _next3rdLocationTracks.add(mlt2);
924                        if (_addtoReport) {
925                            addLine(_buildReport, SEVEN,
926                                    Bundle.getMessage("RouterNextHop", mlt2.getTrackTypeName(),
927                                            mlt2.getLocation().getName(),
928                                            mlt2.getName(), car, mlt1.getLocation().getName(), mlt1.getName(),
929                                            middleTrain3.getName()));
930                        }
931                    }
932                    for (Track llt : _lastLocationTracks) {
933                        Train middleTrain4 = getTrainForCar(testCar, mlt2, llt, middleTrain3,
934                                _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
935                        if (middleTrain4 == null) {
936                            continue;
937                        }
938                        log.debug("Found 5 train route, setting car destination ({}, {})",
939                                nlt.getLocation().getName(),
940                                nlt.getName());
941                        foundRoute = true;
942                        // show the car's route by building an ordered list
943                        // of trains and tracks
944                        List<Train> trains = new ArrayList<>(Arrays.asList(
945                                _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), middleTrain2, middleTrain3,
946                                middleTrain4, _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
947                        List<Track> tracks =
948                                new ArrayList<>(Arrays.asList(nlt, mlt1, mlt2, llt, car.getFinalDestinationTrack()));
949                        showRoute(car, trains, tracks);
950                        if (finshSettingRouteFor(car, nlt)) {
951                            return true; // done 5 train routing
952                        }
953                        break otherloop; // there was an issue with the
954                                         // first stop in the route
955                    }
956                }
957            }
958        }
959        return foundRoute;
960    }
961
962    private boolean routeUsing6Trains(Car car) {
963        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNTrains", "6", car.getFinalDestinationName(),
964                car.getFinalDestinationTrackName()));
965        Car testCar = clone(car); // reload
966        boolean foundRoute = false;
967        for (Track nlt : _nextLocationTracks) {
968            otherloop: for (Track mlt1 : _next2ndLocationTracks) {
969                Train middleTrain2 = getTrainForCar(testCar, nlt, mlt1,
970                        _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null);
971                if (middleTrain2 == null) {
972                    continue;
973                }
974                for (Track mlt2 : _next3rdLocationTracks) {
975                    Train middleTrain3 = getTrainForCar(testCar, mlt1, mlt2, middleTrain2, null);
976                    if (middleTrain3 == null) {
977                        continue;
978                    }
979                    for (Track mlt3 : _otherLocationTracks) {
980                        if (_next2ndLocationTracks.contains(mlt3) || _next3rdLocationTracks.contains(mlt3)) {
981                            continue;
982                        }
983                        Train middleTrain4 = getTrainForCar(testCar, mlt2, mlt3, middleTrain3, null);
984                        if (middleTrain4 == null) {
985                            continue;
986                        }
987                        if (!_next4thLocationTracks.contains(mlt3)) {
988                            _next4thLocationTracks.add(mlt3);
989                            if (_addtoReport) {
990                                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNextHop", mlt3.getTrackTypeName(),
991                                        mlt3.getLocation().getName(), mlt3.getName(), car, mlt2.getLocation().getName(),
992                                        mlt2.getName(), middleTrain4.getName()));
993                            }
994                        }
995                        for (Track llt : _lastLocationTracks) {
996                            Train middleTrain5 = getTrainForCar(testCar, mlt3, llt, middleTrain4,
997                                    _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
998                            if (middleTrain5 == null) {
999                                continue;
1000                            }
1001                            log.debug("Found 6 train route, setting car destination ({}, {})",
1002                                    nlt.getLocation().getName(), nlt.getName());
1003                            foundRoute = true;
1004                            // show the car's route by building an ordered
1005                            // list of trains and tracks
1006                            List<Train> trains = new ArrayList<>(
1007                                    Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)),
1008                                            middleTrain2, middleTrain3, middleTrain4, middleTrain5,
1009                                            _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
1010                            List<Track> tracks = new ArrayList<>(
1011                                    Arrays.asList(nlt, mlt1, mlt2, mlt3, llt, car.getFinalDestinationTrack()));
1012                            showRoute(car, trains, tracks);
1013                            // only set car's destination if specified train
1014                            // can service car
1015                            if (finshSettingRouteFor(car, nlt)) {
1016                                return true; // done 6 train routing
1017                            }
1018                            break otherloop; // there was an issue with the
1019                                             // first stop in the route
1020                        }
1021                    }
1022                }
1023            }
1024        }
1025        return foundRoute;
1026    }
1027
1028    private boolean routeUsing7Trains(Car car) {
1029        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNTrains", "7", car.getFinalDestinationName(),
1030                car.getFinalDestinationTrackName()));
1031        Car testCar = clone(car); // reload
1032        boolean foundRoute = false;
1033        for (Track nlt : _nextLocationTracks) {
1034            otherloop: for (Track mlt1 : _next2ndLocationTracks) {
1035                Train middleTrain2 = getTrainForCar(testCar, nlt, mlt1,
1036                        _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null);
1037                if (middleTrain2 == null) {
1038                    continue;
1039                }
1040                for (Track mlt2 : _next3rdLocationTracks) {
1041                    Train middleTrain3 = getTrainForCar(testCar, mlt1, mlt2, middleTrain2, null);
1042                    if (middleTrain3 == null) {
1043                        continue;
1044                    }
1045                    for (Track mlt3 : _next4thLocationTracks) {
1046                        Train middleTrain4 = getTrainForCar(testCar, mlt2, mlt3, middleTrain3, null);
1047                        if (middleTrain4 == null) {
1048                            continue;
1049                        }
1050                        for (Track mlt4 : _otherLocationTracks) {
1051                            if (_next2ndLocationTracks.contains(mlt4) ||
1052                                    _next3rdLocationTracks.contains(mlt4) ||
1053                                    _next4thLocationTracks.contains(mlt4)) {
1054                                continue;
1055                            }
1056                            Train middleTrain5 = getTrainForCar(testCar, mlt3, mlt4, middleTrain4, null);
1057                            if (middleTrain5 == null) {
1058                                continue;
1059                            }
1060                            for (Track llt : _lastLocationTracks) {
1061                                Train middleTrain6 = getTrainForCar(testCar, mlt4, llt, middleTrain5,
1062                                        _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
1063                                if (middleTrain6 == null) {
1064                                    continue;
1065                                }
1066                                log.debug("Found 7 train route, setting car destination ({}, {})",
1067                                        nlt.getLocation().getName(), nlt.getName());
1068                                foundRoute = true;
1069                                // show the car's route by building an ordered
1070                                // list of trains and tracks
1071                                List<Train> trains = new ArrayList<>(
1072                                        Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)),
1073                                                middleTrain2, middleTrain3, middleTrain4, middleTrain5, middleTrain6,
1074                                                _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
1075                                List<Track> tracks = new ArrayList<>(Arrays.asList(nlt, mlt1, mlt2, mlt3, mlt4, llt,
1076                                        car.getFinalDestinationTrack()));
1077                                showRoute(car, trains, tracks);
1078                                // only set car's destination if specified train
1079                                // can service car
1080                                if (finshSettingRouteFor(car, nlt)) {
1081                                    return true; // done 7 train routing
1082                                }
1083                                break otherloop; // there was an issue with the
1084                                                 // first stop in the route
1085                            }
1086                        }
1087                    }
1088                }
1089            }
1090        }
1091        return foundRoute;
1092    }
1093
1094    /**
1095     * This method returns a train that is able to move the test car between the
1096     * fromTrack and the toTrack. The default for an interchange track is to not
1097     * allow the same train to spot and pull a car.
1098     * 
1099     * @param testCar   test car
1100     * @param fromTrack departure track
1101     * @param toTrack   arrival track
1102     * @param fromTrain train servicing fromTrack (previous drop to fromTrack)
1103     * @param toTrain   train servicing toTrack (pulls from the toTrack)
1104     * @return null if no train found, else a train able to move test car
1105     *         between fromTrack and toTrack.
1106     */
1107    private Train getTrainForCar(Car testCar, Track fromTrack, Track toTrack, Train fromTrain, Train toTrain) {
1108        testCar.setTrack(fromTrack); // car to this location and track
1109        testCar.setDestinationTrack(toTrack); // car to this destination & track
1110        List<Train> excludeTrains = new ArrayList<>();
1111        if (fromTrack.isInterchange() && fromTrack.getPickupOption().equals(Track.ANY)) {
1112            excludeTrains.add(fromTrain);
1113        }
1114        if (toTrack.isInterchange() && toTrack.getPickupOption().equals(Track.ANY)) {
1115            excludeTrains.add(toTrain);
1116        }
1117        // does a train service these two locations? 
1118        String key = fromTrack.getId() + toTrack.getId();
1119        Train train = _listTrains.get(key);
1120        if (train == null) {
1121            train = tmanager.getTrainForCar(testCar, excludeTrains, null);
1122            if (train != null) {
1123                _listTrains.put(key, train);
1124            } else {
1125                _listTrains.put(key, new Train("null", "null"));
1126            }
1127        } else if (train.getId().equals("null")) {
1128            return null;
1129        }
1130        return train;
1131
1132    }
1133
1134    private void showRoute(Car car, List<Train> trains, List<Track> tracks) {
1135        StringBuffer buf = new StringBuffer(
1136                Bundle.getMessage("RouterRouteForCar", car.toString(), car.getLocationName(), car.getTrackName()));
1137        StringBuffer bufRp = new StringBuffer(
1138                Bundle.getMessage("RouterRoutePath", car.getLocationName(), car.getTrackName()));
1139        for (Track track : tracks) {
1140            if (_addtoReport) {
1141                buf.append(Bundle.getMessage("RouterRouteTrain", trains.get(tracks.indexOf(track)).getName()));
1142            }
1143            bufRp.append(Bundle.getMessage("RouterRoutePathTrain", trains.get(tracks.indexOf(track)).getName()));
1144            if (track != null) {
1145                buf.append(Bundle.getMessage("RouterRouteTrack", track.getLocation().getName(), track.getName()));
1146                bufRp.append(
1147                        Bundle.getMessage("RouterRoutePathTrack", track.getLocation().getName(), track.getName()));
1148            } else {
1149                buf.append(Bundle.getMessage("RouterRouteTrack", car.getFinalDestinationName(),
1150                        car.getFinalDestinationTrackName()));
1151                bufRp.append(Bundle.getMessage("RouterRoutePathTrack", car.getFinalDestinationName(),
1152                        car.getFinalDestinationTrackName()));
1153            }
1154        }
1155        car.setRoutePath(bufRp.toString());
1156        addLine(_buildReport, SEVEN, buf.toString());
1157    }
1158
1159    /**
1160     * @param car   The car to which the destination (track) is going to be
1161     *              applied. Will set car's destination if specified train can
1162     *              service car
1163     * @param track The destination track for car
1164     * @return false if there's an issue with the destination track length or
1165     *         wrong track into staging, otherwise true.
1166     */
1167    private boolean finshSettingRouteFor(Car car, Track track) {
1168        // only set car's destination if specified train can service car
1169        Car ts2 = clone(car);
1170        ts2.setDestinationTrack(track);
1171        String specified = canSpecifiedTrainService(ts2);
1172        if (specified.equals(NO)) {
1173            addLine(_buildReport, SEVEN, Bundle.getMessage("TrainDoesNotServiceCar",
1174                    _train.getName(), car.toString(), track.getLocation().getName(), track.getName()));
1175            _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{_train.getName()});
1176            return false;
1177        } else if (specified.equals(NOT_NOW)) {
1178            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainCanNotDueTo", _train.getName(), car.toString(),
1179                    track.getLocation().getName(), track.getName(), _train.getServiceStatus()));
1180            return false; // the issue is route moves or train length
1181        }
1182        // check to see if track is staging
1183        if (track.isStaging() &&
1184                _train != null &&
1185                _train.getTerminationTrack() != null &&
1186                _train.getTerminationTrack() != track) {
1187            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainIntoStaging",
1188                    _train.getName(), _train.getTerminationTrack().getLocation().getName(),
1189                    _train.getTerminationTrack().getName()));
1190            return false; // wrong track into staging
1191        }
1192        _status = car.setDestination(track.getLocation(), track);
1193        if (!_status.equals(Track.OKAY)) {
1194            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCanNotDeliverCar", car.toString(),
1195                    track.getLocation().getName(), track.getName(), _status, track.getTrackTypeName()));
1196            if (_status.startsWith(Track.LENGTH) && !redirectToAlternate(car, track)) {
1197                return false;
1198            }
1199        }
1200        return true;
1201    }
1202
1203    /**
1204     * Used when the 1st hop interchanges and yards are full. Will attempt to
1205     * use a spur's alternate track when pulling a car from the spur. This will
1206     * create a local move. Code checks to see if local move by the train being
1207     * used is allowed. Will only use the alternate track if all possible 1st
1208     * hop tracks were tested.
1209     * 
1210     * @param car the car being redirected
1211     * @return true if car's destination was set to alternate track
1212     */
1213    private boolean redirectToAlternate(Car car, Track track) {
1214        if (car.getTrack().isSpur() &&
1215                car.getTrack().getAlternateTrack() != null &&
1216                _nextLocationTracks.indexOf(track) == _nextLocationTracks.size() - 1) {
1217            // try redirecting car to the alternate track
1218            Car ts = clone(car);
1219            ts.setDestinationTrack(car.getTrack().getAlternateTrack());
1220            String specified = canSpecifiedTrainService(ts);
1221            if (specified.equals(YES)) {
1222                _status = car.setDestination(car.getTrack().getAlternateTrack().getLocation(),
1223                        car.getTrack().getAlternateTrack());
1224                if (_status.equals(Track.OKAY)) {
1225                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterSendCarToAlternative",
1226                            car.toString(), car.getTrack().getAlternateTrack().getName(),
1227                            car.getTrack().getAlternateTrack().getLocation().getName()));
1228                    return true;
1229                }
1230            }
1231        }
1232        return false;
1233    }
1234
1235    // sets clone car destination to final destination and track
1236    private Car clone(Car car) {
1237        Car clone = car.copy();
1238        // modify clone car length if car is part of kernel
1239        if (car.getKernel() != null) {
1240            clone.setLength(Integer.toString(car.getKernel().getTotalLength() - RollingStock.COUPLERS));
1241        }
1242        clone.setTrack(car.getTrack());
1243        clone.setFinalDestination(car.getFinalDestination());
1244        // don't set the clone's final destination track, that will record the
1245        // car as being inbound
1246        // next two items is where the clone is different
1247        clone.setDestination(car.getFinalDestination());
1248        // note that final destination track can be null
1249        clone.setDestinationTrack(car.getFinalDestinationTrack());
1250        return clone;
1251    }
1252
1253    /*
1254     * Creates two sets of tracks when routing. 1st set (_nextLocationTracks) is
1255     * one hop away from car's current location. 2nd set is all other tracks
1256     * (_otherLocationTracks) that aren't one hop away from car's current
1257     * location or destination. Also creates the list of trains used to service
1258     * _nextLocationTracks.
1259     */
1260    private void loadTracksAndTrains(Car car, Car testCar, List<Track> tracks) {
1261        for (Track track : tracks) {
1262            if (track == car.getTrack()) {
1263                continue; // don't use car's current track
1264            }
1265            // note that last could equal next if this routine was used for two
1266            // train routing
1267            if (_lastLocationTracks.contains(track)) {
1268                continue;
1269            }
1270            String status = track.isRollingStockAccepted(testCar);
1271            if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
1272                continue; // track doesn't accept this car
1273            }
1274            // test to see if there's a train that can deliver the car to this
1275            // destination
1276            testCar.setDestinationTrack(track);
1277            Train train = null;
1278            String specified = canSpecifiedTrainService(testCar);
1279            if (specified.equals(YES) || specified.equals(NOT_NOW)) {
1280                train = _train;
1281            } else {
1282                train = tmanager.getTrainForCar(testCar, null);
1283            }
1284            // Can specified train carry this car out of staging?
1285            if (car.getTrack().isStaging() && !specified.equals(YES)) {
1286                train = null;
1287            }
1288            // is the option carry all cars with a final destination enabled?
1289            if (train != null &&
1290                    _train != null &&
1291                    _train != train &&
1292                    _train.isServiceAllCarsWithFinalDestinationsEnabled() &&
1293                    !specified.equals(YES)) {
1294                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterOptionToCarry", _train.getName(),
1295                        train.getName(), car.toString(), track.getLocation().getName(), track.getName()));
1296                train = null;
1297            }
1298            if (train != null) {
1299                _nextLocationTracks.add(track);
1300                _nextLocationTrains.add(train);
1301            } else {
1302                _otherLocationTracks.add(track);
1303            }
1304        }
1305    }
1306
1307    private static final String NO = "no"; // NOI18N
1308    private static final String YES = "yes"; // NOI18N
1309    private static final String NOT_NOW = "not now"; // NOI18N
1310    private static final String NO_SPECIFIED_TRAIN = "no specified train"; // NOI18N
1311
1312    private String canSpecifiedTrainService(Car car) {
1313        if (_train == null) {
1314            return NO_SPECIFIED_TRAIN;
1315        }
1316        if (_train.isServiceable(car)) {
1317            return YES;
1318        } // is the reason this train can't service route moves or train length?
1319        else if (!_train.getServiceStatus().equals(Train.NONE)) {
1320            return NOT_NOW; // the issue is route moves or train length
1321        }
1322        return NO;
1323    }
1324
1325    private final static Logger log = LoggerFactory.getLogger(Router.class);
1326
1327}