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, 2025 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 pulled from an interchange or spur? 510 if (!checkPickupInterchangeOrSpur(car)) { 511 _carList.remove(car); 512 _carIndex--; // removed car from list, so backup pointer 513 addLine(_buildReport, FIVE, BLANK_LINE); 514 continue; // no 515 } 516 // can this car be picked up? 517 if (!checkPickUpTrainDirection(car, rl)) { 518 addLine(_buildReport, FIVE, BLANK_LINE); 519 continue; // no 520 } 521 // do alternate track moves on the second pass (makes FIFO / LIFO work correctly) 522 if (Setup.isBuildAggressive() && !isSecondPass && car.getTrack().isAlternate() && _completedMoves != 0) { 523 continue; 524 } 525 526 showCarServiceOrder(car); // car on FIFO or LIFO track? 527 528 // is car departing staging and generate custom load? 529 if (!generateCarLoadFromStaging(car)) { 530 if (!generateCarLoadStagingToStaging(car) && 531 car.getTrack() == _departStageTrack && 532 !_departStageTrack.isLoadNameAndCarTypeShipped(car.getLoadName(), car.getTypeName())) { 533 // report build failure car departing staging with a 534 // restricted load 535 addLine(_buildReport, ONE, Bundle.getMessage("buildErrorCarStageLoad", car.toString(), 536 car.getLoadName(), _departStageTrack.getName())); 537 addLine(_buildReport, FIVE, BLANK_LINE); 538 continue; // keep going and see if there are other cars with 539 // issues outs of staging 540 } 541 } 542 // check for quick service track timing 543 if (!checkQuickServiceDeparting(car, rl)) { 544 continue; 545 } 546 // If car been given a home division follow division rules for car 547 // movement. 548 if (!findDestinationsForCarsWithHomeDivision(car)) { 549 addLine(_buildReport, FIVE, 550 Bundle.getMessage("buildNoDestForCar", car.toString())); 551 addLine(_buildReport, FIVE, BLANK_LINE); 552 continue; // hold car at current location 553 } 554 // does car have a custom load without a destination? 555 // if departing staging, a destination for this car is needed, so 556 // keep going 557 if (findFinalDestinationForCarLoad(car) && 558 car.getDestination() == null && 559 car.getTrack() != _departStageTrack) { 560 // done with this car, it has a custom load, and there are 561 // spurs/schedules, but no destination found 562 addLine(_buildReport, FIVE, 563 Bundle.getMessage("buildNoDestForCar", car.toString())); 564 addLine(_buildReport, FIVE, BLANK_LINE); 565 continue; 566 } 567 // Check car for final destination, then an assigned destination, if 568 // neither, find a destination for the car 569 if (checkCarForFinalDestination(car)) { 570 log.debug("Car ({}) has a final desination that can't be serviced by train", car.toString()); 571 } else if (checkCarForDestination(car, rl, _routeList.indexOf(rl))) { 572 // car had a destination, could have been added to the train. 573 log.debug("Car ({}) has desination ({}) using train ({})", car.toString(), car.getDestinationName(), 574 car.getTrainName()); 575 } else { 576 findDestinationAndTrack(car, rl, _routeList.indexOf(rl), _routeList.size()); 577 } 578 if (_reqNumOfMoves <= 0) { 579 break; // done 580 } 581 // build failure if car departing staging without a destination and 582 // a train we'll just put out a warning message here so we can find 583 // out how many cars have issues 584 if (car.getTrack() == _departStageTrack && 585 (car.getDestination() == null || car.getDestinationTrack() == null || car.getTrain() == null)) { 586 addLine(_buildReport, ONE, Bundle.getMessage("buildWarningCarStageDest", car.toString())); 587 // does the car have a final destination to staging? If so we 588 // need to reset this car 589 if (car.getFinalDestinationTrack() != null && car.getFinalDestinationTrack() == _terminateStageTrack) { 590 addLine(_buildReport, THREE, 591 Bundle.getMessage("buildStagingCarHasFinal", car.toString(), car.getFinalDestinationName(), 592 car.getFinalDestinationTrackName())); 593 car.reset(); 594 } 595 addLine(_buildReport, SEVEN, BLANK_LINE); 596 } 597 } 598 if (!foundCar && !isSecondPass) { 599 addLine(_buildReport, FIVE, Bundle.getMessage("buildNoCarsAtLocation", rl.getName())); 600 addLine(_buildReport, FIVE, BLANK_LINE); 601 } 602 } 603 604 private boolean generateCarLoadFromStaging(Car car) throws BuildFailedException { 605 return generateCarLoadFromStaging(car, null); 606 } 607 608 /** 609 * Used to generate a car's load from staging. Search for a spur with a 610 * schedule and load car if possible. 611 * 612 * @param car the car 613 * @param rld The route location destination for this car. Can be null. 614 * @return true if car given a custom load 615 * @throws BuildFailedException If code check fails 616 */ 617 private boolean generateCarLoadFromStaging(Car car, RouteLocation rld) throws BuildFailedException { 618 // Code Check, car should have a track assignment 619 if (car.getTrack() == null) { 620 throw new BuildFailedException( 621 Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName())); 622 } 623 if (!car.getTrack().isStaging() || 624 (!car.getTrack().isAddCustomLoadsAnySpurEnabled() && !car.getTrack().isAddCustomLoadsEnabled()) || 625 !car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 626 car.getDestination() != null || 627 car.getFinalDestination() != null) { 628 log.debug( 629 "No load generation for car ({}) isAddLoadsAnySpurEnabled: {}, car load ({}) destination ({}) final destination ({})", 630 car.toString(), car.getTrack().isAddCustomLoadsAnySpurEnabled() ? "true" : "false", 631 car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName()); 632 // if car has a destination or final destination add "no load 633 // generated" message to report 634 if (car.getTrack().isStaging() && 635 car.getTrack().isAddCustomLoadsAnySpurEnabled() && 636 car.getLoadName().equals(carLoads.getDefaultEmptyName())) { 637 addLine(_buildReport, FIVE, 638 Bundle.getMessage("buildCarNoLoadGenerated", car.toString(), car.getLoadName(), 639 car.getDestinationName(), car.getFinalDestinationName())); 640 } 641 return false; // no load generated for this car 642 } 643 addLine(_buildReport, FIVE, 644 Bundle.getMessage("buildSearchTrackNewLoad", car.toString(), car.getTypeName(), 645 car.getLoadType().toLowerCase(), car.getLoadName(), car.getLocationName(), car.getTrackName(), 646 rld != null ? rld.getLocation().getName() : "")); 647 // check to see if car type has custom loads 648 if (carLoads.getNames(car.getTypeName()).size() == 2) { 649 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarNoCustomLoad", car.toString(), car.getTypeName())); 650 return false; 651 } 652 if (car.getKernel() != null) { 653 addLine(_buildReport, SEVEN, 654 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 655 car.getKernel().getSize(), car.getKernel().getTotalLength(), 656 Setup.getLengthUnit().toLowerCase())); 657 } 658 // save the car's load, should be the default empty 659 String oldCarLoad = car.getLoadName(); 660 List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR); 661 log.debug("Found {} spurs", tracks.size()); 662 // show locations not serviced by departure track once 663 List<Location> locationsNotServiced = new ArrayList<>(); 664 for (Track track : tracks) { 665 if (locationsNotServiced.contains(track.getLocation())) { 666 continue; 667 } 668 if (rld != null && track.getLocation() != rld.getLocation()) { 669 locationsNotServiced.add(track.getLocation()); 670 continue; 671 } 672 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 673 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 674 track.getLocation().getName(), car.getTrackName())); 675 locationsNotServiced.add(track.getLocation()); 676 continue; 677 } 678 // only use tracks serviced by this train? 679 if (car.getTrack().isAddCustomLoadsEnabled() && 680 !_train.getRoute().isLocationNameInRoute(track.getLocation().getName())) { 681 continue; 682 } 683 // only the first match in a schedule is used for a spur 684 ScheduleItem si = getScheduleItem(car, track); 685 if (si == null) { 686 continue; // no match 687 } 688 // need to set car load so testDestination will work properly 689 car.setLoadName(si.getReceiveLoadName()); 690 car.setScheduleItemId(si.getId()); 691 String status = car.checkDestination(track.getLocation(), track); 692 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 693 addLine(_buildReport, SEVEN, 694 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 695 track.getLocation().getName(), track.getName(), car.toString(), si.getReceiveLoadName(), 696 status)); 697 continue; 698 } 699 addLine(_buildReport, SEVEN, Bundle.getMessage("buildTrySpurLoad", track.getLocation().getName(), 700 track.getName(), car.getLoadName())); 701 // does the car have a home division? 702 if (car.getDivision() != null) { 703 addLine(_buildReport, SEVEN, 704 Bundle.getMessage("buildCarHasDivisionStaging", car.toString(), car.getTypeName(), 705 car.getLoadType().toLowerCase(), car.getLoadName(), car.getDivisionName(), 706 car.getLocationName(), car.getTrackName(), car.getTrack().getDivisionName())); 707 // load type empty must return to car's home division 708 // or load type load from foreign division must return to car's 709 // home division 710 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) && car.getDivision() != track.getDivision() || 711 car.getLoadType().equals(CarLoad.LOAD_TYPE_LOAD) && 712 car.getTrack().getDivision() != car.getDivision() && 713 car.getDivision() != track.getDivision()) { 714 addLine(_buildReport, SEVEN, 715 Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(), 716 track.getLocation().getName(), track.getName(), track.getDivisionName(), 717 car.toString(), car.getLoadType().toLowerCase(), car.getLoadName())); 718 continue; 719 } 720 } 721 if (!track.isSpaceAvailable(car)) { 722 addLine(_buildReport, SEVEN, 723 Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(), 724 track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(), 725 Setup.getLengthUnit().toLowerCase(), track.getReservationFactor())); 726 continue; 727 } 728 // try routing car 729 car.setFinalDestination(track.getLocation()); 730 car.setFinalDestinationTrack(track); 731 if (router.setDestination(car, _train, _buildReport) && car.getDestination() != null) { 732 // return car with this custom load and destination 733 addLine(_buildReport, FIVE, 734 Bundle.getMessage("buildCreateNewLoadForCar", car.toString(), si.getReceiveLoadName(), 735 track.getLocation().getName(), track.getName())); 736 car.setLoadGeneratedFromStaging(true); 737 // is car part of kernel? 738 car.updateKernel(); 739 track.bumpMoves(); 740 track.bumpSchedule(); 741 return true; // done, car now has a custom load 742 } 743 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotRouteCar", car.toString(), 744 si.getReceiveLoadName(), track.getLocation().getName(), track.getName())); 745 addLine(_buildReport, SEVEN, BLANK_LINE); 746 car.setDestination(null, null); 747 car.setFinalDestination(null); 748 car.setFinalDestinationTrack(null); 749 } 750 // restore car's load 751 car.setLoadName(oldCarLoad); 752 car.setScheduleItemId(Car.NONE); 753 addLine(_buildReport, FIVE, Bundle.getMessage("buildUnableNewLoad", car.toString())); 754 return false; // done, no load generated for this car 755 } 756 757 /** 758 * Tries to place a custom load in the car that is departing staging and 759 * attempts to find a destination for the car that is also staging. 760 * 761 * @param car the car 762 * @return True if custom load added to car 763 * @throws BuildFailedException If code check fails 764 */ 765 private boolean generateCarLoadStagingToStaging(Car car) throws BuildFailedException { 766 // Code Check, car should have a track assignment 767 if (car.getTrack() == null) { 768 throw new BuildFailedException( 769 Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName())); 770 } 771 if (!car.getTrack().isStaging() || 772 !car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() || 773 !car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 774 car.getDestination() != null || 775 car.getFinalDestination() != null) { 776 log.debug( 777 "No load generation for car ({}) isAddCustomLoadsAnyStagingTrackEnabled: {}, car load ({}) destination ({}) final destination ({})", 778 car.toString(), car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() ? "true" : "false", 779 car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName()); 780 return false; 781 } 782 // check to see if car type has custom loads 783 if (carLoads.getNames(car.getTypeName()).size() == 2) { 784 return false; 785 } 786 List<Track> tracks = locationManager.getTracks(Track.STAGING); 787 addLine(_buildReport, FIVE, Bundle.getMessage("buildTryStagingToStaging", car.toString(), tracks.size())); 788 if (Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED)) { 789 for (Track track : tracks) { 790 addLine(_buildReport, SEVEN, 791 Bundle.getMessage("buildStagingLocationTrack", track.getLocation().getName(), track.getName())); 792 } 793 } 794 // list of locations that can't be reached by the router 795 List<Location> locationsNotServiced = new ArrayList<>(); 796 if (_terminateStageTrack != null) { 797 addLine(_buildReport, SEVEN, 798 Bundle.getMessage("buildIgnoreStagingFirstPass", _terminateStageTrack.getLocation().getName())); 799 locationsNotServiced.add(_terminateStageTrack.getLocation()); 800 } 801 while (tracks.size() > 0) { 802 // pick a track randomly 803 int rnd = (int) (Math.random() * tracks.size()); 804 Track track = tracks.get(rnd); 805 tracks.remove(track); 806 log.debug("Try staging track ({}, {})", track.getLocation().getName(), track.getName()); 807 // find a staging track that isn't at the departure 808 if (track.getLocation() == _departLocation) { 809 log.debug("Can't use departure location ({})", track.getLocation().getName()); 810 continue; 811 } 812 if (!_train.isAllowThroughCarsEnabled() && track.getLocation() == _terminateLocation) { 813 log.debug("Through cars to location ({}) not allowed", track.getLocation().getName()); 814 continue; 815 } 816 if (locationsNotServiced.contains(track.getLocation())) { 817 log.debug("Location ({}) not reachable", track.getLocation().getName()); 818 continue; 819 } 820 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 821 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 822 track.getLocation().getName(), car.getTrackName())); 823 locationsNotServiced.add(track.getLocation()); 824 continue; 825 } 826 // the following method sets the Car load generated from staging 827 // boolean 828 if (generateLoadCarDepartingAndTerminatingIntoStaging(car, track)) { 829 // test to see if destination is reachable by this train 830 if (router.setDestination(car, _train, _buildReport) && car.getDestination() != null) { 831 return true; // done, car has a custom load and a final 832 // destination 833 } 834 addLine(_buildReport, SEVEN, Bundle.getMessage("buildStagingTrackNotReachable", 835 track.getLocation().getName(), track.getName(), car.getLoadName())); 836 // return car to original state 837 car.setLoadName(carLoads.getDefaultEmptyName()); 838 car.setLoadGeneratedFromStaging(false); 839 car.setFinalDestination(null); 840 car.updateKernel(); 841 // couldn't route to this staging location 842 locationsNotServiced.add(track.getLocation()); 843 } 844 } 845 // No staging tracks reachable, try the track the train is terminating 846 // to 847 if (_train.isAllowThroughCarsEnabled() && 848 _terminateStageTrack != null && 849 car.getTrack().isDestinationAccepted(_terminateStageTrack.getLocation()) && 850 generateLoadCarDepartingAndTerminatingIntoStaging(car, _terminateStageTrack)) { 851 return true; 852 } 853 854 addLine(_buildReport, SEVEN, 855 Bundle.getMessage("buildNoStagingForCarCustom", car.toString())); 856 addLine(_buildReport, SEVEN, BLANK_LINE); 857 return false; 858 } 859 860 /** 861 * Check to see if car has been assigned a home division. If car has a home 862 * division the following rules are applied when assigning the car a 863 * destination: 864 * <p> 865 * If car load is type empty not at car's home division yard: Car is sent to 866 * a home division yard. If home division yard not available, then car is 867 * sent to home division staging, then spur (industry). 868 * <p> 869 * If car load is type empty at a yard at the car's home division: Car is 870 * sent to a home division spur, then home division staging. 871 * <p> 872 * If car load is type load not at car's home division: Car is sent to home 873 * division spur, and if spur not available then home division staging. 874 * <p> 875 * If car load is type load at car's home division: Car is sent to any 876 * division spur or staging. 877 * 878 * @param car the car being checked for a home division 879 * @return false if destination track not found for this car 880 * @throws BuildFailedException 881 */ 882 private boolean findDestinationsForCarsWithHomeDivision(Car car) throws BuildFailedException { 883 if (car.getDivision() == null || car.getDestination() != null || car.getFinalDestination() != null) { 884 return true; 885 } 886 if (car.getDivision() == car.getTrack().getDivision()) { 887 addLine(_buildReport, FIVE, 888 Bundle.getMessage("buildCarDepartHomeDivision", car.toString(), car.getTypeName(), 889 car.getLoadType().toLowerCase(), 890 car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(), 891 car.getLocationName(), car.getTrackName(), 892 car.getTrack().getDivisionName())); 893 } else { 894 addLine(_buildReport, FIVE, 895 Bundle.getMessage("buildCarDepartForeignDivision", car.toString(), car.getTypeName(), 896 car.getLoadType().toLowerCase(), 897 car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(), 898 car.getLocationName(), car.getTrackName(), 899 car.getTrack().getDivisionName())); 900 } 901 if (car.getKernel() != null) { 902 addLine(_buildReport, SEVEN, 903 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 904 car.getKernel().getSize(), 905 car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 906 } 907 // does train terminate into staging? 908 if (_terminateStageTrack != null) { 909 log.debug("Train terminates into staging track ({})", _terminateStageTrack.getName()); 910 // bias cars to staging 911 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) { 912 log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName()); 913 if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) { 914 log.debug("Car ({}) at it's home division yard", car.toString()); 915 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 916 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 917 } 918 } 919 // try to send to home division staging, then home division yard, 920 // then home division spur 921 else if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 922 if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) { 923 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 924 } 925 } 926 } else { 927 log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName()); 928 // 1st send car to staging dependent of shipping track division, then 929 // try spur 930 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, 931 car.getTrack().getDivision() != car.getDivision())) { 932 return sendCarToHomeDivisionTrack(car, Track.SPUR, 933 car.getTrack().getDivision() != car.getDivision()); 934 } 935 } 936 } else { 937 // train doesn't terminate into staging 938 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) { 939 log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName()); 940 if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) { 941 log.debug("Car ({}) at it's home division yard", car.toString()); 942 if (!sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION)) { 943 return sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION); 944 } 945 } 946 // try to send to home division yard, then home division staging, 947 // then home division spur 948 else if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) { 949 if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) { 950 return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION); 951 } 952 } 953 } else { 954 log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName()); 955 // 1st send car to spur dependent of shipping track division, then 956 // try staging 957 if (!sendCarToHomeDivisionTrack(car, Track.SPUR, car.getTrack().getDivision() != car.getDivision())) { 958 return sendCarToHomeDivisionTrack(car, Track.STAGING, 959 car.getTrack().getDivision() != car.getDivision()); 960 } 961 } 962 } 963 return true; 964 } 965 966 private static final boolean HOME_DIVISION = true; 967 968 /** 969 * Tries to set a final destination for the car with a home division. 970 * 971 * @param car the car 972 * @param trackType One of three track types: Track.SPUR Track.YARD or 973 * Track.STAGING 974 * @param home_division If true track's division must match the car's 975 * @return true if car was given a final destination 976 */ 977 private boolean sendCarToHomeDivisionTrack(Car car, String trackType, boolean home_division) { 978 // locations not reachable 979 List<Location> locationsNotServiced = new ArrayList<>(); 980 List<Track> tracks = locationManager.getTracksByMoves(trackType); 981 log.debug("Found {} {} tracks", tracks.size(), trackType); 982 for (Track track : tracks) { 983 if (home_division && car.getDivision() != track.getDivision()) { 984 addLine(_buildReport, SEVEN, 985 Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(), 986 track.getLocation().getName(), track.getName(), track.getDivisionName(), car.toString(), 987 car.getLoadType().toLowerCase(), 988 car.getLoadName())); 989 continue; 990 } 991 if (locationsNotServiced.contains(track.getLocation())) { 992 continue; 993 } 994 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 995 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 996 track.getLocation().getName(), car.getTrackName())); 997 // location not reachable 998 locationsNotServiced.add(track.getLocation()); 999 continue; 1000 } 1001 // only use the termination staging track for this train 1002 if (trackType.equals(Track.STAGING) && 1003 _terminateStageTrack != null && 1004 track.getLocation() == _terminateLocation && 1005 track != _terminateStageTrack) { 1006 continue; 1007 } 1008 if (trackType.equals(Track.SPUR)) { 1009 if (sendCarToDestinationSpur(car, track)) { 1010 return true; 1011 } 1012 } else { 1013 if (sendCarToDestinationTrack(car, track)) { 1014 return true; 1015 } 1016 } 1017 } 1018 addLine(_buildReport, FIVE, 1019 Bundle.getMessage("buildCouldNotFindTrack", trackType.toLowerCase(), car.toString(), 1020 car.getLoadType().toLowerCase(), car.getLoadName())); 1021 addLine(_buildReport, SEVEN, BLANK_LINE); 1022 return false; 1023 } 1024 1025 /** 1026 * Set the final destination and track for a car with a custom load. Car 1027 * must not have a destination or final destination. There's a check to see 1028 * if there's a spur/schedule for this car. Returns true if a schedule was 1029 * found. Will hold car at current location if any of the spurs checked has 1030 * the the option to "Hold cars with custom loads" enabled and the spur has 1031 * an alternate track assigned. Tries to sent the car to staging if there 1032 * aren't any spurs with schedules available. 1033 * 1034 * @param car the car with the load 1035 * @return true if there's a schedule that can be routed to for this car and 1036 * load 1037 * @throws BuildFailedException 1038 */ 1039 private boolean findFinalDestinationForCarLoad(Car car) throws BuildFailedException { 1040 if (car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 1041 car.getLoadName().equals(carLoads.getDefaultLoadName()) || 1042 car.getDestination() != null || 1043 car.getFinalDestination() != null) { 1044 return false; // car doesn't have a custom load, or already has a 1045 // destination set 1046 } 1047 addLine(_buildReport, FIVE, 1048 Bundle.getMessage("buildSearchForSpur", car.toString(), car.getTypeName(), 1049 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(), 1050 car.getTrackName())); 1051 if (car.getKernel() != null) { 1052 addLine(_buildReport, SEVEN, 1053 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 1054 car.getKernel().getSize(), car.getKernel().getTotalLength(), 1055 Setup.getLengthUnit().toLowerCase())); 1056 } 1057 _routeToTrackFound = false; 1058 List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR); 1059 log.debug("Found {} spurs", tracks.size()); 1060 // locations not reachable 1061 List<Location> locationsNotServiced = new ArrayList<>(); 1062 for (Track track : tracks) { 1063 if (car.getTrack() == track) { 1064 continue; 1065 } 1066 if (track.getSchedule() == null) { 1067 addLine(_buildReport, SEVEN, Bundle.getMessage("buildSpurNoSchedule", 1068 track.getLocation().getName(), track.getName())); 1069 continue; 1070 } 1071 if (locationsNotServiced.contains(track.getLocation())) { 1072 continue; 1073 } 1074 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 1075 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 1076 track.getLocation().getName(), car.getTrackName())); 1077 // location not reachable 1078 locationsNotServiced.add(track.getLocation()); 1079 continue; 1080 } 1081 if (sendCarToDestinationSpur(car, track)) { 1082 return true; 1083 } 1084 } 1085 addLine(_buildReport, SEVEN, 1086 Bundle.getMessage("buildCouldNotFindTrack", Track.getTrackTypeName(Track.SPUR).toLowerCase(), 1087 car.toString(), car.getLoadType().toLowerCase(), car.getLoadName())); 1088 if (_routeToTrackFound && 1089 !_train.isSendCarsWithCustomLoadsToStagingEnabled() && 1090 !car.getLocation().isStaging()) { 1091 addLine(_buildReport, SEVEN, Bundle.getMessage("buildHoldCarValidRoute", car.toString(), 1092 car.getLocationName(), car.getTrackName())); 1093 } else { 1094 // try and send car to staging 1095 addLine(_buildReport, SEVEN, BLANK_LINE); 1096 addLine(_buildReport, FIVE, 1097 Bundle.getMessage("buildTrySendCarToStaging", car.toString(), car.getLoadName())); 1098 tracks = locationManager.getTracks(Track.STAGING); 1099 log.debug("Found {} staging tracks", tracks.size()); 1100 while (tracks.size() > 0) { 1101 // pick a track randomly 1102 int rnd = (int) (Math.random() * tracks.size()); 1103 Track track = tracks.get(rnd); 1104 tracks.remove(track); 1105 log.debug("Staging track ({}, {})", track.getLocation().getName(), track.getName()); 1106 if (track.getLocation() == car.getLocation()) { 1107 continue; 1108 } 1109 if (locationsNotServiced.contains(track.getLocation())) { 1110 continue; 1111 } 1112 if (_terminateStageTrack != null && 1113 track.getLocation() == _terminateLocation && 1114 track != _terminateStageTrack) { 1115 continue; // ignore other staging tracks at terminus 1116 } 1117 if (!car.getTrack().isDestinationAccepted(track.getLocation())) { 1118 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", 1119 track.getLocation().getName(), car.getTrackName())); 1120 locationsNotServiced.add(track.getLocation()); 1121 continue; 1122 } 1123 String status = track.isRollingStockAccepted(car); 1124 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 1125 log.debug("Staging track ({}) can't accept car ({})", track.getName(), car.toString()); 1126 continue; 1127 } 1128 addLine(_buildReport, SEVEN, Bundle.getMessage("buildStagingCanAcceptLoad", track.getLocation(), 1129 track.getName(), car.getLoadName())); 1130 // try to send car to staging 1131 car.setFinalDestination(track.getLocation()); 1132 // test to see if destination is reachable by this train 1133 if (router.setDestination(car, _train, _buildReport)) { 1134 _routeToTrackFound = true; // found a route to staging 1135 } 1136 if (car.getDestination() != null) { 1137 car.updateKernel(); // car part of kernel? 1138 return true; 1139 } 1140 // couldn't route to this staging location 1141 locationsNotServiced.add(track.getLocation()); 1142 car.setFinalDestination(null); 1143 } 1144 addLine(_buildReport, SEVEN, 1145 Bundle.getMessage("buildNoStagingForCarLoad", car.toString(), car.getLoadName())); 1146 if (!_routeToTrackFound) { 1147 addLine(_buildReport, SEVEN, BLANK_LINE); 1148 } 1149 } 1150 log.debug("routeToSpurFound is {}", _routeToTrackFound); 1151 return _routeToTrackFound; // done 1152 } 1153 1154 boolean _routeToTrackFound; 1155 1156 /** 1157 * Used to determine if spur can accept car. Also will set routeToTrackFound 1158 * to true if there's a valid route available to the spur being tested. Sets 1159 * car's final destination to track if okay. 1160 * 1161 * @param car the car 1162 * @param track the spur 1163 * @return false if there's an issue with using the spur 1164 */ 1165 private boolean sendCarToDestinationSpur(Car car, Track track) { 1166 if (!checkBasicMoves(car, track)) { 1167 addLine(_buildReport, SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", _train.getName(), 1168 car.toString(), track.getLocation().getName(), track.getName())); 1169 return false; 1170 } 1171 String status = car.checkDestination(track.getLocation(), track); 1172 if (!status.equals(Track.OKAY)) { 1173 if (track.getScheduleMode() == Track.SEQUENTIAL && status.startsWith(Track.SCHEDULE)) { 1174 addLine(_buildReport, SEVEN, Bundle.getMessage("buildTrackSequentialMode", 1175 track.getLocation().getName(), track.getName(), status)); 1176 } 1177 // if the track has an alternate track don't abort if the issue was 1178 // space 1179 if (!status.startsWith(Track.LENGTH)) { 1180 addLine(_buildReport, SEVEN, 1181 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1182 track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(), 1183 status)); 1184 return false; 1185 } 1186 String scheduleStatus = track.checkSchedule(car); 1187 if (!scheduleStatus.equals(Track.OKAY)) { 1188 addLine(_buildReport, SEVEN, 1189 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1190 track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(), 1191 scheduleStatus)); 1192 return false; 1193 } 1194 if (track.getAlternateTrack() == null) { 1195 // report that the spur is full and no alternate 1196 addLine(_buildReport, SEVEN, 1197 Bundle.getMessage("buildSpurFullNoAlternate", track.getLocation().getName(), track.getName())); 1198 return false; 1199 } else { 1200 addLine(_buildReport, SEVEN, 1201 Bundle.getMessage("buildTrackFullHasAlternate", track.getLocation().getName(), track.getName(), 1202 track.getAlternateTrack().getName())); 1203 // check to see if alternate and track are configured properly 1204 if (!_train.isLocalSwitcher() && 1205 (track.getTrainDirections() & track.getAlternateTrack().getTrainDirections()) == 0) { 1206 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotDropRsUsingTrain4", track.getName(), 1207 formatStringToCommaSeparated(Setup.getDirectionStrings(track.getTrainDirections())), 1208 track.getAlternateTrack().getName(), formatStringToCommaSeparated( 1209 Setup.getDirectionStrings(track.getAlternateTrack().getTrainDirections())))); 1210 return false; 1211 } 1212 } 1213 } 1214 addLine(_buildReport, SEVEN, BLANK_LINE); 1215 addLine(_buildReport, SEVEN, 1216 Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(), 1217 track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(), 1218 car.getLoadName())); 1219 1220 // show if track is requesting cars with custom loads to only go to 1221 // spurs 1222 if (track.isHoldCarsWithCustomLoadsEnabled()) { 1223 addLine(_buildReport, SEVEN, 1224 Bundle.getMessage("buildHoldCarsCustom", track.getLocation().getName(), track.getName())); 1225 } 1226 // check the number of in bound cars to this track 1227 if (!track.isSpaceAvailable(car)) { 1228 // Now determine if we should move the car or just leave it 1229 if (track.isHoldCarsWithCustomLoadsEnabled()) { 1230 // determine if this car can be routed to the spur 1231 String id = track.getScheduleItemId(); 1232 if (router.isCarRouteable(car, _train, track, _buildReport)) { 1233 // hold car if able to route to track 1234 _routeToTrackFound = true; 1235 } else { 1236 addLine(_buildReport, SEVEN, Bundle.getMessage("buildRouteNotFound", car.toString(), 1237 car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1238 } 1239 track.setScheduleItemId(id); // restore id 1240 } 1241 if (car.getTrack().isStaging()) { 1242 addLine(_buildReport, SEVEN, 1243 Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(), 1244 track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(), 1245 Setup.getLengthUnit().toLowerCase(), track.getReservationFactor())); 1246 } else { 1247 addLine(_buildReport, SEVEN, 1248 Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(), 1249 track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(), 1250 track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase())); 1251 } 1252 return false; 1253 } 1254 // try to send car to this spur 1255 car.setFinalDestination(track.getLocation()); 1256 car.setFinalDestinationTrack(track); 1257 // test to see if destination is reachable by this train 1258 if (router.setDestination(car, _train, _buildReport) && track.isHoldCarsWithCustomLoadsEnabled()) { 1259 _routeToTrackFound = true; // if we don't find another spur, don't 1260 // move car 1261 } 1262 if (car.getDestination() == null) { 1263 if (!router.getStatus().equals(Track.OKAY)) { 1264 addLine(_buildReport, SEVEN, 1265 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1266 } 1267 car.setFinalDestination(null); 1268 car.setFinalDestinationTrack(null); 1269 // don't move car if another train can 1270 if (router.getStatus().startsWith(Router.STATUS_NOT_THIS_TRAIN_PREFIX)) { 1271 _routeToTrackFound = true; 1272 } 1273 return false; 1274 } 1275 if (car.getDestinationTrack() != track) { 1276 track.bumpMoves(); 1277 // car is being routed to this track 1278 if (track.getSchedule() != null) { 1279 car.setScheduleItemId(track.getCurrentScheduleItem().getId()); 1280 track.bumpSchedule(); 1281 } 1282 } 1283 car.updateKernel(); 1284 return true; // done, car has a new destination 1285 } 1286 1287 /** 1288 * Destination track can be division yard or staging, NOT a spur. 1289 * 1290 * @param car the car 1291 * @param track the car's destination track 1292 * @return true if car given a new final destination 1293 */ 1294 private boolean sendCarToDestinationTrack(Car car, Track track) { 1295 if (!checkBasicMoves(car, track)) { 1296 addLine(_buildReport, SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", _train.getName(), 1297 car.toString(), track.getLocation().getName(), track.getName())); 1298 return false; 1299 } 1300 String status = car.checkDestination(track.getLocation(), track); 1301 1302 if (!status.equals(Track.OKAY)) { 1303 addLine(_buildReport, SEVEN, 1304 Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()), 1305 track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(), status)); 1306 return false; 1307 } 1308 if (!track.isSpaceAvailable(car)) { 1309 addLine(_buildReport, SEVEN, 1310 Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(), 1311 track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(), 1312 track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase())); 1313 return false; 1314 } 1315 // try to send car to this division track 1316 addLine(_buildReport, SEVEN, 1317 Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(), 1318 track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(), 1319 car.getLoadName())); 1320 car.setFinalDestination(track.getLocation()); 1321 car.setFinalDestinationTrack(track); 1322 // test to see if destination is reachable by this train 1323 if (router.setDestination(car, _train, _buildReport)) { 1324 log.debug("Can route car to destination ({}, {})", track.getLocation().getName(), track.getName()); 1325 } 1326 if (car.getDestination() == null) { 1327 addLine(_buildReport, SEVEN, 1328 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1329 car.setFinalDestination(null); 1330 car.setFinalDestinationTrack(null); 1331 return false; 1332 } 1333 car.updateKernel(); 1334 return true; // done, car has a new final destination 1335 } 1336 1337 /** 1338 * Checks for a car's final destination, and then after checking, tries to 1339 * route the car to that destination. Normal return from this routine is 1340 * false, with the car returning with a set destination. Returns true if car 1341 * has a final destination, but can't be used for this train. 1342 * 1343 * @param car 1344 * @return false if car needs destination processing (normal). 1345 */ 1346 private boolean checkCarForFinalDestination(Car car) { 1347 if (car.getFinalDestination() == null || car.getDestination() != null) { 1348 return false; 1349 } 1350 1351 addLine(_buildReport, FIVE, 1352 Bundle.getMessage("buildCarRoutingBegins", car.toString(), car.getTypeName(), 1353 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(), 1354 car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1355 1356 // no local moves for this train? 1357 if (!_train.isLocalSwitcher() && 1358 !_train.isAllowLocalMovesEnabled() && 1359 car.getSplitLocationName().equals(car.getSplitFinalDestinationName()) && 1360 car.getTrack() != _departStageTrack) { 1361 addLine(_buildReport, FIVE, 1362 Bundle.getMessage("buildCarHasFinalDestNoMove", car.toString(), car.getLocationName(), 1363 car.getFinalDestinationName(), _train.getName())); 1364 addLine(_buildReport, FIVE, BLANK_LINE); 1365 log.debug("Removing car ({}) from list", car.toString()); 1366 _carList.remove(car); 1367 _carIndex--; 1368 return true; // car has a final destination, but no local moves by 1369 // this train 1370 } 1371 // is the car's destination the terminal and is that allowed? 1372 if (!checkThroughCarsAllowed(car, car.getFinalDestinationName())) { 1373 // don't remove car from list if departing staging 1374 if (car.getTrack() == _departStageTrack) { 1375 addLine(_buildReport, ONE, Bundle.getMessage("buildErrorCarStageDest", car.toString())); 1376 } else { 1377 log.debug("Removing car ({}) from list", car.toString()); 1378 _carList.remove(car); 1379 _carIndex--; 1380 } 1381 return true; // car has a final destination, but through traffic not 1382 // allowed by this train 1383 } 1384 // does the car have a final destination track that is willing to 1385 // service the car? 1386 // note the default mode for all track types is MATCH 1387 if (car.getFinalDestinationTrack() != null && car.getFinalDestinationTrack().getScheduleMode() == Track.MATCH) { 1388 String status = car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack()); 1389 // keep going if the only issue was track length and the track 1390 // accepts the car's load 1391 if (!status.equals(Track.OKAY) && 1392 !status.startsWith(Track.LENGTH) && 1393 !(status.contains(Track.CUSTOM) && status.contains(Track.LOAD))) { 1394 addLine(_buildReport, SEVEN, 1395 Bundle.getMessage("buildNoDestTrackNewLoad", 1396 StringUtils.capitalize(car.getFinalDestinationTrack().getTrackTypeName()), 1397 car.getFinalDestination().getName(), car.getFinalDestinationTrack().getName(), 1398 car.toString(), car.getLoadName(), status)); 1399 // is this car or kernel being sent to a track that is too 1400 // short? 1401 if (status.startsWith(Track.CAPACITY)) { 1402 // track is too short for this car or kernel 1403 addLine(_buildReport, SEVEN, 1404 Bundle.getMessage("buildTrackTooShort", car.getFinalDestination().getName(), 1405 car.getFinalDestinationTrack().getName(), car.toString())); 1406 } 1407 _warnings++; 1408 addLine(_buildReport, SEVEN, 1409 Bundle.getMessage("buildWarningRemovingFinalDest", car.getFinalDestination().getName(), 1410 car.getFinalDestinationTrack().getName(), car.toString())); 1411 car.setFinalDestination(null); 1412 car.setFinalDestinationTrack(null); 1413 return false; // car no longer has a final destination 1414 } 1415 } 1416 1417 // now try and route the car 1418 if (!router.setDestination(car, _train, _buildReport)) { 1419 addLine(_buildReport, SEVEN, 1420 Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus())); 1421 // don't move car if routing issue was track space but not departing 1422 // staging 1423 if ((!router.getStatus().startsWith(Track.LENGTH) && 1424 !_train.isServiceAllCarsWithFinalDestinationsEnabled()) || (car.getTrack() == _departStageTrack)) { 1425 // add car to unable to route list 1426 if (!_notRoutable.contains(car)) { 1427 _notRoutable.add(car); 1428 } 1429 addLine(_buildReport, FIVE, BLANK_LINE); 1430 addLine(_buildReport, FIVE, 1431 Bundle.getMessage("buildWarningCarNotRoutable", car.toString(), car.getLocationName(), 1432 car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 1433 addLine(_buildReport, FIVE, BLANK_LINE); 1434 return false; // move this car, routing failed! 1435 } 1436 } else { 1437 if (car.getDestination() != null) { 1438 return false; // routing successful process this car, normal 1439 // exit from this routine 1440 } 1441 if (car.getTrack() == _departStageTrack) { 1442 log.debug("Car ({}) departing staging with final destination ({}) and no destination", 1443 car.toString(), car.getFinalDestinationName()); 1444 return false; // try and move this car out of staging 1445 } 1446 } 1447 addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1448 addLine(_buildReport, FIVE, BLANK_LINE); 1449 return true; 1450 } 1451 1452 /** 1453 * Checks to see if car has a destination and tries to add car to train. 1454 * Will find a track for the car if needed. Returns false if car doesn't 1455 * have a destination. 1456 * 1457 * @param rl the car's route location 1458 * @param routeIndex where in the route to start search 1459 * @return true if car has a destination. Need to check if car given a train 1460 * assignment. 1461 * @throws BuildFailedException if destination was staging and can't place 1462 * car there 1463 */ 1464 private boolean checkCarForDestination(Car car, RouteLocation rl, int routeIndex) throws BuildFailedException { 1465 if (car.getDestination() == null) { 1466 return false; // the only false return 1467 } 1468 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarHasAssignedDest", car.toString(), car.getLoadName(), 1469 car.getDestinationName(), car.getDestinationTrackName(), car.getFinalDestinationName(), 1470 car.getFinalDestinationTrackName())); 1471 RouteLocation rld = _train.getRoute().getLastLocationByName(car.getDestinationName()); 1472 if (rld == null) { 1473 // code check, router doesn't set a car's destination if not carried 1474 // by train being built. Car has a destination that isn't serviced 1475 // by this train. Find buildExcludeCarDestNotPartRoute in 1476 // loadRemoveAndListCars() 1477 throw new BuildFailedException(Bundle.getMessage("buildExcludeCarDestNotPartRoute", car.toString(), 1478 car.getDestinationName(), car.getDestinationTrackName(), _train.getRoute().getName())); 1479 } 1480 // now go through the route and try and find a location with 1481 // the correct destination name 1482 for (int k = routeIndex; k < _routeList.size(); k++) { 1483 rld = _routeList.get(k); 1484 // if car can be picked up later at same location, skip 1485 if (checkForLaterPickUp(car, rl, rld)) { 1486 addLine(_buildReport, SEVEN, BLANK_LINE); 1487 return true; 1488 } 1489 if (!rld.getName().equals(car.getDestinationName())) { 1490 continue; 1491 } 1492 // is the car's destination the terminal and is that allowed? 1493 if (!checkThroughCarsAllowed(car, car.getDestinationName())) { 1494 return true; 1495 } 1496 log.debug("Car ({}) found a destination in train's route", car.toString()); 1497 // are drops allows at this location? 1498 if (!rld.isDropAllowed() && !car.isLocalMove()) { 1499 addLine(_buildReport, FIVE, Bundle.getMessage("buildRouteNoDropLocation", _train.getRoute().getName(), 1500 rld.getId(), rld.getName())); 1501 continue; 1502 } 1503 // are local moves allows at this location? 1504 if (!rld.isLocalMovesAllowed() && car.isLocalMove()) { 1505 addLine(_buildReport, FIVE, Bundle.getMessage("buildRouteNoLocalLocation", _train.getRoute().getName(), 1506 rld.getId(), rld.getName())); 1507 continue; 1508 } 1509 if (_train.isLocationSkipped(rld)) { 1510 addLine(_buildReport, FIVE, 1511 Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), _train.getName())); 1512 continue; 1513 } 1514 // any moves left at this location? 1515 if (rld.getCarMoves() >= rld.getMaxCarMoves()) { 1516 addLine(_buildReport, FIVE, 1517 Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(), 1518 _train.getRoute().getName(), rld.getId(), rld.getName())); 1519 continue; 1520 } 1521 // is the train length okay? 1522 if (!checkTrainLength(car, rl, rld)) { 1523 continue; 1524 } 1525 // check for valid destination track 1526 if (car.getDestinationTrack() == null) { 1527 addLine(_buildReport, FIVE, Bundle.getMessage("buildCarDoesNotHaveDest", car.toString())); 1528 // is car going into staging? 1529 if (rld == _train.getTrainTerminatesRouteLocation() && _terminateStageTrack != null) { 1530 String status = car.checkDestination(car.getDestination(), _terminateStageTrack); 1531 if (status.equals(Track.OKAY)) { 1532 addLine(_buildReport, FIVE, Bundle.getMessage("buildCarAssignedToStaging", car.toString(), 1533 _terminateStageTrack.getName())); 1534 addCarToTrain(car, rl, rld, _terminateStageTrack); 1535 return true; 1536 } else { 1537 addLine(_buildReport, SEVEN, 1538 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 1539 _terminateStageTrack.getTrackTypeName(), 1540 _terminateStageTrack.getLocation().getName(), _terminateStageTrack.getName(), 1541 status)); 1542 continue; 1543 } 1544 } else { 1545 // no staging at this location, now find a destination track 1546 // for this car 1547 List<Track> tracks = getTracksAtDestination(car, rld); 1548 if (tracks.size() > 0) { 1549 if (tracks.get(1) != null) { 1550 car.setFinalDestination(car.getDestination()); 1551 car.setFinalDestinationTrack(tracks.get(1)); 1552 tracks.get(1).bumpMoves(); 1553 } 1554 addLine(_buildReport, FIVE, 1555 Bundle.getMessage("buildCarCanDropMoves", car.toString(), 1556 tracks.get(0).getTrackTypeName(), 1557 tracks.get(0).getLocation().getName(), tracks.get(0).getName(), 1558 rld.getCarMoves(), rld.getMaxCarMoves())); 1559 addCarToTrain(car, rl, rld, tracks.get(0)); 1560 return true; 1561 } 1562 } 1563 } else { 1564 log.debug("Car ({}) has a destination track ({})", car.toString(), car.getDestinationTrack().getName()); 1565 // going into the correct staging track? 1566 if (rld.equals(_train.getTrainTerminatesRouteLocation()) && 1567 _terminateStageTrack != null && 1568 _terminateStageTrack != car.getDestinationTrack()) { 1569 // car going to wrong track in staging, change track 1570 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarDestinationStaging", car.toString(), 1571 car.getDestinationName(), car.getDestinationTrackName())); 1572 car.setDestination(_terminateStageTrack.getLocation(), _terminateStageTrack); 1573 } 1574 if (!rld.equals(_train.getTrainTerminatesRouteLocation()) || 1575 _terminateStageTrack == null || 1576 _terminateStageTrack == car.getDestinationTrack()) { 1577 // is train direction correct? and drop to interchange or 1578 // spur? 1579 if (checkDropTrainDirection(car, rld, car.getDestinationTrack()) && 1580 checkTrainCanDrop(car, car.getDestinationTrack())) { 1581 String status = car.checkDestination(car.getDestination(), car.getDestinationTrack()); 1582 if (status.equals(Track.OKAY) && 1583 (status = checkReserved(_train, rld, car, car.getDestinationTrack(), true)) 1584 .equals(Track.OKAY)) { 1585 Track destTrack = car.getDestinationTrack(); 1586 addCarToTrain(car, rl, rld, destTrack); 1587 return true; 1588 } 1589 if (status.equals(TIMING) && checkForAlternate(car, car.getDestinationTrack())) { 1590 // send car to alternate track) { 1591 car.setFinalDestination(car.getDestination()); 1592 car.setFinalDestinationTrack(car.getDestinationTrack()); 1593 addCarToTrain(car, rl, rld, car.getDestinationTrack().getAlternateTrack()); 1594 return true; 1595 } 1596 addLine(_buildReport, SEVEN, 1597 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 1598 car.getDestinationTrack().getTrackTypeName(), 1599 car.getDestinationTrack().getLocation().getName(), 1600 car.getDestinationTrackName(), status)); 1601 1602 } 1603 } else { 1604 // code check 1605 throw new BuildFailedException(Bundle.getMessage("buildCarDestinationStaging", car.toString(), 1606 car.getDestinationName(), car.getDestinationTrackName())); 1607 } 1608 } 1609 addLine(_buildReport, FIVE, 1610 Bundle.getMessage("buildCanNotDropCar", car.toString(), car.getDestinationName(), rld.getId())); 1611 if (car.getDestinationTrack() == null) { 1612 log.debug("Could not find a destination track for location ({})", car.getDestinationName()); 1613 } 1614 } 1615 log.debug("car ({}) not added to train", car.toString()); 1616 addLine(_buildReport, FIVE, 1617 Bundle.getMessage("buildDestinationNotReachable", car.getDestinationName(), rl.getName(), rl.getId())); 1618 // remove destination and revert to final destination 1619 if (car.getDestinationTrack() != null) { 1620 // going to remove this destination from car 1621 car.getDestinationTrack().setMoves(car.getDestinationTrack().getMoves() - 1); 1622 Track destTrack = car.getDestinationTrack(); 1623 // TODO should we leave the car's destination? The spur expects this 1624 // car! 1625 if (destTrack.getSchedule() != null && destTrack.getScheduleMode() == Track.SEQUENTIAL) { 1626 addLine(_buildReport, SEVEN, Bundle.getMessage("buildPickupCanceled", 1627 destTrack.getLocation().getName(), destTrack.getName())); 1628 } 1629 } 1630 car.setFinalDestination(car.getPreviousFinalDestination()); 1631 car.setFinalDestinationTrack(car.getPreviousFinalDestinationTrack()); 1632 car.setDestination(null, null); 1633 car.updateKernel(); 1634 1635 addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1636 addLine(_buildReport, FIVE, BLANK_LINE); 1637 return true; // car no longer has a destination, but it had one. 1638 } 1639 1640 /** 1641 * Find a destination and track for a car at a route location. 1642 * 1643 * @param car the car! 1644 * @param rl The car's route location 1645 * @param rld The car's route destination 1646 * @return true if successful. 1647 * @throws BuildFailedException if code check fails 1648 */ 1649 private boolean findDestinationAndTrack(Car car, RouteLocation rl, RouteLocation rld) throws BuildFailedException { 1650 int index = _routeList.indexOf(rld); 1651 if (_train.isLocalSwitcher()) { 1652 return findDestinationAndTrack(car, rl, index, index + 1); 1653 } 1654 return findDestinationAndTrack(car, rl, index - 1, index + 1); 1655 } 1656 1657 /** 1658 * Find a destination and track for a car, and add the car to the train. 1659 * 1660 * @param car The car that is looking for a destination and 1661 * destination track. 1662 * @param rl The route location for this car. 1663 * @param routeIndex Where in the train's route to begin a search for a 1664 * destination for this car. 1665 * @param routeEnd Where to stop looking for a destination. 1666 * @return true if successful, car has destination, track and a train. 1667 * @throws BuildFailedException if code check fails 1668 */ 1669 private boolean findDestinationAndTrack(Car car, RouteLocation rl, int routeIndex, int routeEnd) 1670 throws BuildFailedException { 1671 if (routeIndex + 1 == routeEnd) { 1672 log.debug("Car ({}) is at the last location in the train's route", car.toString()); 1673 } 1674 addLine(_buildReport, FIVE, Bundle.getMessage("buildFindDestinationForCar", car.toString(), car.getTypeName(), 1675 car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(), 1676 car.getTrackName())); 1677 if (car.getKernel() != null) { 1678 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 1679 car.getKernel().getSize(), car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 1680 } 1681 1682 // normally start looking after car's route location 1683 int start = routeIndex; 1684 // the route location destination being checked for the car 1685 RouteLocation rld = null; 1686 // holds the best route location destination for the car 1687 RouteLocation rldSave = null; 1688 // holds the best track at destination for the car 1689 Track trackSave = null; 1690 // used when a spur has an alternate track and no schedule 1691 Track finalDestinationTrackSave = null; 1692 // true when car can be picked up from two or more locations in the 1693 // route 1694 boolean multiplePickup = false; 1695 1696 if (!_train.isLocalSwitcher()) { 1697 start++; // begin looking for tracks at the next location 1698 } 1699 // all pick ups to terminal? 1700 if (_train.isSendCarsToTerminalEnabled() && 1701 !rl.getSplitName().equals(_departLocation.getSplitName()) && 1702 routeEnd == _routeList.size()) { 1703 addLine(_buildReport, FIVE, Bundle.getMessage("buildSendToTerminal", _terminateLocation.getName())); 1704 // user could have specified several terminal locations with the 1705 // "same" name 1706 start = routeEnd - 1; 1707 while (start > routeIndex) { 1708 if (!_routeList.get(start - 1).getSplitName() 1709 .equals(_terminateLocation.getSplitName())) { 1710 break; 1711 } 1712 start--; 1713 } 1714 } 1715 // now search for a destination for this car 1716 for (int k = start; k < routeEnd; k++) { 1717 rld = _routeList.get(k); 1718 // if car can be picked up later at same location, set flag 1719 if (checkForLaterPickUp(car, rl, rld)) { 1720 multiplePickup = true; 1721 } 1722 if (rld.isDropAllowed() || car.hasFred() || car.isCaboose()) { 1723 addLine(_buildReport, FIVE, Bundle.getMessage("buildSearchingLocation", rld.getName(), rld.getId())); 1724 } else { 1725 addLine(_buildReport, FIVE, Bundle.getMessage("buildRouteNoDropLocation", _train.getRoute().getName(), 1726 rld.getId(), rld.getName())); 1727 continue; 1728 } 1729 if (_train.isLocationSkipped(rld)) { 1730 addLine(_buildReport, FIVE, 1731 Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), _train.getName())); 1732 continue; 1733 } 1734 // any moves left at this location? 1735 if (rld.getCarMoves() >= rld.getMaxCarMoves()) { 1736 addLine(_buildReport, FIVE, 1737 Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(), 1738 _train.getRoute().getName(), rld.getId(), rld.getName())); 1739 continue; 1740 } 1741 // get the destination 1742 Location testDestination = rld.getLocation(); 1743 // code check, all locations in the route have been already checked 1744 if (testDestination == null) { 1745 throw new BuildFailedException( 1746 Bundle.getMessage("buildErrorRouteLoc", _train.getRoute().getName(), rld.getName())); 1747 } 1748 // don't move car to same location unless the train is a switcher 1749 // (local moves) or is passenger, caboose or car with FRED 1750 if (rl.getSplitName().equals(rld.getSplitName()) && 1751 !_train.isLocalSwitcher() && 1752 !car.isPassenger() && 1753 !car.isCaboose() && 1754 !car.hasFred()) { 1755 // allow cars to return to the same staging location if no other 1756 // options (tracks) are available 1757 if ((_train.isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) && 1758 testDestination.isStaging() && 1759 trackSave == null) { 1760 addLine(_buildReport, SEVEN, 1761 Bundle.getMessage("buildReturnCarToStaging", car.toString(), rld.getName())); 1762 } else { 1763 addLine(_buildReport, SEVEN, 1764 Bundle.getMessage("buildCarLocEqualDestination", car.toString(), rld.getName())); 1765 continue; 1766 } 1767 } 1768 1769 // check to see if departure track has any restrictions 1770 if (!car.getTrack().isDestinationAccepted(testDestination)) { 1771 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", testDestination.getName(), 1772 car.getTrackName())); 1773 continue; 1774 } 1775 1776 if (!testDestination.acceptsTypeName(car.getTypeName())) { 1777 addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotDropLocation", car.toString(), 1778 car.getTypeName(), testDestination.getName())); 1779 continue; 1780 } 1781 // can this location service this train's direction 1782 if (!checkDropTrainDirection(rld)) { 1783 continue; 1784 } 1785 // is the train length okay? 1786 if (!checkTrainLength(car, rl, rld)) { 1787 break; // no, done with this car 1788 } 1789 // is the car's destination the terminal and is that allowed? 1790 if (!checkThroughCarsAllowed(car, rld.getName())) { 1791 continue; // not allowed 1792 } 1793 1794 Track trackTemp = null; 1795 // used when alternate track selected 1796 Track finalDestinationTrackTemp = null; 1797 1798 // is there a track assigned for staging cars? 1799 if (rld == _train.getTrainTerminatesRouteLocation() && _terminateStageTrack != null) { 1800 trackTemp = tryStaging(car, rldSave); 1801 if (trackTemp == null) { 1802 continue; // no 1803 } 1804 } else { 1805 // not staging, start track search 1806 List<Track> tracks = getTracksAtDestination(car, rld); 1807 if (tracks.size() > 0) { 1808 trackTemp = tracks.get(0); 1809 finalDestinationTrackTemp = tracks.get(1); 1810 } 1811 } 1812 // did we find a new destination? 1813 if (trackTemp == null) { 1814 addLine(_buildReport, FIVE, 1815 Bundle.getMessage("buildCouldNotFindDestForCar", car.toString(), rld.getName())); 1816 } else { 1817 addLine(_buildReport, FIVE, 1818 Bundle.getMessage("buildCarCanDropMoves", car.toString(), trackTemp.getTrackTypeName(), 1819 trackTemp.getLocation().getName(), trackTemp.getName(), +rld.getCarMoves(), 1820 rld.getMaxCarMoves())); 1821 if (multiplePickup) { 1822 if (rldSave != null) { 1823 addLine(_buildReport, FIVE, 1824 Bundle.getMessage("buildTrackServicedLater", car.getLocationName(), 1825 trackTemp.getTrackTypeName(), trackTemp.getLocation().getName(), 1826 trackTemp.getName(), car.getLocationName())); 1827 } else { 1828 addLine(_buildReport, FIVE, 1829 Bundle.getMessage("buildCarHasSecond", car.toString(), car.getLocationName())); 1830 trackSave = null; 1831 } 1832 break; // done 1833 } 1834 // if there's more than one available destination use the lowest 1835 // ratio 1836 if (rldSave != null) { 1837 // check for an earlier drop in the route 1838 rld = checkForEarlierDrop(car, trackTemp, rld, start, routeEnd); 1839 double saveCarMoves = rldSave.getCarMoves(); 1840 double saveRatio = saveCarMoves / rldSave.getMaxCarMoves(); 1841 double nextCarMoves = rld.getCarMoves(); 1842 double nextRatio = nextCarMoves / rld.getMaxCarMoves(); 1843 1844 // bias cars to the terminal 1845 if (rld == _train.getTrainTerminatesRouteLocation()) { 1846 nextRatio = nextRatio * nextRatio; 1847 log.debug("Location ({}) is terminate location, adjusted nextRatio {}", rld.getName(), 1848 Double.toString(nextRatio)); 1849 1850 // bias cars with default loads to a track with a 1851 // schedule 1852 } else if (!trackTemp.getScheduleId().equals(Track.NONE)) { 1853 nextRatio = nextRatio * nextRatio; 1854 log.debug("Track ({}) has schedule ({}), adjusted nextRatio {}", trackTemp.getName(), 1855 trackTemp.getScheduleName(), Double.toString(nextRatio)); 1856 } 1857 // bias cars with default loads to saved track with a 1858 // schedule 1859 if (trackSave != null && !trackSave.getScheduleId().equals(Track.NONE)) { 1860 saveRatio = saveRatio * saveRatio; 1861 log.debug("Saved track ({}) has schedule ({}), adjusted nextRatio {}", trackSave.getName(), 1862 trackSave.getScheduleName(), Double.toString(saveRatio)); 1863 } 1864 log.debug("Saved {} = {}, {} = {}", rldSave.getName(), Double.toString(saveRatio), rld.getName(), 1865 Double.toString(nextRatio)); 1866 if (saveRatio < nextRatio) { 1867 // the saved is better than the last found 1868 rld = rldSave; 1869 trackTemp = trackSave; 1870 finalDestinationTrackTemp = finalDestinationTrackSave; 1871 } 1872 } 1873 // every time through, save the best route destination, and 1874 // track 1875 rldSave = rld; 1876 trackSave = trackTemp; 1877 finalDestinationTrackSave = finalDestinationTrackTemp; 1878 } 1879 } 1880 // did we find a destination? 1881 if (trackSave != null && rldSave != null) { 1882 // determine if local staging move is allowed (leaves car in staging) 1883 if ((_train.isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) && 1884 rl.isDropAllowed() && 1885 rl.getLocation().isStaging() && 1886 trackSave.isStaging() && 1887 rl.getLocation() == rldSave.getLocation() && 1888 !_train.isLocalSwitcher() && 1889 !car.isPassenger() && 1890 !car.isCaboose() && 1891 !car.hasFred()) { 1892 addLine(_buildReport, SEVEN, 1893 Bundle.getMessage("buildLeaveCarInStaging", car.toString(), car.getLocationName(), 1894 car.getTrackName())); 1895 rldSave = rl; // make local move 1896 } else if (trackSave.isSpur()) { 1897 car.setScheduleItemId(trackSave.getScheduleItemId()); 1898 trackSave.bumpSchedule(); 1899 log.debug("Sending car to spur ({}, {}) with car schedule id ({}))", trackSave.getLocation().getName(), 1900 trackSave.getName(), car.getScheduleItemId()); 1901 } else { 1902 car.setScheduleItemId(Car.NONE); 1903 } 1904 if (finalDestinationTrackSave != null) { 1905 car.setFinalDestination(finalDestinationTrackSave.getLocation()); 1906 car.setFinalDestinationTrack(finalDestinationTrackSave); 1907 if (trackSave.isAlternate()) { 1908 finalDestinationTrackSave.bumpMoves(); // bump move count 1909 } 1910 } 1911 addCarToTrain(car, rl, rldSave, trackSave); 1912 return true; 1913 } 1914 addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 1915 addLine(_buildReport, FIVE, BLANK_LINE); 1916 return false; // no build errors, but car not given destination 1917 } 1918 1919 /** 1920 * Add car to train, and adjust train length and weight 1921 * 1922 * @param car the car being added to the train 1923 * @param rl the departure route location for this car 1924 * @param rld the destination route location for this car 1925 * @param track the destination track for this car 1926 */ 1927 protected void addCarToTrain(Car car, RouteLocation rl, RouteLocation rld, Track track) { 1928 car = checkQuickServiceArrival(car, rld, track); 1929 addLine(_buildReport, THREE, 1930 Bundle.getMessage("buildCarAssignedDest", car.toString(), rld.getName(), track.getName())); 1931 car.setDestination(track.getLocation(), track, Car.FORCE); 1932 int length = car.getTotalLength(); 1933 int weightTons = car.getAdjustedWeightTons(); 1934 // car could be part of a kernel 1935 if (car.getKernel() != null) { 1936 length = car.getKernel().getTotalLength(); // includes couplers 1937 weightTons = car.getKernel().getAdjustedWeightTons(); 1938 List<Car> kCars = car.getKernel().getCars(); 1939 addLine(_buildReport, THREE, 1940 Bundle.getMessage("buildCarPartOfKernel", car.toString(), car.getKernelName(), kCars.size(), 1941 car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase())); 1942 for (Car kCar : kCars) { 1943 if (kCar != car) { 1944 addLine(_buildReport, THREE, Bundle.getMessage("buildCarKernelAssignedDest", kCar.toString(), 1945 kCar.getKernelName(), rld.getName(), track.getName())); 1946 kCar.setTrain(_train); 1947 kCar.setRouteLocation(rl); 1948 kCar.setRouteDestination(rld); 1949 kCar.setDestination(track.getLocation(), track, Car.FORCE); // force destination 1950 // save final destination and track values in case of train reset 1951 kCar.setPreviousFinalDestination(car.getPreviousFinalDestination()); 1952 kCar.setPreviousFinalDestinationTrack(car.getPreviousFinalDestinationTrack()); 1953 } 1954 } 1955 car.updateKernel(); 1956 } 1957 // warn if car's load wasn't generated out of staging 1958 if (!_train.isLoadNameAccepted(car.getLoadName(), car.getTypeName())) { 1959 _warnings++; 1960 addLine(_buildReport, SEVEN, 1961 Bundle.getMessage("buildWarnCarDepartStaging", car.toString(), car.getLoadName())); 1962 } 1963 addLine(_buildReport, THREE, BLANK_LINE); 1964 _numberCars++; // bump number of cars moved by this train 1965 _completedMoves++; // bump number of car pick up moves for the location 1966 _reqNumOfMoves--; // decrement number of moves left for the location 1967 1968 if (_carList.remove(car)) { 1969 _carIndex--; // removed car from list, so backup pointer 1970 } 1971 1972 rl.setCarMoves(rl.getCarMoves() + 1); 1973 if (rl != rld) { 1974 rld.setCarMoves(rld.getCarMoves() + 1); 1975 } 1976 // now adjust train length and weight for each location that car is in 1977 // the train 1978 finishAddRsToTrain(car, rl, rld, length, weightTons); 1979 } 1980 1981 /** 1982 * Checks to see if cars that are already in the train can be redirected 1983 * from the alternate track to the spur that really wants the car. Fixes the 1984 * issue of having cars placed at the alternate when the spur's cars get 1985 * pulled by this train, but cars were sent to the alternate because the 1986 * spur was full at the time it was tested. 1987 * 1988 * @return true if one or more cars were redirected 1989 * @throws BuildFailedException if coding issue 1990 */ 1991 protected boolean redirectCarsFromAlternateTrack() throws BuildFailedException { 1992 // code check, should be aggressive 1993 if (!Setup.isBuildAggressive()) { 1994 throw new BuildFailedException("ERROR coding issue, should be using aggressive mode"); 1995 } 1996 boolean redirected = false; 1997 List<Car> cars = carManager.getByTrainList(_train); 1998 for (Car car : cars) { 1999 // does the car have a final destination and the destination is this 2000 // one? 2001 if (car.getFinalDestination() == null || 2002 car.getFinalDestinationTrack() == null || 2003 !car.getFinalDestinationName().equals(car.getDestinationName())) { 2004 continue; 2005 } 2006 Track alternate = car.getFinalDestinationTrack().getAlternateTrack(); 2007 if (alternate == null || car.getDestinationTrack() != alternate) { 2008 continue; 2009 } 2010 // is the car in a kernel? 2011 if (car.getKernel() != null && !car.isLead()) { 2012 continue; 2013 } 2014 log.debug("Car ({}) alternate track ({}) has final destination track ({}) location ({})", car.toString(), 2015 car.getDestinationTrackName(), car.getFinalDestinationTrackName(), car.getDestinationName()); // NOI18N 2016 if ((alternate.isYard() || alternate.isInterchange()) && 2017 car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack()) 2018 .equals(Track.OKAY) && 2019 checkReserved(_train, car.getRouteDestination(), car, car.getFinalDestinationTrack(), false) 2020 .equals(Track.OKAY) && 2021 checkDropTrainDirection(car, car.getRouteDestination(), car.getFinalDestinationTrack()) && 2022 checkTrainCanDrop(car, car.getFinalDestinationTrack())) { 2023 log.debug("Car ({}) alternate track ({}) can be redirected to final destination track ({})", 2024 car.toString(), car.getDestinationTrackName(), car.getFinalDestinationTrackName()); 2025 if (car.getKernel() != null) { 2026 for (Car k : car.getKernel().getCars()) { 2027 if (k.isLead()) { 2028 continue; 2029 } 2030 addLine(_buildReport, FIVE, 2031 Bundle.getMessage("buildRedirectFromAlternate", car.getFinalDestinationName(), 2032 car.getFinalDestinationTrackName(), k.toString(), 2033 car.getDestinationTrackName())); 2034 // force car to track 2035 k.setDestination(car.getFinalDestination(), car.getFinalDestinationTrack(), Car.FORCE); 2036 } 2037 } 2038 addLine(_buildReport, FIVE, 2039 Bundle.getMessage("buildRedirectFromAlternate", car.getFinalDestinationName(), 2040 car.getFinalDestinationTrackName(), 2041 car.toString(), car.getDestinationTrackName())); 2042 car.setDestination(car.getFinalDestination(), car.getFinalDestinationTrack(), Car.FORCE); 2043 // check for quick service 2044 checkQuickServiceRedirected(car); 2045 redirected = true; 2046 } 2047 } 2048 return redirected; 2049 } 2050 2051 /* 2052 * Checks to see if the redirected car is going to a track with quick 2053 * service. The car in this case has already been assigned to the train. 2054 * This routine will create clones if needed, and allow the car to be 2055 * reassigned to the same train. Only lead car in a kernel is allowed. 2056 */ 2057 private void checkQuickServiceRedirected(Car car) { 2058 if (car.getDestinationTrack().isQuickServiceEnabled()) { 2059 RouteLocation rl = car.getRouteLocation(); 2060 RouteLocation rld = car.getRouteDestination(); 2061 Track track = car.getDestinationTrack(); 2062 // remove cars from train 2063 if (car.getKernel() != null) { 2064 for (Car kar : car.getKernel().getCars()) 2065 kar.reset(); 2066 } else { 2067 car.reset(); 2068 } 2069 _carList.add(0, car); 2070 addCarToTrain(car, rl, rld, track); 2071 } 2072 } 2073 2074 /** 2075 * Checks to see if spur/industry is requesting a quick load service, which 2076 * means that on the outbound side of the turn a car or set of cars in a 2077 * kernel are set out, and on the return side of the turn the same cars are 2078 * pulled. Since it isn't possible for a car to be pulled and set out twice, 2079 * this code creates a second "clone" car to create the requested Manifest. 2080 * A car could have multiple clones, therefore each clone has a creation 2081 * order number. The first clone is used to restore a car's location and 2082 * load in the case of reset. 2083 * <p> 2084 * Also works with an interchange track to make the car immediately 2085 * available to be pulled by the next train being built. 2086 * 2087 * @param car the car possibly needing a quick turn 2088 * @param track the destination track 2089 * @return the car if not a quick turn, or a clone if quick turn 2090 */ 2091 private Car checkQuickServiceArrival(Car car, RouteLocation rld, Track track) { 2092 if (!track.isQuickServiceEnabled()) { 2093 return car; 2094 } 2095 addLine(_buildReport, FIVE, 2096 Bundle.getMessage("buildTrackQuickService", StringUtils.capitalize(track.getTrackTypeName()), 2097 track.getLocation().getName(), track.getName())); 2098 // quick service enabled, create clones 2099 int cloneCreationOrder = carManager.getCloneCreationOrder(); 2100 Car cloneCar = car.copy(); 2101 cloneCar.setNumber(car.getNumber() + Car.CLONE + cloneCreationOrder); 2102 cloneCar.setClone(true); 2103 // register car before setting location so the car gets logged 2104 carManager.register(cloneCar); 2105 cloneCar.setLocation(car.getLocation(), car.getTrack(), RollingStock.FORCE); 2106 // for reset 2107 cloneCar.setPreviousFinalDestination(car.getPreviousFinalDestination()); 2108 cloneCar.setPreviousFinalDestinationTrack(car.getPreviousFinalDestinationTrack()); 2109 cloneCar.setPreviousScheduleId(car.getScheduleItemId()); 2110 cloneCar.setLastRouteId(car.getLastRouteId()); 2111 // for timing, use arrival times for the train that is building 2112 // other trains will use their departure time, loaded when creating the Manifest 2113 String expectedArrivalTime = _train.getExpectedArrivalTime(rld, true); 2114 cloneCar.setSetoutTime(expectedArrivalTime); 2115 if (car.getKernel() != null) { 2116 String kernelName = car.getKernelName() + Car.CLONE + cloneCreationOrder; 2117 Kernel kernel = InstanceManager.getDefault(KernelManager.class).newKernel(kernelName); 2118 cloneCar.setKernel(kernel); 2119 for (Car kar : car.getKernel().getCars()) { 2120 if (kar != car) { 2121 Car nCar = kar.copy(); 2122 nCar.setNumber(kar.getNumber() + Car.CLONE + cloneCreationOrder); 2123 nCar.setClone(true); 2124 nCar.setKernel(kernel); 2125 carManager.register(nCar); 2126 nCar.setLocation(car.getLocation(), car.getTrack(), RollingStock.FORCE); 2127 // for reset 2128 nCar.setPreviousFinalDestination(car.getPreviousFinalDestination()); 2129 nCar.setPreviousFinalDestinationTrack(car.getPreviousFinalDestinationTrack()); 2130 // move car to new location for later pick up 2131 kar.setLocation(track.getLocation(), track, RollingStock.FORCE); 2132 kar.setLastTrain(_train); 2133 kar.setLastLocationId(car.getLocationId()); 2134 kar.setLastTrackId(car.getTrackId()); 2135 kar.setLastDate(_startTime); 2136 kar.setCloneOrder(cloneCreationOrder); // for reset 2137 } 2138 } 2139 } 2140 // move car to new location for later pick up 2141 car.setLocation(track.getLocation(), track, RollingStock.FORCE); 2142 car.setLastTrain(_train); 2143 car.setLastLocationId(cloneCar.getLocationId()); 2144 car.setLastTrackId(cloneCar.getTrackId()); 2145 car.setLastRouteId(_train.getRoute().getId()); 2146 // this car was moved during the build process 2147 car.setLastDate(_startTime); 2148 car.setCloneOrder(cloneCreationOrder); // for reset 2149 car.setDestination(null, null); 2150 track.scheduleNext(car); // apply schedule to car 2151 car.loadNext(track); // update load, wait count 2152 if (car.getWait() > 0) { 2153 _carList.remove(car); // available for next train 2154 addLine(_buildReport, FIVE, Bundle.getMessage("buildExcludeCarWait", car.toString(), 2155 car.getTypeName(), car.getLocationName(), car.getTrackName(), car.getWait())); 2156 car.setWait(car.getWait() - 1); 2157 car.updateLoad(track); 2158 } 2159 // remember where in the route the car was delivered 2160 car.setRouteDestination(rld); 2161 car.updateKernel(); 2162 return cloneCar; // return clone 2163 } 2164 2165 /* 2166 * Checks to see if car is departing a quick service track and is allowed to 2167 * be pulled by this train. Only one pull or move from a location with quick 2168 * service tracks is allowed per route location. To service the car, the 2169 * train must arrive after the car's clone is set out by this train or by 2170 * another train. 2171 */ 2172 private boolean checkQuickServiceDeparting(Car car, RouteLocation rl) { 2173 if (car.getTrack().isQuickServiceEnabled()) { 2174 Car clone = getClone(car); 2175 if (clone != null) { 2176 // was the car delivered using this route location? 2177 if (car.getRouteDestination() == rl) { 2178 addLine(_buildReport, FIVE, 2179 Bundle.getMessage("buildCarRouteLocation", car.toString(), 2180 car.getTrack().getTrackTypeName(), 2181 car.getLocationName(), car.getTrackName(), _train.getName(), rl.getName(), 2182 rl.getId())); 2183 addLine(_buildReport, FIVE, BLANK_LINE); 2184 return false; 2185 } 2186 2187 // determine when the clone is going to be delivered 2188 String trainExpectedArrival = _train.getExpectedArrivalTime(rl, true); 2189 int trainArrivalTimeMinutes = convertStringTime(trainExpectedArrival); 2190 int cloneSetoutTimeMinutes = convertStringTime(clone.getSetoutTime()); 2191 if (cloneSetoutTimeMinutes > trainArrivalTimeMinutes) { 2192 addLine(_buildReport, FIVE, Bundle.getMessage("buildCarDeliveryTiming", car.toString(), 2193 clone.getSetoutTime(), car.getTrack().getTrackTypeName(), car.getLocationName(), 2194 car.getTrackName(), clone.getTrainName(), _train.getName(), trainExpectedArrival)); 2195 addLine(_buildReport, FIVE, BLANK_LINE); 2196 return false; 2197 } 2198 } 2199 } 2200 return true; 2201 } 2202 2203 /* 2204 * Return null if there isn't a clone car. Returns the car's last clone car 2205 * if there's one. 2206 */ 2207 private Car getClone(Car car) { 2208 for (Car kar : carManager.getList()) { 2209 if (kar.isClone() && 2210 kar.getDestinationTrack() == car.getTrack() && 2211 kar.getRoadName().equals(car.getRoadName()) && 2212 kar.getNumber().split(Car.CLONE_REGEX)[0].equals(car.getNumber())) { 2213 return kar; 2214 } 2215 } 2216 return null; // no clone for this car 2217 } 2218 2219 private final static Logger log = LoggerFactory.getLogger(TrainBuilderCars.class); 2220}