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