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