001package jmri.jmrit.operations.rollingstock.engines; 002 003import java.beans.PropertyChangeEvent; 004import java.util.List; 005 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.rollingstock.RollingStock; 013import jmri.jmrit.operations.routes.RouteLocation; 014import jmri.jmrit.operations.trains.Train; 015import jmri.jmrit.roster.Roster; 016import jmri.jmrit.roster.RosterEntry; 017 018/** 019 * Represents a locomotive on the layout 020 * 021 * @author Daniel Boudreau (C) Copyright 2008 022 */ 023public class Engine extends RollingStock { 024 025 public static final int NCE_REAR_BLOCK_NUMBER = 8; 026 public static final int B_UNIT_BLOCKING = 10; // block B Units after NCE Consists 027 public static final String HP_CHANGED_PROPERTY = "hp"; // NOI18N 028 029 private Consist _consist = null; 030 private String _model = NONE; 031 032 EngineModels engineModels = InstanceManager.getDefault(EngineModels.class); 033 034 public Engine() { 035 super(); 036 } 037 038 public Engine(String road, String number) { 039 super(road, number); 040 log.debug("New engine ({} {})", road, number); 041 addPropertyChangeListeners(); 042 } 043 044 public Engine copy() { 045 Engine eng = new Engine(); 046 super.copy(eng); 047 eng.setModel(getModel()); 048 eng.setBunit(isBunit()); 049 return eng; 050 } 051 052 /** 053 * Set the locomotive's model. Note a model has only one length, type, and 054 * horsepower rating. 055 * 056 * @param model The string model name. 057 * 058 */ 059 public void setModel(String model) { 060 String old = _model; 061 _model = model; 062 if (!old.equals(model)) { 063 setDirtyAndFirePropertyChange("engine model", old, model); // NOI18N 064 } 065 } 066 067 public String getModel() { 068 return _model; 069 } 070 071 /** 072 * Set the locomotive type for this locomotive's model 073 * 074 * @param type Locomotive type: Steam, Diesel, Gas Turbine, etc. 075 */ 076 @Override 077 public void setTypeName(String type) { 078 if (getModel() == null || getModel().equals(NONE)) { 079 return; 080 } 081 String old = getTypeName(); 082 engineModels.setModelType(getModel(), type); 083 if (!old.equals(type)) { 084 setDirtyAndFirePropertyChange(TYPE_CHANGED_PROPERTY, old, type); 085 } 086 } 087 088 @Override 089 public String getTypeName() { 090 String type = engineModels.getModelType(getModel()); 091 if (type == null) { 092 type = super.getTypeName(); 093 } 094 return type; 095 } 096 097 /** 098 * Set the locomotive horsepower rating for this locomotive's model 099 * 100 * @param hp locomotive horsepower 101 */ 102 public void setHp(String hp) { 103 if (getModel() == null || getModel().equals(NONE)) { 104 return; 105 } 106 String old = getHp(); 107 engineModels.setModelHorsepower(getModel(), hp); 108 if (!old.equals(hp)) { 109 setDirtyAndFirePropertyChange(HP_CHANGED_PROPERTY, old, hp); // NOI18N 110 } 111 } 112 113 public String getHp() { 114 String hp = engineModels.getModelHorsepower(getModel()); 115 if (hp == null) { 116 hp = NONE; 117 } 118 return hp; 119 } 120 121 public int getHpInteger() { 122 try { 123 return Integer.parseInt(getHp()); 124 } catch (NumberFormatException e) { 125 log.debug("Locomotive ({}) horsepower ({}) isn't a number", toString(), getHp()); 126 return 0; 127 } 128 } 129 130 /** 131 * Set the locomotive length for this locomotive's model 132 * 133 * @param length locomotive length 134 */ 135 @Override 136 public void setLength(String length) { 137 super.setLength(length); 138 if (getModel() == null || getModel().equals(NONE)) { 139 return; 140 } 141 engineModels.setModelLength(getModel(), length); 142 } 143 144 @Override 145 public String getLength() { 146 String length = super.getLength(); 147 if (getModel() != null && !getModel().equals(NONE)) { 148 length = engineModels.getModelLength(getModel()); 149 } 150 if (length == null) { 151 length = NONE; 152 } 153 if (!length.equals(_length)) { 154 // return "old" length, used for track reserve changes 155 if (_lengthChange) { 156 return _length; 157 } 158 log.debug("Loco ({}) length ({}) has been modified from ({})", toString(), length, _length); 159 super.setLength(length); // adjust track lengths 160 } 161 return length; 162 } 163 164 /** 165 * Set the locomotive weight for this locomotive's model 166 * 167 * @param weight locomotive weight 168 */ 169 @Override 170 public void setWeightTons(String weight) { 171 if (getModel() == null || getModel().equals(NONE)) { 172 return; 173 } 174 String old = getWeightTons(); 175 super.setWeightTons(weight); 176 engineModels.setModelWeight(getModel(), weight); 177 if (!old.equals(weight)) { 178 setDirtyAndFirePropertyChange("Engine Weight Tons", old, weight); // NOI18N 179 } 180 } 181 182 @Override 183 public String getWeightTons() { 184 String weight = null; 185 weight = engineModels.getModelWeight(getModel()); 186 if (weight == null) { 187 weight = NONE; 188 } 189 return weight; 190 } 191 192 public void setBunit(boolean bUnit) { 193 if (getModel() == null || getModel().equals(NONE)) { 194 return; 195 } 196 boolean old = isBunit(); 197 engineModels.setModelBunit(getModel(), bUnit); 198 if (old != bUnit) { 199 setDirtyAndFirePropertyChange(TYPE_CHANGED_PROPERTY, old, bUnit); 200 } 201 } 202 203 public boolean isBunit() { 204 return engineModels.isModelBunit(getModel()); 205 } 206 207 /** 208 * Place locomotive in a consist 209 * 210 * @param consist The Consist to use. 211 * 212 */ 213 public void setConsist(Consist consist) { 214 if (_consist == consist) { 215 return; 216 } 217 String old = ""; 218 if (_consist != null) { 219 old = _consist.getName(); 220 _consist.delete(this); 221 } 222 _consist = consist; 223 String newName = ""; 224 if (_consist != null) { 225 _consist.add(this); 226 newName = _consist.getName(); 227 } 228 229 if (!old.equals(newName)) { 230 setDirtyAndFirePropertyChange("consist", old, newName); // NOI18N 231 } 232 } 233 234 /** 235 * Get the consist for this locomotive 236 * 237 * @return null if locomotive isn't in a consist 238 */ 239 public Consist getConsist() { 240 return _consist; 241 } 242 243 public String getConsistName() { 244 if (_consist != null) { 245 return _consist.getName(); 246 } 247 return NONE; 248 } 249 250 /** 251 * B units that aren't part of a consist are blocked at the end. 252 */ 253 @Override 254 public int getBlocking() { 255 if (isBunit() && getConsist() == null) { 256 return B_UNIT_BLOCKING; 257 } 258 return super.getBlocking(); 259 } 260 261 /** 262 * Used to determine if engine is lead engine in a consist 263 * 264 * @return true if lead engine in a consist 265 */ 266 public boolean isLead() { 267 if (getConsist() != null) { 268 return getConsist().isLead(this); 269 } 270 return false; 271 } 272 273 /** 274 * Get the DCC address for this engine from the JMRI roster. Does 4 275 * attempts, 1st by road and number, 2nd by number, 3rd by dccAddress using 276 * the engine's road number, 4th by id. 277 * 278 * @return dccAddress 279 */ 280 public String getDccAddress() { 281 RosterEntry re = getRosterEntry(); 282 if (re != null) { 283 return re.getDccAddress(); 284 } 285 return NONE; 286 } 287 288 /** 289 * Get the RosterEntry for this engine from the JMRI roster. Does 4 290 * attempts, 1st by road and number, 2nd by number, 3rd by dccAddress using 291 * the engine's road number, 4th by id. 292 * 293 * @return RosterEntry, can be null 294 */ 295 public RosterEntry getRosterEntry() { 296 RosterEntry rosterEntry = null; 297 // 1st by road name and number 298 List<RosterEntry> list = 299 Roster.getDefault().matchingList(getRoadName(), getNumber(), null, null, null, null, null); 300 if (list.size() > 0) { 301 rosterEntry = list.get(0); 302 log.debug("Roster Loco found by road and number: {}", rosterEntry.getDccAddress()); 303 // 2nd by road number 304 } else if (!getNumber().equals(NONE)) { 305 list = Roster.getDefault().matchingList(null, getNumber(), null, null, null, null, null); 306 if (list.size() > 0) { 307 rosterEntry = list.get(0); 308 log.debug("Roster Loco found by number: {}", rosterEntry.getDccAddress()); 309 } 310 } 311 // 3rd by dcc address 312 if (rosterEntry == null) { 313 list = Roster.getDefault().matchingList(null, null, getNumber(), null, null, null, null); 314 if (list.size() > 0) { 315 rosterEntry = list.get(0); 316 log.debug("Roster Loco found by dccAddress: {}", rosterEntry.getDccAddress()); 317 } 318 } 319 // 4th by id 320 if (rosterEntry == null) { 321 list = Roster.getDefault().matchingList(null, null, null, null, null, null, getNumber()); 322 if (list.size() > 0) { 323 rosterEntry = list.get(0); 324 log.debug("Roster Loco found by roster id: {}", rosterEntry.getDccAddress()); 325 } 326 } 327 return rosterEntry; 328 } 329 330 /** 331 * Used to check destination track to see if it will accept locomotive 332 * 333 * @return status, see RollingStock.java 334 */ 335 @Override 336 public String checkDestination(Location destination, Track track) { 337 return super.checkDestination(destination, track); 338 } 339 340 @Override 341 public String setDestination(Location destination, Track track, boolean force) { 342 String destinationName = getDestinationName(); 343 String status = super.setDestination(destination, track, force); 344 // return if not Okay 345 if (!status.equals(Track.OKAY)) { 346 return status; 347 } 348 if (destinationName.equals(NONE) || (destination != null) || getTrain() == null) { 349 return status; 350 } 351 // engine clone was in a train and has been dropped off 352 if (isClone()) { 353 // destroy clone 354 InstanceManager.getDefault(ConsistManager.class).deleteConsist(getConsistName()); 355 InstanceManager.getDefault(EngineManager.class).deregister(this); 356 } 357 return status; 358 } 359 360 /** 361 * Determine if there's a change in the lead locomotive. There are two 362 * possible locations in a train's route. TODO this code places the last 363 * loco added to the train as the lead. It would be better if the first one 364 * became the lead loco. 365 */ 366 @Override 367 protected void moveRollingStock(RouteLocation current, RouteLocation next) { 368 if (current == getRouteLocation()) { 369 if (getConsist() == null || isLead()) { 370 if (getRouteLocation() != getRouteDestination() && 371 getTrain() != null && 372 !isBunit() && 373 getTrain().getLeadEngine() != this) { 374 if (((getTrain().getSecondLegStartRouteLocation() == current && 375 (getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES)) || 376 ((getTrain().getThirdLegStartRouteLocation() == current && 377 (getTrain().getThirdLegOptions() & 378 Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES))) { 379 log.debug("New lead locomotive ({}) for train ({})", toString(), getTrain().getName()); 380 getTrain().setLeadEngine(this); 381 getTrain().createTrainIcon(current); 382 } 383 } 384 } 385 } 386 super.moveRollingStock(current, next); 387 } 388 389 @Override 390 public void reset() { 391 super.reset(); 392 destroyClone(); 393 } 394 395 /* 396 * This routine destroys the clone and restores the cloned car to its 397 * original location and load. Note there can be multiple clones for a car. 398 * Only the first clone created has the right info. A clone has creation 399 * order number appended to the road number. 400 */ 401 private void destroyClone() { 402 if (isClone()) { 403 // move cloned engine back to original location 404 EngineManager engineManager = InstanceManager.getDefault(EngineManager.class); 405 String[] number = getNumber().split(Engine.CLONE_REGEX); 406 Engine engine = engineManager.getByRoadAndNumber(getRoadName(), number[0]); 407 int cloneCreationNumber = Integer.parseInt(number[1]); 408 if (cloneCreationNumber <= engine.getCloneOrder()) { 409 engine.setLocation(getLocation(), getTrack(), Engine.FORCE); 410 engine.setRouteDestination(null); // clear rd 411 engine.setLastTrain(getLastTrain()); 412 engine.setLastRouteId(getLastRouteId()); 413 engine.setLastDate(getLastDate()); 414 engine.setMoves(getMoves()); 415 // remember the last clone destroyed 416 engine.setCloneOrder(cloneCreationNumber); 417 } 418 InstanceManager.getDefault(ConsistManager.class).deleteConsist(getConsistName()); 419 engineManager.deregister(this); 420 } 421 } 422 423 @Override 424 public void dispose() { 425 setConsist(null); 426 InstanceManager.getDefault(EngineTypes.class).removePropertyChangeListener(this); 427 InstanceManager.getDefault(EngineLengths.class).removePropertyChangeListener(this); 428 super.dispose(); 429 } 430 431 /** 432 * Construct this Entry from XML. This member has to remain synchronized 433 * with the detailed DTD in operations-engines.dtd 434 * 435 * @param e Engine XML element 436 */ 437 public Engine(org.jdom2.Element e) { 438 super(e); // MUST create the rolling stock first! 439 org.jdom2.Attribute a; 440 // must set _model first so locomotive hp, length, type and weight is set properly 441 if ((a = e.getAttribute(Xml.MODEL)) != null) { 442 _model = a.getValue(); 443 } 444 if ((a = e.getAttribute(Xml.HP)) != null) { 445 setHp(a.getValue()); 446 } 447 if ((a = e.getAttribute(Xml.LENGTH)) != null) { 448 setLength(a.getValue()); 449 } 450 if ((a = e.getAttribute(Xml.TYPE)) != null) { 451 setTypeName(a.getValue()); 452 } 453 if ((a = e.getAttribute(Xml.WEIGHT_TONS)) != null) { 454 setWeightTons(a.getValue()); 455 } 456 if ((a = e.getAttribute(Xml.B_UNIT)) != null) { 457 setBunit(a.getValue().equals(Xml.TRUE)); 458 } 459 if ((a = e.getAttribute(Xml.CONSIST)) != null) { 460 Consist c = InstanceManager.getDefault(ConsistManager.class).getConsistByName(a.getValue()); 461 if (c != null) { 462 setConsist(c); 463 if ((a = e.getAttribute(Xml.LEAD_CONSIST)) != null && a.getValue().equals(Xml.TRUE)) { 464 _consist.setLead(this); 465 } 466 if ((a = e.getAttribute(Xml.CONSIST_NUM)) != null) { 467 _consist.setConsistNumber(Integer.parseInt(a.getValue())); 468 } 469 } else { 470 log.error("Consist {} does not exist", a.getValue()); 471 } 472 } 473 addPropertyChangeListeners(); 474 } 475 476 boolean verboseStore = false; 477 478 /** 479 * Create an XML element to represent this Entry. This member has to remain 480 * synchronized with the detailed DTD in operations-engines.dtd. 481 * 482 * @return Contents in a JDOM Element 483 */ 484 public org.jdom2.Element store() { 485 org.jdom2.Element e = new org.jdom2.Element(Xml.ENGINE); 486 super.store(e); 487 e.setAttribute(Xml.MODEL, getModel()); 488 e.setAttribute(Xml.HP, getHp()); 489 e.setAttribute(Xml.B_UNIT, (isBunit() ? Xml.TRUE : Xml.FALSE)); 490 if (getConsist() != null) { 491 e.setAttribute(Xml.CONSIST, getConsistName()); 492 if (isLead()) { 493 e.setAttribute(Xml.LEAD_CONSIST, Xml.TRUE); 494 if (getConsist().getConsistNumber() > 0) { 495 e.setAttribute(Xml.CONSIST_NUM, 496 Integer.toString(getConsist().getConsistNumber())); 497 } 498 } 499 } 500 return e; 501 } 502 503 @Override 504 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 505 // Set dirty 506 InstanceManager.getDefault(EngineManagerXml.class).setDirty(true); 507 super.setDirtyAndFirePropertyChange(p, old, n); 508 } 509 510 private void addPropertyChangeListeners() { 511 InstanceManager.getDefault(EngineTypes.class).addPropertyChangeListener(this); 512 InstanceManager.getDefault(EngineLengths.class).addPropertyChangeListener(this); 513 } 514 515 @Override 516 public void propertyChange(PropertyChangeEvent e) { 517 super.propertyChange(e); 518 if (e.getPropertyName().equals(EngineTypes.ENGINETYPES_NAME_CHANGED_PROPERTY)) { 519 if (e.getOldValue().equals(getTypeName())) { 520 log.debug("Loco ({}) sees type name change old: ({}) new: ({})", toString(), 521 e.getOldValue(), e.getNewValue()); // NOI18N 522 setTypeName((String) e.getNewValue()); 523 } 524 } 525 if (e.getPropertyName().equals(EngineLengths.ENGINELENGTHS_NAME_CHANGED_PROPERTY)) { 526 if (e.getOldValue().equals(getLength())) { 527 log.debug("Loco ({}) sees length name change old: {} new: {}", toString(), e.getOldValue(), e 528 .getNewValue()); // NOI18N 529 setLength((String) e.getNewValue()); 530 } 531 } 532 } 533 534 private final static Logger log = LoggerFactory.getLogger(Engine.class); 535 536}