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? 920 if (getTerminateStagingTrack() != null) { 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 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.getTrackType(), 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 if (!checkBasicMoves(car, track)) { 1179 addLine(SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", getTrain().getName(), 1180 car.toString(), track.getLocation().getName(), track.getName())); 1181 return false; 1182 } 1183 String status = car.checkDestination(track.getLocation(), track); 1184 if (!status.equals(Track.OKAY)) { 1185 if (track.getScheduleMode() == Track.SEQUENTIAL && status.startsWith(Track.SCHEDULE)) { 1186 addLine(SEVEN, Bundle.getMessage("buildTrackSequentialMode", 1187 track.getLocation().getName(), track.getName(), status)); 1188 } 1189 // if the track has an alternate track don't abort if the issue was 1190 // space 1191 if (!status.startsWith(Track.LENGTH)) { 1192 addLine(SEVEN, 1193 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1194 track.getLocation().getName(), track.getName(), car.toString(), 1195 car.getLoadType().toLowerCase(), car.getLoadName(), status)); 1196 return false; 1197 } 1198 if (track.getAlternateTrack() == null) { 1199 // report that the spur is full and no alternate 1200 addLine(SEVEN, 1201 Bundle.getMessage("buildSpurFullNoAlternate", track.getLocation().getName(), track.getName())); 1202 return false; 1203 } else { 1204 addLine(SEVEN, 1205 Bundle.getMessage("buildTrackFullHasAlternate", track.getLocation().getName(), track.getName(), 1206 track.getAlternateTrack().getName())); 1207 // check to see if alternate and track are configured properly 1208 if (!getTrain().isLocalSwitcher() && 1209 (track.getTrainDirections() & track.getAlternateTrack().getTrainDirections()) == 0) { 1210 addLine(SEVEN, Bundle.getMessage("buildCanNotDropRsUsingTrain4", track.getName(), 1211 formatStringToCommaSeparated(Setup.getDirectionStrings(track.getTrainDirections())), 1212 track.getAlternateTrack().getName(), formatStringToCommaSeparated( 1213 Setup.getDirectionStrings(track.getAlternateTrack().getTrainDirections())))); 1214 return false; 1215 } 1216 } 1217 } 1218 addLine(SEVEN, BLANK_LINE); 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 // don't move car if another train can 1274 if (router.getStatus().startsWith(Router.STATUS_NOT_THIS_TRAIN_PREFIX)) { 1275 _routeToTrackFound = true; 1276 } 1277 return false; 1278 } 1279 if (car.getDestinationTrack() != track) { 1280 track.bumpMoves(); 1281 // car is being routed to this track 1282 if (track.getSchedule() != null) { 1283 car.setScheduleItemId(track.getCurrentScheduleItem().getId()); 1284 track.bumpSchedule(); 1285 } 1286 } 1287 car.updateKernel(); 1288 return true; // done, car has a new destination 1289 } 1290 1291 /** 1292 * Destination track can be division yard or staging, NOT a spur. 1293 * 1294 * @param car the car 1295 * @param track the car's destination track 1296 * @return true if car given a new final destination 1297 */ 1298 private boolean sendCarToDestinationTrack(Car car, Track track) { 1299 if (!checkBasicMoves(car, track)) { 1300 addLine(SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", getTrain().getName(), 1301 car.toString(), track.getLocation().getName(), track.getName())); 1302 return false; 1303 } 1304 String status = car.checkDestination(track.getLocation(), track); 1305 1306 if (!status.equals(Track.OKAY)) { 1307 addLine(SEVEN, 1308 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1309 track.getLocation().getName(), track.getName(), car.toString(), 1310 car.getLoadType().toLowerCase(), car.getLoadName(), status)); 1311 return false; 1312 } 1313 if (!track.isSpaceAvailable(car)) { 1314 addLine(SEVEN, 1315 Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(), 1316 track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(), 1317 track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase())); 1318 return false; 1319 } 1320 // try to send car to this division track 1321 addLine(SEVEN, 1322 Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(), 1323 track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(), 1324 car.getLoadName())); 1325 car.setFinalDestination(track.getLocation()); 1326 car.setFinalDestinationTrack(track); 1327 // test to see if destination is reachable by this train 1328 if (router.setDestination(car, getTrain(), getBuildReport())) { 1329 log.debug("Can route car to destination ({}, {})", track.getLocation().getName(), track.getName()); 1330 } 1331 if (car.getDestination() == null) { 1332 addLine(SEVEN, 1333 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1334 car.setFinalDestination(null); 1335 car.setFinalDestinationTrack(null); 1336 return false; 1337 } 1338 car.updateKernel(); 1339 return true; // done, car has a new final destination 1340 } 1341 1342 /** 1343 * Checks for a car's final destination, and then after checking, tries to 1344 * route the car to that destination. Normal return from this routine is 1345 * false, with the car returning with a set destination. Returns true if car 1346 * has a final destination, but can't be used for this train. 1347 * 1348 * @param car 1349 * @return false if car needs destination processing (normal). 1350 */ 1351 private boolean checkCarForFinalDestination(Car car) { 1352 if (car.getFinalDestination() == null || car.getDestination() != null) { 1353 return false; 1354 } 1355 1356 addLine(FIVE, 1357 Bundle.getMessage("buildCarRoutingBegins", car.toString(), car.getTypeName(), 1358 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(), 1359 car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1360 1361 // no local moves for this train? 1362 if (!getTrain().isLocalSwitcher() && 1363 !getTrain().isAllowLocalMovesEnabled() && 1364 car.getSplitLocationName().equals(car.getSplitFinalDestinationName()) && 1365 car.getTrack() != getDepartureStagingTrack()) { 1366 addLine(FIVE, 1367 Bundle.getMessage("buildCarHasFinalDestNoMove", car.toString(), car.getLocationName(), 1368 car.getFinalDestinationName(), getTrain().getName())); 1369 addLine(FIVE, BLANK_LINE); 1370 return true; // car has a final destination, but no local moves by 1371 // this train 1372 } 1373 // is the car's destination the terminal and is that allowed? 1374 if (!checkThroughCarsAllowed(car, car.getFinalDestinationName())) { 1375 if (car.getTrack() == getDepartureStagingTrack()) { 1376 addLine(ONE, Bundle.getMessage("buildErrorCarStageDest", car.toString())); 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, 1674 Bundle.getMessage("buildFindDestinationForCar", car.toString(), car.getTypeName(), 1675 car.getTypeExtensions(), car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), 1676 car.getLocationName(), car.getTrackName())); 1677 if (car.getKernel() != null) { 1678 addLine(SEVEN, Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 1679 car.getKernel().getSize(), car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 1680 } 1681 1682 // normally start looking after car's route location 1683 int start = routeIndex; 1684 // the route location destination being checked for the car 1685 RouteLocation rld = null; 1686 // holds the best route location destination for the car 1687 RouteLocation rldSave = null; 1688 // holds the best track at destination for the car 1689 Track trackSave = null; 1690 // used when a spur has an alternate track and no schedule 1691 Track finalDestinationTrackSave = null; 1692 // true when car can be picked up from two or more locations in the 1693 // route 1694 boolean multiplePickup = false; 1695 1696 if (!getTrain().isLocalSwitcher()) { 1697 start++; // begin looking for tracks at the next location 1698 } 1699 // all pick ups to terminal? 1700 if (getTrain().isSendCarsToTerminalEnabled() && 1701 !rl.getSplitName().equals(getDepartureLocation().getSplitName()) && 1702 routeEnd == getRouteList().size()) { 1703 addLine(FIVE, Bundle.getMessage("buildSendToTerminal", getTerminateLocation().getName())); 1704 // user could have specified several terminal locations with the 1705 // "same" name 1706 start = routeEnd - 1; 1707 while (start > routeIndex) { 1708 if (!getRouteList().get(start - 1).getSplitName() 1709 .equals(getTerminateLocation().getSplitName())) { 1710 break; 1711 } 1712 start--; 1713 } 1714 } 1715 // now search for a destination for this car 1716 for (int k = start; k < routeEnd; k++) { 1717 rld = getRouteList().get(k); 1718 // if car can be picked up later at same location, set flag 1719 if (checkForLaterPickUp(car, rl, rld)) { 1720 multiplePickup = true; 1721 } 1722 if (rld.isDropAllowed() || car.hasFred() || car.isCaboose()) { 1723 addLine(FIVE, Bundle.getMessage("buildSearchingLocation", rld.getName(), rld.getId())); 1724 } else { 1725 addLine(FIVE, Bundle.getMessage("buildRouteNoDropLocation", getTrain().getRoute().getName(), 1726 rld.getId(), rld.getName())); 1727 continue; 1728 } 1729 if (getTrain().isLocationSkipped(rld)) { 1730 addLine(FIVE, 1731 Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), getTrain().getName())); 1732 continue; 1733 } 1734 // any moves left at this location? 1735 if (rld.getCarMoves() >= rld.getMaxCarMoves()) { 1736 addLine(FIVE, 1737 Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(), 1738 getTrain().getRoute().getName(), rld.getId(), rld.getName())); 1739 continue; 1740 } 1741 // get the destination 1742 Location testDestination = rld.getLocation(); 1743 // code check, all locations in the route have been already checked 1744 if (testDestination == null) { 1745 throw new BuildFailedException( 1746 Bundle.getMessage("buildErrorRouteLoc", getTrain().getRoute().getName(), rld.getName())); 1747 } 1748 // don't move car to same location unless the train is a switcher 1749 // (local moves) or is passenger, caboose or car with FRED 1750 if (rl.getSplitName().equals(rld.getSplitName()) && 1751 !getTrain().isLocalSwitcher() && 1752 !car.isPassenger() && 1753 !car.isCaboose() && 1754 !car.hasFred()) { 1755 // allow cars to return to the same staging location if no other 1756 // options (tracks) are available 1757 if ((getTrain().isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) && 1758 testDestination.isStaging() && 1759 trackSave == null) { 1760 addLine(SEVEN, 1761 Bundle.getMessage("buildReturnCarToStaging", car.toString(), rld.getName())); 1762 } else { 1763 addLine(SEVEN, 1764 Bundle.getMessage("buildCarLocEqualDestination", car.toString(), rld.getName())); 1765 continue; 1766 } 1767 } 1768 // don't allow local moves for a car with a final destination 1769 if (rl.getSplitName().equals(rld.getSplitName()) && 1770 car.getFinalDestination() != null && 1771 !car.isPassenger() && 1772 !car.isCaboose() && 1773 !car.hasFred()) { 1774 if (!rld.isLocalMovesAllowed()) { 1775 addLine(FIVE, 1776 Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(), 1777 rld.getId(), rld.getName(), car.toString())); 1778 continue; 1779 } 1780 if (!rl.isLocalMovesAllowed()) { 1781 addLine(FIVE, 1782 Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(), 1783 rl.getId(), rl.getName(), car.toString())); 1784 continue; 1785 } 1786 } 1787 1788 // check to see if departure track has any restrictions 1789 if (!car.getTrack().isDestinationAccepted(testDestination)) { 1790 addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", testDestination.getName(), 1791 car.getTrackName())); 1792 continue; 1793 } 1794 1795 if (!testDestination.acceptsTypeName(car.getTypeName())) { 1796 addLine(SEVEN, Bundle.getMessage("buildCanNotDropLocation", car.toString(), 1797 car.getTypeName(), testDestination.getName())); 1798 continue; 1799 } 1800 // can this location service this train's direction 1801 if (!checkDropTrainDirection(rld)) { 1802 continue; 1803 } 1804 // is the train length okay? 1805 if (!checkTrainLength(car, rl, rld)) { 1806 break; // no, done with this car 1807 } 1808 // is the car's destination the terminal and is that allowed? 1809 if (!checkThroughCarsAllowed(car, rld.getName())) { 1810 continue; // not allowed 1811 } 1812 1813 Track trackTemp = null; 1814 // used when alternate track selected 1815 Track finalDestinationTrackTemp = null; 1816 1817 // is there a track assigned for staging cars? 1818 if (rld == getTrain().getTrainTerminatesRouteLocation() && getTerminateStagingTrack() != null) { 1819 trackTemp = tryStaging(car, rldSave); 1820 if (trackTemp == null) { 1821 continue; // no 1822 } 1823 } else { 1824 // not staging, start track search 1825 List<Track> tracks = getTracksAtDestination(car, rld); 1826 if (tracks.size() > 0) { 1827 trackTemp = tracks.get(0); 1828 finalDestinationTrackTemp = tracks.get(1); 1829 } 1830 } 1831 // did we find a new destination? 1832 if (trackTemp == null) { 1833 addLine(FIVE, 1834 Bundle.getMessage("buildCouldNotFindDestForCar", car.toString(), rld.getName())); 1835 } else { 1836 addLine(FIVE, 1837 Bundle.getMessage("buildCarCanDropMoves", car.toString(), trackTemp.getTrackTypeName(), 1838 trackTemp.getLocation().getName(), trackTemp.getName(), +rld.getCarMoves(), 1839 rld.getMaxCarMoves())); 1840 if (multiplePickup) { 1841 if (rldSave != null) { 1842 addLine(FIVE, 1843 Bundle.getMessage("buildTrackServicedLater", car.getLocationName(), 1844 trackTemp.getTrackTypeName(), trackTemp.getLocation().getName(), 1845 trackTemp.getName(), car.getLocationName())); 1846 } else { 1847 addLine(FIVE, 1848 Bundle.getMessage("buildCarHasSecond", car.toString(), car.getLocationName())); 1849 trackSave = null; 1850 } 1851 break; // done 1852 } 1853 // if there's more than one available destination use the lowest 1854 // ratio 1855 if (rldSave != null) { 1856 // check for an earlier drop in the route 1857 rld = checkForEarlierDrop(car, trackTemp, rld, start, routeEnd); 1858 double saveCarMoves = rldSave.getCarMoves(); 1859 double saveRatio = saveCarMoves / rldSave.getMaxCarMoves(); 1860 double nextCarMoves = rld.getCarMoves(); 1861 double nextRatio = nextCarMoves / rld.getMaxCarMoves(); 1862 1863 // bias cars to the terminal 1864 if (rld == getTrain().getTrainTerminatesRouteLocation()) { 1865 nextRatio = nextRatio * nextRatio; 1866 log.debug("Location ({}) is terminate location, adjusted nextRatio {}", rld.getName(), 1867 Double.toString(nextRatio)); 1868 1869 // bias cars with default loads to a track with a 1870 // schedule 1871 } else if (!trackTemp.getScheduleId().equals(Track.NONE)) { 1872 nextRatio = nextRatio * nextRatio; 1873 log.debug("Track ({}) has schedule ({}), adjusted nextRatio {}", trackTemp.getName(), 1874 trackTemp.getScheduleName(), Double.toString(nextRatio)); 1875 } 1876 // bias cars with default loads to saved track with a 1877 // schedule 1878 if (trackSave != null && !trackSave.getScheduleId().equals(Track.NONE)) { 1879 saveRatio = saveRatio * saveRatio; 1880 log.debug("Saved track ({}) has schedule ({}), adjusted nextRatio {}", trackSave.getName(), 1881 trackSave.getScheduleName(), Double.toString(saveRatio)); 1882 } 1883 log.debug("Saved {} = {}, {} = {}", rldSave.getName(), Double.toString(saveRatio), rld.getName(), 1884 Double.toString(nextRatio)); 1885 if (saveRatio < nextRatio) { 1886 // the saved is better than the last found 1887 rld = rldSave; 1888 trackTemp = trackSave; 1889 finalDestinationTrackTemp = finalDestinationTrackSave; 1890 } 1891 } 1892 // every time through, save the best route destination, and 1893 // track 1894 rldSave = rld; 1895 trackSave = trackTemp; 1896 finalDestinationTrackSave = finalDestinationTrackTemp; 1897 } 1898 } 1899 // did we find a destination? 1900 if (trackSave != null && rldSave != null) { 1901 // determine if local staging move is allowed (leaves car in staging) 1902 if ((getTrain().isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) && 1903 rl.isDropAllowed() && 1904 rl.getLocation().isStaging() && 1905 trackSave.isStaging() && 1906 rl.getLocation() == rldSave.getLocation() && 1907 !getTrain().isLocalSwitcher() && 1908 !car.isPassenger() && 1909 !car.isCaboose() && 1910 !car.hasFred()) { 1911 addLine(SEVEN, 1912 Bundle.getMessage("buildLeaveCarInStaging", car.toString(), car.getLocationName(), 1913 car.getTrackName())); 1914 rldSave = rl; // make local move 1915 } else if (trackSave.isSpur()) { 1916 car.setScheduleItemId(trackSave.getScheduleItemId()); 1917 trackSave.bumpSchedule(); 1918 log.debug("Sending car to spur ({}, {}) with car schedule id ({}))", trackSave.getLocation().getName(), 1919 trackSave.getName(), car.getScheduleItemId()); 1920 } else { 1921 car.setScheduleItemId(Car.NONE); 1922 } 1923 if (finalDestinationTrackSave != null) { 1924 car.setFinalDestination(finalDestinationTrackSave.getLocation()); 1925 car.setFinalDestinationTrack(finalDestinationTrackSave); 1926 if (trackSave.isAlternate()) { 1927 finalDestinationTrackSave.bumpMoves(); // bump move count 1928 } 1929 } 1930 addCarToTrain(car, rl, rldSave, trackSave); 1931 return true; 1932 } 1933 addLine(FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1934 addLine(FIVE, BLANK_LINE); 1935 return false; // no build errors, but car not given destination 1936 } 1937 1938 /** 1939 * Add car to train, and adjust train length and weight 1940 * 1941 * @param car the car being added to the train 1942 * @param rl the departure route location for this car 1943 * @param rld the destination route location for this car 1944 * @param track the destination track for this car 1945 */ 1946 protected void addCarToTrain(Car car, RouteLocation rl, RouteLocation rld, Track track) { 1947 car = checkQuickServiceArrival(car, rld, track); 1948 addLine(THREE, 1949 Bundle.getMessage("buildCarAssignedDest", car.toString(), rld.getName(), track.getName())); 1950 car.setDestination(track.getLocation(), track, Car.FORCE); 1951 int length = car.getTotalLength(); 1952 int weightTons = car.getAdjustedWeightTons(); 1953 // car could be part of a kernel 1954 if (car.getKernel() != null) { 1955 length = car.getKernel().getTotalLength(); // includes couplers 1956 weightTons = car.getKernel().getAdjustedWeightTons(); 1957 List<Car> kCars = car.getKernel().getCars(); 1958 addLine(THREE, 1959 Bundle.getMessage("buildCarPartOfKernel", car.toString(), car.getKernelName(), kCars.size(), 1960 car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 1961 for (Car kCar : kCars) { 1962 if (kCar != car) { 1963 addLine(THREE, Bundle.getMessage("buildCarKernelAssignedDest", kCar.toString(), 1964 kCar.getKernelName(), rld.getName(), track.getName())); 1965 kCar.setTrain(getTrain()); 1966 kCar.setRouteLocation(rl); 1967 kCar.setRouteDestination(rld); 1968 kCar.setDestination(track.getLocation(), track, Car.FORCE); // force destination 1969 // save final destination and track values in case of train reset 1970 kCar.setPreviousFinalDestination(car.getPreviousFinalDestination()); 1971 kCar.setPreviousFinalDestinationTrack(car.getPreviousFinalDestinationTrack()); 1972 } 1973 } 1974 car.updateKernel(); 1975 } 1976 // warn if car's load wasn't generated out of staging 1977 if (!getTrain().isLoadNameAccepted(car.getLoadName(), car.getTypeName())) { 1978 _warnings++; 1979 addLine(SEVEN, 1980 Bundle.getMessage("buildWarnCarDepartStaging", car.toString(), car.getLoadName())); 1981 } 1982 addLine(THREE, BLANK_LINE); 1983 _numberCars++; // bump number of cars moved by this train 1984 _completedMoves++; // bump number of car pick up moves for the location 1985 _reqNumOfMoves--; // decrement number of moves left for the location 1986 1987 remove(car); // remove car from list 1988 1989 rl.setCarMoves(rl.getCarMoves() + 1); 1990 if (rl != rld) { 1991 rld.setCarMoves(rld.getCarMoves() + 1); 1992 } 1993 // now adjust train length and weight for each location that car is in 1994 // the train 1995 finishAddRsToTrain(car, rl, rld, length, weightTons); 1996 } 1997 1998 /** 1999 * Checks to see if cars that are already in the train can be redirected 2000 * from the alternate track to the spur that really wants the car. Fixes the 2001 * issue of having cars placed at the alternate when the spur's cars get 2002 * pulled by this train, but cars were sent to the alternate because the 2003 * spur was full at the time it was tested. 2004 * 2005 * @return true if one or more cars were redirected 2006 * @throws BuildFailedException if coding issue 2007 */ 2008 protected boolean redirectCarsFromAlternateTrack() throws BuildFailedException { 2009 // code check, should be aggressive 2010 if (!Setup.isBuildAggressive()) { 2011 throw new BuildFailedException("ERROR coding issue, should be using aggressive mode"); 2012 } 2013 boolean redirected = false; 2014 List<Car> cars = carManager.getByTrainList(getTrain()); 2015 for (Car car : cars) { 2016 // does the car have a final destination and the destination is this 2017 // one? 2018 if (car.getFinalDestination() == null || 2019 car.getFinalDestinationTrack() == null || 2020 !car.getFinalDestinationName().equals(car.getDestinationName())) { 2021 continue; 2022 } 2023 Track alternate = car.getFinalDestinationTrack().getAlternateTrack(); 2024 if (alternate == null || car.getDestinationTrack() != alternate) { 2025 continue; 2026 } 2027 // is the car in a kernel? 2028 if (car.getKernel() != null && !car.isLead()) { 2029 continue; 2030 } 2031 log.debug("Car ({}) alternate track ({}) has final destination track ({}) location ({})", car.toString(), 2032 car.getDestinationTrackName(), car.getFinalDestinationTrackName(), car.getDestinationName()); // NOI18N 2033 if ((alternate.isYard() || alternate.isInterchange()) && 2034 car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack()) 2035 .equals(Track.OKAY) && 2036 checkReserved(getTrain(), car.getRouteDestination(), car, car.getFinalDestinationTrack(), false) 2037 .equals(Track.OKAY) && 2038 checkDropTrainDirection(car, car.getRouteDestination(), car.getFinalDestinationTrack()) && 2039 checkTrainCanDrop(car, car.getFinalDestinationTrack())) { 2040 log.debug("Car ({}) alternate track ({}) can be redirected to final destination track ({})", 2041 car.toString(), car.getDestinationTrackName(), car.getFinalDestinationTrackName()); 2042 if (car.getKernel() != null) { 2043 for (Car k : car.getKernel().getCars()) { 2044 if (k.isLead()) { 2045 continue; 2046 } 2047 addLine(FIVE, 2048 Bundle.getMessage("buildRedirectFromAlternate", car.getFinalDestinationName(), 2049 car.getFinalDestinationTrackName(), k.toString(), 2050 car.getDestinationTrackName())); 2051 // force car to track 2052 k.setDestination(car.getFinalDestination(), car.getFinalDestinationTrack(), Car.FORCE); 2053 } 2054 } 2055 addLine(FIVE, 2056 Bundle.getMessage("buildRedirectFromAlternate", car.getFinalDestinationName(), 2057 car.getFinalDestinationTrackName(), 2058 car.toString(), car.getDestinationTrackName())); 2059 car.setDestination(car.getFinalDestination(), car.getFinalDestinationTrack(), Car.FORCE); 2060 // check for quick service 2061 checkQuickServiceRedirected(car); 2062 redirected = true; 2063 } 2064 } 2065 return redirected; 2066 } 2067 2068 /* 2069 * Checks to see if the redirected car is going to a track with quick 2070 * service. The car in this case has already been assigned to the train. 2071 * This routine will create clones if needed, and allow the car to be 2072 * reassigned to the same train. Only lead car in a kernel is allowed. 2073 */ 2074 private void checkQuickServiceRedirected(Car car) { 2075 if (car.getDestinationTrack().isQuickServiceEnabled()) { 2076 RouteLocation rl = car.getRouteLocation(); 2077 RouteLocation rld = car.getRouteDestination(); 2078 Track track = car.getDestinationTrack(); 2079 // remove cars from train 2080 if (car.getKernel() != null) { 2081 for (Car kar : car.getKernel().getCars()) 2082 kar.reset(); 2083 } else { 2084 car.reset(); 2085 } 2086 getCarList().add(0, car); 2087 addCarToTrain(car, rl, rld, track); 2088 } 2089 } 2090 2091 /** 2092 * Checks to see if track is requesting a quick service. Since it isn't 2093 * possible for a car to be pulled and set out twice, this code creates a 2094 * "clone" car to create the requested Manifest. A car could have multiple 2095 * clones, therefore each clone has a creation order number appended to its 2096 * road number. Clones are used to restore a car's location and load in the 2097 * case of reset. 2098 * 2099 * @param car the car possibly needing quick service 2100 * @param track the destination track 2101 * @return the car if not quick service, or a clone if quick service 2102 */ 2103 private Car checkQuickServiceArrival(Car car, RouteLocation rld, Track track) { 2104 if (!track.isQuickServiceEnabled()) { 2105 if (Setup.isBuildOnTime()) { 2106 addLine(THREE, 2107 Bundle.getMessage("buildTrackNotQuickService", StringUtils.capitalize(track.getTrackTypeName()), 2108 track.getLocation().getName(), track.getName(), car.toString())); 2109 // warn if departing staging that is quick serviced enabled 2110 if (car.getTrack().isStaging() && car.getTrack().isQuickServiceEnabled()) { 2111 _warnings++; 2112 addLine(THREE, 2113 Bundle.getMessage("buildWarningQuickService", car.toString(), 2114 car.getTrack().getTrackTypeName(), 2115 car.getTrack().getLocation().getName(), car.getTrack().getName(), 2116 getTrain().getName(), StringUtils.capitalize(car.getTrack().getTrackTypeName()))); 2117 } 2118 } 2119 return car; 2120 } 2121 // quick service enabled, create clones 2122 Car cloneCar = carManager.createClone(car, track, getTrain(), getStartTime()); 2123 addLine(FIVE, 2124 Bundle.getMessage("buildTrackQuickService", StringUtils.capitalize(track.getTrackTypeName()), 2125 track.getLocation().getName(), track.getName(), cloneCar.toString(), car.toString())); 2126 // for timing, use arrival times for the train that is building 2127 // other trains will use their departure time, loaded when creating the Manifest 2128 String expectedArrivalTime = getTrain().getExpectedArrivalTime(rld, true); 2129 cloneCar.setSetoutTime(expectedArrivalTime); 2130 track.scheduleNext(car); // apply schedule to car 2131 car.loadNext(track); // update load, wait count 2132 if (car.getWait() > 0) { 2133 remove(car); // available for next train 2134 addLine(FIVE, Bundle.getMessage("buildExcludeCarWait", car.toString(), 2135 car.getTypeName(), car.getLocationName(), car.getTrackName(), car.getWait())); 2136 car.setWait(car.getWait() - 1); 2137 car.updateLoad(track); 2138 } 2139 // remember where in the route the car was delivered 2140 car.setRouteDestination(rld); 2141 car.updateKernel(); 2142 return cloneCar; // return clone 2143 } 2144 2145 private static final Logger log = LoggerFactory.getLogger(TrainBuilderCars.class); 2146}