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