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}