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