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