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