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