001package jmri.web.servlet.operations; 002 003import com.fasterxml.jackson.databind.JsonNode; 004import com.fasterxml.jackson.databind.ObjectMapper; 005 006import java.io.IOException; 007import java.util.*; 008import java.util.Map.Entry; 009 010import jmri.InstanceManager; 011import jmri.jmrit.operations.rollingstock.cars.Car; 012import jmri.jmrit.operations.rollingstock.cars.CarManager; 013import jmri.jmrit.operations.routes.RouteLocation; 014import jmri.jmrit.operations.setup.Setup; 015import jmri.jmrit.operations.trains.*; 016import jmri.jmrit.operations.trains.schedules.TrainScheduleManager; 017import jmri.server.json.JSON; 018import jmri.server.json.operations.JsonOperations; 019 020import org.apache.commons.text.StringEscapeUtils; 021import org.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024/** 025 * 026 * @author Randall Wood 027 */ 028public class HtmlManifest extends HtmlTrainCommon { 029 030 protected ObjectMapper mapper; 031 private JsonNode jsonManifest = null; 032 private static final Logger log = LoggerFactory.getLogger(HtmlManifest.class); 033 034 public HtmlManifest(Locale locale, Train train) throws IOException { 035 super(locale, train); 036 this.mapper = new ObjectMapper(); 037 this.resourcePrefix = "Manifest"; 038 } 039 040 // TODO cache the results so a quick check that if the JsonManifest file is not 041 // newer than the Html manifest, the cached copy is returned instead. 042 public String getLocations() throws IOException { 043 // build manifest from JSON manifest 044 if (this.getJsonManifest() == null) { 045 return "Error manifest file not found for this train"; 046 } 047 StringBuilder builder = new StringBuilder(); 048 JsonNode locations = this.getJsonManifest().path(JSON.LOCATIONS); 049 String previousLocationName = null; 050 boolean hasWork; 051 for (JsonNode location : locations) { 052 RouteLocation routeLocation = train.getRoute().getRouteLocationById(location.path(JSON.NAME).textValue()); 053 log.debug("Processing {} ({})", routeLocation.getName(), location.path(JSON.NAME).textValue()); 054 String routeLocationName = location.path(JSON.USERNAME).textValue(); 055 builder.append(String.format(locale, strings.getProperty("LocationStart"), routeLocation.getId())); // NOI18N 056 hasWork = (location.path(JsonOperations.CARS).path(JSON.ADD).size() > 0 057 || location.path(JsonOperations.CARS).path(JSON.REMOVE).size() > 0 058 || location.path(JSON.ENGINES).path(JSON.ADD).size() > 0 || location.path(JSON.ENGINES).path( 059 JSON.REMOVE).size() > 0); 060 if (hasWork && !routeLocationName.equals(previousLocationName)) { 061 if (!train.isShowArrivalAndDepartureTimesEnabled()) { 062 builder.append(String.format(locale, strings.getProperty("ScheduledWorkAt"), routeLocationName)); // NOI18N 063 } else if (routeLocation == train.getTrainDepartsRouteLocation()) { 064 builder.append(String.format(locale, strings.getProperty("WorkDepartureTime"), routeLocationName, 065 train.getFormatedDepartureTime())); // NOI18N 066 } else if (!routeLocation.getDepartureTimeHourMinutes().equals(RouteLocation.NONE)) { 067 builder.append(String.format(locale, strings.getProperty("WorkDepartureTime"), routeLocationName, 068 routeLocation.getFormatedDepartureTime())); // NOI18N 069 } else if (Setup.isUseDepartureTimeEnabled() 070 && routeLocation != train.getTrainTerminatesRouteLocation()) { 071 builder.append(String.format(locale, strings.getProperty("WorkDepartureTime"), routeLocationName, 072 train.getExpectedDepartureTime(routeLocation))); // NOI18N 073 } else if (!train.getExpectedArrivalTime(routeLocation).equals(Train.ALREADY_SERVICED)) { // NOI18N 074 builder.append(String.format(locale, strings.getProperty("WorkArrivalTime"), routeLocationName, 075 train.getExpectedArrivalTime(routeLocation))); // NOI18N 076 } else { 077 builder.append(String.format(locale, strings.getProperty("ScheduledWorkAt"), routeLocationName)); // NOI18N 078 } 079 // add route comment 080 if (!location.path(JSON.COMMENT).textValue().isBlank()) { 081 builder.append(String.format(locale, strings.getProperty("RouteLocationComment"), 082 location.path(JSON.COMMENT).textValue())); 083 } 084 085 // add location comment 086 if (Setup.isPrintLocationCommentsEnabled() 087 && !location.path(JSON.LOCATION).path(JSON.COMMENT).textValue().isBlank()) { 088 builder.append(String.format(locale, strings.getProperty("LocationComment"), location.path( 089 JSON.LOCATION).path(JSON.COMMENT).textValue())); 090 } 091 092 // add track comments 093 builder.append( 094 getTrackComments(location.path(JSON.TRACK), location.path(JsonOperations.CARS))); 095 } 096 097 previousLocationName = routeLocationName; 098 099 // engine change or helper service? 100 if (location.path(JSON.OPTIONS).size() > 0) { 101 boolean changeEngines = false; 102 boolean changeCaboose = false; 103 for (JsonNode option : location.path(JSON.OPTIONS)) { 104 switch (option.asText()) { 105 case JSON.CHANGE_ENGINES: 106 changeEngines = true; 107 break; 108 case JSON.CHANGE_CABOOSE: 109 changeCaboose = true; 110 break; 111 case JSON.ADD_HELPERS: 112 builder.append(String.format(strings.getProperty("AddHelpersAt"), routeLocationName)); 113 break; 114 case JSON.REMOVE_HELPERS: 115 builder.append(String.format(strings.getProperty("RemoveHelpersAt"), routeLocationName)); 116 break; 117 default: 118 break; 119 } 120 } 121 if (changeEngines && changeCaboose) { 122 builder.append(String.format(strings.getProperty("LocoAndCabooseChangeAt"), routeLocationName)); // NOI18N 123 } else if (changeEngines) { 124 builder.append(String.format(strings.getProperty("LocoChangeAt"), routeLocationName)); // NOI18N 125 } else if (changeCaboose) { 126 builder.append(String.format(strings.getProperty("CabooseChangeAt"), routeLocationName)); // NOI18N 127 } 128 } 129 130 builder.append(pickupEngines(location.path(JSON.ENGINES).path(JSON.ADD))); 131 builder.append(blockCars(location.path(JsonOperations.CARS), routeLocation, true)); 132 builder.append(dropEngines(location.path(JSON.ENGINES).path(JSON.REMOVE))); 133 134 if (routeLocation != train.getTrainTerminatesRouteLocation()) { 135 // Is the next location the same as the current? 136 RouteLocation rlNext = train.getRoute().getNextRouteLocation(routeLocation); 137 if (!routeLocationName.equals(rlNext.getSplitName())) { 138 if (hasWork) { 139 if (!Setup.isPrintLoadsAndEmptiesEnabled()) { 140 // Message format: Train departs Boston Westbound with 12 cars, 450 feet, 3000 tons 141 builder.append(String.format(strings.getProperty("TrainDepartsCars"), routeLocationName, 142 strings.getProperty("Heading" 143 + Setup.getDirectionString(location.path(JSON.TRAIN_DIRECTION).intValue())), 144 location.path(JSON.LENGTH).path(JSON.LENGTH).intValue(), location.path(JSON.LENGTH) 145 .path(JSON.UNIT).asText().toLowerCase(), location.path(JSON.WEIGHT) 146 .intValue(), location.path(JsonOperations.CARS).path(JSON.TOTAL).intValue())); 147 } else { 148 // Message format: Train departs Boston Westbound with 4 loads, 8 empties, 450 feet, 3000 149 // tons 150 builder.append(String.format(strings.getProperty("TrainDepartsLoads"), routeLocationName, 151 strings.getProperty("Heading" 152 + Setup.getDirectionString(location.path(JSON.TRAIN_DIRECTION).intValue())), 153 location.path(JSON.LENGTH).path(JSON.LENGTH).intValue(), location.path(JSON.LENGTH) 154 .path(JSON.UNIT).asText().toLowerCase(), location.path(JSON.WEIGHT) 155 .intValue(), location.path(JsonOperations.CARS).path(JSON.LOADS).intValue(), location 156 .path(JsonOperations.CARS).path(JSON.EMPTIES).intValue())); 157 } 158 } else { 159 log.debug("No work ({})", routeLocation.getComment()); 160 if (routeLocation.getComment().isBlank()) { 161 // no route comment, no work at this location 162 if (train.isShowArrivalAndDepartureTimesEnabled()) { 163 if (routeLocation == train.getTrainDepartsRouteLocation()) { 164 builder.append(String.format(locale, strings 165 .getProperty("NoScheduledWorkAtWithDepartureTime"), routeLocationName, 166 train.getFormatedDepartureTime())); 167 } else if (!routeLocation.getDepartureTimeHourMinutes().isEmpty()) { 168 builder.append(String.format(locale, strings 169 .getProperty("NoScheduledWorkAtWithDepartureTime"), routeLocationName, 170 routeLocation.getFormatedDepartureTime())); 171 } else if (Setup.isUseDepartureTimeEnabled()) { 172 builder.append(String.format(locale, strings 173 .getProperty("NoScheduledWorkAtWithDepartureTime"), routeLocationName, 174 location.path(JSON.EXPECTED_DEPARTURE))); 175 } else { // fall back to generic no scheduled work message 176 builder.append(String.format(locale, strings.getProperty("NoScheduledWorkAt"), 177 routeLocationName)); 178 } 179 } else { 180 builder.append(String.format(locale, strings.getProperty("NoScheduledWorkAt"), 181 routeLocationName)); 182 } 183 } else { 184 // if a route comment, then only use location name and route comment, useful for passenger 185 // trains 186 if (!routeLocation.getComment().isBlank()) { 187 builder.append(String.format(locale, strings.getProperty("CommentAt"), // NOI18N 188 routeLocationName, StringEscapeUtils 189 .escapeHtml4(routeLocation.getCommentWithColor()))); 190 } 191 if (train.isShowArrivalAndDepartureTimesEnabled()) { 192 if (routeLocation == train.getTrainDepartsRouteLocation()) { 193 builder.append(String.format(locale, strings 194 .getProperty("CommentAtWithDepartureTime"), routeLocationName, train // NOI18N 195 .getFormatedDepartureTime(), StringEscapeUtils 196 .escapeHtml4(routeLocation.getComment()))); 197 } else if (!routeLocation.getDepartureTimeHourMinutes().equals(RouteLocation.NONE)) { 198 builder.append(String.format(locale, strings 199 .getProperty("CommentAtWithDepartureTime"), routeLocationName, // NOI18N 200 routeLocation.getFormatedDepartureTime(), StringEscapeUtils 201 .escapeHtml4(routeLocation.getComment()))); 202 } else if (Setup.isUseDepartureTimeEnabled() && 203 !routeLocation.getComment().equals(RouteLocation.NONE)) { 204 builder.append(String.format(locale, strings 205 .getProperty("NoScheduledWorkAtWithDepartureTime"), routeLocationName, // NOI18N 206 train.getExpectedDepartureTime(routeLocation))); 207 } 208 } 209 } 210 // add location comment 211 if (Setup.isPrintLocationCommentsEnabled() 212 && !routeLocation.getLocation().getComment().isEmpty()) { 213 builder.append(String.format(locale, strings.getProperty("LocationComment"), 214 StringEscapeUtils.escapeHtml4(routeLocation.getLocation().getCommentWithColor()))); 215 } 216 } 217 } 218 } else { 219 builder.append(String.format(strings.getProperty("TrainTerminatesIn"), routeLocationName)); 220 } 221 } 222 return builder.toString(); 223 } 224 225 protected String blockCars(JsonNode cars, RouteLocation location, boolean isManifest) { 226 StringBuilder builder = new StringBuilder(); 227 log.debug("Cars is {}", cars); 228 229 //copy the adds into a sortable arraylist 230 ArrayList<JsonNode> adds = new ArrayList<JsonNode>(); 231 cars.path(JSON.ADD).forEach(adds::add); 232 233 //sort if requested 234 if (adds.size() > 0 && Setup.isSortByTrackNameEnabled()) { 235 adds.sort(Comparator.comparing(o -> o.path("location").path("track").path("userName").asText())); 236 } 237 //format each car for output 238 // use truncated format if there's a switch list 239 for (JsonNode car : adds) { 240 if (!this.isLocalMove(car)) { 241 if (this.isUtilityCar(car)) { 242 builder.append(pickupUtilityCars(adds, car, location, isManifest)); 243 } else { 244 String[] messageFormat; 245 if (isManifest && 246 Setup.isPrintTruncateManifestEnabled() && 247 location.getLocation().isSwitchListEnabled()) { 248 messageFormat = Setup.getPickupTruncatedManifestMessageFormat(); 249 } else { 250 messageFormat = Setup.getPickupManifestMessageFormat(); 251 } 252 Setup.stringToTagConversion(messageFormat); 253 builder.append(pickUpCar(car, messageFormat)); 254 } 255 } 256 } 257 258 //copy the drops into a sortable arraylist 259 ArrayList<JsonNode> drops = new ArrayList<JsonNode>(); 260 cars.path(JSON.REMOVE).forEach(drops::add); 261 262 for (JsonNode car : drops) { 263 boolean local = isLocalMove(car); 264 if (this.isUtilityCar(car)) { 265 builder.append(setoutUtilityCars(drops, car, location, isManifest)); 266 } else if (isManifest && 267 Setup.isPrintTruncateManifestEnabled() && 268 location.getLocation().isSwitchListEnabled() && 269 !train.isLocalSwitcher()) { 270 builder.append(dropCar(car, Setup.getDropTruncatedManifestMessageFormat(), local)); 271 } else { 272 String[] format; 273 if (isManifest) { 274 format = (!local) ? Setup.getDropManifestMessageFormat() : Setup 275 .getLocalManifestMessageFormat(); 276 } else { 277 format = (!local) ? Setup.getDropSwitchListMessageFormat() : Setup 278 .getLocalSwitchListMessageFormat(); 279 } 280 Setup.stringToTagConversion(format); 281 builder.append(dropCar(car, format, local)); 282 } 283 } 284 return String.format(locale, strings.getProperty("CarsList"), builder.toString()); 285 } 286 287 protected String pickupUtilityCars(ArrayList<JsonNode> jnCars, JsonNode jnCar, RouteLocation location, 288 boolean isManifest) { 289 List<Car> cars = getCarList(jnCars); 290 Car car = getCar(jnCar); 291 return pickupUtilityCars(cars, car, isManifest); 292 } 293 294 protected String setoutUtilityCars(ArrayList<JsonNode> jnCars, JsonNode jnCar, RouteLocation location, 295 boolean isManifest) { 296 List<Car> cars = getCarList(jnCars); 297 Car car = getCar(jnCar); 298 return setoutUtilityCars(cars, car, isManifest); 299 } 300 301 protected List<Car> getCarList(ArrayList<JsonNode> jnCars) { 302 List<Car> cars = new ArrayList<>(); 303 for (JsonNode kar : jnCars) { 304 cars.add(getCar(kar)); 305 } 306 return cars; 307 } 308 309 protected Car getCar(JsonNode jnCar) { 310 String id = jnCar.path(JSON.NAME).asText(); 311 Car car = InstanceManager.getDefault(CarManager.class).getById(id); 312 return car; 313 } 314 315 protected String pickUpCar(JsonNode car, String[] format) { 316 if (isLocalMove(car)) { 317 return ""; // print nothing for local move, see dropCar() 318 } 319 StringBuilder builder = new StringBuilder(); 320 builder.append("<span style=\"color: " + Setup.getPickupTextColor() + ";\">"); 321 builder.append(Setup.getPickupCarPrefix()).append(" "); 322 for (String attribute : format) { 323 if (!attribute.trim().isEmpty()) { 324 attribute = attribute.toLowerCase(); 325 log.trace("Adding car with attribute {}", attribute); 326 if (attribute.equals(JsonOperations.LOCATION) || attribute.equals(JsonOperations.TRACK)) { 327 attribute = JsonOperations.LOCATION; // treat "track" as "location" 328 builder.append( 329 this.getFormattedAttribute(attribute, this.getPickupLocation(car.path(attribute), 330 ShowLocation.track))).append(" "); // NOI18N 331 } else if (attribute.equals(JsonOperations.DESTINATION)) { 332 builder.append( 333 this.getFormattedAttribute(attribute, this.getDropLocation(car.path(attribute), 334 ShowLocation.location))).append(" "); // NOI18N 335 } else if (attribute.equals(JsonOperations.DESTINATION_TRACK)) { 336 builder.append( 337 this.getFormattedAttribute(attribute, this.getDropLocation(car.path(JsonOperations.DESTINATION), 338 ShowLocation.both))).append(" "); // NOI18N 339 } else { 340 builder.append(this.getTextAttribute(attribute, car)).append(" "); // NOI18N 341 } 342 } 343 } 344 log.debug("Picking up car {}", builder); 345 return String.format(locale, strings.getProperty(this.resourcePrefix + "PickUpCar"), builder.toString()); // NOI18N 346 } 347 348 protected String dropCar(JsonNode car, String[] format, boolean isLocal) { 349 StringBuilder builder = new StringBuilder(); 350 if (!isLocal) { 351 builder.append("<span style=\"color: " + Setup.getDropTextColor() + ";\">"); 352 builder.append(Setup.getDropCarPrefix()).append(" "); 353 } else { 354 builder.append("<span style=\"color: " + Setup.getLocalTextColor() + ";\">"); 355 builder.append(Setup.getLocalPrefix()).append(" "); 356 } 357 log.debug("dropCar {}", car); 358 for (String attribute : format) { 359 if (!attribute.trim().isEmpty()) { 360 attribute = attribute.toLowerCase(); 361 log.trace("Removing car with attribute {}", attribute); 362 if (attribute.equals(JsonOperations.DESTINATION) || attribute.equals(JsonOperations.TRACK)) { 363 attribute = JsonOperations.DESTINATION; // treat "track" as "destination" 364 builder.append( 365 this.getFormattedAttribute(attribute, this.getDropLocation(car.path(attribute), 366 ShowLocation.track))).append(" "); // NOI18N 367 } else if (attribute.equals(JsonOperations.DESTINATION_TRACK)) { 368 builder.append( 369 this.getFormattedAttribute(attribute, this.getDropLocation(car.path(JsonOperations.DESTINATION), 370 ShowLocation.both))).append(" "); // NOI18N 371 } else if (attribute.equals(JsonOperations.LOCATION) && isLocal) { 372 builder.append( 373 this.getFormattedAttribute(attribute, this.getPickupLocation(car.path(attribute), 374 ShowLocation.track))).append(" "); // NOI18N 375 } else if (attribute.equals(JsonOperations.LOCATION)) { 376 builder.append( 377 this.getFormattedAttribute(attribute, this.getPickupLocation(car.path(attribute), 378 ShowLocation.location))).append(" "); // NOI18N 379 } else { 380 builder.append(this.getTextAttribute(attribute, car)).append(" "); // NOI18N 381 } 382 } 383 } 384 log.debug("Dropping {}car {}", (isLocal) ? "local " : "", builder); 385 if (!isLocal) { 386 return String.format(locale, strings.getProperty(this.resourcePrefix + "DropCar"), builder.toString()); // NOI18N 387 } else { 388 return String.format(locale, strings.getProperty(this.resourcePrefix + "LocalCar"), builder.toString()); // NOI18N 389 } 390 } 391 392 protected String dropEngines(JsonNode engines) { 393 StringBuilder builder = new StringBuilder(); 394 engines.forEach((engine) -> { 395 builder.append(this.dropEngine(engine)); 396 }); 397 return String.format(locale, strings.getProperty("EnginesList"), builder.toString()); 398 } 399 400 protected String dropEngine(JsonNode engine) { 401 StringBuilder builder = new StringBuilder(); 402 builder.append("<span style=\"color: " + Setup.getDropEngineTextColor() + ";\">"); 403 builder.append(Setup.getDropEnginePrefix()).append(" "); 404 String[] formatMessage = Setup.getDropEngineMessageFormat(); 405 Setup.stringToTagConversion(formatMessage); 406 for (String attribute : formatMessage) { 407 if (!attribute.trim().isEmpty()) { 408 attribute = attribute.toLowerCase(); 409 if (attribute.equals(JsonOperations.DESTINATION) || attribute.equals(JsonOperations.TRACK)) { 410 attribute = JsonOperations.DESTINATION; // treat "track" as "destination" 411 builder.append( 412 this.getFormattedAttribute(attribute, this.getDropLocation(engine.path(attribute), 413 ShowLocation.track))) 414 .append(" "); // NOI18N 415 } else if (attribute.equals(JsonOperations.LOCATION)) { 416 builder.append( 417 this.getFormattedAttribute(attribute, this.getPickupLocation(engine.path(attribute), 418 ShowLocation.location))) 419 .append(" "); // NOI18N 420 } else { 421 builder.append(this.getTextAttribute(attribute, engine)).append(" "); // NOI18N 422 } 423 } 424 } 425 log.debug("Drop engine: {}", builder); 426 return String.format(locale, strings.getProperty(this.resourcePrefix + "DropEngine"), builder.toString()); 427 } 428 429 protected String pickupEngines(JsonNode engines) { 430 StringBuilder builder = new StringBuilder(); 431 if (engines.size() > 0) { 432 for (JsonNode engine : engines) { 433 builder.append(this.pickupEngine(engine)); 434 } 435 } 436 return String.format(locale, strings.getProperty("EnginesList"), builder.toString()); 437 } 438 439 protected String pickupEngine(JsonNode engine) { 440 StringBuilder builder = new StringBuilder(); 441 builder.append("<span style=\"color: " + Setup.getPickupEngineTextColor() + ";\">"); 442 builder.append(Setup.getPickupEnginePrefix()).append(" "); 443 String[] messageFormat = Setup.getPickupEngineMessageFormat(); 444 Setup.stringToTagConversion(messageFormat); 445 log.debug("PickupEngineMessageFormat: {}", (Object) messageFormat); 446 for (String attribute : messageFormat) { 447 if (!attribute.trim().isEmpty()) { 448 attribute = attribute.toLowerCase(); 449 if (attribute.equals(JsonOperations.LOCATION) || attribute.equals(JsonOperations.TRACK)) { 450 attribute = JsonOperations.LOCATION; // treat "track" as "location" 451 builder.append( 452 this.getFormattedAttribute(attribute, this.getPickupLocation(engine.path(attribute), 453 ShowLocation.track))) 454 .append(" "); // NOI18N 455 } else if (attribute.equals(JsonOperations.DESTINATION)) { 456 builder.append( 457 this.getFormattedAttribute(attribute, this.getDropLocation(engine.path(attribute), 458 ShowLocation.location))) 459 .append(" "); // NOI18N 460 } else { 461 builder.append(this.getTextAttribute(attribute, engine)).append(" "); // NOI18N 462 } 463 } 464 } 465 log.debug("Picking up engine: {}", builder); 466 return String.format(locale, strings.getProperty(this.resourcePrefix + "PickUpEngine"), builder.toString()); 467 } 468 469 protected String getDropLocation(JsonNode location, ShowLocation show) { 470 return this.getFormattedLocation(location, show, "To"); // NOI18N 471 } 472 473 protected String getPickupLocation(JsonNode location, ShowLocation show) { 474 return this.getFormattedLocation(location, show, "From"); // NOI18N 475 } 476 477 protected String getTextAttribute(String attribute, JsonNode rollingStock) { 478 if (attribute.equals(JsonOperations.HAZARDOUS)) { 479 return this.getFormattedAttribute(attribute, (rollingStock.path(attribute).asBoolean() ? Setup 480 .getHazardousMsg() : "")); // NOI18N 481 } else if (attribute.equals(JsonOperations.PICKUP_COMMENT)) { 482 return this.getFormattedAttribute(JsonOperations.PICKUP_COMMENT, rollingStock.path(JsonOperations.PICKUP_COMMENT).textValue()); 483 } else if (attribute.equals(JsonOperations.SETOUT_COMMENT)) { 484 return this.getFormattedAttribute(JsonOperations.SETOUT_COMMENT, rollingStock.path(JsonOperations.SETOUT_COMMENT).textValue()); 485 } else if (attribute.equals(JsonOperations.RETURN_WHEN_EMPTY)) { 486 return this.getFormattedLocation(rollingStock.path(JsonOperations.RETURN_WHEN_EMPTY), ShowLocation.both, "RWE"); // NOI18N 487 } else if (attribute.equals(JsonOperations.FINAL_DESTINATION)) { 488 return this.getFormattedLocation(rollingStock.path(JsonOperations.FINAL_DESTINATION), ShowLocation.location, "FinalDestination"); // NOI18N 489 } else if (attribute.equals(JsonOperations.FINAL_DEST_TRACK)) { 490 return this.getFormattedLocation(rollingStock.path(JsonOperations.FINAL_DESTINATION), ShowLocation.both, "FinalDestination"); // NOI18N 491 } else if (attribute.equals(JsonOperations.LAST_TRAIN)) { 492 return TrainManifestHeaderText.getStringHeader_Last_Train() + 493 SPACE + 494 getFormattedAttribute(attribute, rollingStock.path(attribute).asText()); 495 } else if (attribute.equals(JsonOperations.LAST_LOCATION)) { 496 return TrainManifestHeaderText.getStringHeader_Last_Location() + 497 SPACE + 498 getFormattedAttribute(attribute, rollingStock.path(attribute).asText()); 499 } 500 501 return this.getFormattedAttribute(attribute, rollingStock.path(attribute).asText()); 502 } 503 504 protected String getFormattedAttribute(String attribute, String value) { 505 return String.format(locale, strings.getProperty("Attribute"), StringEscapeUtils.escapeHtml4(value), attribute); 506 } 507 508 protected String getFormattedLocation(JsonNode location, ShowLocation show, String prefix) { 509 if (location.isNull() || location.isEmpty()) { 510 // return an empty string if location is an empty or null 511 return ""; 512 } 513 // TODO handle tracks without names 514 switch (show) { 515 case location: 516 return String.format(locale, strings.getProperty(prefix + "Location"), 517 splitString(location.path(JSON.USERNAME).asText())); 518 case track: 519 return String.format(locale, strings.getProperty(prefix + "Track"), 520 splitString(location.path(JSON.TRACK).path(JSON.USERNAME).asText())); 521 case both: 522 default: // default here ensures the method always returns 523 return String.format(locale, strings.getProperty(prefix + "LocationAndTrack"), 524 splitString(location.path(JSON.USERNAME).asText()), 525 splitString(location.path(JSON.TRACK).path(JSON.USERNAME).asText())); 526 } 527 } 528 529 private String getTrackComments(JsonNode tracks, JsonNode cars) { 530 StringBuilder builder = new StringBuilder(); 531 if (tracks.size() > 0) { 532 Set<Entry<String, JsonNode>> properties = tracks.properties(); 533 for (var track : properties) { 534 boolean pickup = false; 535 boolean setout = false; 536 if (cars.path(JSON.ADD).size() > 0) { 537 for (JsonNode car : cars.path(JSON.ADD)) { 538 if (track.getKey().equals(car.path(JSON.LOCATION).path(JSON.TRACK) 539 .path(JSON.NAME).asText())) { 540 pickup = true; 541 break; // we do not need to iterate all cars 542 } 543 } 544 } 545 if (cars.path(JSON.REMOVE).size() > 0) { 546 for (JsonNode car : cars.path(JSON.REMOVE)) { 547 if (track.getKey().equals(car.path(JsonOperations.DESTINATION).path(JSON.TRACK) 548 .path(JSON.NAME).textValue())) { 549 setout = true; 550 break; // we do not need to iterate all cars 551 } 552 } 553 } 554 if (pickup && setout) { 555 builder.append(String.format(locale, strings.getProperty("TrackComments"), track.getValue().path( 556 JSON.ADD_AND_REMOVE).textValue())); 557 } else if (pickup) { 558 builder.append(String.format(locale, strings.getProperty("TrackComments"), track.getValue().path( 559 JSON.ADD).textValue())); 560 } else if (setout) { 561 builder.append(String.format(locale, strings.getProperty("TrackComments"), track.getValue().path( 562 JSON.REMOVE).textValue())); 563 } 564 } 565 } 566 return builder.toString(); 567 } 568 569 protected boolean isLocalMove(JsonNode car) { 570 return car.path(JsonOperations.IS_LOCAL).booleanValue(); 571 } 572 573 protected boolean isUtilityCar(JsonNode car) { 574 return car.path(JsonOperations.UTILITY).booleanValue(); 575 } 576 577 protected JsonNode getJsonManifest() throws IOException { 578 if (this.jsonManifest == null) { 579 try { 580 this.jsonManifest = this.mapper.readTree((new JsonManifest(this.train)).getFile()); 581 } catch (IOException e) { 582 log.error("Json manifest file not found for train ({})", this.train.getName()); 583 } 584 } 585 return this.jsonManifest; 586 } 587 588 @Override 589 public String getValidity() { 590 try { 591 if (Setup.isPrintTrainScheduleNameEnabled()) { 592 return String.format(locale, strings.getProperty(this.resourcePrefix + "ValidityWithSchedule"), 593 this.getJsonManifest().path(JsonOperations.DATE).textValue(), 594 InstanceManager.getDefault(TrainScheduleManager.class).getActiveSchedule().getName()); 595 } else { 596 return String.format(locale, strings.getProperty(this.resourcePrefix + "Validity"), 597 this.getJsonManifest().path(JsonOperations.DATE).textValue()); 598 } 599 } catch (NullPointerException ex) { 600 log.warn("Manifest for train {} (id {}) does not have any validity.", this.train.getIconName(), this.train 601 .getId()); 602// } catch (DateTimeParseException ex) { 603// log.error("Date of JSON manifest could not be parsed as a LocalDateTime."); 604 } catch (IOException ex) { 605 log.error("JSON manifest could not be read."); 606 } 607 return ""; 608 } 609}