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