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