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