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 = getTracks(car, testCar, trackType);
496        for (Track track : tracks) {
497            if (_addtoReportVeryDetailed) {
498                addLine(_buildReport, SEVEN, BLANK_LINE);
499                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterFoundTrack",
500                        Track.getTrackTypeName(trackType), track.getLocation().getName(),
501                        track.getName(), car.toString()));
502            }
503            // test to see if there's a train that can deliver the car to its
504            // final location
505            testCar.setTrack(track);
506            testCar.setDestination(car.getFinalDestination());
507            // note that destination track can be null
508            testCar.setDestinationTrack(car.getFinalDestinationTrack());
509            Train secondTrain = tmanager.getTrainForCar(testCar, _buildReport);
510            if (secondTrain == null) {
511                // maybe the train being built can service the car?
512                String specified = canSpecifiedTrainService(testCar);
513                if (specified.equals(NOT_NOW)) {
514                    secondTrain = _train;
515                } else {
516                    if (_addtoReportVeryDetailed) {
517                        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNotFindTrain",
518                                Track.getTrackTypeName(trackType), track.getLocation().getName(),
519                                track.getName(), testCar.getDestinationName(),
520                                testCar.getDestinationTrackName()));
521                    }
522                    continue;
523                }
524            }
525            if (_addtoReportVeryDetailed) {
526                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainCanTransport",
527                        secondTrain.getName(), car.toString(), testCar.getTrack().getTrackTypeName(),
528                        testCar.getLocationName(), testCar.getTrackName(), testCar.getDestinationName(),
529                        testCar.getDestinationTrackName()));
530            }
531            // Save the "last" tracks for later use if needed
532            _lastLocationTracks.add(track);
533            _lastLocationTrains.add(secondTrain);
534            // now try to forward car to this track
535            testCar.setTrack(car.getTrack()); // restore car origin
536            testCar.setDestination(track.getLocation());
537            testCar.setDestinationTrack(track);
538            // determine if car can be transported from current location to this
539            // interchange, yard, or staging track
540            // Now find a train that will transport the car to this track
541            Train firstTrain = null;
542            String specified = canSpecifiedTrainService(testCar);
543            if (specified.equals(YES)) {
544                firstTrain = _train;
545            } else if (specified.equals(NOT_NOW)) {
546                // found a two train route for this car, show the car's route
547                List<Train> trains = new ArrayList<>(Arrays.asList(_train, secondTrain));
548                tracks = new ArrayList<>(Arrays.asList(track, car.getFinalDestinationTrack()));
549                showRoute(car, trains, tracks);
550
551                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainCanNotDueTo",
552                        _train.getName(), car.toString(), track.getLocation().getName(), track.getName(),
553                        _train.getServiceStatus()));
554                foundRoute = true; // issue is route moves or train length
555            } else {
556                firstTrain = tmanager.getTrainForCar(testCar, _buildReport);
557            }
558            // check to see if a train or trains with the same route is delivering and pulling the car to an interchange track
559            if (firstTrain != null &&
560                    firstTrain.getRoute() == secondTrain.getRoute() &&
561                    track.isInterchange() &&
562                    track.getPickupOption().equals(Track.ANY)) {
563                if (_addtoReportVeryDetailed) {
564                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterSameInterchange", firstTrain.getName(),
565                            track.getLocation().getName(), track.getName()));
566                }
567                List<Train> excludeTrains = new ArrayList<>();
568                excludeTrains.add(firstTrain);
569                firstTrain = tmanager.getTrainForCar(testCar, excludeTrains, _buildReport);
570            }
571            if (firstTrain == null && _addtoReportVeryDetailed) {
572                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNotFindTrain",
573                        testCar.getTrack().getTrackTypeName(), testCar.getTrack().getLocation().getName(),
574                        testCar.getTrack().getName(),
575                        testCar.getDestinationName(), testCar.getDestinationTrackName()));
576            }
577            // Can the specified train carry this car out of staging?
578            if (_train != null && car.getTrack().isStaging() && !specified.equals(YES)) {
579                if (_addtoReport) {
580                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainCanNot",
581                            _train.getName(), car.toString(), car.getLocationName(),
582                            car.getTrackName(), track.getLocation().getName(), track.getName()));
583                }
584                continue; // can't use this train
585            }
586            // Is the option for the specified train carry this car?
587            if (firstTrain != null &&
588                    _train != null &&
589                    _train.isServiceAllCarsWithFinalDestinationsEnabled() &&
590                    !specified.equals(YES)) {
591                if (_addtoReport) {
592                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterOptionToCarry",
593                            _train.getName(), firstTrain.getName(), car.toString(),
594                            track.getLocation().getName(), track.getName()));
595                }
596                continue; // can't use this train
597            }
598            if (firstTrain != null) {
599                foundRoute = true; // found a route
600                if (_addtoReportVeryDetailed) {
601                    addLine(_buildReport, SEVEN,
602                            Bundle.getMessage("RouterTrainCanTransport", firstTrain.getName(), car.toString(),
603                                    testCar.getTrack().getTrackTypeName(),
604                                    testCar.getLocationName(), testCar.getTrackName(), testCar.getDestinationName(),
605                                    testCar.getDestinationTrackName()));
606                }
607                // found a two train route for this car, show the car's route
608                List<Train> trains = new ArrayList<>(Arrays.asList(firstTrain, secondTrain));
609                tracks = new ArrayList<>(Arrays.asList(track, car.getFinalDestinationTrack()));
610                showRoute(car, trains, tracks);
611
612                _status = car.checkDestination(track.getLocation(), track);
613                if (_status.startsWith(Track.LENGTH)) {
614                    // if the issue is length at the interim track, add message
615                    // to build report
616                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCanNotDeliverCar",
617                            car.toString(), track.getLocation().getName(), track.getName(),
618                            _status, track.getTrackTypeName()));
619                    continue;
620                }
621                if (_status.equals(Track.OKAY)) {
622                    // only set car's destination if specified train can service
623                    // car
624                    if (_train != null && _train != firstTrain) {
625                        addLine(_buildReport, SEVEN, Bundle.getMessage("TrainDoesNotServiceCar",
626                                _train.getName(), car.toString(), testCar.getDestinationName(),
627                                testCar.getDestinationTrackName()));
628                        _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{firstTrain.getName()});
629                        continue;// found a route but it doesn't start with the
630                                 // specified train
631                    }
632                    // is this the staging track assigned to the specified
633                    // train?
634                    if (track.isStaging() &&
635                            firstTrain.getTerminationTrack() != null &&
636                            firstTrain.getTerminationTrack() != track) {
637                        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainIntoStaging", firstTrain.getName(),
638                                firstTrain.getTerminationTrack().getLocation().getName(),
639                                firstTrain.getTerminationTrack().getName()));
640                        continue;
641                    }
642                    _status = car.setDestination(track.getLocation(), track);
643                    if (_addtoReport) {
644                        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainCanService",
645                                firstTrain.getName(), car.toString(), car.getLocationName(), car.getTrackName(),
646                                Track.getTrackTypeName(trackType), track.getLocation().getName(), track.getName()));
647                    }
648                    return true; // the specified train and another train can
649                                 // carry the car to its destination
650                }
651            }
652        }
653        if (foundRoute) {
654            if (_train != null) {
655                _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{_train.getName()});
656            } else {
657                _status = STATUS_NOT_ABLE;
658            }
659        }
660        return foundRoute;
661    }
662
663    /**
664     * This routine builds a set of tracks that could be used for routing. It
665     * also lists all of the tracks that can't be used.
666     * 
667     * @param car       The car being routed
668     * @param testCar   the test car
669     * @param trackType the type of track used for routing
670     * @return list of usable tracks
671     */
672    private List<Track> getTracks(Car car, Car testCar, String trackType) {
673        List<Track> inTracks = InstanceManager.getDefault(LocationManager.class).getTracksByMoves(trackType);
674        List<Track> tracks = new ArrayList<Track>();
675        for (Track track : inTracks) {
676            if (car.getTrack() == track || car.getFinalDestinationTrack() == track) {
677                continue; // don't use car's current track
678            }
679            // can't use staging if car's load can be modified
680            if (trackType.equals(Track.STAGING) && track.isModifyLoadsEnabled()) {
681                if (_addtoReportVeryDetailed) {
682                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterStagingExcluded",
683                            track.getLocation().getName(), track.getName()));
684                }
685                continue;
686            }
687            String status = track.isRollingStockAccepted(testCar);
688            if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
689                if (_addtoReportVeryDetailed) {
690                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCanNotDeliverCar",
691                            car.toString(), track.getLocation().getName(), track.getName(),
692                            status, track.getTrackTypeName()));
693                }
694                continue;
695            }
696            tracks.add(track);
697        }
698        return tracks;
699    }
700
701    /*
702     * Note that "last" set of location/tracks (_lastLocationTracks) was loaded
703     * by setCarDestinationTwoTrains. The following code builds two additional
704     * sets of location/tracks called "next" (_nextLocationTracks) and "other"
705     * (_otherLocationTracks). "next" is the next set of location/tracks that
706     * the car can reach by a single train. "last" is the last set of
707     * location/tracks that services the cars final destination. And "other" is
708     * the remaining sets of location/tracks that are not "next" or "last". The
709     * code then tries to connect the "next" and "last" location/track sets with
710     * a train that can service the car. If successful, that would be a three
711     * train route for the car. If not successful, the code than tries
712     * combinations of "next", "other" and "last" location/tracks to create a
713     * route for the car.
714     */
715    private boolean setCarDestinationMultipleTrains(Car car, boolean useStaging) {
716        if (useStaging && !Setup.isCarRoutingViaStagingEnabled())
717            return false; // routing via staging is disabled
718
719        if (_addtoReportVeryDetailed) {
720            addLine(_buildReport, SEVEN, BLANK_LINE);
721        }
722        if (_lastLocationTracks.isEmpty()) {
723            if (useStaging) {
724                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCouldNotFindStaging",
725                        car.getFinalDestinationName()));
726            } else {
727                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCouldNotFindLast",
728                        car.getFinalDestinationName()));
729            }
730            return false;
731        }
732
733        Car testCar = clone(car); // reload
734        // build the "next" and "other" location/tracks
735        List<Track> tracks;
736        if (!useStaging) {
737            // start with interchanges
738            tracks = InstanceManager.getDefault(LocationManager.class).getTracksByMoves(Track.INTERCHANGE);
739            loadTracksAndTrains(car, testCar, tracks);
740            // next load yards if enabled
741            if (Setup.isCarRoutingViaYardsEnabled()) {
742                tracks = InstanceManager.getDefault(LocationManager.class).getTracksByMoves(Track.YARD);
743                loadTracksAndTrains(car, testCar, tracks);
744            }
745        } else {
746            // add staging if requested
747            List<Track> stagingTracks =
748                    InstanceManager.getDefault(LocationManager.class).getTracksByMoves(Track.STAGING);
749            tracks = new ArrayList<Track>();
750            for (Track staging : stagingTracks) {
751                if (!staging.isModifyLoadsEnabled()) {
752                    tracks.add(staging);
753                }
754            }
755            loadTracksAndTrains(car, testCar, tracks);
756        }
757
758        if (_nextLocationTracks.isEmpty()) {
759            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCouldNotFindLoc",
760                    car.getLocationName()));
761            return false;
762        }
763
764        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTwoTrainsFailed", car));
765
766        if (_addtoReport) {
767            // tracks that could be the very next destination for the car
768            for (Track t : _nextLocationTracks) {
769                addLine(_buildReport, SEVEN,
770                        Bundle.getMessage("RouterNextTrack", t.getTrackTypeName(), t.getLocation().getName(),
771                                t.getName(), car, car.getLocationName(), car.getTrackName(),
772                                _nextLocationTrains.get(_nextLocationTracks.indexOf(t))));
773            }
774            // tracks that could be the next to last destination for the car
775            for (Track t : _lastLocationTracks) {
776                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterLastTrack",
777                        t.getTrackTypeName(), t.getLocation().getName(), t.getName(), car,
778                        car.getFinalDestinationName(), car.getFinalDestinationTrackName(),
779                        _lastLocationTrains.get(_lastLocationTracks.indexOf(t))));
780            }
781        }
782        if (_addtoReportVeryDetailed) {
783            // tracks that are not the next or the last list
784            for (Track t : _otherLocationTracks) {
785                addLine(_buildReport, SEVEN,
786                        Bundle.getMessage("RouterOtherTrack", t.getTrackTypeName(), t.getLocation().getName(),
787                                t.getName(), car));
788            }
789            addLine(_buildReport, SEVEN, BLANK_LINE);
790        }
791        boolean foundRoute = routeUsing3Trains(car);
792        if (!foundRoute) {
793            log.debug("Using 3 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName());
794            foundRoute = routeUsing4Trains(car);
795        }
796        if (!foundRoute) {
797            log.debug("Using 4 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName());
798            foundRoute = routeUsing5Trains(car);
799        }
800        if (!foundRoute) {
801            log.debug("Using 5 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName());
802            foundRoute = routeUsing6Trains(car);
803        }
804        if (!foundRoute) {
805            log.debug("Using 6 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName());
806            foundRoute = routeUsing7Trains(car);
807        }
808        if (!foundRoute) {
809            addLine(_buildReport, SEVEN,
810                    Bundle.getMessage("RouterNotAbleToRoute", car.toString(), car.getLocationName(),
811                            car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
812        }
813        return foundRoute;
814    }
815
816    private boolean routeUsing3Trains(Car car) {
817        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNTrains", "3", car.getFinalDestinationName(),
818                car.getFinalDestinationTrackName()));
819        Car testCar = clone(car); // reload
820        boolean foundRoute = false;
821        for (Track nlt : _nextLocationTracks) {
822            for (Track llt : _lastLocationTracks) {
823                // does a train service these two locations?
824                Train middleTrain =
825                        getTrainForCar(testCar, nlt, llt, _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)),
826                                _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
827                if (middleTrain != null) {
828                    log.debug("Found 3 train route, setting car destination ({}, {})", nlt.getLocation().getName(),
829                            nlt.getName());
830                    foundRoute = true;
831                    // show the car's route by building an ordered list of
832                    // trains and tracks
833                    List<Train> trains = new ArrayList<>(
834                            Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), middleTrain,
835                                    _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
836                    List<Track> tracks = new ArrayList<>(Arrays.asList(nlt, llt, car.getFinalDestinationTrack()));
837                    showRoute(car, trains, tracks);
838                    if (finshSettingRouteFor(car, nlt)) {
839                        return true; // done 3 train routing
840                    }
841                    break; // there was an issue with the first stop in the
842                           // route
843                }
844            }
845        }
846        return foundRoute;
847    }
848
849    private boolean routeUsing4Trains(Car car) {
850        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNTrains", "4", car.getFinalDestinationName(),
851                car.getFinalDestinationTrackName()));
852        Car testCar = clone(car); // reload
853        boolean foundRoute = false;
854        for (Track nlt : _nextLocationTracks) {
855            otherloop: for (Track mlt : _otherLocationTracks) {
856                Train middleTrain2 = getTrainForCar(testCar, nlt, mlt,
857                        _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null);
858                if (middleTrain2 == null) {
859                    continue;
860                }
861                // build a list of tracks that are reachable from the 1st
862                // interchange
863                if (!_next2ndLocationTracks.contains(mlt)) {
864                    _next2ndLocationTracks.add(mlt);
865                    if (_addtoReport) {
866                        addLine(_buildReport, SEVEN,
867                                Bundle.getMessage("RouterNextHop", mlt.getTrackTypeName(), mlt.getLocation().getName(),
868                                        mlt.getName(), car, nlt.getLocation().getName(), nlt.getName(),
869                                        middleTrain2.getName()));
870                    }
871                }
872                for (Track llt : _lastLocationTracks) {
873                    Train middleTrain3 = getTrainForCar(testCar, mlt, llt, middleTrain2,
874                            _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
875                    if (middleTrain3 == null) {
876                        continue;
877                    }
878                    log.debug("Found 4 train route, setting car destination ({}, {})", nlt.getLocation().getName(),
879                            nlt.getName());
880                    foundRoute = true;
881                    // show the car's route by building an ordered list of
882                    // trains and tracks
883                    List<Train> trains = new ArrayList<>(
884                            Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), middleTrain2,
885                                    middleTrain3, _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
886                    List<Track> tracks = new ArrayList<>(Arrays.asList(nlt, mlt, llt, car.getFinalDestinationTrack()));
887                    showRoute(car, trains, tracks);
888                    if (finshSettingRouteFor(car, nlt)) {
889                        return true; // done 4 train routing
890                    }
891                    break otherloop; // there was an issue with the first
892                                     // stop in the route
893                }
894            }
895        }
896        return foundRoute;
897    }
898
899    private boolean routeUsing5Trains(Car car) {
900        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNTrains", "5", car.getFinalDestinationName(),
901                car.getFinalDestinationTrackName()));
902        Car testCar = clone(car); // reload
903        boolean foundRoute = false;
904        for (Track nlt : _nextLocationTracks) {
905            otherloop: for (Track mlt1 : _next2ndLocationTracks) {
906                Train middleTrain2 = getTrainForCar(testCar, nlt, mlt1,
907                        _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null);
908                if (middleTrain2 == null) {
909                    continue;
910                }
911                for (Track mlt2 : _otherLocationTracks) {
912                    if (_next2ndLocationTracks.contains(mlt2)) {
913                        continue;
914                    }
915                    Train middleTrain3 = getTrainForCar(testCar, mlt1, mlt2, middleTrain2, null);
916                    if (middleTrain3 == null) {
917                        continue;
918                    }
919                    // build a list of tracks that are reachable from the 2nd
920                    // interchange
921                    if (!_next3rdLocationTracks.contains(mlt2)) {
922                        _next3rdLocationTracks.add(mlt2);
923                        if (_addtoReport) {
924                            addLine(_buildReport, SEVEN,
925                                    Bundle.getMessage("RouterNextHop", mlt2.getTrackTypeName(),
926                                            mlt2.getLocation().getName(),
927                                            mlt2.getName(), car, mlt1.getLocation().getName(), mlt1.getName(),
928                                            middleTrain3.getName()));
929                        }
930                    }
931                    for (Track llt : _lastLocationTracks) {
932                        Train middleTrain4 = getTrainForCar(testCar, mlt2, llt, middleTrain3,
933                                _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
934                        if (middleTrain4 == null) {
935                            continue;
936                        }
937                        log.debug("Found 5 train route, setting car destination ({}, {})",
938                                nlt.getLocation().getName(),
939                                nlt.getName());
940                        foundRoute = true;
941                        // show the car's route by building an ordered list
942                        // of trains and tracks
943                        List<Train> trains = new ArrayList<>(Arrays.asList(
944                                _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), middleTrain2, middleTrain3,
945                                middleTrain4, _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
946                        List<Track> tracks =
947                                new ArrayList<>(Arrays.asList(nlt, mlt1, mlt2, llt, car.getFinalDestinationTrack()));
948                        showRoute(car, trains, tracks);
949                        if (finshSettingRouteFor(car, nlt)) {
950                            return true; // done 5 train routing
951                        }
952                        break otherloop; // there was an issue with the
953                                         // first stop in the route
954                    }
955                }
956            }
957        }
958        return foundRoute;
959    }
960
961    private boolean routeUsing6Trains(Car car) {
962        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNTrains", "6", car.getFinalDestinationName(),
963                car.getFinalDestinationTrackName()));
964        Car testCar = clone(car); // reload
965        boolean foundRoute = false;
966        for (Track nlt : _nextLocationTracks) {
967            otherloop: for (Track mlt1 : _next2ndLocationTracks) {
968                Train middleTrain2 = getTrainForCar(testCar, nlt, mlt1,
969                        _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null);
970                if (middleTrain2 == null) {
971                    continue;
972                }
973                for (Track mlt2 : _next3rdLocationTracks) {
974                    Train middleTrain3 = getTrainForCar(testCar, mlt1, mlt2, middleTrain2, null);
975                    if (middleTrain3 == null) {
976                        continue;
977                    }
978                    for (Track mlt3 : _otherLocationTracks) {
979                        if (_next2ndLocationTracks.contains(mlt3) || _next3rdLocationTracks.contains(mlt3)) {
980                            continue;
981                        }
982                        Train middleTrain4 = getTrainForCar(testCar, mlt2, mlt3, middleTrain3, null);
983                        if (middleTrain4 == null) {
984                            continue;
985                        }
986                        if (!_next4thLocationTracks.contains(mlt3)) {
987                            _next4thLocationTracks.add(mlt3);
988                            if (_addtoReport) {
989                                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNextHop", mlt3.getTrackTypeName(),
990                                        mlt3.getLocation().getName(), mlt3.getName(), car, mlt2.getLocation().getName(),
991                                        mlt2.getName(), middleTrain4.getName()));
992                            }
993                        }
994                        for (Track llt : _lastLocationTracks) {
995                            Train middleTrain5 = getTrainForCar(testCar, mlt3, llt, middleTrain4,
996                                    _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
997                            if (middleTrain5 == null) {
998                                continue;
999                            }
1000                            log.debug("Found 6 train route, setting car destination ({}, {})",
1001                                    nlt.getLocation().getName(), nlt.getName());
1002                            foundRoute = true;
1003                            // show the car's route by building an ordered
1004                            // list of trains and tracks
1005                            List<Train> trains = new ArrayList<>(
1006                                    Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)),
1007                                            middleTrain2, middleTrain3, middleTrain4, middleTrain5,
1008                                            _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
1009                            List<Track> tracks = new ArrayList<>(
1010                                    Arrays.asList(nlt, mlt1, mlt2, mlt3, llt, car.getFinalDestinationTrack()));
1011                            showRoute(car, trains, tracks);
1012                            // only set car's destination if specified train
1013                            // can service car
1014                            if (finshSettingRouteFor(car, nlt)) {
1015                                return true; // done 6 train routing
1016                            }
1017                            break otherloop; // there was an issue with the
1018                                             // first stop in the route
1019                        }
1020                    }
1021                }
1022            }
1023        }
1024        return foundRoute;
1025    }
1026
1027    private boolean routeUsing7Trains(Car car) {
1028        addLine(_buildReport, SEVEN, Bundle.getMessage("RouterNTrains", "7", car.getFinalDestinationName(),
1029                car.getFinalDestinationTrackName()));
1030        Car testCar = clone(car); // reload
1031        boolean foundRoute = false;
1032        for (Track nlt : _nextLocationTracks) {
1033            otherloop: for (Track mlt1 : _next2ndLocationTracks) {
1034                Train middleTrain2 = getTrainForCar(testCar, nlt, mlt1,
1035                        _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null);
1036                if (middleTrain2 == null) {
1037                    continue;
1038                }
1039                for (Track mlt2 : _next3rdLocationTracks) {
1040                    Train middleTrain3 = getTrainForCar(testCar, mlt1, mlt2, middleTrain2, null);
1041                    if (middleTrain3 == null) {
1042                        continue;
1043                    }
1044                    for (Track mlt3 : _next4thLocationTracks) {
1045                        Train middleTrain4 = getTrainForCar(testCar, mlt2, mlt3, middleTrain3, null);
1046                        if (middleTrain4 == null) {
1047                            continue;
1048                        }
1049                        for (Track mlt4 : _otherLocationTracks) {
1050                            if (_next2ndLocationTracks.contains(mlt4) ||
1051                                    _next3rdLocationTracks.contains(mlt4) ||
1052                                    _next4thLocationTracks.contains(mlt4)) {
1053                                continue;
1054                            }
1055                            Train middleTrain5 = getTrainForCar(testCar, mlt3, mlt4, middleTrain4, null);
1056                            if (middleTrain5 == null) {
1057                                continue;
1058                            }
1059                            for (Track llt : _lastLocationTracks) {
1060                                Train middleTrain6 = getTrainForCar(testCar, mlt4, llt, middleTrain5,
1061                                        _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
1062                                if (middleTrain6 == null) {
1063                                    continue;
1064                                }
1065                                log.debug("Found 7 train route, setting car destination ({}, {})",
1066                                        nlt.getLocation().getName(), nlt.getName());
1067                                foundRoute = true;
1068                                // show the car's route by building an ordered
1069                                // list of trains and tracks
1070                                List<Train> trains = new ArrayList<>(
1071                                        Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)),
1072                                                middleTrain2, middleTrain3, middleTrain4, middleTrain5, middleTrain6,
1073                                                _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
1074                                List<Track> tracks = new ArrayList<>(Arrays.asList(nlt, mlt1, mlt2, mlt3, mlt4, llt,
1075                                        car.getFinalDestinationTrack()));
1076                                showRoute(car, trains, tracks);
1077                                // only set car's destination if specified train
1078                                // can service car
1079                                if (finshSettingRouteFor(car, nlt)) {
1080                                    return true; // done 7 train routing
1081                                }
1082                                break otherloop; // there was an issue with the
1083                                                 // first stop in the route
1084                            }
1085                        }
1086                    }
1087                }
1088            }
1089        }
1090        return foundRoute;
1091    }
1092
1093    /**
1094     * This method returns a train that is able to move the test car between the
1095     * fromTrack and the toTrack. The default for an interchange track is to not
1096     * allow the same train to spot and pull a car.
1097     * 
1098     * @param testCar   test car
1099     * @param fromTrack departure track
1100     * @param toTrack   arrival track
1101     * @param fromTrain train servicing fromTrack (previous drop to fromTrack)
1102     * @param toTrain   train servicing toTrack (pulls from the toTrack)
1103     * @return null if no train found, else a train able to move test car
1104     *         between fromTrack and toTrack.
1105     */
1106    private Train getTrainForCar(Car testCar, Track fromTrack, Track toTrack, Train fromTrain, Train toTrain) {
1107        testCar.setTrack(fromTrack); // car to this location and track
1108        testCar.setDestinationTrack(toTrack); // car to this destination & track
1109        List<Train> excludeTrains = new ArrayList<>();
1110        if (fromTrack.isInterchange() && fromTrack.getPickupOption().equals(Track.ANY)) {
1111            excludeTrains.add(fromTrain);
1112        }
1113        if (toTrack.isInterchange() && toTrack.getPickupOption().equals(Track.ANY)) {
1114            excludeTrains.add(toTrain);
1115        }
1116        // does a train service these two locations? 
1117        String key = fromTrack.getId() + toTrack.getId();
1118        Train train = _listTrains.get(key);
1119        if (train == null) {
1120            train = tmanager.getTrainForCar(testCar, excludeTrains, null);
1121            if (train != null) {
1122                _listTrains.put(key, train);
1123            } else {
1124                _listTrains.put(key, new Train("null", "null"));
1125            }
1126        } else if (train.getId().equals("null")) {
1127            return null;
1128        }
1129        return train;
1130
1131    }
1132
1133    private void showRoute(Car car, List<Train> trains, List<Track> tracks) {
1134        StringBuffer buf = new StringBuffer(
1135                Bundle.getMessage("RouterRouteForCar", car.toString(), car.getLocationName(), car.getTrackName()));
1136        StringBuffer bufRp = new StringBuffer(
1137                Bundle.getMessage("RouterRoutePath", car.getLocationName(), car.getTrackName()));
1138        for (Track track : tracks) {
1139            if (_addtoReport) {
1140                buf.append(Bundle.getMessage("RouterRouteTrain", trains.get(tracks.indexOf(track)).getName()));
1141            }
1142            bufRp.append(Bundle.getMessage("RouterRoutePathTrain", trains.get(tracks.indexOf(track)).getName()));
1143            if (track != null) {
1144                buf.append(Bundle.getMessage("RouterRouteTrack", track.getLocation().getName(), track.getName()));
1145                bufRp.append(
1146                        Bundle.getMessage("RouterRoutePathTrack", track.getLocation().getName(), track.getName()));
1147            } else {
1148                buf.append(Bundle.getMessage("RouterRouteTrack", car.getFinalDestinationName(),
1149                        car.getFinalDestinationTrackName()));
1150                bufRp.append(Bundle.getMessage("RouterRoutePathTrack", car.getFinalDestinationName(),
1151                        car.getFinalDestinationTrackName()));
1152            }
1153        }
1154        car.setRoutePath(bufRp.toString());
1155        addLine(_buildReport, SEVEN, buf.toString());
1156    }
1157
1158    /**
1159     * @param car   The car to which the destination (track) is going to be
1160     *              applied. Will set car's destination if specified train can
1161     *              service car
1162     * @param track The destination track for car
1163     * @return false if there's an issue with the destination track length or
1164     *         wrong track into staging, otherwise true.
1165     */
1166    private boolean finshSettingRouteFor(Car car, Track track) {
1167        // only set car's destination if specified train can service car
1168        Car ts2 = clone(car);
1169        ts2.setDestinationTrack(track);
1170        String specified = canSpecifiedTrainService(ts2);
1171        if (specified.equals(NO)) {
1172            addLine(_buildReport, SEVEN, Bundle.getMessage("TrainDoesNotServiceCar",
1173                    _train.getName(), car.toString(), track.getLocation().getName(), track.getName()));
1174            _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{_train.getName()});
1175            return false;
1176        } else if (specified.equals(NOT_NOW)) {
1177            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainCanNotDueTo", _train.getName(), car.toString(),
1178                    track.getLocation().getName(), track.getName(), _train.getServiceStatus()));
1179            return false; // the issue is route moves or train length
1180        }
1181        // check to see if track is staging
1182        if (track.isStaging() &&
1183                _train != null &&
1184                _train.getTerminationTrack() != null &&
1185                _train.getTerminationTrack() != track) {
1186            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterTrainIntoStaging",
1187                    _train.getName(), _train.getTerminationTrack().getLocation().getName(),
1188                    _train.getTerminationTrack().getName()));
1189            return false; // wrong track into staging
1190        }
1191        _status = car.setDestination(track.getLocation(), track);
1192        if (!_status.equals(Track.OKAY)) {
1193            addLine(_buildReport, SEVEN, Bundle.getMessage("RouterCanNotDeliverCar", car.toString(),
1194                    track.getLocation().getName(), track.getName(), _status, track.getTrackTypeName()));
1195            if (_status.startsWith(Track.LENGTH) && !redirectToAlternate(car, track)) {
1196                return false;
1197            }
1198        }
1199        return true;
1200    }
1201
1202    /**
1203     * Used when the 1st hop interchanges and yards are full. Will attempt to
1204     * use a spur's alternate track when pulling a car from the spur. This will
1205     * create a local move. Code checks to see if local move by the train being
1206     * used is allowed. Will only use the alternate track if all possible 1st
1207     * hop tracks were tested.
1208     * 
1209     * @param car the car being redirected
1210     * @return true if car's destination was set to alternate track
1211     */
1212    private boolean redirectToAlternate(Car car, Track track) {
1213        if (car.getTrack().isSpur() &&
1214                car.getTrack().getAlternateTrack() != null &&
1215                _nextLocationTracks.indexOf(track) == _nextLocationTracks.size() - 1) {
1216            // try redirecting car to the alternate track
1217            Car ts = clone(car);
1218            ts.setDestinationTrack(car.getTrack().getAlternateTrack());
1219            String specified = canSpecifiedTrainService(ts);
1220            if (specified.equals(YES)) {
1221                _status = car.setDestination(car.getTrack().getAlternateTrack().getLocation(),
1222                        car.getTrack().getAlternateTrack());
1223                if (_status.equals(Track.OKAY)) {
1224                    addLine(_buildReport, SEVEN, Bundle.getMessage("RouterSendCarToAlternative",
1225                            car.toString(), car.getTrack().getAlternateTrack().getName(),
1226                            car.getTrack().getAlternateTrack().getLocation().getName()));
1227                    return true;
1228                }
1229            }
1230        }
1231        return false;
1232    }
1233
1234    // sets clone car destination to final destination and track
1235    private Car clone(Car car) {
1236        Car clone = car.copy();
1237        // modify clone car length if car is part of kernel
1238        if (car.getKernel() != null) {
1239            clone.setLength(Integer.toString(car.getKernel().getTotalLength() - RollingStock.COUPLERS));
1240        }
1241        clone.setTrack(car.getTrack());
1242        clone.setFinalDestination(car.getFinalDestination());
1243        // don't set the clone's final destination track, that will record the
1244        // car as being inbound
1245        // next two items is where the clone is different
1246        clone.setDestination(car.getFinalDestination());
1247        // note that final destination track can be null
1248        clone.setDestinationTrack(car.getFinalDestinationTrack());
1249        return clone;
1250    }
1251
1252    /*
1253     * Creates two sets of tracks when routing. 1st set (_nextLocationTracks) is
1254     * one hop away from car's current location. 2nd set is all other tracks
1255     * (_otherLocationTracks) that aren't one hop away from car's current
1256     * location or destination. Also creates the list of trains used to service
1257     * _nextLocationTracks.
1258     */
1259    private void loadTracksAndTrains(Car car, Car testCar, List<Track> tracks) {
1260        for (Track track : tracks) {
1261            if (track == car.getTrack()) {
1262                continue; // don't use car's current track
1263            }
1264            // note that last could equal next if this routine was used for two
1265            // train routing
1266            if (_lastLocationTracks.contains(track)) {
1267                continue;
1268            }
1269            String status = track.isRollingStockAccepted(testCar);
1270            if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
1271                continue; // track doesn't accept this car
1272            }
1273            // test to see if there's a train that can deliver the car to this
1274            // destination
1275            testCar.setDestinationTrack(track);
1276            Train train = null;
1277            String specified = canSpecifiedTrainService(testCar);
1278            if (specified.equals(YES) || specified.equals(NOT_NOW)) {
1279                train = _train;
1280            } else {
1281                train = tmanager.getTrainForCar(testCar, null);
1282            }
1283            // Can specified train carry this car out of staging?
1284            if (car.getTrack().isStaging() && !specified.equals(YES)) {
1285                train = null;
1286            }
1287            // is the option carry all cars with a final destination enabled?
1288            if (train != null &&
1289                    _train != null &&
1290                    _train != train &&
1291                    _train.isServiceAllCarsWithFinalDestinationsEnabled() &&
1292                    !specified.equals(YES)) {
1293                addLine(_buildReport, SEVEN, Bundle.getMessage("RouterOptionToCarry", _train.getName(),
1294                        train.getName(), car.toString(), track.getLocation().getName(), track.getName()));
1295                train = null;
1296            }
1297            if (train != null) {
1298                _nextLocationTracks.add(track);
1299                _nextLocationTrains.add(train);
1300            } else {
1301                _otherLocationTracks.add(track);
1302            }
1303        }
1304    }
1305
1306    private static final String NO = "no"; // NOI18N
1307    private static final String YES = "yes"; // NOI18N
1308    private static final String NOT_NOW = "not now"; // NOI18N
1309    private static final String NO_SPECIFIED_TRAIN = "no specified train"; // NOI18N
1310
1311    private String canSpecifiedTrainService(Car car) {
1312        if (_train == null) {
1313            return NO_SPECIFIED_TRAIN;
1314        }
1315        if (_train.isServiceable(car)) {
1316            return YES;
1317        } // is the reason this train can't service route moves or train length?
1318        else if (!_train.getServiceStatus().equals(Train.NONE)) {
1319            return NOT_NOW; // the issue is route moves or train length
1320        }
1321        return NO;
1322    }
1323
1324    private final static Logger log = LoggerFactory.getLogger(Router.class);
1325
1326}