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 Car car = getCarList().get(_carIndex); 476 // second pass deals with cars that have a final destination equal 477 // to this location. 478 // therefore a local move can be made. This causes "off spots" to be 479 // serviced. 480 if (isSecondPass && !car.getFinalDestinationName().equals(rl.getName())) { 481 continue; 482 } 483 // find a car at this location 484 if (!car.getLocationName().equals(rl.getName())) { 485 continue; 486 } 487 foundCar = true; 488 // add message that we're on the second pass for this location 489 if (isSecondPass && messageFlag) { 490 messageFlag = false; 491 addLine(FIVE, Bundle.getMessage("buildExtraPassForLocation", rl.getName())); 492 addLine(SEVEN, BLANK_LINE); 493 } 494 // are pick ups allowed? 495 if (!rl.isPickUpAllowed() && 496 !car.isLocalMove() && 497 !car.getSplitFinalDestinationName().equals(rl.getSplitName())) { 498 addLine(FIVE, 499 Bundle.getMessage("buildNoPickUpCar", car.toString(), rl.getLocation().getName(), rl.getId())); 500 addLine(FIVE, BLANK_LINE); 501 continue; 502 } 503 if (!rl.isLocalMovesAllowed() && car.getSplitFinalDestinationName().equals(rl.getSplitName())) { 504 addLine(FIVE, 505 Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(), 506 rl.getId(), rl.getName(), car.toString())); 507 } 508 // can this car be pulled from an interchange or spur? 509 if (!checkPickupInterchangeOrSpur(car)) { 510 remove(car); 511 addLine(FIVE, BLANK_LINE); 512 continue; // no 513 } 514 // can this car be picked up? 515 if (!checkPickUpTrainDirection(car, rl)) { 516 addLine(FIVE, BLANK_LINE); 517 continue; // no 518 } 519 // do alternate track moves on the second pass (makes FIFO / LIFO work correctly) 520 if (car.getTrack().isAlternate()) { 521 addLine(SEVEN, Bundle.getMessage("buildCarOnAlternateTrack", car.toString(), 522 car.getTrack().getTrackTypeName(), car.getLocationName(), car.getTrackName())); 523 if (Setup.isBuildAggressive() && !isSecondPass && _completedMoves != 0) { 524 addLine(SEVEN, BLANK_LINE); 525 continue; 526 } 527 } 528 529 showCarServiceOrder(car); // car on FIFO or LIFO track? 530 531 // is car departing staging and generate custom load? 532 if (!generateCarLoadFromStaging(car)) { 533 if (!generateCarLoadStagingToStaging(car) && 534 car.getTrack() == getDepartureStagingTrack() && 535 !getDepartureStagingTrack().isLoadNameAndCarTypeShipped(car.getLoadName(), car.getTypeName())) { 536 // report build failure car departing staging with a 537 // restricted load 538 addLine(ONE, Bundle.getMessage("buildErrorCarStageLoad", car.toString(), 539 car.getLoadName(), getDepartureStagingTrack().getName())); 540 addLine(FIVE, BLANK_LINE); 541 continue; // keep going and see if there are other cars with 542 // issues outs of staging 543 } 544 } 545 // check for quick service track timing 546 if (!checkQuickServiceDeparting(car, rl)) { 547 continue; 548 } 549 // If car been given a home division follow division rules for car 550 // movement. 551 if (!findDestinationsForCarsWithHomeDivision(car)) { 552 addLine(FIVE, 553 Bundle.getMessage("buildNoDestForCar", car.toString())); 554 addLine(FIVE, BLANK_LINE); 555 continue; // hold car at current location 556 } 557 // does car have a custom load without a destination? 558 // if departing staging, a destination for this car is needed, so 559 // keep going 560 if (findFinalDestinationForCarLoad(car) && 561 car.getDestination() == null && 562 car.getTrack() != getDepartureStagingTrack()) { 563 // done with this car, it has a custom load, and there are 564 // spurs/schedules, but no destination found 565 addLine(FIVE, 566 Bundle.getMessage("buildNoDestForCar", car.toString())); 567 addLine(FIVE, BLANK_LINE); 568 continue; 569 } 570 // Check car for final destination, then an assigned destination, if 571 // neither, find a destination for the car 572 if (checkCarForFinalDestination(car)) { 573 log.debug("Car ({}) has a final desination that can't be serviced by train", car.toString()); 574 } else if (checkCarForDestination(car, rl, getRouteList().indexOf(rl))) { 575 // car had a destination, could have been added to the train. 576 log.debug("Car ({}) has desination ({}) using train ({})", car.toString(), car.getDestinationName(), 577 car.getTrainName()); 578 } else { 579 findDestinationAndTrack(car, rl, getRouteList().indexOf(rl), getRouteList().size()); 580 } 581 if (_reqNumOfMoves <= 0) { 582 break; // done 583 } 584 // build failure if car departing staging without a destination and 585 // a train we'll just put out a warning message here so we can find 586 // out how many cars have issues 587 if (car.getTrack() == getDepartureStagingTrack() && 588 (car.getDestination() == null || car.getDestinationTrack() == null || car.getTrain() == null)) { 589 addLine(ONE, Bundle.getMessage("buildWarningCarStageDest", car.toString())); 590 // does the car have a final destination to staging? If so we 591 // need to reset this car 592 if (car.getFinalDestinationTrack() != null && 593 car.getFinalDestinationTrack() == getTerminateStagingTrack()) { 594 addLine(THREE, 595 Bundle.getMessage("buildStagingCarHasFinal", car.toString(), car.getFinalDestinationName(), 596 car.getFinalDestinationTrackName())); 597 car.reset(); 598 } 599 addLine(SEVEN, BLANK_LINE); 600 } 601 } 602 if (!foundCar && !isSecondPass) { 603 addLine(FIVE, Bundle.getMessage("buildNoCarsAtLocation", rl.getName())); 604 addLine(FIVE, BLANK_LINE); 605 } 606 } 607 608 private boolean generateCarLoadFromStaging(Car car) throws BuildFailedException { 609 return generateCarLoadFromStaging(car, null); 610 } 611 612 /** 613 * Used to generate a car's load from staging. Search for a spur with a 614 * schedule and load car if possible. 615 * 616 * @param car the car 617 * @param rld The route location destination for this car. Can be null. 618 * @return true if car given a custom load 619 * @throws BuildFailedException If code check fails 620 */ 621 private boolean generateCarLoadFromStaging(Car car, RouteLocation rld) throws BuildFailedException { 622 // Code Check, car should have a track assignment 623 if (car.getTrack() == null) { 624 throw new BuildFailedException( 625 Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName())); 626 } 627 if (!car.getTrack().isStaging() || 628 (!car.getTrack().isAddCustomLoadsAnySpurEnabled() && !car.getTrack().isAddCustomLoadsEnabled()) || 629 !car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 630 car.getDestination() != null || 631 car.getFinalDestination() != null) { 632 log.debug( 633 "No load generation for car ({}) isAddLoadsAnySpurEnabled: {}, car load ({}) destination ({}) final destination ({})", 634 car.toString(), car.getTrack().isAddCustomLoadsAnySpurEnabled() ? "true" : "false", 635 car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName()); 636 // if car has a destination or final destination add "no load 637 // generated" message to report 638 if (car.getTrack().isStaging() && 639 car.getTrack().isAddCustomLoadsAnySpurEnabled() && 640 car.getLoadName().equals(carLoads.getDefaultEmptyName())) { 641 addLine(FIVE, 642 Bundle.getMessage("buildCarNoLoadGenerated", car.toString(), car.getLoadName(), 643 car.getDestinationName(), car.getFinalDestinationName())); 644 } 645 return false; // no load generated for this car 646 } 647 addLine(FIVE, 648 Bundle.getMessage("buildSearchTrackNewLoad", car.toString(), car.getTypeName(), 649 car.getLoadType().toLowerCase(), car.getLoadName(), car.getLocationName(), car.getTrackName(), 650 rld != null ? rld.getLocation().getName() : "")); 651 // check to see if car type has custom loads 652 if (carLoads.getNames(car.getTypeName()).size() == 2) { 653 addLine(SEVEN, Bundle.getMessage("buildCarNoCustomLoad", car.toString(), car.getTypeName())); 654 return false; 655 } 656 if (car.getKernel() != null) { 657 addLine(SEVEN, 658 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 659 car.getKernel().getSize(), car.getKernel().getTotalLength(), 660 Setup.getLengthUnit().toLowerCase())); 661 } 662 // save the car's load, should be the default empty 663 String oldCarLoad = car.getLoadName(); 664 List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR); 665 log.debug("Found {} spurs", tracks.size()); 666 // show locations not serviced by departure track once 667 List<Location> locationsNotServiced = new ArrayList<>(); 668 for (Track track : tracks) { 669 if (locationsNotServiced.contains(track.getLocation())) { 670 continue; 671 } 672 if (rld != null && track.getLocation() != rld.getLocation()) { 673 locationsNotServiced.add(track.getLocation()); 674 continue; 675 } 676 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 677 addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", 678 track.getLocation().getName(), car.getTrackName())); 679 locationsNotServiced.add(track.getLocation()); 680 continue; 681 } 682 // only use tracks serviced by this train? 683 if (car.getTrack().isAddCustomLoadsEnabled() && 684 !getTrain().getRoute().isLocationNameInRoute(track.getLocation().getName())) { 685 continue; 686 } 687 // only the first match in a schedule is used for a spur 688 ScheduleItem si = getScheduleItem(car, track); 689 if (si == null) { 690 continue; // no match 691 } 692 // need to set car load so testDestination will work properly 693 car.setLoadName(si.getReceiveLoadName()); 694 car.setScheduleItemId(si.getId()); 695 String status = car.checkDestination(track.getLocation(), track); 696 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 697 addLine(SEVEN, 698 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 699 track.getLocation().getName(), track.getName(), car.toString(), 700 Track.LOAD, si.getReceiveLoadName(), 701 status)); 702 continue; 703 } 704 addLine(SEVEN, Bundle.getMessage("buildTrySpurLoad", track.getLocation().getName(), 705 track.getName(), car.getLoadName())); 706 // does the car have a home division? 707 if (car.getDivision() != null) { 708 addLine(SEVEN, 709 Bundle.getMessage("buildCarHasDivisionStaging", car.toString(), car.getTypeName(), 710 car.getLoadType().toLowerCase(), car.getLoadName(), car.getDivisionName(), 711 car.getLocationName(), car.getTrackName(), car.getTrack().getDivisionName())); 712 // load type empty must return to car's home division 713 // or load type load from foreign division must return to car's 714 // home division 715 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) && car.getDivision() != track.getDivision() || 716 car.getLoadType().equals(CarLoad.LOAD_TYPE_LOAD) && 717 car.getTrack().getDivision() != car.getDivision() && 718 car.getDivision() != track.getDivision()) { 719 addLine(SEVEN, 720 Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(), 721 track.getLocation().getName(), track.getName(), track.getDivisionName(), 722 car.toString(), car.getLoadType().toLowerCase(), car.getLoadName())); 723 continue; 724 } 725 } 726 if (!track.isSpaceAvailable(car)) { 727 addLine(SEVEN, 728 Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(), 729 track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(), 730 Setup.getLengthUnit().toLowerCase(), track.getReservationFactor())); 731 continue; 732 } 733 // try routing car 734 car.setFinalDestination(track.getLocation()); 735 car.setFinalDestinationTrack(track); 736 if (router.setDestination(car, getTrain(), getBuildReport()) && car.getDestination() != null) { 737 // return car with this custom load and destination 738 addLine(FIVE, 739 Bundle.getMessage("buildCreateNewLoadForCar", car.toString(), si.getReceiveLoadName(), 740 track.getLocation().getName(), track.getName())); 741 car.setLoadGeneratedFromStaging(true); 742 // is car part of kernel? 743 car.updateKernel(); 744 track.bumpMoves(); 745 track.bumpSchedule(); 746 return true; // done, car now has a custom load 747 } 748 addLine(SEVEN, Bundle.getMessage("buildCanNotRouteCar", car.toString(), 749 si.getReceiveLoadName(), track.getLocation().getName(), track.getName())); 750 addLine(SEVEN, BLANK_LINE); 751 car.setDestination(null, null); 752 car.setFinalDestination(null); 753 car.setFinalDestinationTrack(null); 754 } 755 // restore car's load 756 car.setLoadName(oldCarLoad); 757 car.setScheduleItemId(Car.NONE); 758 addLine(FIVE, Bundle.getMessage("buildUnableNewLoad", car.toString())); 759 return false; // done, no load generated for this car 760 } 761 762 /** 763 * Tries to place a custom load in the car that is departing staging and 764 * attempts to find a destination for the car that is also staging. 765 * 766 * @param car the car 767 * @return True if custom load added to car 768 * @throws BuildFailedException If code check fails 769 */ 770 private boolean generateCarLoadStagingToStaging(Car car) throws BuildFailedException { 771 // Code Check, car should have a track assignment 772 if (car.getTrack() == null) { 773 throw new BuildFailedException( 774 Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName())); 775 } 776 if (!car.getTrack().isStaging() || 777 !car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() || 778 !car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 779 car.getDestination() != null || 780 car.getFinalDestination() != null) { 781 log.debug( 782 "No load generation for car ({}) isAddCustomLoadsAnyStagingTrackEnabled: {}, car load ({}) destination ({}) final destination ({})", 783 car.toString(), car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() ? "true" : "false", 784 car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName()); 785 return false; 786 } 787 // check to see if car type has custom loads 788 if (carLoads.getNames(car.getTypeName()).size() == 2) { 789 return false; 790 } 791 List<Track> tracks = locationManager.getTracks(Track.STAGING); 792 addLine(FIVE, Bundle.getMessage("buildTryStagingToStaging", car.toString(), tracks.size())); 793 if (Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED)) { 794 for (Track track : tracks) { 795 addLine(SEVEN, 796 Bundle.getMessage("buildStagingLocationTrack", track.getLocation().getName(), track.getName())); 797 } 798 } 799 // list of locations that can't be reached by the router 800 List<Location> locationsNotServiced = new ArrayList<>(); 801 if (getTerminateStagingTrack() != null) { 802 addLine(SEVEN, 803 Bundle.getMessage("buildIgnoreStagingFirstPass", 804 getTerminateStagingTrack().getLocation().getName())); 805 locationsNotServiced.add(getTerminateStagingTrack().getLocation()); 806 } 807 while (tracks.size() > 0) { 808 // pick a track randomly 809 int rnd = (int) (Math.random() * tracks.size()); 810 Track track = tracks.get(rnd); 811 tracks.remove(track); 812 log.debug("Try staging track ({}, {})", track.getLocation().getName(), track.getName()); 813 // find a staging track that isn't at the departure 814 if (track.getLocation() == getDepartureLocation()) { 815 log.debug("Can't use departure location ({})", track.getLocation().getName()); 816 continue; 817 } 818 if (!getTrain().isAllowThroughCarsEnabled() && track.getLocation() == getTerminateLocation()) { 819 log.debug("Through cars to location ({}) not allowed", track.getLocation().getName()); 820 continue; 821 } 822 if (locationsNotServiced.contains(track.getLocation())) { 823 log.debug("Location ({}) not reachable", track.getLocation().getName()); 824 continue; 825 } 826 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 827 addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", 828 track.getLocation().getName(), car.getTrackName())); 829 locationsNotServiced.add(track.getLocation()); 830 continue; 831 } 832 // the following method sets the Car load generated from staging 833 // boolean 834 if (generateLoadCarDepartingAndTerminatingIntoStaging(car, track)) { 835 // test to see if destination is reachable by this train 836 if (router.setDestination(car, getTrain(), getBuildReport()) && car.getDestination() != null) { 837 return true; // done, car has a custom load and a final 838 // destination 839 } 840 addLine(SEVEN, Bundle.getMessage("buildStagingTrackNotReachable", 841 track.getLocation().getName(), track.getName(), car.getLoadName())); 842 // return car to original state 843 car.setLoadName(carLoads.getDefaultEmptyName()); 844 car.setLoadGeneratedFromStaging(false); 845 car.setFinalDestination(null); 846 car.updateKernel(); 847 // couldn't route to this staging location 848 locationsNotServiced.add(track.getLocation()); 849 } 850 } 851 // No staging tracks reachable, try the track the train is terminating 852 // to 853 if (getTrain().isAllowThroughCarsEnabled() && 854 getTerminateStagingTrack() != null && 855 car.getTrack().isDestinationAccepted(getTerminateStagingTrack().getLocation()) && 856 generateLoadCarDepartingAndTerminatingIntoStaging(car, getTerminateStagingTrack())) { 857 return true; 858 } 859 860 addLine(SEVEN, 861 Bundle.getMessage("buildNoStagingForCarCustom", car.toString())); 862 addLine(SEVEN, BLANK_LINE); 863 return false; 864 } 865 866 /** 867 * Check to see if car has been assigned a home division. If car has a home 868 * division the following rules are applied when assigning the car a 869 * destination: 870 * <p> 871 * If car load is type empty not at car's home division yard: Car is sent to 872 * a home division yard. If home division yard not available, then car is 873 * sent to home division staging, then spur (industry). 874 * <p> 875 * If car load is type empty at a yard at the car's home division: Car is 876 * sent to a home division spur, then home division staging. 877 * <p> 878 * If car load is type load not at car's home division: Car is sent to home 879 * division spur, and if spur not available then home division staging. 880 * <p> 881 * If car load is type load at car's home division: Car is sent to any 882 * division spur or staging. 883 * 884 * @param car the car being checked for a home division 885 * @return false if destination track not found for this car 886 * @throws BuildFailedException 887 */ 888 private boolean findDestinationsForCarsWithHomeDivision(Car car) throws BuildFailedException { 889 if (car.getDivision() == null || car.getDestination() != null || car.getFinalDestination() != null) { 890 return true; 891 } 892 if (car.getDivision() == car.getTrack().getDivision()) { 893 addLine(FIVE, 894 Bundle.getMessage("buildCarDepartHomeDivision", car.toString(), car.getTypeName(), 895 car.getLoadType().toLowerCase(), 896 car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(), 897 car.getLocationName(), car.getTrackName(), 898 car.getTrack().getDivisionName())); 899 } else { 900 addLine(FIVE, 901 Bundle.getMessage("buildCarDepartForeignDivision", car.toString(), car.getTypeName(), 902 car.getLoadType().toLowerCase(), 903 car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(), 904 car.getLocationName(), car.getTrackName(), 905 car.getTrack().getDivisionName())); 906 } 907 if (car.getKernel() != null) { 908 addLine(SEVEN, 909 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 910 car.getKernel().getSize(), 911 car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 912 } 913 // does train terminate into staging? 914 if (getTerminateStagingTrack() != null) { 915 log.debug("Train terminates into staging track ({})", getTerminateStagingTrack().getName()); 916 // bias cars to staging 917 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) { 918 log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName()); 919 if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) { 920 log.debug("Car ({}) at it's home division yard", car.toString()); 921 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 922 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 923 } 924 } 925 // try to send to home division staging, then home division yard, 926 // then home division spur 927 else if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 928 if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) { 929 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 930 } 931 } 932 } else { 933 log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName()); 934 // 1st send car to staging dependent of shipping track division, then 935 // try spur 936 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, 937 car.getTrack().getDivision() != car.getDivision())) { 938 return sendCarToHomeDivisionTrack(car, Track.SPUR, 939 car.getTrack().getDivision() != car.getDivision()); 940 } 941 } 942 } else { 943 // train doesn't terminate into staging 944 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) { 945 log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName()); 946 if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) { 947 log.debug("Car ({}) at it's home division yard", car.toString()); 948 if (!sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION)) { 949 return sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION); 950 } 951 } 952 // try to send to home division yard, then home division staging, 953 // then home division spur 954 else if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) { 955 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 956 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 957 } 958 } 959 } else { 960 log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName()); 961 // 1st send car to spur dependent of shipping track division, then 962 // try staging 963 if (!sendCarToHomeDivisionTrack(car, Track.SPUR, car.getTrack().getDivision() != car.getDivision())) { 964 return sendCarToHomeDivisionTrack(car, Track.STAGING, 965 car.getTrack().getDivision() != car.getDivision()); 966 } 967 } 968 } 969 return true; 970 } 971 972 private static final boolean HOME_DIVISION = true; 973 974 /** 975 * Tries to set a final destination for the car with a home division. 976 * 977 * @param car the car 978 * @param trackType One of three track types: Track.SPUR Track.YARD or 979 * Track.STAGING 980 * @param home_division If true track's division must match the car's 981 * @return true if car was given a final destination 982 */ 983 private boolean sendCarToHomeDivisionTrack(Car car, String trackType, boolean home_division) { 984 // locations not reachable 985 List<Location> locationsNotServiced = new ArrayList<>(); 986 List<Track> tracks = locationManager.getTracksByMoves(trackType); 987 log.debug("Found {} {} tracks", tracks.size(), trackType); 988 for (Track track : tracks) { 989 if (home_division && car.getDivision() != track.getDivision()) { 990 addLine(SEVEN, 991 Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(), 992 track.getLocation().getName(), track.getName(), track.getDivisionName(), car.toString(), 993 car.getLoadType().toLowerCase(), 994 car.getLoadName())); 995 continue; 996 } 997 if (locationsNotServiced.contains(track.getLocation())) { 998 continue; 999 } 1000 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 1001 addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", 1002 track.getLocation().getName(), car.getTrackName())); 1003 // location not reachable 1004 locationsNotServiced.add(track.getLocation()); 1005 continue; 1006 } 1007 // only use the termination staging track for this train 1008 if (trackType.equals(Track.STAGING) && 1009 getTerminateStagingTrack() != null && 1010 track.getLocation() == getTerminateLocation() && 1011 track != getTerminateStagingTrack()) { 1012 continue; 1013 } 1014 if (trackType.equals(Track.SPUR)) { 1015 if (sendCarToDestinationSpur(car, track)) { 1016 return true; 1017 } 1018 } else { 1019 if (sendCarToDestinationTrack(car, track)) { 1020 return true; 1021 } 1022 } 1023 } 1024 addLine(FIVE, 1025 Bundle.getMessage("buildCouldNotFindTrack", trackType.toLowerCase(), car.toString(), 1026 car.getLoadType().toLowerCase(), car.getLoadName())); 1027 addLine(SEVEN, BLANK_LINE); 1028 return false; 1029 } 1030 1031 /** 1032 * Set the final destination and track for a car with a custom load. Car 1033 * must not have a destination or final destination. There's a check to see 1034 * if there's a spur/schedule for this car. Returns true if a schedule was 1035 * found. Will hold car at current location if any of the spurs checked has 1036 * the the option to "Hold cars with custom loads" enabled and the spur has 1037 * an alternate track assigned. Tries to sent the car to staging if there 1038 * aren't any spurs with schedules available. 1039 * 1040 * @param car the car with the load 1041 * @return true if there's a schedule that can be routed to for this car and 1042 * load 1043 * @throws BuildFailedException 1044 */ 1045 private boolean findFinalDestinationForCarLoad(Car car) throws BuildFailedException { 1046 if (car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 1047 car.getLoadName().equals(carLoads.getDefaultLoadName()) || 1048 car.getDestination() != null || 1049 car.getFinalDestination() != null) { 1050 return false; // car doesn't have a custom load, or already has a 1051 // destination set 1052 } 1053 addLine(FIVE, 1054 Bundle.getMessage("buildSearchForSpur", car.toString(), car.getTypeName(), 1055 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(), 1056 car.getTrackName())); 1057 if (car.getKernel() != null) { 1058 addLine(SEVEN, 1059 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 1060 car.getKernel().getSize(), car.getKernel().getTotalLength(), 1061 Setup.getLengthUnit().toLowerCase())); 1062 } 1063 _routeToTrackFound = false; 1064 List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR); 1065 log.debug("Found {} spurs", tracks.size()); 1066 // locations not reachable 1067 List<Location> locationsNotServiced = new ArrayList<>(); 1068 for (Track track : tracks) { 1069 if (car.getTrack() == track) { 1070 continue; 1071 } 1072 if (track.getSchedule() == null) { 1073 addLine(SEVEN, Bundle.getMessage("buildSpurNoSchedule", 1074 track.getLocation().getName(), track.getName())); 1075 continue; 1076 } 1077 if (locationsNotServiced.contains(track.getLocation())) { 1078 continue; 1079 } 1080 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 1081 addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", 1082 track.getLocation().getName(), car.getTrackName())); 1083 // location not reachable 1084 locationsNotServiced.add(track.getLocation()); 1085 continue; 1086 } 1087 if (sendCarToDestinationSpur(car, track)) { 1088 return true; 1089 } 1090 } 1091 addLine(SEVEN, 1092 Bundle.getMessage("buildCouldNotFindTrack", Track.getTrackTypeName(Track.SPUR).toLowerCase(), 1093 car.toString(), car.getLoadType().toLowerCase(), car.getLoadName())); 1094 if (_routeToTrackFound && 1095 !getTrain().isSendCarsWithCustomLoadsToStagingEnabled() && 1096 !car.getLocation().isStaging()) { 1097 addLine(SEVEN, Bundle.getMessage("buildHoldCarValidRoute", car.toString(), 1098 car.getLocationName(), car.getTrackName())); 1099 } else { 1100 // try and send car to staging 1101 addLine(SEVEN, BLANK_LINE); 1102 addLine(FIVE, 1103 Bundle.getMessage("buildTrySendCarToStaging", car.toString(), car.getLoadName())); 1104 tracks = locationManager.getTracks(Track.STAGING); 1105 log.debug("Found {} staging tracks", tracks.size()); 1106 while (tracks.size() > 0) { 1107 // pick a track randomly 1108 int rnd = (int) (Math.random() * tracks.size()); 1109 Track track = tracks.get(rnd); 1110 tracks.remove(track); 1111 log.debug("Staging track ({}, {})", track.getLocation().getName(), track.getName()); 1112 if (track.getLocation() == car.getLocation()) { 1113 continue; 1114 } 1115 if (locationsNotServiced.contains(track.getLocation())) { 1116 continue; 1117 } 1118 if (getTerminateStagingTrack() != null && 1119 track.getLocation() == getTerminateLocation() && 1120 track != getTerminateStagingTrack()) { 1121 continue; // ignore other staging tracks at terminus 1122 } 1123 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 1124 addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", 1125 track.getLocation().getName(), car.getTrackName())); 1126 locationsNotServiced.add(track.getLocation()); 1127 continue; 1128 } 1129 String status = track.isRollingStockAccepted(car); 1130 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 1131 log.debug("Staging track ({}) can't accept car ({})", track.getName(), car.toString()); 1132 continue; 1133 } 1134 addLine(SEVEN, Bundle.getMessage("buildStagingCanAcceptLoad", track.getLocation(), 1135 track.getName(), car.getLoadName())); 1136 // try to send car to staging 1137 car.setFinalDestination(track.getLocation()); 1138 // test to see if destination is reachable by this train 1139 if (router.setDestination(car, getTrain(), getBuildReport())) { 1140 _routeToTrackFound = true; // found a route to staging 1141 } 1142 if (car.getDestination() != null) { 1143 car.updateKernel(); // car part of kernel? 1144 return true; 1145 } 1146 // couldn't route to this staging location 1147 locationsNotServiced.add(track.getLocation()); 1148 car.setFinalDestination(null); 1149 } 1150 addLine(SEVEN, 1151 Bundle.getMessage("buildNoStagingForCarLoad", car.toString(), car.getLoadName())); 1152 if (!_routeToTrackFound) { 1153 addLine(SEVEN, BLANK_LINE); 1154 } 1155 } 1156 log.debug("routeToSpurFound is {}", _routeToTrackFound); 1157 return _routeToTrackFound; // done 1158 } 1159 1160 boolean _routeToTrackFound; 1161 1162 /** 1163 * Used to determine if spur can accept car. Also will set routeToTrackFound 1164 * to true if there's a valid route available to the spur being tested. Sets 1165 * car's final destination to track if okay. 1166 * 1167 * @param car the car 1168 * @param track the spur 1169 * @return false if there's an issue with using the spur 1170 */ 1171 private boolean sendCarToDestinationSpur(Car car, Track track) { 1172 if (!checkBasicMoves(car, track)) { 1173 addLine(SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", getTrain().getName(), 1174 car.toString(), track.getLocation().getName(), track.getName())); 1175 return false; 1176 } 1177 String status = car.checkDestination(track.getLocation(), track); 1178 if (!status.equals(Track.OKAY)) { 1179 if (track.getScheduleMode() == Track.SEQUENTIAL && status.startsWith(Track.SCHEDULE)) { 1180 addLine(SEVEN, Bundle.getMessage("buildTrackSequentialMode", 1181 track.getLocation().getName(), track.getName(), status)); 1182 } 1183 // if the track has an alternate track don't abort if the issue was 1184 // space 1185 if (!status.startsWith(Track.LENGTH)) { 1186 addLine(SEVEN, 1187 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1188 track.getLocation().getName(), track.getName(), car.toString(), 1189 car.getLoadType().toLowerCase(), car.getLoadName(), status)); 1190 return false; 1191 } 1192 if (track.getAlternateTrack() == null) { 1193 // report that the spur is full and no alternate 1194 addLine(SEVEN, 1195 Bundle.getMessage("buildSpurFullNoAlternate", track.getLocation().getName(), track.getName())); 1196 return false; 1197 } else { 1198 addLine(SEVEN, 1199 Bundle.getMessage("buildTrackFullHasAlternate", track.getLocation().getName(), track.getName(), 1200 track.getAlternateTrack().getName())); 1201 // check to see if alternate and track are configured properly 1202 if (!getTrain().isLocalSwitcher() && 1203 (track.getTrainDirections() & track.getAlternateTrack().getTrainDirections()) == 0) { 1204 addLine(SEVEN, Bundle.getMessage("buildCanNotDropRsUsingTrain4", track.getName(), 1205 formatStringToCommaSeparated(Setup.getDirectionStrings(track.getTrainDirections())), 1206 track.getAlternateTrack().getName(), formatStringToCommaSeparated( 1207 Setup.getDirectionStrings(track.getAlternateTrack().getTrainDirections())))); 1208 return false; 1209 } 1210 } 1211 } 1212 addLine(SEVEN, BLANK_LINE); 1213 addLine(SEVEN, 1214 Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(), 1215 track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(), 1216 car.getLoadName())); 1217 1218 // show if track is requesting cars with custom loads to only go to 1219 // spurs 1220 if (track.isHoldCarsWithCustomLoadsEnabled()) { 1221 addLine(SEVEN, 1222 Bundle.getMessage("buildHoldCarsCustom", track.getLocation().getName(), track.getName())); 1223 } 1224 // check the number of in bound cars to this track 1225 if (!track.isSpaceAvailable(car)) { 1226 // Now determine if we should move the car or just leave it 1227 if (track.isHoldCarsWithCustomLoadsEnabled()) { 1228 // determine if this car can be routed to the spur 1229 String id = track.getScheduleItemId(); 1230 if (router.isCarRouteable(car, getTrain(), track, getBuildReport())) { 1231 // hold car if able to route to track 1232 _routeToTrackFound = true; 1233 } else { 1234 addLine(SEVEN, Bundle.getMessage("buildRouteNotFound", car.toString(), 1235 car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1236 } 1237 track.setScheduleItemId(id); // restore id 1238 } 1239 if (car.getTrack().isStaging()) { 1240 addLine(SEVEN, 1241 Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(), 1242 track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(), 1243 Setup.getLengthUnit().toLowerCase(), track.getReservationFactor())); 1244 } else { 1245 addLine(SEVEN, 1246 Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(), 1247 track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(), 1248 track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase())); 1249 } 1250 return false; 1251 } 1252 // try to send car to this spur 1253 car.setFinalDestination(track.getLocation()); 1254 car.setFinalDestinationTrack(track); 1255 // test to see if destination is reachable by this train 1256 if (router.setDestination(car, getTrain(), getBuildReport()) && track.isHoldCarsWithCustomLoadsEnabled()) { 1257 _routeToTrackFound = true; // if we don't find another spur, don't 1258 // move car 1259 } 1260 if (car.getDestination() == null) { 1261 if (!router.getStatus().equals(Track.OKAY)) { 1262 addLine(SEVEN, 1263 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1264 } 1265 car.setFinalDestination(null); 1266 car.setFinalDestinationTrack(null); 1267 // don't move car if another train can 1268 if (router.getStatus().startsWith(Router.STATUS_NOT_THIS_TRAIN_PREFIX)) { 1269 _routeToTrackFound = true; 1270 } 1271 return false; 1272 } 1273 if (car.getDestinationTrack() != track) { 1274 track.bumpMoves(); 1275 // car is being routed to this track 1276 if (track.getSchedule() != null) { 1277 car.setScheduleItemId(track.getCurrentScheduleItem().getId()); 1278 track.bumpSchedule(); 1279 } 1280 } 1281 car.updateKernel(); 1282 return true; // done, car has a new destination 1283 } 1284 1285 /** 1286 * Destination track can be division yard or staging, NOT a spur. 1287 * 1288 * @param car the car 1289 * @param track the car's destination track 1290 * @return true if car given a new final destination 1291 */ 1292 private boolean sendCarToDestinationTrack(Car car, Track track) { 1293 if (!checkBasicMoves(car, track)) { 1294 addLine(SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", getTrain().getName(), 1295 car.toString(), track.getLocation().getName(), track.getName())); 1296 return false; 1297 } 1298 String status = car.checkDestination(track.getLocation(), track); 1299 1300 if (!status.equals(Track.OKAY)) { 1301 addLine(SEVEN, 1302 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1303 track.getLocation().getName(), track.getName(), car.toString(), 1304 car.getLoadType().toLowerCase(), car.getLoadName(), status)); 1305 return false; 1306 } 1307 if (!track.isSpaceAvailable(car)) { 1308 addLine(SEVEN, 1309 Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(), 1310 track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(), 1311 track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase())); 1312 return false; 1313 } 1314 // try to send car to this division track 1315 addLine(SEVEN, 1316 Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(), 1317 track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(), 1318 car.getLoadName())); 1319 car.setFinalDestination(track.getLocation()); 1320 car.setFinalDestinationTrack(track); 1321 // test to see if destination is reachable by this train 1322 if (router.setDestination(car, getTrain(), getBuildReport())) { 1323 log.debug("Can route car to destination ({}, {})", track.getLocation().getName(), track.getName()); 1324 } 1325 if (car.getDestination() == null) { 1326 addLine(SEVEN, 1327 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1328 car.setFinalDestination(null); 1329 car.setFinalDestinationTrack(null); 1330 return false; 1331 } 1332 car.updateKernel(); 1333 return true; // done, car has a new final destination 1334 } 1335 1336 /** 1337 * Checks for a car's final destination, and then after checking, tries to 1338 * route the car to that destination. Normal return from this routine is 1339 * false, with the car returning with a set destination. Returns true if car 1340 * has a final destination, but can't be used for this train. 1341 * 1342 * @param car 1343 * @return false if car needs destination processing (normal). 1344 */ 1345 private boolean checkCarForFinalDestination(Car car) { 1346 if (car.getFinalDestination() == null || car.getDestination() != null) { 1347 return false; 1348 } 1349 1350 addLine(FIVE, 1351 Bundle.getMessage("buildCarRoutingBegins", car.toString(), car.getTypeName(), 1352 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(), 1353 car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1354 1355 // no local moves for this train? 1356 if (!getTrain().isLocalSwitcher() && 1357 !getTrain().isAllowLocalMovesEnabled() && 1358 car.getSplitLocationName().equals(car.getSplitFinalDestinationName()) && 1359 car.getTrack() != getDepartureStagingTrack()) { 1360 addLine(FIVE, 1361 Bundle.getMessage("buildCarHasFinalDestNoMove", car.toString(), car.getLocationName(), 1362 car.getFinalDestinationName(), getTrain().getName())); 1363 addLine(FIVE, BLANK_LINE); 1364 log.debug("Removing car ({}) from list", car.toString()); 1365 remove(car); 1366 return true; // car has a final destination, but no local moves by 1367 // this train 1368 } 1369 // is the car's destination the terminal and is that allowed? 1370 if (!checkThroughCarsAllowed(car, car.getFinalDestinationName())) { 1371 // don't remove car from list if departing staging 1372 if (car.getTrack() == getDepartureStagingTrack()) { 1373 addLine(ONE, Bundle.getMessage("buildErrorCarStageDest", car.toString())); 1374 } else { 1375 log.debug("Removing car ({}) from list", car.toString()); 1376 remove(car); 1377 } 1378 return true; // car has a final destination, but through traffic not 1379 // allowed by this train 1380 } 1381 // does the car have a final destination track that is willing to 1382 // service the car? 1383 // note the default mode for all track types is MATCH 1384 if (car.getFinalDestinationTrack() != null && car.getFinalDestinationTrack().getScheduleMode() == Track.MATCH) { 1385 String status = car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack()); 1386 // keep going if the only issue was track length and the track 1387 // accepts the car's load 1388 if (!status.equals(Track.OKAY) && 1389 !status.startsWith(Track.LENGTH) && 1390 !(status.contains(Track.CUSTOM) && status.contains(Track.LOAD))) { 1391 addLine(SEVEN, 1392 Bundle.getMessage("buildNoDestTrackNewLoad", 1393 StringUtils.capitalize(car.getFinalDestinationTrack().getTrackTypeName()), 1394 car.getFinalDestination().getName(), car.getFinalDestinationTrack().getName(), 1395 car.toString(), car.getLoadType().toLowerCase(), car.getLoadName(), status)); 1396 // is this car or kernel being sent to a track that is too 1397 // short? 1398 if (status.startsWith(Track.CAPACITY)) { 1399 // track is too short for this car or kernel 1400 addLine(SEVEN, 1401 Bundle.getMessage("buildTrackTooShort", car.getFinalDestination().getName(), 1402 car.getFinalDestinationTrack().getName(), car.toString())); 1403 } 1404 _warnings++; 1405 addLine(SEVEN, 1406 Bundle.getMessage("buildWarningRemovingFinalDest", car.getFinalDestination().getName(), 1407 car.getFinalDestinationTrack().getName(), car.toString())); 1408 car.setFinalDestination(null); 1409 car.setFinalDestinationTrack(null); 1410 return false; // car no longer has a final destination 1411 } 1412 } 1413 1414 // now try and route the car 1415 if (!router.setDestination(car, getTrain(), getBuildReport())) { 1416 addLine(SEVEN, 1417 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1418 // don't move car if routing issue was track space but not departing 1419 // staging 1420 if ((!router.getStatus().startsWith(Track.LENGTH) && 1421 !getTrain().isServiceAllCarsWithFinalDestinationsEnabled()) || 1422 (car.getTrack() == getDepartureStagingTrack())) { 1423 // add car to unable to route list 1424 if (!_notRoutable.contains(car)) { 1425 _notRoutable.add(car); 1426 } 1427 addLine(FIVE, BLANK_LINE); 1428 addLine(FIVE, 1429 Bundle.getMessage("buildWarningCarNotRoutable", car.toString(), car.getLocationName(), 1430 car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1431 addLine(FIVE, BLANK_LINE); 1432 return false; // move this car, routing failed! 1433 } 1434 } else { 1435 if (car.getDestination() != null) { 1436 return false; // routing successful process this car, normal 1437 // exit from this routine 1438 } 1439 if (car.getTrack() == getDepartureStagingTrack()) { 1440 log.debug("Car ({}) departing staging with final destination ({}) and no destination", 1441 car.toString(), car.getFinalDestinationName()); 1442 return false; // try and move this car out of staging 1443 } 1444 } 1445 addLine(FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1446 addLine(FIVE, BLANK_LINE); 1447 return true; 1448 } 1449 1450 /** 1451 * Checks to see if car has a destination and tries to add car to train. 1452 * Will find a track for the car if needed. Returns false if car doesn't 1453 * have a destination. 1454 * 1455 * @param rl the car's route location 1456 * @param routeIndex where in the route to start search 1457 * @return true if car has a destination. Need to check if car given a train 1458 * assignment. 1459 * @throws BuildFailedException if destination was staging and can't place 1460 * car there 1461 */ 1462 private boolean checkCarForDestination(Car car, RouteLocation rl, int routeIndex) throws BuildFailedException { 1463 if (car.getDestination() == null) { 1464 return false; // the only false return 1465 } 1466 addLine(SEVEN, Bundle.getMessage("buildCarHasAssignedDest", car.toString(), car.getLoadName(), 1467 car.getDestinationName(), car.getDestinationTrackName(), car.getFinalDestinationName(), 1468 car.getFinalDestinationTrackName())); 1469 RouteLocation rld = getTrain().getRoute().getLastLocationByName(car.getDestinationName()); 1470 if (rld == null) { 1471 // code check, router doesn't set a car's destination if not carried 1472 // by train being built. Car has a destination that isn't serviced 1473 // by this train. Find buildExcludeCarDestNotPartRoute in 1474 // loadRemoveAndListCars() 1475 throw new BuildFailedException(Bundle.getMessage("buildExcludeCarDestNotPartRoute", car.toString(), 1476 car.getDestinationName(), car.getDestinationTrackName(), getTrain().getRoute().getName())); 1477 } 1478 // now go through the route and try and find a location with 1479 // the correct destination name 1480 for (int k = routeIndex; k < getRouteList().size(); k++) { 1481 rld = getRouteList().get(k); 1482 // if car can be picked up later at same location, skip 1483 if (checkForLaterPickUp(car, rl, rld)) { 1484 addLine(SEVEN, BLANK_LINE); 1485 return true; 1486 } 1487 if (!rld.getName().equals(car.getDestinationName())) { 1488 continue; 1489 } 1490 // is the car's destination the terminal and is that allowed? 1491 if (!checkThroughCarsAllowed(car, car.getDestinationName())) { 1492 return true; 1493 } 1494 log.debug("Car ({}) found a destination in train's route", car.toString()); 1495 // are drops allows at this location? 1496 if (!rld.isDropAllowed() && !car.isLocalMove()) { 1497 addLine(FIVE, Bundle.getMessage("buildRouteNoDropLocation", getTrain().getRoute().getName(), 1498 rld.getId(), rld.getName())); 1499 continue; 1500 } 1501 // are local moves allows at this location? 1502 if (!rld.isLocalMovesAllowed() && car.isLocalMove()) { 1503 addLine(FIVE, Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(), 1504 rld.getId(), rld.getName(), car.toString())); 1505 continue; 1506 } 1507 if (getTrain().isLocationSkipped(rld)) { 1508 addLine(FIVE, 1509 Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), getTrain().getName())); 1510 continue; 1511 } 1512 // any moves left at this location? 1513 if (rld.getCarMoves() >= rld.getMaxCarMoves()) { 1514 addLine(FIVE, 1515 Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(), 1516 getTrain().getRoute().getName(), rld.getId(), rld.getName())); 1517 continue; 1518 } 1519 // is the train length okay? 1520 if (!checkTrainLength(car, rl, rld)) { 1521 continue; 1522 } 1523 // check for valid destination track 1524 if (car.getDestinationTrack() == null) { 1525 addLine(FIVE, Bundle.getMessage("buildCarDoesNotHaveDest", car.toString())); 1526 // is car going into staging? 1527 if (rld == getTrain().getTrainTerminatesRouteLocation() && getTerminateStagingTrack() != null) { 1528 String status = car.checkDestination(car.getDestination(), getTerminateStagingTrack()); 1529 if (status.equals(Track.OKAY)) { 1530 addLine(FIVE, Bundle.getMessage("buildCarAssignedToStaging", car.toString(), 1531 getTerminateStagingTrack().getName())); 1532 addCarToTrain(car, rl, rld, getTerminateStagingTrack()); 1533 return true; 1534 } else { 1535 addLine(SEVEN, 1536 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 1537 getTerminateStagingTrack().getTrackTypeName(), 1538 getTerminateStagingTrack().getLocation().getName(), 1539 getTerminateStagingTrack().getName(), 1540 status)); 1541 continue; 1542 } 1543 } else { 1544 // no staging at this location, now find a destination track 1545 // for this car 1546 List<Track> tracks = getTracksAtDestination(car, rld); 1547 if (tracks.size() > 0) { 1548 if (tracks.get(1) != null) { 1549 car.setFinalDestination(car.getDestination()); 1550 car.setFinalDestinationTrack(tracks.get(1)); 1551 tracks.get(1).bumpMoves(); 1552 } 1553 addLine(FIVE, 1554 Bundle.getMessage("buildCarCanDropMoves", car.toString(), 1555 tracks.get(0).getTrackTypeName(), 1556 tracks.get(0).getLocation().getName(), tracks.get(0).getName(), 1557 rld.getCarMoves(), rld.getMaxCarMoves())); 1558 addCarToTrain(car, rl, rld, tracks.get(0)); 1559 return true; 1560 } 1561 } 1562 } else { 1563 log.debug("Car ({}) has a destination track ({})", car.toString(), car.getDestinationTrack().getName()); 1564 // going into the correct staging track? 1565 if (rld.equals(getTrain().getTrainTerminatesRouteLocation()) && 1566 getTerminateStagingTrack() != null && 1567 getTerminateStagingTrack() != car.getDestinationTrack()) { 1568 // car going to wrong track in staging, change track 1569 addLine(SEVEN, Bundle.getMessage("buildCarDestinationStaging", car.toString(), 1570 car.getDestinationName(), car.getDestinationTrackName())); 1571 car.setDestination(getTerminateStagingTrack().getLocation(), getTerminateStagingTrack()); 1572 } 1573 if (!rld.equals(getTrain().getTrainTerminatesRouteLocation()) || 1574 getTerminateStagingTrack() == null || 1575 getTerminateStagingTrack() == car.getDestinationTrack()) { 1576 // is train direction correct? and drop to interchange or 1577 // spur? 1578 if (checkDropTrainDirection(car, rld, car.getDestinationTrack()) && 1579 checkTrainCanDrop(car, car.getDestinationTrack())) { 1580 String status = car.checkDestination(car.getDestination(), car.getDestinationTrack()); 1581 if (status.equals(Track.OKAY) && 1582 (status = checkReserved(getTrain(), rld, car, car.getDestinationTrack(), true)) 1583 .equals(Track.OKAY)) { 1584 Track destTrack = car.getDestinationTrack(); 1585 addCarToTrain(car, rl, rld, destTrack); 1586 return true; 1587 } 1588 if (status.equals(TIMING) && checkForAlternate(car, car.getDestinationTrack())) { 1589 // send car to alternate track) { 1590 car.setFinalDestination(car.getDestination()); 1591 car.setFinalDestinationTrack(car.getDestinationTrack()); 1592 addCarToTrain(car, rl, rld, car.getDestinationTrack().getAlternateTrack()); 1593 return true; 1594 } 1595 addLine(SEVEN, 1596 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 1597 car.getDestinationTrack().getTrackTypeName(), 1598 car.getDestinationTrack().getLocation().getName(), 1599 car.getDestinationTrackName(), status)); 1600 1601 } 1602 } else { 1603 // code check 1604 throw new BuildFailedException(Bundle.getMessage("buildCarDestinationStaging", car.toString(), 1605 car.getDestinationName(), car.getDestinationTrackName())); 1606 } 1607 } 1608 addLine(FIVE, 1609 Bundle.getMessage("buildCanNotDropCar", car.toString(), car.getDestinationName(), rld.getId())); 1610 if (car.getDestinationTrack() == null) { 1611 log.debug("Could not find a destination track for location ({})", car.getDestinationName()); 1612 } 1613 } 1614 log.debug("car ({}) not added to train", car.toString()); 1615 addLine(FIVE, 1616 Bundle.getMessage("buildDestinationNotReachable", car.getDestinationName(), rl.getName(), rl.getId())); 1617 // remove destination and revert to final destination 1618 if (car.getDestinationTrack() != null) { 1619 // going to remove this destination from car 1620 car.getDestinationTrack().setMoves(car.getDestinationTrack().getMoves() - 1); 1621 Track destTrack = car.getDestinationTrack(); 1622 // TODO should we leave the car's destination? The spur expects this 1623 // car! 1624 if (destTrack.getSchedule() != null && destTrack.getScheduleMode() == Track.SEQUENTIAL) { 1625 addLine(SEVEN, Bundle.getMessage("buildPickupCanceled", 1626 destTrack.getLocation().getName(), destTrack.getName())); 1627 } 1628 } 1629 car.setFinalDestination(car.getPreviousFinalDestination()); 1630 car.setFinalDestinationTrack(car.getPreviousFinalDestinationTrack()); 1631 car.setDestination(null, null); 1632 car.updateKernel(); 1633 1634 addLine(FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1635 addLine(FIVE, BLANK_LINE); 1636 return true; // car no longer has a destination, but it had one. 1637 } 1638 1639 /** 1640 * Find a destination and track for a car at a route location. 1641 * 1642 * @param car the car! 1643 * @param rl The car's route location 1644 * @param rld The car's route destination 1645 * @return true if successful. 1646 * @throws BuildFailedException if code check fails 1647 */ 1648 private boolean findDestinationAndTrack(Car car, RouteLocation rl, RouteLocation rld) throws BuildFailedException { 1649 int index = getRouteList().indexOf(rld); 1650 if (getTrain().isLocalSwitcher()) { 1651 return findDestinationAndTrack(car, rl, index, index + 1); 1652 } 1653 return findDestinationAndTrack(car, rl, index - 1, index + 1); 1654 } 1655 1656 /** 1657 * Find a destination and track for a car, and add the car to the train. 1658 * 1659 * @param car The car that is looking for a destination and 1660 * destination track. 1661 * @param rl The route location for this car. 1662 * @param routeIndex Where in the train's route to begin a search for a 1663 * destination for this car. 1664 * @param routeEnd Where to stop looking for a destination. 1665 * @return true if successful, car has destination, track and a train. 1666 * @throws BuildFailedException if code check fails 1667 */ 1668 private boolean findDestinationAndTrack(Car car, RouteLocation rl, int routeIndex, int routeEnd) 1669 throws BuildFailedException { 1670 if (routeIndex + 1 == routeEnd) { 1671 log.debug("Car ({}) is at the last location in the train's route", car.toString()); 1672 } 1673 addLine(FIVE, Bundle.getMessage("buildFindDestinationForCar", car.toString(), car.getTypeName(), 1674 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(), 1675 car.getTrackName())); 1676 if (car.getKernel() != null) { 1677 addLine(SEVEN, Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 1678 car.getKernel().getSize(), car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 1679 } 1680 1681 // normally start looking after car's route location 1682 int start = routeIndex; 1683 // the route location destination being checked for the car 1684 RouteLocation rld = null; 1685 // holds the best route location destination for the car 1686 RouteLocation rldSave = null; 1687 // holds the best track at destination for the car 1688 Track trackSave = null; 1689 // used when a spur has an alternate track and no schedule 1690 Track finalDestinationTrackSave = null; 1691 // true when car can be picked up from two or more locations in the 1692 // route 1693 boolean multiplePickup = false; 1694 1695 if (!getTrain().isLocalSwitcher()) { 1696 start++; // begin looking for tracks at the next location 1697 } 1698 // all pick ups to terminal? 1699 if (getTrain().isSendCarsToTerminalEnabled() && 1700 !rl.getSplitName().equals(getDepartureLocation().getSplitName()) && 1701 routeEnd == getRouteList().size()) { 1702 addLine(FIVE, Bundle.getMessage("buildSendToTerminal", getTerminateLocation().getName())); 1703 // user could have specified several terminal locations with the 1704 // "same" name 1705 start = routeEnd - 1; 1706 while (start > routeIndex) { 1707 if (!getRouteList().get(start - 1).getSplitName() 1708 .equals(getTerminateLocation().getSplitName())) { 1709 break; 1710 } 1711 start--; 1712 } 1713 } 1714 // now search for a destination for this car 1715 for (int k = start; k < routeEnd; k++) { 1716 rld = getRouteList().get(k); 1717 // if car can be picked up later at same location, set flag 1718 if (checkForLaterPickUp(car, rl, rld)) { 1719 multiplePickup = true; 1720 } 1721 if (rld.isDropAllowed() || car.hasFred() || car.isCaboose()) { 1722 addLine(FIVE, Bundle.getMessage("buildSearchingLocation", rld.getName(), rld.getId())); 1723 } else { 1724 addLine(FIVE, Bundle.getMessage("buildRouteNoDropLocation", getTrain().getRoute().getName(), 1725 rld.getId(), rld.getName())); 1726 continue; 1727 } 1728 if (getTrain().isLocationSkipped(rld)) { 1729 addLine(FIVE, 1730 Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), getTrain().getName())); 1731 continue; 1732 } 1733 // any moves left at this location? 1734 if (rld.getCarMoves() >= rld.getMaxCarMoves()) { 1735 addLine(FIVE, 1736 Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(), 1737 getTrain().getRoute().getName(), rld.getId(), rld.getName())); 1738 continue; 1739 } 1740 // get the destination 1741 Location testDestination = rld.getLocation(); 1742 // code check, all locations in the route have been already checked 1743 if (testDestination == null) { 1744 throw new BuildFailedException( 1745 Bundle.getMessage("buildErrorRouteLoc", getTrain().getRoute().getName(), rld.getName())); 1746 } 1747 // don't move car to same location unless the train is a switcher 1748 // (local moves) or is passenger, caboose or car with FRED 1749 if (rl.getSplitName().equals(rld.getSplitName()) && 1750 !getTrain().isLocalSwitcher() && 1751 !car.isPassenger() && 1752 !car.isCaboose() && 1753 !car.hasFred()) { 1754 // allow cars to return to the same staging location if no other 1755 // options (tracks) are available 1756 if ((getTrain().isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) && 1757 testDestination.isStaging() && 1758 trackSave == null) { 1759 addLine(SEVEN, 1760 Bundle.getMessage("buildReturnCarToStaging", car.toString(), rld.getName())); 1761 } else { 1762 addLine(SEVEN, 1763 Bundle.getMessage("buildCarLocEqualDestination", car.toString(), rld.getName())); 1764 continue; 1765 } 1766 } 1767 // don't allow local moves for a car with a final destination 1768 if (rl.getSplitName().equals(rld.getSplitName()) && 1769 car.getFinalDestination() != null && 1770 !car.isPassenger() && 1771 !car.isCaboose() && 1772 !car.hasFred()) { 1773 if (!rld.isLocalMovesAllowed()) { 1774 addLine(FIVE, 1775 Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(), 1776 rld.getId(), rld.getName(), car.toString())); 1777 continue; 1778 } 1779 if (!rl.isLocalMovesAllowed()) { 1780 addLine(FIVE, 1781 Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(), 1782 rl.getId(), rl.getName(), car.toString())); 1783 continue; 1784 } 1785 } 1786 1787 // check to see if departure track has any restrictions 1788 if (!car.getTrack().isDestinationAccepted(testDestination)) { 1789 addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", testDestination.getName(), 1790 car.getTrackName())); 1791 continue; 1792 } 1793 1794 if (!testDestination.acceptsTypeName(car.getTypeName())) { 1795 addLine(SEVEN, Bundle.getMessage("buildCanNotDropLocation", car.toString(), 1796 car.getTypeName(), testDestination.getName())); 1797 continue; 1798 } 1799 // can this location service this train's direction 1800 if (!checkDropTrainDirection(rld)) { 1801 continue; 1802 } 1803 // is the train length okay? 1804 if (!checkTrainLength(car, rl, rld)) { 1805 break; // no, done with this car 1806 } 1807 // is the car's destination the terminal and is that allowed? 1808 if (!checkThroughCarsAllowed(car, rld.getName())) { 1809 continue; // not allowed 1810 } 1811 1812 Track trackTemp = null; 1813 // used when alternate track selected 1814 Track finalDestinationTrackTemp = null; 1815 1816 // is there a track assigned for staging cars? 1817 if (rld == getTrain().getTrainTerminatesRouteLocation() && getTerminateStagingTrack() != null) { 1818 trackTemp = tryStaging(car, rldSave); 1819 if (trackTemp == null) { 1820 continue; // no 1821 } 1822 } else { 1823 // not staging, start track search 1824 List<Track> tracks = getTracksAtDestination(car, rld); 1825 if (tracks.size() > 0) { 1826 trackTemp = tracks.get(0); 1827 finalDestinationTrackTemp = tracks.get(1); 1828 } 1829 } 1830 // did we find a new destination? 1831 if (trackTemp == null) { 1832 addLine(FIVE, 1833 Bundle.getMessage("buildCouldNotFindDestForCar", car.toString(), rld.getName())); 1834 } else { 1835 addLine(FIVE, 1836 Bundle.getMessage("buildCarCanDropMoves", car.toString(), trackTemp.getTrackTypeName(), 1837 trackTemp.getLocation().getName(), trackTemp.getName(), +rld.getCarMoves(), 1838 rld.getMaxCarMoves())); 1839 if (multiplePickup) { 1840 if (rldSave != null) { 1841 addLine(FIVE, 1842 Bundle.getMessage("buildTrackServicedLater", car.getLocationName(), 1843 trackTemp.getTrackTypeName(), trackTemp.getLocation().getName(), 1844 trackTemp.getName(), car.getLocationName())); 1845 } else { 1846 addLine(FIVE, 1847 Bundle.getMessage("buildCarHasSecond", car.toString(), car.getLocationName())); 1848 trackSave = null; 1849 } 1850 break; // done 1851 } 1852 // if there's more than one available destination use the lowest 1853 // ratio 1854 if (rldSave != null) { 1855 // check for an earlier drop in the route 1856 rld = checkForEarlierDrop(car, trackTemp, rld, start, routeEnd); 1857 double saveCarMoves = rldSave.getCarMoves(); 1858 double saveRatio = saveCarMoves / rldSave.getMaxCarMoves(); 1859 double nextCarMoves = rld.getCarMoves(); 1860 double nextRatio = nextCarMoves / rld.getMaxCarMoves(); 1861 1862 // bias cars to the terminal 1863 if (rld == getTrain().getTrainTerminatesRouteLocation()) { 1864 nextRatio = nextRatio * nextRatio; 1865 log.debug("Location ({}) is terminate location, adjusted nextRatio {}", rld.getName(), 1866 Double.toString(nextRatio)); 1867 1868 // bias cars with default loads to a track with a 1869 // schedule 1870 } else if (!trackTemp.getScheduleId().equals(Track.NONE)) { 1871 nextRatio = nextRatio * nextRatio; 1872 log.debug("Track ({}) has schedule ({}), adjusted nextRatio {}", trackTemp.getName(), 1873 trackTemp.getScheduleName(), Double.toString(nextRatio)); 1874 } 1875 // bias cars with default loads to saved track with a 1876 // schedule 1877 if (trackSave != null && !trackSave.getScheduleId().equals(Track.NONE)) { 1878 saveRatio = saveRatio * saveRatio; 1879 log.debug("Saved track ({}) has schedule ({}), adjusted nextRatio {}", trackSave.getName(), 1880 trackSave.getScheduleName(), Double.toString(saveRatio)); 1881 } 1882 log.debug("Saved {} = {}, {} = {}", rldSave.getName(), Double.toString(saveRatio), rld.getName(), 1883 Double.toString(nextRatio)); 1884 if (saveRatio < nextRatio) { 1885 // the saved is better than the last found 1886 rld = rldSave; 1887 trackTemp = trackSave; 1888 finalDestinationTrackTemp = finalDestinationTrackSave; 1889 } 1890 } 1891 // every time through, save the best route destination, and 1892 // track 1893 rldSave = rld; 1894 trackSave = trackTemp; 1895 finalDestinationTrackSave = finalDestinationTrackTemp; 1896 } 1897 } 1898 // did we find a destination? 1899 if (trackSave != null && rldSave != null) { 1900 // determine if local staging move is allowed (leaves car in staging) 1901 if ((getTrain().isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) && 1902 rl.isDropAllowed() && 1903 rl.getLocation().isStaging() && 1904 trackSave.isStaging() && 1905 rl.getLocation() == rldSave.getLocation() && 1906 !getTrain().isLocalSwitcher() && 1907 !car.isPassenger() && 1908 !car.isCaboose() && 1909 !car.hasFred()) { 1910 addLine(SEVEN, 1911 Bundle.getMessage("buildLeaveCarInStaging", car.toString(), car.getLocationName(), 1912 car.getTrackName())); 1913 rldSave = rl; // make local move 1914 } else if (trackSave.isSpur()) { 1915 car.setScheduleItemId(trackSave.getScheduleItemId()); 1916 trackSave.bumpSchedule(); 1917 log.debug("Sending car to spur ({}, {}) with car schedule id ({}))", trackSave.getLocation().getName(), 1918 trackSave.getName(), car.getScheduleItemId()); 1919 } else { 1920 car.setScheduleItemId(Car.NONE); 1921 } 1922 if (finalDestinationTrackSave != null) { 1923 car.setFinalDestination(finalDestinationTrackSave.getLocation()); 1924 car.setFinalDestinationTrack(finalDestinationTrackSave); 1925 if (trackSave.isAlternate()) { 1926 finalDestinationTrackSave.bumpMoves(); // bump move count 1927 } 1928 } 1929 addCarToTrain(car, rl, rldSave, trackSave); 1930 return true; 1931 } 1932 addLine(FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1933 addLine(FIVE, BLANK_LINE); 1934 return false; // no build errors, but car not given destination 1935 } 1936 1937 /** 1938 * Add car to train, and adjust train length and weight 1939 * 1940 * @param car the car being added to the train 1941 * @param rl the departure route location for this car 1942 * @param rld the destination route location for this car 1943 * @param track the destination track for this car 1944 */ 1945 protected void addCarToTrain(Car car, RouteLocation rl, RouteLocation rld, Track track) { 1946 car = checkQuickServiceArrival(car, rld, track); 1947 addLine(THREE, 1948 Bundle.getMessage("buildCarAssignedDest", car.toString(), rld.getName(), track.getName())); 1949 car.setDestination(track.getLocation(), track, Car.FORCE); 1950 int length = car.getTotalLength(); 1951 int weightTons = car.getAdjustedWeightTons(); 1952 // car could be part of a kernel 1953 if (car.getKernel() != null) { 1954 length = car.getKernel().getTotalLength(); // includes couplers 1955 weightTons = car.getKernel().getAdjustedWeightTons(); 1956 List<Car> kCars = car.getKernel().getCars(); 1957 addLine(THREE, 1958 Bundle.getMessage("buildCarPartOfKernel", car.toString(), car.getKernelName(), kCars.size(), 1959 car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 1960 for (Car kCar : kCars) { 1961 if (kCar != car) { 1962 addLine(THREE, Bundle.getMessage("buildCarKernelAssignedDest", kCar.toString(), 1963 kCar.getKernelName(), rld.getName(), track.getName())); 1964 kCar.setTrain(getTrain()); 1965 kCar.setRouteLocation(rl); 1966 kCar.setRouteDestination(rld); 1967 kCar.setDestination(track.getLocation(), track, Car.FORCE); // force destination 1968 // save final destination and track values in case of train reset 1969 kCar.setPreviousFinalDestination(car.getPreviousFinalDestination()); 1970 kCar.setPreviousFinalDestinationTrack(car.getPreviousFinalDestinationTrack()); 1971 } 1972 } 1973 car.updateKernel(); 1974 } 1975 // warn if car's load wasn't generated out of staging 1976 if (!getTrain().isLoadNameAccepted(car.getLoadName(), car.getTypeName())) { 1977 _warnings++; 1978 addLine(SEVEN, 1979 Bundle.getMessage("buildWarnCarDepartStaging", car.toString(), car.getLoadName())); 1980 } 1981 addLine(THREE, BLANK_LINE); 1982 _numberCars++; // bump number of cars moved by this train 1983 _completedMoves++; // bump number of car pick up moves for the location 1984 _reqNumOfMoves--; // decrement number of moves left for the location 1985 1986 remove(car); // remove car from list 1987 1988 rl.setCarMoves(rl.getCarMoves() + 1); 1989 if (rl != rld) { 1990 rld.setCarMoves(rld.getCarMoves() + 1); 1991 } 1992 // now adjust train length and weight for each location that car is in 1993 // the train 1994 finishAddRsToTrain(car, rl, rld, length, weightTons); 1995 } 1996 1997 /** 1998 * Checks to see if cars that are already in the train can be redirected 1999 * from the alternate track to the spur that really wants the car. Fixes the 2000 * issue of having cars placed at the alternate when the spur's cars get 2001 * pulled by this train, but cars were sent to the alternate because the 2002 * spur was full at the time it was tested. 2003 * 2004 * @return true if one or more cars were redirected 2005 * @throws BuildFailedException if coding issue 2006 */ 2007 protected boolean redirectCarsFromAlternateTrack() throws BuildFailedException { 2008 // code check, should be aggressive 2009 if (!Setup.isBuildAggressive()) { 2010 throw new BuildFailedException("ERROR coding issue, should be using aggressive mode"); 2011 } 2012 boolean redirected = false; 2013 List<Car> cars = carManager.getByTrainList(getTrain()); 2014 for (Car car : cars) { 2015 // does the car have a final destination and the destination is this 2016 // one? 2017 if (car.getFinalDestination() == null || 2018 car.getFinalDestinationTrack() == null || 2019 !car.getFinalDestinationName().equals(car.getDestinationName())) { 2020 continue; 2021 } 2022 Track alternate = car.getFinalDestinationTrack().getAlternateTrack(); 2023 if (alternate == null || car.getDestinationTrack() != alternate) { 2024 continue; 2025 } 2026 // is the car in a kernel? 2027 if (car.getKernel() != null && !car.isLead()) { 2028 continue; 2029 } 2030 log.debug("Car ({}) alternate track ({}) has final destination track ({}) location ({})", car.toString(), 2031 car.getDestinationTrackName(), car.getFinalDestinationTrackName(), car.getDestinationName()); // NOI18N 2032 if ((alternate.isYard() || alternate.isInterchange()) && 2033 car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack()) 2034 .equals(Track.OKAY) && 2035 checkReserved(getTrain(), car.getRouteDestination(), car, car.getFinalDestinationTrack(), false) 2036 .equals(Track.OKAY) && 2037 checkDropTrainDirection(car, car.getRouteDestination(), car.getFinalDestinationTrack()) && 2038 checkTrainCanDrop(car, car.getFinalDestinationTrack())) { 2039 log.debug("Car ({}) alternate track ({}) can be redirected to final destination track ({})", 2040 car.toString(), car.getDestinationTrackName(), car.getFinalDestinationTrackName()); 2041 if (car.getKernel() != null) { 2042 for (Car k : car.getKernel().getCars()) { 2043 if (k.isLead()) { 2044 continue; 2045 } 2046 addLine(FIVE, 2047 Bundle.getMessage("buildRedirectFromAlternate", car.getFinalDestinationName(), 2048 car.getFinalDestinationTrackName(), k.toString(), 2049 car.getDestinationTrackName())); 2050 // force car to track 2051 k.setDestination(car.getFinalDestination(), car.getFinalDestinationTrack(), Car.FORCE); 2052 } 2053 } 2054 addLine(FIVE, 2055 Bundle.getMessage("buildRedirectFromAlternate", car.getFinalDestinationName(), 2056 car.getFinalDestinationTrackName(), 2057 car.toString(), car.getDestinationTrackName())); 2058 car.setDestination(car.getFinalDestination(), car.getFinalDestinationTrack(), Car.FORCE); 2059 // check for quick service 2060 checkQuickServiceRedirected(car); 2061 redirected = true; 2062 } 2063 } 2064 return redirected; 2065 } 2066 2067 /* 2068 * Checks to see if the redirected car is going to a track with quick 2069 * service. The car in this case has already been assigned to the train. 2070 * This routine will create clones if needed, and allow the car to be 2071 * reassigned to the same train. Only lead car in a kernel is allowed. 2072 */ 2073 private void checkQuickServiceRedirected(Car car) { 2074 if (car.getDestinationTrack().isQuickServiceEnabled()) { 2075 RouteLocation rl = car.getRouteLocation(); 2076 RouteLocation rld = car.getRouteDestination(); 2077 Track track = car.getDestinationTrack(); 2078 // remove cars from train 2079 if (car.getKernel() != null) { 2080 for (Car kar : car.getKernel().getCars()) 2081 kar.reset(); 2082 } else { 2083 car.reset(); 2084 } 2085 getCarList().add(0, car); 2086 addCarToTrain(car, rl, rld, track); 2087 } 2088 } 2089 2090 /** 2091 * Checks to see if track is requesting a quick service. Since it isn't 2092 * possible for a car to be pulled and set out twice, this code creates a 2093 * "clone" car to create the requested Manifest. A car could have multiple 2094 * clones, therefore each clone has a creation order number appended to its 2095 * road number. Clones are used to restore a car's location and load in the 2096 * case of reset. 2097 * 2098 * @param car the car possibly needing quick service 2099 * @param track the destination track 2100 * @return the car if not quick service, or a clone if quick service 2101 */ 2102 private Car checkQuickServiceArrival(Car car, RouteLocation rld, Track track) { 2103 if (!track.isQuickServiceEnabled()) { 2104 if (Setup.isBuildOnTime()) { 2105 addLine(THREE, 2106 Bundle.getMessage("buildTrackNotQuickService", StringUtils.capitalize(track.getTrackTypeName()), 2107 track.getLocation().getName(), track.getName(), car.toString())); 2108 // warn if departing staging that is quick serviced enabled 2109 if (car.getTrack().isStaging() && car.getTrack().isQuickServiceEnabled()) { 2110 _warnings++; 2111 addLine(THREE, 2112 Bundle.getMessage("buildWarningQuickService", car.toString(), 2113 car.getTrack().getTrackTypeName(), 2114 car.getTrack().getLocation().getName(), car.getTrack().getName(), 2115 getTrain().getName(), StringUtils.capitalize(car.getTrack().getTrackTypeName()))); 2116 } 2117 } 2118 return car; 2119 } 2120 // quick service enabled, create clones 2121 Car cloneCar = carManager.createClone(car, track, getTrain(), getStartTime()); 2122 addLine(FIVE, 2123 Bundle.getMessage("buildTrackQuickService", StringUtils.capitalize(track.getTrackTypeName()), 2124 track.getLocation().getName(), track.getName(), cloneCar.toString(), car.toString())); 2125 // for timing, use arrival times for the train that is building 2126 // other trains will use their departure time, loaded when creating the Manifest 2127 String expectedArrivalTime = getTrain().getExpectedArrivalTime(rld, true); 2128 cloneCar.setSetoutTime(expectedArrivalTime); 2129 track.scheduleNext(car); // apply schedule to car 2130 car.loadNext(track); // update load, wait count 2131 if (car.getWait() > 0) { 2132 remove(car); // available for next train 2133 addLine(FIVE, Bundle.getMessage("buildExcludeCarWait", car.toString(), 2134 car.getTypeName(), car.getLocationName(), car.getTrackName(), car.getWait())); 2135 car.setWait(car.getWait() - 1); 2136 car.updateLoad(track); 2137 } 2138 // remember where in the route the car was delivered 2139 car.setRouteDestination(rld); 2140 car.updateKernel(); 2141 return cloneCar; // return clone 2142 } 2143 2144 private static final Logger log = LoggerFactory.getLogger(TrainBuilderCars.class); 2145}