001package jmri.web.servlet.operations; 002 003import java.io.IOException; 004import java.text.ParseException; 005import java.util.ArrayList; 006import java.util.Comparator; 007import java.util.Iterator; 008import java.util.Locale; 009import java.util.Map.Entry; 010 011import org.apache.commons.text.StringEscapeUtils; 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015import com.fasterxml.jackson.databind.JsonNode; 016import com.fasterxml.jackson.databind.ObjectMapper; 017import com.fasterxml.jackson.databind.util.StdDateFormat; 018 019import jmri.InstanceManager; 020import jmri.jmrit.operations.rollingstock.Xml; 021import jmri.jmrit.operations.routes.RouteLocation; 022import jmri.jmrit.operations.setup.Setup; 023import jmri.jmrit.operations.trains.JsonManifest; 024import jmri.jmrit.operations.trains.Train; 025import jmri.jmrit.operations.trains.schedules.TrainScheduleManager; 026import jmri.server.json.JSON; 027import jmri.server.json.operations.JsonOperations; 028 029/** 030 * 031 * @author Randall Wood 032 */ 033public class HtmlManifest extends HtmlTrainCommon { 034 035 protected ObjectMapper mapper; 036 private JsonNode jsonManifest = null; 037 private final static Logger log = LoggerFactory.getLogger(HtmlManifest.class); 038 039 public HtmlManifest(Locale locale, Train train) throws IOException { 040 super(locale, train); 041 this.mapper = new ObjectMapper(); 042 this.resourcePrefix = "Manifest"; 043 } 044 045 // TODO cache the results so a quick check that if the JsonManifest file is not 046 // newer than the Html manifest, the cached copy is returned instead. 047 public String getLocations() throws IOException { 048 // build manifest from JSON manifest 049 if (this.getJsonManifest() == null) { 050 return "Error manifest file not found for this train"; 051 } 052 StringBuilder builder = new StringBuilder(); 053 JsonNode locations = this.getJsonManifest().path(JsonOperations.LOCATIONS); 054 String previousLocationName = null; 055 boolean hasWork; 056 for (JsonNode location : locations) { 057 RouteLocation routeLocation = train.getRoute().getLocationById(location.path(JSON.NAME).textValue()); 058 log.debug("Processing {} ({})", routeLocation.getName(), location.path(JSON.NAME).textValue()); 059 String routeLocationName = location.path(JSON.USERNAME).textValue(); 060 builder.append(String.format(locale, strings.getProperty("LocationStart"), routeLocation.getId())); // NOI18N 061 hasWork = (location.path(JsonOperations.CARS).path(JSON.ADD).size() > 0 062 || location.path(JsonOperations.CARS).path(JSON.REMOVE).size() > 0 063 || location.path(JSON.ENGINES).path(JSON.ADD).size() > 0 || location.path(JSON.ENGINES).path( 064 JSON.REMOVE).size() > 0); 065 if (hasWork && !routeLocationName.equals(previousLocationName)) { 066 if (!train.isShowArrivalAndDepartureTimesEnabled()) { 067 builder.append(String.format(locale, strings.getProperty("ScheduledWorkAt"), routeLocationName)); // NOI18N 068 } else if (routeLocation == train.getTrainDepartsRouteLocation()) { 069 builder.append(String.format(locale, strings.getProperty("WorkDepartureTime"), routeLocationName, 070 train.getFormatedDepartureTime())); // NOI18N 071 } else if (!routeLocation.getDepartureTime().equals(RouteLocation.NONE)) { 072 builder.append(String.format(locale, strings.getProperty("WorkDepartureTime"), routeLocationName, 073 routeLocation.getFormatedDepartureTime())); // NOI18N 074 } else if (Setup.isUseDepartureTimeEnabled() 075 && routeLocation != train.getTrainTerminatesRouteLocation()) { 076 builder.append(String.format(locale, strings.getProperty("WorkDepartureTime"), routeLocationName, 077 train.getExpectedDepartureTime(routeLocation))); // NOI18N 078 } else if (!train.getExpectedArrivalTime(routeLocation).equals(Train.ALREADY_SERVICED)) { // NOI18N 079 builder.append(String.format(locale, strings.getProperty("WorkArrivalTime"), routeLocationName, 080 train.getExpectedArrivalTime(routeLocation))); // NOI18N 081 } else { 082 builder.append(String.format(locale, strings.getProperty("ScheduledWorkAt"), routeLocationName)); // NOI18N 083 } 084 // add route comment 085 if (!location.path(JSON.COMMENT).textValue().trim().isEmpty()) { 086 builder.append(String.format(locale, strings.getProperty("RouteLocationComment"), 087 location.path(JSON.COMMENT).textValue())); 088 } 089 090 builder.append(getTrackComments(location.path(JsonOperations.TRACK), location.path(JsonOperations.CARS))); 091 092 // add location comment 093 if (Setup.isPrintLocationCommentsEnabled() 094 && !location.path(JsonOperations.LOCATION).path(JSON.COMMENT).textValue().trim().isEmpty()) { 095 builder.append(String.format(locale, strings.getProperty("LocationComment"), location.path( 096 JsonOperations.LOCATION).path(JSON.COMMENT).textValue())); 097 } 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(JsonOperations.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(JsonOperations.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().trim().isEmpty()) { 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().equals(RouteLocation.NONE)) { 190 if (routeLocation.getComment().trim().length() > 0) { 191 builder.append(String.format(locale, strings.getProperty("CommentAt"), // NOI18N 192 routeLocationName, StringEscapeUtils 193 .escapeHtml4(routeLocation.getComment()))); 194 } 195 } 196 if (train.isShowArrivalAndDepartureTimesEnabled()) { 197 if (routeLocation == train.getTrainDepartsRouteLocation()) { 198 builder.append(String.format(locale, strings 199 .getProperty("CommentAtWithDepartureTime"), routeLocationName, train // NOI18N 200 .getFormatedDepartureTime(), StringEscapeUtils 201 .escapeHtml4(routeLocation.getComment()))); 202 } else if (!routeLocation.getDepartureTime().equals(RouteLocation.NONE)) { 203 builder.append(String.format(locale, strings 204 .getProperty("CommentAtWithDepartureTime"), routeLocationName, // NOI18N 205 routeLocation.getFormatedDepartureTime(), StringEscapeUtils 206 .escapeHtml4(routeLocation.getComment()))); 207 } else if (Setup.isUseDepartureTimeEnabled() && 208 !routeLocation.getComment().equals(RouteLocation.NONE)) { 209 builder.append(String.format(locale, strings 210 .getProperty("NoScheduledWorkAtWithDepartureTime"), routeLocationName, // NOI18N 211 train.getExpectedDepartureTime(routeLocation))); 212 } 213 } 214 } 215 // add location comment 216 if (Setup.isPrintLocationCommentsEnabled() 217 && !routeLocation.getLocation().getComment().isEmpty()) { 218 builder.append(String.format(locale, strings.getProperty("LocationComment"), 219 StringEscapeUtils.escapeHtml4(routeLocation.getLocation().getComment()))); 220 } 221 } 222 } 223 } else { 224 builder.append(String.format(strings.getProperty("TrainTerminatesIn"), routeLocationName)); 225 } 226 } 227 return builder.toString(); 228 } 229 230 protected String blockCars(JsonNode cars, RouteLocation location, boolean isManifest) { 231 StringBuilder builder = new StringBuilder(); 232 log.debug("Cars is {}", cars); 233 234 //copy the adds into a sortable arraylist 235 ArrayList<JsonNode> adds = new ArrayList<JsonNode>(); 236 cars.path(JSON.ADD).forEach(adds::add); 237 238 //sort if requested 239 if (adds.size() > 0 && Setup.isSortByTrackNameEnabled()) { 240 adds.sort(Comparator.comparing(o -> o.path("location").path("track").path("userName").asText())); 241 } 242 //format each car for output 243 for (JsonNode car : adds) { 244 if (!this.isLocalMove(car)) { 245 // TODO utility format not quite ready, so display each car in 246 // manifest for now. 247 // if (this.isUtilityCar(car)) { 248 // builder.append(pickupUtilityCars(cars, car, location, 249 // isManifest)); 250 // } 251 // else 252 // use truncated format if there's a switch list 253 if (isManifest && 254 Setup.isPrintTruncateManifestEnabled() && 255 location.getLocation().isSwitchListEnabled()) { 256 builder.append(pickUpCar(car, Setup.getPickupTruncatedManifestMessageFormat())); 257 } else { 258 builder.append(pickUpCar(car, Setup.getPickupManifestMessageFormat())); 259 } 260 } 261 } 262 for (JsonNode car : cars.path(JSON.REMOVE)) { 263 boolean local = isLocalMove(car); 264 // TODO utility format not quite ready, so display each car in 265 // manifest for now. 266 // if (this.isUtilityCar(car)) { 267 // builder.append(setoutUtilityCars(cars, car, location, 268 // isManifest)); 269 // } else 270 if (isManifest && 271 Setup.isPrintTruncateManifestEnabled() && 272 location.getLocation().isSwitchListEnabled() && 273 !train.isLocalSwitcher()) { 274 // use truncated format if there's a switch list 275 builder.append(dropCar(car, Setup.getDropTruncatedManifestMessageFormat(), local)); 276 } else { 277 String[] format; 278 if (isManifest) { 279 format = (!local) ? Setup.getDropManifestMessageFormat() : Setup 280 .getLocalManifestMessageFormat(); 281 } else { 282 format = (!local) ? Setup.getDropSwitchListMessageFormat() : Setup 283 .getLocalSwitchListMessageFormat(); 284 } 285 builder.append(dropCar(car, format, local)); 286 } 287 } 288 return String.format(locale, strings.getProperty("CarsList"), builder.toString()); 289 } 290 291 protected String pickupUtilityCars(JsonNode cars, JsonNode car, RouteLocation location, boolean isManifest) { 292 // list utility cars by type, track, length, and load 293 String[] messageFormat; 294 if (isManifest) { 295 messageFormat = Setup.getPickupUtilityManifestMessageFormat(); 296 } else { 297 messageFormat = Setup.getPickupUtilitySwitchListMessageFormat(); 298 } 299 // TODO: reimplement following commented out code 300 // if (this.countUtilityCars(messageFormat, carList, car, location, rld, PICKUP) == 0) { 301 // return ""; // already printed out this car type 302 // } 303 return this.pickUpCar(car, messageFormat); 304 } 305 306 protected String setoutUtilityCars(JsonNode cars, JsonNode car, RouteLocation location, boolean isManifest) { 307 boolean isLocal = isLocalMove(car); 308 String[] messageFormat; 309 if (isLocal && isManifest) { 310 messageFormat = Setup.getLocalUtilityManifestMessageFormat(); 311 } else if (isLocal && !isManifest) { 312 messageFormat = Setup.getLocalUtilitySwitchListMessageFormat(); 313 } else if (!isLocal && !isManifest) { 314 messageFormat = Setup.getDropUtilitySwitchListMessageFormat(); 315 } else { 316 messageFormat = Setup.getDropUtilityManifestMessageFormat(); 317 } 318 // TODO: reimplement following commented out code 319 // if (countUtilityCars(messageFormat, carList, car, location, null, !PICKUP) == 0) { 320 // return ""; // already printed out this car type 321 // } 322 return dropCar(car, messageFormat, isLocal); 323 } 324 325 protected String pickUpCar(JsonNode car, String[] format) { 326 if (isLocalMove(car)) { 327 return ""; // print nothing for local move, see dropCar() 328 } 329 StringBuilder builder = new StringBuilder(); 330 builder.append(Setup.getPickupCarPrefix()).append(" "); 331 for (String attribute : format) { 332 if (!attribute.trim().isEmpty()) { 333 attribute = attribute.toLowerCase(); 334 log.trace("Adding car with attribute {}", attribute); 335 if (attribute.equals(JsonOperations.LOCATION) || attribute.equals(JsonOperations.TRACK)) { 336 attribute = JsonOperations.LOCATION; // treat "track" as "location" 337 builder.append( 338 this.getFormattedAttribute(attribute, this.getPickupLocation(car.path(attribute), 339 ShowLocation.track))).append(" "); // NOI18N 340 } else if (attribute.equals(JsonOperations.DESTINATION)) { 341 builder.append( 342 this.getFormattedAttribute(attribute, this.getDropLocation(car.path(attribute), 343 ShowLocation.location))).append(" "); // NOI18N 344 } else if (attribute.equals(JsonOperations.DESTINATION_TRACK)) { 345 builder.append( 346 this.getFormattedAttribute(attribute, this.getDropLocation(car.path(JsonOperations.DESTINATION), 347 ShowLocation.both))).append(" "); // NOI18N 348 } else if (attribute.equals(Xml.TYPE)) { 349 builder.append(this.getTextAttribute(JsonOperations.CAR_TYPE, car)).append(" "); // NOI18N 350 } else { 351 builder.append(this.getTextAttribute(attribute, car)).append(" "); // NOI18N 352 } 353 } 354 } 355 log.debug("Picking up car {}", builder); 356 return String.format(locale, strings.getProperty(this.resourcePrefix + "PickUpCar"), builder.toString()); // NOI18N 357 } 358 359 protected String dropCar(JsonNode car, String[] format, boolean isLocal) { 360 StringBuilder builder = new StringBuilder(); 361 if (!isLocal) { 362 builder.append(Setup.getDropCarPrefix()).append(" "); 363 } else { 364 builder.append(Setup.getLocalPrefix()).append(" "); 365 } 366 log.debug("dropCar {}", car); 367 for (String attribute : format) { 368 if (!attribute.trim().isEmpty()) { 369 attribute = attribute.toLowerCase(); 370 log.trace("Removing car with attribute {}", attribute); 371 if (attribute.equals(JsonOperations.DESTINATION) || attribute.equals(JsonOperations.TRACK)) { 372 attribute = JsonOperations.DESTINATION; // treat "track" as "destination" 373 builder.append( 374 this.getFormattedAttribute(attribute, this.getDropLocation(car.path(attribute), 375 ShowLocation.track))).append(" "); // NOI18N 376 } else if (attribute.equals(JsonOperations.LOCATION) && isLocal) { 377 builder.append( 378 this.getFormattedAttribute(attribute, this.getPickupLocation(car.path(attribute), 379 ShowLocation.track))).append(" "); // NOI18N 380 } else if (attribute.equals(JsonOperations.LOCATION)) { 381 builder.append( 382 this.getFormattedAttribute(attribute, this.getPickupLocation(car.path(attribute), 383 ShowLocation.location))).append(" "); // NOI18N 384 } else if (attribute.equals(Xml.TYPE)) { 385 builder.append(this.getTextAttribute(JsonOperations.CAR_TYPE, car)).append(" "); // NOI18N 386 } else { 387 builder.append(this.getTextAttribute(attribute, car)).append(" "); // NOI18N 388 } 389 } 390 } 391 log.debug("Dropping {}car {}", (isLocal) ? "local " : "", builder); 392 if (!isLocal) { 393 return String.format(locale, strings.getProperty(this.resourcePrefix + "DropCar"), builder.toString()); // NOI18N 394 } else { 395 return String.format(locale, strings.getProperty(this.resourcePrefix + "LocalCar"), builder.toString()); // NOI18N 396 } 397 } 398 399 protected String dropEngines(JsonNode engines) { 400 StringBuilder builder = new StringBuilder(); 401 engines.forEach((engine) -> { 402 builder.append(this.dropEngine(engine)); 403 }); 404 return String.format(locale, strings.getProperty("EnginesList"), builder.toString()); 405 } 406 407 protected String dropEngine(JsonNode engine) { 408 StringBuilder builder = new StringBuilder(); 409 builder.append(Setup.getDropEnginePrefix()).append(" "); 410 for (String attribute : Setup.getDropEngineMessageFormat()) { 411 if (!attribute.trim().isEmpty()) { 412 attribute = attribute.toLowerCase(); 413 if (attribute.equals(JsonOperations.DESTINATION) || attribute.equals(JsonOperations.TRACK)) { 414 attribute = JsonOperations.DESTINATION; // treat "track" as "destination" 415 builder.append( 416 this.getFormattedAttribute(attribute, this.getDropLocation(engine.path(attribute), 417 ShowLocation.track))).append(" "); // NOI18N 418 } else { 419 builder.append(this.getTextAttribute(attribute, engine)).append(" "); // NOI18N 420 } 421 } 422 } 423 log.debug("Drop engine: {}", builder); 424 return String.format(locale, strings.getProperty(this.resourcePrefix + "DropEngine"), builder.toString()); 425 } 426 427 protected String pickupEngines(JsonNode engines) { 428 StringBuilder builder = new StringBuilder(); 429 if (engines.size() > 0) { 430 for (JsonNode engine : engines) { 431 builder.append(this.pickupEngine(engine)); 432 } 433 } 434 return String.format(locale, strings.getProperty("EnginesList"), builder.toString()); 435 } 436 437 protected String pickupEngine(JsonNode engine) { 438 StringBuilder builder = new StringBuilder(); 439 builder.append(Setup.getPickupEnginePrefix()).append(" "); 440 log.debug("PickupEngineMessageFormat: {}", (Object) Setup.getPickupEngineMessageFormat()); 441 for (String attribute : Setup.getPickupEngineMessageFormat()) { 442 if (!attribute.trim().isEmpty()) { 443 attribute = attribute.toLowerCase(); 444 if (attribute.equals(JsonOperations.LOCATION) || attribute.equals(JsonOperations.TRACK)) { 445 attribute = JsonOperations.LOCATION; // treat "track" as "location" 446 builder.append( 447 this.getFormattedAttribute(attribute, this.getPickupLocation(engine.path(attribute), 448 ShowLocation.track))).append(" "); // NOI18N 449 } else { 450 builder.append(this.getTextAttribute(attribute, engine)).append(" "); // NOI18N 451 } 452 } 453 } 454 log.debug("Picking up engine: {}", builder); 455 return String.format(locale, strings.getProperty(this.resourcePrefix + "PickUpEngine"), builder.toString()); 456 } 457 458 protected String getDropLocation(JsonNode location, ShowLocation show) { 459 return this.getFormattedLocation(location, show, "To"); // NOI18N 460 } 461 462 protected String getPickupLocation(JsonNode location, ShowLocation show) { 463 return this.getFormattedLocation(location, show, "From"); // NOI18N 464 } 465 466 protected String getTextAttribute(String attribute, JsonNode rollingStock) { 467 if (attribute.equals(JSON.HAZARDOUS)) { 468 return this.getFormattedAttribute(attribute, (rollingStock.path(attribute).asBoolean() ? Setup 469 .getHazardousMsg() : "")); // NOI18N 470 } else if (attribute.equals(Setup.PICKUP_COMMENT.toLowerCase())) { // NOI18N 471 return this.getFormattedAttribute(JSON.ADD_COMMENT, rollingStock.path(JSON.ADD_COMMENT).textValue()); 472 } else if (attribute.equals(Setup.DROP_COMMENT.toLowerCase())) { // NOI18N 473 return this.getFormattedAttribute(JSON.REMOVE_COMMENT, rollingStock.path(JSON.REMOVE_COMMENT).textValue()); 474 } else if (attribute.equals(Setup.RWE.toLowerCase())) { 475 return this.getFormattedLocation(rollingStock.path(JSON.RETURN_WHEN_EMPTY), ShowLocation.both, "RWE"); // NOI18N 476 } else if (attribute.equals(Setup.FINAL_DEST.toLowerCase())) { 477 return this.getFormattedLocation(rollingStock.path(JSON.FINAL_DESTINATION), ShowLocation.location, "FinalDestination"); // NOI18N 478 } else if (attribute.equals(Setup.FINAL_DEST_TRACK.toLowerCase())) { 479 return this.getFormattedLocation(rollingStock.path(JSON.FINAL_DESTINATION), ShowLocation.track, "FinalDestination"); // NOI18N 480 } 481 return this.getFormattedAttribute(attribute, rollingStock.path(attribute).asText()); 482 } 483 484 protected String getFormattedAttribute(String attribute, String value) { 485 return String.format(locale, strings.getProperty("Attribute"), StringEscapeUtils.escapeHtml4(value), attribute); 486 } 487 488 protected String getFormattedLocation(JsonNode location, ShowLocation show, String prefix) { 489 if (location.isNull() || location.isEmpty()) { 490 // return an empty string if location is an empty or null 491 return ""; 492 } 493 // TODO handle tracks without names 494 switch (show) { 495 case location: 496 return String.format(locale, strings.getProperty(prefix + "Location"), 497 splitString(location.path(JSON.USERNAME).asText())); 498 case track: 499 return String.format(locale, strings.getProperty(prefix + "Track"), 500 splitString(location.path(JsonOperations.TRACK).path(JSON.USERNAME).asText())); 501 case both: 502 default: // default here ensures the method always returns 503 return String.format(locale, strings.getProperty(prefix + "LocationAndTrack"), 504 splitString(location.path(JSON.USERNAME).asText()), 505 splitString(location.path(JsonOperations.TRACK).path(JSON.USERNAME).asText())); 506 } 507 } 508 509 private String getTrackComments(JsonNode tracks, JsonNode cars) { 510 StringBuilder builder = new StringBuilder(); 511 if (tracks.size() > 0) { 512 Iterator<Entry<String, JsonNode>> iterator = tracks.fields(); 513 while (iterator.hasNext()) { 514 Entry<String, JsonNode> track = iterator.next(); 515 boolean pickup = false; 516 boolean setout = false; 517 if (cars.path(JSON.ADD).size() > 0) { 518 for (JsonNode car : cars.path(JSON.ADD)) { 519 if (track.getKey().equals(car.path(JsonOperations.TRACK).path(JSON.NAME).textValue())) { 520 pickup = true; 521 break; // we do not need to iterate all cars 522 } 523 } 524 } 525 if (cars.path(JSON.REMOVE).size() > 0) { 526 for (JsonNode car : cars.path(JSON.REMOVE)) { 527 if (track.getKey().equals(car.path(JsonOperations.TRACK).path(JSON.NAME).textValue())) { 528 setout = true; 529 break; // we do not need to iterate all cars 530 } 531 } 532 } 533 if (pickup && setout) { 534 builder.append(String.format(locale, strings.getProperty("TrackComments"), track.getValue().path( 535 JSON.ADD_AND_REMOVE).textValue())); 536 } else if (pickup) { 537 builder.append(String.format(locale, strings.getProperty("TrackComments"), track.getValue().path( 538 JSON.ADD).textValue())); 539 } else if (setout) { 540 builder.append(String.format(locale, strings.getProperty("TrackComments"), track.getValue().path( 541 JSON.REMOVE).textValue())); 542 } 543 } 544 } 545 return builder.toString(); 546 } 547 548 protected boolean isLocalMove(JsonNode car) { 549 return car.path(JSON.IS_LOCAL).booleanValue(); 550 } 551 552 protected boolean isUtilityCar(JsonNode car) { 553 return car.path(JSON.UTILITY).booleanValue(); 554 } 555 556 protected JsonNode getJsonManifest() throws IOException { 557 if (this.jsonManifest == null) { 558 try { 559 this.jsonManifest = this.mapper.readTree((new JsonManifest(this.train)).getFile()); 560 } catch (IOException e) { 561 log.error("Json manifest file not found for train ({})", this.train.getName()); 562 } 563 } 564 return this.jsonManifest; 565 } 566 567 @Override 568 public String getValidity() { 569 try { 570 if (Setup.isPrintTrainScheduleNameEnabled()) { 571 return String.format(locale, strings.getProperty(this.resourcePrefix + "ValidityWithSchedule"), 572 getDate((new StdDateFormat()).parse(this.getJsonManifest().path(JsonOperations.DATE).textValue())), 573 InstanceManager.getDefault(TrainScheduleManager.class).getScheduleById(train.getId())); 574 } else { 575 return String.format(locale, strings.getProperty(this.resourcePrefix + "Validity"), 576 getDate((new StdDateFormat()).parse(this.getJsonManifest().path(JsonOperations.DATE).textValue()))); 577 } 578 } catch (NullPointerException ex) { 579 log.warn("Manifest for train {} (id {}) does not have any validity.", this.train.getIconName(), this.train 580 .getId()); 581 } catch (ParseException ex) { 582 log.error("Date of JSON manifest could not be parsed as a Date."); 583 } catch (IOException ex) { 584 log.error("JSON manifest could not be read."); 585 } 586 return ""; 587 } 588}