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