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