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