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