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