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