001package jmri.jmrit.operations.locations;
002
003import java.util.*;
004
005import org.jdom2.Attribute;
006import org.jdom2.Element;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import jmri.InstanceManager;
011import jmri.Reporter;
012import jmri.beans.PropertyChangeSupport;
013import jmri.jmrit.operations.locations.divisions.Division;
014import jmri.jmrit.operations.locations.schedules.*;
015import jmri.jmrit.operations.rollingstock.RollingStock;
016import jmri.jmrit.operations.rollingstock.cars.*;
017import jmri.jmrit.operations.rollingstock.engines.Engine;
018import jmri.jmrit.operations.rollingstock.engines.EngineTypes;
019import jmri.jmrit.operations.routes.Route;
020import jmri.jmrit.operations.routes.RouteLocation;
021import jmri.jmrit.operations.setup.Setup;
022import jmri.jmrit.operations.trains.*;
023import jmri.jmrit.operations.trains.schedules.TrainSchedule;
024import jmri.jmrit.operations.trains.schedules.TrainScheduleManager;
025
026/**
027 * Represents a location (track) on the layout Can be a spur, yard, staging, or
028 * interchange track.
029 *
030 * @author Daniel Boudreau Copyright (C) 2008 - 2014
031 */
032public class Track extends PropertyChangeSupport {
033
034    public static final String NONE = "";
035
036    protected String _id = NONE;
037    protected String _name = NONE;
038    protected String _trackType = NONE; // yard, spur, interchange or staging
039    protected Location _location; // the location for this track
040    protected int _trainDir = EAST + WEST + NORTH + SOUTH; // train directions
041    protected int _numberRS = 0; // number of cars and engines
042    protected int _numberCars = 0; // number of cars
043    protected int _numberEngines = 0; // number of engines
044    protected int _pickupRS = 0; // number of pick ups by trains
045    protected int _dropRS = 0; // number of set outs by trains
046    protected int _length = 0; // length of track
047    protected int _reserved = 0; // length of track reserved by trains
048    protected int _reservedLengthDrops = 0; // reserved for car drops
049    protected int _numberCarsEnRoute = 0; // number of cars en-route
050    protected int _usedLength = 0; // length of track filled by cars and engines
051    protected int _ignoreUsedLengthPercentage = IGNORE_0;
052    // ignore values 0 - 100%
053    public static final int IGNORE_0 = 0;
054    public static final int IGNORE_25 = 25;
055    public static final int IGNORE_50 = 50;
056    public static final int IGNORE_75 = 75;
057    public static final int IGNORE_100 = 100;
058    protected int _moves = 0; // count of the drops since creation
059    protected int _blockingOrder = 0; // the order tracks are serviced
060    protected String _alternateTrackId = NONE; // the alternate track id
061    protected String _comment = NONE;
062
063    // car types serviced by this track
064    protected List<String> _typeList = new ArrayList<>();
065
066    // Manifest and switch list comments
067    protected boolean _printCommentManifest = true;
068    protected boolean _printCommentSwitchList = false;
069    protected String _commentPickup = NONE;
070    protected String _commentSetout = NONE;
071    protected String _commentBoth = NONE;
072
073    // road options
074    protected String _roadOption = ALL_ROADS; // controls car roads
075    protected List<String> _roadList = new ArrayList<>();
076
077    // load options
078    protected String _loadOption = ALL_LOADS; // receive track load restrictions
079    protected List<String> _loadList = new ArrayList<>();
080    protected String _shipLoadOption = ALL_LOADS;// ship track load restrictions
081    protected List<String> _shipLoadList = new ArrayList<>();
082
083    // destinations that this track will service
084    protected String _destinationOption = ALL_DESTINATIONS;
085    protected List<String> _destinationIdList = new ArrayList<>();
086
087    // schedule options
088    protected String _scheduleName = NONE; // Schedule name if there's one
089    protected String _scheduleId = NONE; // Schedule id if there's one
090    protected String _scheduleItemId = NONE; // the current scheduled item id
091    protected int _scheduleCount = 0; // item count
092    protected int _reservedEnRoute = 0; // length of cars en-route to this track
093    protected int _reservationFactor = 100; // percentage of track space for
094                                            // cars en-route
095    protected int _mode = MATCH; // default is match mode
096    protected boolean _holdCustomLoads = false; // hold cars with custom loads
097
098    // drop options
099    protected String _dropOption = ANY; // controls which route or train can set
100                                        // out cars
101    protected String _pickupOption = ANY; // controls which route or train can
102                                          // pick up cars
103    public static final String ANY = "Any"; // track accepts any train or route
104    public static final String TRAINS = "trains"; // track accepts trains
105    public static final String ROUTES = "routes"; // track accepts routes
106    public static final String EXCLUDE_TRAINS = "excludeTrains";
107    public static final String EXCLUDE_ROUTES = "excludeRoutes";
108    protected List<String> _dropList = new ArrayList<>();
109    protected List<String> _pickupList = new ArrayList<>();
110
111    // load options for staging
112    protected int _loadOptions = 0;
113    private static final int SWAP_GENERIC_LOADS = 1;
114    private static final int EMPTY_CUSTOM_LOADS = 2;
115    private static final int GENERATE_CUSTOM_LOADS = 4;
116    private static final int GENERATE_CUSTOM_LOADS_ANY_SPUR = 8;
117    private static final int EMPTY_GENERIC_LOADS = 16;
118    private static final int GENERATE_CUSTOM_LOADS_ANY_STAGING_TRACK = 32;
119
120    // load option for spur
121    private static final int DISABLE_LOAD_CHANGE = 64;
122
123    // block options
124    protected int _blockOptions = 0;
125    private static final int BLOCK_CARS = 1;
126
127    // order cars are serviced
128    protected String _order = NORMAL;
129    public static final String NORMAL = Bundle.getMessage("Normal");
130    public static final String FIFO = Bundle.getMessage("FIFO");
131    public static final String LIFO = Bundle.getMessage("LIFO");
132
133    // the four types of tracks
134    public static final String STAGING = "Staging";
135    public static final String INTERCHANGE = "Interchange";
136    public static final String YARD = "Yard";
137    // note that code before 2020 (4.21.1) used Siding as the spur type
138    public static final String SPUR = "Spur"; 
139    private static final String SIDING = "Siding"; // For loading older files
140
141    // train directions serviced by this track
142    public static final int EAST = 1;
143    public static final int WEST = 2;
144    public static final int NORTH = 4;
145    public static final int SOUTH = 8;
146
147    // how roads are serviced by this track
148    public static final String ALL_ROADS = Bundle.getMessage("All"); 
149    // track accepts only certain roads
150    public static final String INCLUDE_ROADS = Bundle.getMessage("Include");
151    // track excludes certain roads
152    public static final String EXCLUDE_ROADS = Bundle.getMessage("Exclude");
153
154    // load options
155    public static final String ALL_LOADS = Bundle.getMessage("All"); 
156    public static final String INCLUDE_LOADS = Bundle.getMessage("Include");
157    public static final String EXCLUDE_LOADS = Bundle.getMessage("Exclude");
158
159    // destination options
160    public static final String ALL_DESTINATIONS = Bundle.getMessage("All"); 
161    public static final String INCLUDE_DESTINATIONS = Bundle.getMessage("Include");
162    public static final String EXCLUDE_DESTINATIONS = Bundle.getMessage("Exclude");
163    // when true only cars with final destinations are allowed to use track
164    protected boolean _onlyCarsWithFD = false;
165
166    // schedule modes
167    public static final int SEQUENTIAL = 0;
168    public static final int MATCH = 1;
169
170    // pickup status
171    public static final String PICKUP_OKAY = "";
172
173    // pool
174    protected Pool _pool = null;
175    protected int _minimumLength = 0;
176
177    // return status when checking rolling stock
178    public static final String OKAY = Bundle.getMessage("okay");
179    public static final String LENGTH = Bundle.getMessage("rollingStock") +
180            " " +
181            Bundle.getMessage("Length").toLowerCase(); // lower case in report
182    public static final String TYPE = Bundle.getMessage("type");
183    public static final String ROAD = Bundle.getMessage("road");
184    public static final String LOAD = Bundle.getMessage("load");
185    public static final String CAPACITY = Bundle.getMessage("track") + " " + Bundle.getMessage("capacity");
186    public static final String SCHEDULE = Bundle.getMessage("schedule");
187    public static final String CUSTOM = Bundle.getMessage("custom");
188    public static final String DESTINATION = Bundle.getMessage("carDestination");
189    public static final String NO_FINAL_DESTINATION = Bundle.getMessage("noFinalDestination");
190
191    // For property change
192    public static final String TYPES_CHANGED_PROPERTY = "trackRollingStockTypes"; // NOI18N
193    public static final String ROADS_CHANGED_PROPERTY = "trackRoads"; // NOI18N
194    public static final String NAME_CHANGED_PROPERTY = "trackName"; // NOI18N
195    public static final String LENGTH_CHANGED_PROPERTY = "trackLength"; // NOI18N
196    public static final String MIN_LENGTH_CHANGED_PROPERTY = "trackMinLength"; // NOI18N
197    public static final String SCHEDULE_CHANGED_PROPERTY = "trackScheduleChange"; // NOI18N
198    public static final String DISPOSE_CHANGED_PROPERTY = "trackDispose"; // NOI18N
199    public static final String TRAIN_DIRECTION_CHANGED_PROPERTY = "trackTrainDirection"; // NOI18N
200    public static final String DROP_CHANGED_PROPERTY = "trackDrop"; // NOI18N
201    public static final String PICKUP_CHANGED_PROPERTY = "trackPickup"; // NOI18N
202    public static final String TRACK_TYPE_CHANGED_PROPERTY = "trackType"; // NOI18N
203    public static final String LOADS_CHANGED_PROPERTY = "trackLoads"; // NOI18N
204    public static final String POOL_CHANGED_PROPERTY = "trackPool"; // NOI18N
205    public static final String PLANNED_PICKUPS_CHANGED_PROPERTY = "plannedPickUps"; // NOI18N
206    public static final String LOAD_OPTIONS_CHANGED_PROPERTY = "trackLoadOptions"; // NOI18N
207    public static final String DESTINATIONS_CHANGED_PROPERTY = "trackDestinations"; // NOI18N
208    public static final String DESTINATION_OPTIONS_CHANGED_PROPERTY = "trackDestinationOptions"; // NOI18N
209    public static final String SCHEDULE_MODE_CHANGED_PROPERTY = "trackScheduleMode"; // NOI18N
210    public static final String SCHEDULE_ID_CHANGED_PROPERTY = "trackScheduleId"; // NOI18N
211    public static final String SERVICE_ORDER_CHANGED_PROPERTY = "trackServiceOrder"; // NOI18N
212    public static final String ALTERNATE_TRACK_CHANGED_PROPERTY = "trackAlternate"; // NOI18N
213    public static final String TRACK_BLOCKING_ORDER_CHANGED_PROPERTY = "trackBlockingOrder"; // NOI18N
214    public static final String TRACK_REPORTER_CHANGED_PROPERTY = "trackReporterChange"; // NOI18N
215    public static final String ROUTED_CHANGED_PROPERTY = "onlyCarsWithFinalDestinations"; // NOI18N
216    public static final String HOLD_CARS_CHANGED_PROPERTY = "trackHoldCarsWithCustomLoads"; // NOI18N
217    public static final String TRACK_COMMENT_CHANGED_PROPERTY = "trackComments"; // NOI18N
218
219    // IdTag reader associated with this track.
220    protected Reporter _reader = null;
221
222    public Track(String id, String name, String type, Location location) {
223        log.debug("New ({}) track ({}) id: {}", type, name, id);
224        _location = location;
225        _trackType = type;
226        _name = name;
227        _id = id;
228        // a new track accepts all types
229        setTypeNames(InstanceManager.getDefault(CarTypes.class).getNames());
230        setTypeNames(InstanceManager.getDefault(EngineTypes.class).getNames());
231    }
232
233    /**
234     * Creates a copy of this track.
235     *
236     * @param newName     The name of the new track.
237     * @param newLocation The location of the new track.
238     * @return Track
239     */
240    public Track copyTrack(String newName, Location newLocation) {
241        Track newTrack = newLocation.addTrack(newName, getTrackType());
242        newTrack.clearTypeNames(); // all types are accepted by a new track
243
244        newTrack.setAddCustomLoadsAnySpurEnabled(isAddCustomLoadsAnySpurEnabled());
245        newTrack.setAddCustomLoadsAnyStagingTrackEnabled(isAddCustomLoadsAnyStagingTrackEnabled());
246        newTrack.setAddCustomLoadsEnabled(isAddCustomLoadsEnabled());
247
248        newTrack.setAlternateTrack(getAlternateTrack());
249        newTrack.setBlockCarsEnabled(isBlockCarsEnabled());
250        newTrack.setComment(getComment());
251        newTrack.setCommentBoth(getCommentBothWithColor());
252        newTrack.setCommentPickup(getCommentPickupWithColor());
253        newTrack.setCommentSetout(getCommentSetoutWithColor());
254
255        newTrack.setDestinationOption(getDestinationOption());
256        newTrack.setDestinationIds(getDestinationIds());
257
258        // must set option before setting ids
259        newTrack.setDropOption(getDropOption()); 
260        newTrack.setDropIds(getDropIds());
261
262        newTrack.setIgnoreUsedLengthPercentage(getIgnoreUsedLengthPercentage());
263        newTrack.setLength(getLength());
264        newTrack.setLoadEmptyEnabled(isLoadEmptyEnabled());
265        newTrack.setLoadNames(getLoadNames());
266        newTrack.setLoadOption(getLoadOption());
267        newTrack.setLoadSwapEnabled(isLoadSwapEnabled());
268
269        newTrack.setOnlyCarsWithFinalDestinationEnabled(isOnlyCarsWithFinalDestinationEnabled());
270
271        // must set option before setting ids
272        newTrack.setPickupOption(getPickupOption());
273        newTrack.setPickupIds(getPickupIds());
274
275        // track pools are only shared within a specific location
276        if (getPool() != null) {
277            newTrack.setPool(newLocation.addPool(getPool().getName()));
278            newTrack.setMinimumLength(getMinimumLength());
279        }
280
281        newTrack.setPrintManifestCommentEnabled(isPrintManifestCommentEnabled());
282        newTrack.setPrintSwitchListCommentEnabled(isPrintSwitchListCommentEnabled());
283
284        newTrack.setRemoveCustomLoadsEnabled(isRemoveCustomLoadsEnabled());
285        newTrack.setReservationFactor(getReservationFactor());
286        newTrack.setRoadNames(getRoadNames());
287        newTrack.setRoadOption(getRoadOption());
288        newTrack.setSchedule(getSchedule());
289        newTrack.setScheduleMode(getScheduleMode());
290        newTrack.setServiceOrder(getServiceOrder());
291        newTrack.setShipLoadNames(getShipLoadNames());
292        newTrack.setShipLoadOption(getShipLoadOption());
293        newTrack.setTrainDirections(getTrainDirections());
294        newTrack.setTypeNames(getTypeNames());
295        return newTrack;
296    }
297
298    // for combo boxes
299    @Override
300    public String toString() {
301        return _name;
302    }
303
304    public String getId() {
305        return _id;
306    }
307
308    public Location getLocation() {
309        return _location;
310    }
311
312    public void setName(String name) {
313        String old = _name;
314        _name = name;
315        if (!old.equals(name)) {
316            // recalculate max track name length
317            InstanceManager.getDefault(LocationManager.class).resetNameLengths();
318            setDirtyAndFirePropertyChange(NAME_CHANGED_PROPERTY, old, name);
319        }
320    }
321
322    public String getName() {
323        return _name;
324    }
325    
326    public String getSplitName() {
327        return TrainCommon.splitString(getName());
328    }
329
330    public Division getDivision() {
331        return getLocation().getDivision();
332    }
333
334    public String getDivisionName() {
335        return getLocation().getDivisionName();
336    }
337
338    public boolean isSpur() {
339        return getTrackType().equals(Track.SPUR);
340    }
341
342    public boolean isYard() {
343        return getTrackType().equals(Track.YARD);
344    }
345
346    public boolean isInterchange() {
347        return getTrackType().equals(Track.INTERCHANGE);
348    }
349
350    public boolean isStaging() {
351        return getTrackType().equals(Track.STAGING);
352    }
353
354    public boolean hasMessages() {
355        if (!getCommentBoth().isBlank() ||
356                !getCommentPickup().isBlank() ||
357                !getCommentSetout().isBlank()) {
358            return true;
359        }
360        return false;
361    }
362
363    /**
364     * Gets the track type
365     *
366     * @return Track.SPUR Track.YARD Track.INTERCHANGE or Track.STAGING
367     */
368    public String getTrackType() {
369        return _trackType;
370    }
371
372    /**
373     * Sets the track type, spur, interchange, yard, staging
374     *
375     * @param type Track.SPUR Track.YARD Track.INTERCHANGE Track.STAGING
376     */
377    public void setTrackType(String type) {
378        String old = _trackType;
379        _trackType = type;
380        if (!old.equals(type)) {
381            setDirtyAndFirePropertyChange(TRACK_TYPE_CHANGED_PROPERTY, old, type);
382        }
383    }
384
385    public String getTrackTypeName() {
386        return (getTrackTypeName(getTrackType()));
387    }
388
389    public static String getTrackTypeName(String trackType) {
390        if (trackType.equals(Track.SPUR)) {
391            return Bundle.getMessage("Spur").toLowerCase();
392        }
393        if (trackType.equals(Track.YARD)) {
394            return Bundle.getMessage("Yard").toLowerCase();
395        }
396        if (trackType.equals(Track.INTERCHANGE)) {
397            return Bundle.getMessage("Class/Interchange"); // abbreviation
398        }
399        if (trackType.equals(Track.STAGING)) {
400            return Bundle.getMessage("Staging").toLowerCase();
401        }
402        return ("unknown"); // NOI18N
403    }
404
405    public void setLength(int length) {
406        int old = _length;
407        _length = length;
408        if (old != length) {
409            setDirtyAndFirePropertyChange(LENGTH_CHANGED_PROPERTY, Integer.toString(old), Integer.toString(length));
410        }
411    }
412
413    public int getLength() {
414        return _length;
415    }
416
417    /**
418     * Sets the minimum length of this track when the track is in a pool.
419     *
420     * @param length minimum
421     */
422    public void setMinimumLength(int length) {
423        int old = _minimumLength;
424        _minimumLength = length;
425        if (old != length) {
426            setDirtyAndFirePropertyChange(MIN_LENGTH_CHANGED_PROPERTY, Integer.toString(old), Integer.toString(length));
427        }
428    }
429
430    public int getMinimumLength() {
431        return _minimumLength;
432    }
433
434    public void setReserved(int reserved) {
435        int old = _reserved;
436        _reserved = reserved;
437        if (old != reserved) {
438            setDirtyAndFirePropertyChange("trackReserved", Integer.toString(old), // NOI18N
439                    Integer.toString(reserved)); // NOI18N
440        }
441    }
442
443    public int getReserved() {
444        return _reserved;
445    }
446
447    public void addReservedInRoute(Car car) {
448        int old = _reservedEnRoute;
449        _numberCarsEnRoute++;
450        _reservedEnRoute = old + car.getTotalLength();
451        if (old != _reservedEnRoute) {
452            setDirtyAndFirePropertyChange("trackAddReservedInRoute", Integer.toString(old), // NOI18N
453                    Integer.toString(_reservedEnRoute)); // NOI18N
454        }
455    }
456
457    public void deleteReservedInRoute(Car car) {
458        int old = _reservedEnRoute;
459        _numberCarsEnRoute--;
460        _reservedEnRoute = old - car.getTotalLength();
461        if (old != _reservedEnRoute) {
462            setDirtyAndFirePropertyChange("trackDeleteReservedInRoute", Integer.toString(old), // NOI18N
463                    Integer.toString(_reservedEnRoute)); // NOI18N
464        }
465    }
466
467    /**
468     * Used to determine how much track space is going to be consumed by cars in
469     * route to this track. See isSpaceAvailable().
470     *
471     * @return The length of all cars en route to this track including couplers.
472     */
473    public int getReservedInRoute() {
474        return _reservedEnRoute;
475    }
476
477    public int getNumberOfCarsInRoute() {
478        return _numberCarsEnRoute;
479    }
480
481    /**
482     * Set the reservation factor. Default 100 (100%). Used by the program when
483     * generating car loads from staging. A factor of 100% allows the program to
484     * fill a track with car loads. Numbers over 100% can overload a track.
485     *
486     * @param factor A number from 0 to 10000.
487     */
488    public void setReservationFactor(int factor) {
489        int old = _reservationFactor;
490        _reservationFactor = factor;
491        if (old != factor) {
492            setDirtyAndFirePropertyChange("trackReservationFactor", old, factor); // NOI18N
493        }
494    }
495
496    public int getReservationFactor() {
497        return _reservationFactor;
498    }
499
500    /**
501     * Sets the mode of operation for the schedule assigned to this track.
502     *
503     * @param mode Track.SEQUENTIAL or Track.MATCH
504     */
505    public void setScheduleMode(int mode) {
506        int old = _mode;
507        _mode = mode;
508        if (old != mode) {
509            setDirtyAndFirePropertyChange(SCHEDULE_MODE_CHANGED_PROPERTY, old, mode); // NOI18N
510        }
511    }
512
513    /**
514     * Gets the mode of operation for the schedule assigned to this track.
515     *
516     * @return Mode of operation: Track.SEQUENTIAL or Track.MATCH
517     */
518    public int getScheduleMode() {
519        return _mode;
520    }
521
522    public String getScheduleModeName() {
523        if (getScheduleMode() == Track.MATCH) {
524            return Bundle.getMessage("Match");
525        }
526        return Bundle.getMessage("Sequential");
527    }
528
529    public void setAlternateTrack(Track track) {
530        Track oldTrack = _location.getTrackById(_alternateTrackId);
531        String old = _alternateTrackId;
532        if (track != null) {
533            _alternateTrackId = track.getId();
534        } else {
535            _alternateTrackId = NONE;
536        }
537        if (!old.equals(_alternateTrackId)) {
538            setDirtyAndFirePropertyChange(ALTERNATE_TRACK_CHANGED_PROPERTY, oldTrack, track);
539        }
540    }
541
542    /**
543     * Returns the alternate track for a spur
544     * 
545     * @return alternate track
546     */
547    public Track getAlternateTrack() {
548        if (!isSpur()) {
549            return null;
550        }
551        return _location.getTrackById(_alternateTrackId);
552    }
553
554    public void setHoldCarsWithCustomLoadsEnabled(boolean enable) {
555        boolean old = _holdCustomLoads;
556        _holdCustomLoads = enable;
557        setDirtyAndFirePropertyChange(HOLD_CARS_CHANGED_PROPERTY, old, enable);
558    }
559
560    /**
561     * If enabled (true), hold cars with custom loads rather than allowing them
562     * to go to staging if the spur and the alternate track were full. If
563     * disabled, cars with custom loads can be forwarded to staging when this
564     * spur and all others with this option are also false.
565     * 
566     * @return True if enabled
567     */
568    public boolean isHoldCarsWithCustomLoadsEnabled() {
569        return _holdCustomLoads;
570    }
571
572    /**
573     * Used to determine if there's space available at this track for the car.
574     * Considers cars en-route to this track. Used to prevent overloading the
575     * track.
576     *
577     * @param car The car to be set out.
578     * @return true if space available.
579     */
580    public boolean isSpaceAvailable(Car car) {
581        int carLength = car.getTotalKernelLength();
582        int trackLength = getLength();
583        // is the car or kernel too long for the track?
584        if (trackLength < carLength && getPool() == null) {
585            return false;
586        }
587        // is track part of a pool?
588        if (getPool() != null && getPool().getMaxLengthTrack(this) < carLength) {
589            return false;
590        }
591        // ignore reservation factor unless car is departing staging
592        if (car.getTrack() != null && car.getTrack().isStaging()) {
593            return (getLength() * getReservationFactor() / 100 - (getReservedInRoute() + carLength) >= 0);
594        }
595        // if there's alternate, include that length in the calculation
596        if (getAlternateTrack() != null) {
597            trackLength = trackLength + getAlternateTrack().getLength();
598        }
599        return (trackLength - (getReservedInRoute() + carLength) >= 0);
600    }
601
602    public void setUsedLength(int length) {
603        int old = _usedLength;
604        _usedLength = length;
605        if (old != length) {
606            setDirtyAndFirePropertyChange("trackUsedLength", Integer.toString(old), // NOI18N
607                    Integer.toString(length));
608        }
609    }
610
611    public int getUsedLength() {
612        return _usedLength;
613    }
614
615    /**
616     * The amount of consumed track space to be ignored when sending new rolling
617     * stock to the track. See Planned Pickups in help.
618     *
619     * @param percentage a number between 0 and 100
620     */
621    public void setIgnoreUsedLengthPercentage(int percentage) {
622        int old = _ignoreUsedLengthPercentage;
623        _ignoreUsedLengthPercentage = percentage;
624        if (old != percentage) {
625            setDirtyAndFirePropertyChange(PLANNED_PICKUPS_CHANGED_PROPERTY, Integer.toString(old),
626                    Integer.toString(percentage));
627        }
628    }
629
630    public int getIgnoreUsedLengthPercentage() {
631        return _ignoreUsedLengthPercentage;
632    }
633
634    /**
635     * Sets the number of rolling stock (cars and or engines) on this track
636     */
637    private void setNumberRS(int number) {
638        int old = _numberRS;
639        _numberRS = number;
640        if (old != number) {
641            setDirtyAndFirePropertyChange("trackNumberRS", Integer.toString(old), // NOI18N
642                    Integer.toString(number)); // NOI18N
643        }
644    }
645
646    /**
647     * Sets the number of cars on this track
648     */
649    private void setNumberCars(int number) {
650        int old = _numberCars;
651        _numberCars = number;
652        if (old != number) {
653            setDirtyAndFirePropertyChange("trackNumberCars", Integer.toString(old), // NOI18N
654                    Integer.toString(number));
655        }
656    }
657
658    /**
659     * Sets the number of engines on this track
660     */
661    private void setNumberEngines(int number) {
662        int old = _numberEngines;
663        _numberEngines = number;
664        if (old != number) {
665            setDirtyAndFirePropertyChange("trackNumberEngines", Integer.toString(old), // NOI18N
666                    Integer.toString(number));
667        }
668    }
669
670    /**
671     * @return The number of rolling stock (cars and engines) on this track
672     */
673    public int getNumberRS() {
674        return _numberRS;
675    }
676
677    /**
678     * @return The number of cars on this track
679     */
680    public int getNumberCars() {
681        return _numberCars;
682    }
683
684    /**
685     * @return The number of engines on this track
686     */
687    public int getNumberEngines() {
688        return _numberEngines;
689    }
690
691    /**
692     * Adds rolling stock to a specific track.
693     * 
694     * @param rs The rolling stock to place on the track.
695     */
696    public void addRS(RollingStock rs) {
697        setNumberRS(getNumberRS() + 1);
698        if (rs.getClass() == Car.class) {
699            setNumberCars(getNumberCars() + 1);
700        } else if (rs.getClass() == Engine.class) {
701            setNumberEngines(getNumberEngines() + 1);
702        }
703        setUsedLength(getUsedLength() + rs.getTotalLength());
704    }
705
706    public void deleteRS(RollingStock rs) {
707        setNumberRS(getNumberRS() - 1);
708        if (rs.getClass() == Car.class) {
709            setNumberCars(getNumberCars() - 1);
710        } else if (rs.getClass() == Engine.class) {
711            setNumberEngines(getNumberEngines() - 1);
712        }
713        setUsedLength(getUsedLength() - rs.getTotalLength());
714    }
715
716    /**
717     * Increments the number of cars and or engines that will be picked up by a
718     * train from this track.
719     * 
720     * @param rs The rolling stock.
721     */
722    public void addPickupRS(RollingStock rs) {
723        int old = _pickupRS;
724        _pickupRS++;
725        if (Setup.isBuildAggressive()) {
726            setReserved(getReserved() - rs.getTotalLength());
727        }
728        setDirtyAndFirePropertyChange("trackPickupRS", Integer.toString(old), // NOI18N
729                Integer.toString(_pickupRS));
730    }
731
732    public void deletePickupRS(RollingStock rs) {
733        int old = _pickupRS;
734        if (Setup.isBuildAggressive()) {
735            setReserved(getReserved() + rs.getTotalLength());
736        }
737        _pickupRS--;
738        setDirtyAndFirePropertyChange("trackDeletePickupRS", Integer.toString(old), // NOI18N
739                Integer.toString(_pickupRS));
740    }
741
742    /**
743     * @return the number of rolling stock (cars and or locos) that are
744     *         scheduled for pick up from this track.
745     */
746    public int getPickupRS() {
747        return _pickupRS;
748    }
749
750    public int getDropRS() {
751        return _dropRS;
752    }
753
754    public void addDropRS(RollingStock rs) {
755        int old = _dropRS;
756        _dropRS++;
757        bumpMoves();
758        setReserved(getReserved() + rs.getTotalLength());
759        _reservedLengthDrops = _reservedLengthDrops + rs.getTotalLength();
760        setDirtyAndFirePropertyChange("trackAddDropRS", Integer.toString(old), Integer.toString(_dropRS)); // NOI18N
761    }
762
763    public void deleteDropRS(RollingStock rs) {
764        int old = _dropRS;
765        _dropRS--;
766        setReserved(getReserved() - rs.getTotalLength());
767        _reservedLengthDrops = _reservedLengthDrops - rs.getTotalLength();
768        setDirtyAndFirePropertyChange("trackDeleteDropRS", Integer.toString(old), // NOI18N
769                Integer.toString(_dropRS));
770    }
771
772    public void setComment(String comment) {
773        String old = _comment;
774        _comment = comment;
775        if (!old.equals(comment)) {
776            setDirtyAndFirePropertyChange("trackComment", old, comment); // NOI18N
777        }
778    }
779
780    public String getComment() {
781        return _comment;
782    }
783
784    public void setCommentPickup(String comment) {
785        String old = _commentPickup;
786        _commentPickup = comment;
787        if (!old.equals(comment)) {
788            setDirtyAndFirePropertyChange(TRACK_COMMENT_CHANGED_PROPERTY, old, comment); // NOI18N
789        }
790    }
791
792    public String getCommentPickup() {
793        return TrainCommon.getTextColorString(getCommentPickupWithColor());
794    }
795
796    public String getCommentPickupWithColor() {
797        return _commentPickup;
798    }
799
800    public void setCommentSetout(String comment) {
801        String old = _commentSetout;
802        _commentSetout = comment;
803        if (!old.equals(comment)) {
804            setDirtyAndFirePropertyChange(TRACK_COMMENT_CHANGED_PROPERTY, old, comment); // NOI18N
805        }
806    }
807
808    public String getCommentSetout() {
809        return TrainCommon.getTextColorString(getCommentSetoutWithColor());
810    }
811
812    public String getCommentSetoutWithColor() {
813        return _commentSetout;
814    }
815
816    public void setCommentBoth(String comment) {
817        String old = _commentBoth;
818        _commentBoth = comment;
819        if (!old.equals(comment)) {
820            setDirtyAndFirePropertyChange(TRACK_COMMENT_CHANGED_PROPERTY, old, comment); // NOI18N
821        }
822    }
823
824    public String getCommentBoth() {
825        return TrainCommon.getTextColorString(getCommentBothWithColor());
826    }
827
828    public String getCommentBothWithColor() {
829        return _commentBoth;
830    }
831
832    public boolean isPrintManifestCommentEnabled() {
833        return _printCommentManifest;
834    }
835
836    public void setPrintManifestCommentEnabled(boolean enable) {
837        boolean old = isPrintManifestCommentEnabled();
838        _printCommentManifest = enable;
839        setDirtyAndFirePropertyChange("trackPrintManifestComment", old, enable);
840    }
841
842    public boolean isPrintSwitchListCommentEnabled() {
843        return _printCommentSwitchList;
844    }
845
846    public void setPrintSwitchListCommentEnabled(boolean enable) {
847        boolean old = isPrintSwitchListCommentEnabled();
848        _printCommentSwitchList = enable;
849        setDirtyAndFirePropertyChange("trackPrintSwitchListComment", old, enable);
850    }
851
852    /**
853     * Returns all of the rolling stock type names serviced by this track.
854     *
855     * @return rolling stock type names
856     */
857    public String[] getTypeNames() {
858        List<String> list = new ArrayList<>();
859        for (String typeName : _typeList) {
860            if (_location.acceptsTypeName(typeName)) {
861                list.add(typeName);
862            }
863        }
864        return list.toArray(new String[0]);
865    }
866
867    private void setTypeNames(String[] types) {
868        if (types.length > 0) {
869            Arrays.sort(types);
870            for (String type : types) {
871                if (!_typeList.contains(type)) {
872                    _typeList.add(type);
873                }
874            }
875        }
876    }
877
878    private void clearTypeNames() {
879        _typeList.clear();
880    }
881
882    public void addTypeName(String type) {
883        // insert at start of list, sort later
884        if (type == null || _typeList.contains(type)) {
885            return;
886        }
887        _typeList.add(0, type);
888        log.debug("Track ({}) add rolling stock type ({})", getName(), type);
889        setDirtyAndFirePropertyChange(TYPES_CHANGED_PROPERTY, _typeList.size() - 1, _typeList.size());
890    }
891
892    public void deleteTypeName(String type) {
893        if (_typeList.remove(type)) {
894            log.debug("Track ({}) delete rolling stock type ({})", getName(), type);
895            setDirtyAndFirePropertyChange(TYPES_CHANGED_PROPERTY, _typeList.size() + 1, _typeList.size());
896        }
897    }
898
899    public boolean isTypeNameAccepted(String type) {
900        if (!_location.acceptsTypeName(type)) {
901            return false;
902        }
903        return _typeList.contains(type);
904    }
905
906    /**
907     * Sets the train directions that can service this track
908     *
909     * @param direction EAST, WEST, NORTH, SOUTH
910     */
911    public void setTrainDirections(int direction) {
912        int old = _trainDir;
913        _trainDir = direction;
914        if (old != direction) {
915            setDirtyAndFirePropertyChange(TRAIN_DIRECTION_CHANGED_PROPERTY, Integer.toString(old),
916                    Integer.toString(direction));
917        }
918    }
919
920    public int getTrainDirections() {
921        return _trainDir;
922    }
923
924    public String getRoadOption() {
925        return _roadOption;
926    }
927
928    public String getRoadOptionString() {
929        String s;
930        if (getRoadOption().equals(Track.INCLUDE_ROADS)) {
931            s = Bundle.getMessage("AcceptOnly") + " " + getRoadNames().length + " " + Bundle.getMessage("Roads");
932        } else if (getRoadOption().equals(Track.EXCLUDE_ROADS)) {
933            s = Bundle.getMessage("Exclude") + " " + getRoadNames().length + " " + Bundle.getMessage("Roads");
934        } else {
935            s = Bundle.getMessage("AcceptsAllRoads");
936        }
937        return s;
938    }
939
940    /**
941     * Set the road option for this track.
942     *
943     * @param option ALLROADS, INCLUDEROADS, or EXCLUDEROADS
944     */
945    public void setRoadOption(String option) {
946        String old = _roadOption;
947        _roadOption = option;
948        setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, old, option);
949    }
950
951    public String[] getRoadNames() {
952        String[] roads = _roadList.toArray(new String[0]);
953        if (_roadList.size() > 0) {
954            Arrays.sort(roads);
955        }
956        return roads;
957    }
958
959    private void setRoadNames(String[] roads) {
960        if (roads.length > 0) {
961            Arrays.sort(roads);
962            for (String roadName : roads) {
963                if (!roadName.equals(NONE)) {
964                    _roadList.add(roadName);
965                }
966            }
967        }
968    }
969
970    public void addRoadName(String road) {
971        if (!_roadList.contains(road)) {
972            _roadList.add(road);
973            log.debug("Track ({}) add car road ({})", getName(), road);
974            setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _roadList.size() - 1, _roadList.size());
975        }
976    }
977
978    public void deleteRoadName(String road) {
979        if (_roadList.remove(road)) {
980            log.debug("Track ({}) delete car road ({})", getName(), road);
981            setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _roadList.size() + 1, _roadList.size());
982        }
983    }
984
985    public boolean isRoadNameAccepted(String road) {
986        if (_roadOption.equals(ALL_ROADS)) {
987            return true;
988        }
989        if (_roadOption.equals(INCLUDE_ROADS)) {
990            return _roadList.contains(road);
991        }
992        // exclude!
993        return !_roadList.contains(road);
994    }
995
996    public boolean containsRoadName(String road) {
997        return _roadList.contains(road);
998    }
999
1000    /**
1001     * Gets the car receive load option for this track.
1002     *
1003     * @return ALL_LOADS INCLUDE_LOADS EXCLUDE_LOADS
1004     */
1005    public String getLoadOption() {
1006        return _loadOption;
1007    }
1008
1009    public String getLoadOptionString() {
1010        String s;
1011        if (getLoadOption().equals(Track.INCLUDE_LOADS)) {
1012            s = Bundle.getMessage("AcceptOnly") + " " + getLoadNames().length + " " + Bundle.getMessage("Loads");
1013        } else if (getLoadOption().equals(Track.EXCLUDE_LOADS)) {
1014            s = Bundle.getMessage("Exclude") + " " + getLoadNames().length + " " + Bundle.getMessage("Loads");
1015        } else {
1016            s = Bundle.getMessage("AcceptsAllLoads");
1017        }
1018        return s;
1019    }
1020
1021    /**
1022     * Set how this track deals with receiving car loads
1023     *
1024     * @param option ALL_LOADS INCLUDE_LOADS EXCLUDE_LOADS
1025     */
1026    public void setLoadOption(String option) {
1027        String old = _loadOption;
1028        _loadOption = option;
1029        setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, old, option);
1030    }
1031
1032    private void setLoadNames(String[] loads) {
1033        if (loads.length > 0) {
1034            Arrays.sort(loads);
1035            for (String loadName : loads) {
1036                if (!loadName.equals(NONE)) {
1037                    _loadList.add(loadName);
1038                }
1039            }
1040        }
1041    }
1042
1043    /**
1044     * Provides a list of receive loads that the track will either service or
1045     * exclude. See setLoadOption
1046     *
1047     * @return Array of load names as Strings
1048     */
1049    public String[] getLoadNames() {
1050        String[] loads = _loadList.toArray(new String[0]);
1051        if (_loadList.size() > 0) {
1052            Arrays.sort(loads);
1053        }
1054        return loads;
1055    }
1056
1057    /**
1058     * Add a receive load that the track will either service or exclude. See
1059     * setLoadOption
1060     * 
1061     * @param load The string load name.
1062     */
1063    public void addLoadName(String load) {
1064        if (!_loadList.contains(load)) {
1065            _loadList.add(load);
1066            log.debug("track ({}) add car load ({})", getName(), load);
1067            setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, _loadList.size() - 1, _loadList.size());
1068        }
1069    }
1070
1071    /**
1072     * Delete a receive load name that the track will either service or exclude.
1073     * See setLoadOption
1074     * 
1075     * @param load The string load name.
1076     */
1077    public void deleteLoadName(String load) {
1078        if (_loadList.remove(load)) {
1079            log.debug("track ({}) delete car load ({})", getName(), load);
1080            setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, _loadList.size() + 1, _loadList.size());
1081        }
1082    }
1083
1084    /**
1085     * Determine if track will service a specific receive load name.
1086     *
1087     * @param load the load name to check.
1088     * @return true if track will service this load.
1089     */
1090    public boolean isLoadNameAccepted(String load) {
1091        if (_loadOption.equals(ALL_LOADS)) {
1092            return true;
1093        }
1094        if (_loadOption.equals(INCLUDE_LOADS)) {
1095            return _loadList.contains(load);
1096        }
1097        // exclude!
1098        return !_loadList.contains(load);
1099    }
1100
1101    /**
1102     * Determine if track will service a specific receive load and car type.
1103     *
1104     * @param load the load name to check.
1105     * @param type the type of car used to carry the load.
1106     * @return true if track will service this load.
1107     */
1108    public boolean isLoadNameAndCarTypeAccepted(String load, String type) {
1109        if (_loadOption.equals(ALL_LOADS)) {
1110            return true;
1111        }
1112        if (_loadOption.equals(INCLUDE_LOADS)) {
1113            return _loadList.contains(load) || _loadList.contains(type + CarLoad.SPLIT_CHAR + load);
1114        }
1115        // exclude!
1116        return !_loadList.contains(load) && !_loadList.contains(type + CarLoad.SPLIT_CHAR + load);
1117    }
1118
1119    /**
1120     * Gets the car ship load option for this track.
1121     *
1122     * @return ALL_LOADS INCLUDE_LOADS EXCLUDE_LOADS
1123     */
1124    public String getShipLoadOption() {
1125        if (!isStaging()) {
1126            return ALL_LOADS;
1127        }
1128        return _shipLoadOption;
1129    }
1130
1131    public String getShipLoadOptionString() {
1132        String s;
1133        if (getShipLoadOption().equals(Track.INCLUDE_LOADS)) {
1134            s = Bundle.getMessage("ShipOnly") + " " + getShipLoadNames().length + " " + Bundle.getMessage("Loads");
1135        } else if (getShipLoadOption().equals(Track.EXCLUDE_LOADS)) {
1136            s = Bundle.getMessage("Exclude") + " " + getShipLoadNames().length + " " + Bundle.getMessage("Loads");
1137        } else {
1138            s = Bundle.getMessage("ShipsAllLoads");
1139        }
1140        return s;
1141    }
1142
1143    /**
1144     * Set how this track deals with shipping car loads
1145     *
1146     * @param option ALL_LOADS INCLUDE_LOADS EXCLUDE_LOADS
1147     */
1148    public void setShipLoadOption(String option) {
1149        String old = _shipLoadOption;
1150        _shipLoadOption = option;
1151        setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, old, option);
1152    }
1153
1154    private void setShipLoadNames(String[] loads) {
1155        if (loads.length > 0) {
1156            Arrays.sort(loads);
1157            for (String shipLoadName : loads) {
1158                if (!shipLoadName.equals(NONE)) {
1159                    _shipLoadList.add(shipLoadName);
1160                }
1161            }
1162        }
1163    }
1164
1165    /**
1166     * Provides a list of ship loads that the track will either service or
1167     * exclude. See setShipLoadOption
1168     *
1169     * @return Array of load names as Strings
1170     */
1171    public String[] getShipLoadNames() {
1172        String[] loads = _shipLoadList.toArray(new String[0]);
1173        if (_shipLoadList.size() > 0) {
1174            Arrays.sort(loads);
1175        }
1176        return loads;
1177    }
1178
1179    /**
1180     * Add a ship load that the track will either service or exclude. See
1181     * setShipLoadOption
1182     * 
1183     * @param load The string load name.
1184     */
1185    public void addShipLoadName(String load) {
1186        if (!_shipLoadList.contains(load)) {
1187            _shipLoadList.add(load);
1188            log.debug("track ({}) add car load ({})", getName(), load);
1189            setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, _shipLoadList.size() - 1, _shipLoadList.size());
1190        }
1191    }
1192
1193    /**
1194     * Delete a ship load name that the track will either service or exclude.
1195     * See setLoadOption
1196     * 
1197     * @param load The string load name.
1198     */
1199    public void deleteShipLoadName(String load) {
1200        if (_shipLoadList.remove(load)) {
1201            log.debug("track ({}) delete car load ({})", getName(), load);
1202            setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, _shipLoadList.size() + 1, _shipLoadList.size());
1203        }
1204    }
1205
1206    /**
1207     * Determine if track will service a specific ship load name.
1208     *
1209     * @param load the load name to check.
1210     * @return true if track will service this load.
1211     */
1212    public boolean isLoadNameShipped(String load) {
1213        if (_shipLoadOption.equals(ALL_LOADS)) {
1214            return true;
1215        }
1216        if (_shipLoadOption.equals(INCLUDE_LOADS)) {
1217            return _shipLoadList.contains(load);
1218        }
1219        // exclude!
1220        return !_shipLoadList.contains(load);
1221    }
1222
1223    /**
1224     * Determine if track will service a specific ship load and car type.
1225     *
1226     * @param load the load name to check.
1227     * @param type the type of car used to carry the load.
1228     * @return true if track will service this load.
1229     */
1230    public boolean isLoadNameAndCarTypeShipped(String load, String type) {
1231        if (_shipLoadOption.equals(ALL_LOADS)) {
1232            return true;
1233        }
1234        if (_shipLoadOption.equals(INCLUDE_LOADS)) {
1235            return _shipLoadList.contains(load) || _shipLoadList.contains(type + CarLoad.SPLIT_CHAR + load);
1236        }
1237        // exclude!
1238        return !_shipLoadList.contains(load) && !_shipLoadList.contains(type + CarLoad.SPLIT_CHAR + load);
1239    }
1240
1241    /**
1242     * Gets the drop option for this track. ANY means that all trains and routes
1243     * can drop cars to this track. The other four options are used to restrict
1244     * the track to certain trains or routes.
1245     * 
1246     * @return ANY, TRAINS, ROUTES, EXCLUDE_TRAINS, or EXCLUDE_ROUTES
1247     */
1248    public String getDropOption() {
1249        if (isYard()) {
1250            return ANY;
1251        }
1252        return _dropOption;
1253    }
1254
1255    /**
1256     * Set the car drop option for this track.
1257     *
1258     * @param option ANY, TRAINS, ROUTES, EXCLUDE_TRAINS, or EXCLUDE_ROUTES
1259     */
1260    public void setDropOption(String option) {
1261        String old = _dropOption;
1262        _dropOption = option;
1263        if (!old.equals(option)) {
1264            _dropList.clear();
1265        }
1266        setDirtyAndFirePropertyChange(DROP_CHANGED_PROPERTY, old, option);
1267    }
1268
1269    /**
1270     * Gets the pickup option for this track. ANY means that all trains and
1271     * routes can pull cars from this track. The other four options are used to
1272     * restrict the track to certain trains or routes.
1273     * 
1274     * @return ANY, TRAINS, ROUTES, EXCLUDE_TRAINS, or EXCLUDE_ROUTES
1275     */
1276    public String getPickupOption() {
1277        if (isYard()) {
1278            return ANY;
1279        }
1280        return _pickupOption;
1281    }
1282
1283    /**
1284     * Set the car pick up option for this track.
1285     *
1286     * @param option ANY, TRAINS, ROUTES, EXCLUDE_TRAINS, or EXCLUDE_ROUTES
1287     */
1288    public void setPickupOption(String option) {
1289        String old = _pickupOption;
1290        _pickupOption = option;
1291        if (!old.equals(option)) {
1292            _pickupList.clear();
1293        }
1294        setDirtyAndFirePropertyChange(PICKUP_CHANGED_PROPERTY, old, option);
1295    }
1296
1297    public String[] getDropIds() {
1298        return _dropList.toArray(new String[0]);
1299    }
1300
1301    private void setDropIds(String[] ids) {
1302        for (String id : ids) {
1303            if (id != null) {
1304                _dropList.add(id);
1305            }
1306        }
1307    }
1308
1309    public void addDropId(String id) {
1310        if (!_dropList.contains(id)) {
1311            _dropList.add(id);
1312            log.debug("Track ({}) add drop id: {}", getName(), id);
1313            setDirtyAndFirePropertyChange(DROP_CHANGED_PROPERTY, null, id);
1314        }
1315    }
1316
1317    public void deleteDropId(String id) {
1318        if (_dropList.remove(id)) {
1319            log.debug("Track ({}) delete drop id: {}", getName(), id);
1320            setDirtyAndFirePropertyChange(DROP_CHANGED_PROPERTY, id, null);
1321        }
1322    }
1323
1324    /**
1325     * Determine if train can set out cars to this track. Based on the train's
1326     * id or train's route id. See setDropOption(option).
1327     * 
1328     * @param train The Train to test.
1329     * @return true if the train can set out cars to this track.
1330     */
1331    public boolean isDropTrainAccepted(Train train) {
1332        if (getDropOption().equals(ANY)) {
1333            return true;
1334        }
1335        if (getDropOption().equals(TRAINS)) {
1336            return containsDropId(train.getId());
1337        }
1338        if (getDropOption().equals(EXCLUDE_TRAINS)) {
1339            return !containsDropId(train.getId());
1340        } else if (train.getRoute() == null) {
1341            return false;
1342        }
1343        return isDropRouteAccepted(train.getRoute());
1344    }
1345
1346    public boolean isDropRouteAccepted(Route route) {
1347        if (getDropOption().equals(ANY) || getDropOption().equals(TRAINS) || getDropOption().equals(EXCLUDE_TRAINS)) {
1348            return true;
1349        }
1350        if (getDropOption().equals(EXCLUDE_ROUTES)) {
1351            return !containsDropId(route.getId());
1352        }
1353        return containsDropId(route.getId());
1354    }
1355
1356    public boolean containsDropId(String id) {
1357        return _dropList.contains(id);
1358    }
1359
1360    public String[] getPickupIds() {
1361        return _pickupList.toArray(new String[0]);
1362    }
1363
1364    private void setPickupIds(String[] ids) {
1365        for (String id : ids) {
1366            if (id != null) {
1367                _pickupList.add(id);
1368            }
1369        }
1370    }
1371
1372    /**
1373     * Add train or route id to this track.
1374     * 
1375     * @param id The string id for the train or route.
1376     */
1377    public void addPickupId(String id) {
1378        if (!_pickupList.contains(id)) {
1379            _pickupList.add(id);
1380            log.debug("track ({}) add pick up id {}", getName(), id);
1381            setDirtyAndFirePropertyChange(PICKUP_CHANGED_PROPERTY, null, id);
1382        }
1383    }
1384
1385    public void deletePickupId(String id) {
1386        if (_pickupList.remove(id)) {
1387            log.debug("track ({}) delete pick up id {}", getName(), id);
1388            setDirtyAndFirePropertyChange(PICKUP_CHANGED_PROPERTY, id, null);
1389        }
1390    }
1391
1392    /**
1393     * Determine if train can pick up cars from this track. Based on the train's
1394     * id or train's route id. See setPickupOption(option).
1395     * 
1396     * @param train The Train to test.
1397     * @return true if the train can pick up cars from this track.
1398     */
1399    public boolean isPickupTrainAccepted(Train train) {
1400        if (getPickupOption().equals(ANY)) {
1401            return true;
1402        }
1403        if (getPickupOption().equals(TRAINS)) {
1404            return containsPickupId(train.getId());
1405        }
1406        if (getPickupOption().equals(EXCLUDE_TRAINS)) {
1407            return !containsPickupId(train.getId());
1408        } else if (train.getRoute() == null) {
1409            return false;
1410        }
1411        return isPickupRouteAccepted(train.getRoute());
1412    }
1413
1414    public boolean isPickupRouteAccepted(Route route) {
1415        if (getPickupOption().equals(ANY) ||
1416                getPickupOption().equals(TRAINS) ||
1417                getPickupOption().equals(EXCLUDE_TRAINS)) {
1418            return true;
1419        }
1420        if (getPickupOption().equals(EXCLUDE_ROUTES)) {
1421            return !containsPickupId(route.getId());
1422        }
1423        return containsPickupId(route.getId());
1424    }
1425
1426    public boolean containsPickupId(String id) {
1427        return _pickupList.contains(id);
1428    }
1429
1430    /**
1431     * Checks to see if all car types can be pulled from this track
1432     * 
1433     * @return PICKUP_OKAY if any train can pull all car types from this track
1434     */
1435    public String checkPickups() {
1436        String status = PICKUP_OKAY;
1437        S1: for (String carType : InstanceManager.getDefault(CarTypes.class).getNames()) {
1438            if (!isTypeNameAccepted(carType)) {
1439                continue;
1440            }
1441            for (Train train : InstanceManager.getDefault(TrainManager.class).getTrainsByNameList()) {
1442                if (!train.isTypeNameAccepted(carType) || !isPickupTrainAccepted(train)) {
1443                    continue;
1444                }
1445                // does the train services this location and track?
1446                Route route = train.getRoute();
1447                if (route != null) {
1448                    for (RouteLocation rLoc : route.getLocationsBySequenceList()) {
1449                        if (rLoc.getName().equals(getLocation().getName()) &&
1450                                rLoc.isPickUpAllowed() &&
1451                                rLoc.getMaxCarMoves() > 0 &&
1452                                !train.isLocationSkipped(rLoc) &&
1453                                ((getTrainDirections() & rLoc.getTrainDirection()) != 0 || train.isLocalSwitcher()) &&
1454                                ((getLocation().getTrainDirections() & rLoc.getTrainDirection()) != 0 ||
1455                                        train.isLocalSwitcher())) {
1456
1457                            continue S1; // car type serviced by this train, try
1458                                         // next car type
1459                        }
1460                    }
1461                }
1462            }
1463            // None of the trains servicing this track can pick up car type
1464            // ({0})
1465            status = Bundle.getMessage("ErrorNoTrain", getName(), carType);
1466            break;
1467        }
1468        return status;
1469    }
1470
1471    /**
1472     * Used to determine if track can service the rolling stock.
1473     *
1474     * @param rs the car or loco to be tested
1475     * @return Error string starting with TYPE, ROAD, CAPACITY, LENGTH,
1476     *         DESTINATION or LOAD if there's an issue. OKAY if track can
1477     *         service Rolling Stock.
1478     */
1479    public String isRollingStockAccepted(RollingStock rs) {
1480        // first determine if rolling stock can be move to the new location
1481        // note that there's code that checks for certain issues by checking the
1482        // first word of the status string returned
1483        if (!isTypeNameAccepted(rs.getTypeName())) {
1484            log.debug("Rolling stock ({}) type ({}) not accepted at location ({}, {}) wrong type", rs.toString(),
1485                    rs.getTypeName(), getLocation().getName(), getName()); // NOI18N
1486            return TYPE + " (" + rs.getTypeName() + ")";
1487        }
1488        if (!isRoadNameAccepted(rs.getRoadName())) {
1489            log.debug("Rolling stock ({}) road ({}) not accepted at location ({}, {}) wrong road", rs.toString(),
1490                    rs.getRoadName(), getLocation().getName(), getName()); // NOI18N
1491            return ROAD + " (" + rs.getRoadName() + ")";
1492        }
1493        // now determine if there's enough space for the rolling stock
1494        int length = rs.getTotalLength();
1495        // error check
1496        try {
1497            Integer.parseInt(rs.getLength());
1498        } catch (Exception e) {
1499            return LENGTH + " (" + rs.getLength() + ")";
1500        }
1501
1502        if (Car.class.isInstance(rs)) {
1503            Car car = (Car) rs;
1504            // does this track service the car's final destination?
1505            if (!isDestinationAccepted(car.getFinalDestination())) {
1506                // && getLocation() != car.getFinalDestination()) { // 4/14/2014
1507                // I can't remember why this was needed
1508                return DESTINATION +
1509                        " (" +
1510                        car.getFinalDestinationName() +
1511                        ") " +
1512                        Bundle.getMessage("carIsNotAllowed", getName()); // no
1513            }
1514            // does this track accept cars without a final destination?
1515            if (isOnlyCarsWithFinalDestinationEnabled() &&
1516                    car.getFinalDestination() == null &&
1517                    !car.isCaboose() &&
1518                    !car.hasFred()) {
1519                return NO_FINAL_DESTINATION;
1520            }
1521            // check for car in kernel
1522            if (car.isLead()) {
1523                length = car.getKernel().getTotalLength();
1524            }
1525            if (!isLoadNameAndCarTypeAccepted(car.getLoadName(), car.getTypeName())) {
1526                log.debug("Car ({}) load ({}) not accepted at location ({}, {})", rs.toString(), car.getLoadName(),
1527                        getLocation(), getName()); // NOI18N
1528                return LOAD + " (" + car.getLoadName() + ")";
1529            }
1530        }
1531        // check for loco in consist
1532        if (Engine.class.isInstance(rs)) {
1533            Engine eng = (Engine) rs;
1534            if (eng.isLead()) {
1535                length = eng.getConsist().getTotalLength();
1536            }
1537        }
1538        if (rs.getTrack() != this &&
1539                rs.getDestinationTrack() != this &&
1540                (getUsedLength() + getReserved() + length) > getLength()) {
1541            // not enough track length check to see if track is in a pool
1542            if (getPool() != null && getPool().requestTrackLength(this, length)) {
1543                return OKAY;
1544            }
1545            // ignore used length option?
1546            if (checkPlannedPickUps(length)) {
1547                return OKAY;
1548            }
1549            // The code assumes everything is fine with the track if the Length issue is returned.
1550            // Is rolling stock too long for this track?
1551            if ((getLength() < length && getPool() == null) ||
1552                    (getPool() != null && getPool().getTotalLengthTracks() < length)) {
1553                return Bundle.getMessage("capacityIssue",
1554                        CAPACITY, length, Setup.getLengthUnit().toLowerCase(), getLength());
1555            }
1556            log.debug("Rolling stock ({}) not accepted at location ({}, {}) no room!", rs.toString(),
1557                    getLocation().getName(), getName()); // NOI18N
1558
1559            return Bundle.getMessage("lengthIssue",
1560                    LENGTH, length, Setup.getLengthUnit().toLowerCase(), getAvailableTrackSpace(), getLength());
1561        }
1562        return OKAY;
1563    }
1564
1565    /**
1566     * Performs two checks, number of new set outs shouldn't exceed the track
1567     * length. The second check protects against overloading, the total number
1568     * of cars shouldn't exceed the track length plus the number of cars to
1569     * ignore.
1570     * 
1571     * @param length rolling stock length
1572     * @return true if the program should ignore some percentage of the car's
1573     *         length currently consuming track space.
1574     */
1575    private boolean checkPlannedPickUps(int length) {
1576        if (getIgnoreUsedLengthPercentage() > IGNORE_0 && getAvailableTrackSpace() >= length) {
1577            return true;
1578        }
1579        return false;
1580    }
1581
1582    /**
1583     * Available track space. Adjusted when a track is using the planned pickups
1584     * feature
1585     * 
1586     * @return available track space
1587     */
1588    public int getAvailableTrackSpace() {
1589        // calculate the available space
1590        int available = getLength() -
1591                (getUsedLength() * (IGNORE_100 - getIgnoreUsedLengthPercentage()) / IGNORE_100 + getReserved());
1592        // could be less if track is overloaded
1593        int available3 = getLength() +
1594                (getLength() * getIgnoreUsedLengthPercentage() / IGNORE_100) -
1595                getUsedLength() -
1596                getReserved();
1597        if (available3 < available) {
1598            available = available3;
1599        }
1600        // could be less based on track length
1601        int available2 = getLength() - getReservedLengthDrops();
1602        if (available2 < available) {
1603            available = available2;
1604        }
1605        return available;
1606    }
1607
1608    public int getReservedLengthDrops() {
1609        return _reservedLengthDrops;
1610    }
1611
1612    public int getMoves() {
1613        return _moves;
1614    }
1615
1616    public void setMoves(int moves) {
1617        int old = _moves;
1618        _moves = moves;
1619        setDirtyAndFirePropertyChange("trackMoves", old, moves); // NOI18N
1620    }
1621
1622    public void bumpMoves() {
1623        setMoves(getMoves() + 1);
1624    }
1625
1626    /**
1627     * Gets the blocking order for this track. Default is zero, in that case,
1628     * tracks are sorted by name.
1629     * 
1630     * @return the blocking order
1631     */
1632    public int getBlockingOrder() {
1633        return _blockingOrder;
1634    }
1635
1636    public void setBlockingOrder(int order) {
1637        int old = _blockingOrder;
1638        _blockingOrder = order;
1639        setDirtyAndFirePropertyChange(TRACK_BLOCKING_ORDER_CHANGED_PROPERTY, old, order); // NOI18N
1640    }
1641
1642    /**
1643     * Get the service order for this track. Yards and interchange have this
1644     * feature for cars. Staging has this feature for trains.
1645     *
1646     * @return Service order: Track.NORMAL, Track.FIFO, Track.LIFO
1647     */
1648    public String getServiceOrder() {
1649        if (isSpur() || (isStaging() && getPool() == null)) {
1650            return NORMAL;
1651        }
1652        return _order;
1653    }
1654
1655    /**
1656     * Set the service order for this track. Only yards and interchange have
1657     * this feature.
1658     * 
1659     * @param order Track.NORMAL, Track.FIFO, Track.LIFO
1660     */
1661    public void setServiceOrder(String order) {
1662        String old = _order;
1663        _order = order;
1664        setDirtyAndFirePropertyChange(SERVICE_ORDER_CHANGED_PROPERTY, old, order); // NOI18N
1665    }
1666
1667    /**
1668     * Returns the name of the schedule. Note that this returns the schedule
1669     * name based on the schedule's id. A schedule's name can be modified by the
1670     * user.
1671     *
1672     * @return Schedule name
1673     */
1674    public String getScheduleName() {
1675        if (getScheduleId().equals(NONE)) {
1676            return NONE;
1677        }
1678        Schedule schedule = getSchedule();
1679        if (schedule == null) {
1680            log.error("No name schedule for id: {}", getScheduleId());
1681            return NONE;
1682        }
1683        return schedule.getName();
1684    }
1685
1686    public Schedule getSchedule() {
1687        if (getScheduleId().equals(NONE)) {
1688            return null;
1689        }
1690        Schedule schedule = InstanceManager.getDefault(ScheduleManager.class).getScheduleById(getScheduleId());
1691        if (schedule == null) {
1692            log.error("No schedule for id: {}", getScheduleId());
1693        }
1694        return schedule;
1695    }
1696
1697    public void setSchedule(Schedule schedule) {
1698        String scheduleId = NONE;
1699        if (schedule != null) {
1700            scheduleId = schedule.getId();
1701        }
1702        setScheduleId(scheduleId);
1703    }
1704
1705    public String getScheduleId() {
1706        // Only spurs can have a schedule
1707        if (!isSpur()) {
1708            return NONE;
1709        }
1710        // old code only stored schedule name, so create id if needed.
1711        if (_scheduleId.equals(NONE) && !_scheduleName.equals(NONE)) {
1712            Schedule schedule = InstanceManager.getDefault(ScheduleManager.class).getScheduleByName(_scheduleName);
1713            if (schedule == null) {
1714                log.error("No schedule for name: {}", _scheduleName);
1715            } else {
1716                _scheduleId = schedule.getId();
1717            }
1718        }
1719        return _scheduleId;
1720    }
1721
1722    public void setScheduleId(String id) {
1723        String old = _scheduleId;
1724        _scheduleId = id;
1725        if (!old.equals(id)) {
1726            Schedule schedule = InstanceManager.getDefault(ScheduleManager.class).getScheduleById(id);
1727            if (schedule == null) {
1728                _scheduleName = NONE;
1729            } else {
1730                // set the sequence to the first item in the list
1731                if (schedule.getItemsBySequenceList().size() > 0) {
1732                    setScheduleItemId(schedule.getItemsBySequenceList().get(0).getId());
1733                }
1734                setScheduleCount(0);
1735            }
1736            setDirtyAndFirePropertyChange(SCHEDULE_ID_CHANGED_PROPERTY, old, id);
1737        }
1738    }
1739
1740    /**
1741     * Recommend getCurrentScheduleItem() to get the current schedule item for
1742     * this track. Protects against user deleting a schedule item from the
1743     * schedule.
1744     *
1745     * @return schedule item id
1746     */
1747    public String getScheduleItemId() {
1748        return _scheduleItemId;
1749    }
1750
1751    public void setScheduleItemId(String id) {
1752        log.debug("Set schedule item id ({}) for track ({})", id, getName());
1753        String old = _scheduleItemId;
1754        _scheduleItemId = id;
1755        setDirtyAndFirePropertyChange(SCHEDULE_CHANGED_PROPERTY, old, id);
1756    }
1757
1758    /**
1759     * Get's the current schedule item for this track Protects against user
1760     * deleting an item in a shared schedule. Recommend using this versus
1761     * getScheduleItemId() as the id can be obsolete.
1762     * 
1763     * @return The current ScheduleItem.
1764     */
1765    public ScheduleItem getCurrentScheduleItem() {
1766        Schedule sch = getSchedule();
1767        if (sch == null) {
1768            log.debug("Can not find schedule id: ({}) assigned to track ({})", getScheduleId(), getName());
1769            return null;
1770        }
1771        ScheduleItem currentSi = sch.getItemById(getScheduleItemId());
1772        if (currentSi == null && sch.getSize() > 0) {
1773            log.debug("Can not find schedule item id: ({}) for schedule ({})", getScheduleItemId(), getScheduleName());
1774            // reset schedule
1775            setScheduleItemId((sch.getItemsBySequenceList().get(0)).getId());
1776            currentSi = sch.getItemById(getScheduleItemId());
1777        }
1778        return currentSi;
1779    }
1780
1781    /**
1782     * Increments the schedule count if there's a schedule and the schedule is
1783     * running in sequential mode. Resets the schedule count if the maximum is
1784     * reached and then goes to the next item in the schedule's list.
1785     */
1786    public void bumpSchedule() {
1787        if (getSchedule() != null && getScheduleMode() == SEQUENTIAL) {
1788            // bump the schedule count
1789            setScheduleCount(getScheduleCount() + 1);
1790            if (getScheduleCount() >= getCurrentScheduleItem().getCount()) {
1791                setScheduleCount(0);
1792                // go to the next item in the schedule
1793                getNextScheduleItem();
1794            }
1795        }
1796    }
1797
1798    public ScheduleItem getNextScheduleItem() {
1799        Schedule sch = getSchedule();
1800        if (sch == null) {
1801            log.warn("Can not find schedule ({}) assigned to track ({})", getScheduleId(), getName());
1802            return null;
1803        }
1804        List<ScheduleItem> items = sch.getItemsBySequenceList();
1805        ScheduleItem nextSi = null;
1806        for (int i = 0; i < items.size(); i++) {
1807            nextSi = items.get(i);
1808            if (getCurrentScheduleItem() == nextSi) {
1809                if (++i < items.size()) {
1810                    nextSi = items.get(i);
1811                } else {
1812                    nextSi = items.get(0);
1813                }
1814                setScheduleItemId(nextSi.getId());
1815                break;
1816            }
1817        }
1818        return nextSi;
1819    }
1820
1821    /**
1822     * Returns how many times the current schedule item has been accessed.
1823     *
1824     * @return count
1825     */
1826    public int getScheduleCount() {
1827        return _scheduleCount;
1828    }
1829
1830    public void setScheduleCount(int count) {
1831        int old = _scheduleCount;
1832        _scheduleCount = count;
1833        setDirtyAndFirePropertyChange(SCHEDULE_CHANGED_PROPERTY, old, count);
1834    }
1835
1836    /**
1837     * Check to see if schedule is valid for the track at this location.
1838     *
1839     * @return SCHEDULE_OKAY if schedule okay, otherwise an error message.
1840     */
1841    public String checkScheduleValid() {
1842        if (getScheduleId().equals(NONE)) {
1843            return Schedule.SCHEDULE_OKAY;
1844        }
1845        Schedule schedule = getSchedule();
1846        if (schedule == null) {
1847            return Bundle.getMessage("CanNotFindSchedule", getScheduleId());
1848        }
1849        return schedule.checkScheduleValid(this);
1850    }
1851
1852    /**
1853     * Checks to see if car can be placed on this spur using this schedule.
1854     * Returns OKAY if the schedule can service the car.
1855     * 
1856     * @param car The Car to be tested.
1857     * @return Track.OKAY track.CUSTOM track.SCHEDULE
1858     */
1859    public String checkSchedule(Car car) {
1860        // does car already have this destination?
1861        if (car.getDestinationTrack() == this) {
1862            return OKAY;
1863        }
1864        // only spurs can have a schedule
1865        if (!isSpur()) {
1866            return OKAY;
1867        }
1868        if (getScheduleId().equals(NONE)) {
1869            // does car have a custom load?
1870            if (car.getLoadName().equals(InstanceManager.getDefault(CarLoads.class).getDefaultEmptyName()) ||
1871                    car.getLoadName().equals(InstanceManager.getDefault(CarLoads.class).getDefaultLoadName())) {
1872                return OKAY; // no
1873            }
1874            return Bundle.getMessage("carHasA", CUSTOM, LOAD, car.getLoadName());
1875        }
1876        log.debug("Track ({}) has schedule ({}) mode {} ({})", getName(), getScheduleName(), getScheduleMode(),
1877                getScheduleModeName()); // NOI18N
1878
1879        ScheduleItem si = getCurrentScheduleItem();
1880        // code check, should never be null
1881        if (si == null) {
1882            log.error("Could not find schedule item id: ({}) for schedule ({})", getScheduleItemId(),
1883                    getScheduleName()); // NOI18N
1884            return SCHEDULE + " ERROR"; // NOI18N
1885        }
1886        if (getScheduleMode() == SEQUENTIAL) {
1887            return getSchedule().checkScheduleItem(si, car, this);
1888        }
1889        // schedule in is match mode search entire schedule for a match
1890        return getSchedule().searchSchedule(car, this);
1891    }
1892
1893    /**
1894     * Check to see if track has schedule and if it does will schedule the next
1895     * item in the list. Load the car with the next schedule load if one exists,
1896     * and set the car's final destination if there's one in the schedule.
1897     * 
1898     * @param car The Car to be modified.
1899     * @return Track.OKAY or Track.SCHEDULE
1900     */
1901    public String scheduleNext(Car car) {
1902        // clean up the car's final destination if sent to that destination and
1903        // there isn't a schedule
1904        if (getScheduleId().equals(NONE) &&
1905                car.getDestination() != null &&
1906                car.getDestination().equals(car.getFinalDestination()) &&
1907                car.getDestinationTrack() != null &&
1908                (car.getDestinationTrack().equals(car.getFinalDestinationTrack()) ||
1909                        car.getFinalDestinationTrack() == null)) {
1910            car.setFinalDestination(null);
1911            car.setFinalDestinationTrack(null);
1912        }
1913        // check for schedule, only spurs can have a schedule
1914        if (getScheduleId().equals(NONE) || getSchedule() == null) {
1915            return OKAY;
1916        }
1917        // is car part of a kernel?
1918        if (car.getKernel() != null && !car.isLead()) {
1919            log.debug("Car ({}) is part of kernel ({}) not lead", car.toString(), car.getKernelName());
1920            return OKAY;
1921        }
1922        if (!car.getScheduleItemId().equals(Car.NONE)) {
1923            log.debug("Car ({}) has schedule item id ({})", car.toString(), car.getScheduleItemId());
1924            ScheduleItem si = car.getScheduleItem(this);
1925            if (si != null) {
1926                car.loadNext(si);
1927                return OKAY;
1928            }
1929            log.debug("Schedule id ({}) not valid for track ({})", car.getScheduleItemId(), getName());
1930            car.setScheduleItemId(Car.NONE);
1931        }
1932        // search schedule if match mode
1933        if (getScheduleMode() == MATCH && !getSchedule().searchSchedule(car, this).equals(OKAY)) {
1934            return Bundle.getMessage("matchMessage", SCHEDULE, getScheduleName(),
1935                            getSchedule().hasRandomItem() ? Bundle.getMessage("Random") : "");
1936        }
1937        ScheduleItem currentSi = getCurrentScheduleItem();
1938        log.debug("Destination track ({}) has schedule ({}) item id ({}) mode: {} ({})", getName(), getScheduleName(),
1939                getScheduleItemId(), getScheduleMode(), getScheduleModeName()); // NOI18N
1940        if (currentSi != null &&
1941                (currentSi.getSetoutTrainScheduleId().equals(ScheduleItem.NONE) ||
1942                        InstanceManager.getDefault(TrainScheduleManager.class).getTrainScheduleActiveId()
1943                                .equals(currentSi.getSetoutTrainScheduleId())) &&
1944                car.getTypeName().equals(currentSi.getTypeName()) &&
1945                (currentSi.getRoadName().equals(ScheduleItem.NONE) ||
1946                        car.getRoadName().equals(currentSi.getRoadName())) &&
1947                (currentSi.getReceiveLoadName().equals(ScheduleItem.NONE) ||
1948                        car.getLoadName().equals(currentSi.getReceiveLoadName()))) {
1949            car.setScheduleItemId(currentSi.getId());
1950            car.loadNext(currentSi);
1951            // bump schedule
1952            bumpSchedule();
1953        } else if (currentSi != null) {
1954            // build return failure message
1955            String scheduleName = "";
1956            String currentTrainScheduleName = "";
1957            TrainSchedule sch = InstanceManager.getDefault(TrainScheduleManager.class)
1958                    .getScheduleById(InstanceManager.getDefault(TrainScheduleManager.class).getTrainScheduleActiveId());
1959            if (sch != null) {
1960                scheduleName = sch.getName();
1961            }
1962            sch = InstanceManager.getDefault(TrainScheduleManager.class)
1963                    .getScheduleById(currentSi.getSetoutTrainScheduleId());
1964            if (sch != null) {
1965                currentTrainScheduleName = sch.getName();
1966            }
1967            return Bundle.getMessage("sequentialMessage", SCHEDULE, getScheduleName(), getScheduleModeName(),
1968                    car.toString(), car.getTypeName(), scheduleName, car.getRoadName(), car.getLoadName(),
1969                    currentSi.getTypeName(), currentTrainScheduleName, currentSi.getRoadName(),
1970                    currentSi.getReceiveLoadName());
1971        } else {
1972            log.error("ERROR Track {} current schedule item is null!", getName());
1973            return SCHEDULE + " ERROR Track " + getName() + " current schedule item is null!"; // NOI18N
1974        }
1975        return OKAY;
1976    }
1977
1978    public static final String TRAIN_SCHEDULE = "trainSchedule"; // NOI18N
1979    public static final String ALL = "all"; // NOI18N
1980
1981    public boolean checkScheduleAttribute(String attribute, String carType, Car car) {
1982        Schedule schedule = getSchedule();
1983        if (schedule == null) {
1984            return true;
1985        }
1986        // if car is already placed at track, don't check car type and load
1987        if (car != null && car.getTrack() == this) {
1988            return true;
1989        }
1990        return schedule.checkScheduleAttribute(attribute, carType, car);
1991    }
1992
1993    /**
1994     * Enable changing the car generic load state when car arrives at this
1995     * track.
1996     *
1997     * @param enable when true, swap generic car load state
1998     */
1999    public void setLoadSwapEnabled(boolean enable) {
2000        boolean old = isLoadSwapEnabled();
2001        if (enable) {
2002            _loadOptions = _loadOptions | SWAP_GENERIC_LOADS;
2003        } else {
2004            _loadOptions = _loadOptions & 0xFFFF - SWAP_GENERIC_LOADS;
2005        }
2006        setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable);
2007    }
2008
2009    public boolean isLoadSwapEnabled() {
2010        return (0 != (_loadOptions & SWAP_GENERIC_LOADS));
2011    }
2012
2013    /**
2014     * Enable setting the car generic load state to empty when car arrives at
2015     * this track.
2016     *
2017     * @param enable when true, set generic car load to empty
2018     */
2019    public void setLoadEmptyEnabled(boolean enable) {
2020        boolean old = isLoadEmptyEnabled();
2021        if (enable) {
2022            _loadOptions = _loadOptions | EMPTY_GENERIC_LOADS;
2023        } else {
2024            _loadOptions = _loadOptions & 0xFFFF - EMPTY_GENERIC_LOADS;
2025        }
2026        setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable);
2027    }
2028
2029    public boolean isLoadEmptyEnabled() {
2030        return (0 != (_loadOptions & EMPTY_GENERIC_LOADS));
2031    }
2032
2033    /**
2034     * When enabled, remove Scheduled car loads.
2035     *
2036     * @param enable when true, remove Scheduled loads from cars
2037     */
2038    public void setRemoveCustomLoadsEnabled(boolean enable) {
2039        boolean old = isRemoveCustomLoadsEnabled();
2040        if (enable) {
2041            _loadOptions = _loadOptions | EMPTY_CUSTOM_LOADS;
2042        } else {
2043            _loadOptions = _loadOptions & 0xFFFF - EMPTY_CUSTOM_LOADS;
2044        }
2045        setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable);
2046    }
2047
2048    public boolean isRemoveCustomLoadsEnabled() {
2049        return (0 != (_loadOptions & EMPTY_CUSTOM_LOADS));
2050    }
2051
2052    /**
2053     * When enabled, add custom car loads if there's a demand.
2054     *
2055     * @param enable when true, add custom loads to cars
2056     */
2057    public void setAddCustomLoadsEnabled(boolean enable) {
2058        boolean old = isAddCustomLoadsEnabled();
2059        if (enable) {
2060            _loadOptions = _loadOptions | GENERATE_CUSTOM_LOADS;
2061        } else {
2062            _loadOptions = _loadOptions & 0xFFFF - GENERATE_CUSTOM_LOADS;
2063        }
2064        setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable);
2065    }
2066
2067    public boolean isAddCustomLoadsEnabled() {
2068        return (0 != (_loadOptions & GENERATE_CUSTOM_LOADS));
2069    }
2070
2071    /**
2072     * When enabled, add custom car loads if there's a demand by any
2073     * spur/industry.
2074     *
2075     * @param enable when true, add custom loads to cars
2076     */
2077    public void setAddCustomLoadsAnySpurEnabled(boolean enable) {
2078        boolean old = isAddCustomLoadsAnySpurEnabled();
2079        if (enable) {
2080            _loadOptions = _loadOptions | GENERATE_CUSTOM_LOADS_ANY_SPUR;
2081        } else {
2082            _loadOptions = _loadOptions & 0xFFFF - GENERATE_CUSTOM_LOADS_ANY_SPUR;
2083        }
2084        setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable);
2085    }
2086
2087    public boolean isAddCustomLoadsAnySpurEnabled() {
2088        return (0 != (_loadOptions & GENERATE_CUSTOM_LOADS_ANY_SPUR));
2089    }
2090
2091    /**
2092     * When enabled, add custom car loads to cars in staging for new
2093     * destinations that are staging.
2094     *
2095     * @param enable when true, add custom load to car
2096     */
2097    public void setAddCustomLoadsAnyStagingTrackEnabled(boolean enable) {
2098        boolean old = isAddCustomLoadsAnyStagingTrackEnabled();
2099        if (enable) {
2100            _loadOptions = _loadOptions | GENERATE_CUSTOM_LOADS_ANY_STAGING_TRACK;
2101        } else {
2102            _loadOptions = _loadOptions & 0xFFFF - GENERATE_CUSTOM_LOADS_ANY_STAGING_TRACK;
2103        }
2104        setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable);
2105    }
2106
2107    public boolean isAddCustomLoadsAnyStagingTrackEnabled() {
2108        return (0 != (_loadOptions & GENERATE_CUSTOM_LOADS_ANY_STAGING_TRACK));
2109    }
2110
2111    public boolean isModifyLoadsEnabled() {
2112        return isLoadEmptyEnabled() ||
2113                isLoadSwapEnabled() ||
2114                isRemoveCustomLoadsEnabled() ||
2115                isAddCustomLoadsAnySpurEnabled() ||
2116                isAddCustomLoadsAnyStagingTrackEnabled() ||
2117                isAddCustomLoadsEnabled();
2118    }
2119
2120    public void setDisableLoadChangeEnabled(boolean enable) {
2121        boolean old = isDisableLoadChangeEnabled();
2122        if (enable) {
2123            _loadOptions = _loadOptions | DISABLE_LOAD_CHANGE;
2124        } else {
2125            _loadOptions = _loadOptions & 0xFFFF - DISABLE_LOAD_CHANGE;
2126        }
2127        setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable);
2128    }
2129
2130    public boolean isDisableLoadChangeEnabled() {
2131        return (0 != (_loadOptions & DISABLE_LOAD_CHANGE));
2132    }
2133
2134    public void setBlockCarsEnabled(boolean enable) {
2135        boolean old = isBlockCarsEnabled();
2136        if (enable) {
2137            _blockOptions = _blockOptions | BLOCK_CARS;
2138        } else {
2139            _blockOptions = _blockOptions & 0xFFFF - BLOCK_CARS;
2140        }
2141        setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable);
2142    }
2143
2144    /**
2145     * When enabled block cars from staging.
2146     *
2147     * @return true if blocking is enabled.
2148     */
2149    public boolean isBlockCarsEnabled() {
2150        if (isStaging()) {
2151            return (0 != (_blockOptions & BLOCK_CARS));
2152        }
2153        return false;
2154    }
2155
2156    public void setPool(Pool pool) {
2157        Pool old = _pool;
2158        _pool = pool;
2159        if (old != pool) {
2160            if (old != null) {
2161                old.remove(this);
2162            }
2163            if (_pool != null) {
2164                _pool.add(this);
2165            }
2166            setDirtyAndFirePropertyChange(POOL_CHANGED_PROPERTY, old, pool);
2167        }
2168    }
2169
2170    public Pool getPool() {
2171        return _pool;
2172    }
2173
2174    public String getPoolName() {
2175        if (getPool() != null) {
2176            return getPool().getName();
2177        }
2178        return NONE;
2179    }
2180
2181    public int getDestinationListSize() {
2182        return _destinationIdList.size();
2183    }
2184
2185    /**
2186     * adds a location to the list of acceptable destinations for this track.
2187     * 
2188     * @param destination location that is acceptable
2189     */
2190    public void addDestination(Location destination) {
2191        if (!_destinationIdList.contains(destination.getId())) {
2192            _destinationIdList.add(destination.getId());
2193            setDirtyAndFirePropertyChange(DESTINATIONS_CHANGED_PROPERTY, null, destination.getName()); // NOI18N
2194        }
2195    }
2196
2197    public void deleteDestination(Location destination) {
2198        if (_destinationIdList.remove(destination.getId())) {
2199            setDirtyAndFirePropertyChange(DESTINATIONS_CHANGED_PROPERTY, destination.getName(), null); // NOI18N
2200        }
2201    }
2202
2203    /**
2204     * Returns true if destination is valid from this track.
2205     * 
2206     * @param destination The Location to be checked.
2207     * @return true if track services the destination
2208     */
2209    public boolean isDestinationAccepted(Location destination) {
2210        if (getDestinationOption().equals(ALL_DESTINATIONS) || destination == null) {
2211            return true;
2212        }
2213        return _destinationIdList.contains(destination.getId());
2214    }
2215
2216    public void setDestinationIds(String[] ids) {
2217        for (String id : ids) {
2218            _destinationIdList.add(id);
2219        }
2220    }
2221
2222    public String[] getDestinationIds() {
2223        String[] ids = _destinationIdList.toArray(new String[0]);
2224        return ids;
2225    }
2226
2227    /**
2228     * Sets the destination option for this track. The three options are:
2229     * <p>
2230     * ALL_DESTINATIONS which means this track services all destinations, the
2231     * default.
2232     * <p>
2233     * INCLUDE_DESTINATIONS which means this track services only certain
2234     * destinations.
2235     * <p>
2236     * EXCLUDE_DESTINATIONS which means this track does not service certain
2237     * destinations.
2238     *
2239     * @param option Track.ALL_DESTINATIONS, Track.INCLUDE_DESTINATIONS, or
2240     *               Track.EXCLUDE_DESTINATIONS
2241     */
2242    public void setDestinationOption(String option) {
2243        String old = _destinationOption;
2244        _destinationOption = option;
2245        if (!option.equals(old)) {
2246            setDirtyAndFirePropertyChange(DESTINATION_OPTIONS_CHANGED_PROPERTY, old, option); // NOI18N
2247        }
2248    }
2249
2250    /**
2251     * Get destination option for interchange or staging track
2252     * 
2253     * @return option
2254     */
2255    public String getDestinationOption() {
2256        if (isInterchange() || isStaging()) {
2257            return _destinationOption;
2258        }
2259        return ALL_DESTINATIONS;
2260    }
2261
2262    public void setOnlyCarsWithFinalDestinationEnabled(boolean enable) {
2263        boolean old = _onlyCarsWithFD;
2264        _onlyCarsWithFD = enable;
2265        setDirtyAndFirePropertyChange(ROUTED_CHANGED_PROPERTY, old, enable);
2266    }
2267
2268    /**
2269     * When true the track will only accept cars that have a final destination
2270     * that can be serviced by the track. See acceptsDestination(Location).
2271     * 
2272     * @return false if any car spotted, true if only cars with a FD.
2273     */
2274    public boolean isOnlyCarsWithFinalDestinationEnabled() {
2275        if (isInterchange() || isStaging()) {
2276            return _onlyCarsWithFD;
2277        }
2278        return false;
2279    }
2280
2281    /**
2282     * Used to determine if track has been assigned as an alternate
2283     *
2284     * @return true if track is an alternate
2285     */
2286    public boolean isAlternate() {
2287        for (Track track : getLocation().getTracksList()) {
2288            if (track.getAlternateTrack() == this) {
2289                return true;
2290            }
2291        }
2292        return false;
2293    }
2294
2295    public void dispose() {
2296        // change the name in case object is still in use, for example
2297        // ScheduleItem.java
2298        setName(Bundle.getMessage("NotValid", getName()));
2299        setDirtyAndFirePropertyChange(DISPOSE_CHANGED_PROPERTY, null, DISPOSE_CHANGED_PROPERTY);
2300    }
2301
2302    /**
2303     * Construct this Entry from XML. This member has to remain synchronized
2304     * with the detailed DTD in operations-location.dtd.
2305     *
2306     * @param e        Consist XML element
2307     * @param location The Location loading this track.
2308     */
2309    public Track(Element e, Location location) {
2310        _location = location;
2311        Attribute a;
2312        if ((a = e.getAttribute(Xml.ID)) != null) {
2313            _id = a.getValue();
2314        } else {
2315            log.warn("no id attribute in track element when reading operations");
2316        }
2317        if ((a = e.getAttribute(Xml.NAME)) != null) {
2318            _name = a.getValue();
2319        }
2320        if ((a = e.getAttribute(Xml.TRACK_TYPE)) != null) {
2321            _trackType = a.getValue();
2322
2323            // old way of storing track type before 4.21.1
2324        } else if ((a = e.getAttribute(Xml.LOC_TYPE)) != null) {
2325            if (a.getValue().equals(SIDING)) {
2326                _trackType = SPUR;
2327            } else {
2328                _trackType = a.getValue();
2329            }
2330        }
2331
2332        if ((a = e.getAttribute(Xml.LENGTH)) != null) {
2333            try {
2334                _length = Integer.parseInt(a.getValue());
2335            } catch (NumberFormatException nfe) {
2336                log.error("Track length isn't a vaild number for track {}", getName());
2337            }
2338        }
2339        if ((a = e.getAttribute(Xml.MOVES)) != null) {
2340            try {
2341                _moves = Integer.parseInt(a.getValue());
2342            } catch (NumberFormatException nfe) {
2343                log.error("Track moves isn't a vaild number for track {}", getName());
2344            }
2345
2346        }
2347        if ((a = e.getAttribute(Xml.BLOCKING_ORDER)) != null) {
2348            try {
2349                _blockingOrder = Integer.parseInt(a.getValue());
2350            } catch (NumberFormatException nfe) {
2351                log.error("Track blocking order isn't a vaild number for track {}", getName());
2352            }
2353        }
2354        if ((a = e.getAttribute(Xml.DIR)) != null) {
2355            try {
2356                _trainDir = Integer.parseInt(a.getValue());
2357            } catch (NumberFormatException nfe) {
2358                log.error("Track service direction isn't a vaild number for track {}", getName());
2359            }
2360        }
2361        // old way of reading track comment, see comments below for new format
2362        if ((a = e.getAttribute(Xml.COMMENT)) != null) {
2363            _comment = a.getValue();
2364        }
2365        // new way of reading car types using elements added in 3.3.1
2366        if (e.getChild(Xml.TYPES) != null) {
2367            List<Element> carTypes = e.getChild(Xml.TYPES).getChildren(Xml.CAR_TYPE);
2368            String[] types = new String[carTypes.size()];
2369            for (int i = 0; i < carTypes.size(); i++) {
2370                Element type = carTypes.get(i);
2371                if ((a = type.getAttribute(Xml.NAME)) != null) {
2372                    types[i] = a.getValue();
2373                }
2374            }
2375            setTypeNames(types);
2376            List<Element> locoTypes = e.getChild(Xml.TYPES).getChildren(Xml.LOCO_TYPE);
2377            types = new String[locoTypes.size()];
2378            for (int i = 0; i < locoTypes.size(); i++) {
2379                Element type = locoTypes.get(i);
2380                if ((a = type.getAttribute(Xml.NAME)) != null) {
2381                    types[i] = a.getValue();
2382                }
2383            }
2384            setTypeNames(types);
2385        } // old way of reading car types up to version 3.2
2386        else if ((a = e.getAttribute(Xml.CAR_TYPES)) != null) {
2387            String names = a.getValue();
2388            String[] types = names.split("%%"); // NOI18N
2389            setTypeNames(types);
2390        }
2391        if ((a = e.getAttribute(Xml.CAR_LOAD_OPTION)) != null) {
2392            _loadOption = a.getValue();
2393        }
2394        // new way of reading car loads using elements
2395        if (e.getChild(Xml.CAR_LOADS) != null) {
2396            List<Element> carLoads = e.getChild(Xml.CAR_LOADS).getChildren(Xml.CAR_LOAD);
2397            String[] loads = new String[carLoads.size()];
2398            for (int i = 0; i < carLoads.size(); i++) {
2399                Element load = carLoads.get(i);
2400                if ((a = load.getAttribute(Xml.NAME)) != null) {
2401                    loads[i] = a.getValue();
2402                }
2403            }
2404            setLoadNames(loads);
2405        } // old way of reading car loads up to version 3.2
2406        else if ((a = e.getAttribute(Xml.CAR_LOADS)) != null) {
2407            String names = a.getValue();
2408            String[] loads = names.split("%%"); // NOI18N
2409            log.debug("Track ({}) {} car loads: {}", getName(), getLoadOption(), names);
2410            setLoadNames(loads);
2411        }
2412        if ((a = e.getAttribute(Xml.CAR_SHIP_LOAD_OPTION)) != null) {
2413            _shipLoadOption = a.getValue();
2414        }
2415        // new way of reading car loads using elements
2416        if (e.getChild(Xml.CAR_SHIP_LOADS) != null) {
2417            List<Element> carLoads = e.getChild(Xml.CAR_SHIP_LOADS).getChildren(Xml.CAR_LOAD);
2418            String[] loads = new String[carLoads.size()];
2419            for (int i = 0; i < carLoads.size(); i++) {
2420                Element load = carLoads.get(i);
2421                if ((a = load.getAttribute(Xml.NAME)) != null) {
2422                    loads[i] = a.getValue();
2423                }
2424            }
2425            setShipLoadNames(loads);
2426        }
2427        // new way of reading drop ids using elements
2428        if (e.getChild(Xml.DROP_IDS) != null) {
2429            List<Element> dropIds = e.getChild(Xml.DROP_IDS).getChildren(Xml.DROP_ID);
2430            String[] ids = new String[dropIds.size()];
2431            for (int i = 0; i < dropIds.size(); i++) {
2432                Element dropId = dropIds.get(i);
2433                if ((a = dropId.getAttribute(Xml.ID)) != null) {
2434                    ids[i] = a.getValue();
2435                }
2436            }
2437            setDropIds(ids);
2438        } // old way of reading drop ids up to version 3.2
2439        else if ((a = e.getAttribute(Xml.DROP_IDS)) != null) {
2440            String names = a.getValue();
2441            String[] ids = names.split("%%"); // NOI18N
2442            setDropIds(ids);
2443        }
2444        if ((a = e.getAttribute(Xml.DROP_OPTION)) != null) {
2445            _dropOption = a.getValue();
2446        }
2447
2448        // new way of reading pick up ids using elements
2449        if (e.getChild(Xml.PICKUP_IDS) != null) {
2450            List<Element> pickupIds = e.getChild(Xml.PICKUP_IDS).getChildren(Xml.PICKUP_ID);
2451            String[] ids = new String[pickupIds.size()];
2452            for (int i = 0; i < pickupIds.size(); i++) {
2453                Element pickupId = pickupIds.get(i);
2454                if ((a = pickupId.getAttribute(Xml.ID)) != null) {
2455                    ids[i] = a.getValue();
2456                }
2457            }
2458            setPickupIds(ids);
2459        } // old way of reading pick up ids up to version 3.2
2460        else if ((a = e.getAttribute(Xml.PICKUP_IDS)) != null) {
2461            String names = a.getValue();
2462            String[] ids = names.split("%%"); // NOI18N
2463            setPickupIds(ids);
2464        }
2465        if ((a = e.getAttribute(Xml.PICKUP_OPTION)) != null) {
2466            _pickupOption = a.getValue();
2467        }
2468
2469        // new way of reading car roads using elements
2470        if (e.getChild(Xml.CAR_ROADS) != null) {
2471            List<Element> carRoads = e.getChild(Xml.CAR_ROADS).getChildren(Xml.CAR_ROAD);
2472            String[] roads = new String[carRoads.size()];
2473            for (int i = 0; i < carRoads.size(); i++) {
2474                Element road = carRoads.get(i);
2475                if ((a = road.getAttribute(Xml.NAME)) != null) {
2476                    roads[i] = a.getValue();
2477                }
2478            }
2479            setRoadNames(roads);
2480        } // old way of reading car roads up to version 3.2
2481        else if ((a = e.getAttribute(Xml.CAR_ROADS)) != null) {
2482            String names = a.getValue();
2483            String[] roads = names.split("%%"); // NOI18N
2484            setRoadNames(roads);
2485        }
2486        if ((a = e.getAttribute(Xml.CAR_ROAD_OPTION)) != null) {
2487            _roadOption = a.getValue();
2488        } else if ((a = e.getAttribute(Xml.CAR_ROAD_OPERATION)) != null) {
2489            _roadOption = a.getValue();
2490        }
2491
2492        if ((a = e.getAttribute(Xml.SCHEDULE)) != null) {
2493            _scheduleName = a.getValue();
2494        }
2495        if ((a = e.getAttribute(Xml.SCHEDULE_ID)) != null) {
2496            _scheduleId = a.getValue();
2497        }
2498        if ((a = e.getAttribute(Xml.ITEM_ID)) != null) {
2499            _scheduleItemId = a.getValue();
2500        }
2501        if ((a = e.getAttribute(Xml.ITEM_COUNT)) != null) {
2502            try {
2503                _scheduleCount = Integer.parseInt(a.getValue());
2504            } catch (NumberFormatException nfe) {
2505                log.error("Schedule count isn't a vaild number for track {}", getName());
2506            }
2507        }
2508        if ((a = e.getAttribute(Xml.FACTOR)) != null) {
2509            try {
2510                _reservationFactor = Integer.parseInt(a.getValue());
2511            } catch (NumberFormatException nfe) {
2512                log.error("Reservation factor isn't a vaild number for track {}", getName());
2513            }
2514        }
2515        if ((a = e.getAttribute(Xml.SCHEDULE_MODE)) != null) {
2516            try {
2517                _mode = Integer.parseInt(a.getValue());
2518            } catch (NumberFormatException nfe) {
2519                log.error("Schedule mode isn't a vaild number for track {}", getName());
2520            }
2521        }
2522        if ((a = e.getAttribute(Xml.HOLD_CARS_CUSTOM)) != null) {
2523            setHoldCarsWithCustomLoadsEnabled(a.getValue().equals(Xml.TRUE));
2524        }
2525        if ((a = e.getAttribute(Xml.ONLY_CARS_WITH_FD)) != null) {
2526            setOnlyCarsWithFinalDestinationEnabled(a.getValue().equals(Xml.TRUE));
2527        }
2528
2529        if ((a = e.getAttribute(Xml.ALTERNATIVE)) != null) {
2530            _alternateTrackId = a.getValue();
2531        }
2532
2533        if ((a = e.getAttribute(Xml.LOAD_OPTIONS)) != null) {
2534            try {
2535                _loadOptions = Integer.parseInt(a.getValue());
2536            } catch (NumberFormatException nfe) {
2537                log.error("Load options isn't a vaild number for track {}", getName());
2538            }
2539        }
2540        if ((a = e.getAttribute(Xml.BLOCK_OPTIONS)) != null) {
2541            try {
2542                _blockOptions = Integer.parseInt(a.getValue());
2543            } catch (NumberFormatException nfe) {
2544                log.error("Block options isn't a vaild number for track {}", getName());
2545            }
2546        }
2547        if ((a = e.getAttribute(Xml.ORDER)) != null) {
2548            _order = a.getValue();
2549        }
2550        if ((a = e.getAttribute(Xml.POOL)) != null) {
2551            setPool(getLocation().addPool(a.getValue()));
2552            if ((a = e.getAttribute(Xml.MIN_LENGTH)) != null) {
2553                try {
2554                    _minimumLength = Integer.parseInt(a.getValue());
2555                } catch (NumberFormatException nfe) {
2556                    log.error("Minimum pool length isn't a vaild number for track {}", getName());
2557                }
2558            }
2559        }
2560        if ((a = e.getAttribute(Xml.IGNORE_USED_PERCENTAGE)) != null) {
2561            try {
2562                _ignoreUsedLengthPercentage = Integer.parseInt(a.getValue());
2563            } catch (NumberFormatException nfe) {
2564                log.error("Ignore used percentage isn't a vaild number for track {}", getName());
2565            }
2566        }
2567        if ((a = e.getAttribute(Xml.TRACK_DESTINATION_OPTION)) != null) {
2568            _destinationOption = a.getValue();
2569        }
2570        if (e.getChild(Xml.DESTINATIONS) != null) {
2571            List<Element> eDestinations = e.getChild(Xml.DESTINATIONS).getChildren(Xml.DESTINATION);
2572            for (Element eDestination : eDestinations) {
2573                if ((a = eDestination.getAttribute(Xml.ID)) != null) {
2574                    _destinationIdList.add(a.getValue());
2575                }
2576            }
2577        }
2578
2579        if (e.getChild(Xml.COMMENTS) != null) {
2580            if (e.getChild(Xml.COMMENTS).getChild(Xml.TRACK) != null &&
2581                    (a = e.getChild(Xml.COMMENTS).getChild(Xml.TRACK).getAttribute(Xml.COMMENT)) != null) {
2582                _comment = a.getValue();
2583            }
2584            if (e.getChild(Xml.COMMENTS).getChild(Xml.BOTH) != null &&
2585                    (a = e.getChild(Xml.COMMENTS).getChild(Xml.BOTH).getAttribute(Xml.COMMENT)) != null) {
2586                _commentBoth = a.getValue();
2587            }
2588            if (e.getChild(Xml.COMMENTS).getChild(Xml.PICKUP) != null &&
2589                    (a = e.getChild(Xml.COMMENTS).getChild(Xml.PICKUP).getAttribute(Xml.COMMENT)) != null) {
2590                _commentPickup = a.getValue();
2591            }
2592            if (e.getChild(Xml.COMMENTS).getChild(Xml.SETOUT) != null &&
2593                    (a = e.getChild(Xml.COMMENTS).getChild(Xml.SETOUT).getAttribute(Xml.COMMENT)) != null) {
2594                _commentSetout = a.getValue();
2595            }
2596            if (e.getChild(Xml.COMMENTS).getChild(Xml.PRINT_MANIFEST) != null &&
2597                    (a = e.getChild(Xml.COMMENTS).getChild(Xml.PRINT_MANIFEST).getAttribute(Xml.COMMENT)) != null) {
2598                _printCommentManifest = a.getValue().equals(Xml.TRUE);
2599            }
2600            if (e.getChild(Xml.COMMENTS).getChild(Xml.PRINT_SWITCH_LISTS) != null &&
2601                    (a = e.getChild(Xml.COMMENTS).getChild(Xml.PRINT_SWITCH_LISTS).getAttribute(Xml.COMMENT)) != null) {
2602                _printCommentSwitchList = a.getValue().equals(Xml.TRUE);
2603            }
2604        }
2605
2606        if ((a = e.getAttribute(Xml.READER)) != null) {
2607            try {
2608                Reporter r = jmri.InstanceManager.getDefault(jmri.ReporterManager.class).provideReporter(a.getValue());
2609                _reader = r;
2610            } catch (IllegalArgumentException ex) {
2611                log.warn("Not able to find reader: {} for location ({})", a.getValue(), getName());
2612            }
2613        }
2614    }
2615
2616    /**
2617     * Create an XML element to represent this Entry. This member has to remain
2618     * synchronized with the detailed DTD in operations-location.dtd.
2619     *
2620     * @return Contents in a JDOM Element
2621     */
2622    public Element store() {
2623        Element e = new Element(Xml.TRACK);
2624        e.setAttribute(Xml.ID, getId());
2625        e.setAttribute(Xml.NAME, getName());
2626        e.setAttribute(Xml.TRACK_TYPE, getTrackType());
2627        e.setAttribute(Xml.DIR, Integer.toString(getTrainDirections()));
2628        e.setAttribute(Xml.LENGTH, Integer.toString(getLength()));
2629        e.setAttribute(Xml.MOVES, Integer.toString(getMoves() - getDropRS()));
2630        if (getBlockingOrder() != 0) {
2631            e.setAttribute(Xml.BLOCKING_ORDER, Integer.toString(getBlockingOrder()));
2632        }
2633        // build list of car types for this track
2634        String[] types = getTypeNames();
2635        // new way of saving car types using elements
2636        Element eTypes = new Element(Xml.TYPES);
2637        for (String type : types) {
2638            // don't save types that have been deleted by user
2639            if (InstanceManager.getDefault(EngineTypes.class).containsName(type)) {
2640                Element eType = new Element(Xml.LOCO_TYPE);
2641                eType.setAttribute(Xml.NAME, type);
2642                eTypes.addContent(eType);
2643            } else if (InstanceManager.getDefault(CarTypes.class).containsName(type)) {
2644                Element eType = new Element(Xml.CAR_TYPE);
2645                eType.setAttribute(Xml.NAME, type);
2646                eTypes.addContent(eType);
2647            }
2648        }
2649        e.addContent(eTypes);
2650
2651        // build list of car roads for this track
2652        if (!getRoadOption().equals(ALL_ROADS)) {
2653            e.setAttribute(Xml.CAR_ROAD_OPTION, getRoadOption());
2654            String[] roads = getRoadNames();
2655            // new way of saving road names
2656            Element eRoads = new Element(Xml.CAR_ROADS);
2657            for (String road : roads) {
2658                Element eRoad = new Element(Xml.CAR_ROAD);
2659                eRoad.setAttribute(Xml.NAME, road);
2660                eRoads.addContent(eRoad);
2661            }
2662            e.addContent(eRoads);
2663        }
2664
2665        // save list of car loads for this track
2666        if (!getLoadOption().equals(ALL_LOADS)) {
2667            e.setAttribute(Xml.CAR_LOAD_OPTION, getLoadOption());
2668            String[] loads = getLoadNames();
2669            // new way of saving car loads using elements
2670            Element eLoads = new Element(Xml.CAR_LOADS);
2671            for (String load : loads) {
2672                Element eLoad = new Element(Xml.CAR_LOAD);
2673                eLoad.setAttribute(Xml.NAME, load);
2674                eLoads.addContent(eLoad);
2675            }
2676            e.addContent(eLoads);
2677        }
2678
2679        // save list of car loads for this track
2680        if (!getShipLoadOption().equals(ALL_LOADS)) {
2681            e.setAttribute(Xml.CAR_SHIP_LOAD_OPTION, getShipLoadOption());
2682            String[] loads = getShipLoadNames();
2683            // new way of saving car loads using elements
2684            Element eLoads = new Element(Xml.CAR_SHIP_LOADS);
2685            for (String load : loads) {
2686                Element eLoad = new Element(Xml.CAR_LOAD);
2687                eLoad.setAttribute(Xml.NAME, load);
2688                eLoads.addContent(eLoad);
2689            }
2690            e.addContent(eLoads);
2691        }
2692
2693        if (!getDropOption().equals(ANY)) {
2694            e.setAttribute(Xml.DROP_OPTION, getDropOption());
2695            // build list of drop ids for this track
2696            String[] dropIds = getDropIds();
2697            // new way of saving drop ids using elements
2698            Element eDropIds = new Element(Xml.DROP_IDS);
2699            for (String id : dropIds) {
2700                Element eDropId = new Element(Xml.DROP_ID);
2701                eDropId.setAttribute(Xml.ID, id);
2702                eDropIds.addContent(eDropId);
2703            }
2704            e.addContent(eDropIds);
2705        }
2706
2707        if (!getPickupOption().equals(ANY)) {
2708            e.setAttribute(Xml.PICKUP_OPTION, getPickupOption());
2709            // build list of pickup ids for this track
2710            String[] pickupIds = getPickupIds();
2711            // new way of saving pick up ids using elements
2712            Element ePickupIds = new Element(Xml.PICKUP_IDS);
2713            for (String id : pickupIds) {
2714                Element ePickupId = new Element(Xml.PICKUP_ID);
2715                ePickupId.setAttribute(Xml.ID, id);
2716                ePickupIds.addContent(ePickupId);
2717            }
2718            e.addContent(ePickupIds);
2719        }
2720
2721        if (getSchedule() != null) {
2722            e.setAttribute(Xml.SCHEDULE, getScheduleName());
2723            e.setAttribute(Xml.SCHEDULE_ID, getScheduleId());
2724            e.setAttribute(Xml.ITEM_ID, getScheduleItemId());
2725            e.setAttribute(Xml.ITEM_COUNT, Integer.toString(getScheduleCount()));
2726            e.setAttribute(Xml.FACTOR, Integer.toString(getReservationFactor()));
2727            e.setAttribute(Xml.SCHEDULE_MODE, Integer.toString(getScheduleMode()));
2728            e.setAttribute(Xml.HOLD_CARS_CUSTOM, isHoldCarsWithCustomLoadsEnabled() ? Xml.TRUE : Xml.FALSE);
2729        }
2730        if (isInterchange() || isStaging()) {
2731            e.setAttribute(Xml.ONLY_CARS_WITH_FD, isOnlyCarsWithFinalDestinationEnabled() ? Xml.TRUE : Xml.FALSE);
2732        }
2733        if (getAlternateTrack() != null) {
2734            e.setAttribute(Xml.ALTERNATIVE, getAlternateTrack().getId());
2735        }
2736        if (_loadOptions != 0) {
2737            e.setAttribute(Xml.LOAD_OPTIONS, Integer.toString(_loadOptions));
2738        }
2739        if (isBlockCarsEnabled()) {
2740            e.setAttribute(Xml.BLOCK_OPTIONS, Integer.toString(_blockOptions));
2741        }
2742        if (!getServiceOrder().equals(NORMAL)) {
2743            e.setAttribute(Xml.ORDER, getServiceOrder());
2744        }
2745        if (getPool() != null) {
2746            e.setAttribute(Xml.POOL, getPool().getName());
2747            e.setAttribute(Xml.MIN_LENGTH, Integer.toString(getMinimumLength()));
2748        }
2749        if (getIgnoreUsedLengthPercentage() > IGNORE_0) {
2750            e.setAttribute(Xml.IGNORE_USED_PERCENTAGE, Integer.toString(getIgnoreUsedLengthPercentage()));
2751        }
2752
2753        if ((isStaging() || isInterchange()) && !getDestinationOption().equals(ALL_DESTINATIONS)) {
2754            e.setAttribute(Xml.TRACK_DESTINATION_OPTION, getDestinationOption());
2755            // save destinations if they exist
2756            String[] destIds = getDestinationIds();
2757            if (destIds.length > 0) {
2758                Element destinations = new Element(Xml.DESTINATIONS);
2759                for (String id : destIds) {
2760                    Location loc = InstanceManager.getDefault(LocationManager.class).getLocationById(id);
2761                    if (loc != null) {
2762                        Element destination = new Element(Xml.DESTINATION);
2763                        destination.setAttribute(Xml.ID, id);
2764                        destination.setAttribute(Xml.NAME, loc.getName());
2765                        destinations.addContent(destination);
2766                    }
2767                }
2768                e.addContent(destinations);
2769            }
2770        }
2771        // save manifest track comments if they exist
2772        if (!getComment().equals(NONE) ||
2773                !getCommentBothWithColor().equals(NONE) ||
2774                !getCommentPickupWithColor().equals(NONE) ||
2775                !getCommentSetoutWithColor().equals(NONE)) {
2776            Element comments = new Element(Xml.COMMENTS);
2777            Element track = new Element(Xml.TRACK);
2778            Element both = new Element(Xml.BOTH);
2779            Element pickup = new Element(Xml.PICKUP);
2780            Element setout = new Element(Xml.SETOUT);
2781            Element printManifest = new Element(Xml.PRINT_MANIFEST);
2782            Element printSwitchList = new Element(Xml.PRINT_SWITCH_LISTS);
2783
2784            comments.addContent(track);
2785            comments.addContent(both);
2786            comments.addContent(pickup);
2787            comments.addContent(setout);
2788            comments.addContent(printManifest);
2789            comments.addContent(printSwitchList);
2790
2791            track.setAttribute(Xml.COMMENT, getComment());
2792            both.setAttribute(Xml.COMMENT, getCommentBothWithColor());
2793            pickup.setAttribute(Xml.COMMENT, getCommentPickupWithColor());
2794            setout.setAttribute(Xml.COMMENT, getCommentSetoutWithColor());
2795            printManifest.setAttribute(Xml.COMMENT, isPrintManifestCommentEnabled() ? Xml.TRUE : Xml.FALSE);
2796            printSwitchList.setAttribute(Xml.COMMENT, isPrintSwitchListCommentEnabled() ? Xml.TRUE : Xml.FALSE);
2797
2798            e.addContent(comments);
2799        }
2800        if (getReporter() != null) {
2801            e.setAttribute(Xml.READER, getReporter().getDisplayName());
2802        }
2803        return e;
2804    }
2805
2806    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
2807        InstanceManager.getDefault(LocationManagerXml.class).setDirty(true);
2808        firePropertyChange(p, old, n);
2809    }
2810
2811    /*
2812     * set the jmri.Reporter object associated with this location.
2813     *
2814     * @param reader jmri.Reporter object.
2815     */
2816    public void setReporter(Reporter r) {
2817        Reporter old = _reader;
2818        _reader = r;
2819        if (old != r) {
2820            setDirtyAndFirePropertyChange(TRACK_REPORTER_CHANGED_PROPERTY, old, r);
2821        }
2822    }
2823
2824    /*
2825     * get the jmri.Reporter object associated with this location.
2826     *
2827     * @return jmri.Reporter object.
2828     */
2829    public Reporter getReporter() {
2830        return _reader;
2831    }
2832
2833    public String getReporterName() {
2834        if (getReporter() != null) {
2835            return getReporter().getDisplayName();
2836        }
2837        return "";
2838    }
2839
2840    private final static Logger log = LoggerFactory.getLogger(Track.class);
2841
2842}