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}