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