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