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}