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