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