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