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