001package jmri.jmrit.operations.trains.trainbuilder; 002 003import org.slf4j.Logger; 004import org.slf4j.LoggerFactory; 005 006import jmri.jmrit.operations.locations.Track; 007import jmri.jmrit.operations.rollingstock.engines.Engine; 008import jmri.jmrit.operations.routes.Route; 009import jmri.jmrit.operations.routes.RouteLocation; 010import jmri.jmrit.operations.setup.Control; 011import jmri.jmrit.operations.setup.Setup; 012import jmri.jmrit.operations.trains.*; 013 014/** 015 * Contains methods for engines when building a train. 016 * 017 * @author Daniel Boudreau Copyright (C) 2022 018 */ 019public class TrainBuilderEngines extends TrainBuilderBase { 020 021 /** 022 * Builds a list of possible engines for this train. 023 */ 024 protected void getAndRemoveEnginesFromList() { 025 _engineList = engineManager.getAvailableTrainList(_train); 026 027 // remove any locos that the train can't use 028 for (int indexEng = 0; indexEng < _engineList.size(); indexEng++) { 029 Engine engine = _engineList.get(indexEng); 030 // remove engines types that train does not service 031 if (!_train.isTypeNameAccepted(engine.getTypeName())) { 032 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeEngineType", engine.toString(), 033 engine.getLocationName(), engine.getTrackName(), engine.getTypeName())); 034 _engineList.remove(indexEng--); 035 continue; 036 } 037 // remove engines with roads that train does not service 038 if (!_train.isLocoRoadNameAccepted(engine.getRoadName())) { 039 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeEngineRoad", engine.toString(), 040 engine.getLocationName(), engine.getTrackName(), engine.getRoadName())); 041 _engineList.remove(indexEng--); 042 continue; 043 } 044 // remove engines with owners that train does not service 045 if (!_train.isOwnerNameAccepted(engine.getOwnerName())) { 046 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeEngineOwner", engine.toString(), 047 engine.getLocationName(), engine.getTrackName(), engine.getOwnerName())); 048 _engineList.remove(indexEng--); 049 continue; 050 } 051 // remove engines with built dates that train does not service 052 if (!_train.isBuiltDateAccepted(engine.getBuilt())) { 053 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeEngineBuilt", engine.toString(), 054 engine.getLocationName(), engine.getTrackName(), engine.getBuilt())); 055 _engineList.remove(indexEng--); 056 continue; 057 } 058 // remove engines that are out of service 059 if (engine.isOutOfService()) { 060 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeEngineOutOfService", engine.toString(), 061 engine.getLocationName(), engine.getTrackName())); 062 _engineList.remove(indexEng--); 063 continue; 064 } 065 // remove engines that aren't on the train's route 066 if (_train.getRoute().getLastLocationByName(engine.getLocationName()) == null) { 067 log.debug("removing engine ({}) location ({}) not serviced by train", engine.toString(), 068 engine.getLocationName()); 069 _engineList.remove(indexEng--); 070 continue; 071 } 072 // is engine at interchange? 073 if (engine.getTrack().isInterchange()) { 074 // don't service a engine at interchange and has been dropped off 075 // by this train 076 if (engine.getTrack().getPickupOption().equals(Track.ANY) && 077 engine.getLastRouteId().equals(_train.getRoute().getId())) { 078 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeEngineDropByTrain", engine.toString(), 079 engine.getTypeName(), _train.getRoute().getName(), engine.getLocationName(), engine.getTrackName())); 080 _engineList.remove(indexEng--); 081 continue; 082 } 083 } 084 // is engine at interchange or spur and is this train allowed to pull? 085 if (engine.getTrack().isInterchange() || engine.getTrack().isSpur()) { 086 if (engine.getTrack().getPickupOption().equals(Track.TRAINS) || 087 engine.getTrack().getPickupOption().equals(Track.EXCLUDE_TRAINS)) { 088 if (engine.getTrack().isPickupTrainAccepted(_train)) { 089 log.debug("Engine ({}) can be picked up by this train", engine.toString()); 090 } else { 091 addLine(_buildReport, SEVEN, 092 Bundle.getMessage("buildExcludeEngineByTrain", engine.toString(), engine.getTypeName(), 093 engine.getTrack().getTrackTypeName(), engine.getLocationName(), engine.getTrackName())); 094 _engineList.remove(indexEng--); 095 continue; 096 } 097 } else if (engine.getTrack().getPickupOption().equals(Track.ROUTES) || 098 engine.getTrack().getPickupOption().equals(Track.EXCLUDE_ROUTES)) { 099 if (engine.getTrack().isPickupRouteAccepted(_train.getRoute())) { 100 log.debug("Engine ({}) can be picked up by this route", engine.toString()); 101 } else { 102 addLine(_buildReport, SEVEN, 103 Bundle.getMessage("buildExcludeEngineByRoute", engine.toString(), engine.getTypeName(), 104 engine.getTrack().getTrackTypeName(), engine.getLocationName(), engine.getTrackName())); 105 _engineList.remove(indexEng--); 106 continue; 107 } 108 } 109 } 110 } 111 } 112 113 /** 114 * Adds engines to the train starting at the first location in the train's 115 * route. Note that engines from staging are already part of the train. 116 * There can be up to two engine swaps in a train's route. 117 * 118 * @throws BuildFailedException if required engines can't be added to train. 119 */ 120 protected void addEnginesToTrain() throws BuildFailedException { 121 // allow up to two engine and caboose swaps in the train's route 122 RouteLocation engineTerminatesFirstLeg = _train.getTrainTerminatesRouteLocation(); 123 RouteLocation engineTerminatesSecondLeg = _train.getTrainTerminatesRouteLocation(); 124 125 // Adjust where the locos will terminate 126 if ((_train.getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 127 _train.getSecondLegStartRouteLocation() != null) { 128 engineTerminatesFirstLeg = _train.getSecondLegStartRouteLocation(); 129 } 130 if ((_train.getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 131 _train.getThirdLegStartRouteLocation() != null) { 132 engineTerminatesSecondLeg = _train.getThirdLegStartRouteLocation(); 133 // No engine or caboose change at first leg? 134 if ((_train.getSecondLegOptions() & Train.CHANGE_ENGINES) != Train.CHANGE_ENGINES) { 135 engineTerminatesFirstLeg = _train.getThirdLegStartRouteLocation(); 136 } 137 } 138 139 if (_train.getLeadEngine() == null) { 140 // option to remove locos from the train 141 if ((_train.getSecondLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES && 142 _train.getSecondLegStartRouteLocation() != null) { 143 addLine(_buildReport, THREE, BLANK_LINE); 144 addLine(_buildReport, THREE, 145 Bundle.getMessage("buildTrainRemoveEngines", _train.getSecondLegNumberEngines(), 146 _train.getSecondLegStartLocationName(), _train.getSecondLegEngineModel(), 147 _train.getSecondLegEngineRoad())); 148 if (getEngines(_train.getSecondLegNumberEngines(), _train.getSecondLegEngineModel(), 149 _train.getSecondLegEngineRoad(), _train.getTrainDepartsRouteLocation(), 150 _train.getSecondLegStartRouteLocation())) { 151 } else if (getConsist(_train.getSecondLegNumberEngines(), _train.getSecondLegEngineModel(), 152 _train.getSecondLegEngineRoad(), _train.getTrainDepartsRouteLocation(), 153 _train.getSecondLegStartRouteLocation())) { 154 } else { 155 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", 156 _train.getSecondLegNumberEngines(), _train.getTrainDepartsName(), 157 _train.getSecondLegStartRouteLocation().getLocation().getName())); 158 } 159 } 160 if ((_train.getThirdLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES && 161 _train.getThirdLegStartRouteLocation() != null) { 162 addLine(_buildReport, THREE, BLANK_LINE); 163 addLine(_buildReport, THREE, 164 Bundle.getMessage("buildTrainRemoveEngines", _train.getThirdLegNumberEngines(), 165 _train.getThirdLegStartLocationName(), _train.getThirdLegEngineModel(), 166 _train.getThirdLegEngineRoad())); 167 if (getEngines(_train.getThirdLegNumberEngines(), _train.getThirdLegEngineModel(), 168 _train.getThirdLegEngineRoad(), _train.getTrainDepartsRouteLocation(), 169 _train.getThirdLegStartRouteLocation())) { 170 } else if (getConsist(_train.getThirdLegNumberEngines(), _train.getThirdLegEngineModel(), 171 _train.getThirdLegEngineRoad(), _train.getTrainDepartsRouteLocation(), 172 _train.getThirdLegStartRouteLocation())) { 173 } else { 174 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", 175 _train.getThirdLegNumberEngines(), _train.getTrainDepartsName(), 176 _train.getThirdLegStartRouteLocation().getLocation().getName())); 177 } 178 } 179 // load engines at the start of the route for this train 180 addLine(_buildReport, THREE, BLANK_LINE); 181 if (getEngines(_train.getNumberEngines(), _train.getEngineModel(), _train.getEngineRoad(), 182 _train.getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) { 183 // when adding a caboose later in the route, no engine change 184 _secondLeadEngine = _lastEngine; 185 _thirdLeadEngine = _lastEngine; 186 } else if (getConsist(_train.getNumberEngines(), _train.getEngineModel(), _train.getEngineRoad(), 187 _train.getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) { 188 // when adding a caboose later in the route, no engine change 189 _secondLeadEngine = _lastEngine; 190 _thirdLeadEngine = _lastEngine; 191 } else { 192 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", _train.getNumberEngines(), 193 _train.getTrainDepartsName(), engineTerminatesFirstLeg.getName())); 194 } 195 } 196 197 // First engine change in route? 198 if ((_train.getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES || 199 (_train.getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES) { 200 addLine(_buildReport, THREE, BLANK_LINE); 201 if ((_train.getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 202 addLine(_buildReport, THREE, 203 Bundle.getMessage("buildTrainEngineChange", _train.getSecondLegStartLocationName(), 204 _train.getSecondLegNumberEngines(), _train.getSecondLegEngineModel(), 205 _train.getSecondLegEngineRoad())); 206 } else { 207 addLine(_buildReport, THREE, 208 Bundle.getMessage("buildTrainAddEngines", _train.getSecondLegNumberEngines(), 209 _train.getSecondLegStartLocationName(), _train.getSecondLegEngineModel(), 210 _train.getSecondLegEngineRoad())); 211 } 212 if (getEngines(_train.getSecondLegNumberEngines(), _train.getSecondLegEngineModel(), 213 _train.getSecondLegEngineRoad(), _train.getSecondLegStartRouteLocation(), 214 engineTerminatesSecondLeg)) { 215 _secondLeadEngine = _lastEngine; 216 _thirdLeadEngine = _lastEngine; 217 } else if (getConsist(_train.getSecondLegNumberEngines(), _train.getSecondLegEngineModel(), 218 _train.getSecondLegEngineRoad(), _train.getSecondLegStartRouteLocation(), 219 engineTerminatesSecondLeg)) { 220 _secondLeadEngine = _lastEngine; 221 _thirdLeadEngine = _lastEngine; 222 } else { 223 throw new BuildFailedException( 224 Bundle.getMessage("buildErrorEngines", _train.getSecondLegNumberEngines(), 225 _train.getSecondLegStartRouteLocation(), engineTerminatesSecondLeg)); 226 } 227 } 228 // Second engine change in route? 229 if ((_train.getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES || 230 (_train.getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES) { 231 addLine(_buildReport, THREE, BLANK_LINE); 232 if ((_train.getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 233 addLine(_buildReport, THREE, 234 Bundle.getMessage("buildTrainEngineChange", _train.getThirdLegStartLocationName(), 235 _train.getThirdLegNumberEngines(), _train.getThirdLegEngineModel(), 236 _train.getThirdLegEngineRoad())); 237 } else { 238 addLine(_buildReport, THREE, 239 Bundle.getMessage("buildTrainAddEngines", _train.getThirdLegNumberEngines(), 240 _train.getThirdLegStartLocationName(), _train.getThirdLegEngineModel(), 241 _train.getThirdLegEngineRoad())); 242 } 243 if (getEngines(_train.getThirdLegNumberEngines(), _train.getThirdLegEngineModel(), 244 _train.getThirdLegEngineRoad(), _train.getThirdLegStartRouteLocation(), 245 _train.getTrainTerminatesRouteLocation())) { 246 _thirdLeadEngine = _lastEngine; 247 } else if (getConsist(_train.getThirdLegNumberEngines(), _train.getThirdLegEngineModel(), 248 _train.getThirdLegEngineRoad(), _train.getThirdLegStartRouteLocation(), 249 _train.getTrainTerminatesRouteLocation())) { 250 _thirdLeadEngine = _lastEngine; 251 } else { 252 throw new BuildFailedException( 253 Bundle.getMessage("buildErrorEngines", Integer.parseInt(_train.getThirdLegNumberEngines()), 254 _train.getThirdLegStartRouteLocation(), _train.getTrainTerminatesRouteLocation())); 255 } 256 } 257 if (!_train.getNumberEngines().equals("0") && 258 (!_train.isBuildConsistEnabled() || Setup.getHorsePowerPerTon() == 0)) { 259 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", _train.getName())); 260 } 261 } 262 263 /** 264 * Checks to see if the engine or consist assigned to the train has the 265 * appropriate HP. If the train's HP requirements are significantly higher 266 * or lower than the engine that was assigned, the program will search for a 267 * more appropriate engine or consist, and assign that engine or consist to 268 * the train. The HP calculation is based on a minimum train speed of 36 269 * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the 270 * horsepower required. Speed is fixed at 36 MPH. For example a 1% grade 271 * requires a minimum of 3 HPT. Disabled for trains departing staging. 272 * 273 * @throws BuildFailedException if coding error. 274 */ 275 protected void checkEngineHP() throws BuildFailedException { 276 if (Setup.getHorsePowerPerTon() != 0) { 277 if (_train.getNumberEngines().equals(Train.AUTO_HPT)) { 278 checkEngineHP(_train.getLeadEngine(), _train.getEngineModel(), _train.getEngineRoad()); // 1st 279 // leg 280 } 281 if ((_train.getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 282 _train.getSecondLegNumberEngines().equals(Train.AUTO_HPT)) { 283 checkEngineHP(_secondLeadEngine, _train.getSecondLegEngineModel(), _train.getSecondLegEngineRoad()); 284 } 285 if ((_train.getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 286 _train.getThirdLegNumberEngines().equals(Train.AUTO_HPT)) { 287 checkEngineHP(_thirdLeadEngine, _train.getThirdLegEngineModel(), _train.getThirdLegEngineRoad()); 288 } 289 } 290 } 291 292 private void checkEngineHP(Engine leadEngine, String model, String road) throws BuildFailedException { 293 // code check 294 if (leadEngine == null) { 295 throw new BuildFailedException("ERROR coding issue, engine missing from checkEngineHP()"); 296 } 297 // departing staging? 298 if (leadEngine.getRouteLocation() == _train.getTrainDepartsRouteLocation() && _train.isDepartingStaging()) { 299 return; 300 } 301 addLine(_buildReport, ONE, BLANK_LINE); 302 addLine(_buildReport, ONE, 303 Bundle.getMessage("buildDetermineHpNeeded", leadEngine.toString(), leadEngine.getLocationName(), 304 leadEngine.getDestinationName(), _train.getTrainHorsePower(leadEngine.getRouteLocation()), 305 Setup.getHorsePowerPerTon())); 306 // now determine the HP needed for this train 307 double hpNeeded = 0; 308 int hpAvailable = 0; 309 Route route = _train.getRoute(); 310 if (route != null) { 311 boolean helper = false; 312 boolean foundStart = false; 313 for (RouteLocation rl : route.getLocationsBySequenceList()) { 314 if (!foundStart && rl != leadEngine.getRouteLocation()) { 315 continue; 316 } 317 foundStart = true; 318 if ((_train.getSecondLegOptions() == Train.HELPER_ENGINES && 319 rl == _train.getSecondLegStartRouteLocation()) || 320 (_train.getThirdLegOptions() == Train.HELPER_ENGINES && 321 rl == _train.getThirdLegStartRouteLocation())) { 322 addLine(_buildReport, FIVE, 323 Bundle.getMessage("AddHelpersAt", rl.getName())); 324 helper = true; 325 } 326 if ((_train.getSecondLegOptions() == Train.HELPER_ENGINES && 327 rl == _train.getSecondLegEndRouteLocation()) || 328 (_train.getThirdLegOptions() == Train.HELPER_ENGINES && 329 rl == _train.getThirdLegEndRouteLocation())) { 330 addLine(_buildReport, FIVE, 331 Bundle.getMessage("RemoveHelpersAt", rl.getName())); 332 helper = false; 333 } 334 if (helper) { 335 continue; // ignore HP needed when helpers are assigned to 336 // the train 337 } 338 // check for a change of engines in the train's route 339 if (rl == leadEngine.getRouteDestination()) { 340 log.debug("Remove loco ({}) at ({})", leadEngine.toString(), rl.getName()); 341 break; // done 342 } 343 if (_train.getTrainHorsePower(rl) > hpAvailable) 344 hpAvailable = _train.getTrainHorsePower(rl); 345 int weight = rl.getTrainWeight(); 346 double hpRequired = (Control.speedHpt * rl.getGrade() / 12) * weight; 347 if (hpRequired < Setup.getHorsePowerPerTon() * weight) 348 hpRequired = Setup.getHorsePowerPerTon() * weight; // min HPT 349 if (hpRequired > hpNeeded) { 350 addLine(_buildReport, SEVEN, 351 Bundle.getMessage("buildReportTrainHpNeeds", weight, _train.getNumberCarsInTrain(rl), 352 rl.getGrade(), rl.getName(), rl.getId(), hpRequired)); 353 hpNeeded = hpRequired; 354 } 355 } 356 } 357 if (hpNeeded > hpAvailable) { 358 addLine(_buildReport, ONE, 359 Bundle.getMessage("buildAssignedHpNotEnough", leadEngine.toString(), hpAvailable, hpNeeded)); 360 getNewEngine((int) hpNeeded, leadEngine, model, road); 361 } else if (hpAvailable > 2 * hpNeeded) { 362 addLine(_buildReport, ONE, 363 Bundle.getMessage("buildAssignedHpTooMuch", leadEngine.toString(), hpAvailable, hpNeeded)); 364 getNewEngine((int) hpNeeded, leadEngine, model, road); 365 } else { 366 log.debug("Keeping engine ({}) it meets the train's HP requirement", leadEngine.toString()); 367 } 368 } 369 370 /** 371 * Checks to see if additional engines are needed for the train based on the 372 * train's calculated tonnage. Minimum speed for the train is fixed at 36 373 * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the 374 * horsepower needed. For example a 1% grade requires a minimum of 3 HPT. 375 * 376 * Ignored when departing staging 377 * 378 * @throws BuildFailedException if build failure 379 */ 380 protected void checkNumnberOfEnginesNeededHPT() throws BuildFailedException { 381 if (!_train.isBuildConsistEnabled() || 382 Setup.getHorsePowerPerTon() == 0) { 383 return; 384 } 385 addLine(_buildReport, ONE, BLANK_LINE); 386 addLine(_buildReport, ONE, Bundle.getMessage("buildDetermineNeeds", Setup.getHorsePowerPerTon())); 387 Route route = _train.getRoute(); 388 int hpAvailable = 0; 389 int extraHpNeeded = 0; 390 RouteLocation rlNeedHp = null; 391 RouteLocation rlStart = _train.getTrainDepartsRouteLocation(); 392 RouteLocation rlEnd = _train.getTrainTerminatesRouteLocation(); 393 boolean departingStaging = _train.isDepartingStaging(); 394 if (route != null) { 395 boolean helper = false; 396 for (RouteLocation rl : route.getLocationsBySequenceList()) { 397 if ((_train.getSecondLegOptions() == Train.HELPER_ENGINES && 398 rl == _train.getSecondLegStartRouteLocation()) || 399 (_train.getThirdLegOptions() == Train.HELPER_ENGINES && 400 rl == _train.getThirdLegStartRouteLocation())) { 401 addLine(_buildReport, FIVE, Bundle.getMessage("AddHelpersAt", rl.getName())); 402 helper = true; 403 } 404 if ((_train.getSecondLegOptions() == Train.HELPER_ENGINES && 405 rl == _train.getSecondLegEndRouteLocation()) || 406 (_train.getThirdLegOptions() == Train.HELPER_ENGINES && 407 rl == _train.getThirdLegEndRouteLocation())) { 408 addLine(_buildReport, FIVE, 409 Bundle.getMessage("RemoveHelpersAt", rl.getName())); 410 helper = false; 411 } 412 if (helper) { 413 continue; 414 } 415 // check for a change of engines in the train's route 416 if (((_train.getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 417 rl == _train.getSecondLegStartRouteLocation()) || 418 ((_train.getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 419 rl == _train.getThirdLegStartRouteLocation())) { 420 log.debug("Loco change at ({})", rl.getName()); 421 addEnginesBasedHPT(hpAvailable, extraHpNeeded, rlNeedHp, rlStart, rl); 422 addLine(_buildReport, THREE, BLANK_LINE); 423 // reset for next leg of train's route 424 rlStart = rl; 425 rlNeedHp = null; 426 extraHpNeeded = 0; 427 departingStaging = false; 428 } 429 if (departingStaging) { 430 continue; 431 } 432 double weight = rl.getTrainWeight(); 433 if (weight > 0) { 434 double hptMinimum = Setup.getHorsePowerPerTon(); 435 double hptGrade = (Control.speedHpt * rl.getGrade() / 12); 436 double hp = _train.getTrainHorsePower(rl); 437 double hpt = hp / weight; 438 if (hptGrade > hptMinimum) { 439 hptMinimum = hptGrade; 440 } 441 if (hptMinimum > hpt) { 442 int addHp = (int) (hptMinimum * weight - hp); 443 if (addHp > extraHpNeeded) { 444 hpAvailable = (int) hp; 445 extraHpNeeded = addHp; 446 rlNeedHp = rl; 447 } 448 addLine(_buildReport, SEVEN, 449 Bundle.getMessage("buildAddLocosStatus", weight, hp, Control.speedHpt, rl.getGrade(), 450 hpt, hptMinimum, rl.getName(), rl.getId())); 451 addLine(_buildReport, FIVE, 452 Bundle.getMessage("buildTrainRequiresAddHp", addHp, rl.getName(), hptMinimum)); 453 } 454 } 455 } 456 } 457 addEnginesBasedHPT(hpAvailable, extraHpNeeded, rlNeedHp, rlStart, rlEnd); 458 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", _train.getName())); 459 addLine(_buildReport, THREE, BLANK_LINE); 460 } 461 462 private final static Logger log = LoggerFactory.getLogger(TrainBuilderEngines.class); 463}