001package jmri.jmrit.operations.trains.trainbuilder; 002 003import java.util.*; 004 005import org.apache.commons.lang3.StringUtils; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009import jmri.jmrit.operations.locations.Location; 010import jmri.jmrit.operations.locations.Track; 011import jmri.jmrit.operations.locations.schedules.ScheduleItem; 012import jmri.jmrit.operations.rollingstock.cars.Car; 013import jmri.jmrit.operations.rollingstock.cars.CarLoad; 014import jmri.jmrit.operations.rollingstock.engines.Engine; 015import jmri.jmrit.operations.router.Router; 016import jmri.jmrit.operations.routes.RouteLocation; 017import jmri.jmrit.operations.setup.Setup; 018import jmri.jmrit.operations.trains.BuildFailedException; 019import jmri.jmrit.operations.trains.Train; 020 021/** 022 * Contains methods for cars when building a train. 023 * 024 * @author Daniel Boudreau Copyright (C) 2022, 2025 025 */ 026public class TrainBuilderCars extends TrainBuilderEngines { 027 028 /** 029 * Find a caboose if needed at the correct location and add it to the train. 030 * If departing staging, all cabooses are added to the train. If there isn't 031 * a road name required for the caboose, tries to find a caboose with the 032 * same road name as the lead engine. 033 * 034 * @param roadCaboose Optional road name for this car. 035 * @param leadEngine The lead engine for this train. Used to find a 036 * caboose with the same road name as the engine. 037 * @param rl Where in the route to pick up this car. 038 * @param rld Where in the route to set out this car. 039 * @param requiresCaboose When true, the train requires a caboose. 040 * @throws BuildFailedException If car not found. 041 */ 042 protected void getCaboose(String roadCaboose, Engine leadEngine, RouteLocation rl, RouteLocation rld, 043 boolean requiresCaboose) throws BuildFailedException { 044 // code check 045 if (rl == null) { 046 throw new BuildFailedException(Bundle.getMessage("buildErrorCabooseNoLocation", getTrain().getName())); 047 } 048 // code check 049 if (rld == null) { 050 throw new BuildFailedException( 051 Bundle.getMessage("buildErrorCabooseNoDestination", getTrain().getName(), rl.getName())); 052 } 053 // load departure track if staging 054 Track departStagingTrack = null; 055 if (rl == getTrain().getTrainDepartsRouteLocation()) { 056 departStagingTrack = getDepartureStagingTrack(); // can be null 057 } 058 if (!requiresCaboose) { 059 addLine(FIVE, 060 Bundle.getMessage("buildTrainNoCaboose", rl.getName())); 061 if (departStagingTrack == null) { 062 return; 063 } 064 } else { 065 addLine(ONE, Bundle.getMessage("buildTrainReqCaboose", getTrain().getName(), roadCaboose, 066 rl.getName(), rld.getName())); 067 } 068 069 // Now go through the car list looking for cabooses 070 boolean cabooseTip = true; // add a user tip to the build report about 071 // cabooses if none found 072 boolean cabooseAtDeparture = false; // set to true if caboose at 073 // departure location is found 074 boolean foundCaboose = false; 075 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 076 Car car = getCarList().get(_carIndex); 077 if (!car.isCaboose()) { 078 continue; 079 } 080 showCarServiceOrder(car); 081 082 cabooseTip = false; // found at least one caboose, so they exist! 083 addLine(FIVE, Bundle.getMessage("buildCarIsCaboose", car.toString(), car.getRoadName(), 084 car.getLocationName(), car.getTrackName())); 085 // car departing staging must leave with train 086 if (car.getTrack() == departStagingTrack) { 087 foundCaboose = false; 088 if (!generateCarLoadFromStaging(car, rld)) { 089 // departing and terminating into staging? 090 if (car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() && 091 rld.getLocation() == getTerminateLocation() && 092 getTerminateStagingTrack() != null) { 093 // try and generate a custom load for this caboose 094 generateLoadCarDepartingAndTerminatingIntoStaging(car, getTerminateStagingTrack()); 095 } 096 } 097 if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) { 098 if (car.getTrain() == getTrain()) { 099 foundCaboose = true; 100 } 101 } else if (findDestinationAndTrack(car, rl, rld)) { 102 foundCaboose = true; 103 } 104 if (!foundCaboose) { 105 throw new BuildFailedException(Bundle.getMessage("buildErrorCarStageDest", car.toString())); 106 } 107 // is there a specific road requirement for the caboose? 108 } else if (!roadCaboose.equals(Train.NONE) && !roadCaboose.equals(car.getRoadName())) { 109 addLine(SEVEN, Bundle.getMessage("buildCabooseWrongRoad", car.toString(), 110 car.getRoadName(), roadCaboose, rl.getName())); 111 continue; 112 } else if (!foundCaboose && car.getLocationName().equals(rl.getName())) { 113 // remove cars that can't be picked up due to train and track 114 // directions 115 if (!checkPickUpTrainDirection(car, rl)) { 116 addLine(SEVEN, 117 Bundle.getMessage("buildExcludeCarTypeAtLoc", car.toString(), car.getTypeName(), 118 car.getTypeExtensions(), car.getLocationName(), car.getTrackName())); 119 remove(car); // remove this car from the list 120 continue; 121 } 122 // first pass, find a caboose that matches the engine road 123 if (leadEngine != null && car.getRoadName().equals(leadEngine.getRoadName())) { 124 addLine(SEVEN, Bundle.getMessage("buildCabooseRoadMatches", car.toString(), 125 car.getRoadName(), leadEngine.toString())); 126 if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) { 127 if (car.getTrain() == getTrain()) { 128 foundCaboose = true; 129 } 130 } else if (findDestinationAndTrack(car, rl, rld)) { 131 foundCaboose = true; 132 } 133 if (!foundCaboose) { 134 remove(car); // remove this car from the list 135 continue; 136 } 137 } 138 // done if we found a caboose and not departing staging 139 if (foundCaboose && departStagingTrack == null) { 140 break; 141 } 142 } 143 } 144 // second pass, take a caboose with a road name that is "similar" 145 // (hyphen feature) to the engine road name 146 if (requiresCaboose && !foundCaboose && roadCaboose.equals(Train.NONE)) { 147 log.debug("Second pass looking for caboose"); 148 for (Car car : getCarList()) { 149 if (car.isCaboose() && car.getLocationName().equals(rl.getName())) { 150 if (leadEngine != null && 151 TrainCommon.splitString(car.getRoadName()) 152 .equals(TrainCommon.splitString(leadEngine.getRoadName()))) { 153 addLine(SEVEN, Bundle.getMessage("buildCabooseRoadMatches", car.toString(), 154 car.getRoadName(), leadEngine.toString())); 155 if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) { 156 if (car.getTrain() == getTrain()) { 157 foundCaboose = true; 158 break; 159 } 160 } else if (findDestinationAndTrack(car, rl, rld)) { 161 foundCaboose = true; 162 break; 163 } 164 } 165 } 166 } 167 } 168 // third pass, take any caboose unless a caboose road name is specified 169 if (requiresCaboose && !foundCaboose) { 170 log.debug("Third pass looking for caboose"); 171 for (Car car : getCarList()) { 172 if (!car.isCaboose()) { 173 continue; 174 } 175 if (car.getLocationName().equals(rl.getName())) { 176 // is there a specific road requirement for the caboose? 177 if (!roadCaboose.equals(Train.NONE) && !roadCaboose.equals(car.getRoadName())) { 178 continue; // yes 179 } 180 // okay, we found a caboose at the departure location 181 cabooseAtDeparture = true; 182 if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) { 183 if (car.getTrain() == getTrain()) { 184 foundCaboose = true; 185 break; 186 } 187 } else if (findDestinationAndTrack(car, rl, rld)) { 188 foundCaboose = true; 189 break; 190 } 191 } 192 } 193 } 194 if (requiresCaboose && !foundCaboose) { 195 if (cabooseTip) { 196 addLine(ONE, Bundle.getMessage("buildNoteCaboose")); 197 addLine(ONE, Bundle.getMessage("buildNoteCaboose2")); 198 } 199 if (!cabooseAtDeparture) { 200 throw new BuildFailedException(Bundle.getMessage("buildErrorReqDepature", getTrain().getName(), 201 Bundle.getMessage("Caboose").toLowerCase(), rl.getName())); 202 } 203 // we did find a caboose at departure that meet requirements, but 204 // couldn't place it at destination. 205 throw new BuildFailedException(Bundle.getMessage("buildErrorReqDest", getTrain().getName(), 206 Bundle.getMessage("Caboose"), rld.getName())); 207 } 208 } 209 210 /** 211 * Find a car with FRED if needed at the correct location and adds the car 212 * to the train. If departing staging, will make sure all cars with FRED are 213 * added to the train. 214 * 215 * @param road Optional road name for this car. 216 * @param rl Where in the route to pick up this car. 217 * @param rld Where in the route to set out this car. 218 * @throws BuildFailedException If car not found. 219 */ 220 protected void getCarWithFred(String road, RouteLocation rl, RouteLocation rld) throws BuildFailedException { 221 // load departure track if staging 222 Track departStagingTrack = null; 223 if (rl == getTrain().getTrainDepartsRouteLocation()) { 224 departStagingTrack = getDepartureStagingTrack(); 225 } 226 boolean foundCarWithFred = false; 227 if (getTrain().isFredNeeded()) { 228 addLine(ONE, 229 Bundle.getMessage("buildTrainReqFred", getTrain().getName(), road, rl.getName(), rld.getName())); 230 } else { 231 addLine(FIVE, Bundle.getMessage("buildTrainNoFred")); 232 // if not departing staging we're done 233 if (departStagingTrack == null) { 234 return; 235 } 236 } 237 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 238 Car car = getCarList().get(_carIndex); 239 if (!car.hasFred()) { 240 continue; 241 } 242 showCarServiceOrder(car); 243 addLine(FIVE, 244 Bundle.getMessage("buildCarHasFRED", car.toString(), car.getRoadName(), car.getLocationName(), 245 car.getTrackName())); 246 // all cars with FRED departing staging must leave with train 247 if (car.getTrack() == departStagingTrack) { 248 foundCarWithFred = false; 249 if (!generateCarLoadFromStaging(car, rld)) { 250 // departing and terminating into staging? 251 if (car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() && 252 rld.getLocation() == getTerminateLocation() && 253 getTerminateStagingTrack() != null) { 254 // try and generate a custom load for this car with FRED 255 generateLoadCarDepartingAndTerminatingIntoStaging(car, getTerminateStagingTrack()); 256 } 257 } 258 if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) { 259 if (car.getTrain() == getTrain()) { 260 foundCarWithFred = true; 261 } 262 } else if (findDestinationAndTrack(car, rl, rld)) { 263 foundCarWithFred = true; 264 } 265 if (!foundCarWithFred) { 266 throw new BuildFailedException(Bundle.getMessage("buildErrorCarStageDest", car.toString())); 267 } 268 } // is there a specific road requirement for the car with FRED? 269 else if (!road.equals(Train.NONE) && !road.equals(car.getRoadName())) { 270 addLine(SEVEN, Bundle.getMessage("buildExcludeCarWrongRoad", car.toString(), 271 car.getLocationName(), car.getTrackName(), car.getTypeName(), car.getTypeExtensions(), 272 car.getRoadName())); 273 remove(car); // remove this car from the list 274 continue; 275 } else if (!foundCarWithFred && car.getLocationName().equals(rl.getName())) { 276 // remove cars that can't be picked up due to train and track 277 // directions 278 if (!checkPickUpTrainDirection(car, rl)) { 279 addLine(SEVEN, Bundle.getMessage("buildExcludeCarTypeAtLoc", car.toString(), 280 car.getTypeName(), car.getTypeExtensions(), car.getLocationName(), car.getTrackName())); 281 remove(car); // remove this car from the list 282 continue; 283 } 284 if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) { 285 if (car.getTrain() == getTrain()) { 286 foundCarWithFred = true; 287 } 288 } else if (findDestinationAndTrack(car, rl, rld)) { 289 foundCarWithFred = true; 290 } 291 if (foundCarWithFred && departStagingTrack == null) { 292 break; 293 } 294 } 295 } 296 if (getTrain().isFredNeeded() && !foundCarWithFred) { 297 throw new BuildFailedException(Bundle.getMessage("buildErrorRequirements", getTrain().getName(), 298 Bundle.getMessage("FRED"), rl.getName(), rld.getName())); 299 } 300 } 301 302 /** 303 * Determine if caboose or car with FRED was given a destination and track. 304 * Need to check if there's been a train assignment. 305 * 306 * @param car the car in question 307 * @param rl car's route location 308 * @param rld car's route location destination 309 * @return true if car has a destination. Need to check if there's been a 310 * train assignment. 311 * @throws BuildFailedException if destination was staging and can't place 312 * car there 313 */ 314 private boolean checkAndAddCarForDestinationAndTrack(Car car, RouteLocation rl, RouteLocation rld) 315 throws BuildFailedException { 316 return checkCarForDestination(car, rl, getRouteList().indexOf(rld)); 317 } 318 319 /** 320 * Optionally block cars departing staging. No guarantee that cars departing 321 * staging can be blocked by destination. By using the pick up location id, 322 * this routine tries to find destinations that are willing to accepts all 323 * of the cars that were "blocked" together when they were picked up. Rules: 324 * The route must allow set outs at the destination. The route must allow 325 * the correct number of set outs. The destination must accept all cars in 326 * the pick up block. 327 * 328 * @throws BuildFailedException if blocking fails 329 */ 330 protected void blockCarsFromStaging() throws BuildFailedException { 331 if (getDepartureStagingTrack() == null || !getDepartureStagingTrack().isBlockCarsEnabled()) { 332 return; 333 } 334 335 addLine(THREE, BLANK_LINE); 336 addLine(THREE, 337 Bundle.getMessage("blockDepartureHasBlocks", getDepartureStagingTrack().getName(), 338 _numOfBlocks.size())); 339 340 Enumeration<String> en = _numOfBlocks.keys(); 341 while (en.hasMoreElements()) { 342 String locId = en.nextElement(); 343 int numCars = _numOfBlocks.get(locId); 344 String locName = ""; 345 Location l = locationManager.getLocationById(locId); 346 if (l != null) { 347 locName = l.getName(); 348 } 349 addLine(SEVEN, Bundle.getMessage("blockFromHasCars", locId, locName, numCars)); 350 if (_numOfBlocks.size() < 2) { 351 addLine(SEVEN, Bundle.getMessage("blockUnable")); 352 return; 353 } 354 } 355 blockCarsByLocationMoves(); 356 addLine(SEVEN, Bundle.getMessage("blockDone", getDepartureStagingTrack().getName())); 357 } 358 359 /** 360 * Blocks cars out of staging by assigning the largest blocks of cars to 361 * locations requesting the most moves. 362 * 363 * @throws BuildFailedException 364 */ 365 private void blockCarsByLocationMoves() throws BuildFailedException { 366 List<RouteLocation> blockRouteList = getTrain().getRoute().getLocationsBySequenceList(); 367 for (RouteLocation rl : blockRouteList) { 368 // start at the second location in the route to begin blocking 369 if (rl == getTrain().getTrainDepartsRouteLocation()) { 370 continue; 371 } 372 int possibleMoves = rl.getMaxCarMoves() - rl.getCarMoves(); 373 if (rl.isDropAllowed() && possibleMoves > 0) { 374 addLine(SEVEN, Bundle.getMessage("blockLocationHasMoves", rl.getName(), possibleMoves)); 375 } 376 } 377 // now block out cars, send the largest block of cars to the locations 378 // requesting the greatest number of moves 379 while (true) { 380 String blockId = getLargestBlock(); // get the id of the largest 381 // block of cars 382 if (blockId.isEmpty() || _numOfBlocks.get(blockId) == 1) { 383 break; // done 384 } 385 // get the remaining location with the greatest number of moves 386 RouteLocation rld = getLocationWithMaximumMoves(blockRouteList, blockId); 387 if (rld == null) { 388 break; // done 389 } 390 // check to see if there are enough moves for all of the cars 391 // departing staging 392 if (rld.getMaxCarMoves() > _numOfBlocks.get(blockId)) { 393 // remove the largest block and maximum moves RouteLocation from 394 // the lists 395 _numOfBlocks.remove(blockId); 396 // block 0 cars have never left staging. 397 if (blockId.equals(Car.LOCATION_UNKNOWN)) { 398 continue; 399 } 400 blockRouteList.remove(rld); 401 Location loc = locationManager.getLocationById(blockId); 402 Location setOutLoc = rld.getLocation(); 403 if (loc != null && setOutLoc != null && checkDropTrainDirection(rld)) { 404 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 405 Car car = getCarList().get(_carIndex); 406 if (car.getTrack() == getDepartureStagingTrack() && car.getLastLocationId().equals(blockId)) { 407 if (car.getDestination() != null) { 408 addLine(SEVEN, Bundle.getMessage("blockNotAbleDest", car.toString(), 409 car.getDestinationName())); 410 continue; // can't block this car 411 } 412 if (car.getFinalDestination() != null) { 413 addLine(SEVEN, 414 Bundle.getMessage("blockNotAbleFinalDest", car.toString(), 415 car.getFinalDestination().getName())); 416 continue; // can't block this car 417 } 418 if (!car.getLoadName().equals(carLoads.getDefaultEmptyName()) && 419 !car.getLoadName().equals(carLoads.getDefaultLoadName())) { 420 addLine(SEVEN, 421 Bundle.getMessage("blockNotAbleCustomLoad", car.toString(), car.getLoadName())); 422 continue; // can't block this car 423 } 424 if (car.getLoadName().equals(carLoads.getDefaultEmptyName()) && 425 (getDepartureStagingTrack().isAddCustomLoadsEnabled() || 426 getDepartureStagingTrack().isAddCustomLoadsAnySpurEnabled() || 427 getDepartureStagingTrack().isAddCustomLoadsAnyStagingTrackEnabled())) { 428 addLine(SEVEN, 429 Bundle.getMessage("blockNotAbleCarTypeGenerate", car.toString(), 430 car.getLoadName())); 431 continue; // can't block this car 432 } 433 addLine(SEVEN, 434 Bundle.getMessage("blockingCar", car.toString(), loc.getName(), rld.getName())); 435 if (!findDestinationAndTrack(car, getTrain().getTrainDepartsRouteLocation(), rld)) { 436 addLine(SEVEN, 437 Bundle.getMessage("blockNotAbleCarType", car.toString(), rld.getName(), 438 car.getTypeName())); 439 } 440 } 441 } 442 } 443 } else { 444 addLine(SEVEN, Bundle.getMessage("blockDestNotEnoughMoves", rld.getName(), blockId)); 445 // block is too large for any stop along this train's route 446 _numOfBlocks.remove(blockId); 447 } 448 } 449 } 450 451 /** 452 * Attempts to find a destinations for cars departing a specific route 453 * location. 454 * 455 * @param rl The route location where cars need destinations. 456 * @param isSecondPass When true this is the second time we've looked at 457 * these cars. Used to perform local moves. 458 * @throws BuildFailedException if failure 459 */ 460 protected void findDestinationsForCarsFromLocation(RouteLocation rl, boolean isSecondPass) 461 throws BuildFailedException { 462 if (_reqNumOfMoves <= 0) { 463 return; 464 } 465 if (!rl.isLocalMovesAllowed() && isSecondPass) { 466 addLine(FIVE, 467 Bundle.getMessage("buildRouteNoLocalLocation", getTrain().getRoute().getName(), 468 rl.getId(), rl.getName())); 469 addLine(FIVE, BLANK_LINE); 470 return; 471 } 472 boolean messageFlag = true; 473 boolean foundCar = false; 474 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 475 if (_reqNumOfMoves <= 0) { 476 break; // done 477 } 478 Car car = getCarList().get(_carIndex); 479 // second pass deals with cars that have a final destination equal 480 // to this location. 481 // therefore a local move can be made. This causes "off spots" to be 482 // serviced. 483 if (isSecondPass && !car.getFinalDestinationName().equals(rl.getName())) { 484 continue; 485 } 486 // find a car at this location 487 if (!car.getLocationName().equals(rl.getName())) { 488 continue; 489 } 490 foundCar = true; 491 // add message that we're on the second pass for this location 492 if (isSecondPass && messageFlag) { 493 messageFlag = false; 494 addLine(FIVE, Bundle.getMessage("buildExtraPassForLocation", rl.getName())); 495 addLine(SEVEN, BLANK_LINE); 496 } 497 // are pick ups allowed? 498 if (!rl.isPickUpAllowed() && 499 !car.isLocalMove() && 500 !car.getSplitFinalDestinationName().equals(rl.getSplitName())) { 501 addLine(FIVE, 502 Bundle.getMessage("buildNoPickUpCar", car.toString(), rl.getLocation().getName(), rl.getId())); 503 addLine(FIVE, BLANK_LINE); 504 continue; 505 } 506 findDestinationsFromLocation(rl, car, isSecondPass); 507 } 508 if (!foundCar && !isSecondPass) { 509 addLine(FIVE, Bundle.getMessage("buildNoCarsAtLocation", rl.getName())); 510 addLine(FIVE, BLANK_LINE); 511 } 512 } 513 514 protected void findDestinationsFromLocation(RouteLocation rl, Car car, boolean isSecondPass) 515 throws BuildFailedException { 516 if (!rl.isLocalMovesAllowed() && car.getSplitFinalDestinationName().equals(rl.getSplitName())) { 517 addLine(FIVE, 518 Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(), 519 rl.getId(), rl.getName(), car.toString())); 520 } 521 // can this car be pulled from an interchange or spur? 522 if (!checkPickupInterchangeOrSpur(car)) { 523 log.debug("Removing car ({}) from list", car.toString()); 524 remove(car); 525 addLine(FIVE, BLANK_LINE); 526 return; // no 527 } 528 // can this car be picked up? 529 if (!checkPickUpTrainDirection(car, rl)) { 530 addLine(FIVE, BLANK_LINE); 531 return; // no 532 } 533 // do alternate track moves on the second pass (makes FIFO / LIFO work correctly) 534 if (car.getTrack().isAlternate()) { 535 addLine(SEVEN, Bundle.getMessage("buildCarOnAlternateTrack", car.toString(), 536 car.getTrack().getTrackTypeName(), car.getLocationName(), car.getTrackName())); 537 if (Setup.isBuildAggressive() && !isSecondPass && _completedMoves != 0) { 538 addLine(SEVEN, BLANK_LINE); 539 return; 540 } 541 } 542 543 showCarServiceOrder(car); // car on FIFO or LIFO track? 544 545 // is car departing staging and generate custom load? 546 if (!generateCarLoadFromStaging(car)) { 547 if (!generateCarLoadStagingToStaging(car) && 548 car.getTrack() == getDepartureStagingTrack() && 549 !getDepartureStagingTrack().isLoadNameAndCarTypeShipped(car.getLoadName(), car.getTypeName())) { 550 // report build failure car departing staging with a 551 // restricted load 552 addLine(ONE, Bundle.getMessage("buildErrorCarStageLoad", car.toString(), 553 car.getLoadName(), getDepartureStagingTrack().getName())); 554 addLine(FIVE, BLANK_LINE); 555 return; // keep going and see if there are other cars with 556 // issues outs of staging 557 } 558 } 559 // check for quick service track timing 560 if (!checkQuickServiceDeparting(car, rl)) { 561 return; 562 } 563 // If car been given a home division follow division rules for car 564 // movement. 565 if (!findDestinationsForCarsWithHomeDivision(car)) { 566 addLine(FIVE, 567 Bundle.getMessage("buildNoDestForCar", car.toString())); 568 addLine(FIVE, BLANK_LINE); 569 return; // hold car at current location 570 } 571 // does car have a custom load without a destination? 572 // if departing staging, a destination for this car is needed, so 573 // keep going 574 if (findFinalDestinationForCarLoad(car) && 575 car.getDestination() == null && 576 car.getTrack() != getDepartureStagingTrack()) { 577 // done with this car, it has a custom load, and there are 578 // spurs/schedules, but no destination found 579 addLine(FIVE, 580 Bundle.getMessage("buildNoDestForCar", car.toString())); 581 addLine(FIVE, BLANK_LINE); 582 return; 583 } 584 // Check car for final destination, then an assigned destination, if 585 // neither, find a destination for the car 586 if (checkCarForFinalDestination(car)) { 587 log.debug("Car ({}) has a final desination that can't be serviced by train", car.toString()); 588 } else if (checkCarForDestination(car, rl, getRouteList().indexOf(rl))) { 589 // car had a destination, could have been added to the train. 590 log.debug("Car ({}) has desination ({}) using train ({})", car.toString(), car.getDestinationName(), 591 car.getTrainName()); 592 } else { 593 findDestinationAndTrack(car, rl, getRouteList().indexOf(rl), getRouteList().size()); 594 } 595 // build failure if car departing staging without a destination and 596 // a train we'll just put out a warning message here so we can find 597 // out how many cars have issues 598 if (car.getTrack() == getDepartureStagingTrack() && 599 (car.getDestination() == null || car.getDestinationTrack() == null || car.getTrain() == null)) { 600 addLine(ONE, Bundle.getMessage("buildWarningCarStageDest", car.toString())); 601 // does the car have a final destination to staging? If so we 602 // need to reset this car 603 if (car.getFinalDestinationTrack() != null && 604 car.getFinalDestinationTrack() == getTerminateStagingTrack()) { 605 addLine(THREE, 606 Bundle.getMessage("buildStagingCarHasFinal", car.toString(), car.getFinalDestinationName(), 607 car.getFinalDestinationTrackName())); 608 car.reset(); 609 } 610 addLine(SEVEN, BLANK_LINE); 611 } 612 } 613 614 private boolean generateCarLoadFromStaging(Car car) throws BuildFailedException { 615 return generateCarLoadFromStaging(car, null); 616 } 617 618 /** 619 * Used to generate a car's load from staging. Search for a spur with a 620 * schedule and load car if possible. 621 * 622 * @param car the car 623 * @param rld The route location destination for this car. Can be null. 624 * @return true if car given a custom load 625 * @throws BuildFailedException If code check fails 626 */ 627 private boolean generateCarLoadFromStaging(Car car, RouteLocation rld) throws BuildFailedException { 628 // Code Check, car should have a track assignment 629 if (car.getTrack() == null) { 630 throw new BuildFailedException( 631 Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName())); 632 } 633 if (!car.getTrack().isStaging() || 634 (!car.getTrack().isAddCustomLoadsAnySpurEnabled() && !car.getTrack().isAddCustomLoadsEnabled()) || 635 !car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 636 car.getDestination() != null || 637 car.getFinalDestination() != null) { 638 log.debug( 639 "No load generation for car ({}) isAddLoadsAnySpurEnabled: {}, car load ({}) destination ({}) final destination ({})", 640 car.toString(), car.getTrack().isAddCustomLoadsAnySpurEnabled() ? "true" : "false", 641 car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName()); 642 // if car has a destination or final destination add "no load 643 // generated" message to report 644 if (car.getTrack().isStaging() && 645 car.getTrack().isAddCustomLoadsAnySpurEnabled() && 646 car.getLoadName().equals(carLoads.getDefaultEmptyName())) { 647 addLine(FIVE, 648 Bundle.getMessage("buildCarNoLoadGenerated", car.toString(), car.getLoadName(), 649 car.getDestinationName(), car.getFinalDestinationName())); 650 } 651 return false; // no load generated for this car 652 } 653 addLine(FIVE, 654 Bundle.getMessage("buildSearchTrackNewLoad", car.toString(), car.getTypeName(), 655 car.getLoadType().toLowerCase(), car.getLoadName(), car.getLocationName(), car.getTrackName(), 656 rld != null ? rld.getLocation().getName() : "")); 657 // check to see if car type has custom loads 658 if (carLoads.getNames(car.getTypeName()).size() == 2) { 659 addLine(SEVEN, Bundle.getMessage("buildCarNoCustomLoad", car.toString(), car.getTypeName())); 660 return false; 661 } 662 if (car.getKernel() != null) { 663 addLine(SEVEN, 664 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 665 car.getKernel().getSize(), car.getKernel().getTotalLength(), 666 Setup.getLengthUnit().toLowerCase())); 667 } 668 // save the car's load, should be the default empty 669 String oldCarLoad = car.getLoadName(); 670 List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR); 671 log.debug("Found {} spurs", tracks.size()); 672 // show locations not serviced by departure track once 673 List<Location> locationsNotServiced = new ArrayList<>(); 674 for (Track track : tracks) { 675 if (locationsNotServiced.contains(track.getLocation())) { 676 continue; 677 } 678 if (rld != null && track.getLocation() != rld.getLocation()) { 679 locationsNotServiced.add(track.getLocation()); 680 continue; 681 } 682 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 683 addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", 684 track.getLocation().getName(), car.getTrackName())); 685 locationsNotServiced.add(track.getLocation()); 686 continue; 687 } 688 // only use tracks serviced by this train? 689 if (car.getTrack().isAddCustomLoadsEnabled() && 690 !getTrain().getRoute().isLocationNameInRoute(track.getLocation().getName())) { 691 continue; 692 } 693 // only the first match in a schedule is used for a spur 694 ScheduleItem si = getScheduleItem(car, track); 695 if (si == null) { 696 continue; // no match 697 } 698 // need to set car load so testDestination will work properly 699 car.setLoadName(si.getReceiveLoadName()); 700 car.setScheduleItemId(si.getId()); 701 String status = car.checkDestination(track.getLocation(), track); 702 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 703 addLine(SEVEN, 704 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 705 track.getLocation().getName(), track.getName(), car.toString(), 706 Track.LOAD, si.getReceiveLoadName(), 707 status)); 708 continue; 709 } 710 addLine(SEVEN, Bundle.getMessage("buildTrySpurLoad", track.getLocation().getName(), 711 track.getName(), car.getLoadName())); 712 // does the car have a home division? 713 if (car.getDivision() != null) { 714 addLine(SEVEN, 715 Bundle.getMessage("buildCarHasDivisionStaging", car.toString(), car.getTypeName(), 716 car.getLoadType().toLowerCase(), car.getLoadName(), car.getDivisionName(), 717 car.getLocationName(), car.getTrackName(), car.getTrack().getDivisionName())); 718 // load type empty must return to car's home division 719 // or load type load from foreign division must return to car's 720 // home division 721 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) && car.getDivision() != track.getDivision() || 722 car.getLoadType().equals(CarLoad.LOAD_TYPE_LOAD) && 723 car.getTrack().getDivision() != car.getDivision() && 724 car.getDivision() != track.getDivision()) { 725 addLine(SEVEN, 726 Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(), 727 track.getLocation().getName(), track.getName(), track.getDivisionName(), 728 car.toString(), car.getLoadType().toLowerCase(), car.getLoadName())); 729 continue; 730 } 731 } 732 if (!track.isSpaceAvailable(car)) { 733 addLine(SEVEN, 734 Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(), 735 track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(), 736 Setup.getLengthUnit().toLowerCase(), track.getReservationFactor())); 737 continue; 738 } 739 // try routing car 740 car.setFinalDestination(track.getLocation()); 741 car.setFinalDestinationTrack(track); 742 if (router.setDestination(car, getTrain(), getBuildReport()) && car.getDestination() != null) { 743 // return car with this custom load and destination 744 addLine(FIVE, 745 Bundle.getMessage("buildCreateNewLoadForCar", car.toString(), si.getReceiveLoadName(), 746 track.getLocation().getName(), track.getName())); 747 car.setLoadGeneratedFromStaging(true); 748 // is car part of kernel? 749 car.updateKernel(); 750 track.bumpMoves(); 751 track.bumpSchedule(); 752 return true; // done, car now has a custom load 753 } 754 addLine(SEVEN, Bundle.getMessage("buildCanNotRouteCar", car.toString(), 755 si.getReceiveLoadName(), track.getLocation().getName(), track.getName())); 756 addLine(SEVEN, BLANK_LINE); 757 car.setDestination(null, null); 758 car.setFinalDestination(null); 759 car.setFinalDestinationTrack(null); 760 } 761 // restore car's load 762 car.setLoadName(oldCarLoad); 763 car.setScheduleItemId(Car.NONE); 764 addLine(FIVE, Bundle.getMessage("buildUnableNewLoad", car.toString())); 765 return false; // done, no load generated for this car 766 } 767 768 /** 769 * Tries to place a custom load in the car that is departing staging and 770 * attempts to find a destination for the car that is also staging. 771 * 772 * @param car the car 773 * @return True if custom load added to car 774 * @throws BuildFailedException If code check fails 775 */ 776 private boolean generateCarLoadStagingToStaging(Car car) throws BuildFailedException { 777 // Code Check, car should have a track assignment 778 if (car.getTrack() == null) { 779 throw new BuildFailedException( 780 Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName())); 781 } 782 if (!car.getTrack().isStaging() || 783 !car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() || 784 !car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 785 car.getDestination() != null || 786 car.getFinalDestination() != null) { 787 log.debug( 788 "No load generation for car ({}) isAddCustomLoadsAnyStagingTrackEnabled: {}, car load ({}) destination ({}) final destination ({})", 789 car.toString(), car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() ? "true" : "false", 790 car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName()); 791 return false; 792 } 793 // check to see if car type has custom loads 794 if (carLoads.getNames(car.getTypeName()).size() == 2) { 795 return false; 796 } 797 List<Track> tracks = locationManager.getTracks(Track.STAGING); 798 addLine(FIVE, Bundle.getMessage("buildTryStagingToStaging", car.toString(), tracks.size())); 799 if (Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED)) { 800 for (Track track : tracks) { 801 addLine(SEVEN, 802 Bundle.getMessage("buildStagingLocationTrack", track.getLocation().getName(), track.getName())); 803 } 804 } 805 // list of locations that can't be reached by the router 806 List<Location> locationsNotServiced = new ArrayList<>(); 807 if (getTerminateStagingTrack() != null) { 808 addLine(SEVEN, 809 Bundle.getMessage("buildIgnoreStagingFirstPass", 810 getTerminateStagingTrack().getLocation().getName())); 811 locationsNotServiced.add(getTerminateStagingTrack().getLocation()); 812 } 813 while (tracks.size() > 0) { 814 // pick a track randomly 815 int rnd = (int) (Math.random() * tracks.size()); 816 Track track = tracks.get(rnd); 817 tracks.remove(track); 818 log.debug("Try staging track ({}, {})", track.getLocation().getName(), track.getName()); 819 // find a staging track that isn't at the departure 820 if (track.getLocation() == getDepartureLocation()) { 821 log.debug("Can't use departure location ({})", track.getLocation().getName()); 822 continue; 823 } 824 if (!getTrain().isAllowThroughCarsEnabled() && track.getLocation() == getTerminateLocation()) { 825 log.debug("Through cars to location ({}) not allowed", track.getLocation().getName()); 826 continue; 827 } 828 if (locationsNotServiced.contains(track.getLocation())) { 829 log.debug("Location ({}) not reachable", track.getLocation().getName()); 830 continue; 831 } 832 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 833 addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", 834 track.getLocation().getName(), car.getTrackName())); 835 locationsNotServiced.add(track.getLocation()); 836 continue; 837 } 838 // the following method sets the Car load generated from staging 839 // boolean 840 if (generateLoadCarDepartingAndTerminatingIntoStaging(car, track)) { 841 // test to see if destination is reachable by this train 842 if (router.setDestination(car, getTrain(), getBuildReport()) && car.getDestination() != null) { 843 return true; // done, car has a custom load and a final 844 // destination 845 } 846 addLine(SEVEN, Bundle.getMessage("buildStagingTrackNotReachable", 847 track.getLocation().getName(), track.getName(), car.getLoadName())); 848 // return car to original state 849 car.setLoadName(carLoads.getDefaultEmptyName()); 850 car.setLoadGeneratedFromStaging(false); 851 car.setFinalDestination(null); 852 car.updateKernel(); 853 // couldn't route to this staging location 854 locationsNotServiced.add(track.getLocation()); 855 } 856 } 857 // No staging tracks reachable, try the track the train is terminating 858 // to 859 if (getTrain().isAllowThroughCarsEnabled() && 860 getTerminateStagingTrack() != null && 861 car.getTrack().isDestinationAccepted(getTerminateStagingTrack().getLocation()) && 862 generateLoadCarDepartingAndTerminatingIntoStaging(car, getTerminateStagingTrack())) { 863 return true; 864 } 865 866 addLine(SEVEN, 867 Bundle.getMessage("buildNoStagingForCarCustom", car.toString())); 868 addLine(SEVEN, BLANK_LINE); 869 return false; 870 } 871 872 /** 873 * Check to see if car has been assigned a home division. If car has a home 874 * division the following rules are applied when assigning the car a 875 * destination: 876 * <p> 877 * If car load is type empty not at car's home division yard: Car is sent to 878 * a home division yard. If home division yard not available, then car is 879 * sent to home division staging, then spur (industry). 880 * <p> 881 * If car load is type empty at a yard at the car's home division: Car is 882 * sent to a home division spur, then home division staging. 883 * <p> 884 * If car load is type load not at car's home division: Car is sent to home 885 * division spur, and if spur not available then home division staging. 886 * <p> 887 * If car load is type load at car's home division: Car is sent to any 888 * division spur or staging. 889 * 890 * @param car the car being checked for a home division 891 * @return false if destination track not found for this car 892 * @throws BuildFailedException 893 */ 894 private boolean findDestinationsForCarsWithHomeDivision(Car car) throws BuildFailedException { 895 if (car.getDivision() == null || car.getDestination() != null || car.getFinalDestination() != null) { 896 return true; 897 } 898 if (car.getDivision() == car.getTrack().getDivision()) { 899 addLine(FIVE, 900 Bundle.getMessage("buildCarDepartHomeDivision", car.toString(), car.getTypeName(), 901 car.getLoadType().toLowerCase(), 902 car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(), 903 car.getLocationName(), car.getTrackName(), 904 car.getTrack().getDivisionName())); 905 } else { 906 addLine(FIVE, 907 Bundle.getMessage("buildCarDepartForeignDivision", car.toString(), car.getTypeName(), 908 car.getLoadType().toLowerCase(), 909 car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(), 910 car.getLocationName(), car.getTrackName(), 911 car.getTrack().getDivisionName())); 912 } 913 if (car.getKernel() != null) { 914 addLine(SEVEN, 915 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 916 car.getKernel().getSize(), 917 car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 918 } 919 // does train terminate into staging? and car not departing staging? 920 if (getTerminateStagingTrack() != null && !car.getTrack().getTrackType().equals(Track.STAGING)) { 921 log.debug("Train terminates into staging track ({})", getTerminateStagingTrack().getName()); 922 // bias cars to staging 923 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) { 924 log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName()); 925 if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) { 926 log.debug("Car ({}) at it's home division yard", car.toString()); 927 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 928 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 929 } 930 } 931 // try to send to home division staging, then home division yard, 932 // then home division spur 933 else if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 934 if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) { 935 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 936 } 937 } 938 } else { 939 log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName()); 940 // 1st send car to staging dependent of shipping track division, then 941 // try spur 942 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, 943 car.getTrack().getDivision() != car.getDivision())) { 944 return sendCarToHomeDivisionTrack(car, Track.SPUR, 945 car.getTrack().getDivision() != car.getDivision()); 946 } 947 } 948 } else { 949 // train doesn't terminate into staging or the car is departing staging 950 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) { 951 log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName()); 952 if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) { 953 log.debug("Car ({}) at it's home division yard", car.toString()); 954 if (!sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION)) { 955 return sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION); 956 } 957 } 958 // try to send to home division yard, then home division staging, 959 // then home division spur 960 else if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) { 961 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 962 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 963 } 964 } 965 } else { 966 log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName()); 967 // 1st send car to spur dependent of shipping track division, then 968 // try staging 969 if (!sendCarToHomeDivisionTrack(car, Track.SPUR, car.getTrack().getDivision() != car.getDivision())) { 970 return sendCarToHomeDivisionTrack(car, Track.STAGING, 971 car.getTrack().getDivision() != car.getDivision()); 972 } 973 } 974 } 975 return true; 976 } 977 978 private static final boolean HOME_DIVISION = true; 979 980 /** 981 * Tries to set a final destination for the car with a home division. 982 * 983 * @param car the car 984 * @param trackType One of three track types: Track.SPUR Track.YARD or 985 * Track.STAGING 986 * @param home_division If true track's division must match the car's 987 * @return true if car was given a final destination 988 */ 989 private boolean sendCarToHomeDivisionTrack(Car car, String trackType, boolean home_division) { 990 // locations not reachable 991 List<Location> locationsNotServiced = new ArrayList<>(); 992 List<Track> tracks = locationManager.getTracksByMoves(trackType); 993 log.debug("Found {} {} tracks", tracks.size(), trackType); 994 for (Track track : tracks) { 995 if (home_division && car.getDivision() != track.getDivision()) { 996 addLine(SEVEN, 997 Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(), 998 track.getLocation().getName(), track.getName(), track.getDivisionName(), car.toString(), 999 car.getLoadType().toLowerCase(), 1000 car.getLoadName())); 1001 continue; 1002 } 1003 if (locationsNotServiced.contains(track.getLocation())) { 1004 continue; 1005 } 1006 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 1007 addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", 1008 track.getLocation().getName(), car.getTrackName())); 1009 // location not reachable 1010 locationsNotServiced.add(track.getLocation()); 1011 continue; 1012 } 1013 // only use the termination staging track for this train 1014 if (trackType.equals(Track.STAGING) && 1015 getTerminateStagingTrack() != null && 1016 track.getLocation() == getTerminateLocation() && 1017 track != getTerminateStagingTrack()) { 1018 continue; 1019 } 1020 if (trackType.equals(Track.SPUR)) { 1021 if (sendCarToDestinationSpur(car, track)) { 1022 return true; 1023 } 1024 } else { 1025 if (sendCarToDestinationTrack(car, track)) { 1026 return true; 1027 } 1028 } 1029 } 1030 addLine(FIVE, 1031 Bundle.getMessage("buildCouldNotFindTrack", trackType.toLowerCase(), car.toString(), 1032 car.getLoadType().toLowerCase(), car.getLoadName())); 1033 addLine(SEVEN, BLANK_LINE); 1034 return false; 1035 } 1036 1037 /** 1038 * Set the final destination and track for a car with a custom load. Car 1039 * must not have a destination or final destination. There's a check to see 1040 * if there's a spur/schedule for this car. Returns true if a schedule was 1041 * found. Will hold car at current location if any of the spurs checked has 1042 * the the option to "Hold cars with custom loads" enabled and the spur has 1043 * an alternate track assigned. Tries to sent the car to staging if there 1044 * aren't any spurs with schedules available. 1045 * 1046 * @param car the car with the load 1047 * @return true if there's a schedule that can be routed to for this car and 1048 * load 1049 * @throws BuildFailedException 1050 */ 1051 private boolean findFinalDestinationForCarLoad(Car car) throws BuildFailedException { 1052 if (car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 1053 car.getLoadName().equals(carLoads.getDefaultLoadName()) || 1054 car.getDestination() != null || 1055 car.getFinalDestination() != null) { 1056 return false; // car doesn't have a custom load, or already has a 1057 // destination set 1058 } 1059 addLine(FIVE, 1060 Bundle.getMessage("buildSearchForSpur", car.toString(), car.getTypeName(), car.getTypeExtensions(), 1061 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackTypeName(), car.getLocationName(), 1062 car.getTrackName())); 1063 if (car.getKernel() != null) { 1064 addLine(SEVEN, 1065 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 1066 car.getKernel().getSize(), car.getKernel().getTotalLength(), 1067 Setup.getLengthUnit().toLowerCase())); 1068 } 1069 _routeToTrackFound = false; 1070 List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR); 1071 log.debug("Found {} spurs", tracks.size()); 1072 // locations not reachable 1073 List<Location> locationsNotServiced = new ArrayList<>(); 1074 for (Track track : tracks) { 1075 if (car.getTrack() == track) { 1076 continue; 1077 } 1078 if (track.getSchedule() == null) { 1079 addLine(SEVEN, Bundle.getMessage("buildSpurNoSchedule", 1080 track.getLocation().getName(), track.getName())); 1081 continue; 1082 } 1083 if (locationsNotServiced.contains(track.getLocation())) { 1084 continue; 1085 } 1086 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 1087 addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", 1088 track.getLocation().getName(), car.getTrackName())); 1089 // location not reachable 1090 locationsNotServiced.add(track.getLocation()); 1091 continue; 1092 } 1093 if (sendCarToDestinationSpur(car, track)) { 1094 return true; 1095 } 1096 } 1097 addLine(SEVEN, 1098 Bundle.getMessage("buildCouldNotFindTrack", Track.getTrackTypeName(Track.SPUR).toLowerCase(), 1099 car.toString(), car.getLoadType().toLowerCase(), car.getLoadName())); 1100 if (_routeToTrackFound && 1101 !getTrain().isSendCarsWithCustomLoadsToStagingEnabled() && 1102 !car.getLocation().isStaging()) { 1103 addLine(SEVEN, Bundle.getMessage("buildHoldCarValidRoute", car.toString(), 1104 car.getLocationName(), car.getTrackName())); 1105 } else { 1106 // try and send car to staging 1107 addLine(SEVEN, BLANK_LINE); 1108 addLine(FIVE, 1109 Bundle.getMessage("buildTrySendCarToStaging", car.toString(), car.getLoadName())); 1110 tracks = locationManager.getTracks(Track.STAGING); 1111 log.debug("Found {} staging tracks", tracks.size()); 1112 while (tracks.size() > 0) { 1113 // pick a track randomly 1114 int rnd = (int) (Math.random() * tracks.size()); 1115 Track track = tracks.get(rnd); 1116 tracks.remove(track); 1117 log.debug("Staging track ({}, {})", track.getLocation().getName(), track.getName()); 1118 if (track.getLocation() == car.getLocation()) { 1119 continue; 1120 } 1121 if (locationsNotServiced.contains(track.getLocation())) { 1122 continue; 1123 } 1124 if (getTerminateStagingTrack() != null && 1125 track.getLocation() == getTerminateLocation() && 1126 track != getTerminateStagingTrack()) { 1127 continue; // ignore other staging tracks at terminus 1128 } 1129 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 1130 addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", 1131 track.getLocation().getName(), car.getTrackName())); 1132 locationsNotServiced.add(track.getLocation()); 1133 continue; 1134 } 1135 String status = track.isRollingStockAccepted(car); 1136 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 1137 log.debug("Staging track ({}) can't accept car ({})", track.getName(), car.toString()); 1138 continue; 1139 } 1140 addLine(SEVEN, Bundle.getMessage("buildStagingCanAcceptLoad", track.getLocation(), 1141 track.getName(), car.getLoadName())); 1142 // try to send car to staging 1143 car.setFinalDestination(track.getLocation()); 1144 // test to see if destination is reachable by this train 1145 if (router.setDestination(car, getTrain(), getBuildReport())) { 1146 _routeToTrackFound = true; // found a route to staging 1147 } 1148 if (car.getDestination() != null) { 1149 car.updateKernel(); // car part of kernel? 1150 return true; 1151 } 1152 // couldn't route to this staging location 1153 locationsNotServiced.add(track.getLocation()); 1154 car.setFinalDestination(null); 1155 } 1156 addLine(SEVEN, 1157 Bundle.getMessage("buildNoStagingForCarLoad", car.toString(), car.getLoadName())); 1158 if (!_routeToTrackFound) { 1159 addLine(SEVEN, BLANK_LINE); 1160 } 1161 } 1162 log.debug("routeToSpurFound is {}", _routeToTrackFound); 1163 return _routeToTrackFound; // done 1164 } 1165 1166 boolean _routeToTrackFound; 1167 1168 /** 1169 * Used to determine if spur can accept car. Also will set routeToTrackFound 1170 * to true if there's a valid route available to the spur being tested. Sets 1171 * car's final destination to track if okay. 1172 * 1173 * @param car the car 1174 * @param track the spur 1175 * @return false if there's an issue with using the spur 1176 */ 1177 private boolean sendCarToDestinationSpur(Car car, Track track) { 1178 addLine(SEVEN, BLANK_LINE); 1179 if (!checkBasicMoves(car, track)) { 1180 addLine(SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", getTrain().getName(), 1181 car.toString(), track.getLocation().getName(), track.getName())); 1182 return false; 1183 } 1184 String status = car.checkDestination(track.getLocation(), track); 1185 if (!status.equals(Track.OKAY)) { 1186 if (track.getScheduleMode() == Track.SEQUENTIAL && status.startsWith(Track.SCHEDULE)) { 1187 addLine(SEVEN, Bundle.getMessage("buildTrackSequentialMode", 1188 track.getLocation().getName(), track.getName(), status)); 1189 } 1190 // if the track has an alternate track don't abort if the issue was 1191 // space 1192 if (!status.startsWith(Track.LENGTH)) { 1193 addLine(SEVEN, 1194 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1195 track.getLocation().getName(), track.getName(), car.toString(), 1196 car.getLoadType().toLowerCase(), car.getLoadName(), status)); 1197 return false; 1198 } 1199 if (track.getAlternateTrack() == null) { 1200 // report that the spur is full and no alternate 1201 addLine(SEVEN, 1202 Bundle.getMessage("buildSpurFullNoAlternate", track.getLocation().getName(), track.getName())); 1203 return false; 1204 } else { 1205 addLine(SEVEN, 1206 Bundle.getMessage("buildTrackFullHasAlternate", track.getLocation().getName(), track.getName(), 1207 track.getAlternateTrack().getName())); 1208 // check to see if alternate and track are configured properly 1209 if (!getTrain().isLocalSwitcher() && 1210 (track.getTrainDirections() & track.getAlternateTrack().getTrainDirections()) == 0) { 1211 addLine(SEVEN, Bundle.getMessage("buildCanNotDropRsUsingTrain4", track.getName(), 1212 formatStringToCommaSeparated(Setup.getDirectionStrings(track.getTrainDirections())), 1213 track.getAlternateTrack().getName(), formatStringToCommaSeparated( 1214 Setup.getDirectionStrings(track.getAlternateTrack().getTrainDirections())))); 1215 return false; 1216 } 1217 } 1218 } 1219 addLine(SEVEN, 1220 Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(), 1221 track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(), 1222 car.getLoadName())); 1223 1224 // show if track is requesting cars with custom loads to only go to 1225 // spurs 1226 if (track.isHoldCarsWithCustomLoadsEnabled()) { 1227 addLine(SEVEN, 1228 Bundle.getMessage("buildHoldCarsCustom", track.getLocation().getName(), track.getName())); 1229 } 1230 // check the number of in bound cars to this track 1231 if (!track.isSpaceAvailable(car)) { 1232 // Now determine if we should move the car or just leave it 1233 if (track.isHoldCarsWithCustomLoadsEnabled()) { 1234 // determine if this car can be routed to the spur 1235 String id = track.getScheduleItemId(); 1236 if (router.isCarRouteable(car, getTrain(), track, getBuildReport())) { 1237 // hold car if able to route to track 1238 _routeToTrackFound = true; 1239 } else { 1240 addLine(SEVEN, Bundle.getMessage("buildRouteNotFound", car.toString(), 1241 car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1242 } 1243 track.setScheduleItemId(id); // restore id 1244 } 1245 if (car.getTrack().isStaging()) { 1246 addLine(SEVEN, 1247 Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(), 1248 track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(), 1249 Setup.getLengthUnit().toLowerCase(), track.getReservationFactor())); 1250 } else { 1251 addLine(SEVEN, 1252 Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(), 1253 track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(), 1254 track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase())); 1255 } 1256 return false; 1257 } 1258 // try to send car to this spur 1259 car.setFinalDestination(track.getLocation()); 1260 car.setFinalDestinationTrack(track); 1261 // test to see if destination is reachable by this train 1262 if (router.setDestination(car, getTrain(), getBuildReport()) && track.isHoldCarsWithCustomLoadsEnabled()) { 1263 _routeToTrackFound = true; // if we don't find another spur, don't 1264 // move car 1265 } 1266 if (car.getDestination() == null) { 1267 if (!router.getStatus().equals(Track.OKAY)) { 1268 addLine(SEVEN, 1269 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1270 } 1271 car.setFinalDestination(null); 1272 car.setFinalDestinationTrack(null); 1273 car.setScheduleItemId(Car.NONE); 1274 // don't move car if another train can 1275 if (router.getStatus().startsWith(Router.STATUS_NOT_THIS_TRAIN_PREFIX)) { 1276 _routeToTrackFound = true; 1277 } 1278 return false; 1279 } 1280 if (car.getDestinationTrack() != track) { 1281 track.bumpMoves(); 1282 // car is being routed to this track 1283 if (track.getSchedule() != null) { 1284 car.setScheduleItemId(track.getScheduleItemId()); 1285 track.bumpSchedule(); 1286 } 1287 } 1288 car.updateKernel(); 1289 return true; // done, car has a new destination 1290 } 1291 1292 /** 1293 * Destination track can be division yard or staging, NOT a spur. 1294 * 1295 * @param car the car 1296 * @param track the car's destination track 1297 * @return true if car given a new final destination 1298 */ 1299 private boolean sendCarToDestinationTrack(Car car, Track track) { 1300 if (!checkBasicMoves(car, track)) { 1301 addLine(SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", getTrain().getName(), 1302 car.toString(), track.getLocation().getName(), track.getName())); 1303 return false; 1304 } 1305 String status = car.checkDestination(track.getLocation(), track); 1306 1307 if (!status.equals(Track.OKAY)) { 1308 addLine(SEVEN, 1309 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1310 track.getLocation().getName(), track.getName(), car.toString(), 1311 car.getLoadType().toLowerCase(), car.getLoadName(), status)); 1312 return false; 1313 } 1314 if (!track.isSpaceAvailable(car)) { 1315 addLine(SEVEN, 1316 Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(), 1317 track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(), 1318 track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase())); 1319 return false; 1320 } 1321 // try to send car to this division track 1322 addLine(SEVEN, 1323 Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(), 1324 track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(), 1325 car.getLoadName())); 1326 car.setFinalDestination(track.getLocation()); 1327 car.setFinalDestinationTrack(track); 1328 // test to see if destination is reachable by this train 1329 if (router.setDestination(car, getTrain(), getBuildReport())) { 1330 log.debug("Can route car to destination ({}, {})", track.getLocation().getName(), track.getName()); 1331 } 1332 if (car.getDestination() == null) { 1333 addLine(SEVEN, 1334 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1335 car.setFinalDestination(null); 1336 car.setFinalDestinationTrack(null); 1337 return false; 1338 } 1339 car.updateKernel(); 1340 return true; // done, car has a new final destination 1341 } 1342 1343 /** 1344 * Checks for a car's final destination, and then after checking, tries to 1345 * route the car to that destination. Normal return from this routine is 1346 * false, with the car returning with a set destination. Returns true if car 1347 * has a final destination, but can't be used for this train. 1348 * 1349 * @param car 1350 * @return false if car needs destination processing (normal). 1351 */ 1352 private boolean checkCarForFinalDestination(Car car) { 1353 if (car.getFinalDestination() == null || car.getDestination() != null) { 1354 return false; 1355 } 1356 1357 addLine(FIVE, 1358 Bundle.getMessage("buildCarRoutingBegins", car.toString(), car.getTypeName(), 1359 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackTypeName(), car.getLocationName(), 1360 car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1361 1362 // no local moves for this train? 1363 if (!getTrain().isLocalSwitcher() && 1364 !getTrain().isAllowLocalMovesEnabled() && 1365 car.getSplitLocationName().equals(car.getSplitFinalDestinationName()) && 1366 car.getTrack() != getDepartureStagingTrack()) { 1367 addLine(FIVE, 1368 Bundle.getMessage("buildCarHasFinalDestNoMove", car.toString(), car.getLocationName(), 1369 car.getFinalDestinationName(), getTrain().getName())); 1370 addLine(FIVE, BLANK_LINE); 1371 return true; // car has a final destination, but no local moves by 1372 // this train 1373 } 1374 // is the car's destination the terminal and is that allowed? 1375 if (!checkThroughCarsAllowed(car, car.getFinalDestinationName())) { 1376 if (car.getTrack() == getDepartureStagingTrack()) { 1377 addLine(ONE, Bundle.getMessage("buildErrorCarStageDest", car.toString())); 1378 } 1379 return true; // car has a final destination, but through traffic not 1380 // allowed by this train 1381 } 1382 // does the car have a final destination track that is willing to 1383 // service the car? 1384 // note the default mode for all track types is MATCH 1385 if (car.getFinalDestinationTrack() != null && car.getFinalDestinationTrack().getScheduleMode() == Track.MATCH) { 1386 String status = car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack()); 1387 // keep going if the only issue was track length and the track 1388 // accepts the car's load 1389 if (!status.equals(Track.OKAY) && 1390 !status.startsWith(Track.LENGTH) && 1391 !(status.contains(Track.CUSTOM) && status.contains(Track.LOAD))) { 1392 addLine(SEVEN, 1393 Bundle.getMessage("buildNoDestTrackNewLoad", 1394 StringUtils.capitalize(car.getFinalDestinationTrack().getTrackTypeName()), 1395 car.getFinalDestination().getName(), car.getFinalDestinationTrack().getName(), 1396 car.toString(), car.getLoadType().toLowerCase(), car.getLoadName(), status)); 1397 // is this car or kernel being sent to a track that is too 1398 // short? 1399 if (status.startsWith(Track.CAPACITY)) { 1400 // track is too short for this car or kernel 1401 addLine(SEVEN, 1402 Bundle.getMessage("buildTrackTooShort", car.getFinalDestination().getName(), 1403 car.getFinalDestinationTrack().getName(), car.toString())); 1404 } 1405 _warnings++; 1406 addLine(SEVEN, 1407 Bundle.getMessage("buildWarningRemovingFinalDest", car.getFinalDestination().getName(), 1408 car.getFinalDestinationTrack().getName(), car.toString())); 1409 car.setFinalDestination(null); 1410 car.setFinalDestinationTrack(null); 1411 return false; // car no longer has a final destination 1412 } 1413 } 1414 1415 // now try and route the car 1416 if (!router.setDestination(car, getTrain(), getBuildReport())) { 1417 addLine(SEVEN, 1418 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1419 // don't move car if routing issue was track space but not departing 1420 // staging 1421 if ((!router.getStatus().startsWith(Track.LENGTH) && 1422 !getTrain().isServiceAllCarsWithFinalDestinationsEnabled()) || 1423 (car.getTrack() == getDepartureStagingTrack())) { 1424 // add car to unable to route list 1425 if (!_notRoutable.contains(car)) { 1426 _notRoutable.add(car); 1427 } 1428 addLine(FIVE, BLANK_LINE); 1429 addLine(FIVE, 1430 Bundle.getMessage("buildWarningCarNotRoutable", car.toString(), car.getLocationName(), 1431 car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1432 addLine(FIVE, BLANK_LINE); 1433 return false; // move this car, routing failed! 1434 } 1435 } else { 1436 if (car.getDestination() != null) { 1437 return false; // routing successful process this car, normal 1438 // exit from this routine 1439 } 1440 if (car.getTrack() == getDepartureStagingTrack()) { 1441 log.debug("Car ({}) departing staging with final destination ({}) and no destination", 1442 car.toString(), car.getFinalDestinationName()); 1443 return false; // try and move this car out of staging 1444 } 1445 } 1446 addLine(FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1447 addLine(FIVE, BLANK_LINE); 1448 return true; 1449 } 1450 1451 /** 1452 * Checks to see if car has a destination and tries to add car to train. 1453 * Will find a track for the car if needed. Returns false if car doesn't 1454 * have a destination. 1455 * 1456 * @param rl the car's route location 1457 * @param routeIndex where in the route to start search 1458 * @return true if car has a destination. Need to check if car given a train 1459 * assignment. 1460 * @throws BuildFailedException if destination was staging and can't place 1461 * car there 1462 */ 1463 private boolean checkCarForDestination(Car car, RouteLocation rl, int routeIndex) throws BuildFailedException { 1464 if (car.getDestination() == null) { 1465 return false; // the only false return 1466 } 1467 addLine(SEVEN, Bundle.getMessage("buildCarHasAssignedDest", car.toString(), car.getLoadName(), 1468 car.getDestinationName(), car.getDestinationTrackName(), car.getFinalDestinationName(), 1469 car.getFinalDestinationTrackName())); 1470 RouteLocation rld = getTrain().getRoute().getLastLocationByName(car.getDestinationName()); 1471 if (rld == null) { 1472 // code check, router doesn't set a car's destination if not carried 1473 // by train being built. Car has a destination that isn't serviced 1474 // by this train. Find buildExcludeCarDestNotPartRoute in 1475 // loadRemoveAndListCars() 1476 throw new BuildFailedException(Bundle.getMessage("buildExcludeCarDestNotPartRoute", car.toString(), 1477 car.getDestinationName(), car.getDestinationTrackName(), getTrain().getRoute().getName())); 1478 } 1479 // now go through the route and try and find a location with 1480 // the correct destination name 1481 for (int k = routeIndex; k < getRouteList().size(); k++) { 1482 rld = getRouteList().get(k); 1483 // if car can be picked up later at same location, skip 1484 if (checkForLaterPickUp(car, rl, rld)) { 1485 addLine(SEVEN, BLANK_LINE); 1486 return true; 1487 } 1488 if (!rld.getName().equals(car.getDestinationName())) { 1489 continue; 1490 } 1491 // is the car's destination the terminal and is that allowed? 1492 if (!checkThroughCarsAllowed(car, car.getDestinationName())) { 1493 return true; 1494 } 1495 log.debug("Car ({}) found a destination in train's route", car.toString()); 1496 // are drops allows at this location? 1497 if (!rld.isDropAllowed() && !car.isLocalMove()) { 1498 addLine(FIVE, Bundle.getMessage("buildRouteNoDropLocation", getTrain().getRoute().getName(), 1499 rld.getId(), rld.getName())); 1500 continue; 1501 } 1502 // are local moves allows at this location? 1503 if (!rld.isLocalMovesAllowed() && car.isLocalMove()) { 1504 addLine(FIVE, Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(), 1505 rld.getId(), rld.getName(), car.toString())); 1506 continue; 1507 } 1508 if (getTrain().isLocationSkipped(rld)) { 1509 addLine(FIVE, 1510 Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), getTrain().getName())); 1511 continue; 1512 } 1513 // any moves left at this location? 1514 if (rld.getCarMoves() >= rld.getMaxCarMoves()) { 1515 addLine(FIVE, 1516 Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(), 1517 getTrain().getRoute().getName(), rld.getId(), rld.getName())); 1518 continue; 1519 } 1520 // is the train length okay? 1521 if (!checkTrainLength(car, rl, rld)) { 1522 continue; 1523 } 1524 // check for valid destination track 1525 if (car.getDestinationTrack() == null) { 1526 addLine(FIVE, Bundle.getMessage("buildCarDoesNotHaveDest", car.toString())); 1527 // is car going into staging? 1528 if (rld == getTrain().getTrainTerminatesRouteLocation() && getTerminateStagingTrack() != null) { 1529 String status = car.checkDestination(car.getDestination(), getTerminateStagingTrack()); 1530 if (status.equals(Track.OKAY)) { 1531 addLine(FIVE, Bundle.getMessage("buildCarAssignedToStaging", car.toString(), 1532 getTerminateStagingTrack().getName())); 1533 addCarToTrain(car, rl, rld, getTerminateStagingTrack()); 1534 return true; 1535 } else { 1536 addLine(SEVEN, 1537 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 1538 getTerminateStagingTrack().getTrackTypeName(), 1539 getTerminateStagingTrack().getLocation().getName(), 1540 getTerminateStagingTrack().getName(), 1541 status)); 1542 continue; 1543 } 1544 } else { 1545 // no staging at this location, now find a destination track 1546 // for this car 1547 List<Track> tracks = getTracksAtDestination(car, rld); 1548 if (tracks.size() > 0) { 1549 if (tracks.get(1) != null) { 1550 car.setFinalDestination(car.getDestination()); 1551 car.setFinalDestinationTrack(tracks.get(1)); 1552 tracks.get(1).bumpMoves(); 1553 } 1554 addLine(FIVE, 1555 Bundle.getMessage("buildCarCanDropMoves", car.toString(), 1556 tracks.get(0).getTrackTypeName(), 1557 tracks.get(0).getLocation().getName(), tracks.get(0).getName(), 1558 rld.getCarMoves(), rld.getMaxCarMoves())); 1559 addCarToTrain(car, rl, rld, tracks.get(0)); 1560 return true; 1561 } 1562 } 1563 } else { 1564 log.debug("Car ({}) has a destination track ({})", car.toString(), car.getDestinationTrack().getName()); 1565 // going into the correct staging track? 1566 if (rld.equals(getTrain().getTrainTerminatesRouteLocation()) && 1567 getTerminateStagingTrack() != null && 1568 getTerminateStagingTrack() != car.getDestinationTrack()) { 1569 // car going to wrong track in staging, change track 1570 addLine(SEVEN, Bundle.getMessage("buildCarDestinationStaging", car.toString(), 1571 car.getDestinationName(), car.getDestinationTrackName())); 1572 car.setDestination(getTerminateStagingTrack().getLocation(), getTerminateStagingTrack()); 1573 } 1574 if (!rld.equals(getTrain().getTrainTerminatesRouteLocation()) || 1575 getTerminateStagingTrack() == null || 1576 getTerminateStagingTrack() == car.getDestinationTrack()) { 1577 // is train direction correct? and drop to interchange or 1578 // spur? 1579 if (checkDropTrainDirection(car, rld, car.getDestinationTrack()) && 1580 checkTrainCanDrop(car, car.getDestinationTrack())) { 1581 String status = car.checkDestination(car.getDestination(), car.getDestinationTrack()); 1582 if (status.equals(Track.OKAY) && 1583 (status = checkReserved(getTrain(), rld, car, car.getDestinationTrack(), true)) 1584 .equals(Track.OKAY)) { 1585 Track destTrack = car.getDestinationTrack(); 1586 addCarToTrain(car, rl, rld, destTrack); 1587 return true; 1588 } 1589 if (status.equals(TIMING) && checkForAlternate(car, car.getDestinationTrack())) { 1590 // send car to alternate track) { 1591 car.setFinalDestination(car.getDestination()); 1592 car.setFinalDestinationTrack(car.getDestinationTrack()); 1593 addCarToTrain(car, rl, rld, car.getDestinationTrack().getAlternateTrack()); 1594 return true; 1595 } 1596 addLine(SEVEN, 1597 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 1598 car.getDestinationTrack().getTrackTypeName(), 1599 car.getDestinationTrack().getLocation().getName(), 1600 car.getDestinationTrackName(), status)); 1601 1602 } 1603 } else { 1604 // code check 1605 throw new BuildFailedException(Bundle.getMessage("buildCarDestinationStaging", car.toString(), 1606 car.getDestinationName(), car.getDestinationTrackName())); 1607 } 1608 } 1609 addLine(FIVE, 1610 Bundle.getMessage("buildCanNotDropCar", car.toString(), car.getDestinationName(), rld.getId())); 1611 if (car.getDestinationTrack() == null) { 1612 log.debug("Could not find a destination track for location ({})", car.getDestinationName()); 1613 } 1614 } 1615 log.debug("car ({}) not added to train", car.toString()); 1616 addLine(FIVE, 1617 Bundle.getMessage("buildDestinationNotReachable", car.getDestinationName(), rl.getName(), rl.getId())); 1618 // remove destination and revert to final destination 1619 if (car.getDestinationTrack() != null) { 1620 // going to remove this destination from car 1621 car.getDestinationTrack().setMoves(car.getDestinationTrack().getMoves() - 1); 1622 Track destTrack = car.getDestinationTrack(); 1623 // TODO should we leave the car's destination? The spur expects this 1624 // car! 1625 if (destTrack.getSchedule() != null && destTrack.getScheduleMode() == Track.SEQUENTIAL) { 1626 addLine(SEVEN, Bundle.getMessage("buildPickupCanceled", 1627 destTrack.getLocation().getName(), destTrack.getName())); 1628 } 1629 } 1630 car.setFinalDestination(car.getPreviousFinalDestination()); 1631 car.setFinalDestinationTrack(car.getPreviousFinalDestinationTrack()); 1632 car.setDestination(null, null); 1633 car.updateKernel(); 1634 1635 addLine(FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1636 addLine(FIVE, BLANK_LINE); 1637 return true; // car no longer has a destination, but it had one. 1638 } 1639 1640 /** 1641 * Find a destination and track for a car at a route location. 1642 * 1643 * @param car the car! 1644 * @param rl The car's route location 1645 * @param rld The car's route destination 1646 * @return true if successful. 1647 * @throws BuildFailedException if code check fails 1648 */ 1649 private boolean findDestinationAndTrack(Car car, RouteLocation rl, RouteLocation rld) throws BuildFailedException { 1650 int index = getRouteList().indexOf(rld); 1651 if (getTrain().isLocalSwitcher()) { 1652 return findDestinationAndTrack(car, rl, index, index + 1); 1653 } 1654 return findDestinationAndTrack(car, rl, index - 1, index + 1); 1655 } 1656 1657 /** 1658 * Find a destination and track for a car, and add the car to the train. 1659 * 1660 * @param car The car that is looking for a destination and 1661 * destination track. 1662 * @param rl The route location for this car. 1663 * @param routeIndex Where in the train's route to begin a search for a 1664 * destination for this car. 1665 * @param routeEnd Where to stop looking for a destination. 1666 * @return true if successful, car has destination, track and a train. 1667 * @throws BuildFailedException if code check fails 1668 */ 1669 private boolean findDestinationAndTrack(Car car, RouteLocation rl, int routeIndex, int routeEnd) 1670 throws BuildFailedException { 1671 if (routeIndex + 1 == routeEnd) { 1672 log.debug("Car ({}) is at the last location in the train's route", car.toString()); 1673 } 1674 addLine(FIVE, 1675 Bundle.getMessage("buildFindDestinationForCar", car.toString(), car.getTypeName(), 1676 car.getTypeExtensions(), car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackTypeName(), 1677 car.getLocationName(), car.getTrackName())); 1678 if (car.getKernel() != null) { 1679 addLine(SEVEN, Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 1680 car.getKernel().getSize(), car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 1681 } 1682 1683 // normally start looking after car's route location 1684 int start = routeIndex; 1685 // the route location destination being checked for the car 1686 RouteLocation rld = null; 1687 // holds the best route location destination for the car 1688 RouteLocation rldSave = null; 1689 // holds the best track at destination for the car 1690 Track trackSave = null; 1691 // used when a spur has an alternate track and no schedule 1692 Track finalDestinationTrackSave = null; 1693 // true when car can be picked up from two or more locations in the 1694 // route 1695 boolean multiplePickup = false; 1696 1697 if (!getTrain().isLocalSwitcher()) { 1698 start++; // begin looking for tracks at the next location 1699 } 1700 // all pick ups to terminal? 1701 if (getTrain().isSendCarsToTerminalEnabled() && 1702 !rl.getSplitName().equals(getDepartureLocation().getSplitName()) && 1703 routeEnd == getRouteList().size()) { 1704 addLine(FIVE, Bundle.getMessage("buildSendToTerminal", getTerminateLocation().getName())); 1705 // user could have specified several terminal locations with the 1706 // "same" name 1707 start = routeEnd - 1; 1708 while (start > routeIndex) { 1709 if (!getRouteList().get(start - 1).getSplitName() 1710 .equals(getTerminateLocation().getSplitName())) { 1711 break; 1712 } 1713 start--; 1714 } 1715 } 1716 // now search for a destination for this car 1717 for (int k = start; k < routeEnd; k++) { 1718 rld = getRouteList().get(k); 1719 // if car can be picked up later at same location, set flag 1720 if (checkForLaterPickUp(car, rl, rld)) { 1721 multiplePickup = true; 1722 } 1723 if (rld.isDropAllowed() || car.hasFred() || car.isCaboose()) { 1724 addLine(FIVE, Bundle.getMessage("buildSearchingLocation", rld.getName(), rld.getId())); 1725 } else { 1726 addLine(FIVE, Bundle.getMessage("buildRouteNoDropLocation", getTrain().getRoute().getName(), 1727 rld.getId(), rld.getName())); 1728 continue; 1729 } 1730 if (getTrain().isLocationSkipped(rld)) { 1731 addLine(FIVE, 1732 Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), getTrain().getName())); 1733 continue; 1734 } 1735 // any moves left at this location? 1736 if (rld.getCarMoves() >= rld.getMaxCarMoves()) { 1737 addLine(FIVE, 1738 Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(), 1739 getTrain().getRoute().getName(), rld.getId(), rld.getName())); 1740 continue; 1741 } 1742 // get the destination 1743 Location testDestination = rld.getLocation(); 1744 // code check, all locations in the route have been already checked 1745 if (testDestination == null) { 1746 throw new BuildFailedException( 1747 Bundle.getMessage("buildErrorRouteLoc", getTrain().getRoute().getName(), rld.getName())); 1748 } 1749 // don't move car to same location unless the train is a switcher 1750 // (local moves) or is passenger, caboose or car with FRED 1751 if (rl.getSplitName().equals(rld.getSplitName()) && 1752 !getTrain().isLocalSwitcher() && 1753 !car.isPassenger() && 1754 !car.isCaboose() && 1755 !car.hasFred()) { 1756 // allow cars to return to the same staging location if no other 1757 // options (tracks) are available 1758 if ((getTrain().isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) && 1759 testDestination.isStaging() && 1760 trackSave == null) { 1761 addLine(SEVEN, 1762 Bundle.getMessage("buildReturnCarToStaging", car.toString(), rld.getName())); 1763 } else { 1764 addLine(SEVEN, 1765 Bundle.getMessage("buildCarLocEqualDestination", car.toString(), rld.getName())); 1766 continue; 1767 } 1768 } 1769 // don't allow local moves for a car with a final destination 1770 if (rl.getSplitName().equals(rld.getSplitName()) && 1771 car.getFinalDestination() != null && 1772 !car.isPassenger() && 1773 !car.isCaboose() && 1774 !car.hasFred()) { 1775 if (!rld.isLocalMovesAllowed()) { 1776 addLine(FIVE, 1777 Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(), 1778 rld.getId(), rld.getName(), car.toString())); 1779 continue; 1780 } 1781 if (!rl.isLocalMovesAllowed()) { 1782 addLine(FIVE, 1783 Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(), 1784 rl.getId(), rl.getName(), car.toString())); 1785 continue; 1786 } 1787 } 1788 1789 // check to see if departure track has any restrictions 1790 if (!car.getTrack().isDestinationAccepted(testDestination)) { 1791 addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", testDestination.getName(), 1792 car.getTrackName())); 1793 continue; 1794 } 1795 1796 if (!testDestination.acceptsTypeName(car.getTypeName())) { 1797 addLine(SEVEN, Bundle.getMessage("buildCanNotDropLocation", car.toString(), 1798 car.getTypeName(), testDestination.getName())); 1799 continue; 1800 } 1801 // can this location service this train's direction 1802 if (!checkDropTrainDirection(rld)) { 1803 continue; 1804 } 1805 // is the train length okay? 1806 if (!checkTrainLength(car, rl, rld)) { 1807 break; // no, done with this car 1808 } 1809 // is the car's destination the terminal and is that allowed? 1810 if (!checkThroughCarsAllowed(car, rld.getName())) { 1811 continue; // not allowed 1812 } 1813 1814 Track trackTemp = null; 1815 // used when alternate track selected 1816 Track finalDestinationTrackTemp = null; 1817 1818 // is there a track assigned for staging cars? 1819 if (rld == getTrain().getTrainTerminatesRouteLocation() && getTerminateStagingTrack() != null) { 1820 trackTemp = tryStaging(car, rldSave); 1821 if (trackTemp == null) { 1822 continue; // no 1823 } 1824 } else { 1825 // not staging, start track search 1826 List<Track> tracks = getTracksAtDestination(car, rld); 1827 if (tracks.size() > 0) { 1828 trackTemp = tracks.get(0); 1829 finalDestinationTrackTemp = tracks.get(1); 1830 } 1831 } 1832 // did we find a new destination? 1833 if (trackTemp == null) { 1834 addLine(FIVE, 1835 Bundle.getMessage("buildCouldNotFindDestForCar", car.toString(), rld.getName())); 1836 } else { 1837 addLine(FIVE, 1838 Bundle.getMessage("buildCarCanDropMoves", car.toString(), trackTemp.getTrackTypeName(), 1839 trackTemp.getLocation().getName(), trackTemp.getName(), +rld.getCarMoves(), 1840 rld.getMaxCarMoves())); 1841 if (multiplePickup) { 1842 if (rldSave != null) { 1843 addLine(FIVE, 1844 Bundle.getMessage("buildTrackServicedLater", car.getLocationName(), 1845 trackTemp.getTrackTypeName(), trackTemp.getLocation().getName(), 1846 trackTemp.getName(), car.getLocationName())); 1847 } else { 1848 addLine(FIVE, 1849 Bundle.getMessage("buildCarHasSecond", car.toString(), car.getLocationName())); 1850 trackSave = null; 1851 } 1852 break; // done 1853 } 1854 // if there's more than one available destination use the lowest 1855 // ratio 1856 if (rldSave != null) { 1857 // check for an earlier drop in the route 1858 rld = checkForEarlierDrop(car, trackTemp, rld, start, routeEnd); 1859 double saveCarMoves = rldSave.getCarMoves(); 1860 double saveRatio = saveCarMoves / rldSave.getMaxCarMoves(); 1861 double nextCarMoves = rld.getCarMoves(); 1862 double nextRatio = nextCarMoves / rld.getMaxCarMoves(); 1863 1864 // bias cars to the terminal 1865 if (rld == getTrain().getTrainTerminatesRouteLocation()) { 1866 nextRatio = nextRatio * nextRatio; 1867 log.debug("Location ({}) is terminate location, adjusted nextRatio {}", rld.getName(), 1868 Double.toString(nextRatio)); 1869 1870 // bias cars with default loads to a track with a 1871 // schedule 1872 } else if (!trackTemp.getScheduleId().equals(Track.NONE)) { 1873 nextRatio = nextRatio * nextRatio; 1874 log.debug("Track ({}) has schedule ({}), adjusted nextRatio {}", trackTemp.getName(), 1875 trackTemp.getScheduleName(), Double.toString(nextRatio)); 1876 } 1877 // bias cars with default loads to saved track with a 1878 // schedule 1879 if (trackSave != null && !trackSave.getScheduleId().equals(Track.NONE)) { 1880 saveRatio = saveRatio * saveRatio; 1881 log.debug("Saved track ({}) has schedule ({}), adjusted nextRatio {}", trackSave.getName(), 1882 trackSave.getScheduleName(), Double.toString(saveRatio)); 1883 } 1884 log.debug("Saved {} = {}, {} = {}", rldSave.getName(), Double.toString(saveRatio), rld.getName(), 1885 Double.toString(nextRatio)); 1886 if (saveRatio < nextRatio) { 1887 // the saved is better than the last found 1888 rld = rldSave; 1889 trackTemp = trackSave; 1890 finalDestinationTrackTemp = finalDestinationTrackSave; 1891 } 1892 } 1893 // every time through, save the best route destination, and 1894 // track 1895 rldSave = rld; 1896 trackSave = trackTemp; 1897 finalDestinationTrackSave = finalDestinationTrackTemp; 1898 } 1899 } 1900 // did we find a destination? 1901 if (trackSave != null && rldSave != null) { 1902 // determine if local staging move is allowed (leaves car in staging) 1903 if ((getTrain().isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) && 1904 rl.isDropAllowed() && 1905 rl.getLocation().isStaging() && 1906 trackSave.isStaging() && 1907 rl.getLocation() == rldSave.getLocation() && 1908 !getTrain().isLocalSwitcher() && 1909 !car.isPassenger() && 1910 !car.isCaboose() && 1911 !car.hasFred()) { 1912 addLine(SEVEN, 1913 Bundle.getMessage("buildLeaveCarInStaging", car.toString(), car.getLocationName(), 1914 car.getTrackName())); 1915 rldSave = rl; // make local move 1916 } else if (trackSave.isSpur()) { 1917 car.setScheduleItemId(trackSave.getScheduleItemId()); 1918 trackSave.bumpSchedule(); 1919 log.debug("Sending car to spur ({}, {}) with car schedule id ({}))", trackSave.getLocation().getName(), 1920 trackSave.getName(), car.getScheduleItemId()); 1921 } else { 1922 car.setScheduleItemId(Car.NONE); 1923 } 1924 if (finalDestinationTrackSave != null) { 1925 car.setFinalDestination(finalDestinationTrackSave.getLocation()); 1926 car.setFinalDestinationTrack(finalDestinationTrackSave); 1927 if (trackSave.isAlternate()) { 1928 finalDestinationTrackSave.bumpMoves(); // bump move count 1929 } 1930 } 1931 addCarToTrain(car, rl, rldSave, trackSave); 1932 return true; 1933 } 1934 addLine(FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1935 addLine(FIVE, BLANK_LINE); 1936 return false; // no build errors, but car not given destination 1937 } 1938 1939 /** 1940 * Add car to train, and adjust train length and weight 1941 * 1942 * @param car the car being added to the train 1943 * @param rl the departure route location for this car 1944 * @param rld the destination route location for this car 1945 * @param track the destination track for this car 1946 */ 1947 protected void addCarToTrain(Car car, RouteLocation rl, RouteLocation rld, Track track) { 1948 car = checkQuickServiceArrival(car, rld, track); 1949 addLine(THREE, 1950 Bundle.getMessage("buildCarAssignedDest", car.toString(), rld.getName(), track.getName())); 1951 car.setDestination(track.getLocation(), track, Car.FORCE); 1952 int length = car.getTotalLength(); 1953 int weightTons = car.getAdjustedWeightTons(); 1954 // car could be part of a kernel 1955 if (car.getKernel() != null) { 1956 length = car.getKernel().getTotalLength(); // includes couplers 1957 weightTons = car.getKernel().getAdjustedWeightTons(); 1958 List<Car> kCars = car.getKernel().getCars(); 1959 addLine(THREE, 1960 Bundle.getMessage("buildCarPartOfKernel", car.toString(), car.getKernelName(), kCars.size(), 1961 car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 1962 for (Car kCar : kCars) { 1963 if (kCar != car) { 1964 addLine(THREE, Bundle.getMessage("buildCarKernelAssignedDest", kCar.toString(), 1965 kCar.getKernelName(), rld.getName(), track.getName())); 1966 kCar.setTrain(getTrain()); 1967 kCar.setRouteLocation(rl); 1968 kCar.setRouteDestination(rld); 1969 kCar.setDestination(track.getLocation(), track, Car.FORCE); // force destination 1970 // save final destination and track values in case of train reset 1971 kCar.setPreviousFinalDestination(car.getPreviousFinalDestination()); 1972 kCar.setPreviousFinalDestinationTrack(car.getPreviousFinalDestinationTrack()); 1973 } 1974 } 1975 car.updateKernel(); 1976 } 1977 // warn if car's load wasn't generated out of staging 1978 if (!getTrain().isLoadNameAccepted(car.getLoadName(), car.getTypeName())) { 1979 _warnings++; 1980 addLine(SEVEN, 1981 Bundle.getMessage("buildWarnCarDepartStaging", car.toString(), car.getLoadName())); 1982 } 1983 addLine(THREE, BLANK_LINE); 1984 _numberCars++; // bump number of cars moved by this train 1985 _completedMoves++; // bump number of car pick up moves for the location 1986 _reqNumOfMoves--; // decrement number of moves left for the location 1987 1988 remove(car); // remove car from list 1989 1990 rl.setCarMoves(rl.getCarMoves() + 1); 1991 if (rl != rld) { 1992 rld.setCarMoves(rld.getCarMoves() + 1); 1993 } 1994 // now adjust train length and weight for each location that car is in 1995 // the train 1996 finishAddRsToTrain(car, rl, rld, length, weightTons); 1997 } 1998 1999 /** 2000 * Checks to see if cars that are already in the train can be redirected 2001 * from the alternate track to the spur that really wants the car. Fixes the 2002 * issue of having cars placed at the alternate when the spur's cars get 2003 * pulled by this train, but cars were sent to the alternate because the 2004 * spur was full at the time it was tested. 2005 * 2006 * @return true if one or more cars were redirected 2007 * @throws BuildFailedException if coding issue 2008 */ 2009 protected boolean redirectCarsFromAlternateTrack() throws BuildFailedException { 2010 // code check, should be aggressive 2011 if (!Setup.isBuildAggressive()) { 2012 throw new BuildFailedException("ERROR coding issue, should be using aggressive mode"); 2013 } 2014 boolean redirected = false; 2015 List<Car> cars = carManager.getByTrainList(getTrain()); 2016 for (Car car : cars) { 2017 // does the car have a final destination and the destination is this 2018 // one? 2019 if (car.getFinalDestination() == null || 2020 car.getFinalDestinationTrack() == null || 2021 !car.getFinalDestinationName().equals(car.getDestinationName())) { 2022 continue; 2023 } 2024 Track alternate = car.getFinalDestinationTrack().getAlternateTrack(); 2025 if (alternate == null || car.getDestinationTrack() != alternate) { 2026 continue; 2027 } 2028 // is the car in a kernel? 2029 if (car.getKernel() != null && !car.isLead()) { 2030 continue; 2031 } 2032 log.debug("Car ({}) alternate track ({}) has final destination track ({}) location ({})", car.toString(), 2033 car.getDestinationTrackName(), car.getFinalDestinationTrackName(), car.getDestinationName()); // NOI18N 2034 if ((alternate.isYard() || alternate.isInterchange()) && 2035 car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack()) 2036 .equals(Track.OKAY) && 2037 checkReserved(getTrain(), car.getRouteDestination(), car, car.getFinalDestinationTrack(), false) 2038 .equals(Track.OKAY) && 2039 checkDropTrainDirection(car, car.getRouteDestination(), car.getFinalDestinationTrack()) && 2040 checkTrainCanDrop(car, car.getFinalDestinationTrack())) { 2041 log.debug("Car ({}) alternate track ({}) can be redirected to final destination track ({})", 2042 car.toString(), car.getDestinationTrackName(), car.getFinalDestinationTrackName()); 2043 if (car.getKernel() != null) { 2044 for (Car k : car.getKernel().getCars()) { 2045 if (k.isLead()) { 2046 continue; 2047 } 2048 addLine(FIVE, 2049 Bundle.getMessage("buildRedirectFromAlternate", car.getFinalDestinationName(), 2050 car.getFinalDestinationTrackName(), k.toString(), 2051 car.getDestinationTrackName())); 2052 // force car to track 2053 k.setDestination(car.getFinalDestination(), car.getFinalDestinationTrack(), Car.FORCE); 2054 } 2055 } 2056 addLine(FIVE, 2057 Bundle.getMessage("buildRedirectFromAlternate", car.getFinalDestinationName(), 2058 car.getFinalDestinationTrackName(), 2059 car.toString(), car.getDestinationTrackName())); 2060 car.setDestination(car.getFinalDestination(), car.getFinalDestinationTrack(), Car.FORCE); 2061 // check for quick service 2062 checkQuickServiceRedirected(car); 2063 redirected = true; 2064 } 2065 } 2066 return redirected; 2067 } 2068 2069 /* 2070 * Checks to see if the redirected car is going to a track with quick 2071 * service. The car in this case has already been assigned to the train. 2072 * This routine will create clones if needed, and allow the car to be 2073 * reassigned to the same train. Only lead car in a kernel is allowed. 2074 */ 2075 private void checkQuickServiceRedirected(Car car) { 2076 if (car.getDestinationTrack().isQuickServiceEnabled()) { 2077 RouteLocation rl = car.getRouteLocation(); 2078 RouteLocation rld = car.getRouteDestination(); 2079 Track track = car.getDestinationTrack(); 2080 // remove cars from train 2081 if (car.getKernel() != null) { 2082 for (Car kar : car.getKernel().getCars()) 2083 kar.reset(); 2084 } else { 2085 car.reset(); 2086 } 2087 getCarList().add(0, car); 2088 addCarToTrain(car, rl, rld, track); 2089 } 2090 } 2091 2092 /** 2093 * Checks to see if track is requesting a quick service. Since it isn't 2094 * possible for a car to be pulled and set out twice, this code creates a 2095 * "clone" car to create the requested Manifest. A car could have multiple 2096 * clones, therefore each clone has a creation order number appended to its 2097 * road number. Clones are used to restore a car's location and load in the 2098 * case of reset. 2099 * 2100 * @param car the car possibly needing quick service 2101 * @param track the destination track 2102 * @return the car if not quick service, or a clone if quick service 2103 */ 2104 private Car checkQuickServiceArrival(Car car, RouteLocation rld, Track track) { 2105 if (!track.isQuickServiceEnabled()) { 2106 if (Setup.isBuildOnTime()) { 2107 addLine(THREE, 2108 Bundle.getMessage("buildTrackNotQuickService", StringUtils.capitalize(track.getTrackTypeName()), 2109 track.getLocation().getName(), track.getName(), car.toString())); 2110 // warn if departing staging that is quick serviced enabled 2111 if (car.getTrack().isStaging() && car.getTrack().isQuickServiceEnabled()) { 2112 _warnings++; 2113 addLine(THREE, 2114 Bundle.getMessage("buildWarningQuickService", car.toString(), 2115 car.getTrack().getTrackTypeName(), 2116 car.getTrack().getLocation().getName(), car.getTrack().getName(), 2117 getTrain().getName(), StringUtils.capitalize(car.getTrack().getTrackTypeName()))); 2118 } 2119 } 2120 return car; 2121 } 2122 // quick service enabled, create clones 2123 Car cloneCar = carManager.createClone(car, track, getTrain(), getStartTime()); 2124 addLine(FIVE, 2125 Bundle.getMessage("buildTrackQuickService", StringUtils.capitalize(track.getTrackTypeName()), 2126 track.getLocation().getName(), track.getName(), cloneCar.toString(), car.toString())); 2127 // for timing, use arrival times for the train that is building 2128 // other trains will use their departure time, loaded when creating the Manifest 2129 String expectedArrivalTime = getTrain().getExpectedArrivalTime(rld, true); 2130 cloneCar.setSetoutTime(expectedArrivalTime); 2131 track.scheduleNext(car); // apply schedule to car 2132 car.loadNext(track); // update load, wait count 2133 if (car.getWait() > 0) { 2134 remove(car); // available for next train 2135 addLine(FIVE, Bundle.getMessage("buildExcludeCarWait", car.toString(), 2136 car.getTypeName(), car.getLocationName(), car.getTrackName(), car.getWait())); 2137 car.setWait(car.getWait() - 1); 2138 car.updateLoad(track); 2139 } 2140 // remember where in the route the car was delivered 2141 car.setRouteDestination(rld); 2142 car.updateKernel(); 2143 return cloneCar; // return clone 2144 } 2145 2146 private static final Logger log = LoggerFactory.getLogger(TrainBuilderCars.class); 2147}