001package jmri.jmrit.timetable;
002
003import java.util.ArrayList;
004import java.util.List;
005import java.util.Collections;
006import java.util.TreeMap;
007
008/**
009 * Provide data base management services.
010 * <p>
011 * The data structure was migrated from a MySQL database.  As such, it contains
012 * <strong>tables</strong> implemented as TreeMaps and <strong>records</strong>
013 * implemented as Classes.  The logical relationships are handled using foreign keys.
014 *
015 * <pre>
016 * Data Structure:
017 *   Layout -- Global data.
018 *     TrainTypes -- Assigned to trains for diagram colors.
019 *     Segments -- Used for division / sub-division arrangements.
020 *       Stations -- Any place a train can stop.
021 *     Schedules -- Basic information about a schedule.
022 *       Trains -- Train characteristics.
023 *         Stops -- A junction between a train and a station that contains arrival and departure times.
024 * </pre>
025 *
026 * @author Dave Sand Copyright (C) 2018
027 */
028public class TimeTableDataManager {
029
030    /**
031     * Create a TimeTableDataManager instance.
032     * @param loadData False to create an empty instance, otherwise load the data
033     */
034    public TimeTableDataManager(boolean loadData) {
035        jmri.InstanceManager.setDefault(TimeTableDataManager.class, this);
036        if (loadData) {
037            _lockCalculate = true;
038            if (!jmri.jmrit.timetable.configurexml.TimeTableXml.doLoad()) {
039                log.error("Unable to load the time table data");  // NOI18N
040            }
041            _lockCalculate = false;
042        }
043    }
044
045    /**
046     * Use the InstanceManager to only allow a single data manager instance.
047     * @return the current or new data manager.
048     */
049    public static TimeTableDataManager getDataManager() {
050        TimeTableDataManager dm = jmri.InstanceManager.getNullableDefault(TimeTableDataManager.class);
051        if (dm != null) {
052            return dm;
053        }
054        return new TimeTableDataManager(true);
055    }
056
057    // Exception key words
058    public static final String CLOCK_LT_1 = "FastClockLt1";    // NOI18N
059    public static final String DURATION_LT_0 = "DurationLt0";    // NOI18N
060    public static final String THROTTLES_LT_0 = "ThrottlesLt0";    // NOI18N
061    public static final String THROTTLES_IN_USE = "ThrottlesInUse";    // NOI18N
062    public static final String SCALE_NF = "ScaleNotFound";    // NOI18N
063    public static final String TIME_OUT_OF_RANGE = "TimeOutOfRange";    // NOI18N
064    public static final String SEGMENT_CHANGE_ERROR = "SegmentChangeError";    // NOI18N
065    public static final String DISTANCE_LT_0 = "DistanceLt0";    // NOI18N
066    public static final String SIDINGS_LT_0 = "SidingsLt0";    // NOI18N
067    public static final String STAGING_LT_0 = "StagingLt0";    // NOI18N
068    public static final String STAGING_IN_USE = "StagingInUse";    // NOI18N
069    public static final String START_HOUR_RANGE = "StartHourRange";    // NOI18N
070    public static final String DURATION_RANGE = "DurationRange";    // NOI18N
071    public static final String DEFAULT_SPEED_LT_0 = "DefaultSpeedLt0";    // NOI18N
072    public static final String START_TIME_FORMAT = "StartTimeFormat";    // NOI18N
073    public static final String START_TIME_RANGE = "StartTimeRange";    // NOI18N
074    public static final String THROTTLE_RANGE = "ThrottleRange";    // NOI18N
075    public static final String STAGING_RANGE = "StagingRange";    // NOI18N
076    public static final String STOP_DURATION_LT_0 = "StopDurationLt0";    // NOI18N
077    public static final String NEXT_SPEED_LT_0 = "NextSpeedLt0";    // NOI18N
078    public static final String LAYOUT_HAS_CHILDREN = "LayoutHasChildren";    // NOI18N
079    public static final String TYPE_HAS_REFERENCE = "TypeHasReference";    // NOI18N
080    public static final String SEGMENT_HAS_CHILDREN = "SegmentHaSChildren";    // NOI18N
081    public static final String STATION_HAS_REFERENCE = "StationHasReference";    // NOI18N
082    public static final String SCHEDULE_HAS_CHILDREN = "ScheduleHasChildren";    // NOI18N
083    public static final String TRAIN_HAS_CHILDREN = "TrainHasChildren";    // NOI18N
084    public static final String TYPE_ADD_FAIL = "TypeAddFail";    // NOI18N
085    public static final String SEGMENT_ADD_FAIL = "SegmentAddFail";    // NOI18N
086    public static final String STATION_ADD_FAIL = "StationAddFail";    // NOI18N
087    public static final String SCHEDULE_ADD_FAIL = "ScheduleAddFail";    // NOI18N
088    public static final String TRAIN_ADD_FAIL = "TrainAddFail";    // NOI18N
089    public static final String STOP_ADD_FAIL = "StopAddFail";    // NOI18N
090
091    private TreeMap<Integer, Layout> _layoutMap = new TreeMap<>();
092    private TreeMap<Integer, TrainType> _trainTypeMap = new TreeMap<>();
093    private TreeMap<Integer, Segment> _segmentMap = new TreeMap<>();
094    private TreeMap<Integer, Station> _stationMap = new TreeMap<>();
095    private TreeMap<Integer, Schedule> _scheduleMap = new TreeMap<>();
096    private TreeMap<Integer, Train> _trainMap = new TreeMap<>();
097    private TreeMap<Integer, Stop> _stopMap = new TreeMap<>();
098
099    private List<SegmentStation> _segmentStations = new ArrayList<>();
100
101    boolean _lockCalculate = false;
102    public void setLockCalculate(boolean lock) {
103        _lockCalculate = lock;
104    }
105
106    // ------------ Map maintenance methods ------------ //
107
108    public void addLayout(int id, Layout newLayout) {
109        _layoutMap.put(id, newLayout);
110    }
111
112    public void addTrainType(int id, TrainType newTrainType) {
113        _trainTypeMap.put(id, newTrainType);
114    }
115
116    public void addSegment(int id, Segment newSegment) {
117        _segmentMap.put(id, newSegment);
118    }
119
120    /**
121     * Add a new station.
122     * Create a SegmentStation instance.
123     * Add it to the SegmentStation list.
124     * @param id map id.
125     * @param newStation the new station.
126     */
127    public void addStation(int id, Station newStation) {
128        _stationMap.put(id, newStation);
129        SegmentStation segmentStation = new SegmentStation(newStation.getSegmentId(), id);
130        if (!_segmentStations.contains(segmentStation)) {
131            _segmentStations.add(segmentStation);
132        }
133    }
134
135    public void addSchedule(int id, Schedule newSchedule) {
136        _scheduleMap.put(id, newSchedule);
137    }
138
139    public void addTrain(int id, Train newTrain) {
140        _trainMap.put(id, newTrain);
141    }
142
143    public void addStop(int id, Stop newStop) {
144        _stopMap.put(id, newStop);
145    }
146
147    /**
148     * Delete the layout if there are no train types, segments or schedules.
149     * @param id The layout id.
150     * @throws IllegalArgumentException LAYOUT_HAS_CHILDREN
151     */
152    public void deleteLayout(int id) {
153        if (!getTrainTypes(id, false).isEmpty()
154                || !getSegments(id, false).isEmpty()
155                || !getSchedules(id, false).isEmpty()) {
156            throw new IllegalArgumentException(LAYOUT_HAS_CHILDREN);
157        }
158        _layoutMap.remove(id);
159    }
160
161    /**
162     * Delete the train type if there are no train references.
163     * @param id The train type id.
164     * @throws IllegalArgumentException TYPE_HAS_REFERENCE
165     */
166    public void deleteTrainType(int id) {
167        if (!getTrains(0, id, false).isEmpty()) {
168            throw new IllegalArgumentException(TYPE_HAS_REFERENCE);
169        }
170        _trainTypeMap.remove(id);
171    }
172
173    /**
174     * Delete the segment if it has no stations.
175     * @param id The segment id.
176     * @throws IllegalArgumentException SEGMENT_HAS_CHILDREN
177     */
178    public void deleteSegment(int id) {
179        if (!getStations(id, false).isEmpty()) {
180            throw new IllegalArgumentException(SEGMENT_HAS_CHILDREN);
181        }
182        _segmentMap.remove(id);
183    }
184
185    /**
186     * Delete the station if there are no stop references.
187     * @param id The station id.
188     * @throws IllegalArgumentException STATION_HAS_REFERENCE
189     */
190    public void deleteStation(int id) {
191        if (!getStops(0, id, false).isEmpty()) {
192            throw new IllegalArgumentException(STATION_HAS_REFERENCE);
193        }
194
195        int segmentId = getStation(id).getSegmentId();
196        List<SegmentStation> list = new ArrayList<>();
197        for (SegmentStation segmentStation : _segmentStations) {
198            if (segmentStation.getStationId() == id && segmentStation.getSegmentId() == segmentId) {
199                list.add(segmentStation);
200            }
201        }
202        for (SegmentStation ss : list) {
203            _segmentStations.remove(ss);
204        }
205
206        _stationMap.remove(id);
207    }
208
209    /**
210     * Delete the schedule if it has no trains.
211     * @param id The schedule id.
212     * @throws IllegalArgumentException SCHEDULE_HAS_CHILDREN
213     */
214    public void deleteSchedule(int id) {
215        if (!getTrains(id, 0, false).isEmpty()) {
216            throw new IllegalArgumentException(SCHEDULE_HAS_CHILDREN);
217        }
218        _scheduleMap.remove(id);
219    }
220
221    /**
222     * Delete the train if it has no stops.
223     * @param id The train id.
224     * @throws IllegalArgumentException TRAIN_HAS_CHILDREN
225     */
226    public void deleteTrain(int id) {
227        if (!getStops(id, 0, false).isEmpty()) {
228            throw new IllegalArgumentException(TRAIN_HAS_CHILDREN);
229        }
230        _trainMap.remove(id);
231    }
232
233    /**
234     * Delete the stop and update train schedule.
235     * @param id The stop id.
236     */
237    public void deleteStop(int id) {
238        int trainId = getStop(id).getTrainId();
239        _stopMap.remove(id);
240        calculateTrain(trainId, true);
241    }
242
243    // ------------ Map access methods: get by id  ------------ //
244
245    public Layout getLayout(int id) {
246        return _layoutMap.get(id);
247    }
248
249    public TrainType getTrainType(int id) {
250        return _trainTypeMap.get(id);
251    }
252
253    public Segment getSegment(int id) {
254        return _segmentMap.get(id);
255    }
256
257    public Station getStation(int id) {
258        return _stationMap.get(id);
259    }
260
261    public Schedule getSchedule(int id) {
262        return _scheduleMap.get(id);
263    }
264
265    public Train getTrain(int id) {
266        return _trainMap.get(id);
267    }
268
269    public Stop getStop(int id) {
270        return _stopMap.get(id);
271    }
272
273    /**
274     * Get the last key from the map and add 1.
275     * @param type The record type which is used to select the appropriate map.
276     * @return the next id, or 0 if there is an error.
277     */
278    public int getNextId(String type) {
279        int nextId = 0;
280        switch (type) {
281            case "Layout":    // NOI18N
282                nextId = (_layoutMap.isEmpty()) ? 1 : _layoutMap.lastKey() + 1;
283                break;
284            case "TrainType": // NOI18N
285                nextId = (_trainTypeMap.isEmpty()) ? 1 : _trainTypeMap.lastKey() + 1;
286                break;
287            case "Segment":   // NOI18N
288                nextId = (_segmentMap.isEmpty()) ? 1 : _segmentMap.lastKey() + 1;
289                break;
290            case "Station":   // NOI18N
291                nextId = (_stationMap.isEmpty()) ? 1 : _stationMap.lastKey() + 1;
292                break;
293            case "Schedule":  // NOI18N
294                nextId = (_scheduleMap.isEmpty()) ? 1 : _scheduleMap.lastKey() + 1;
295                break;
296            case "Train":     // NOI18N
297                nextId = (_trainMap.isEmpty()) ? 1 : _trainMap.lastKey() + 1;
298                break;
299            case "Stop":      // NOI18N
300                nextId = (_stopMap.isEmpty()) ? 1 : _stopMap.lastKey() + 1;
301                break;
302            default:
303                log.error("getNextId: Invalid record type: {}", type);  // NOI18N
304        }
305        return nextId;
306    }
307
308    // ------------ Map access methods: get all entries or by foreign key ------------ //
309
310    /**
311     * Create a list of layouts
312     * @param sort If true, sort the resulting list
313     * @return a list of layouts
314     */
315    public List<Layout> getLayouts(boolean sort) {
316        // No foreign keys
317        ArrayList<Layout> list = new ArrayList<>(_layoutMap.values());
318        if (sort) {
319            Collections.sort(list, (o1, o2) -> o1.getLayoutName().compareTo(o2.getLayoutName()));
320        }
321        return list;
322    }
323
324    /**
325     * Create a list of train types
326     * @param fKeyLayout If non-zero, select the types that have the specified foreign key
327     * @param sort If true, sort the resulting list
328     * @return a list of train types
329     */
330    public List<TrainType> getTrainTypes(int fKeyLayout, boolean sort) {
331        ArrayList<TrainType> list = new ArrayList<>();
332        for (TrainType type : _trainTypeMap.values()) {
333            if (fKeyLayout == 0 || fKeyLayout == type.getLayoutId()) {
334                list.add(type);
335            }
336        }
337        if (sort) {
338            Collections.sort(list, (o1, o2) -> o1.getTypeName().compareTo(o2.getTypeName()));
339        }
340        return list;
341    }
342
343    /**
344     * Create a list of segments
345     * @param fKeyLayout If non-zero, select the segments that have the specified foreign key
346     * @param sort If true, sort the resulting list
347     * @return a list of segments
348     */
349    public List<Segment> getSegments(int fKeyLayout, boolean sort) {
350        ArrayList<Segment> list = new ArrayList<>();
351        for (Segment segment : _segmentMap.values()) {
352            if (fKeyLayout == 0 || fKeyLayout == segment.getLayoutId()) {
353                list.add(segment);
354            }
355        }
356        if (sort) {
357            Collections.sort(list, (o1, o2) -> o1.getSegmentName().compareTo(o2.getSegmentName()));
358        }
359        return list;
360    }
361
362    /**
363     * Create a list of stations
364     * @param fKeySegment If non-zero, select the stations that have the specified foreign key
365     * @param sort If true, sort the resulting list
366     * @return a list of stations
367     */
368    public List<Station> getStations(int fKeySegment, boolean sort) {
369        ArrayList<Station> list = new ArrayList<>();
370        for (Station station : _stationMap.values()) {
371            if (fKeySegment == 0 || fKeySegment == station.getSegmentId()) {
372                list.add(station);
373            }
374        }
375        if (sort) {
376            Collections.sort(list, (o1, o2) -> Double.compare(o1.getDistance(), o2.getDistance()));
377        }
378        return list;
379    }
380
381    /**
382     * Create a list of schedules
383     * @param fKeyLayout If non-zero, select the schedules that have the specified foreign key
384     * @param sort If true, sort the resulting list
385     * @return a list of schedules
386     */
387    public List<Schedule> getSchedules(int fKeyLayout, boolean sort) {
388        ArrayList<Schedule> list = new ArrayList<>();
389        for (Schedule schedule : _scheduleMap.values()) {
390            if (fKeyLayout == 0 || fKeyLayout == schedule.getLayoutId()) {
391                list.add(schedule);
392            }
393        }
394        if (sort) {
395            Collections.sort(list, (o1, o2) -> o1.getScheduleName().compareTo(o2.getScheduleName()));
396        }
397        return list;
398    }
399
400    /**
401     * Create a list of trains
402     * @param fKeySchedule If non-zero, select the trains that have the specified foreign key
403     * @param fKeyType If non-zero, select the trains that have the specified foreign key
404     * @param sort If true, sort the resulting list
405     * @return a list of trains
406     */
407    public List<Train> getTrains(int fKeySchedule, int fKeyType, boolean sort) {
408        ArrayList<Train> list = new ArrayList<>();
409        for (Train train : _trainMap.values()) {
410            if ((fKeySchedule == 0 && fKeyType == 0)
411                    || fKeySchedule == train.getScheduleId()
412                    || fKeyType == train.getTypeId()) {
413                list.add(train);
414            }
415        }
416        if (sort) {
417            Collections.sort(list, (o1, o2) -> o1.getTrainName().compareTo(o2.getTrainName()));
418        }
419        return list;
420    }
421
422    /**
423     * Create a list of stops
424     * @param fKeyTrain If non-zero, select the stops that have the specified foreign key
425     * @param fKeyStation If non-zero, select the stops that have the specified foreign key
426     * @param sort If true, sort the resulting list
427     * @return a list of stops
428     */
429    public List<Stop> getStops(int fKeyTrain, int fKeyStation, boolean sort) {
430        ArrayList<Stop> list = new ArrayList<>();
431        for (Stop stop : _stopMap.values()) {
432            if ((fKeyTrain == 0 && fKeyStation == 0)
433                    || fKeyTrain == stop.getTrainId()
434                    || fKeyStation == stop.getStationId()) {
435                list.add(stop);
436            }
437        }
438        if (sort) {
439            Collections.sort(list, (o1, o2) -> Integer.compare(o1.getSeq(), o2.getSeq()));
440        }
441        return list;
442    }
443
444    // ------------ Special Map access methods ------------ //
445
446    public Layout getLayoutForStop(int stopId) {
447        return getLayout(getSchedule(getTrain(getStop(stopId).getTrainId()).getScheduleId()).getLayoutId());
448    }
449
450    public List<SegmentStation> getSegmentStations(int layoutId) {
451        List<SegmentStation> list = new ArrayList<>();
452        for (SegmentStation segmentStation : _segmentStations) {
453            if (getSegment(segmentStation.getSegmentId()).getLayoutId() == layoutId) {
454                list.add(segmentStation);
455            }
456        }
457        Collections.sort(list, (o1, o2) -> o1.toString().compareTo(o2.toString()));
458        return list;
459    }
460
461    // ------------  Calculate Train Times ------------
462
463    /**
464     * Update the stops for all of the trains for this layout.
465     * Invoked by updates to fast clock speed, metric, scale and station distances.
466     * @param layoutId The id for the layout that has been updated.
467     * @param updateStops True for update
468     */
469    void calculateLayoutTrains(int layoutId, boolean updateStops) {
470        if (_lockCalculate) return;
471        for (Schedule schedule : getSchedules(layoutId, false)) {
472            calculateScheduleTrains(schedule.getScheduleId(), updateStops);
473        }
474    }
475
476    /**
477     * Update the stop times for all of the trains that use this schedule.
478     * @param scheduleId The id for the schedule that has been updated.
479     * @param updateStops True for update
480     */
481    void calculateScheduleTrains(int scheduleId, boolean updateStops) {
482        if (_lockCalculate) return;
483        for (Train train : getTrains(scheduleId, 0, false)) {
484            calculateTrain(train.getTrainId(), updateStops);
485        }
486    }
487
488    /**
489     * Calculate the arrival and departure times for all of the stops.
490     * @param trainId The id of the train to be updated.
491     * @param updateStops When true, update the arrive and depart times.
492     * @throws IllegalArgumentException when stop times are outside of the
493     * schedule times or a segment change failed.  The TIME_OUT_OF_RANGE
494     * exception message includes the stop id and train name.  The SEGMENT_CHANGE_ERROR
495     * message includes the segment name and the station name.  The tilde
496     * character is used as the string separator.
497     */
498    public void calculateTrain(int trainId, boolean updateStops) {
499        if (_lockCalculate) return;
500        Train train = getTrain(trainId);
501        Schedule schedule = getSchedule(train.getScheduleId());
502        Layout layout = getLayout(schedule.getLayoutId());
503        List<Stop> stops = getStops(trainId, 0, true);
504
505        double smile = layout.getScaleMK();
506        int startHH = schedule.getStartHour();
507        int duration = schedule.getDuration();
508        int currentTime = train.getStartTime();
509        int defaultSpeed = train.getDefaultSpeed();
510
511        int checkStart = startHH;
512        int checkDuration = duration;
513
514        String currentStationName = "";
515        double currentDistance = 0.0;
516        int currentSegment = 0;
517        int currentSpeed = 0;
518        int newArrive = 0;
519        int newDepart = 0;
520        int elapseTime = 0;
521        boolean firstStop = true;
522
523        for (Stop stop : stops) {
524            Station station = getStation(stop.getStationId());
525            Segment segment = getSegment(station.getSegmentId());
526            if (firstStop) {
527                newArrive = currentTime;
528                currentTime += stop.getDuration();
529                newDepart = currentTime;
530                currentDistance = station.getDistance();
531                currentSpeed = (stop.getNextSpeed() > 0) ? stop.getNextSpeed() : defaultSpeed;
532                currentStationName = station.getStationName();
533                currentSegment = segment.getSegmentId();
534
535                if (validateTime(checkStart, checkDuration, newArrive) && validateTime(checkStart, checkDuration, newDepart)) {
536                    if (updateStops) {
537                        stop.setArriveTime(newArrive);
538                        stop.setDepartTime(newDepart);
539                    }
540                } else {
541                    throw new IllegalArgumentException(String.format("%s~%d~%s", TIME_OUT_OF_RANGE, stop.getSeq(), train.getTrainName()));  // NOI18N
542                }
543                firstStop = false;
544                continue;
545            }
546
547            // Calculate times for remaining stops
548            double wrkDistance = Math.abs(currentDistance - station.getDistance());
549
550            // If the segment has changed, a new distance will need to be calculated.
551            if (segment.getSegmentId() != currentSegment) {
552                // Find the station in the current segment that has the same name
553                // as the station in the previous segment.
554                Station wrkStation = null;
555                for (Station findStation : getStations(segment.getSegmentId(), false)) {
556                    if (findStation.getStationName().equals(currentStationName)) {
557                        wrkStation = findStation;
558                        break;
559                    }
560                }
561                if (wrkStation == null) {
562                    throw new IllegalArgumentException(SEGMENT_CHANGE_ERROR);
563                }
564                wrkDistance = Math.abs(station.getDistance() - wrkStation.getDistance());
565            }
566
567            elapseTime = (int) Math.round(wrkDistance / smile / currentSpeed * 60);
568            if (elapseTime < 1) {
569                elapseTime = 1;
570            }
571            currentTime += elapseTime;
572            if (currentTime > 1439)
573                currentTime -= 1440;
574            newArrive = currentTime;
575            currentTime += stop.getDuration();
576            if (currentTime > 1439)
577                currentTime -= 1440;
578            newDepart = currentTime;
579
580            currentDistance = station.getDistance();
581            currentSpeed = (stop.getNextSpeed() > 0) ? stop.getNextSpeed() : defaultSpeed;
582            currentSegment = station.getSegmentId();
583            currentStationName = station.getStationName();
584
585            if (validateTime(checkStart, checkDuration, newArrive) && validateTime(checkStart, checkDuration, newDepart)) {
586                if (updateStops) {
587                    stop.setArriveTime(newArrive);
588                    stop.setDepartTime(newDepart);
589                }
590            } else {
591                throw new IllegalArgumentException(String.format("%s~%d~%s", TIME_OUT_OF_RANGE, stop.getSeq(), train.getTrainName()));  // NOI18N
592            }
593        }
594    }
595
596    /**
597     * Check to see if the supplied time is within the time range for the supplied schedule.
598     * If the duration is 24 hours, then all times are valid.
599     * Otherwise, we need to calculate the valid range, which can span midnight.
600     * @param checkStart The schedule start hour.
601     * @param checkDuration The schedule duration.
602     * @param checkTime The time value to be check.
603     * @return true if the time is valid.
604     */
605    public boolean validateTime(int checkStart, int checkDuration, int checkTime) {
606        if (checkDuration == 24 && checkTime < 1440) {
607            return true;
608        }
609
610        boolean dayWrap;
611        int lowLimit;
612        int highLimit;
613
614        if (checkStart + checkDuration > 24) {
615            dayWrap = true;
616            lowLimit = checkStart * 60;
617            highLimit = ((checkStart + checkDuration - 24) * 60) - 1;
618        } else {
619            dayWrap = false;
620            lowLimit = checkStart * 60;
621            highLimit = ((checkStart + checkDuration) * 60) - 1;
622        }
623
624        if (dayWrap) {
625            if (checkTime < 1440 && (checkTime >= lowLimit || checkTime <= highLimit)) {
626                return true;
627            }
628        } else {
629            if (checkTime < 1440 && (checkTime >= lowLimit && checkTime <= highLimit)) {
630                return true;
631            }
632        }
633        return false;
634    }
635
636    /**
637     * Internal class that provides a combined segment and station view.
638     */
639    public class SegmentStation {
640        private int _segmentId;
641        private int _stationId;
642
643        public SegmentStation(int segmentId, int stationId) {
644            _segmentId = segmentId;
645            _stationId = stationId;
646        }
647
648        public int getSegmentId() {
649            return _segmentId;
650        }
651
652        public int getStationId() {
653            return _stationId;
654        }
655
656        @Override
657        public String toString() {
658            return String.format("%s : %s", getSegment(_segmentId).getSegmentName(), getStation(_stationId).getStationName());  // NOI18N
659        }
660    }
661
662    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TimeTableDataManager.class);
663}