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(Setup.getPickupCarPrefix()).append(" "); 319 for (String attribute : format) { 320 if (!attribute.trim().isEmpty()) { 321 attribute = attribute.toLowerCase(); 322 log.trace("Adding car with attribute {}", attribute); 323 if (attribute.equals(JsonOperations.LOCATION) || attribute.equals(JsonOperations.TRACK)) { 324 attribute = JsonOperations.LOCATION; // treat "track" as "location" 325 builder.append( 326 this.getFormattedAttribute(attribute, this.getPickupLocation(car.path(attribute), 327 ShowLocation.track))).append(" "); // NOI18N 328 } else if (attribute.equals(JsonOperations.DESTINATION)) { 329 builder.append( 330 this.getFormattedAttribute(attribute, this.getDropLocation(car.path(attribute), 331 ShowLocation.location))).append(" "); // NOI18N 332 } else if (attribute.equals(JsonOperations.DESTINATION_TRACK)) { 333 builder.append( 334 this.getFormattedAttribute(attribute, this.getDropLocation(car.path(JsonOperations.DESTINATION), 335 ShowLocation.both))).append(" "); // NOI18N 336 } else if (attribute.equals(Xml.TYPE)) { 337 builder.append(this.getTextAttribute(JsonOperations.CAR_TYPE, car)).append(" "); // NOI18N 338 } else { 339 builder.append(this.getTextAttribute(attribute, car)).append(" "); // NOI18N 340 } 341 } 342 } 343 log.debug("Picking up car {}", builder); 344 return String.format(locale, strings.getProperty(this.resourcePrefix + "PickUpCar"), builder.toString()); // NOI18N 345 } 346 347 protected String dropCar(JsonNode car, String[] format, boolean isLocal) { 348 StringBuilder builder = new StringBuilder(); 349 if (!isLocal) { 350 builder.append(Setup.getDropCarPrefix()).append(" "); 351 } else { 352 builder.append(Setup.getLocalPrefix()).append(" "); 353 } 354 log.debug("dropCar {}", car); 355 for (String attribute : format) { 356 if (!attribute.trim().isEmpty()) { 357 attribute = attribute.toLowerCase(); 358 log.trace("Removing car with attribute {}", attribute); 359 if (attribute.equals(JsonOperations.DESTINATION) || attribute.equals(JsonOperations.TRACK)) { 360 attribute = JsonOperations.DESTINATION; // treat "track" as "destination" 361 builder.append( 362 this.getFormattedAttribute(attribute, this.getDropLocation(car.path(attribute), 363 ShowLocation.track))).append(" "); // NOI18N 364 } else if (attribute.equals(JsonOperations.LOCATION) && isLocal) { 365 builder.append( 366 this.getFormattedAttribute(attribute, this.getPickupLocation(car.path(attribute), 367 ShowLocation.track))).append(" "); // NOI18N 368 } else if (attribute.equals(JsonOperations.LOCATION)) { 369 builder.append( 370 this.getFormattedAttribute(attribute, this.getPickupLocation(car.path(attribute), 371 ShowLocation.location))).append(" "); // NOI18N 372 } else if (attribute.equals(Xml.TYPE)) { 373 builder.append(this.getTextAttribute(JsonOperations.CAR_TYPE, car)).append(" "); // NOI18N 374 } else { 375 builder.append(this.getTextAttribute(attribute, car)).append(" "); // NOI18N 376 } 377 } 378 } 379 log.debug("Dropping {}car {}", (isLocal) ? "local " : "", builder); 380 if (!isLocal) { 381 return String.format(locale, strings.getProperty(this.resourcePrefix + "DropCar"), builder.toString()); // NOI18N 382 } else { 383 return String.format(locale, strings.getProperty(this.resourcePrefix + "LocalCar"), builder.toString()); // NOI18N 384 } 385 } 386 387 protected String dropEngines(JsonNode engines) { 388 StringBuilder builder = new StringBuilder(); 389 engines.forEach((engine) -> { 390 builder.append(this.dropEngine(engine)); 391 }); 392 return String.format(locale, strings.getProperty("EnginesList"), builder.toString()); 393 } 394 395 protected String dropEngine(JsonNode engine) { 396 StringBuilder builder = new StringBuilder(); 397 builder.append(Setup.getDropEnginePrefix()).append(" "); 398 for (String attribute : Setup.getDropEngineMessageFormat()) { 399 if (!attribute.trim().isEmpty()) { 400 attribute = attribute.toLowerCase(); 401 if (attribute.equals(JsonOperations.DESTINATION) || attribute.equals(JsonOperations.TRACK)) { 402 attribute = JsonOperations.DESTINATION; // treat "track" as "destination" 403 builder.append( 404 this.getFormattedAttribute(attribute, this.getDropLocation(engine.path(attribute), 405 ShowLocation.track))).append(" "); // NOI18N 406 } else { 407 builder.append(this.getTextAttribute(attribute, engine)).append(" "); // NOI18N 408 } 409 } 410 } 411 log.debug("Drop engine: {}", builder); 412 return String.format(locale, strings.getProperty(this.resourcePrefix + "DropEngine"), builder.toString()); 413 } 414 415 protected String pickupEngines(JsonNode engines) { 416 StringBuilder builder = new StringBuilder(); 417 if (engines.size() > 0) { 418 for (JsonNode engine : engines) { 419 builder.append(this.pickupEngine(engine)); 420 } 421 } 422 return String.format(locale, strings.getProperty("EnginesList"), builder.toString()); 423 } 424 425 protected String pickupEngine(JsonNode engine) { 426 StringBuilder builder = new StringBuilder(); 427 builder.append(Setup.getPickupEnginePrefix()).append(" "); 428 log.debug("PickupEngineMessageFormat: {}", (Object) Setup.getPickupEngineMessageFormat()); 429 for (String attribute : Setup.getPickupEngineMessageFormat()) { 430 if (!attribute.trim().isEmpty()) { 431 attribute = attribute.toLowerCase(); 432 if (attribute.equals(JsonOperations.LOCATION) || attribute.equals(JsonOperations.TRACK)) { 433 attribute = JsonOperations.LOCATION; // treat "track" as "location" 434 builder.append( 435 this.getFormattedAttribute(attribute, this.getPickupLocation(engine.path(attribute), 436 ShowLocation.track))).append(" "); // NOI18N 437 } else { 438 builder.append(this.getTextAttribute(attribute, engine)).append(" "); // NOI18N 439 } 440 } 441 } 442 log.debug("Picking up engine: {}", builder); 443 return String.format(locale, strings.getProperty(this.resourcePrefix + "PickUpEngine"), builder.toString()); 444 } 445 446 protected String getDropLocation(JsonNode location, ShowLocation show) { 447 return this.getFormattedLocation(location, show, "To"); // NOI18N 448 } 449 450 protected String getPickupLocation(JsonNode location, ShowLocation show) { 451 return this.getFormattedLocation(location, show, "From"); // NOI18N 452 } 453 454 protected String getTextAttribute(String attribute, JsonNode rollingStock) { 455 if (attribute.equals(JSON.HAZARDOUS)) { 456 return this.getFormattedAttribute(attribute, (rollingStock.path(attribute).asBoolean() ? Setup 457 .getHazardousMsg() : "")); // NOI18N 458 } else if (attribute.equals(Setup.PICKUP_COMMENT.toLowerCase())) { // NOI18N 459 return this.getFormattedAttribute(JSON.ADD_COMMENT, rollingStock.path(JSON.ADD_COMMENT).textValue()); 460 } else if (attribute.equals(Setup.DROP_COMMENT.toLowerCase())) { // NOI18N 461 return this.getFormattedAttribute(JSON.REMOVE_COMMENT, rollingStock.path(JSON.REMOVE_COMMENT).textValue()); 462 } else if (attribute.equals(Setup.RWE.toLowerCase())) { 463 return this.getFormattedLocation(rollingStock.path(JSON.RETURN_WHEN_EMPTY), ShowLocation.both, "RWE"); // NOI18N 464 } else if (attribute.equals(Setup.FINAL_DEST.toLowerCase())) { 465 return this.getFormattedLocation(rollingStock.path(JSON.FINAL_DESTINATION), ShowLocation.location, "FinalDestination"); // NOI18N 466 } else if (attribute.equals(Setup.FINAL_DEST_TRACK.toLowerCase())) { 467 return this.getFormattedLocation(rollingStock.path(JSON.FINAL_DESTINATION), ShowLocation.track, "FinalDestination"); // NOI18N 468 } 469 return this.getFormattedAttribute(attribute, rollingStock.path(attribute).asText()); 470 } 471 472 protected String getFormattedAttribute(String attribute, String value) { 473 return String.format(locale, strings.getProperty("Attribute"), StringEscapeUtils.escapeHtml4(value), attribute); 474 } 475 476 protected String getFormattedLocation(JsonNode location, ShowLocation show, String prefix) { 477 if (location.isNull() || location.isEmpty()) { 478 // return an empty string if location is an empty or null 479 return ""; 480 } 481 // TODO handle tracks without names 482 switch (show) { 483 case location: 484 return String.format(locale, strings.getProperty(prefix + "Location"), 485 splitString(location.path(JSON.USERNAME).asText())); 486 case track: 487 return String.format(locale, strings.getProperty(prefix + "Track"), 488 splitString(location.path(JsonOperations.TRACK).path(JSON.USERNAME).asText())); 489 case both: 490 default: // default here ensures the method always returns 491 return String.format(locale, strings.getProperty(prefix + "LocationAndTrack"), 492 splitString(location.path(JSON.USERNAME).asText()), 493 splitString(location.path(JsonOperations.TRACK).path(JSON.USERNAME).asText())); 494 } 495 } 496 497 private String getTrackComments(JsonNode tracks, JsonNode cars) { 498 StringBuilder builder = new StringBuilder(); 499 if (tracks.size() > 0) { 500 Iterator<Entry<String, JsonNode>> iterator = tracks.fields(); 501 while (iterator.hasNext()) { 502 Entry<String, JsonNode> track = iterator.next(); 503 boolean pickup = false; 504 boolean setout = false; 505 if (cars.path(JSON.ADD).size() > 0) { 506 for (JsonNode car : cars.path(JSON.ADD)) { 507 if (track.getKey().equals(car.path(JsonOperations.LOCATION).path(JsonOperations.TRACK) 508 .path(JSON.NAME).asText())) { 509 pickup = true; 510 break; // we do not need to iterate all cars 511 } 512 } 513 } 514 if (cars.path(JSON.REMOVE).size() > 0) { 515 for (JsonNode car : cars.path(JSON.REMOVE)) { 516 if (track.getKey().equals(car.path(JsonOperations.DESTINATION).path(JsonOperations.TRACK) 517 .path(JSON.NAME).textValue())) { 518 setout = true; 519 break; // we do not need to iterate all cars 520 } 521 } 522 } 523 if (pickup && setout) { 524 builder.append(String.format(locale, strings.getProperty("TrackComments"), track.getValue().path( 525 JSON.ADD_AND_REMOVE).textValue())); 526 } else if (pickup) { 527 builder.append(String.format(locale, strings.getProperty("TrackComments"), track.getValue().path( 528 JSON.ADD).textValue())); 529 } else if (setout) { 530 builder.append(String.format(locale, strings.getProperty("TrackComments"), track.getValue().path( 531 JSON.REMOVE).textValue())); 532 } 533 } 534 } 535 return builder.toString(); 536 } 537 538 protected boolean isLocalMove(JsonNode car) { 539 return car.path(JSON.IS_LOCAL).booleanValue(); 540 } 541 542 protected boolean isUtilityCar(JsonNode car) { 543 return car.path(JSON.UTILITY).booleanValue(); 544 } 545 546 protected JsonNode getJsonManifest() throws IOException { 547 if (this.jsonManifest == null) { 548 try { 549 this.jsonManifest = this.mapper.readTree((new JsonManifest(this.train)).getFile()); 550 } catch (IOException e) { 551 log.error("Json manifest file not found for train ({})", this.train.getName()); 552 } 553 } 554 return this.jsonManifest; 555 } 556 557 @Override 558 public String getValidity() { 559 try { 560 if (Setup.isPrintTrainScheduleNameEnabled()) { 561 return String.format(locale, strings.getProperty(this.resourcePrefix + "ValidityWithSchedule"), 562 getDate((new StdDateFormat()).parse(this.getJsonManifest().path(JsonOperations.DATE).textValue())), 563 InstanceManager.getDefault(TrainScheduleManager.class).getActiveSchedule().getName()); 564 } else { 565 return String.format(locale, strings.getProperty(this.resourcePrefix + "Validity"), 566 getDate((new StdDateFormat()).parse(this.getJsonManifest().path(JsonOperations.DATE).textValue()))); 567 } 568 } catch (NullPointerException ex) { 569 log.warn("Manifest for train {} (id {}) does not have any validity.", this.train.getIconName(), this.train 570 .getId()); 571 } catch (ParseException ex) { 572 log.error("Date of JSON manifest could not be parsed as a Date."); 573 } catch (IOException ex) { 574 log.error("JSON manifest could not be read."); 575 } 576 return ""; 577 } 578}