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