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 // all cars with FRED departing staging must leave with train 244 if (car.getTrack() == departTrack) { 245 foundCarWithFred = false; 246 if (!generateCarLoadFromStaging(car, rld)) { 247 // departing and terminating into staging? 248 if (car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() && 249 rld.getLocation() == _terminateLocation && 250 _terminateStageTrack != null) { 251 // try and generate a custom load for this car with FRED 252 generateLoadCarDepartingAndTerminatingIntoStaging(car, _terminateStageTrack); 253 } 254 } 255 if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) { 256 if (car.getTrain() == _train) { 257 foundCarWithFred = true; 258 } 259 } else if (findDestinationAndTrack(car, rl, rld)) { 260 foundCarWithFred = true; 261 } 262 if (!foundCarWithFred) { 263 throw new BuildFailedException(Bundle.getMessage("buildErrorCarStageDest", car.toString())); 264 } 265 } // is there a specific road requirement for the car with FRED? 266 else if (!road.equals(Train.NONE) && !road.equals(car.getRoadName())) { 267 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeCarWrongRoad", car.toString(), 268 car.getLocationName(), car.getTrackName(), car.getTypeName(), car.getRoadName())); 269 _carList.remove(car); // remove this car from the list 270 _carIndex--; 271 continue; 272 } else if (!foundCarWithFred && car.getLocationName().equals(rl.getName())) { 273 // remove cars that can't be picked up due to train and track 274 // directions 275 if (!checkPickUpTrainDirection(car, rl)) { 276 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeCarTypeAtLoc", car.toString(), 277 car.getTypeName(), car.getTypeExtensions(), car.getLocationName(), car.getTrackName())); 278 _carList.remove(car); // remove this car from the list 279 _carIndex--; 280 continue; 281 } 282 if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) { 283 if (car.getTrain() == _train) { 284 foundCarWithFred = true; 285 } 286 } else if (findDestinationAndTrack(car, rl, rld)) { 287 foundCarWithFred = true; 288 } 289 if (foundCarWithFred && departTrack == null) { 290 break; 291 } 292 } 293 } 294 if (_train.isFredNeeded() && !foundCarWithFred) { 295 throw new BuildFailedException(Bundle.getMessage("buildErrorRequirements", _train.getName(), 296 Bundle.getMessage("FRED"), rl.getName(), rld.getName())); 297 } 298 } 299 300 /** 301 * Determine if caboose or car with FRED was given a destination and track. 302 * Need to check if there's been a train assignment. 303 * 304 * @param car the car in question 305 * @param rl car's route location 306 * @param rld car's route location destination 307 * @return true if car has a destination. Need to check if there's been a 308 * train assignment. 309 * @throws BuildFailedException if destination was staging and can't place 310 * car there 311 */ 312 private boolean checkAndAddCarForDestinationAndTrack(Car car, RouteLocation rl, RouteLocation rld) 313 throws BuildFailedException { 314 return checkCarForDestination(car, rl, _routeList.indexOf(rld)); 315 } 316 317 /** 318 * Optionally block cars departing staging. No guarantee that cars departing 319 * staging can be blocked by destination. By using the pick up location id, 320 * this routine tries to find destinations that are willing to accepts all 321 * of the cars that were "blocked" together when they were picked up. Rules: 322 * The route must allow set outs at the destination. The route must allow 323 * the correct number of set outs. The destination must accept all cars in 324 * the pick up block. 325 * 326 * @throws BuildFailedException if blocking fails 327 */ 328 protected void blockCarsFromStaging() throws BuildFailedException { 329 if (_departStageTrack == null || !_departStageTrack.isBlockCarsEnabled()) { 330 return; 331 } 332 333 addLine(_buildReport, THREE, BLANK_LINE); 334 addLine(_buildReport, THREE, 335 Bundle.getMessage("blockDepartureHasBlocks", _departStageTrack.getName(), _numOfBlocks.size())); 336 337 Enumeration<String> en = _numOfBlocks.keys(); 338 while (en.hasMoreElements()) { 339 String locId = en.nextElement(); 340 int numCars = _numOfBlocks.get(locId); 341 String locName = ""; 342 Location l = locationManager.getLocationById(locId); 343 if (l != null) { 344 locName = l.getName(); 345 } 346 addLine(_buildReport, SEVEN, Bundle.getMessage("blockFromHasCars", locId, locName, numCars)); 347 if (_numOfBlocks.size() < 2) { 348 addLine(_buildReport, SEVEN, Bundle.getMessage("blockUnable")); 349 return; 350 } 351 } 352 blockCarsByLocationMoves(); 353 addLine(_buildReport, SEVEN, Bundle.getMessage("blockDone", _departStageTrack.getName())); 354 } 355 356 /** 357 * Blocks cars out of staging by assigning the largest blocks of cars to 358 * locations requesting the most moves. 359 * 360 * @throws BuildFailedException 361 */ 362 private void blockCarsByLocationMoves() throws BuildFailedException { 363 List<RouteLocation> blockRouteList = _train.getRoute().getLocationsBySequenceList(); 364 for (RouteLocation rl : blockRouteList) { 365 // start at the second location in the route to begin blocking 366 if (rl == _train.getTrainDepartsRouteLocation()) { 367 continue; 368 } 369 int possibleMoves = rl.getMaxCarMoves() - rl.getCarMoves(); 370 if (rl.isDropAllowed() && possibleMoves > 0) { 371 addLine(_buildReport, SEVEN, Bundle.getMessage("blockLocationHasMoves", rl.getName(), possibleMoves)); 372 } 373 } 374 // now block out cars, send the largest block of cars to the locations 375 // requesting the greatest number of moves 376 while (true) { 377 String blockId = getLargestBlock(); // get the id of the largest 378 // block of cars 379 if (blockId.isEmpty() || _numOfBlocks.get(blockId) == 1) { 380 break; // done 381 } 382 // get the remaining location with the greatest number of moves 383 RouteLocation rld = getLocationWithMaximumMoves(blockRouteList, blockId); 384 if (rld == null) { 385 break; // done 386 } 387 // check to see if there are enough moves for all of the cars 388 // departing staging 389 if (rld.getMaxCarMoves() > _numOfBlocks.get(blockId)) { 390 // remove the largest block and maximum moves RouteLocation from 391 // the lists 392 _numOfBlocks.remove(blockId); 393 // block 0 cars have never left staging. 394 if (blockId.equals(Car.LOCATION_UNKNOWN)) { 395 continue; 396 } 397 blockRouteList.remove(rld); 398 Location loc = locationManager.getLocationById(blockId); 399 Location setOutLoc = rld.getLocation(); 400 if (loc != null && setOutLoc != null && checkDropTrainDirection(rld)) { 401 for (_carIndex = 0; _carIndex < _carList.size(); _carIndex++) { 402 Car car = _carList.get(_carIndex); 403 if (car.getTrack() == _departStageTrack && car.getLastLocationId().equals(blockId)) { 404 if (car.getDestination() != null) { 405 addLine(_buildReport, SEVEN, Bundle.getMessage("blockNotAbleDest", car.toString(), 406 car.getDestinationName())); 407 continue; // can't block this car 408 } 409 if (car.getFinalDestination() != null) { 410 addLine(_buildReport, SEVEN, 411 Bundle.getMessage("blockNotAbleFinalDest", car.toString(), 412 car.getFinalDestination().getName())); 413 continue; // can't block this car 414 } 415 if (!car.getLoadName().equals(carLoads.getDefaultEmptyName()) && 416 !car.getLoadName().equals(carLoads.getDefaultLoadName())) { 417 addLine(_buildReport, SEVEN, 418 Bundle.getMessage("blockNotAbleCustomLoad", car.toString(), car.getLoadName())); 419 continue; // can't block this car 420 } 421 if (car.getLoadName().equals(carLoads.getDefaultEmptyName()) && 422 (_departStageTrack.isAddCustomLoadsEnabled() || 423 _departStageTrack.isAddCustomLoadsAnySpurEnabled() || 424 _departStageTrack.isAddCustomLoadsAnyStagingTrackEnabled())) { 425 addLine(_buildReport, SEVEN, 426 Bundle.getMessage("blockNotAbleCarTypeGenerate", car.toString(), 427 car.getLoadName())); 428 continue; // can't block this car 429 } 430 addLine(_buildReport, SEVEN, 431 Bundle.getMessage("blockingCar", car.toString(), loc.getName(), rld.getName())); 432 if (!findDestinationAndTrack(car, _train.getTrainDepartsRouteLocation(), rld)) { 433 addLine(_buildReport, SEVEN, 434 Bundle.getMessage("blockNotAbleCarType", car.toString(), rld.getName(), 435 car.getTypeName())); 436 } 437 } 438 } 439 } 440 } else { 441 addLine(_buildReport, SEVEN, Bundle.getMessage("blockDestNotEnoughMoves", rld.getName(), blockId)); 442 // block is too large for any stop along this train's route 443 _numOfBlocks.remove(blockId); 444 } 445 } 446 } 447 448 /** 449 * Attempts to find a destinations for cars departing a specific route 450 * location. 451 * 452 * @param rl The route location where cars need destinations. 453 * @param isSecondPass When true this is the second time we've looked at 454 * these cars. Used to perform local moves. 455 * @throws BuildFailedException if failure 456 */ 457 protected void findDestinationsForCarsFromLocation(RouteLocation rl, boolean isSecondPass) 458 throws BuildFailedException { 459 if (_reqNumOfMoves <= 0) { 460 return; 461 } 462 boolean messageFlag = true; 463 boolean foundCar = false; 464 for (_carIndex = 0; _carIndex < _carList.size(); _carIndex++) { 465 Car car = _carList.get(_carIndex); 466 // second pass deals with cars that have a final destination equal 467 // to this location. 468 // therefore a local move can be made. This causes "off spots" to be 469 // serviced. 470 if (isSecondPass && !car.getFinalDestinationName().equals(rl.getName())) { 471 continue; 472 } 473 // find a car at this location 474 if (!car.getLocationName().equals(rl.getName())) { 475 continue; 476 } 477 foundCar = true; 478 // add message that we're on the second pass for this location 479 if (isSecondPass && messageFlag) { 480 messageFlag = false; 481 addLine(_buildReport, FIVE, Bundle.getMessage("buildExtraPassForLocation", rl.getName())); 482 addLine(_buildReport, SEVEN, BLANK_LINE); 483 } 484 // can this car be picked up? 485 if (!checkPickUpTrainDirection(car, rl)) { 486 addLine(_buildReport, FIVE, BLANK_LINE); 487 continue; // no 488 } 489 490 showCarServiceOrder(car); // car on FIFO or LIFO track? 491 492 // is car departing staging and generate custom load? 493 if (!generateCarLoadFromStaging(car)) { 494 if (!generateCarLoadStagingToStaging(car) && 495 car.getTrack() == _departStageTrack && 496 !_departStageTrack.isLoadNameAndCarTypeShipped(car.getLoadName(), car.getTypeName())) { 497 // report build failure car departing staging with a 498 // restricted load 499 addLine(_buildReport, ONE, Bundle.getMessage("buildErrorCarStageLoad", car.toString(), 500 car.getLoadName(), _departStageTrack.getName())); 501 addLine(_buildReport, FIVE, BLANK_LINE); 502 continue; // keep going and see if there are other cars with 503 // issues outs of staging 504 } 505 } 506 // If car been given a home division follow division rules for car 507 // movement. 508 if (!findDestinationsForCarsWithHomeDivision(car)) { 509 addLine(_buildReport, FIVE, 510 Bundle.getMessage("buildNoDestForCar", car.toString())); 511 addLine(_buildReport, FIVE, BLANK_LINE); 512 continue; // hold car at current location 513 } 514 // does car have a custom load without a destination? 515 // if departing staging, a destination for this car is needed, so 516 // keep going 517 if (findFinalDestinationForCarLoad(car) && 518 car.getDestination() == null && 519 car.getTrack() != _departStageTrack) { 520 // done with this car, it has a custom load, and there are 521 // spurs/schedules, but no destination found 522 addLine(_buildReport, FIVE, 523 Bundle.getMessage("buildNoDestForCar", car.toString())); 524 addLine(_buildReport, FIVE, BLANK_LINE); 525 continue; 526 } 527 // Check car for final destination, then an assigned destination, if 528 // neither, find a destination for the car 529 if (checkCarForFinalDestination(car)) { 530 log.debug("Car ({}) has a final desination that can't be serviced by train", car.toString()); 531 } else if (checkCarForDestination(car, rl, _routeList.indexOf(rl))) { 532 // car had a destination, could have been added to the train. 533 log.debug("Car ({}) has desination ({}) using train ({})", car.toString(), car.getDestinationName(), 534 car.getTrainName()); 535 } else { 536 findDestinationAndTrack(car, rl, _routeList.indexOf(rl), _routeList.size()); 537 } 538 if (_reqNumOfMoves <= 0) { 539 break; // done 540 } 541 // build failure if car departing staging without a destination and 542 // a train we'll just put out a warning message here so we can find 543 // out how many cars have issues 544 if (car.getTrack() == _departStageTrack && 545 (car.getDestination() == null || car.getDestinationTrack() == null || car.getTrain() == null)) { 546 addLine(_buildReport, ONE, Bundle.getMessage("buildWarningCarStageDest", car.toString())); 547 // does the car have a final destination to staging? If so we 548 // need to reset this car 549 if (car.getFinalDestinationTrack() != null && car.getFinalDestinationTrack() == _terminateStageTrack) { 550 addLine(_buildReport, THREE, 551 Bundle.getMessage("buildStagingCarHasFinal", car.toString(), car.getFinalDestinationName(), 552 car.getFinalDestinationTrackName())); 553 car.reset(); 554 } 555 addLine(_buildReport, SEVEN, BLANK_LINE); 556 } 557 } 558 if (!foundCar && !isSecondPass) { 559 addLine(_buildReport, FIVE, Bundle.getMessage("buildNoCarsAtLocation", rl.getName())); 560 addLine(_buildReport, FIVE, BLANK_LINE); 561 } 562 } 563 564 private boolean generateCarLoadFromStaging(Car car) throws BuildFailedException { 565 return generateCarLoadFromStaging(car, null); 566 } 567 568 /** 569 * Used to generate a car's load from staging. Search for a spur with a 570 * schedule and load car if possible. 571 * 572 * @param car the car 573 * @param rld The route location destination for this car. Can be null. 574 * @return true if car given a custom load 575 * @throws BuildFailedException If code check fails 576 */ 577 private boolean generateCarLoadFromStaging(Car car, RouteLocation rld) throws BuildFailedException { 578 // Code Check, car should have a track assignment 579 if (car.getTrack() == null) { 580 throw new BuildFailedException( 581 Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName())); 582 } 583 if (!car.getTrack().isStaging() || 584 (!car.getTrack().isAddCustomLoadsAnySpurEnabled() && !car.getTrack().isAddCustomLoadsEnabled()) || 585 !car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 586 car.getDestination() != null || 587 car.getFinalDestination() != null) { 588 log.debug( 589 "No load generation for car ({}) isAddLoadsAnySpurEnabled: {}, car load ({}) destination ({}) final destination ({})", 590 car.toString(), car.getTrack().isAddCustomLoadsAnySpurEnabled() ? "true" : "false", 591 car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName()); 592 // if car has a destination or final destination add "no load 593 // generated" message to report 594 if (car.getTrack().isStaging() && 595 car.getTrack().isAddCustomLoadsAnySpurEnabled() && 596 car.getLoadName().equals(carLoads.getDefaultEmptyName())) { 597 addLine(_buildReport, FIVE, 598 Bundle.getMessage("buildCarNoLoadGenerated", car.toString(), car.getLoadName(), 599 car.getDestinationName(), car.getFinalDestinationName())); 600 } 601 return false; // no load generated for this car 602 } 603 addLine(_buildReport, FIVE, 604 Bundle.getMessage("buildSearchTrackNewLoad", car.toString(), car.getTypeName(), 605 car.getLoadType().toLowerCase(), car.getLoadName(), car.getLocationName(), car.getTrackName(), 606 rld != null ? rld.getLocation().getName() : "")); 607 // check to see if car type has custom loads 608 if (carLoads.getNames(car.getTypeName()).size() == 2) { 609 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarNoCustomLoad", car.toString(), car.getTypeName())); 610 return false; 611 } 612 if (car.getKernel() != null) { 613 addLine(_buildReport, SEVEN, 614 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 615 car.getKernel().getSize(), car.getKernel().getTotalLength(), 616 Setup.getLengthUnit().toLowerCase())); 617 } 618 // save the car's load, should be the default empty 619 String oldCarLoad = car.getLoadName(); 620 List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR); 621 log.debug("Found {} spurs", tracks.size()); 622 // show locations not serviced by departure track once 623 List<Location> locationsNotServiced = new ArrayList<>(); 624 for (Track track : tracks) { 625 if (locationsNotServiced.contains(track.getLocation())) { 626 continue; 627 } 628 if (rld != null && track.getLocation() != rld.getLocation()) { 629 locationsNotServiced.add(track.getLocation()); 630 continue; 631 } 632 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 633 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 634 track.getLocation().getName(), car.getTrackName())); 635 locationsNotServiced.add(track.getLocation()); 636 continue; 637 } 638 // only use tracks serviced by this train? 639 if (car.getTrack().isAddCustomLoadsEnabled() && 640 !_train.getRoute().isLocationNameInRoute(track.getLocation().getName())) { 641 continue; 642 } 643 // only the first match in a schedule is used for a spur 644 ScheduleItem si = getScheduleItem(car, track); 645 if (si == null) { 646 continue; // no match 647 } 648 // need to set car load so testDestination will work properly 649 car.setLoadName(si.getReceiveLoadName()); 650 car.setScheduleItemId(si.getId()); 651 String status = car.checkDestination(track.getLocation(), track); 652 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 653 addLine(_buildReport, SEVEN, 654 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 655 track.getLocation().getName(), track.getName(), car.toString(), si.getReceiveLoadName(), 656 status)); 657 continue; 658 } 659 addLine(_buildReport, SEVEN, Bundle.getMessage("buildTrySpurLoad", track.getLocation().getName(), 660 track.getName(), car.getLoadName())); 661 // does the car have a home division? 662 if (car.getDivision() != null) { 663 addLine(_buildReport, SEVEN, 664 Bundle.getMessage("buildCarHasDivisionStaging", car.toString(), car.getTypeName(), 665 car.getLoadType().toLowerCase(), car.getLoadName(), car.getDivisionName(), 666 car.getLocationName(), car.getTrackName(), car.getTrack().getDivisionName())); 667 // load type empty must return to car's home division 668 // or load type load from foreign division must return to car's 669 // home division 670 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) && car.getDivision() != track.getDivision() || 671 car.getLoadType().equals(CarLoad.LOAD_TYPE_LOAD) && 672 car.getTrack().getDivision() != car.getDivision() && 673 car.getDivision() != track.getDivision()) { 674 addLine(_buildReport, SEVEN, 675 Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(), 676 track.getLocation().getName(), track.getName(), track.getDivisionName(), 677 car.toString(), car.getLoadType().toLowerCase(), car.getLoadName())); 678 continue; 679 } 680 } 681 if (!track.isSpaceAvailable(car)) { 682 addLine(_buildReport, SEVEN, 683 Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(), 684 track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(), 685 Setup.getLengthUnit().toLowerCase(), track.getReservationFactor())); 686 continue; 687 } 688 // try routing car 689 car.setFinalDestination(track.getLocation()); 690 car.setFinalDestinationTrack(track); 691 if (router.setDestination(car, _train, _buildReport) && car.getDestination() != null) { 692 // return car with this custom load and destination 693 addLine(_buildReport, FIVE, 694 Bundle.getMessage("buildCreateNewLoadForCar", car.toString(), si.getReceiveLoadName(), 695 track.getLocation().getName(), track.getName())); 696 car.setLoadGeneratedFromStaging(true); 697 // is car part of kernel? 698 car.updateKernel(); 699 track.bumpMoves(); 700 track.bumpSchedule(); 701 return true; // done, car now has a custom load 702 } 703 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotRouteCar", car.toString(), 704 si.getReceiveLoadName(), track.getLocation().getName(), track.getName())); 705 addLine(_buildReport, SEVEN, BLANK_LINE); 706 car.setDestination(null, null); 707 car.setFinalDestination(null); 708 car.setFinalDestinationTrack(null); 709 } 710 // restore car's load 711 car.setLoadName(oldCarLoad); 712 car.setScheduleItemId(Car.NONE); 713 addLine(_buildReport, FIVE, Bundle.getMessage("buildUnableNewLoad", car.toString())); 714 return false; // done, no load generated for this car 715 } 716 717 /** 718 * Tries to place a custom load in the car that is departing staging and 719 * attempts to find a destination for the car that is also staging. 720 * 721 * @param car the car 722 * @return True if custom load added to car 723 * @throws BuildFailedException If code check fails 724 */ 725 private boolean generateCarLoadStagingToStaging(Car car) throws BuildFailedException { 726 // Code Check, car should have a track assignment 727 if (car.getTrack() == null) { 728 throw new BuildFailedException( 729 Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName())); 730 } 731 if (!car.getTrack().isStaging() || 732 !car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() || 733 !car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 734 car.getDestination() != null || 735 car.getFinalDestination() != null) { 736 log.debug( 737 "No load generation for car ({}) isAddCustomLoadsAnyStagingTrackEnabled: {}, car load ({}) destination ({}) final destination ({})", 738 car.toString(), car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() ? "true" : "false", 739 car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName()); 740 return false; 741 } 742 // check to see if car type has custom loads 743 if (carLoads.getNames(car.getTypeName()).size() == 2) { 744 return false; 745 } 746 List<Track> tracks = locationManager.getTracks(Track.STAGING); 747 addLine(_buildReport, FIVE, Bundle.getMessage("buildTryStagingToStaging", car.toString(), tracks.size())); 748 if (Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED)) { 749 for (Track track : tracks) { 750 addLine(_buildReport, SEVEN, 751 Bundle.getMessage("buildStagingLocationTrack", track.getLocation().getName(), track.getName())); 752 } 753 } 754 // list of locations that can't be reached by the router 755 List<Location> locationsNotServiced = new ArrayList<>(); 756 if (_terminateStageTrack != null) { 757 addLine(_buildReport, SEVEN, 758 Bundle.getMessage("buildIgnoreStagingFirstPass", _terminateStageTrack.getLocation().getName())); 759 locationsNotServiced.add(_terminateStageTrack.getLocation()); 760 } 761 while (tracks.size() > 0) { 762 // pick a track randomly 763 int rnd = (int) (Math.random() * tracks.size()); 764 Track track = tracks.get(rnd); 765 tracks.remove(track); 766 log.debug("Try staging track ({}, {})", track.getLocation().getName(), track.getName()); 767 // find a staging track that isn't at the departure 768 if (track.getLocation() == _departLocation) { 769 log.debug("Can't use departure location ({})", track.getLocation().getName()); 770 continue; 771 } 772 if (!_train.isAllowThroughCarsEnabled() && track.getLocation() == _terminateLocation) { 773 log.debug("Through cars to location ({}) not allowed", track.getLocation().getName()); 774 continue; 775 } 776 if (locationsNotServiced.contains(track.getLocation())) { 777 log.debug("Location ({}) not reachable", track.getLocation().getName()); 778 continue; 779 } 780 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 781 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 782 track.getLocation().getName(), car.getTrackName())); 783 locationsNotServiced.add(track.getLocation()); 784 continue; 785 } 786 // the following method sets the Car load generated from staging 787 // boolean 788 if (generateLoadCarDepartingAndTerminatingIntoStaging(car, track)) { 789 // test to see if destination is reachable by this train 790 if (router.setDestination(car, _train, _buildReport) && car.getDestination() != null) { 791 return true; // done, car has a custom load and a final 792 // destination 793 } 794 addLine(_buildReport, SEVEN, Bundle.getMessage("buildStagingTrackNotReachable", 795 track.getLocation().getName(), track.getName(), car.getLoadName())); 796 // return car to original state 797 car.setLoadName(carLoads.getDefaultEmptyName()); 798 car.setLoadGeneratedFromStaging(false); 799 car.setFinalDestination(null); 800 car.updateKernel(); 801 // couldn't route to this staging location 802 locationsNotServiced.add(track.getLocation()); 803 } 804 } 805 // No staging tracks reachable, try the track the train is terminating 806 // to 807 if (_train.isAllowThroughCarsEnabled() && 808 _terminateStageTrack != null && 809 car.getTrack().isDestinationAccepted(_terminateStageTrack.getLocation()) && 810 generateLoadCarDepartingAndTerminatingIntoStaging(car, _terminateStageTrack)) { 811 return true; 812 } 813 814 addLine(_buildReport, SEVEN, 815 Bundle.getMessage("buildNoStagingForCarCustom", car.toString())); 816 addLine(_buildReport, SEVEN, BLANK_LINE); 817 return false; 818 } 819 820 /** 821 * Check to see if car has been assigned a home division. If car has a home 822 * division the following rules are applied when assigning the car a 823 * destination: 824 * <p> 825 * If car load is type empty not at car's home division yard: Car is sent to 826 * a home division yard. If home division yard not available, then car is 827 * sent to home division staging, then spur (industry). 828 * <p> 829 * If car load is type empty at a yard at the car's home division: Car is 830 * sent to a home division spur, then home division staging. 831 * <p> 832 * If car load is type load not at car's home division: Car is sent to home 833 * division spur, and if spur not available then home division staging. 834 * <p> 835 * If car load is type load at car's home division: Car is sent to any 836 * division spur or staging. 837 * 838 * @param car the car being checked for a home division 839 * @return false if destination track not found for this car 840 * @throws BuildFailedException 841 */ 842 private boolean findDestinationsForCarsWithHomeDivision(Car car) throws BuildFailedException { 843 if (car.getDivision() == null || car.getDestination() != null || car.getFinalDestination() != null) { 844 return true; 845 } 846 if (car.getDivision() == car.getTrack().getDivision()) { 847 addLine(_buildReport, FIVE, 848 Bundle.getMessage("buildCarDepartHomeDivision", car.toString(), car.getTypeName(), 849 car.getLoadType().toLowerCase(), 850 car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(), 851 car.getLocationName(), car.getTrackName(), 852 car.getTrack().getDivisionName())); 853 } else { 854 addLine(_buildReport, FIVE, 855 Bundle.getMessage("buildCarDepartForeignDivision", car.toString(), car.getTypeName(), 856 car.getLoadType().toLowerCase(), 857 car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(), 858 car.getLocationName(), car.getTrackName(), 859 car.getTrack().getDivisionName())); 860 } 861 if (car.getKernel() != null) { 862 addLine(_buildReport, SEVEN, 863 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 864 car.getKernel().getSize(), 865 car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 866 } 867 // does train terminate into staging? 868 if (_terminateStageTrack != null) { 869 log.debug("Train terminates into staging track ({})", _terminateStageTrack.getName()); 870 // bias cars to staging 871 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) { 872 log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName()); 873 if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) { 874 log.debug("Car ({}) at it's home division yard", car.toString()); 875 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 876 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 877 } 878 } 879 // try to send to home division staging, then home division yard, 880 // then home division spur 881 else if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 882 if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) { 883 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 884 } 885 } 886 } else { 887 log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName()); 888 // 1st send car to staging dependent of shipping track division, then 889 // try spur 890 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, car.getTrack().getDivision() != car.getDivision())) { 891 return sendCarToHomeDivisionTrack(car, Track.SPUR, 892 car.getTrack().getDivision() != car.getDivision()); 893 } 894 } 895 } else { 896 // train doesn't terminate into staging 897 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) { 898 log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName()); 899 if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) { 900 log.debug("Car ({}) at it's home division yard", car.toString()); 901 if (!sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION)) { 902 return sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION); 903 } 904 } 905 // try to send to home division yard, then home division staging, 906 // then home division spur 907 else if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) { 908 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 909 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 910 } 911 } 912 } else { 913 log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName()); 914 // 1st send car to spur dependent of shipping track division, then 915 // try staging 916 if (!sendCarToHomeDivisionTrack(car, Track.SPUR, car.getTrack().getDivision() != car.getDivision())) { 917 return sendCarToHomeDivisionTrack(car, Track.STAGING, 918 car.getTrack().getDivision() != car.getDivision()); 919 } 920 } 921 } 922 return true; 923 } 924 925 private static final boolean HOME_DIVISION = true; 926 927 /** 928 * Tries to set a final destination for the car with a home division. 929 * 930 * @param car the car 931 * @param trackType One of three track types: Track.SPUR Track.YARD or 932 * Track.STAGING 933 * @param home_division If true track's division must match the car's 934 * @return true if car was given a final destination 935 */ 936 private boolean sendCarToHomeDivisionTrack(Car car, String trackType, boolean home_division) { 937 // locations not reachable 938 List<Location> locationsNotServiced = new ArrayList<>(); 939 List<Track> tracks = locationManager.getTracksByMoves(trackType); 940 log.debug("Found {} {} tracks", tracks.size(), trackType); 941 for (Track track : tracks) { 942 if (home_division && car.getDivision() != track.getDivision()) { 943 addLine(_buildReport, SEVEN, 944 Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(), 945 track.getLocation().getName(), track.getName(), track.getDivisionName(), car.toString(), 946 car.getLoadType().toLowerCase(), 947 car.getLoadName())); 948 continue; 949 } 950 if (locationsNotServiced.contains(track.getLocation())) { 951 continue; 952 } 953 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 954 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 955 track.getLocation().getName(), car.getTrackName())); 956 // location not reachable 957 locationsNotServiced.add(track.getLocation()); 958 continue; 959 } 960 // only use the termination staging track for this train 961 if (trackType.equals(Track.STAGING) && 962 _terminateStageTrack != null && 963 track.getLocation() == _terminateLocation && 964 track != _terminateStageTrack) { 965 continue; 966 } 967 if (trackType.equals(Track.SPUR)) { 968 if (sendCarToDestinationSpur(car, track)) { 969 return true; 970 } 971 } else { 972 if (sendCarToDestinationTrack(car, track)) { 973 return true; 974 } 975 } 976 } 977 addLine(_buildReport, FIVE, 978 Bundle.getMessage("buildCouldNotFindTrack", trackType.toLowerCase(), car.toString(), 979 car.getLoadType().toLowerCase(), car.getLoadName())); 980 addLine(_buildReport, SEVEN, BLANK_LINE); 981 return false; 982 } 983 984 /** 985 * Set the final destination and track for a car with a custom load. Car 986 * must not have a destination or final destination. There's a check to see 987 * if there's a spur/schedule for this car. Returns true if a schedule was 988 * found. Will hold car at current location if any of the spurs checked has 989 * the the option to "Hold cars with custom loads" enabled and the spur has 990 * an alternate track assigned. Tries to sent the car to staging if there 991 * aren't any spurs with schedules available. 992 * 993 * @param car the car with the load 994 * @return true if there's a schedule that can be routed to for this car and 995 * load 996 * @throws BuildFailedException 997 */ 998 private boolean findFinalDestinationForCarLoad(Car car) throws BuildFailedException { 999 if (car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 1000 car.getLoadName().equals(carLoads.getDefaultLoadName()) || 1001 car.getDestination() != null || 1002 car.getFinalDestination() != null) { 1003 return false; // car doesn't have a custom load, or already has a 1004 // destination set 1005 } 1006 addLine(_buildReport, FIVE, 1007 Bundle.getMessage("buildSearchForSpur", car.toString(), car.getTypeName(), 1008 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(), 1009 car.getTrackName())); 1010 if (car.getKernel() != null) { 1011 addLine(_buildReport, SEVEN, 1012 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 1013 car.getKernel().getSize(), car.getKernel().getTotalLength(), 1014 Setup.getLengthUnit().toLowerCase())); 1015 } 1016 _routeToTrackFound = false; 1017 List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR); 1018 log.debug("Found {} spurs", tracks.size()); 1019 // locations not reachable 1020 List<Location> locationsNotServiced = new ArrayList<>(); 1021 for (Track track : tracks) { 1022 if (car.getTrack() == track) { 1023 continue; 1024 } 1025 if (track.getSchedule() == null) { 1026 addLine(_buildReport, SEVEN, Bundle.getMessage("buildSpurNoSchedule", 1027 track.getLocation().getName(), track.getName())); 1028 continue; 1029 } 1030 if (locationsNotServiced.contains(track.getLocation())) { 1031 continue; 1032 } 1033 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 1034 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 1035 track.getLocation().getName(), car.getTrackName())); 1036 // location not reachable 1037 locationsNotServiced.add(track.getLocation()); 1038 continue; 1039 } 1040 if (sendCarToDestinationSpur(car, track)) { 1041 return true; 1042 } 1043 } 1044 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCouldNotFindTrack", Track.getTrackTypeName(Track.SPUR).toLowerCase(), 1045 car.toString(), car.getLoadType().toLowerCase(), car.getLoadName())); 1046 if (_routeToTrackFound && 1047 !_train.isSendCarsWithCustomLoadsToStagingEnabled() && 1048 !car.getLocation().isStaging()) { 1049 addLine(_buildReport, SEVEN, Bundle.getMessage("buildHoldCarValidRoute", car.toString(), 1050 car.getLocationName(), car.getTrackName())); 1051 } else { 1052 // try and send car to staging 1053 addLine(_buildReport, FIVE, 1054 Bundle.getMessage("buildTrySendCarToStaging", car.toString(), car.getLoadName())); 1055 tracks = locationManager.getTracks(Track.STAGING); 1056 log.debug("Found {} staging tracks", tracks.size()); 1057 while (tracks.size() > 0) { 1058 // pick a track randomly 1059 int rnd = (int) (Math.random() * tracks.size()); 1060 Track track = tracks.get(rnd); 1061 tracks.remove(track); 1062 log.debug("Staging track ({}, {})", track.getLocation().getName(), track.getName()); 1063 if (track.getLocation() == car.getLocation()) { 1064 continue; 1065 } 1066 if (locationsNotServiced.contains(track.getLocation())) { 1067 continue; 1068 } 1069 if (_terminateStageTrack != null && 1070 track.getLocation() == _terminateLocation && 1071 track != _terminateStageTrack) { 1072 continue; // ignore other staging tracks at terminus 1073 } 1074 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 1075 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 1076 track.getLocation().getName(), car.getTrackName())); 1077 locationsNotServiced.add(track.getLocation()); 1078 continue; 1079 } 1080 String status = track.isRollingStockAccepted(car); 1081 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 1082 log.debug("Staging track ({}) can't accept car ({})", track.getName(), car.toString()); 1083 continue; 1084 } 1085 addLine(_buildReport, SEVEN, Bundle.getMessage("buildStagingCanAcceptLoad", track.getLocation(), 1086 track.getName(), car.getLoadName())); 1087 // try to send car to staging 1088 car.setFinalDestination(track.getLocation()); 1089 // test to see if destination is reachable by this train 1090 if (router.setDestination(car, _train, _buildReport)) { 1091 _routeToTrackFound = true; // found a route to staging 1092 } 1093 if (car.getDestination() != null) { 1094 car.updateKernel(); // car part of kernel? 1095 return true; 1096 } 1097 // couldn't route to this staging location 1098 locationsNotServiced.add(track.getLocation()); 1099 car.setFinalDestination(null); 1100 } 1101 addLine(_buildReport, SEVEN, 1102 Bundle.getMessage("buildNoStagingForCarLoad", car.toString(), car.getLoadName())); 1103 } 1104 log.debug("routeToSpurFound is {}", _routeToTrackFound); 1105 return _routeToTrackFound; // done 1106 } 1107 1108 boolean _routeToTrackFound; 1109 1110 /** 1111 * Used to determine if spur can accept car. Also will set routeToTrackFound 1112 * to true if there's a valid route available to the spur being tested. Sets 1113 * car's final destination to track if okay. 1114 * 1115 * @param car the car 1116 * @param track the spur 1117 * @return false if there's an issue with using the spur 1118 */ 1119 private boolean sendCarToDestinationSpur(Car car, Track track) { 1120 if (!checkBasicMoves(car, track)) { 1121 addLine(_buildReport, SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", _train.getName(), 1122 car.toString(), track.getLocation().getName(), track.getName())); 1123 return false; 1124 } 1125 String status = car.checkDestination(track.getLocation(), track); 1126 if (!status.equals(Track.OKAY)) { 1127 if (track.getScheduleMode() == Track.SEQUENTIAL && status.startsWith(Track.SCHEDULE)) { 1128 addLine(_buildReport, SEVEN, Bundle.getMessage("buildTrackSequentialMode", track.getName(), 1129 track.getLocation().getName(), status)); 1130 } 1131 // if the track has an alternate track don't abort if the issue was 1132 // space 1133 if (!status.startsWith(Track.LENGTH)) { 1134 addLine(_buildReport, SEVEN, 1135 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1136 track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(), 1137 status)); 1138 return false; 1139 } 1140 String scheduleStatus = track.checkSchedule(car); 1141 if (!scheduleStatus.equals(Track.OKAY)) { 1142 addLine(_buildReport, SEVEN, 1143 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1144 track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(), 1145 scheduleStatus)); 1146 return false; 1147 } 1148 if (track.getAlternateTrack() == null) { 1149 // report that the spur is full and no alternate 1150 addLine(_buildReport, SEVEN, 1151 Bundle.getMessage("buildSpurFullNoAlternate", track.getLocation().getName(), track.getName())); 1152 return false; 1153 } else { 1154 addLine(_buildReport, SEVEN, 1155 Bundle.getMessage("buildTrackFullHasAlternate", track.getLocation().getName(), track.getName(), 1156 track.getAlternateTrack().getName())); 1157 // check to see if alternate and track are configured properly 1158 if (!_train.isLocalSwitcher() && 1159 (track.getTrainDirections() & track.getAlternateTrack().getTrainDirections()) == 0) { 1160 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotDropRsUsingTrain4", track.getName(), 1161 formatStringToCommaSeparated(Setup.getDirectionStrings(track.getTrainDirections())), 1162 track.getAlternateTrack().getName(), formatStringToCommaSeparated( 1163 Setup.getDirectionStrings(track.getAlternateTrack().getTrainDirections())))); 1164 return false; 1165 } 1166 } 1167 } 1168 addLine(_buildReport, SEVEN, BLANK_LINE); 1169 addLine(_buildReport, SEVEN, 1170 Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(), 1171 track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(), 1172 car.getLoadName())); 1173 1174 // show if track is requesting cars with custom loads to only go to 1175 // spurs 1176 if (track.isHoldCarsWithCustomLoadsEnabled()) { 1177 addLine(_buildReport, SEVEN, 1178 Bundle.getMessage("buildHoldCarsCustom", track.getLocation().getName(), track.getName())); 1179 } 1180 // check the number of in bound cars to this track 1181 if (!track.isSpaceAvailable(car)) { 1182 // Now determine if we should move the car or just leave it 1183 if (track.isHoldCarsWithCustomLoadsEnabled()) { 1184 // determine if this car can be routed to the spur 1185 String id = track.getScheduleItemId(); 1186 if (router.isCarRouteable(car, _train, track, _buildReport)) { 1187 // hold car if able to route to track 1188 _routeToTrackFound = true; 1189 } else { 1190 addLine(_buildReport, SEVEN, Bundle.getMessage("buildRouteNotFound", car.toString(), 1191 car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1192 } 1193 track.setScheduleItemId(id); // restore id 1194 } 1195 if (car.getTrack().isStaging()) { 1196 addLine(_buildReport, SEVEN, 1197 Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(), 1198 track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(), 1199 Setup.getLengthUnit().toLowerCase(), track.getReservationFactor())); 1200 } else { 1201 addLine(_buildReport, SEVEN, 1202 Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(), 1203 track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(), 1204 track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase())); 1205 } 1206 return false; 1207 } 1208 // try to send car to this spur 1209 car.setFinalDestination(track.getLocation()); 1210 car.setFinalDestinationTrack(track); 1211 // test to see if destination is reachable by this train 1212 if (router.setDestination(car, _train, _buildReport) && track.isHoldCarsWithCustomLoadsEnabled()) { 1213 _routeToTrackFound = true; // if we don't find another spur, don't 1214 // move car 1215 } 1216 if (car.getDestination() == null) { 1217 if (!router.getStatus().equals(Track.OKAY)) { 1218 addLine(_buildReport, SEVEN, 1219 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1220 } 1221 car.setFinalDestination(null); 1222 car.setFinalDestinationTrack(null); 1223 // don't move car if another train can 1224 if (router.getStatus().startsWith(Router.STATUS_NOT_THIS_TRAIN_PREFIX)) { 1225 _routeToTrackFound = true; 1226 } 1227 return false; 1228 } 1229 if (car.getDestinationTrack() != track) { 1230 track.bumpMoves(); 1231 // car is being routed to this track 1232 if (track.getSchedule() != null) { 1233 car.setScheduleItemId(track.getCurrentScheduleItem().getId()); 1234 track.bumpSchedule(); 1235 } 1236 } 1237 car.updateKernel(); 1238 return true; // done, car has a new destination 1239 } 1240 1241 /** 1242 * Destination track can be division yard or staging, NOT a spur. 1243 * 1244 * @param car the car 1245 * @param track the car's destination track 1246 * @return true if car given a new final destination 1247 */ 1248 private boolean sendCarToDestinationTrack(Car car, Track track) { 1249 if (!checkBasicMoves(car, track)) { 1250 addLine(_buildReport, SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", _train.getName(), 1251 car.toString(), track.getLocation().getName(), track.getName())); 1252 return false; 1253 } 1254 String status = car.checkDestination(track.getLocation(), track); 1255 if (!status.equals(Track.OKAY)) { 1256 addLine(_buildReport, SEVEN, 1257 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1258 track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(), status)); 1259 return false; 1260 } 1261 if (!track.isSpaceAvailable(car)) { 1262 addLine(_buildReport, SEVEN, 1263 Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(), 1264 track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(), 1265 track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase())); 1266 return false; 1267 } 1268 // try to send car to this division track 1269 addLine(_buildReport, SEVEN, 1270 Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(), 1271 track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(), 1272 car.getLoadName())); 1273 car.setFinalDestination(track.getLocation()); 1274 car.setFinalDestinationTrack(track); 1275 // test to see if destination is reachable by this train 1276 if (router.setDestination(car, _train, _buildReport)) { 1277 log.debug("Can route car to destination ({}, {})", track.getLocation().getName(), track.getName()); 1278 } 1279 if (car.getDestination() == null) { 1280 addLine(_buildReport, SEVEN, 1281 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1282 car.setFinalDestination(null); 1283 car.setFinalDestinationTrack(null); 1284 return false; 1285 } 1286 car.updateKernel(); 1287 return true; // done, car has a new final destination 1288 } 1289 1290 /** 1291 * Checks for a car's final destination, and then after checking, tries to 1292 * route the car to that destination. Normal return from this routine is 1293 * false, with the car returning with a set destination. Returns true if car 1294 * has a final destination, but can't be used for this train. 1295 * 1296 * @param car 1297 * @return false if car needs destination processing (normal). 1298 */ 1299 private boolean checkCarForFinalDestination(Car car) { 1300 if (car.getFinalDestination() == null || car.getDestination() != null) { 1301 return false; 1302 } 1303 1304 addLine(_buildReport, FIVE, 1305 Bundle.getMessage("buildCarRoutingBegins", car.toString(), car.getTypeName(), 1306 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(), 1307 car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1308 1309 // no local moves for this train? 1310 if (!_train.isLocalSwitcher() && !_train.isAllowLocalMovesEnabled() && 1311 car.getSplitLocationName().equals(car.getSplitFinalDestinationName()) && 1312 car.getTrack() != _departStageTrack) { 1313 addLine(_buildReport, FIVE, 1314 Bundle.getMessage("buildCarHasFinalDestNoMove", car.toString(), car.getLocationName(), 1315 car.getFinalDestinationName(), _train.getName())); 1316 addLine(_buildReport, FIVE, BLANK_LINE); 1317 log.debug("Removing car ({}) from list", car.toString()); 1318 _carList.remove(car); 1319 _carIndex--; 1320 return true; // car has a final destination, but no local moves by 1321 // this train 1322 } 1323 // is the car's destination the terminal and is that allowed? 1324 if (!checkThroughCarsAllowed(car, car.getFinalDestinationName())) { 1325 // don't remove car from list if departing staging 1326 if (car.getTrack() == _departStageTrack) { 1327 addLine(_buildReport, ONE, Bundle.getMessage("buildErrorCarStageDest", car.toString())); 1328 } else { 1329 log.debug("Removing car ({}) from list", car.toString()); 1330 _carList.remove(car); 1331 _carIndex--; 1332 } 1333 return true; // car has a final destination, but through traffic not 1334 // allowed by this train 1335 } 1336 // does the car have a final destination track that is willing to 1337 // service the car? 1338 // note the default mode for all track types is MATCH 1339 if (car.getFinalDestinationTrack() != null && car.getFinalDestinationTrack().getScheduleMode() == Track.MATCH) { 1340 String status = car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack()); 1341 // keep going if the only issue was track length and the track 1342 // accepts the car's load 1343 if (!status.equals(Track.OKAY) && 1344 !status.startsWith(Track.LENGTH) && 1345 !(status.contains(Track.CUSTOM) && status.contains(Track.LOAD))) { 1346 addLine(_buildReport, SEVEN, 1347 Bundle.getMessage("buildNoDestTrackNewLoad", 1348 StringUtils.capitalize(car.getFinalDestinationTrack().getTrackTypeName()), 1349 car.getFinalDestination().getName(), car.getFinalDestinationTrack().getName(), 1350 car.toString(), car.getLoadName(), status)); 1351 // is this car or kernel being sent to a track that is too 1352 // short? 1353 if (status.startsWith(Track.CAPACITY)) { 1354 // track is too short for this car or kernel 1355 addLine(_buildReport, SEVEN, 1356 Bundle.getMessage("buildTrackTooShort", car.getFinalDestination().getName(), 1357 car.getFinalDestinationTrack().getName(), car.toString())); 1358 } 1359 addLine(_buildReport, SEVEN, 1360 Bundle.getMessage("buildRemovingFinalDestinaton", car.getFinalDestination().getName(), 1361 car.getFinalDestinationTrack().getName(), car.toString())); 1362 car.setFinalDestination(null); 1363 car.setFinalDestinationTrack(null); 1364 return false; // car no longer has a final destination 1365 } 1366 } 1367 1368 // now try and route the car 1369 if (!router.setDestination(car, _train, _buildReport)) { 1370 addLine(_buildReport, SEVEN, 1371 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1372 // don't move car if routing issue was track space but not departing 1373 // staging 1374 if ((!router.getStatus().startsWith(Track.LENGTH) && 1375 !_train.isServiceAllCarsWithFinalDestinationsEnabled()) || (car.getTrack() == _departStageTrack)) { 1376 // add car to unable to route list 1377 if (!_notRoutable.contains(car)) { 1378 _notRoutable.add(car); 1379 } 1380 addLine(_buildReport, FIVE, BLANK_LINE); 1381 addLine(_buildReport, FIVE, 1382 Bundle.getMessage("buildWarningCarNotRoutable", car.toString(), car.getLocationName(), 1383 car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1384 addLine(_buildReport, FIVE, BLANK_LINE); 1385 return false; // move this car, routing failed! 1386 } 1387 } else { 1388 if (car.getDestination() != null) { 1389 return false; // routing successful process this car, normal 1390 // exit from this routine 1391 } 1392 if (car.getTrack() == _departStageTrack) { 1393 log.debug("Car ({}) departing staging with final destination ({}) and no destination", 1394 car.toString(), car.getFinalDestinationName()); 1395 return false; // try and move this car out of staging 1396 } 1397 } 1398 addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1399 addLine(_buildReport, FIVE, BLANK_LINE); 1400 return true; 1401 } 1402 1403 /** 1404 * Checks to see if car has a destination and tries to add car to train. 1405 * Will find a track for the car if needed. Returns false if car doesn't 1406 * have a destination. 1407 * 1408 * @param rl the car's route location 1409 * @param routeIndex where in the route to start search 1410 * @return true if car has a destination. Need to check if car given a train 1411 * assignment. 1412 * @throws BuildFailedException if destination was staging and can't place 1413 * car there 1414 */ 1415 private boolean checkCarForDestination(Car car, RouteLocation rl, int routeIndex) throws BuildFailedException { 1416 if (car.getDestination() == null) { 1417 return false; // the only false return 1418 } 1419 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarHasAssignedDest", car.toString(), car.getLoadName(), 1420 car.getDestinationName(), car.getDestinationTrackName())); 1421 RouteLocation rld = _train.getRoute().getLastLocationByName(car.getDestinationName()); 1422 if (rld == null) { 1423 // code check, router doesn't set a car's destination if not carried 1424 // by train being built. Car has a destination that isn't serviced 1425 // by this train. Find buildExcludeCarDestNotPartRoute in 1426 // loadRemoveAndListCars() 1427 throw new BuildFailedException(Bundle.getMessage("buildExcludeCarDestNotPartRoute", car.toString(), 1428 car.getDestinationName(), car.getDestinationTrackName(), _train.getRoute().getName())); 1429 } 1430 // now go through the route and try and find a location with 1431 // the correct destination name 1432 for (int k = routeIndex; k < _routeList.size(); k++) { 1433 rld = _routeList.get(k); 1434 // if car can be picked up later at same location, skip 1435 if (checkForLaterPickUp(car, rl, rld)) { 1436 addLine(_buildReport, SEVEN, BLANK_LINE); 1437 return true; 1438 } 1439 if (!rld.getName().equals(car.getDestinationName())) { 1440 continue; 1441 } 1442 // is the car's destination the terminal and is that allowed? 1443 if (!checkThroughCarsAllowed(car, car.getDestinationName())) { 1444 return true; 1445 } 1446 log.debug("Car ({}) found a destination in train's route", car.toString()); 1447 // are drops allows at this location? 1448 if (!rld.isDropAllowed()) { 1449 addLine(_buildReport, FIVE, Bundle.getMessage("buildRouteNoDropLocation", _train.getRoute().getName(), 1450 rld.getId(), rld.getName())); 1451 continue; 1452 } 1453 if (_train.isLocationSkipped(rld.getId())) { 1454 addLine(_buildReport, FIVE, 1455 Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), _train.getName())); 1456 continue; 1457 } 1458 // any moves left at this location? 1459 if (rld.getCarMoves() >= rld.getMaxCarMoves()) { 1460 addLine(_buildReport, FIVE, 1461 Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(), 1462 _train.getRoute().getName(), rld.getId(), rld.getName())); 1463 continue; 1464 } 1465 // is the train length okay? 1466 if (!checkTrainLength(car, rl, rld)) { 1467 continue; 1468 } 1469 // check for valid destination track 1470 if (car.getDestinationTrack() == null) { 1471 addLine(_buildReport, FIVE, Bundle.getMessage("buildCarDoesNotHaveDest", car.toString())); 1472 // is car going into staging? 1473 if (rld == _train.getTrainTerminatesRouteLocation() && _terminateStageTrack != null) { 1474 String status = car.checkDestination(car.getDestination(), _terminateStageTrack); 1475 if (status.equals(Track.OKAY)) { 1476 addLine(_buildReport, FIVE, Bundle.getMessage("buildCarAssignedToStaging", car.toString(), 1477 _terminateStageTrack.getName())); 1478 addCarToTrain(car, rl, rld, _terminateStageTrack); 1479 return true; 1480 } else { 1481 addLine(_buildReport, SEVEN, 1482 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 1483 _terminateStageTrack.getTrackTypeName(), 1484 _terminateStageTrack.getLocation().getName(), _terminateStageTrack.getName(), 1485 status)); 1486 continue; 1487 } 1488 } else { 1489 // no staging at this location, now find a destination track 1490 // for this car 1491 List<Track> tracks = getTracksAtDestination(car, rld); 1492 if (tracks.size() > 0) { 1493 if (tracks.get(1) != null) { 1494 car.setFinalDestination(car.getDestination()); 1495 car.setFinalDestinationTrack(tracks.get(1)); 1496 tracks.get(1).bumpMoves(); 1497 } 1498 addCarToTrain(car, rl, rld, tracks.get(0)); 1499 return true; 1500 } 1501 } 1502 } else { 1503 log.debug("Car ({}) has a destination track ({})", car.toString(), car.getDestinationTrack().getName()); 1504 // going into the correct staging track? 1505 if (rld.equals(_train.getTrainTerminatesRouteLocation()) && 1506 _terminateStageTrack != null && 1507 _terminateStageTrack != car.getDestinationTrack()) { 1508 // car going to wrong track in staging, change track 1509 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarDestinationStaging", car.toString(), 1510 car.getDestinationName(), car.getDestinationTrackName())); 1511 car.setDestination(_terminateStageTrack.getLocation(), _terminateStageTrack); 1512 } 1513 if (!rld.equals(_train.getTrainTerminatesRouteLocation()) || 1514 _terminateStageTrack == null || 1515 _terminateStageTrack == car.getDestinationTrack()) { 1516 // is train direction correct? and drop to interchange or 1517 // spur? 1518 if (checkDropTrainDirection(car, rld, car.getDestinationTrack()) && 1519 checkTrainCanDrop(car, car.getDestinationTrack())) { 1520 String status = car.checkDestination(car.getDestination(), car.getDestinationTrack()); 1521 if (status.equals(Track.OKAY)) { 1522 addCarToTrain(car, rl, rld, car.getDestinationTrack()); 1523 return true; 1524 } else { 1525 addLine(_buildReport, SEVEN, 1526 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 1527 car.getDestinationTrack().getTrackTypeName(), 1528 car.getDestinationTrack().getLocation().getName(), 1529 car.getDestinationTrackName(), status)); 1530 } 1531 } 1532 } else { 1533 // code check 1534 throw new BuildFailedException(Bundle.getMessage("buildCarDestinationStaging", car.toString(), 1535 car.getDestinationName(), car.getDestinationTrackName())); 1536 } 1537 } 1538 addLine(_buildReport, FIVE, 1539 Bundle.getMessage("buildCanNotDropCar", car.toString(), car.getDestinationName(), rld.getId())); 1540 if (car.getDestinationTrack() == null) { 1541 log.debug("Could not find a destination track for location ({})", car.getDestinationName()); 1542 } 1543 } 1544 log.debug("car ({}) not added to train", car.toString()); 1545 addLine(_buildReport, FIVE, 1546 Bundle.getMessage("buildDestinationNotReachable", car.getDestinationName(), rl.getName(), rl.getId())); 1547 // remove destination and revert to final destination 1548 if (car.getDestinationTrack() != null) { 1549 // going to remove this destination from car 1550 car.getDestinationTrack().setMoves(car.getDestinationTrack().getMoves() - 1); 1551 Track destTrack = car.getDestinationTrack(); 1552 // TODO should we leave the car's destination? The spur expects this 1553 // car! 1554 if (destTrack.getSchedule() != null && destTrack.getScheduleMode() == Track.SEQUENTIAL) { 1555 addLine(_buildReport, SEVEN, Bundle.getMessage("buildPickupCancelled", 1556 destTrack.getLocation().getName(), destTrack.getName())); 1557 } 1558 } 1559 car.setFinalDestination(car.getPreviousFinalDestination()); 1560 car.setFinalDestinationTrack(car.getPreviousFinalDestinationTrack()); 1561 car.setDestination(null, null); 1562 car.updateKernel(); 1563 1564 addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1565 addLine(_buildReport, FIVE, BLANK_LINE); 1566 return true; // car no longer has a destination, but it had one. 1567 } 1568 1569 /** 1570 * Find a destination and track for a car at a route location. 1571 * 1572 * @param car the car! 1573 * @param rl The car's route location 1574 * @param rld The car's route destination 1575 * @return true if successful. 1576 * @throws BuildFailedException if code check fails 1577 */ 1578 private boolean findDestinationAndTrack(Car car, RouteLocation rl, RouteLocation rld) throws BuildFailedException { 1579 int index = _routeList.indexOf(rld); 1580 if (_train.isLocalSwitcher()) { 1581 return findDestinationAndTrack(car, rl, index, index + 1); 1582 } 1583 return findDestinationAndTrack(car, rl, index - 1, index + 1); 1584 } 1585 1586 /** 1587 * Find a destination and track for a car, and add the car to the train. 1588 * 1589 * @param car The car that is looking for a destination and 1590 * destination track. 1591 * @param rl The route location for this car. 1592 * @param routeIndex Where in the train's route to begin a search for a 1593 * destination for this car. 1594 * @param routeEnd Where to stop looking for a destination. 1595 * @return true if successful, car has destination, track and a train. 1596 * @throws BuildFailedException if code check fails 1597 */ 1598 private boolean findDestinationAndTrack(Car car, RouteLocation rl, int routeIndex, int routeEnd) 1599 throws BuildFailedException { 1600 if (routeIndex + 1 == routeEnd) { 1601 log.debug("Car ({}) is at the last location in the train's route", car.toString()); 1602 } 1603 addLine(_buildReport, FIVE, Bundle.getMessage("buildFindDestinationForCar", car.toString(), car.getTypeName(), 1604 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(), 1605 car.getTrackName())); 1606 if (car.getKernel() != null) { 1607 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 1608 car.getKernel().getSize(), car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 1609 } 1610 1611 // normally start looking after car's route location 1612 int start = routeIndex; 1613 // the route location destination being checked for the car 1614 RouteLocation rld = null; 1615 // holds the best route location destination for the car 1616 RouteLocation rldSave = null; 1617 // holds the best track at destination for the car 1618 Track trackSave = null; 1619 // used when a spur has an alternate track and no schedule 1620 Track finalDestinationTrackSave = null; 1621 // true when car can be picked up from two or more locations in the 1622 // route 1623 boolean multiplePickup = false; 1624 1625 // more than one location in this route? 1626 if (!_train.isLocalSwitcher()) { 1627 start++; // begin looking for tracks at the next location 1628 } 1629 // all pick ups to terminal? 1630 if (_train.isSendCarsToTerminalEnabled() && 1631 !rl.getSplitName().equals(_departLocation.getSplitName()) && 1632 routeEnd == _routeList.size()) { 1633 addLine(_buildReport, FIVE, Bundle.getMessage("buildSendToTerminal", _terminateLocation.getName())); 1634 // user could have specified several terminal locations with the 1635 // "same" name 1636 start = routeEnd - 1; 1637 while (start > routeIndex) { 1638 if (!_routeList.get(start - 1).getSplitName() 1639 .equals(_terminateLocation.getSplitName())) { 1640 break; 1641 } 1642 start--; 1643 } 1644 } 1645 // now search for a destination for this car 1646 for (int k = start; k < routeEnd; k++) { 1647 rld = _routeList.get(k); 1648 // if car can be picked up later at same location, set flag 1649 if (checkForLaterPickUp(car, rl, rld)) { 1650 multiplePickup = true; 1651 } 1652 if (rld.isDropAllowed() || car.hasFred() || car.isCaboose()) { 1653 addLine(_buildReport, FIVE, Bundle.getMessage("buildSearchingLocation", rld.getName(), rld.getId())); 1654 } else { 1655 addLine(_buildReport, FIVE, Bundle.getMessage("buildRouteNoDropLocation", _train.getRoute().getName(), 1656 rld.getId(), rld.getName())); 1657 continue; 1658 } 1659 if (_train.isLocationSkipped(rld.getId())) { 1660 addLine(_buildReport, FIVE, 1661 Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), _train.getName())); 1662 continue; 1663 } 1664 // any moves left at this location? 1665 if (rld.getCarMoves() >= rld.getMaxCarMoves()) { 1666 addLine(_buildReport, FIVE, 1667 Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(), 1668 _train.getRoute().getName(), rld.getId(), rld.getName())); 1669 continue; 1670 } 1671 // get the destination 1672 Location testDestination = rld.getLocation(); 1673 // code check, all locations in the route have been already checked 1674 if (testDestination == null) { 1675 throw new BuildFailedException( 1676 Bundle.getMessage("buildErrorRouteLoc", _train.getRoute().getName(), rld.getName())); 1677 } 1678 // don't move car to same location unless the train is a switcher 1679 // (local moves) or is passenger, caboose or car with FRED 1680 if (rl.getSplitName().equals(rld.getSplitName()) && 1681 !_train.isLocalSwitcher() && 1682 !car.isPassenger() && 1683 !car.isCaboose() && 1684 !car.hasFred()) { 1685 // allow cars to return to the same staging location if no other 1686 // options (tracks) are available 1687 if ((_train.isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) && 1688 testDestination.isStaging() && 1689 trackSave == null) { 1690 addLine(_buildReport, SEVEN, 1691 Bundle.getMessage("buildReturnCarToStaging", car.toString(), rld.getName())); 1692 } else { 1693 addLine(_buildReport, SEVEN, 1694 Bundle.getMessage("buildCarLocEqualDestination", car.toString(), rld.getName())); 1695 continue; 1696 } 1697 } 1698 1699 // check to see if departure track has any restrictions 1700 if (!car.getTrack().isDestinationAccepted(testDestination)) { 1701 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", testDestination.getName(), 1702 car.getTrackName())); 1703 continue; 1704 } 1705 1706 if (!testDestination.acceptsTypeName(car.getTypeName())) { 1707 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotDropLocation", car.toString(), 1708 car.getTypeName(), testDestination.getName())); 1709 continue; 1710 } 1711 // can this location service this train's direction 1712 if (!checkDropTrainDirection(rld)) { 1713 continue; 1714 } 1715 // is the train length okay? 1716 if (!checkTrainLength(car, rl, rld)) { 1717 break; // no, done with this car 1718 } 1719 // is the car's destination the terminal and is that allowed? 1720 if (!checkThroughCarsAllowed(car, rld.getName())) { 1721 continue; // not allowed 1722 } 1723 1724 Track trackTemp = null; 1725 // used when alternate track selected 1726 Track finalDestinationTrackTemp = null; 1727 1728 // is there a track assigned for staging cars? 1729 if (rld == _train.getTrainTerminatesRouteLocation() && _terminateStageTrack != null) { 1730 trackTemp = tryStaging(car, rldSave); 1731 if (trackTemp == null) { 1732 continue; // no 1733 } 1734 } else { 1735 // no staging, start track search 1736 List<Track> tracks = getTracksAtDestination(car, rld); 1737 if (tracks.size() > 0) { 1738 trackTemp = tracks.get(0); 1739 finalDestinationTrackTemp = tracks.get(1); 1740 } 1741 } 1742 // did we find a new destination? 1743 if (trackTemp == null) { 1744 addLine(_buildReport, FIVE, 1745 Bundle.getMessage("buildCouldNotFindDestForCar", car.toString(), rld.getName())); 1746 } else { 1747 addLine(_buildReport, FIVE, 1748 Bundle.getMessage("buildCarCanDropMoves", car.toString(), trackTemp.getTrackTypeName(), 1749 trackTemp.getLocation().getName(), trackTemp.getName(), +rld.getCarMoves(), 1750 rld.getMaxCarMoves())); 1751 if (multiplePickup) { 1752 if (rldSave != null) { 1753 addLine(_buildReport, FIVE, 1754 Bundle.getMessage("buildTrackServicedLater", car.getLocationName(), 1755 trackTemp.getTrackTypeName(), trackTemp.getLocation().getName(), 1756 trackTemp.getName(), car.getLocationName())); 1757 } else { 1758 addLine(_buildReport, FIVE, 1759 Bundle.getMessage("buildCarHasSecond", car.toString(), car.getLocationName())); 1760 trackSave = null; 1761 } 1762 break; // done 1763 } 1764 // if there's more than one available destination use the lowest 1765 // ratio 1766 if (rldSave != null) { 1767 // check for an earlier drop in the route 1768 rld = checkForEarlierDrop(car, trackTemp, rld, start, routeEnd); 1769 double saveCarMoves = rldSave.getCarMoves(); 1770 double saveRatio = saveCarMoves / rldSave.getMaxCarMoves(); 1771 double nextCarMoves = rld.getCarMoves(); 1772 double nextRatio = nextCarMoves / rld.getMaxCarMoves(); 1773 1774 // bias cars to the terminal 1775 if (rld == _train.getTrainTerminatesRouteLocation()) { 1776 nextRatio = nextRatio * nextRatio; 1777 log.debug("Location ({}) is terminate location, adjusted nextRatio {}", rld.getName(), 1778 Double.toString(nextRatio)); 1779 1780 // bias cars with default loads to a track with a 1781 // schedule 1782 } else if (!trackTemp.getScheduleId().equals(Track.NONE)) { 1783 nextRatio = nextRatio * nextRatio; 1784 log.debug("Track ({}) has schedule ({}), adjusted nextRatio {}", trackTemp.getName(), 1785 trackTemp.getScheduleName(), Double.toString(nextRatio)); 1786 } 1787 // bias cars with default loads to saved track with a 1788 // schedule 1789 if (trackSave != null && !trackSave.getScheduleId().equals(Track.NONE)) { 1790 saveRatio = saveRatio * saveRatio; 1791 log.debug("Saved track ({}) has schedule ({}), adjusted nextRatio {}", trackSave.getName(), 1792 trackSave.getScheduleName(), Double.toString(saveRatio)); 1793 } 1794 log.debug("Saved {} = {}, {} = {}", rldSave.getName(), Double.toString(saveRatio), rld.getName(), 1795 Double.toString(nextRatio)); 1796 if (saveRatio < nextRatio) { 1797 // the saved is better than the last found 1798 rld = rldSave; 1799 trackTemp = trackSave; 1800 finalDestinationTrackTemp = finalDestinationTrackSave; 1801 } 1802 } 1803 // every time through, save the best route destination, and 1804 // track 1805 rldSave = rld; 1806 trackSave = trackTemp; 1807 finalDestinationTrackSave = finalDestinationTrackTemp; 1808 } 1809 } 1810 // did we find a destination? 1811 if (trackSave != null) { 1812 if (trackSave.isSpur()) { 1813 car.setScheduleItemId(trackSave.getScheduleItemId()); 1814 trackSave.bumpSchedule(); 1815 log.debug("Sending car to spur ({}, {}) with car schedule id ({}))", trackSave.getLocation().getName(), 1816 trackSave.getName(), car.getScheduleItemId()); 1817 } else { 1818 car.setScheduleItemId(Car.NONE); 1819 } 1820 if (finalDestinationTrackSave != null) { 1821 car.setFinalDestination(finalDestinationTrackSave.getLocation()); 1822 car.setFinalDestinationTrack(finalDestinationTrackSave); 1823 if (trackSave.isAlternate()) { 1824 finalDestinationTrackSave.bumpMoves(); // bump move count 1825 } 1826 } 1827 addCarToTrain(car, rl, rldSave, trackSave); 1828 return true; 1829 } 1830 addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1831 addLine(_buildReport, FIVE, BLANK_LINE); 1832 return false; // no build errors, but car not given destination 1833 } 1834 1835 private final static Logger log = LoggerFactory.getLogger(TrainBuilderCars.class); 1836}