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