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