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