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