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