001package jmri.jmrit.operations.trains;
002
003import java.io.*;
004import java.nio.charset.StandardCharsets;
005import java.text.MessageFormat;
006import java.util.*;
007
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011import jmri.InstanceManager;
012import jmri.jmrit.operations.locations.Location;
013import jmri.jmrit.operations.locations.Track;
014import jmri.jmrit.operations.rollingstock.cars.*;
015import jmri.jmrit.operations.rollingstock.engines.Engine;
016import jmri.jmrit.operations.routes.Route;
017import jmri.jmrit.operations.routes.RouteLocation;
018import jmri.jmrit.operations.setup.Control;
019import jmri.jmrit.operations.setup.Setup;
020import jmri.jmrit.operations.trains.schedules.TrainSchedule;
021import jmri.jmrit.operations.trains.schedules.TrainScheduleManager;
022import jmri.jmrit.operations.trains.trainbuilder.TrainCommon;
023import jmri.util.FileUtil;
024
025/**
026 * Builds a switch list for a location on the railroad
027 *
028 * @author Daniel Boudreau (C) Copyright 2008, 2011, 2012, 2013, 2015, 2024,
029 *         2026
030 */
031public class TrainSwitchLists extends TrainCommon {
032
033    TrainManager trainManager = InstanceManager.getDefault(TrainManager.class);
034    private static final char FORM_FEED = '\f';
035    private static final boolean IS_PRINT_HEADER = true;
036
037    String messageFormatText = ""; // the text being formated in case there's an exception
038
039    /**
040     * Builds a switch list for a location showing the work by train arrival
041     * time. If not running in real time, new train work is appended to the end
042     * of the file. User has the ability to modify the text of the messages
043     * which can cause an IllegalArgumentException. Some messages have more
044     * arguments than the default message allowing the user to customize the
045     * message to their liking. There also an option to list all of the car work
046     * by track name. This option is only available in real time and is shown
047     * after the switch list by train.
048     *
049     * @param location The Location needing a switch list
050     */
051    public void buildSwitchList(Location location) {
052
053        boolean append = false; // add text to end of file when true
054        boolean checkFormFeed = true; // used to determine if FF needed between trains
055
056        // Append switch list data if not operating in real time
057        if (!Setup.isSwitchListRealTime()) {
058            if (!location.getStatus().equals(Location.MODIFIED) && !Setup.isSwitchListAllTrainsEnabled()) {
059                return; // nothing to add
060            }
061            append = location.getSwitchListState() == Location.SW_APPEND;
062            location.setSwitchListState(Location.SW_APPEND);
063        }
064
065        log.debug("Append: {} for location ({})", append, location.getName());
066
067        // create switch list file
068        File file = InstanceManager.getDefault(TrainManagerXml.class).createSwitchListFile(location.getName());
069
070        PrintWriter fileOut = null;
071        try {
072            fileOut = new PrintWriter(new BufferedWriter(
073                    new OutputStreamWriter(new FileOutputStream(file, append), StandardCharsets.UTF_8)), true);
074        } catch (IOException e) {
075            log.error("Can not open switchlist file: {}", e.getLocalizedMessage());
076            return;
077        }
078        try {
079            // build header
080            if (!append) {
081                newLine(fileOut, Setup.getRailroadName());
082                newLine(fileOut);
083                newLine(fileOut, MessageFormat.format(messageFormatText = TrainSwitchListText.getStringSwitchListFor(),
084                        new Object[]{location.getSplitName()}));
085                if (!location.getSwitchListCommentWithColor().isEmpty()) {
086                    newLine(fileOut, location.getSwitchListCommentWithColor());
087                }
088            } else {
089                newLine(fileOut);
090            }
091
092            // get a list of built trains sorted by arrival time
093            List<Train> trains = trainManager.getTrainsArrivingThisLocationList(location, true);
094            List<Train> trainsAdded = new ArrayList<>();
095            for (Train train : trains) {
096                if (!Setup.isSwitchListRealTime() && train.getSwitchListStatus().equals(Train.PRINTED)) {
097                    continue; // already printed this train
098                }
099                if (Setup.getSwitchListPageFormat().equals(Setup.PAGE_PER_TRAIN) &&
100                        Collections.frequency(trainsAdded, train) > 0) {
101                    continue;
102                }
103                Route route = train.getRoute();
104                // TODO throw exception? only built trains should be in the list, so no route is
105                // an error
106                if (route == null) {
107                    continue; // no route for this train
108                } // determine if train works this location
109                int count = Collections.frequency(trainsAdded, train);
110                boolean works = isThereWorkAtLocation(train, location);
111                if (!works && !Setup.isSwitchListAllTrainsEnabled()) {
112                    log.debug("No work for train ({}) at location ({})", train.getName(), location.getName());
113                    continue;
114                }
115                // we're now going to add to the switch list
116                if (checkFormFeed) {
117                    if (append && !Setup.getSwitchListPageFormat().equals(Setup.PAGE_NORMAL)) {
118                        fileOut.write(FORM_FEED);
119                    }
120                    if (Setup.isPrintValidEnabled()) {
121                        newLine(fileOut, getValid());
122                    }
123                } else if (!Setup.getSwitchListPageFormat().equals(Setup.PAGE_NORMAL)) {
124                    fileOut.write(FORM_FEED);
125                }
126                checkFormFeed = false; // done with FF for this train
127                _pickupCars = false; // when true there was a car pick up
128                _dropCars = false; // when true there was a car set out
129                int stops = 0;
130                boolean trainDone = false;
131                // get engine and car lists
132                List<Engine> engineList = engineManager.getByTrainBlockingList(train);
133                List<Car> carList = carManager.getByTrainDestinationList(train);
134                List<RouteLocation> routeList = route.getLocationsBySequenceList();
135                RouteLocation rlPrevious = null;
136                for (RouteLocation rl : routeList) {
137                    if (!rl.getSplitName().equals(location.getSplitName())) {
138                        rlPrevious = rl;
139                        if (Setup.getSwitchListPageFormat().equals(Setup.PAGE_PER_TRAIN)) {
140                            _pickupCars = false; // reset
141                            _dropCars = false;
142                        }
143                        continue;
144                    }
145
146                    if (train.getExpectedArrivalTime(rl).equals(Train.ALREADY_SERVICED) &&
147                            train.getCurrentRouteLocation() != rl) {
148                        trainDone = true;
149                    }
150
151                    if (count == stops || Setup.getSwitchListPageFormat().equals(Setup.PAGE_PER_TRAIN)) {
152                        if (rlPrevious == null ||
153                                !rl.getSplitName().equals(rlPrevious.getSplitName())) {
154                            // does train visit this location more than once?
155                            int visits = Collections.frequency(trains, train);
156                            if (visits == 1) {
157                                firstTimeMessages(fileOut, train, rl);
158                            } else {
159                                // multiple visits to this location
160                                multipleVisitMessages(fileOut, train, rl, rlPrevious, stops + 1, visits);
161                            }
162                        } else {
163                            // Does the train reverse direction?
164                            reverseDirectionMessage(fileOut, train, rl, rlPrevious);
165                        }
166                        printWork(fileOut, train, rl, carList, engineList);
167                        // done with work, now print summary for this location if we're done
168                        if (rl != train.getTrainTerminatesRouteLocation()) {
169                            RouteLocation nextRl = train.getRoute().getNextRouteLocation(rl);
170                            if (!rl.getSplitName().equals(nextRl.getSplitName())) {
171                                // print departure text if not a switcher
172                                if (!train.isLocalSwitcher() && !trainDone) {
173                                    departureMessages(fileOut, train, rl);
174                                }
175                                // report if no pick ups or set outs or train has left
176                                trainSummaryMessages(fileOut, train, location, trainDone, stops);
177                            }
178                        } else {
179                            // report if no pick ups or set outs or train has left
180                            trainSummaryMessages(fileOut, train, location, trainDone, stops);
181                        }
182                    }
183                    if (rl != train.getTrainTerminatesRouteLocation()) {
184                        RouteLocation nextRl = train.getRoute().getNextRouteLocation(rl);
185                        if (!rl.getSplitName().equals(nextRl.getSplitName())) {
186                            stops++;
187                        }
188                    }
189                    // save current location in case there's back to back location with the same name
190                    rlPrevious = rl;
191                }
192                trainsAdded.add(train);
193            }
194
195            // now report car movement by tracks at location
196            reportByTrack(fileOut, location);
197
198        } catch (
199
200        IllegalArgumentException e) {
201            newLine(fileOut, Bundle.getMessage("ErrorIllegalArgument",
202                    Bundle.getMessage("TitleSwitchListText"), e.getLocalizedMessage()));
203            newLine(fileOut, messageFormatText);
204            log.error("Illegal argument", e);
205        }
206
207        // Are there any cars that need to be found?
208        addCarsLocationUnknown(fileOut, !IS_MANIFEST);
209        fileOut.flush();
210        fileOut.close();
211        location.setStatus(Location.UPDATED);
212    }
213
214    private String getValid() {
215        String valid = MessageFormat.format(messageFormatText = TrainManifestText.getStringValid(),
216                new Object[]{getDate(true)});
217        if (Setup.isPrintTrainScheduleNameEnabled()) {
218            TrainSchedule sch = InstanceManager.getDefault(TrainScheduleManager.class).getActiveSchedule();
219            if (sch != null) {
220                valid = valid + " (" + sch.getName() + ")";
221            }
222        }
223        return valid;
224    }
225
226    /*
227     * Messages for the switch list when the train first arrives
228     */
229    private void firstTimeMessages(PrintWriter fileOut, Train train, RouteLocation rl) {
230        newLine(fileOut);
231        newLine(fileOut,
232                MessageFormat.format(messageFormatText = TrainSwitchListText.getStringScheduledWork(),
233                        new Object[]{train.getSplitName(), train.getDescription()}));
234        newLine(fileOut, getSwitchListTrainStatus(train, rl));
235    }
236
237    /*
238     * Messages when a train services the location two or more times
239     */
240    private void multipleVisitMessages(PrintWriter fileOut, Train train, RouteLocation rl, RouteLocation rlPrevious,
241            int stops, int visits) {
242        newLine(fileOut);
243        if (stops == 1) {
244            newLine(fileOut, MessageFormat.format(messageFormatText = TrainSwitchListText.getStringTrainVisits(),
245                    new Object[]{train.getName(), rl.getLocation().getName(), visits}));
246            firstTimeMessages(fileOut, train, rl);
247        } else {
248            String expectedArrivalTime = train.getExpectedArrivalTime(rl);
249            if (train.isTrainEnRoute()) {
250                if (expectedArrivalTime.equals(Train.ALREADY_SERVICED)) {
251                    // Visit number {0} for train ({1})
252                    newLine(fileOut,
253                            MessageFormat.format(
254                                    messageFormatText = TrainSwitchListText.getStringVisitNumberDone(),
255                                    new Object[]{stops, train.getSplitName(),
256                                            train.getDescription()}));
257                } else if (rl != train.getTrainTerminatesRouteLocation()) {
258                    // Visit number {0} for train ({1}) expect to arrive in {2}, arrives {3}bound
259                    newLine(fileOut, MessageFormat.format(
260                            messageFormatText = TrainSwitchListText.getStringVisitNumberDeparted(),
261                            new Object[]{stops, train.getSplitName(), expectedArrivalTime,
262                                    rl.getTrainDirectionString(), train.getDescription()}));
263                } else {
264                    // Visit number {0} for train ({1}) expect to arrive in {2}, terminates {3}
265                    newLine(fileOut,
266                            MessageFormat.format(
267                                    messageFormatText = TrainSwitchListText
268                                            .getStringVisitNumberTerminatesDeparted(),
269                                    new Object[]{stops, train.getSplitName(),
270                                            expectedArrivalTime, rl.getSplitName(), train.getDescription()}));
271                }
272            } else {
273                // train hasn't departed
274                if (rl != train.getTrainTerminatesRouteLocation()) {
275                    // Visit number {0} for train ({1}) expected arrival {2}, arrives {3}bound
276                    newLine(fileOut,
277                            MessageFormat.format(
278                                    messageFormatText = TrainSwitchListText.getStringVisitNumber(),
279                                    new Object[]{stops, train.getSplitName(),
280                                            expectedArrivalTime, rl.getTrainDirectionString(),
281                                            train.getDescription()}));
282                    if (Setup.isUseSwitchListDepartureTimeEnabled()) {
283                        // Departs {0} {1}bound at {2}
284                        newLine(fileOut, MessageFormat.format(
285                                messageFormatText = TrainSwitchListText.getStringDepartsAt(),
286                                new Object[]{splitString(rl.getName()),
287                                        rl.getTrainDirectionString(),
288                                        train.getExpectedDepartureTime(rl)}));
289                    }
290                } else {
291                    // Visit number {0} for train ({1}) expected arrival {2}, terminates {3}
292                    newLine(fileOut, MessageFormat.format(
293                            messageFormatText = TrainSwitchListText.getStringVisitNumberTerminates(),
294                            new Object[]{stops, train.getSplitName(), expectedArrivalTime,
295                                    rl.getSplitName(), train.getDescription()}));
296                }
297            }
298        }
299    }
300
301    private void reverseDirectionMessage(PrintWriter fileOut, Train train, RouteLocation rl, RouteLocation rlPrevious) {
302        // Does the train reverse direction?
303        if (rl.getTrainDirection() != rlPrevious.getTrainDirection() &&
304                !TrainSwitchListText.getStringTrainDirectionChange().isEmpty()) {
305            // Train ({0}) direction change, departs {1}bound
306            newLine(fileOut,
307                    MessageFormat.format(
308                            messageFormatText = TrainSwitchListText.getStringTrainDirectionChange(),
309                            new Object[]{train.getSplitName(), rl.getTrainDirectionString(),
310                                    train.getDescription(), train.getTrainTerminatesName()}));
311        }
312    }
313
314    private void printWork(PrintWriter fileOut, Train train, RouteLocation rl, List<Car> carList,
315            List<Engine> engineList) {
316        // add route location comment
317        if (Setup.isSwitchListRouteLocationCommentEnabled() && !rl.getComment().trim().isEmpty()) {
318            newLine(fileOut, rl.getCommentWithColor());
319        }
320
321        printTrackComments(fileOut, rl, carList, !IS_MANIFEST);
322
323        if (isThereWorkAtLocation(carList, engineList, rl)) {
324            // now print out the work for this location
325            if (Setup.getManifestFormat().equals(Setup.STANDARD_FORMAT)) {
326                pickupEngines(fileOut, engineList, rl, !IS_MANIFEST);
327                // if switcher show loco drop at end of list
328                if (train.isLocalSwitcher() || Setup.isPrintLocoLastEnabled()) {
329                    blockCarsByTrack(fileOut, train, carList, rl, IS_PRINT_HEADER, !IS_MANIFEST);
330                    dropEngines(fileOut, engineList, rl, !IS_MANIFEST);
331                } else {
332                    dropEngines(fileOut, engineList, rl, !IS_MANIFEST);
333                    blockCarsByTrack(fileOut, train, carList, rl, IS_PRINT_HEADER, !IS_MANIFEST);
334                }
335            } else if (Setup.getManifestFormat().equals(Setup.TWO_COLUMN_FORMAT)) {
336                // if switcher show loco drop at end of list
337                if (train.isLocalSwitcher() ||
338                        Setup.isPrintLocoLastEnabled() && train.getTrainTerminatesRouteLocation() == rl) {
339                    blockCarsTwoColumn(fileOut, train, carList, rl, IS_PRINT_HEADER, !IS_MANIFEST);
340                    blockLocosTwoColumn(fileOut, engineList, rl, !IS_MANIFEST);
341                } else {
342                    blockLocosTwoColumn(fileOut, engineList, rl, !IS_MANIFEST);
343                    blockCarsTwoColumn(fileOut, train, carList, rl, IS_PRINT_HEADER, !IS_MANIFEST);
344                }
345            } else {
346                // if switcher show loco drop at end of list
347                if (train.isLocalSwitcher() ||
348                        Setup.isPrintLocoLastEnabled() && train.getTrainTerminatesRouteLocation() == rl) {
349                    blockCarsByTrackNameTwoColumn(fileOut, train, carList, rl, IS_PRINT_HEADER, !IS_MANIFEST);
350                    blockLocosByTrackNameTwoColumn(fileOut, engineList, rl, !IS_MANIFEST);
351                } else {
352                    blockLocosByTrackNameTwoColumn(fileOut, engineList, rl, !IS_MANIFEST);
353                    blockCarsByTrackNameTwoColumn(fileOut, train, carList, rl, IS_PRINT_HEADER, !IS_MANIFEST);
354                }
355            }
356            // print horizontal line if there was work and enabled
357            printHorizontalLine3(fileOut, !IS_MANIFEST);
358        }
359    }
360
361    /*
362     * Train departure messages at the end of the switch list
363     */
364    private void departureMessages(PrintWriter fileOut, Train train, RouteLocation rl) {
365        String trainDeparts = "";
366        if (Setup.isPrintLoadsAndEmptiesEnabled()) {
367            int emptyCars = train.getNumberEmptyCarsInTrain(rl);
368            // Train departs {0} {1}bound with {2} loads, {3} empties, {4} {5}, {6} tons
369            trainDeparts = MessageFormat.format(TrainSwitchListText.getStringTrainDepartsLoads(),
370                    new Object[]{rl.getSplitName(),
371                            rl.getTrainDirectionString(),
372                            train.getNumberCarsInTrain(rl) - emptyCars, emptyCars,
373                            train.getTrainLength(rl), Setup.getLengthUnit().toLowerCase(),
374                            train.getTrainWeight(rl), train.getTrainTerminatesName(),
375                            train.getSplitName()});
376        } else {
377            // Train departs {0} {1}bound with {2} cars, {3} {4}, {5} tons
378            trainDeparts = MessageFormat.format(TrainSwitchListText.getStringTrainDepartsCars(),
379                    new Object[]{rl.getSplitName(),
380                            rl.getTrainDirectionString(), train.getNumberCarsInTrain(rl),
381                            train.getTrainLength(rl), Setup.getLengthUnit().toLowerCase(),
382                            train.getTrainWeight(rl), train.getTrainTerminatesName(),
383                            train.getSplitName()});
384        }
385        newLine(fileOut, trainDeparts);
386    }
387
388    private void trainSummaryMessages(PrintWriter fileOut, Train train, Location location, boolean trainDone,
389            int stops) {
390        if (trainDone && !_pickupCars && !_dropCars) {
391            // Default message: Train ({0}) has serviced this location
392            newLine(fileOut, MessageFormat.format(messageFormatText = TrainSwitchListText.getStringTrainDone(),
393                    new Object[]{train.getSplitName(), train.getDescription(),
394                            location.getSplitName()}));
395        } else {
396            if (!_pickupCars) {
397                // Default message: No car pick ups for train ({0}) at this location
398                newLine(fileOut,
399                        MessageFormat.format(messageFormatText = TrainSwitchListText.getStringNoCarPickUps(),
400                                new Object[]{train.getSplitName(), train.getDescription(),
401                                        location.getSplitName()}));
402            }
403            if (!_dropCars) {
404                // Default message: No car set outs for train ({0}) at this location
405                newLine(fileOut,
406                        MessageFormat.format(messageFormatText = TrainSwitchListText.getStringNoCarDrops(),
407                                new Object[]{train.getSplitName(), train.getDescription(),
408                                        location.getSplitName()}));
409            }
410        }
411    }
412
413    private void reportByTrack(PrintWriter fileOut, Location location) {
414        if (Setup.isPrintTrackSummaryEnabled() && Setup.isSwitchListRealTime()) {
415            clearUtilityCarTypes(); // list utility cars by quantity
416            if (Setup.getSwitchListPageFormat().equals(Setup.PAGE_NORMAL)) {
417                newLine(fileOut);
418                newLine(fileOut);
419            } else {
420                fileOut.write(FORM_FEED);
421            }
422            newLine(fileOut,
423                    MessageFormat.format(messageFormatText = TrainSwitchListText.getStringSwitchListByTrack(),
424                            new Object[]{location.getSplitName()}));
425
426            // we only need the cars delivered to or at this location
427            List<Car> rsList = carManager.getByTrainList();
428            List<Car> carList = new ArrayList<>();
429            for (Car rs : rsList) {
430                if ((rs.getLocation() != null &&
431                        rs.getLocation().getSplitName().equals(location.getSplitName())) ||
432                        (rs.getDestination() != null &&
433                                rs.getSplitDestinationName().equals(location.getSplitName())))
434                    carList.add(rs);
435            }
436            // locations and tracks can have "similar" names, only list track names once
437            List<String> trackNames = new ArrayList<>();
438            for (Location loc : locationManager.getLocationsByNameList()) {
439                if (!loc.getSplitName().equals(location.getSplitName()))
440                    continue;
441                for (Track track : loc.getTracksByBlockingOrderList(null)) {
442                    String trackName = track.getSplitName();
443                    if (trackNames.contains(trackName))
444                        continue;
445                    trackNames.add(trackName);
446
447                    String trainName = ""; // for printing train message once
448                    newLine(fileOut);
449                    newLine(fileOut, trackName); // print out just the track name
450                    // now show the cars pickup and holds for this track
451                    for (Car car : carList) {
452                        if (!car.getSplitTrackName().equals(trackName) || car.isLocalMove()) {
453                            continue;
454                        }
455                        // is the car scheduled for pickup?
456                        if (car.getRouteLocation() != null) {
457                            if (car.getRouteLocation().getLocation().getSplitName()
458                                    .equals(location.getSplitName())) {
459                                // cars are sorted by train name, print train message once
460                                if (!trainName.equals(car.getTrainName())) {
461                                    trainName = car.getTrainName();
462                                    newLine(fileOut, MessageFormat.format(
463                                            messageFormatText = TrainSwitchListText.getStringScheduledWork(),
464                                            new Object[]{car.getTrainName(), car.getTrain().getDescription()}));
465                                    printPickupCarHeader(fileOut, !IS_MANIFEST, !IS_TWO_COLUMN_TRACK);
466                                }
467                                if (car.isUtility()) {
468                                    pickupUtilityCars(fileOut, carList, car, false, !IS_MANIFEST);
469                                } else {
470                                    pickUpCar(fileOut, car, !IS_MANIFEST);
471                                }
472                            }
473                            // car holds
474                        } else if (car.isUtility()) {
475                            String s = pickupUtilityCars(carList, car, !IS_MANIFEST, !IS_TWO_COLUMN_TRACK);
476                            if (s != null) {
477                                newLine(fileOut, TrainSwitchListText.getStringHoldCar().split("\\{")[0] + s.trim()); // NOI18N
478                            }
479                        } else {
480                            newLine(fileOut,
481                                    MessageFormat.format(messageFormatText = TrainSwitchListText.getStringHoldCar(),
482                                            new Object[]{
483                                                    padAndTruncateIfNeeded(car.getRoadName(),
484                                                            InstanceManager.getDefault(CarRoads.class)
485                                                                    .getMaxNameLength()),
486                                                    padAndTruncateIfNeeded(
487                                                            TrainCommon.splitString(car.getNumber()),
488                                                            Control.max_len_string_print_road_number),
489                                                    padAndTruncateIfNeeded(
490                                                            car.getTypeName().split(TrainCommon.HYPHEN)[0],
491                                                            InstanceManager.getDefault(CarTypes.class)
492                                                                    .getMaxNameLength()),
493                                                    padAndTruncateIfNeeded(
494                                                            car.getLength() + Setup.getLengthUnitAbv(),
495                                                            Control.max_len_string_length_name),
496                                                    padAndTruncateIfNeeded(car.getLoadName(),
497                                                            InstanceManager.getDefault(CarLoads.class)
498                                                                    .getMaxNameLength()),
499                                                    padAndTruncateIfNeeded(trackName,
500                                                            locationManager.getMaxTrackNameLength()),
501                                                    padAndTruncateIfNeeded(car.getColor(), InstanceManager
502                                                            .getDefault(CarColors.class).getMaxNameLength())}));
503                        }
504                    }
505                    // now do set outs at this location
506                    trainName = ""; // for printing train message once
507                    for (Car car : carList) {
508                        if (!car.getSplitDestinationTrackName().equals(trackName) || car.isLocalMove()) {
509                            continue;
510                        }
511                        if (car.getRouteDestination() != null &&
512                                car.getRouteDestination().getLocation().getSplitName()
513                                        .equals(location.getSplitName())) {
514                            // cars are sorted by train name, print train message once
515                            if (!trainName.equals(car.getTrainName())) {
516                                trainName = car.getTrainName();
517                                newLine(fileOut, MessageFormat.format(
518                                        messageFormatText = TrainSwitchListText.getStringScheduledWork(),
519                                        new Object[]{car.getTrainName(), car.getTrain().getDescription()}));
520                                printDropCarHeader(fileOut, !IS_MANIFEST, !IS_TWO_COLUMN_TRACK);
521                            }
522                            if (car.isUtility()) {
523                                setoutUtilityCars(fileOut, carList, car, false, !IS_MANIFEST);
524                            } else {
525                                dropCar(fileOut, car, !IS_MANIFEST);
526                            }
527                        }
528                    }
529                    // only local moves
530                    trainName = ""; // for printing train message once
531                    for (Car car : carList) {
532                        if (!car.getSplitDestinationTrackName().equals(trackName) || !car.isLocalMove()) {
533                            continue;
534                        }
535                        if (car.getRouteDestination() != null &&
536                                car.getRouteDestination().getLocation().getSplitName()
537                                        .equals(location.getSplitName())) {
538                            // cars are sorted by train name, print train message once
539                            if (!trainName.equals(car.getTrainName())) {
540                                trainName = car.getTrainName();
541                                newLine(fileOut, MessageFormat.format(
542                                        messageFormatText = TrainSwitchListText.getStringScheduledWork(),
543                                        new Object[]{car.getTrainName(), car.getTrain().getDescription()}));
544                                printLocalCarMoveHeader(fileOut, !IS_MANIFEST);
545                            }
546                            if (car.isUtility()) {
547                                setoutUtilityCars(fileOut, carList, car, false, !IS_MANIFEST);
548                            } else {
549                                dropCar(fileOut, car, !IS_MANIFEST);
550                            }
551                        }
552                    }
553                }
554            }
555        }
556    }
557
558    public void printSwitchList(Location location, boolean isPreview) {
559        File switchListFile = InstanceManager.getDefault(TrainManagerXml.class).getSwitchListFile(location.getName());
560        if (!switchListFile.exists()) {
561            log.warn("Switch list file missing for location ({})", location.getName());
562            return;
563        }
564        if (isPreview && Setup.isManifestEditorEnabled()) {
565            TrainUtilities.openDesktop(switchListFile);
566        } else {
567            TrainPrintManifest.printReport(switchListFile, location.getName(), isPreview, Setup.getFontName(),
568                    FileUtil.getExternalFilename(Setup.getManifestLogoURL()), location.getDefaultPrinterName(),
569                    Setup.getSwitchListOrientation(), Setup.getManifestFontSize(), Setup.isPrintPageHeaderEnabled(),
570                    Setup.getPrintDuplexSides());
571        }
572        if (!isPreview) {
573            location.setStatus(Location.PRINTED);
574            location.setSwitchListState(Location.SW_PRINTED);
575        }
576    }
577
578    private static final Logger log = LoggerFactory.getLogger(TrainSwitchLists.class);
579}