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