001package jmri.jmrit.operations.trains; 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; 018 019/** 020 * Contains methods for cars when building a train. 021 * 022 * @author Daniel Boudreau Copyright (C) 2022 023 */ 024public class TrainBuilderCars extends TrainBuilderEngines { 025 026 /** 027 * Find a caboose if needed at the correct location and add it to the train. 028 * If departing staging, all cabooses are added to the train. If there isn't 029 * a road name required for the caboose, tries to find a caboose with the 030 * same road name as the lead engine. 031 * 032 * @param roadCaboose Optional road name for this car. 033 * @param leadEngine The lead engine for this train. Used to find a 034 * caboose with the same road name as the engine. 035 * @param rl Where in the route to pick up this car. 036 * @param rld Where in the route to set out this car. 037 * @param requiresCaboose When true, the train requires a caboose. 038 * @throws BuildFailedException If car not found. 039 */ 040 protected void getCaboose(String roadCaboose, Engine leadEngine, RouteLocation rl, RouteLocation rld, 041 boolean requiresCaboose) throws BuildFailedException { 042 // code check 043 if (rl == null) { 044 throw new BuildFailedException(Bundle.getMessage("buildErrorCabooseNoLocation", _train.getName())); 045 } 046 // code check 047 if (rld == null) { 048 throw new BuildFailedException( 049 Bundle.getMessage("buildErrorCabooseNoDestination", _train.getName(), rl.getName())); 050 } 051 // load departure track if staging 052 Track departTrack = null; 053 if (rl == _train.getTrainDepartsRouteLocation()) { 054 departTrack = _departStageTrack; // can be null 055 } 056 if (!requiresCaboose) { 057 addLine(_buildReport, FIVE, 058 Bundle.getMessage("buildTrainNoCaboose", rl.getName())); 059 if (departTrack == null) { 060 return; 061 } 062 } else { 063 addLine(_buildReport, ONE, Bundle.getMessage("buildTrainReqCaboose", _train.getName(), roadCaboose, 064 rl.getName(), rld.getName())); 065 } 066 067 // Now go through the car list looking for cabooses 068 boolean cabooseTip = true; // add a user tip to the build report about 069 // cabooses if none found 070 boolean cabooseAtDeparture = false; // set to true if caboose at 071 // departure location is found 072 boolean foundCaboose = false; 073 for (_carIndex = 0; _carIndex < _carList.size(); _carIndex++) { 074 Car car = _carList.get(_carIndex); 075 if (!car.isCaboose()) { 076 continue; 077 } 078 showCarServiceOrder(car); 079 080 cabooseTip = false; // found at least one caboose, so they exist! 081 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarIsCaboose", car.toString(), car.getRoadName(), 082 car.getLocationName(), car.getTrackName())); 083 // car departing staging must leave with train 084 if (car.getTrack() == departTrack) { 085 foundCaboose = false; 086 if (!generateCarLoadFromStaging(car, rld)) { 087 // departing and terminating into staging? 088 if (car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() && 089 rld.getLocation() == _terminateLocation && 090 _terminateStageTrack != null) { 091 // try and generate a custom load for this caboose 092 generateLoadCarDepartingAndTerminatingIntoStaging(car, _terminateStageTrack); 093 } 094 } 095 if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) { 096 if (car.getTrain() == _train) { 097 foundCaboose = true; 098 } 099 } else if (findDestinationAndTrack(car, rl, rld)) { 100 foundCaboose = true; 101 } 102 if (!foundCaboose) { 103 throw new BuildFailedException(Bundle.getMessage("buildErrorCarStageDest", car.toString())); 104 } 105 // is there a specific road requirement for the caboose? 106 } else if (!roadCaboose.equals(Train.NONE) && !roadCaboose.equals(car.getRoadName())) { 107 continue; 108 } else if (!foundCaboose && car.getLocationName().equals(rl.getName())) { 109 // remove cars that can't be picked up due to train and track 110 // directions 111 if (!checkPickUpTrainDirection(car, rl)) { 112 addLine(_buildReport, SEVEN, 113 Bundle.getMessage("buildExcludeCarTypeAtLoc", car.toString(), car.getTypeName(), 114 car.getTypeExtensions(), car.getLocationName(), car.getTrackName())); 115 _carList.remove(car); // remove this car from the list 116 _carIndex--; 117 continue; 118 } 119 // first pass, find a caboose that matches the engine road 120 if (leadEngine != null && car.getRoadName().equals(leadEngine.getRoadName())) { 121 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCabooseRoadMatches", car.toString(), 122 car.getRoadName(), leadEngine.toString())); 123 if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) { 124 if (car.getTrain() == _train) { 125 foundCaboose = true; 126 } 127 } else if (findDestinationAndTrack(car, rl, rld)) { 128 foundCaboose = true; 129 } 130 if (!foundCaboose) { 131 _carList.remove(car); // remove this car from the list 132 _carIndex--; 133 continue; 134 } 135 } 136 // done if we found a caboose and not departing staging 137 if (foundCaboose && departTrack == null) { 138 break; 139 } 140 } 141 } 142 // second pass, take a caboose with a road name that is "similar" 143 // (hyphen feature) to the engine road name 144 if (requiresCaboose && !foundCaboose && roadCaboose.equals(Train.NONE)) { 145 log.debug("Second pass looking for caboose"); 146 for (Car car : _carList) { 147 if (car.isCaboose() && car.getLocationName().equals(rl.getName())) { 148 if (leadEngine != null && 149 TrainCommon.splitString(car.getRoadName()) 150 .equals(TrainCommon.splitString(leadEngine.getRoadName()))) { 151 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCabooseRoadMatches", car.toString(), 152 car.getRoadName(), leadEngine.toString())); 153 if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) { 154 if (car.getTrain() == _train) { 155 foundCaboose = true; 156 break; 157 } 158 } else if (findDestinationAndTrack(car, rl, rld)) { 159 foundCaboose = true; 160 break; 161 } 162 } 163 } 164 } 165 } 166 // third pass, take any caboose unless a caboose road name is specified 167 if (requiresCaboose && !foundCaboose) { 168 log.debug("Third pass looking for caboose"); 169 for (Car car : _carList) { 170 if (!car.isCaboose()) { 171 continue; 172 } 173 if (car.getLocationName().equals(rl.getName())) { 174 // is there a specific road requirement for the caboose? 175 if (!roadCaboose.equals(Train.NONE) && !roadCaboose.equals(car.getRoadName())) { 176 continue; // yes 177 } 178 // okay, we found a caboose at the departure location 179 cabooseAtDeparture = true; 180 if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) { 181 if (car.getTrain() == _train) { 182 foundCaboose = true; 183 break; 184 } 185 } else if (findDestinationAndTrack(car, rl, rld)) { 186 foundCaboose = true; 187 break; 188 } 189 } 190 } 191 } 192 if (requiresCaboose && !foundCaboose) { 193 if (cabooseTip) { 194 addLine(_buildReport, ONE, Bundle.getMessage("buildNoteCaboose")); 195 addLine(_buildReport, ONE, Bundle.getMessage("buildNoteCaboose2")); 196 } 197 if (!cabooseAtDeparture) { 198 throw new BuildFailedException(Bundle.getMessage("buildErrorReqDepature", _train.getName(), 199 Bundle.getMessage("Caboose").toLowerCase(), rl.getName())); 200 } 201 // we did find a caboose at departure that meet requirements, but 202 // couldn't place it at destination. 203 throw new BuildFailedException(Bundle.getMessage("buildErrorReqDest", _train.getName(), 204 Bundle.getMessage("Caboose"), rld.getName())); 205 } 206 } 207 208 /** 209 * Find a car with FRED if needed at the correct location and adds the car 210 * to the train. If departing staging, will make sure all cars with FRED are 211 * added to the train. 212 * 213 * @param road Optional road name for this car. 214 * @param rl Where in the route to pick up this car. 215 * @param rld Where in the route to set out this car. 216 * @throws BuildFailedException If car not found. 217 */ 218 protected void getCarWithFred(String road, RouteLocation rl, RouteLocation rld) throws BuildFailedException { 219 // load departure track if staging 220 Track departTrack = null; 221 if (rl == _train.getTrainDepartsRouteLocation()) { 222 departTrack = _departStageTrack; 223 } 224 boolean foundCarWithFred = false; 225 if (_train.isFredNeeded()) { 226 addLine(_buildReport, ONE, 227 Bundle.getMessage("buildTrainReqFred", _train.getName(), road, rl.getName(), rld.getName())); 228 } else { 229 addLine(_buildReport, FIVE, Bundle.getMessage("buildTrainNoFred")); 230 // if not departing staging we're done 231 if (departTrack == null) { 232 return; 233 } 234 } 235 for (_carIndex = 0; _carIndex < _carList.size(); _carIndex++) { 236 Car car = _carList.get(_carIndex); 237 if (!car.hasFred()) { 238 continue; 239 } 240 showCarServiceOrder(car); 241 addLine(_buildReport, SEVEN, 242 Bundle.getMessage("buildCarHasFRED", car.toString(), car.getRoadName(), car.getLocationName(), 243 car.getTrackName())); 244 // all cars with FRED departing staging must leave with train 245 if (car.getTrack() == departTrack) { 246 foundCarWithFred = false; 247 if (!generateCarLoadFromStaging(car, rld)) { 248 // departing and terminating into staging? 249 if (car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() && 250 rld.getLocation() == _terminateLocation && 251 _terminateStageTrack != null) { 252 // try and generate a custom load for this car with FRED 253 generateLoadCarDepartingAndTerminatingIntoStaging(car, _terminateStageTrack); 254 } 255 } 256 if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) { 257 if (car.getTrain() == _train) { 258 foundCarWithFred = true; 259 } 260 } else if (findDestinationAndTrack(car, rl, rld)) { 261 foundCarWithFred = true; 262 } 263 if (!foundCarWithFred) { 264 throw new BuildFailedException(Bundle.getMessage("buildErrorCarStageDest", car.toString())); 265 } 266 } // is there a specific road requirement for the car with FRED? 267 else if (!road.equals(Train.NONE) && !road.equals(car.getRoadName())) { 268 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeCarWrongRoad", car.toString(), 269 car.getLocationName(), car.getTrackName(), car.getTypeName(), car.getRoadName())); 270 _carList.remove(car); // remove this car from the list 271 _carIndex--; 272 continue; 273 } else if (!foundCarWithFred && car.getLocationName().equals(rl.getName())) { 274 // remove cars that can't be picked up due to train and track 275 // directions 276 if (!checkPickUpTrainDirection(car, rl)) { 277 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeCarTypeAtLoc", car.toString(), 278 car.getTypeName(), car.getTypeExtensions(), car.getLocationName(), car.getTrackName())); 279 _carList.remove(car); // remove this car from the list 280 _carIndex--; 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("buildRouteNoLocalLocation", _train.getRoute().getName(), 504 rl.getId(), rl.getName())); 505 } 506 // can this car be picked up? 507 if (!checkPickUpTrainDirection(car, rl)) { 508 addLine(_buildReport, FIVE, BLANK_LINE); 509 continue; // no 510 } 511 512 showCarServiceOrder(car); // car on FIFO or LIFO track? 513 514 // is car departing staging and generate custom load? 515 if (!generateCarLoadFromStaging(car)) { 516 if (!generateCarLoadStagingToStaging(car) && 517 car.getTrack() == _departStageTrack && 518 !_departStageTrack.isLoadNameAndCarTypeShipped(car.getLoadName(), car.getTypeName())) { 519 // report build failure car departing staging with a 520 // restricted load 521 addLine(_buildReport, ONE, Bundle.getMessage("buildErrorCarStageLoad", car.toString(), 522 car.getLoadName(), _departStageTrack.getName())); 523 addLine(_buildReport, FIVE, BLANK_LINE); 524 continue; // keep going and see if there are other cars with 525 // issues outs of staging 526 } 527 } 528 // If car been given a home division follow division rules for car 529 // movement. 530 if (!findDestinationsForCarsWithHomeDivision(car)) { 531 addLine(_buildReport, FIVE, 532 Bundle.getMessage("buildNoDestForCar", car.toString())); 533 addLine(_buildReport, FIVE, BLANK_LINE); 534 continue; // hold car at current location 535 } 536 // does car have a custom load without a destination? 537 // if departing staging, a destination for this car is needed, so 538 // keep going 539 if (findFinalDestinationForCarLoad(car) && 540 car.getDestination() == null && 541 car.getTrack() != _departStageTrack) { 542 // done with this car, it has a custom load, and there are 543 // spurs/schedules, but no destination found 544 addLine(_buildReport, FIVE, 545 Bundle.getMessage("buildNoDestForCar", car.toString())); 546 addLine(_buildReport, FIVE, BLANK_LINE); 547 continue; 548 } 549 // Check car for final destination, then an assigned destination, if 550 // neither, find a destination for the car 551 if (checkCarForFinalDestination(car)) { 552 log.debug("Car ({}) has a final desination that can't be serviced by train", car.toString()); 553 } else if (checkCarForDestination(car, rl, _routeList.indexOf(rl))) { 554 // car had a destination, could have been added to the train. 555 log.debug("Car ({}) has desination ({}) using train ({})", car.toString(), car.getDestinationName(), 556 car.getTrainName()); 557 } else { 558 findDestinationAndTrack(car, rl, _routeList.indexOf(rl), _routeList.size()); 559 } 560 if (_reqNumOfMoves <= 0) { 561 break; // done 562 } 563 // build failure if car departing staging without a destination and 564 // a train we'll just put out a warning message here so we can find 565 // out how many cars have issues 566 if (car.getTrack() == _departStageTrack && 567 (car.getDestination() == null || car.getDestinationTrack() == null || car.getTrain() == null)) { 568 addLine(_buildReport, ONE, Bundle.getMessage("buildWarningCarStageDest", car.toString())); 569 // does the car have a final destination to staging? If so we 570 // need to reset this car 571 if (car.getFinalDestinationTrack() != null && car.getFinalDestinationTrack() == _terminateStageTrack) { 572 addLine(_buildReport, THREE, 573 Bundle.getMessage("buildStagingCarHasFinal", car.toString(), car.getFinalDestinationName(), 574 car.getFinalDestinationTrackName())); 575 car.reset(); 576 } 577 addLine(_buildReport, SEVEN, BLANK_LINE); 578 } 579 } 580 if (!foundCar && !isSecondPass) { 581 addLine(_buildReport, FIVE, Bundle.getMessage("buildNoCarsAtLocation", rl.getName())); 582 addLine(_buildReport, FIVE, BLANK_LINE); 583 } 584 } 585 586 private boolean generateCarLoadFromStaging(Car car) throws BuildFailedException { 587 return generateCarLoadFromStaging(car, null); 588 } 589 590 /** 591 * Used to generate a car's load from staging. Search for a spur with a 592 * schedule and load car if possible. 593 * 594 * @param car the car 595 * @param rld The route location destination for this car. Can be null. 596 * @return true if car given a custom load 597 * @throws BuildFailedException If code check fails 598 */ 599 private boolean generateCarLoadFromStaging(Car car, RouteLocation rld) throws BuildFailedException { 600 // Code Check, car should have a track assignment 601 if (car.getTrack() == null) { 602 throw new BuildFailedException( 603 Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName())); 604 } 605 if (!car.getTrack().isStaging() || 606 (!car.getTrack().isAddCustomLoadsAnySpurEnabled() && !car.getTrack().isAddCustomLoadsEnabled()) || 607 !car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 608 car.getDestination() != null || 609 car.getFinalDestination() != null) { 610 log.debug( 611 "No load generation for car ({}) isAddLoadsAnySpurEnabled: {}, car load ({}) destination ({}) final destination ({})", 612 car.toString(), car.getTrack().isAddCustomLoadsAnySpurEnabled() ? "true" : "false", 613 car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName()); 614 // if car has a destination or final destination add "no load 615 // generated" message to report 616 if (car.getTrack().isStaging() && 617 car.getTrack().isAddCustomLoadsAnySpurEnabled() && 618 car.getLoadName().equals(carLoads.getDefaultEmptyName())) { 619 addLine(_buildReport, FIVE, 620 Bundle.getMessage("buildCarNoLoadGenerated", car.toString(), car.getLoadName(), 621 car.getDestinationName(), car.getFinalDestinationName())); 622 } 623 return false; // no load generated for this car 624 } 625 addLine(_buildReport, FIVE, 626 Bundle.getMessage("buildSearchTrackNewLoad", car.toString(), car.getTypeName(), 627 car.getLoadType().toLowerCase(), car.getLoadName(), car.getLocationName(), car.getTrackName(), 628 rld != null ? rld.getLocation().getName() : "")); 629 // check to see if car type has custom loads 630 if (carLoads.getNames(car.getTypeName()).size() == 2) { 631 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarNoCustomLoad", car.toString(), car.getTypeName())); 632 return false; 633 } 634 if (car.getKernel() != null) { 635 addLine(_buildReport, SEVEN, 636 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 637 car.getKernel().getSize(), car.getKernel().getTotalLength(), 638 Setup.getLengthUnit().toLowerCase())); 639 } 640 // save the car's load, should be the default empty 641 String oldCarLoad = car.getLoadName(); 642 List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR); 643 log.debug("Found {} spurs", tracks.size()); 644 // show locations not serviced by departure track once 645 List<Location> locationsNotServiced = new ArrayList<>(); 646 for (Track track : tracks) { 647 if (locationsNotServiced.contains(track.getLocation())) { 648 continue; 649 } 650 if (rld != null && track.getLocation() != rld.getLocation()) { 651 locationsNotServiced.add(track.getLocation()); 652 continue; 653 } 654 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 655 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 656 track.getLocation().getName(), car.getTrackName())); 657 locationsNotServiced.add(track.getLocation()); 658 continue; 659 } 660 // only use tracks serviced by this train? 661 if (car.getTrack().isAddCustomLoadsEnabled() && 662 !_train.getRoute().isLocationNameInRoute(track.getLocation().getName())) { 663 continue; 664 } 665 // only the first match in a schedule is used for a spur 666 ScheduleItem si = getScheduleItem(car, track); 667 if (si == null) { 668 continue; // no match 669 } 670 // need to set car load so testDestination will work properly 671 car.setLoadName(si.getReceiveLoadName()); 672 car.setScheduleItemId(si.getId()); 673 String status = car.checkDestination(track.getLocation(), track); 674 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 675 addLine(_buildReport, SEVEN, 676 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 677 track.getLocation().getName(), track.getName(), car.toString(), si.getReceiveLoadName(), 678 status)); 679 continue; 680 } 681 addLine(_buildReport, SEVEN, Bundle.getMessage("buildTrySpurLoad", track.getLocation().getName(), 682 track.getName(), car.getLoadName())); 683 // does the car have a home division? 684 if (car.getDivision() != null) { 685 addLine(_buildReport, SEVEN, 686 Bundle.getMessage("buildCarHasDivisionStaging", car.toString(), car.getTypeName(), 687 car.getLoadType().toLowerCase(), car.getLoadName(), car.getDivisionName(), 688 car.getLocationName(), car.getTrackName(), car.getTrack().getDivisionName())); 689 // load type empty must return to car's home division 690 // or load type load from foreign division must return to car's 691 // home division 692 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) && car.getDivision() != track.getDivision() || 693 car.getLoadType().equals(CarLoad.LOAD_TYPE_LOAD) && 694 car.getTrack().getDivision() != car.getDivision() && 695 car.getDivision() != track.getDivision()) { 696 addLine(_buildReport, SEVEN, 697 Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(), 698 track.getLocation().getName(), track.getName(), track.getDivisionName(), 699 car.toString(), car.getLoadType().toLowerCase(), car.getLoadName())); 700 continue; 701 } 702 } 703 if (!track.isSpaceAvailable(car)) { 704 addLine(_buildReport, SEVEN, 705 Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(), 706 track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(), 707 Setup.getLengthUnit().toLowerCase(), track.getReservationFactor())); 708 continue; 709 } 710 // try routing car 711 car.setFinalDestination(track.getLocation()); 712 car.setFinalDestinationTrack(track); 713 if (router.setDestination(car, _train, _buildReport) && car.getDestination() != null) { 714 // return car with this custom load and destination 715 addLine(_buildReport, FIVE, 716 Bundle.getMessage("buildCreateNewLoadForCar", car.toString(), si.getReceiveLoadName(), 717 track.getLocation().getName(), track.getName())); 718 car.setLoadGeneratedFromStaging(true); 719 // is car part of kernel? 720 car.updateKernel(); 721 track.bumpMoves(); 722 track.bumpSchedule(); 723 return true; // done, car now has a custom load 724 } 725 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotRouteCar", car.toString(), 726 si.getReceiveLoadName(), track.getLocation().getName(), track.getName())); 727 addLine(_buildReport, SEVEN, BLANK_LINE); 728 car.setDestination(null, null); 729 car.setFinalDestination(null); 730 car.setFinalDestinationTrack(null); 731 } 732 // restore car's load 733 car.setLoadName(oldCarLoad); 734 car.setScheduleItemId(Car.NONE); 735 addLine(_buildReport, FIVE, Bundle.getMessage("buildUnableNewLoad", car.toString())); 736 return false; // done, no load generated for this car 737 } 738 739 /** 740 * Tries to place a custom load in the car that is departing staging and 741 * attempts to find a destination for the car that is also staging. 742 * 743 * @param car the car 744 * @return True if custom load added to car 745 * @throws BuildFailedException If code check fails 746 */ 747 private boolean generateCarLoadStagingToStaging(Car car) throws BuildFailedException { 748 // Code Check, car should have a track assignment 749 if (car.getTrack() == null) { 750 throw new BuildFailedException( 751 Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName())); 752 } 753 if (!car.getTrack().isStaging() || 754 !car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() || 755 !car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 756 car.getDestination() != null || 757 car.getFinalDestination() != null) { 758 log.debug( 759 "No load generation for car ({}) isAddCustomLoadsAnyStagingTrackEnabled: {}, car load ({}) destination ({}) final destination ({})", 760 car.toString(), car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() ? "true" : "false", 761 car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName()); 762 return false; 763 } 764 // check to see if car type has custom loads 765 if (carLoads.getNames(car.getTypeName()).size() == 2) { 766 return false; 767 } 768 List<Track> tracks = locationManager.getTracks(Track.STAGING); 769 addLine(_buildReport, FIVE, Bundle.getMessage("buildTryStagingToStaging", car.toString(), tracks.size())); 770 if (Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED)) { 771 for (Track track : tracks) { 772 addLine(_buildReport, SEVEN, 773 Bundle.getMessage("buildStagingLocationTrack", track.getLocation().getName(), track.getName())); 774 } 775 } 776 // list of locations that can't be reached by the router 777 List<Location> locationsNotServiced = new ArrayList<>(); 778 if (_terminateStageTrack != null) { 779 addLine(_buildReport, SEVEN, 780 Bundle.getMessage("buildIgnoreStagingFirstPass", _terminateStageTrack.getLocation().getName())); 781 locationsNotServiced.add(_terminateStageTrack.getLocation()); 782 } 783 while (tracks.size() > 0) { 784 // pick a track randomly 785 int rnd = (int) (Math.random() * tracks.size()); 786 Track track = tracks.get(rnd); 787 tracks.remove(track); 788 log.debug("Try staging track ({}, {})", track.getLocation().getName(), track.getName()); 789 // find a staging track that isn't at the departure 790 if (track.getLocation() == _departLocation) { 791 log.debug("Can't use departure location ({})", track.getLocation().getName()); 792 continue; 793 } 794 if (!_train.isAllowThroughCarsEnabled() && track.getLocation() == _terminateLocation) { 795 log.debug("Through cars to location ({}) not allowed", track.getLocation().getName()); 796 continue; 797 } 798 if (locationsNotServiced.contains(track.getLocation())) { 799 log.debug("Location ({}) not reachable", track.getLocation().getName()); 800 continue; 801 } 802 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 803 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 804 track.getLocation().getName(), car.getTrackName())); 805 locationsNotServiced.add(track.getLocation()); 806 continue; 807 } 808 // the following method sets the Car load generated from staging 809 // boolean 810 if (generateLoadCarDepartingAndTerminatingIntoStaging(car, track)) { 811 // test to see if destination is reachable by this train 812 if (router.setDestination(car, _train, _buildReport) && car.getDestination() != null) { 813 return true; // done, car has a custom load and a final 814 // destination 815 } 816 addLine(_buildReport, SEVEN, Bundle.getMessage("buildStagingTrackNotReachable", 817 track.getLocation().getName(), track.getName(), car.getLoadName())); 818 // return car to original state 819 car.setLoadName(carLoads.getDefaultEmptyName()); 820 car.setLoadGeneratedFromStaging(false); 821 car.setFinalDestination(null); 822 car.updateKernel(); 823 // couldn't route to this staging location 824 locationsNotServiced.add(track.getLocation()); 825 } 826 } 827 // No staging tracks reachable, try the track the train is terminating 828 // to 829 if (_train.isAllowThroughCarsEnabled() && 830 _terminateStageTrack != null && 831 car.getTrack().isDestinationAccepted(_terminateStageTrack.getLocation()) && 832 generateLoadCarDepartingAndTerminatingIntoStaging(car, _terminateStageTrack)) { 833 return true; 834 } 835 836 addLine(_buildReport, SEVEN, 837 Bundle.getMessage("buildNoStagingForCarCustom", car.toString())); 838 addLine(_buildReport, SEVEN, BLANK_LINE); 839 return false; 840 } 841 842 /** 843 * Check to see if car has been assigned a home division. If car has a home 844 * division the following rules are applied when assigning the car a 845 * destination: 846 * <p> 847 * If car load is type empty not at car's home division yard: Car is sent to 848 * a home division yard. If home division yard not available, then car is 849 * sent to home division staging, then spur (industry). 850 * <p> 851 * If car load is type empty at a yard at the car's home division: Car is 852 * sent to a home division spur, then home division staging. 853 * <p> 854 * If car load is type load not at car's home division: Car is sent to home 855 * division spur, and if spur not available then home division staging. 856 * <p> 857 * If car load is type load at car's home division: Car is sent to any 858 * division spur or staging. 859 * 860 * @param car the car being checked for a home division 861 * @return false if destination track not found for this car 862 * @throws BuildFailedException 863 */ 864 private boolean findDestinationsForCarsWithHomeDivision(Car car) throws BuildFailedException { 865 if (car.getDivision() == null || car.getDestination() != null || car.getFinalDestination() != null) { 866 return true; 867 } 868 if (car.getDivision() == car.getTrack().getDivision()) { 869 addLine(_buildReport, FIVE, 870 Bundle.getMessage("buildCarDepartHomeDivision", car.toString(), car.getTypeName(), 871 car.getLoadType().toLowerCase(), 872 car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(), 873 car.getLocationName(), car.getTrackName(), 874 car.getTrack().getDivisionName())); 875 } else { 876 addLine(_buildReport, FIVE, 877 Bundle.getMessage("buildCarDepartForeignDivision", car.toString(), car.getTypeName(), 878 car.getLoadType().toLowerCase(), 879 car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(), 880 car.getLocationName(), car.getTrackName(), 881 car.getTrack().getDivisionName())); 882 } 883 if (car.getKernel() != null) { 884 addLine(_buildReport, SEVEN, 885 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 886 car.getKernel().getSize(), 887 car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 888 } 889 // does train terminate into staging? 890 if (_terminateStageTrack != null) { 891 log.debug("Train terminates into staging track ({})", _terminateStageTrack.getName()); 892 // bias cars to staging 893 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) { 894 log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName()); 895 if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) { 896 log.debug("Car ({}) at it's home division yard", car.toString()); 897 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 898 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 899 } 900 } 901 // try to send to home division staging, then home division yard, 902 // then home division spur 903 else if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 904 if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) { 905 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 906 } 907 } 908 } else { 909 log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName()); 910 // 1st send car to staging dependent of shipping track division, then 911 // try spur 912 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, 913 car.getTrack().getDivision() != car.getDivision())) { 914 return sendCarToHomeDivisionTrack(car, Track.SPUR, 915 car.getTrack().getDivision() != car.getDivision()); 916 } 917 } 918 } else { 919 // train doesn't terminate into staging 920 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) { 921 log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName()); 922 if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) { 923 log.debug("Car ({}) at it's home division yard", car.toString()); 924 if (!sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION)) { 925 return sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION); 926 } 927 } 928 // try to send to home division yard, then home division staging, 929 // then home division spur 930 else if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) { 931 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 932 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 933 } 934 } 935 } else { 936 log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName()); 937 // 1st send car to spur dependent of shipping track division, then 938 // try staging 939 if (!sendCarToHomeDivisionTrack(car, Track.SPUR, car.getTrack().getDivision() != car.getDivision())) { 940 return sendCarToHomeDivisionTrack(car, Track.STAGING, 941 car.getTrack().getDivision() != car.getDivision()); 942 } 943 } 944 } 945 return true; 946 } 947 948 private static final boolean HOME_DIVISION = true; 949 950 /** 951 * Tries to set a final destination for the car with a home division. 952 * 953 * @param car the car 954 * @param trackType One of three track types: Track.SPUR Track.YARD or 955 * Track.STAGING 956 * @param home_division If true track's division must match the car's 957 * @return true if car was given a final destination 958 */ 959 private boolean sendCarToHomeDivisionTrack(Car car, String trackType, boolean home_division) { 960 // locations not reachable 961 List<Location> locationsNotServiced = new ArrayList<>(); 962 List<Track> tracks = locationManager.getTracksByMoves(trackType); 963 log.debug("Found {} {} tracks", tracks.size(), trackType); 964 for (Track track : tracks) { 965 if (home_division && car.getDivision() != track.getDivision()) { 966 addLine(_buildReport, SEVEN, 967 Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(), 968 track.getLocation().getName(), track.getName(), track.getDivisionName(), car.toString(), 969 car.getLoadType().toLowerCase(), 970 car.getLoadName())); 971 continue; 972 } 973 if (locationsNotServiced.contains(track.getLocation())) { 974 continue; 975 } 976 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 977 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 978 track.getLocation().getName(), car.getTrackName())); 979 // location not reachable 980 locationsNotServiced.add(track.getLocation()); 981 continue; 982 } 983 // only use the termination staging track for this train 984 if (trackType.equals(Track.STAGING) && 985 _terminateStageTrack != null && 986 track.getLocation() == _terminateLocation && 987 track != _terminateStageTrack) { 988 continue; 989 } 990 if (trackType.equals(Track.SPUR)) { 991 if (sendCarToDestinationSpur(car, track)) { 992 return true; 993 } 994 } else { 995 if (sendCarToDestinationTrack(car, track)) { 996 return true; 997 } 998 } 999 } 1000 addLine(_buildReport, FIVE, 1001 Bundle.getMessage("buildCouldNotFindTrack", trackType.toLowerCase(), car.toString(), 1002 car.getLoadType().toLowerCase(), car.getLoadName())); 1003 addLine(_buildReport, SEVEN, BLANK_LINE); 1004 return false; 1005 } 1006 1007 /** 1008 * Set the final destination and track for a car with a custom load. Car 1009 * must not have a destination or final destination. There's a check to see 1010 * if there's a spur/schedule for this car. Returns true if a schedule was 1011 * found. Will hold car at current location if any of the spurs checked has 1012 * the the option to "Hold cars with custom loads" enabled and the spur has 1013 * an alternate track assigned. Tries to sent the car to staging if there 1014 * aren't any spurs with schedules available. 1015 * 1016 * @param car the car with the load 1017 * @return true if there's a schedule that can be routed to for this car and 1018 * load 1019 * @throws BuildFailedException 1020 */ 1021 private boolean findFinalDestinationForCarLoad(Car car) throws BuildFailedException { 1022 if (car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 1023 car.getLoadName().equals(carLoads.getDefaultLoadName()) || 1024 car.getDestination() != null || 1025 car.getFinalDestination() != null) { 1026 return false; // car doesn't have a custom load, or already has a 1027 // destination set 1028 } 1029 addLine(_buildReport, FIVE, 1030 Bundle.getMessage("buildSearchForSpur", car.toString(), car.getTypeName(), 1031 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(), 1032 car.getTrackName())); 1033 if (car.getKernel() != null) { 1034 addLine(_buildReport, SEVEN, 1035 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 1036 car.getKernel().getSize(), car.getKernel().getTotalLength(), 1037 Setup.getLengthUnit().toLowerCase())); 1038 } 1039 _routeToTrackFound = false; 1040 List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR); 1041 log.debug("Found {} spurs", tracks.size()); 1042 // locations not reachable 1043 List<Location> locationsNotServiced = new ArrayList<>(); 1044 for (Track track : tracks) { 1045 if (car.getTrack() == track) { 1046 continue; 1047 } 1048 if (track.getSchedule() == null) { 1049 addLine(_buildReport, SEVEN, Bundle.getMessage("buildSpurNoSchedule", 1050 track.getLocation().getName(), track.getName())); 1051 continue; 1052 } 1053 if (locationsNotServiced.contains(track.getLocation())) { 1054 continue; 1055 } 1056 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 1057 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 1058 track.getLocation().getName(), car.getTrackName())); 1059 // location not reachable 1060 locationsNotServiced.add(track.getLocation()); 1061 continue; 1062 } 1063 if (sendCarToDestinationSpur(car, track)) { 1064 return true; 1065 } 1066 } 1067 addLine(_buildReport, SEVEN, 1068 Bundle.getMessage("buildCouldNotFindTrack", Track.getTrackTypeName(Track.SPUR).toLowerCase(), 1069 car.toString(), car.getLoadType().toLowerCase(), car.getLoadName())); 1070 if (_routeToTrackFound && 1071 !_train.isSendCarsWithCustomLoadsToStagingEnabled() && 1072 !car.getLocation().isStaging()) { 1073 addLine(_buildReport, SEVEN, Bundle.getMessage("buildHoldCarValidRoute", car.toString(), 1074 car.getLocationName(), car.getTrackName())); 1075 } else { 1076 // try and send car to staging 1077 addLine(_buildReport, FIVE, 1078 Bundle.getMessage("buildTrySendCarToStaging", car.toString(), car.getLoadName())); 1079 tracks = locationManager.getTracks(Track.STAGING); 1080 log.debug("Found {} staging tracks", tracks.size()); 1081 while (tracks.size() > 0) { 1082 // pick a track randomly 1083 int rnd = (int) (Math.random() * tracks.size()); 1084 Track track = tracks.get(rnd); 1085 tracks.remove(track); 1086 log.debug("Staging track ({}, {})", track.getLocation().getName(), track.getName()); 1087 if (track.getLocation() == car.getLocation()) { 1088 continue; 1089 } 1090 if (locationsNotServiced.contains(track.getLocation())) { 1091 continue; 1092 } 1093 if (_terminateStageTrack != null && 1094 track.getLocation() == _terminateLocation && 1095 track != _terminateStageTrack) { 1096 continue; // ignore other staging tracks at terminus 1097 } 1098 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 1099 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 1100 track.getLocation().getName(), car.getTrackName())); 1101 locationsNotServiced.add(track.getLocation()); 1102 continue; 1103 } 1104 String status = track.isRollingStockAccepted(car); 1105 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 1106 log.debug("Staging track ({}) can't accept car ({})", track.getName(), car.toString()); 1107 continue; 1108 } 1109 addLine(_buildReport, SEVEN, Bundle.getMessage("buildStagingCanAcceptLoad", track.getLocation(), 1110 track.getName(), car.getLoadName())); 1111 // try to send car to staging 1112 car.setFinalDestination(track.getLocation()); 1113 // test to see if destination is reachable by this train 1114 if (router.setDestination(car, _train, _buildReport)) { 1115 _routeToTrackFound = true; // found a route to staging 1116 } 1117 if (car.getDestination() != null) { 1118 car.updateKernel(); // car part of kernel? 1119 return true; 1120 } 1121 // couldn't route to this staging location 1122 locationsNotServiced.add(track.getLocation()); 1123 car.setFinalDestination(null); 1124 } 1125 addLine(_buildReport, SEVEN, 1126 Bundle.getMessage("buildNoStagingForCarLoad", car.toString(), car.getLoadName())); 1127 } 1128 log.debug("routeToSpurFound is {}", _routeToTrackFound); 1129 return _routeToTrackFound; // done 1130 } 1131 1132 boolean _routeToTrackFound; 1133 1134 /** 1135 * Used to determine if spur can accept car. Also will set routeToTrackFound 1136 * to true if there's a valid route available to the spur being tested. Sets 1137 * car's final destination to track if okay. 1138 * 1139 * @param car the car 1140 * @param track the spur 1141 * @return false if there's an issue with using the spur 1142 */ 1143 private boolean sendCarToDestinationSpur(Car car, Track track) { 1144 if (!checkBasicMoves(car, track)) { 1145 addLine(_buildReport, SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", _train.getName(), 1146 car.toString(), track.getLocation().getName(), track.getName())); 1147 return false; 1148 } 1149 String status = car.checkDestination(track.getLocation(), track); 1150 if (!status.equals(Track.OKAY)) { 1151 if (track.getScheduleMode() == Track.SEQUENTIAL && status.startsWith(Track.SCHEDULE)) { 1152 addLine(_buildReport, SEVEN, Bundle.getMessage("buildTrackSequentialMode", 1153 track.getLocation().getName(), track.getName(), status)); 1154 } 1155 // if the track has an alternate track don't abort if the issue was 1156 // space 1157 if (!status.startsWith(Track.LENGTH)) { 1158 addLine(_buildReport, SEVEN, 1159 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1160 track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(), 1161 status)); 1162 return false; 1163 } 1164 String scheduleStatus = track.checkSchedule(car); 1165 if (!scheduleStatus.equals(Track.OKAY)) { 1166 addLine(_buildReport, SEVEN, 1167 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1168 track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(), 1169 scheduleStatus)); 1170 return false; 1171 } 1172 if (track.getAlternateTrack() == null) { 1173 // report that the spur is full and no alternate 1174 addLine(_buildReport, SEVEN, 1175 Bundle.getMessage("buildSpurFullNoAlternate", track.getLocation().getName(), track.getName())); 1176 return false; 1177 } else { 1178 addLine(_buildReport, SEVEN, 1179 Bundle.getMessage("buildTrackFullHasAlternate", track.getLocation().getName(), track.getName(), 1180 track.getAlternateTrack().getName())); 1181 // check to see if alternate and track are configured properly 1182 if (!_train.isLocalSwitcher() && 1183 (track.getTrainDirections() & track.getAlternateTrack().getTrainDirections()) == 0) { 1184 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotDropRsUsingTrain4", track.getName(), 1185 formatStringToCommaSeparated(Setup.getDirectionStrings(track.getTrainDirections())), 1186 track.getAlternateTrack().getName(), formatStringToCommaSeparated( 1187 Setup.getDirectionStrings(track.getAlternateTrack().getTrainDirections())))); 1188 return false; 1189 } 1190 } 1191 } 1192 addLine(_buildReport, SEVEN, BLANK_LINE); 1193 addLine(_buildReport, SEVEN, 1194 Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(), 1195 track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(), 1196 car.getLoadName())); 1197 1198 // show if track is requesting cars with custom loads to only go to 1199 // spurs 1200 if (track.isHoldCarsWithCustomLoadsEnabled()) { 1201 addLine(_buildReport, SEVEN, 1202 Bundle.getMessage("buildHoldCarsCustom", track.getLocation().getName(), track.getName())); 1203 } 1204 // check the number of in bound cars to this track 1205 if (!track.isSpaceAvailable(car)) { 1206 // Now determine if we should move the car or just leave it 1207 if (track.isHoldCarsWithCustomLoadsEnabled()) { 1208 // determine if this car can be routed to the spur 1209 String id = track.getScheduleItemId(); 1210 if (router.isCarRouteable(car, _train, track, _buildReport)) { 1211 // hold car if able to route to track 1212 _routeToTrackFound = true; 1213 } else { 1214 addLine(_buildReport, SEVEN, Bundle.getMessage("buildRouteNotFound", car.toString(), 1215 car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1216 } 1217 track.setScheduleItemId(id); // restore id 1218 } 1219 if (car.getTrack().isStaging()) { 1220 addLine(_buildReport, SEVEN, 1221 Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(), 1222 track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(), 1223 Setup.getLengthUnit().toLowerCase(), track.getReservationFactor())); 1224 } else { 1225 addLine(_buildReport, SEVEN, 1226 Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(), 1227 track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(), 1228 track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase())); 1229 } 1230 return false; 1231 } 1232 // try to send car to this spur 1233 car.setFinalDestination(track.getLocation()); 1234 car.setFinalDestinationTrack(track); 1235 // test to see if destination is reachable by this train 1236 if (router.setDestination(car, _train, _buildReport) && track.isHoldCarsWithCustomLoadsEnabled()) { 1237 _routeToTrackFound = true; // if we don't find another spur, don't 1238 // move car 1239 } 1240 if (car.getDestination() == null) { 1241 if (!router.getStatus().equals(Track.OKAY)) { 1242 addLine(_buildReport, SEVEN, 1243 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1244 } 1245 car.setFinalDestination(null); 1246 car.setFinalDestinationTrack(null); 1247 // don't move car if another train can 1248 if (router.getStatus().startsWith(Router.STATUS_NOT_THIS_TRAIN_PREFIX)) { 1249 _routeToTrackFound = true; 1250 } 1251 return false; 1252 } 1253 if (car.getDestinationTrack() != track) { 1254 track.bumpMoves(); 1255 // car is being routed to this track 1256 if (track.getSchedule() != null) { 1257 car.setScheduleItemId(track.getCurrentScheduleItem().getId()); 1258 track.bumpSchedule(); 1259 } 1260 } 1261 car.updateKernel(); 1262 return true; // done, car has a new destination 1263 } 1264 1265 /** 1266 * Destination track can be division yard or staging, NOT a spur. 1267 * 1268 * @param car the car 1269 * @param track the car's destination track 1270 * @return true if car given a new final destination 1271 */ 1272 private boolean sendCarToDestinationTrack(Car car, Track track) { 1273 if (!checkBasicMoves(car, track)) { 1274 addLine(_buildReport, SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", _train.getName(), 1275 car.toString(), track.getLocation().getName(), track.getName())); 1276 return false; 1277 } 1278 String status = car.checkDestination(track.getLocation(), track); 1279 1280 if (!status.equals(Track.OKAY)) { 1281 addLine(_buildReport, SEVEN, 1282 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1283 track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(), status)); 1284 return false; 1285 } 1286 if (!track.isSpaceAvailable(car)) { 1287 addLine(_buildReport, SEVEN, 1288 Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(), 1289 track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(), 1290 track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase())); 1291 return false; 1292 } 1293 // try to send car to this division track 1294 addLine(_buildReport, SEVEN, 1295 Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(), 1296 track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(), 1297 car.getLoadName())); 1298 car.setFinalDestination(track.getLocation()); 1299 car.setFinalDestinationTrack(track); 1300 // test to see if destination is reachable by this train 1301 if (router.setDestination(car, _train, _buildReport)) { 1302 log.debug("Can route car to destination ({}, {})", track.getLocation().getName(), track.getName()); 1303 } 1304 if (car.getDestination() == null) { 1305 addLine(_buildReport, SEVEN, 1306 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1307 car.setFinalDestination(null); 1308 car.setFinalDestinationTrack(null); 1309 return false; 1310 } 1311 car.updateKernel(); 1312 return true; // done, car has a new final destination 1313 } 1314 1315 /** 1316 * Checks for a car's final destination, and then after checking, tries to 1317 * route the car to that destination. Normal return from this routine is 1318 * false, with the car returning with a set destination. Returns true if car 1319 * has a final destination, but can't be used for this train. 1320 * 1321 * @param car 1322 * @return false if car needs destination processing (normal). 1323 */ 1324 private boolean checkCarForFinalDestination(Car car) { 1325 if (car.getFinalDestination() == null || car.getDestination() != null) { 1326 return false; 1327 } 1328 1329 addLine(_buildReport, FIVE, 1330 Bundle.getMessage("buildCarRoutingBegins", car.toString(), car.getTypeName(), 1331 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(), 1332 car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1333 1334 // no local moves for this train? 1335 if (!_train.isLocalSwitcher() && 1336 !_train.isAllowLocalMovesEnabled() && 1337 car.getSplitLocationName().equals(car.getSplitFinalDestinationName()) && 1338 car.getTrack() != _departStageTrack) { 1339 addLine(_buildReport, FIVE, 1340 Bundle.getMessage("buildCarHasFinalDestNoMove", car.toString(), car.getLocationName(), 1341 car.getFinalDestinationName(), _train.getName())); 1342 addLine(_buildReport, FIVE, BLANK_LINE); 1343 log.debug("Removing car ({}) from list", car.toString()); 1344 _carList.remove(car); 1345 _carIndex--; 1346 return true; // car has a final destination, but no local moves by 1347 // this train 1348 } 1349 // is the car's destination the terminal and is that allowed? 1350 if (!checkThroughCarsAllowed(car, car.getFinalDestinationName())) { 1351 // don't remove car from list if departing staging 1352 if (car.getTrack() == _departStageTrack) { 1353 addLine(_buildReport, ONE, Bundle.getMessage("buildErrorCarStageDest", car.toString())); 1354 } else { 1355 log.debug("Removing car ({}) from list", car.toString()); 1356 _carList.remove(car); 1357 _carIndex--; 1358 } 1359 return true; // car has a final destination, but through traffic not 1360 // allowed by this train 1361 } 1362 // does the car have a final destination track that is willing to 1363 // service the car? 1364 // note the default mode for all track types is MATCH 1365 if (car.getFinalDestinationTrack() != null && car.getFinalDestinationTrack().getScheduleMode() == Track.MATCH) { 1366 String status = car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack()); 1367 // keep going if the only issue was track length and the track 1368 // accepts the car's load 1369 if (!status.equals(Track.OKAY) && 1370 !status.startsWith(Track.LENGTH) && 1371 !(status.contains(Track.CUSTOM) && status.contains(Track.LOAD))) { 1372 addLine(_buildReport, SEVEN, 1373 Bundle.getMessage("buildNoDestTrackNewLoad", 1374 StringUtils.capitalize(car.getFinalDestinationTrack().getTrackTypeName()), 1375 car.getFinalDestination().getName(), car.getFinalDestinationTrack().getName(), 1376 car.toString(), car.getLoadName(), status)); 1377 // is this car or kernel being sent to a track that is too 1378 // short? 1379 if (status.startsWith(Track.CAPACITY)) { 1380 // track is too short for this car or kernel 1381 addLine(_buildReport, SEVEN, 1382 Bundle.getMessage("buildTrackTooShort", car.getFinalDestination().getName(), 1383 car.getFinalDestinationTrack().getName(), car.toString())); 1384 } 1385 _warnings++; 1386 addLine(_buildReport, SEVEN, 1387 Bundle.getMessage("buildWarningRemovingFinalDest", car.getFinalDestination().getName(), 1388 car.getFinalDestinationTrack().getName(), car.toString())); 1389 car.setFinalDestination(null); 1390 car.setFinalDestinationTrack(null); 1391 return false; // car no longer has a final destination 1392 } 1393 } 1394 1395 // now try and route the car 1396 if (!router.setDestination(car, _train, _buildReport)) { 1397 addLine(_buildReport, SEVEN, 1398 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1399 // don't move car if routing issue was track space but not departing 1400 // staging 1401 if ((!router.getStatus().startsWith(Track.LENGTH) && 1402 !_train.isServiceAllCarsWithFinalDestinationsEnabled()) || (car.getTrack() == _departStageTrack)) { 1403 // add car to unable to route list 1404 if (!_notRoutable.contains(car)) { 1405 _notRoutable.add(car); 1406 } 1407 addLine(_buildReport, FIVE, BLANK_LINE); 1408 addLine(_buildReport, FIVE, 1409 Bundle.getMessage("buildWarningCarNotRoutable", car.toString(), car.getLocationName(), 1410 car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1411 addLine(_buildReport, FIVE, BLANK_LINE); 1412 return false; // move this car, routing failed! 1413 } 1414 } else { 1415 if (car.getDestination() != null) { 1416 return false; // routing successful process this car, normal 1417 // exit from this routine 1418 } 1419 if (car.getTrack() == _departStageTrack) { 1420 log.debug("Car ({}) departing staging with final destination ({}) and no destination", 1421 car.toString(), car.getFinalDestinationName()); 1422 return false; // try and move this car out of staging 1423 } 1424 } 1425 addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1426 addLine(_buildReport, FIVE, BLANK_LINE); 1427 return true; 1428 } 1429 1430 /** 1431 * Checks to see if car has a destination and tries to add car to train. 1432 * Will find a track for the car if needed. Returns false if car doesn't 1433 * have a destination. 1434 * 1435 * @param rl the car's route location 1436 * @param routeIndex where in the route to start search 1437 * @return true if car has a destination. Need to check if car given a train 1438 * assignment. 1439 * @throws BuildFailedException if destination was staging and can't place 1440 * car there 1441 */ 1442 private boolean checkCarForDestination(Car car, RouteLocation rl, int routeIndex) throws BuildFailedException { 1443 if (car.getDestination() == null) { 1444 return false; // the only false return 1445 } 1446 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarHasAssignedDest", car.toString(), car.getLoadName(), 1447 car.getDestinationName(), car.getDestinationTrackName())); 1448 RouteLocation rld = _train.getRoute().getLastLocationByName(car.getDestinationName()); 1449 if (rld == null) { 1450 // code check, router doesn't set a car's destination if not carried 1451 // by train being built. Car has a destination that isn't serviced 1452 // by this train. Find buildExcludeCarDestNotPartRoute in 1453 // loadRemoveAndListCars() 1454 throw new BuildFailedException(Bundle.getMessage("buildExcludeCarDestNotPartRoute", car.toString(), 1455 car.getDestinationName(), car.getDestinationTrackName(), _train.getRoute().getName())); 1456 } 1457 // now go through the route and try and find a location with 1458 // the correct destination name 1459 for (int k = routeIndex; k < _routeList.size(); k++) { 1460 rld = _routeList.get(k); 1461 // if car can be picked up later at same location, skip 1462 if (checkForLaterPickUp(car, rl, rld)) { 1463 addLine(_buildReport, SEVEN, BLANK_LINE); 1464 return true; 1465 } 1466 if (!rld.getName().equals(car.getDestinationName())) { 1467 continue; 1468 } 1469 // is the car's destination the terminal and is that allowed? 1470 if (!checkThroughCarsAllowed(car, car.getDestinationName())) { 1471 return true; 1472 } 1473 log.debug("Car ({}) found a destination in train's route", car.toString()); 1474 // are drops allows at this location? 1475 if (!rld.isDropAllowed() && !car.isLocalMove()) { 1476 addLine(_buildReport, FIVE, Bundle.getMessage("buildRouteNoDropLocation", _train.getRoute().getName(), 1477 rld.getId(), rld.getName())); 1478 continue; 1479 } 1480 // are local moves allows at this location? 1481 if (!rld.isLocalMovesAllowed() && car.isLocalMove()) { 1482 addLine(_buildReport, FIVE, Bundle.getMessage("buildRouteNoLocalLocation", _train.getRoute().getName(), 1483 rld.getId(), rld.getName())); 1484 continue; 1485 } 1486 if (_train.isLocationSkipped(rld)) { 1487 addLine(_buildReport, FIVE, 1488 Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), _train.getName())); 1489 continue; 1490 } 1491 // any moves left at this location? 1492 if (rld.getCarMoves() >= rld.getMaxCarMoves()) { 1493 addLine(_buildReport, FIVE, 1494 Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(), 1495 _train.getRoute().getName(), rld.getId(), rld.getName())); 1496 continue; 1497 } 1498 // is the train length okay? 1499 if (!checkTrainLength(car, rl, rld)) { 1500 continue; 1501 } 1502 // check for valid destination track 1503 if (car.getDestinationTrack() == null) { 1504 addLine(_buildReport, FIVE, Bundle.getMessage("buildCarDoesNotHaveDest", car.toString())); 1505 // is car going into staging? 1506 if (rld == _train.getTrainTerminatesRouteLocation() && _terminateStageTrack != null) { 1507 String status = car.checkDestination(car.getDestination(), _terminateStageTrack); 1508 if (status.equals(Track.OKAY)) { 1509 addLine(_buildReport, FIVE, Bundle.getMessage("buildCarAssignedToStaging", car.toString(), 1510 _terminateStageTrack.getName())); 1511 addCarToTrain(car, rl, rld, _terminateStageTrack); 1512 return true; 1513 } else { 1514 addLine(_buildReport, SEVEN, 1515 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 1516 _terminateStageTrack.getTrackTypeName(), 1517 _terminateStageTrack.getLocation().getName(), _terminateStageTrack.getName(), 1518 status)); 1519 continue; 1520 } 1521 } else { 1522 // no staging at this location, now find a destination track 1523 // for this car 1524 List<Track> tracks = getTracksAtDestination(car, rld); 1525 if (tracks.size() > 0) { 1526 if (tracks.get(1) != null) { 1527 car.setFinalDestination(car.getDestination()); 1528 car.setFinalDestinationTrack(tracks.get(1)); 1529 tracks.get(1).bumpMoves(); 1530 } 1531 addCarToTrain(car, rl, rld, tracks.get(0)); 1532 return true; 1533 } 1534 } 1535 } else { 1536 log.debug("Car ({}) has a destination track ({})", car.toString(), car.getDestinationTrack().getName()); 1537 // going into the correct staging track? 1538 if (rld.equals(_train.getTrainTerminatesRouteLocation()) && 1539 _terminateStageTrack != null && 1540 _terminateStageTrack != car.getDestinationTrack()) { 1541 // car going to wrong track in staging, change track 1542 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarDestinationStaging", car.toString(), 1543 car.getDestinationName(), car.getDestinationTrackName())); 1544 car.setDestination(_terminateStageTrack.getLocation(), _terminateStageTrack); 1545 } 1546 if (!rld.equals(_train.getTrainTerminatesRouteLocation()) || 1547 _terminateStageTrack == null || 1548 _terminateStageTrack == car.getDestinationTrack()) { 1549 // is train direction correct? and drop to interchange or 1550 // spur? 1551 if (checkDropTrainDirection(car, rld, car.getDestinationTrack()) && 1552 checkTrainCanDrop(car, car.getDestinationTrack())) { 1553 String status = car.checkDestination(car.getDestination(), car.getDestinationTrack()); 1554 if (status.equals(Track.OKAY) && 1555 (status = checkReserved(_train, rld, car, car.getDestinationTrack(), true)) 1556 .equals(Track.OKAY)) { 1557 addCarToTrain(car, rl, rld, car.getDestinationTrack()); 1558 return true; 1559 } 1560 if (status.equals(TIMING) && checkForAlternate(car, car.getDestinationTrack())) { 1561 // send car to alternate track) { 1562 car.setFinalDestination(car.getDestination()); 1563 car.setFinalDestinationTrack(car.getDestinationTrack()); 1564 addCarToTrain(car, rl, rld, car.getDestinationTrack().getAlternateTrack()); 1565 return true; 1566 } 1567 addLine(_buildReport, SEVEN, 1568 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 1569 car.getDestinationTrack().getTrackTypeName(), 1570 car.getDestinationTrack().getLocation().getName(), 1571 car.getDestinationTrackName(), status)); 1572 1573 } 1574 } else { 1575 // code check 1576 throw new BuildFailedException(Bundle.getMessage("buildCarDestinationStaging", car.toString(), 1577 car.getDestinationName(), car.getDestinationTrackName())); 1578 } 1579 } 1580 addLine(_buildReport, FIVE, 1581 Bundle.getMessage("buildCanNotDropCar", car.toString(), car.getDestinationName(), rld.getId())); 1582 if (car.getDestinationTrack() == null) { 1583 log.debug("Could not find a destination track for location ({})", car.getDestinationName()); 1584 } 1585 } 1586 log.debug("car ({}) not added to train", car.toString()); 1587 addLine(_buildReport, FIVE, 1588 Bundle.getMessage("buildDestinationNotReachable", car.getDestinationName(), rl.getName(), rl.getId())); 1589 // remove destination and revert to final destination 1590 if (car.getDestinationTrack() != null) { 1591 // going to remove this destination from car 1592 car.getDestinationTrack().setMoves(car.getDestinationTrack().getMoves() - 1); 1593 Track destTrack = car.getDestinationTrack(); 1594 // TODO should we leave the car's destination? The spur expects this 1595 // car! 1596 if (destTrack.getSchedule() != null && destTrack.getScheduleMode() == Track.SEQUENTIAL) { 1597 addLine(_buildReport, SEVEN, Bundle.getMessage("buildPickupCancelled", 1598 destTrack.getLocation().getName(), destTrack.getName())); 1599 } 1600 } 1601 car.setFinalDestination(car.getPreviousFinalDestination()); 1602 car.setFinalDestinationTrack(car.getPreviousFinalDestinationTrack()); 1603 car.setDestination(null, null); 1604 car.updateKernel(); 1605 1606 addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1607 addLine(_buildReport, FIVE, BLANK_LINE); 1608 return true; // car no longer has a destination, but it had one. 1609 } 1610 1611 /** 1612 * Find a destination and track for a car at a route location. 1613 * 1614 * @param car the car! 1615 * @param rl The car's route location 1616 * @param rld The car's route destination 1617 * @return true if successful. 1618 * @throws BuildFailedException if code check fails 1619 */ 1620 private boolean findDestinationAndTrack(Car car, RouteLocation rl, RouteLocation rld) throws BuildFailedException { 1621 int index = _routeList.indexOf(rld); 1622 if (_train.isLocalSwitcher()) { 1623 return findDestinationAndTrack(car, rl, index, index + 1); 1624 } 1625 return findDestinationAndTrack(car, rl, index - 1, index + 1); 1626 } 1627 1628 /** 1629 * Find a destination and track for a car, and add the car to the train. 1630 * 1631 * @param car The car that is looking for a destination and 1632 * destination track. 1633 * @param rl The route location for this car. 1634 * @param routeIndex Where in the train's route to begin a search for a 1635 * destination for this car. 1636 * @param routeEnd Where to stop looking for a destination. 1637 * @return true if successful, car has destination, track and a train. 1638 * @throws BuildFailedException if code check fails 1639 */ 1640 private boolean findDestinationAndTrack(Car car, RouteLocation rl, int routeIndex, int routeEnd) 1641 throws BuildFailedException { 1642 if (routeIndex + 1 == routeEnd) { 1643 log.debug("Car ({}) is at the last location in the train's route", car.toString()); 1644 } 1645 addLine(_buildReport, FIVE, Bundle.getMessage("buildFindDestinationForCar", car.toString(), car.getTypeName(), 1646 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(), 1647 car.getTrackName())); 1648 if (car.getKernel() != null) { 1649 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 1650 car.getKernel().getSize(), car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 1651 } 1652 1653 // normally start looking after car's route location 1654 int start = routeIndex; 1655 // the route location destination being checked for the car 1656 RouteLocation rld = null; 1657 // holds the best route location destination for the car 1658 RouteLocation rldSave = null; 1659 // holds the best track at destination for the car 1660 Track trackSave = null; 1661 // used when a spur has an alternate track and no schedule 1662 Track finalDestinationTrackSave = null; 1663 // true when car can be picked up from two or more locations in the 1664 // route 1665 boolean multiplePickup = false; 1666 1667 // more than one location in this route? 1668 if (!_train.isLocalSwitcher()) { 1669 start++; // begin looking for tracks at the next location 1670 } 1671 // all pick ups to terminal? 1672 if (_train.isSendCarsToTerminalEnabled() && 1673 !rl.getSplitName().equals(_departLocation.getSplitName()) && 1674 routeEnd == _routeList.size()) { 1675 addLine(_buildReport, FIVE, Bundle.getMessage("buildSendToTerminal", _terminateLocation.getName())); 1676 // user could have specified several terminal locations with the 1677 // "same" name 1678 start = routeEnd - 1; 1679 while (start > routeIndex) { 1680 if (!_routeList.get(start - 1).getSplitName() 1681 .equals(_terminateLocation.getSplitName())) { 1682 break; 1683 } 1684 start--; 1685 } 1686 } 1687 // now search for a destination for this car 1688 for (int k = start; k < routeEnd; k++) { 1689 rld = _routeList.get(k); 1690 // if car can be picked up later at same location, set flag 1691 if (checkForLaterPickUp(car, rl, rld)) { 1692 multiplePickup = true; 1693 } 1694 if (rld.isDropAllowed() || car.hasFred() || car.isCaboose()) { 1695 addLine(_buildReport, FIVE, Bundle.getMessage("buildSearchingLocation", rld.getName(), rld.getId())); 1696 } else { 1697 addLine(_buildReport, FIVE, Bundle.getMessage("buildRouteNoDropLocation", _train.getRoute().getName(), 1698 rld.getId(), rld.getName())); 1699 continue; 1700 } 1701 if (_train.isLocationSkipped(rld)) { 1702 addLine(_buildReport, FIVE, 1703 Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), _train.getName())); 1704 continue; 1705 } 1706 // any moves left at this location? 1707 if (rld.getCarMoves() >= rld.getMaxCarMoves()) { 1708 addLine(_buildReport, FIVE, 1709 Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(), 1710 _train.getRoute().getName(), rld.getId(), rld.getName())); 1711 continue; 1712 } 1713 // get the destination 1714 Location testDestination = rld.getLocation(); 1715 // code check, all locations in the route have been already checked 1716 if (testDestination == null) { 1717 throw new BuildFailedException( 1718 Bundle.getMessage("buildErrorRouteLoc", _train.getRoute().getName(), rld.getName())); 1719 } 1720 // don't move car to same location unless the train is a switcher 1721 // (local moves) or is passenger, caboose or car with FRED 1722 if (rl.getSplitName().equals(rld.getSplitName()) && 1723 !_train.isLocalSwitcher() && 1724 !car.isPassenger() && 1725 !car.isCaboose() && 1726 !car.hasFred()) { 1727 // allow cars to return to the same staging location if no other 1728 // options (tracks) are available 1729 if ((_train.isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) && 1730 testDestination.isStaging() && 1731 trackSave == null) { 1732 addLine(_buildReport, SEVEN, 1733 Bundle.getMessage("buildReturnCarToStaging", car.toString(), rld.getName())); 1734 } else { 1735 addLine(_buildReport, SEVEN, 1736 Bundle.getMessage("buildCarLocEqualDestination", car.toString(), rld.getName())); 1737 continue; 1738 } 1739 } 1740 1741 // check to see if departure track has any restrictions 1742 if (!car.getTrack().isDestinationAccepted(testDestination)) { 1743 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", testDestination.getName(), 1744 car.getTrackName())); 1745 continue; 1746 } 1747 1748 if (!testDestination.acceptsTypeName(car.getTypeName())) { 1749 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotDropLocation", car.toString(), 1750 car.getTypeName(), testDestination.getName())); 1751 continue; 1752 } 1753 // can this location service this train's direction 1754 if (!checkDropTrainDirection(rld)) { 1755 continue; 1756 } 1757 // is the train length okay? 1758 if (!checkTrainLength(car, rl, rld)) { 1759 break; // no, done with this car 1760 } 1761 // is the car's destination the terminal and is that allowed? 1762 if (!checkThroughCarsAllowed(car, rld.getName())) { 1763 continue; // not allowed 1764 } 1765 1766 Track trackTemp = null; 1767 // used when alternate track selected 1768 Track finalDestinationTrackTemp = null; 1769 1770 // is there a track assigned for staging cars? 1771 if (rld == _train.getTrainTerminatesRouteLocation() && _terminateStageTrack != null) { 1772 trackTemp = tryStaging(car, rldSave); 1773 if (trackTemp == null) { 1774 continue; // no 1775 } 1776 } else { 1777 // no staging, start track search 1778 List<Track> tracks = getTracksAtDestination(car, rld); 1779 if (tracks.size() > 0) { 1780 trackTemp = tracks.get(0); 1781 finalDestinationTrackTemp = tracks.get(1); 1782 } 1783 } 1784 // did we find a new destination? 1785 if (trackTemp == null) { 1786 addLine(_buildReport, FIVE, 1787 Bundle.getMessage("buildCouldNotFindDestForCar", car.toString(), rld.getName())); 1788 } else { 1789 addLine(_buildReport, FIVE, 1790 Bundle.getMessage("buildCarCanDropMoves", car.toString(), trackTemp.getTrackTypeName(), 1791 trackTemp.getLocation().getName(), trackTemp.getName(), +rld.getCarMoves(), 1792 rld.getMaxCarMoves())); 1793 if (multiplePickup) { 1794 if (rldSave != null) { 1795 addLine(_buildReport, FIVE, 1796 Bundle.getMessage("buildTrackServicedLater", car.getLocationName(), 1797 trackTemp.getTrackTypeName(), trackTemp.getLocation().getName(), 1798 trackTemp.getName(), car.getLocationName())); 1799 } else { 1800 addLine(_buildReport, FIVE, 1801 Bundle.getMessage("buildCarHasSecond", car.toString(), car.getLocationName())); 1802 trackSave = null; 1803 } 1804 break; // done 1805 } 1806 // if there's more than one available destination use the lowest 1807 // ratio 1808 if (rldSave != null) { 1809 // check for an earlier drop in the route 1810 rld = checkForEarlierDrop(car, trackTemp, rld, start, routeEnd); 1811 double saveCarMoves = rldSave.getCarMoves(); 1812 double saveRatio = saveCarMoves / rldSave.getMaxCarMoves(); 1813 double nextCarMoves = rld.getCarMoves(); 1814 double nextRatio = nextCarMoves / rld.getMaxCarMoves(); 1815 1816 // bias cars to the terminal 1817 if (rld == _train.getTrainTerminatesRouteLocation()) { 1818 nextRatio = nextRatio * nextRatio; 1819 log.debug("Location ({}) is terminate location, adjusted nextRatio {}", rld.getName(), 1820 Double.toString(nextRatio)); 1821 1822 // bias cars with default loads to a track with a 1823 // schedule 1824 } else if (!trackTemp.getScheduleId().equals(Track.NONE)) { 1825 nextRatio = nextRatio * nextRatio; 1826 log.debug("Track ({}) has schedule ({}), adjusted nextRatio {}", trackTemp.getName(), 1827 trackTemp.getScheduleName(), Double.toString(nextRatio)); 1828 } 1829 // bias cars with default loads to saved track with a 1830 // schedule 1831 if (trackSave != null && !trackSave.getScheduleId().equals(Track.NONE)) { 1832 saveRatio = saveRatio * saveRatio; 1833 log.debug("Saved track ({}) has schedule ({}), adjusted nextRatio {}", trackSave.getName(), 1834 trackSave.getScheduleName(), Double.toString(saveRatio)); 1835 } 1836 log.debug("Saved {} = {}, {} = {}", rldSave.getName(), Double.toString(saveRatio), rld.getName(), 1837 Double.toString(nextRatio)); 1838 if (saveRatio < nextRatio) { 1839 // the saved is better than the last found 1840 rld = rldSave; 1841 trackTemp = trackSave; 1842 finalDestinationTrackTemp = finalDestinationTrackSave; 1843 } 1844 } 1845 // every time through, save the best route destination, and 1846 // track 1847 rldSave = rld; 1848 trackSave = trackTemp; 1849 finalDestinationTrackSave = finalDestinationTrackTemp; 1850 } 1851 } 1852 // did we find a destination? 1853 if (trackSave != null && rldSave != null) { 1854 // determine if local staging move is allowed (leaves car in staging) 1855 if ((_train.isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) && 1856 rl.isDropAllowed() && 1857 rl.getLocation().isStaging() && 1858 trackSave.isStaging() && 1859 rl.getLocation() == rldSave.getLocation() && 1860 !_train.isLocalSwitcher() && 1861 !car.isPassenger() && 1862 !car.isCaboose() && 1863 !car.hasFred()) { 1864 addLine(_buildReport, SEVEN, 1865 Bundle.getMessage("buildLeaveCarInStaging", car.toString(), car.getLocationName(), 1866 car.getTrackName())); 1867 rldSave = rl; // make local move 1868 } else if (trackSave.isSpur()) { 1869 car.setScheduleItemId(trackSave.getScheduleItemId()); 1870 trackSave.bumpSchedule(); 1871 log.debug("Sending car to spur ({}, {}) with car schedule id ({}))", trackSave.getLocation().getName(), 1872 trackSave.getName(), car.getScheduleItemId()); 1873 } else { 1874 car.setScheduleItemId(Car.NONE); 1875 } 1876 if (finalDestinationTrackSave != null) { 1877 car.setFinalDestination(finalDestinationTrackSave.getLocation()); 1878 car.setFinalDestinationTrack(finalDestinationTrackSave); 1879 if (trackSave.isAlternate()) { 1880 finalDestinationTrackSave.bumpMoves(); // bump move count 1881 } 1882 } 1883 addCarToTrain(car, rl, rldSave, trackSave); 1884 return true; 1885 } 1886 addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1887 addLine(_buildReport, FIVE, BLANK_LINE); 1888 return false; // no build errors, but car not given destination 1889 } 1890 1891 private final static Logger log = LoggerFactory.getLogger(TrainBuilderCars.class); 1892}