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