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