001package jmri.jmrit.operations.locations;
002
003import java.awt.Point;
004import java.beans.PropertyChangeListener;
005import java.text.MessageFormat;
006import java.util.ArrayList;
007import java.util.Enumeration;
008import java.util.Hashtable;
009import java.util.List;
010
011import javax.swing.JComboBox;
012
013import org.jdom2.Attribute;
014import org.jdom2.Element;
015import org.slf4j.Logger;
016import org.slf4j.LoggerFactory;
017
018import jmri.InstanceManager;
019import jmri.Reporter;
020import jmri.beans.Identifiable;
021import jmri.beans.PropertyChangeSupport;
022import jmri.jmrit.operations.OperationsXml;
023import jmri.jmrit.operations.rollingstock.RollingStock;
024import jmri.jmrit.operations.rollingstock.cars.Car;
025import jmri.jmrit.operations.rollingstock.cars.CarLoad;
026import jmri.jmrit.operations.rollingstock.cars.CarRoads;
027import jmri.jmrit.operations.rollingstock.cars.CarTypes;
028import jmri.jmrit.operations.rollingstock.engines.Engine;
029import jmri.jmrit.operations.rollingstock.engines.EngineTypes;
030import jmri.jmrit.operations.setup.Control;
031import jmri.jmrit.operations.setup.Setup;
032import jmri.util.PhysicalLocation;
033
034/**
035 * Represents a location on the layout
036 *
037 * @author Daniel Boudreau Copyright (C) 2008, 2012, 2013
038 */
039public class Location extends PropertyChangeSupport implements Identifiable, PropertyChangeListener {
040
041    public static final String LOC_TRACK_REGIX = "s";
042
043    public static final String NONE = "";
044    public static final int RANGE_DEFAULT = 25;
045
046    protected String _id = NONE;
047    protected String _name = NONE;
048    protected int _IdNumber = 0;
049    protected int _numberRS = 0; // number of cars and engines (total rolling stock)
050    protected int _numberCars = 0; // number of cars
051    protected int _numberEngines = 0; // number of engines
052    protected int _pickupRS = 0;
053    protected int _dropRS = 0;
054    protected int _locationOps = NORMAL; // type of operations at this location
055    protected int _trainDir = EAST + WEST + NORTH + SOUTH; // train direction served by this location
056    protected int _length = 0; // length of all tracks at this location
057    protected int _usedLength = 0; // length of track filled by cars and engines
058    protected String _comment = NONE;
059    protected String _switchListComment = NONE; // optional switch list comment
060    protected boolean _switchList = true; // when true print switchlist for this location
061    protected String _defaultPrinter = NONE; // the default printer name when printing a switchlist
062    protected String _status = UNKNOWN; // print switch list status
063    protected int _switchListState = SW_CREATE; // switch list state, saved between sessions
064    protected Point _trainIconEast = new Point(); // coordinates of east bound train icons
065    protected Point _trainIconWest = new Point();
066    protected Point _trainIconNorth = new Point();
067    protected Point _trainIconSouth = new Point();
068    protected int _trainIconRangeX = RANGE_DEFAULT; // the x & y detection range for the train icon
069    protected int _trainIconRangeY = RANGE_DEFAULT;
070    protected Hashtable<String, Track> _trackHashTable = new Hashtable<>();
071    protected PhysicalLocation _physicalLocation = new PhysicalLocation();
072    protected List<String> _listTypes = new ArrayList<>();
073
074    // IdTag reader associated with this location.
075    protected Reporter _reader = null;
076
077    // Pool
078    protected int _idPoolNumber = 0;
079    protected Hashtable<String, Pool> _poolHashTable = new Hashtable<>();
080
081    public static final int NORMAL = 1; // types of track allowed at this location
082    public static final int STAGING = 2; // staging only
083
084    public static final int EAST = 1; // train direction serviced by this location
085    public static final int WEST = 2;
086    public static final int NORTH = 4;
087    public static final int SOUTH = 8;
088
089    // Switch list status
090    public static final String UNKNOWN = "";
091    public static final String PRINTED = Bundle.getMessage("Printed");
092    public static final String CSV_GENERATED = Bundle.getMessage("CsvGenerated");
093    public static final String MODIFIED = Bundle.getMessage("Modified");
094    public static final String UPDATED = Bundle.getMessage("Updated");
095
096    // Switch list states
097    public static final int SW_CREATE = 0; // create new switch list
098    public static final int SW_APPEND = 1; // append train into to switch list
099    public static final int SW_PRINTED = 2; // switch list printed
100
101    // For property change
102    public static final String TRACK_LISTLENGTH_CHANGED_PROPERTY = "trackListLength"; // NOI18N
103    public static final String TYPES_CHANGED_PROPERTY = "locationTypes"; // NOI18N
104    public static final String TRAINDIRECTION_CHANGED_PROPERTY = "locationTrainDirection"; // NOI18N
105    public static final String LENGTH_CHANGED_PROPERTY = "locationTrackLengths"; // NOI18N
106    public static final String USEDLENGTH_CHANGED_PROPERTY = "locationUsedLength"; // NOI18N
107    public static final String NAME_CHANGED_PROPERTY = "locationName"; // NOI18N
108    public static final String SWITCHLIST_CHANGED_PROPERTY = "switchList"; // NOI18N
109    public static final String DISPOSE_CHANGED_PROPERTY = "locationDispose"; // NOI18N
110    public static final String STATUS_CHANGED_PROPERTY = "locationStatus"; // NOI18N
111    public static final String POOL_LENGTH_CHANGED_PROPERTY = "poolLengthChanged"; // NOI18N
112    public static final String SWITCHLIST_COMMENT_CHANGED_PROPERTY = "switchListComment";// NOI18N
113    public static final String TRACK_BLOCKING_ORDER_CHANGED_PROPERTY = "locationTrackBlockingOrder";// NOI18N
114    public static final String LOCATION_REPORTER_PROPERTY = "locationReporterChange"; // NOI18N
115
116    public Location(String id, String name) {
117        log.debug("New location ({}) id: {}", name, id);
118        _name = name;
119        _id = id;
120        // a new location accepts all types
121        setTypeNames(InstanceManager.getDefault(CarTypes.class).getNames());
122        setTypeNames(InstanceManager.getDefault(EngineTypes.class).getNames());
123        addPropertyChangeListeners();
124    }
125
126    @Override
127    public String getId() {
128        return _id;
129    }
130
131    /**
132     * Sets the location's name.
133     * 
134     * @param name The string name for this location.
135     *
136     */
137    public void setName(String name) {
138        String old = _name;
139        _name = name;
140        if (!old.equals(name)) {
141            InstanceManager.getDefault(LocationManager.class).resetNameLengths(); // recalculate max location name length for manifests
142            setDirtyAndFirePropertyChange(NAME_CHANGED_PROPERTY, old, name);
143        }
144    }
145
146    // for combo boxes
147    @Override
148    public String toString() {
149        return _name;
150    }
151
152    public String getName() {
153        return _name;
154    }
155
156    /**
157     * Makes a copy of this location.
158     *
159     * @param newLocation the location to copy to
160     */
161    public void copyLocation(Location newLocation) {
162        newLocation.setComment(getComment());
163        newLocation.setDefaultPrinterName(getDefaultPrinterName());
164        newLocation.setLocationOps(getLocationOps());
165        newLocation.setSwitchListComment(getSwitchListComment());
166        newLocation.setSwitchListEnabled(isSwitchListEnabled());
167        newLocation.setTrainDirections(getTrainDirections());
168        // TODO should we set the train icon coordinates?
169        // rolling stock serviced by this location
170        for (String type : newLocation.getTypeNames()) {
171            if (acceptsTypeName(type)) {
172                continue;
173            } else {
174                newLocation.deleteTypeName(type);
175            }
176        }
177        copyTracksLocation(newLocation);
178
179    }
180
181    /**
182     * Copies all of the tracks at this location. If there's a track already at
183     * the copy to location with the same name, the track is skipped.
184     *
185     * @param location the location to copy the tracks to.
186     */
187    public void copyTracksLocation(Location location) {
188        for (Track track : getTracksList()) {
189            if (location.getTrackByName(track.getName(), null) != null) {
190                continue;
191            }
192            track.copyTrack(track.getName(), location);
193        }
194    }
195
196    public PhysicalLocation getPhysicalLocation() {
197        return (_physicalLocation);
198    }
199
200    public void setPhysicalLocation(PhysicalLocation l) {
201        _physicalLocation = l;
202        InstanceManager.getDefault(LocationManagerXml.class).setDirty(true);
203    }
204
205    /**
206     * Set total length of all tracks for this location
207     * 
208     * @param length The integer sum of all tracks at this location.
209     *
210     */
211    public void setLength(int length) {
212        int old = _length;
213        _length = length;
214        if (old != length) {
215            setDirtyAndFirePropertyChange(LENGTH_CHANGED_PROPERTY, Integer.toString(old), Integer
216                    .toString(length));
217        }
218    }
219
220    /**
221     *
222     * @return total length of all tracks for this location
223     */
224    public int getLength() {
225        return _length;
226    }
227
228    public void setUsedLength(int length) {
229        int old = _usedLength;
230        _usedLength = length;
231        if (old != length) {
232            setDirtyAndFirePropertyChange(USEDLENGTH_CHANGED_PROPERTY, Integer.toString(old), Integer
233                    .toString(length));
234        }
235    }
236
237    /**
238     *
239     * @return The length of the track that is occupied by cars and engines
240     */
241    public int getUsedLength() {
242        return _usedLength;
243    }
244
245    /**
246     * Set the operations mode for this location
247     *
248     * @param ops NORMAL STAGING
249     */
250    public void setLocationOps(int ops) {
251        int old = _locationOps;
252        _locationOps = ops;
253        if (old != ops) {
254            setDirtyAndFirePropertyChange("locationOps", Integer.toString(old), Integer.toString(ops)); // NOI18N
255        }
256    }
257
258    /**
259     * Gets the operations mode for this location
260     *
261     * @return NORMAL STAGING
262     */
263    public int getLocationOps() {
264        return _locationOps;
265    }
266
267    /**
268     * Used to determine if location is setup for staging
269     *
270     * @return true if location is setup as staging
271     */
272    public boolean isStaging() {
273        return getLocationOps() == STAGING;
274    }
275
276    /**
277     *
278     * @return True if location has spurs
279     */
280    public boolean hasSpurs() {
281        return hasTrackType(Track.SPUR);
282    }
283
284    /**
285     *
286     * @return True if location has classification/interchange tracks
287     */
288    public boolean hasInterchanges() {
289        return hasTrackType(Track.INTERCHANGE);
290    }
291
292    /**
293     *
294     * @return True if location has yard tracks
295     */
296    public boolean hasYards() {
297        return hasTrackType(Track.YARD);
298    }
299
300    /**
301     *
302     * @param trackType The track type to check.
303     * @return True if location has the track type specified Track.INTERCHANGE
304     *         Track.YARD Track.SPUR Track.Staging
305     */
306    public boolean hasTrackType(String trackType) {
307        Track track;
308        Enumeration<Track> en = _trackHashTable.elements();
309        while (en.hasMoreElements()) {
310            track = en.nextElement();
311            if (track.getTrackType().equals(trackType)) {
312                return true;
313            }
314        }
315        return false;
316    }
317
318    public int getNumberOfTracks() {
319        return _trackHashTable.size();
320    }
321
322    /**
323     * Sets the train directions that this location can service. EAST means that
324     * an Eastbound train can service the location.
325     *
326     * @param direction Any combination of EAST WEST NORTH SOUTH
327     */
328    public void setTrainDirections(int direction) {
329        int old = _trainDir;
330        _trainDir = direction;
331        if (old != direction) {
332            setDirtyAndFirePropertyChange(TRAINDIRECTION_CHANGED_PROPERTY, Integer.toString(old), Integer
333                    .toString(direction));
334        }
335    }
336
337    /**
338     * Gets the train directions that this location can service. EAST means that
339     * an Eastbound train can service the location.
340     *
341     * @return Any combination of EAST WEST NORTH SOUTH
342     */
343    public int getTrainDirections() {
344        return _trainDir;
345    }
346
347    /**
348     * Sets the quantity of rolling stock for this location
349     * 
350     * @param number An integer representing the quantity of rolling stock at
351     *            this location.
352     *
353     */
354    public void setNumberRS(int number) {
355        int old = _numberRS;
356        _numberRS = number;
357        if (old != number) {
358            setDirtyAndFirePropertyChange("locationNumberRS", Integer.toString(old), Integer.toString(number)); // NOI18N
359        }
360    }
361
362    /**
363     * Gets the number of cars and engines at this location
364     *
365     * @return number of cars at this location
366     */
367    public int getNumberRS() {
368        return _numberRS;
369    }
370
371    /**
372     * Sets the number of cars at this location
373     *
374     */
375    private void setNumberCars(int number) {
376        int old = _numberCars;
377        _numberCars = number;
378        if (old != number) {
379            setDirtyAndFirePropertyChange("locationNumberCars", Integer.toString(old), // NOI18N
380                    Integer.toString(number)); // NOI18N
381        }
382    }
383
384    /**
385     *
386     * @return The number of cars at this location
387     */
388    public int getNumberCars() {
389        return _numberCars;
390    }
391
392    /**
393     * Sets the number of engines at this location
394     *
395     */
396    private void setNumberEngines(int number) {
397        int old = _numberEngines;
398        _numberEngines = number;
399        if (old != number) {
400            setDirtyAndFirePropertyChange("locationNumberEngines", Integer.toString(old), // NOI18N
401                    Integer.toString(number)); // NOI18N
402        }
403    }
404
405    /**
406     *
407     * @return The number of engines at this location
408     */
409    public int getNumberEngines() {
410        return _numberEngines;
411    }
412
413    /**
414     * When true, a switchlist is desired for this location. Used for preview
415     * and printing a manifest for a single location
416     * 
417     * @param switchList When true, switch lists are enabled for this location.
418     *
419     */
420    public void setSwitchListEnabled(boolean switchList) {
421        boolean old = _switchList;
422        _switchList = switchList;
423        if (old != switchList) {
424            setDirtyAndFirePropertyChange(SWITCHLIST_CHANGED_PROPERTY, old ? "true" : "false", // NOI18N
425                    switchList ? "true" : "false"); // NOI18N
426        }
427    }
428
429    /**
430     * Used to determine if switch list is needed for this location
431     *
432     * @return true if switch list needed
433     */
434    public boolean isSwitchListEnabled() {
435        return _switchList;
436    }
437
438    public void setDefaultPrinterName(String name) {
439        String old = _defaultPrinter;
440        _defaultPrinter = name;
441        if (!old.equals(name)) {
442            setDirtyAndFirePropertyChange("locationDefaultPrinter", old, name); // NOI18N
443        }
444    }
445
446    public String getDefaultPrinterName() {
447        return _defaultPrinter;
448    }
449
450    /**
451     * Sets the print status for this location's switch list
452     *
453     * @param status UNKNOWN PRINTED MODIFIED UPDATED CSV_GENERATED
454     */
455    public void setStatus(String status) {
456        String old = _status;
457        _status = status;
458        if (!old.equals(status)) {
459            setDirtyAndFirePropertyChange(STATUS_CHANGED_PROPERTY, old, status);
460        }
461    }
462
463    /**
464     * The print status for this location's switch list
465     * 
466     * @return UNKNOWN PRINTED MODIFIED UPDATED CSV_GENERATED
467     */
468    public String getStatus() {
469        return _status;
470    }
471
472    /**
473     *
474     * @param state Location.SW_CREATE Location.SW_PRINTED Location.SW_APPEND
475     */
476    public void setSwitchListState(int state) {
477        int old = _switchListState;
478        _switchListState = state;
479        if (old != state) {
480            setDirtyAndFirePropertyChange("locationSwitchListState", old, state); // NOI18N
481        }
482    }
483
484    /**
485     * Returns the state of the switch list for this location.
486     *
487     * @return Location.SW_CREATE, Location.SW_PRINTED or Location.SW_APPEND
488     */
489    public int getSwitchListState() {
490        return _switchListState;
491    }
492
493    /**
494     * Sets the train icon coordinates for an eastbound train arriving at this
495     * location.
496     *
497     * @param point The XY coordinates on the panel.
498     */
499    public void setTrainIconEast(Point point) {
500        Point old = _trainIconEast;
501        _trainIconEast = point;
502        setDirtyAndFirePropertyChange("locationTrainIconEast", old.toString(), point.toString()); // NOI18N
503    }
504
505    public Point getTrainIconEast() {
506        return _trainIconEast;
507    }
508
509    public void setTrainIconWest(Point point) {
510        Point old = _trainIconWest;
511        _trainIconWest = point;
512        setDirtyAndFirePropertyChange("locationTrainIconWest", old.toString(), point.toString()); // NOI18N
513    }
514
515    public Point getTrainIconWest() {
516        return _trainIconWest;
517    }
518
519    public void setTrainIconNorth(Point point) {
520        Point old = _trainIconNorth;
521        _trainIconNorth = point;
522        setDirtyAndFirePropertyChange("locationTrainIconNorth", old.toString(), point.toString()); // NOI18N
523    }
524
525    public Point getTrainIconNorth() {
526        return _trainIconNorth;
527    }
528
529    public void setTrainIconSouth(Point point) {
530        Point old = _trainIconSouth;
531        _trainIconSouth = point;
532        setDirtyAndFirePropertyChange("locationTrainIconSouth", old.toString(), point.toString()); // NOI18N
533    }
534
535    public Point getTrainIconSouth() {
536        return _trainIconSouth;
537    }
538
539    /**
540     * Sets the X range for detecting the manual movement of a train icon.
541     * 
542     * @param x the +/- range for detection
543     */
544    public void setTrainIconRangeX(int x) {
545        int old = _trainIconRangeX;
546        _trainIconRangeX = x;
547        if (old != x) {
548            setDirtyAndFirePropertyChange("trainIconRangeX", Integer.toString(old), Integer.toString(x)); // NOI18N
549        }
550    }
551
552    public int getTrainIconRangeX() {
553        return _trainIconRangeX;
554    }
555
556    /**
557     * Sets the Y range for detecting the manual movement of a train icon.
558     * 
559     * @param y the +/- range for detection
560     */
561    public void setTrainIconRangeY(int y) {
562        int old = _trainIconRangeY;
563        _trainIconRangeY = y;
564        if (old != y) {
565            setDirtyAndFirePropertyChange("trainIconRangeY", Integer.toString(old), Integer.toString(y)); // NOI18N
566        }
567    }
568
569    public int getTrainIconRangeY() {
570        return _trainIconRangeY;
571    }
572
573    /**
574     * Adds rolling stock to a specific location.
575     * 
576     * @param rs The RollingStock to add.
577     *
578     */
579    public void addRS(RollingStock rs) {
580        setNumberRS(getNumberRS() + 1);
581        if (rs.getClass() == Car.class) {
582            setNumberCars(getNumberCars() + 1);
583        } else if (rs.getClass() == Engine.class) {
584            setNumberEngines(getNumberEngines() + 1);
585        }
586        setUsedLength(getUsedLength() + rs.getTotalLength());
587    }
588
589    public void deleteRS(RollingStock rs) {
590        setNumberRS(getNumberRS() - 1);
591        if (rs.getClass() == Car.class) {
592            setNumberCars(getNumberCars() - 1);
593        } else if (rs.getClass() == Engine.class) {
594            setNumberEngines(getNumberEngines() - 1);
595        }
596        setUsedLength(getUsedLength() - rs.getTotalLength());
597    }
598
599    /**
600     * Increments the number of cars and or engines that will be picked up by a
601     * train at this location.
602     */
603    public void addPickupRS() {
604        int old = _pickupRS;
605        _pickupRS++;
606        setDirtyAndFirePropertyChange("locationAddPickupRS", Integer.toString(old), Integer.toString(_pickupRS)); // NOI18N
607    }
608
609    /**
610     * Decrements the number of cars and or engines that will be picked up by a
611     * train at this location.
612     */
613    public void deletePickupRS() {
614        int old = _pickupRS;
615        _pickupRS--;
616        setDirtyAndFirePropertyChange("locationDeletePickupRS", Integer.toString(old), Integer.toString(_pickupRS)); // NOI18N
617    }
618
619    /**
620     * Increments the number of cars and or engines that will be dropped off by
621     * trains at this location.
622     */
623    public void addDropRS() {
624        int old = _dropRS;
625        _dropRS++;
626        setDirtyAndFirePropertyChange("locationAddDropRS", Integer.toString(old), Integer.toString(_dropRS)); // NOI18N
627    }
628
629    /**
630     * Decrements the number of cars and or engines that will be dropped off by
631     * trains at this location.
632     */
633    public void deleteDropRS() {
634        int old = _dropRS;
635        _dropRS--;
636        setDirtyAndFirePropertyChange("locationDeleteDropRS", Integer.toString(old), Integer.toString(_dropRS)); // NOI18N
637    }
638
639    /**
640     *
641     * @return the number of cars and engines that are scheduled for pick up at
642     *         this location.
643     */
644    public int getPickupRS() {
645        return _pickupRS;
646    }
647
648    /**
649     *
650     * @return the number of cars and engines that are scheduled for drop at
651     *         this location.
652     */
653    public int getDropRS() {
654        return _dropRS;
655    }
656
657    public void setComment(String comment) {
658        String old = _comment;
659        _comment = comment;
660        if (!old.equals(comment)) {
661            setDirtyAndFirePropertyChange("locationComment", old, comment); // NOI18N
662        }
663    }
664
665    public String getComment() {
666        return _comment;
667    }
668
669    public void setSwitchListComment(String comment) {
670        String old = _switchListComment;
671        _switchListComment = comment;
672        if (!old.equals(comment)) {
673            setDirtyAndFirePropertyChange(SWITCHLIST_COMMENT_CHANGED_PROPERTY, old, comment);
674        }
675    }
676
677    public String getSwitchListComment() {
678        return _switchListComment;
679    }
680
681    public String[] getTypeNames() {
682        String[] types = new String[_listTypes.size()];
683        for (int i = 0; i < _listTypes.size(); i++) {
684            types[i] = _listTypes.get(i);
685        }
686        return types;
687    }
688
689    private void setTypeNames(String[] types) {
690        if (types.length == 0) {
691            return;
692        }
693        java.util.Arrays.sort(types);
694        for (String type : types) {
695            _listTypes.add(type);
696        }
697    }
698
699    /**
700     * Adds the specific type of rolling stock to the will service list
701     *
702     * @param type of rolling stock that location will service
703     */
704    public void addTypeName(String type) {
705        // insert at start of list, sort later
706        if (type == null || _listTypes.contains(type)) {
707            return;
708        }
709        _listTypes.add(0, type);
710        log.debug("Location ({}) add rolling stock type ({})", getName(), type);
711        setDirtyAndFirePropertyChange(TYPES_CHANGED_PROPERTY, _listTypes.size() - 1, _listTypes.size());
712    }
713
714    public void deleteTypeName(String type) {
715        if (!_listTypes.contains(type)) {
716            return;
717        }
718        _listTypes.remove(type);
719        log.debug("Location ({}) delete rolling stock type ({})", getName(), type);
720        setDirtyAndFirePropertyChange(TYPES_CHANGED_PROPERTY, _listTypes.size() + 1, _listTypes.size());
721    }
722
723    public boolean acceptsTypeName(String type) {
724        return _listTypes.contains(type);
725    }
726
727    /**
728     * Adds a track to this location. Valid track types are spurs, yards,
729     * staging and interchange tracks.
730     *
731     * @param name of track
732     * @param type of track, Track.INTERCHANGE, Track.SPUR, Track.STAGING,
733     *            Track.YARD
734     * @return Track
735     */
736    public Track addTrack(String name, String type) {
737        Track track = getTrackByName(name, type);
738        if (track == null) {
739            _IdNumber++;
740            String id = _id + LOC_TRACK_REGIX + Integer.toString(_IdNumber);
741            log.debug("Adding new ({}) to ({}) track name ({}) id: {}", type, getName(), name, id);
742            track = new Track(id, name, type, this);
743            InstanceManager.getDefault(LocationManager.class).resetNameLengths(); // recalculate max track name length for manifests
744            register(track);
745        }
746        resetMoves(); // give all of the tracks equal weighting
747        return track;
748    }
749
750    /**
751     * Remember a NamedBean Object created outside the manager.
752     * 
753     * @param track The Track to be loaded at this location.
754     */
755    public void register(Track track) {
756        Integer old = Integer.valueOf(_trackHashTable.size());
757        _trackHashTable.put(track.getId(), track);
758        // add to the locations's available track length
759        setLength(getLength() + track.getLength());
760        // find last id created
761        String[] getId = track.getId().split(LOC_TRACK_REGIX);
762        int id = Integer.parseInt(getId[1]);
763        if (id > _IdNumber) {
764            _IdNumber = id;
765        }
766        setDirtyAndFirePropertyChange(TRACK_LISTLENGTH_CHANGED_PROPERTY, old, Integer.valueOf(_trackHashTable
767                .size()));
768        // listen for name and state changes to forward
769        track.addPropertyChangeListener(this);
770    }
771
772    public void deleteTrack(Track track) {
773        if (track != null) {
774            track.removePropertyChangeListener(this);
775            // subtract from the locations's available track length
776            setLength(getLength() - track.getLength());
777            track.dispose();
778            Integer old = Integer.valueOf(_trackHashTable.size());
779            _trackHashTable.remove(track.getId());
780            setDirtyAndFirePropertyChange(TRACK_LISTLENGTH_CHANGED_PROPERTY, old, Integer
781                    .valueOf(_trackHashTable.size()));
782        }
783    }
784
785    /**
786     * Get track at this location by name and type. Track type can be null.
787     *
788     * @param name track's name
789     * @param type track type
790     * @return track at location
791     */
792    public Track getTrackByName(String name, String type) {
793        Track track;
794        Enumeration<Track> en = _trackHashTable.elements();
795        while (en.hasMoreElements()) {
796            track = en.nextElement();
797            if (type == null) {
798                if (track.getName().equals(name)) {
799                    return track;
800                }
801            } else if (track.getName().equals(name) && track.getTrackType().equals(type)) {
802                return track;
803            }
804        }
805        return null;
806    }
807
808    public Track getTrackById(String id) {
809        return _trackHashTable.get(id);
810    }
811
812    /**
813     * Gets a list of track ids ordered by id for this location.
814     *
815     * @return list of track ids for this location
816     */
817    public List<String> getTrackIdsByIdList() {
818        String[] arr = new String[_trackHashTable.size()];
819        List<String> out = new ArrayList<>();
820        Enumeration<String> en = _trackHashTable.keys();
821        int i = 0;
822        while (en.hasMoreElements()) {
823            arr[i] = en.nextElement();
824            i++;
825        }
826        java.util.Arrays.sort(arr);
827        for (i = 0; i < arr.length; i++) {
828            out.add(arr[i]);
829        }
830        return out;
831    }
832
833    /**
834     * Gets a sorted by id list of tracks for this location.
835     *
836     * @return Sorted list of tracks by id for this location.
837     */
838    public List<Track> getTracksByIdList() {
839        List<Track> out = new ArrayList<>();
840        List<String> trackIds = getTrackIdsByIdList();
841        for (String id : trackIds) {
842            out.add(getTrackById(id));
843        }
844        return out;
845    }
846
847    /**
848     * Gets a unsorted list of the tracks at this location.
849     *
850     * @return tracks at this location.
851     */
852    public List<Track> getTracksList() {
853        List<Track> out = new ArrayList<>();
854        Enumeration<Track> en = _trackHashTable.elements();
855        while (en.hasMoreElements()) {
856            out.add(en.nextElement());
857        }
858        return out;
859    }
860
861    /**
862     * Sorted list by track name. Returns a list of tracks of a given track
863     * type. If type is null returns all tracks for the location.
864     *
865     * @param type track type: Track.YARD, Track.SPUR, Track.INTERCHANGE,
866     *            Track.STAGING
867     * @return list of tracks ordered by name for this location
868     */
869    public List<Track> getTracksByNameList(String type) {
870
871        List<Track> out = new ArrayList<>();
872
873        for (Track track : getTracksByIdList()) {
874            boolean locAdded = false;
875            for (int j = 0; j < out.size(); j++) {
876                if (track.getName().compareToIgnoreCase(out.get(j).getName()) < 0 &&
877                        (type != null && track.getTrackType().equals(type) || type == null)) {
878                    out.add(j, track);
879                    locAdded = true;
880                    break;
881                }
882            }
883            if (!locAdded && (type != null && track.getTrackType().equals(type) || type == null)) {
884                out.add(track);
885            }
886        }
887        return out;
888    }
889
890    /**
891     * Sorted list by track moves. Returns a list of a given track type. If type
892     * is null, all tracks for the location are returned. Tracks with schedules
893     * are placed at the start of the list. Tracks that are alternates are
894     * removed.
895     *
896     * @param type track type: Track.YARD, Track.SPUR, Track.INTERCHANGE,
897     *            Track.STAGING
898     * @return list of tracks at this location ordered by moves
899     */
900    public List<Track> getTracksByMovesList(String type) {
901
902        List<Track> moveList = new ArrayList<>();
903
904        for (Track track : getTracksByIdList()) {
905            boolean locAdded = false;
906            for (int j = 0; j < moveList.size(); j++) {
907                if (track.getMoves() < moveList.get(j).getMoves() &&
908                        (type != null && track.getTrackType().equals(type) || type == null)) {
909                    moveList.add(j, track);
910                    locAdded = true;
911                    break;
912                }
913            }
914            if (!locAdded && (type != null && track.getTrackType().equals(type) || type == null)) {
915                moveList.add(track);
916            }
917        }
918        // bias tracks with schedules to the start of the list
919        // remove any alternate tracks from the list
920        List<Track> out = new ArrayList<>();
921        for (int i = 0; i < moveList.size(); i++) {
922            Track track = moveList.get(i);
923            if (!track.getScheduleId().equals(NONE)) {
924                out.add(track);
925                moveList.remove(i--);
926            } else if (track.isAlternate()) {
927                moveList.remove(i--);
928            }
929        }
930        for (Track track : moveList) {
931            out.add(track);
932        }
933        return out;
934    }
935
936    /**
937     * Sorted list by track blocking order. Returns a list of a given track
938     * type. If type is null, all tracks for the location are returned.
939     *
940     * @param type track type: Track.YARD, Track.SPUR, Track.INTERCHANGE,
941     *            Track.STAGING
942     * @return list of tracks at this location ordered by blocking order
943     */
944    public List<Track> getTracksByBlockingOrderList(String type) {
945        List<Track> orderList = new ArrayList<>();
946        for (Track track : getTracksByNameList(type)) {
947            boolean trackAdded = false;
948            for (int j = 0; j < orderList.size(); j++) {
949                if (track.getBlockingOrder() < orderList.get(j).getBlockingOrder()) {
950                    orderList.add(j, track);
951                    trackAdded = true;
952                    break;
953                }
954            }
955            if (!trackAdded) {
956                orderList.add(track);
957            }
958        }
959        return orderList;
960    }
961
962    public void resetTracksByBlockingOrder() {
963        for (Track track : getTracksList()) {
964            track.setBlockingOrder(0);
965        }
966        setDirtyAndFirePropertyChange(TRACK_BLOCKING_ORDER_CHANGED_PROPERTY, true, false);
967    }
968
969    public void resequnceTracksByBlockingOrder() {
970        int order = 1;
971        for (Track track : getTracksByBlockingOrderList(null)) {
972            track.setBlockingOrder(order++);
973        }
974        setDirtyAndFirePropertyChange(TRACK_BLOCKING_ORDER_CHANGED_PROPERTY, true, false);
975    }
976
977    public void changeTrackBlockingOrderEarlier(Track track) {
978        // if track blocking order is 0, then the blocking table has never been initialized
979        if (track.getBlockingOrder() != 0) {
980            //first adjust the track being replaced
981            Track repalceTrack = getTrackByBlockingOrder(track.getBlockingOrder() - 1);
982            if (repalceTrack != null) {
983                repalceTrack.setBlockingOrder(track.getBlockingOrder());
984            }
985            track.setBlockingOrder(track.getBlockingOrder() - 1);
986            // move the end of order
987            if (track.getBlockingOrder() <= 0)
988                track.setBlockingOrder(_trackHashTable.size() + 1);
989        }
990        resequnceTracksByBlockingOrder();
991    }
992
993    public void changeTrackBlockingOrderLater(Track track) {
994        // if track blocking order is 0, then the blocking table has never been initialized
995        if (track.getBlockingOrder() != 0) {
996            //first adjust the track being replaced
997            Track repalceTrack = getTrackByBlockingOrder(track.getBlockingOrder() + 1);
998            if (repalceTrack != null) {
999                repalceTrack.setBlockingOrder(track.getBlockingOrder());
1000            }
1001            track.setBlockingOrder(track.getBlockingOrder() + 1);
1002            // move the start of order
1003            if (track.getBlockingOrder() > _trackHashTable.size())
1004                track.setBlockingOrder(0);
1005        }
1006        resequnceTracksByBlockingOrder();
1007    }
1008
1009    private Track getTrackByBlockingOrder(int order) {
1010        for (Track track : getTracksList()) {
1011            if (track.getBlockingOrder() == order)
1012                return track;
1013        }
1014        return null; // not found!
1015    }
1016
1017    public boolean isTrackAtLocation(Track track) {
1018        if (track == null) {
1019            return true;
1020        }
1021        return _trackHashTable.contains(track);
1022    }
1023
1024    /**
1025     * Reset the move count for all tracks at this location
1026     */
1027    public void resetMoves() {
1028        List<Track> tracks = getTracksList();
1029        for (Track track : tracks) {
1030            track.setMoves(0);
1031        }
1032    }
1033
1034    /**
1035     * Updates a JComboBox with all of the track locations for this location.
1036     *
1037     * @param box JComboBox to be updated.
1038     */
1039    public void updateComboBox(JComboBox<Track> box) {
1040        box.removeAllItems();
1041        box.addItem(null);
1042        List<Track> tracks = getTracksByNameList(null);
1043        for (Track track : tracks) {
1044            box.addItem(track);
1045        }
1046    }
1047
1048    /**
1049     * Updates a JComboBox with tracks that can service the rolling stock.
1050     *
1051     * @param box JComboBox to be updated.
1052     * @param rs Rolling Stock to be serviced
1053     * @param filter When true, remove tracks not able to service rs.
1054     * @param isDestination When true, the tracks are destinations for the rs.
1055     */
1056    public void updateComboBox(JComboBox<Track> box, RollingStock rs, boolean filter, boolean isDestination) {
1057        updateComboBox(box);
1058        if (!filter || rs == null) {
1059            return;
1060        }
1061        List<Track> tracks = getTracksByNameList(null);
1062        for (Track track : tracks) {
1063            String status = "";
1064            if (isDestination) {
1065                status = rs.testDestination(this, track);
1066            } else {
1067                status = rs.testLocation(this, track);
1068            }
1069            if (status.equals(Track.OKAY) && (!isDestination || !track.isStaging())) {
1070                box.setSelectedItem(track);
1071                log.debug("Available track: {} for location: {}", track.getName(), getName());
1072            } else {
1073                box.removeItem(track);
1074            }
1075        }
1076    }
1077
1078    /**
1079     * Adds a track pool for this location. A track pool is a set of tracks
1080     * where the length of the tracks is shared between all of them.
1081     *
1082     * @param name the name of the Pool to create
1083     * @return Pool
1084     */
1085    public Pool addPool(String name) {
1086        Pool pool = getPoolByName(name);
1087        if (pool == null) {
1088            _idPoolNumber++;
1089            String id = _id + "p" + Integer.toString(_idPoolNumber);
1090            log.debug("creating new pool ({}) id: {}", name, id);
1091            pool = new Pool(id, name);
1092            register(pool);
1093        }
1094        return pool;
1095    }
1096
1097    public void removePool(Pool pool) {
1098        if (pool != null) {
1099            _poolHashTable.remove(pool.getId());
1100            setDirtyAndFirePropertyChange(POOL_LENGTH_CHANGED_PROPERTY, Integer
1101                    .valueOf(_poolHashTable.size() + 1), Integer.valueOf(_poolHashTable.size()));
1102        }
1103    }
1104
1105    public Pool getPoolByName(String name) {
1106        Pool pool;
1107        Enumeration<Pool> en = _poolHashTable.elements();
1108        while (en.hasMoreElements()) {
1109            pool = en.nextElement();
1110            if (pool.getName().equals(name)) {
1111                return pool;
1112            }
1113        }
1114        return null;
1115    }
1116
1117    public void register(Pool pool) {
1118        Integer old = Integer.valueOf(_poolHashTable.size());
1119        _poolHashTable.put(pool.getId(), pool);
1120        // find last id created
1121        String[] getId = pool.getId().split("p");
1122        int id = Integer.parseInt(getId[1]);
1123        if (id > _idPoolNumber) {
1124            _idPoolNumber = id;
1125        }
1126        setDirtyAndFirePropertyChange(POOL_LENGTH_CHANGED_PROPERTY, old, Integer.valueOf(_poolHashTable
1127                .size()));
1128    }
1129
1130    public void updatePoolComboBox(JComboBox<Pool> box) {
1131        box.removeAllItems();
1132        box.addItem(null);
1133        for (Pool pool : getPoolsByNameList()) {
1134            box.addItem(pool);
1135        }
1136    }
1137
1138    /**
1139     * Gets a list of Pools for this location.
1140     *
1141     * @return A list of Pools
1142     */
1143    public List<Pool> getPoolsByNameList() {
1144        List<Pool> pools = new ArrayList<>();
1145        Enumeration<Pool> en = _poolHashTable.elements();
1146        while (en.hasMoreElements()) {
1147            pools.add(en.nextElement());
1148        }
1149        return pools;
1150    }
1151
1152    /**
1153     * True if this location has a track with pick up or set out restrictions.
1154     * 
1155     * @return True if there are restrictions at this location.
1156     */
1157    public boolean hasServiceRestrictions() {
1158        Track track;
1159        Enumeration<Track> en = _trackHashTable.elements();
1160        while (en.hasMoreElements()) {
1161            track = en.nextElement();
1162            if (!track.getDropOption().equals(Track.ANY) || !track.getPickupOption().equals(Track.ANY)) {
1163                return true;
1164            }
1165        }
1166        return false;
1167    }
1168
1169    /**
1170     * Used to determine if there are Pools at this location.
1171     *
1172     * @return True if there are Pools at this location
1173     */
1174    public boolean hasPools() {
1175        return _poolHashTable.size() > 0;
1176    }
1177
1178    /**
1179     * Used to determine if there are any planned pickups at this location.
1180     *
1181     * @return True if there are planned pickups
1182     */
1183    public boolean hasPlannedPickups() {
1184        List<Track> tracks = getTracksList();
1185        for (Track track : tracks) {
1186            if (track.getIgnoreUsedLengthPercentage() > 0) {
1187                return true;
1188            }
1189        }
1190        return false;
1191    }
1192
1193    /**
1194     * Used to determine if there are any load restrictions at this location.
1195     *
1196     * @return True if there are load restrictions
1197     */
1198    public boolean hasLoadRestrictions() {
1199        List<Track> tracks = getTracksList();
1200        for (Track track : tracks) {
1201            if (!track.getLoadOption().equals(Track.ALL_LOADS)) {
1202                return true;
1203            }
1204        }
1205        return false;
1206    }
1207
1208    /**
1209     * Used to determine if there are any load ship restrictions at this
1210     * location.
1211     *
1212     * @return True if there are load ship restrictions
1213     */
1214    public boolean hasShipLoadRestrictions() {
1215        List<Track> tracks = getTracksList();
1216        for (Track track : tracks) {
1217            if (!track.getShipLoadOption().equals(Track.ALL_LOADS)) {
1218                return true;
1219            }
1220        }
1221        return false;
1222    }
1223
1224    /**
1225     * Used to determine if there are any road restrictions at this location.
1226     *
1227     * @return True if there are road restrictions
1228     */
1229    public boolean hasRoadRestrictions() {
1230        List<Track> tracks = getTracksList();
1231        for (Track track : tracks) {
1232            if (!track.getRoadOption().equals(Track.ALL_ROADS)) {
1233                return true;
1234            }
1235        }
1236        return false;
1237    }
1238
1239    /**
1240     * Used to determine if there are any track destination restrictions at this
1241     * location.
1242     *
1243     * @return True if there are road restrictions
1244     */
1245    public boolean hasDestinationRestrictions() {
1246        List<Track> tracks = getTracksList();
1247        for (Track track : tracks) {
1248            if (!track.getDestinationOption().equals(Track.ALL_DESTINATIONS)) {
1249                return true;
1250            }
1251        }
1252        return false;
1253    }
1254
1255    public boolean hasAlternateTracks() {
1256        for (Track track : getTracksList()) {
1257            if (track.getAlternateTrack() != null) {
1258                return true;
1259            }
1260        }
1261        return false;
1262    }
1263
1264    public boolean hasOrderRestrictions() {
1265        for (Track track : getTracksList()) {
1266            if (!track.getServiceOrder().equals(Track.NORMAL)) {
1267                return true;
1268            }
1269        }
1270        return false;
1271    }
1272    
1273    public boolean hasSchedules() {
1274        for (Track track : getTracksList()) {
1275            if (track.isSpur() && track.getSchedule() != null) {
1276                return true;
1277            }
1278        }
1279        return false;
1280    }
1281    
1282    public boolean hasReporters() {
1283        for (Track track : getTracksList()) {
1284            if (track.getReporter() != null) {
1285                return true;
1286            }
1287        }
1288        return false;
1289    }
1290
1291    /*
1292     * set the jmri.Reporter object associated with this location.
1293     * 
1294     * @param reader jmri.Reporter object.
1295     */
1296    public void setReporter(Reporter r) {
1297        Reporter old = _reader;
1298        _reader = r;
1299        if (old != r) {
1300            setDirtyAndFirePropertyChange(LOCATION_REPORTER_PROPERTY, old, r);
1301        }
1302    }
1303
1304    /*
1305     * get the jmri.Reporter object associated with this location.
1306     * 
1307     * @return jmri.Reporter object.
1308     */
1309    public Reporter getReporter() {
1310        return _reader;
1311    }
1312    
1313    public String getReporterName() {
1314        if (getReporter() != null) {
1315            return getReporter().getDisplayName();
1316        }
1317        return "";
1318    }
1319
1320    public void dispose() {
1321        List<Track> tracks = getTracksList();
1322        for (Track track : tracks) {
1323            deleteTrack(track);
1324        }
1325        InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this);
1326        InstanceManager.getDefault(CarRoads.class).removePropertyChangeListener(this);
1327        InstanceManager.getDefault(EngineTypes.class).removePropertyChangeListener(this);
1328        // Change name in case object is still in use, for example Schedules
1329        setName(MessageFormat.format(Bundle.getMessage("NotValid"), new Object[]{getName()}));
1330        setDirtyAndFirePropertyChange(DISPOSE_CHANGED_PROPERTY, null, DISPOSE_CHANGED_PROPERTY);
1331    }
1332
1333    private void addPropertyChangeListeners() {
1334        InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this);
1335        InstanceManager.getDefault(CarRoads.class).addPropertyChangeListener(this);
1336        InstanceManager.getDefault(EngineTypes.class).addPropertyChangeListener(this);
1337    }
1338
1339    /**
1340     * Construct this Entry from XML. This member has to remain synchronized
1341     * with the detailed DTD in operations-locations.dtd
1342     *
1343     * @param e Consist XML element
1344     */
1345    @SuppressWarnings("deprecation") // until there's a replacement for convertFromXmlComment()
1346    public Location(Element e) {
1347        Attribute a;
1348        if ((a = e.getAttribute(Xml.ID)) != null) {
1349            _id = a.getValue();
1350        } else {
1351            log.warn("no id attribute in location element when reading operations");
1352        }
1353        if ((a = e.getAttribute(Xml.NAME)) != null) {
1354            _name = a.getValue();
1355        }
1356        if ((a = e.getAttribute(Xml.OPS)) != null) {
1357            try {
1358                _locationOps = Integer.parseInt(a.getValue());
1359            } catch (NumberFormatException nfe) {
1360                log.error("Location ops isn't a vaild number for location {}", getName());
1361            }
1362        }
1363        if ((a = e.getAttribute(Xml.DIR)) != null) {
1364            try {
1365                _trainDir = Integer.parseInt(a.getValue());
1366            } catch (NumberFormatException nfe) {
1367                log.error("Train directions isn't a vaild number for location {}", getName());
1368            }
1369        }
1370        if ((a = e.getAttribute(Xml.SWITCH_LIST)) != null) {
1371            _switchList = (a.getValue().equals(Xml.TRUE));
1372        }
1373        if ((a = e.getAttribute(Xml.SWITCH_LIST_STATE)) != null) {
1374            try {
1375                _switchListState = Integer.parseInt(a.getValue());
1376            } catch (NumberFormatException nfe) {
1377                log.error("Switch list state isn't a vaild number for location {}", getName());
1378            }
1379            if (getSwitchListState() == SW_PRINTED) {
1380                setStatus(PRINTED);
1381            }
1382        }
1383        if ((a = e.getAttribute(Xml.PRINTER_NAME)) != null) {
1384            _defaultPrinter = a.getValue();
1385        }
1386        // load train icon coordinates
1387        Attribute x;
1388        Attribute y;
1389        try {
1390            if ((x = e.getAttribute(Xml.EAST_TRAIN_ICON_X)) != null &&
1391                    (y = e.getAttribute(Xml.EAST_TRAIN_ICON_Y)) != null) {
1392                setTrainIconEast(new Point(Integer.parseInt(x.getValue()), Integer.parseInt(y.getValue())));
1393            }
1394            if ((x = e.getAttribute(Xml.WEST_TRAIN_ICON_X)) != null &&
1395                    (y = e.getAttribute(Xml.WEST_TRAIN_ICON_Y)) != null) {
1396                setTrainIconWest(new Point(Integer.parseInt(x.getValue()), Integer.parseInt(y.getValue())));
1397            }
1398            if ((x = e.getAttribute(Xml.NORTH_TRAIN_ICON_X)) != null &&
1399                    (y = e.getAttribute(Xml.NORTH_TRAIN_ICON_Y)) != null) {
1400                setTrainIconNorth(new Point(Integer.parseInt(x.getValue()), Integer.parseInt(y.getValue())));
1401            }
1402            if ((x = e.getAttribute(Xml.SOUTH_TRAIN_ICON_X)) != null &&
1403                    (y = e.getAttribute(Xml.SOUTH_TRAIN_ICON_Y)) != null) {
1404                setTrainIconSouth(new Point(Integer.parseInt(x.getValue()), Integer.parseInt(y.getValue())));
1405            }
1406
1407            if ((x = e.getAttribute(Xml.TRAIN_ICON_RANGE_X)) != null) {
1408                setTrainIconRangeX(Integer.parseInt(x.getValue()));
1409            }
1410            if ((y = e.getAttribute(Xml.TRAIN_ICON_RANGE_Y)) != null) {
1411                setTrainIconRangeY(Integer.parseInt(y.getValue()));
1412            }
1413
1414        } catch (NumberFormatException nfe) {
1415            log.error("Train icon coordinates aren't vaild for location {}", getName());
1416        }
1417        
1418        if ((a = e.getAttribute(Xml.COMMENT)) != null) {
1419            _comment = OperationsXml.convertFromXmlComment(a.getValue());
1420        }
1421
1422        if ((a = e.getAttribute(Xml.SWITCH_LIST_COMMENT)) != null) {
1423            _switchListComment = a.getValue();
1424        }
1425        if ((a = e.getAttribute(Xml.PHYSICAL_LOCATION)) != null) {
1426            _physicalLocation = PhysicalLocation.parse(a.getValue());
1427        }
1428        // new way of reading car types using elements added in 3.3.1
1429        if (e.getChild(Xml.TYPES) != null) {
1430            List<Element> carTypes = e.getChild(Xml.TYPES).getChildren(Xml.CAR_TYPE);
1431            String[] types = new String[carTypes.size()];
1432            for (int i = 0; i < carTypes.size(); i++) {
1433                Element type = carTypes.get(i);
1434                if ((a = type.getAttribute(Xml.NAME)) != null) {
1435                    types[i] = a.getValue();
1436                }
1437            }
1438            setTypeNames(types);
1439            List<Element> locoTypes = e.getChild(Xml.TYPES).getChildren(Xml.LOCO_TYPE);
1440            types = new String[locoTypes.size()];
1441            for (int i = 0; i < locoTypes.size(); i++) {
1442                Element type = locoTypes.get(i);
1443                if ((a = type.getAttribute(Xml.NAME)) != null) {
1444                    types[i] = a.getValue();
1445                }
1446            }
1447            setTypeNames(types);
1448        } // old way of reading car types up to version 2.99.6
1449        else if ((a = e.getAttribute(Xml.CAR_TYPES)) != null) {
1450            String names = a.getValue();
1451            String[] Types = names.split("%%"); // NOI18N
1452            setTypeNames(Types);
1453        }
1454        // early version of operations called tracks "secondary"
1455        if (e.getChildren(Xml.SECONDARY) != null) {
1456            List<Element> eTracks = e.getChildren(Xml.SECONDARY);
1457            for (Element eTrack : eTracks) {
1458                register(new Track(eTrack, this));
1459            }
1460        }
1461        if (e.getChildren(Xml.TRACK) != null) {
1462            List<Element> eTracks = e.getChildren(Xml.TRACK);
1463            log.debug("location ({}) has {} tracks", getName(), eTracks.size());
1464            for (Element eTrack : eTracks) {
1465                register(new Track(eTrack, this));
1466            }
1467        }
1468        if (e.getAttribute(Xml.READER) != null) {
1469            //            @SuppressWarnings("unchecked")
1470            try {
1471                Reporter r = jmri.InstanceManager
1472                        .getDefault(jmri.ReporterManager.class)
1473                        .provideReporter(
1474                                e.getAttribute(Xml.READER).getValue());
1475                _reader = r;
1476            } catch (IllegalArgumentException ex) {
1477                log.warn("Not able to find reader: {} for location ({})", e.getAttribute(Xml.READER).getValue(),
1478                        getName());
1479            }
1480        }
1481        addPropertyChangeListeners();
1482    }
1483
1484    /**
1485     * Create an XML element to represent this Entry. This member has to remain
1486     * synchronized with the detailed DTD in operations-locations.dtd.
1487     *
1488     * @return Contents in a JDOM Element
1489     */
1490    public Element store() {
1491        Element e = new Element(Xml.LOCATION);
1492        e.setAttribute(Xml.ID, getId());
1493        e.setAttribute(Xml.NAME, getName());
1494        e.setAttribute(Xml.OPS, Integer.toString(getLocationOps()));
1495        e.setAttribute(Xml.DIR, Integer.toString(getTrainDirections()));
1496        e.setAttribute(Xml.SWITCH_LIST, isSwitchListEnabled() ? Xml.TRUE : Xml.FALSE);
1497        if (!Setup.isSwitchListRealTime()) {
1498            e.setAttribute(Xml.SWITCH_LIST_STATE, Integer.toString(getSwitchListState()));
1499        }
1500        if (!getDefaultPrinterName().equals(NONE)) {
1501            e.setAttribute(Xml.PRINTER_NAME, getDefaultPrinterName());
1502        }
1503        if (!getTrainIconEast().equals(new Point())) {
1504            e.setAttribute(Xml.EAST_TRAIN_ICON_X, Integer.toString(getTrainIconEast().x));
1505            e.setAttribute(Xml.EAST_TRAIN_ICON_Y, Integer.toString(getTrainIconEast().y));
1506        }
1507        if (!getTrainIconWest().equals(new Point())) {
1508            e.setAttribute(Xml.WEST_TRAIN_ICON_X, Integer.toString(getTrainIconWest().x));
1509            e.setAttribute(Xml.WEST_TRAIN_ICON_Y, Integer.toString(getTrainIconWest().y));
1510        }
1511        if (!getTrainIconNorth().equals(new Point())) {
1512            e.setAttribute(Xml.NORTH_TRAIN_ICON_X, Integer.toString(getTrainIconNorth().x));
1513            e.setAttribute(Xml.NORTH_TRAIN_ICON_Y, Integer.toString(getTrainIconNorth().y));
1514        }
1515        if (!getTrainIconSouth().equals(new Point())) {
1516            e.setAttribute(Xml.SOUTH_TRAIN_ICON_X, Integer.toString(getTrainIconSouth().x));
1517            e.setAttribute(Xml.SOUTH_TRAIN_ICON_Y, Integer.toString(getTrainIconSouth().y));
1518        }
1519        if (getTrainIconRangeX() != RANGE_DEFAULT) {
1520            e.setAttribute(Xml.TRAIN_ICON_RANGE_X, Integer.toString(getTrainIconRangeX()));
1521        }
1522        if (getTrainIconRangeY() != RANGE_DEFAULT) {
1523            e.setAttribute(Xml.TRAIN_ICON_RANGE_Y, Integer.toString(getTrainIconRangeY()));
1524        }
1525        if (_reader != null) {
1526            e.setAttribute(Xml.READER, _reader.getDisplayName());
1527        }
1528        // build list of rolling stock types for this location
1529        String[] types = getTypeNames();
1530        // new way of saving car types
1531        Element eTypes = new Element(Xml.TYPES);
1532        for (String type : types) {
1533            // don't save types that have been deleted by user
1534            if (InstanceManager.getDefault(EngineTypes.class).containsName(type)) {
1535                Element eType = new Element(Xml.LOCO_TYPE);
1536                eType.setAttribute(Xml.NAME, type);
1537                eTypes.addContent(eType);
1538            } else if (InstanceManager.getDefault(CarTypes.class).containsName(type)) {
1539                Element eType = new Element(Xml.CAR_TYPE);
1540                eType.setAttribute(Xml.NAME, type);
1541                eTypes.addContent(eType);
1542            }
1543        }
1544        e.addContent(eTypes);
1545
1546        // save physical location if not default
1547        if (getPhysicalLocation() != null && !getPhysicalLocation().equals(PhysicalLocation.Origin)) {
1548            e.setAttribute(Xml.PHYSICAL_LOCATION, getPhysicalLocation().toString());
1549        }
1550
1551        e.setAttribute(Xml.COMMENT, getComment());
1552        e.setAttribute(Xml.SWITCH_LIST_COMMENT, getSwitchListComment());
1553
1554        List<Track> tracks = getTracksByIdList();
1555        for (Track track : tracks) {
1556            e.addContent(track.store());
1557        }
1558
1559        return e;
1560    }
1561
1562    private void replaceType(String oldType, String newType) {
1563        if (acceptsTypeName(oldType)) {
1564            if (newType != null) {
1565                addTypeName(newType);
1566            }
1567            // now adjust tracks
1568            List<Track> tracks = getTracksList();
1569            for (Track track : tracks) {
1570                if (track.isTypeNameAccepted(oldType)) {
1571                    track.deleteTypeName(oldType);
1572                    if (newType != null) {
1573                        track.addTypeName(newType);
1574                    }
1575                }
1576                // adjust custom loads
1577                String[] loadNames = track.getLoadNames();
1578                for (String load : loadNames) {
1579                    String[] splitLoad = load.split(CarLoad.SPLIT_CHAR);
1580                    if (splitLoad.length > 1) {
1581                        if (splitLoad[0].equals(oldType)) {
1582                            track.deleteLoadName(load);
1583                            if (newType != null) {
1584                                load = newType + CarLoad.SPLIT_CHAR + splitLoad[1];
1585                                track.addLoadName(load);
1586                            }
1587                        }
1588                    }
1589                }
1590                loadNames = track.getShipLoadNames();
1591                for (String load : loadNames) {
1592                    String[] splitLoad = load.split(CarLoad.SPLIT_CHAR);
1593                    if (splitLoad.length > 1) {
1594                        if (splitLoad[0].equals(oldType)) {
1595                            track.deleteShipLoadName(load);
1596                            if (newType != null) {
1597                                load = newType + CarLoad.SPLIT_CHAR + splitLoad[1];
1598                                track.addShipLoadName(load);
1599                            }
1600                        }
1601                    }
1602                }
1603            }
1604            deleteTypeName(oldType);
1605        }
1606    }
1607
1608    private void replaceRoad(String oldRoad, String newRoad) {
1609        // now adjust any track locations
1610        List<Track> tracks = getTracksList();
1611        for (Track track : tracks) {
1612            if (track.containsRoadName(oldRoad)) {
1613                track.deleteRoadName(oldRoad);
1614                if (newRoad != null) {
1615                    track.addRoadName(newRoad);
1616                }
1617            }
1618        }
1619    }
1620
1621    @Override
1622    public void propertyChange(java.beans.PropertyChangeEvent e) {
1623        if (Control.SHOW_PROPERTY) {
1624            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
1625                    .getNewValue());
1626        }
1627        // update length of tracks at this location if track length changes
1628        if (e.getPropertyName().equals(Track.LENGTH_CHANGED_PROPERTY)) {
1629            setLength(getLength() -
1630                    Integer.parseInt((String) e.getOldValue()) +
1631                    Integer.parseInt((String) e.getNewValue()));
1632        }
1633        // if a track type change, must update all tables
1634        if (e.getPropertyName().equals(Track.TRACK_TYPE_CHANGED_PROPERTY)) {
1635            setDirtyAndFirePropertyChange(TRACK_LISTLENGTH_CHANGED_PROPERTY, null, null);
1636        }
1637        if (e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY) ||
1638                e.getPropertyName().equals(CarTypes.CARTYPES_CHANGED_PROPERTY) ||
1639                e.getPropertyName().equals(EngineTypes.ENGINETYPES_NAME_CHANGED_PROPERTY)) {
1640            replaceType((String) e.getOldValue(), (String) e.getNewValue());
1641        }
1642        if (e.getPropertyName().equals(CarRoads.CARROADS_NAME_CHANGED_PROPERTY)) {
1643            replaceRoad((String) e.getOldValue(), (String) e.getNewValue());
1644        }
1645    }
1646
1647    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
1648        InstanceManager.getDefault(LocationManagerXml.class).setDirty(true);
1649        firePropertyChange(p, old, n);
1650    }
1651
1652    private final static Logger log = LoggerFactory.getLogger(Location.class);
1653
1654}