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