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