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