001package jmri.jmrit.operations.trains; 002 003import java.io.File; 004import java.io.IOException; 005import java.util.List; 006import java.util.Locale; 007 008import org.apache.commons.text.StringEscapeUtils; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012import com.fasterxml.jackson.databind.ObjectMapper; 013import com.fasterxml.jackson.databind.SerializationFeature; 014import com.fasterxml.jackson.databind.node.ArrayNode; 015import com.fasterxml.jackson.databind.node.ObjectNode; 016 017import jmri.InstanceManager; 018import jmri.jmrit.operations.locations.Track; 019import jmri.jmrit.operations.rollingstock.cars.Car; 020import jmri.jmrit.operations.rollingstock.engines.Engine; 021import jmri.jmrit.operations.routes.RouteLocation; 022import jmri.jmrit.operations.setup.Setup; 023import jmri.server.json.JSON; 024import jmri.server.json.operations.JsonOperations; 025import jmri.server.json.operations.JsonUtil; 026 027/** 028 * A minimal manifest in JSON. 029 * 030 * This manifest is intended to be read by machines for building manifests in 031 * other, human-readable outputs. This manifest is retained at build time so 032 * that manifests can be endlessly recreated in other formats, even if the 033 * operations database state has changed. It is expected that the parsers for 034 * this manifest will be capable of querying operations for more specific 035 * information while transforming this manifest into other formats. 036 * 037 * @author Randall Wood 038 * @author Daniel Boudreau 1/26/2015 Load all cars including utility cars into 039 * the JSON file, and tidied up the code a bit. 040 * 041 */ 042public class JsonManifest extends TrainCommon { 043 044 protected final Locale locale = Locale.getDefault(); 045 protected final Train train; 046 private final ObjectMapper mapper = new ObjectMapper(); 047 private final JsonUtil utilities = new JsonUtil(mapper); 048 049 private final static Logger log = LoggerFactory.getLogger(JsonManifest.class); 050 051 public JsonManifest(Train train) { 052 this.train = train; 053 this.mapper.enable(SerializationFeature.INDENT_OUTPUT); 054 } 055 056 public File getFile() { 057 return InstanceManager.getDefault(TrainManagerXml.class).getManifestFile(this.train.getName(), JSON.JSON); 058 } 059 060 public void build() throws IOException { 061 ObjectNode root = this.mapper.createObjectNode(); 062 if (!this.train.getRailroadName().equals(Train.NONE)) { 063 root.put(JSON.RAILROAD, StringEscapeUtils.escapeHtml4(this.train.getRailroadName())); 064 } else { 065 root.put(JSON.RAILROAD, StringEscapeUtils.escapeHtml4(Setup.getRailroadName())); 066 } 067 root.put(JSON.USERNAME, StringEscapeUtils.escapeHtml4(this.train.getName())); 068 root.put(JSON.DESCRIPTION, StringEscapeUtils.escapeHtml4(this.train.getDescription())); 069 root.set(JsonOperations.LOCATIONS, this.getLocations()); 070 if (!this.train.getManifestLogoPathName().equals(Train.NONE)) { 071 // The operationsServlet will need to change this to a usable URL 072 root.put(JSON.IMAGE, this.train.getManifestLogoPathName()); 073 } 074 root.put(JsonOperations.DATE, TrainCommon.getISO8601Date(true)); // Validity 075 this.mapper.writeValue(InstanceManager.getDefault(TrainManagerXml.class).createManifestFile(this.train.getName(), JSON.JSON), root); 076 } 077 078 public ArrayNode getLocations() { 079 // get engine and car lists 080 List<Engine> engineList = engineManager.getByTrainBlockingList(train); 081 List<Car> carList = carManager.getByTrainDestinationList(train); 082 ArrayNode locations = this.mapper.createArrayNode(); 083 List<RouteLocation> route = train.getRoute().getLocationsBySequenceList(); 084 for (RouteLocation routeLocation : route) { 085 String locationName = splitString(routeLocation.getName()); 086 ObjectNode jsonLocation = this.mapper.createObjectNode(); 087 ObjectNode jsonCars = this.mapper.createObjectNode(); 088 jsonLocation.put(JSON.USERNAME, StringEscapeUtils.escapeHtml4(locationName)); 089 jsonLocation.put(JSON.NAME, routeLocation.getId()); 090 if (routeLocation != train.getTrainDepartsRouteLocation()) { 091 jsonLocation.put(JSON.ARRIVAL_TIME, train.getExpectedArrivalTime(routeLocation)); 092 } 093 if (routeLocation == train.getTrainDepartsRouteLocation()) { 094 jsonLocation.put(JSON.DEPARTURE_TIME, train.getDepartureTime()); 095 } else if (!routeLocation.getDepartureTime().equals(RouteLocation.NONE)) { 096 jsonLocation.put(JSON.DEPARTURE_TIME, routeLocation.getDepartureTime()); 097 } else { 098 jsonLocation.put(JSON.EXPECTED_DEPARTURE, train.getExpectedDepartureTime(routeLocation)); 099 } 100 // add location comment and id 101 ObjectNode locationNode = this.mapper.createObjectNode(); 102 locationNode.put(JSON.COMMENT, StringEscapeUtils.escapeHtml4(routeLocation.getLocation().getComment())); 103 locationNode.put(JSON.NAME, routeLocation.getLocation().getId()); 104 jsonLocation.set(JsonOperations.LOCATION, locationNode); 105 jsonLocation.put(JSON.COMMENT, StringEscapeUtils.escapeHtml4(routeLocation.getComment())); 106 // engine change or helper service? 107 if (train.getSecondLegOptions() != Train.NO_CABOOSE_OR_FRED) { 108 ArrayNode options = this.mapper.createArrayNode(); 109 if (routeLocation == train.getSecondLegStartRouteLocation()) { 110 if ((train.getSecondLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) { 111 options.add(JSON.ADD_HELPERS); 112 } else if ((train.getSecondLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE 113 || (train.getSecondLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) { 114 options.add(JSON.CHANGE_CABOOSE); 115 } else if ((train.getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 116 options.add(JSON.CHANGE_ENGINES); 117 } 118 } 119 if (routeLocation == train.getSecondLegEndRouteLocation()) { 120 options.add(JSON.REMOVE_HELPERS); 121 } 122 jsonLocation.set(JSON.OPTIONS, options); 123 } 124 if (train.getThirdLegOptions() != Train.NO_CABOOSE_OR_FRED) { 125 ArrayNode options = this.mapper.createArrayNode(); 126 if (routeLocation == train.getThirdLegStartRouteLocation()) { 127 if ((train.getThirdLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) { 128 options.add(JSON.ADD_HELPERS); 129 } else if ((train.getThirdLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE 130 || (train.getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) { 131 options.add(JSON.CHANGE_CABOOSE); 132 } else if ((train.getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 133 options.add(JSON.CHANGE_ENGINES); 134 } 135 } 136 if (routeLocation == train.getThirdLegEndRouteLocation()) { 137 options.add(JSON.ADD_HELPERS); 138 } 139 jsonLocation.set(JSON.OPTIONS, options); 140 } 141 142 ObjectNode engines = this.mapper.createObjectNode(); 143 engines.set(JSON.ADD, pickupEngines(engineList, routeLocation)); 144 engines.set(JSON.REMOVE, dropEngines(engineList, routeLocation)); 145 jsonLocation.set(JSON.ENGINES, engines); 146 147 // block cars by destination 148 // caboose or FRED is placed at end of the train 149 // passenger cars are already blocked in the car list 150 // passenger cars with negative block numbers are placed at 151 // the front of the train, positive numbers at the end of 152 // the train. 153 ArrayNode pickups = this.mapper.createArrayNode(); 154 for (RouteLocation destination : route) { 155 for (Car car : carList) { 156 if (isNextCar(car, routeLocation, destination)) { 157 pickups.add(this.utilities.getCar(car, locale)); 158 } 159 } 160 } 161 jsonCars.set(JSON.ADD, pickups); 162 // car set outs 163 ArrayNode setouts = this.mapper.createArrayNode(); 164 for (Car car : carList) { 165 if (car.getRouteDestination() == routeLocation) { 166 setouts.add(this.utilities.getCar(car, locale)); 167 } 168 } 169 jsonCars.set(JSON.REMOVE, setouts); 170 171 if (routeLocation != train.getTrainTerminatesRouteLocation()) { 172 jsonLocation.set(JsonOperations.TRACK, this.getTrackComments(routeLocation, carList)); 173 jsonLocation.put(JSON.TRAIN_DIRECTION, routeLocation.getTrainDirection()); 174 ObjectNode length = this.mapper.createObjectNode(); 175 length.put(JSON.LENGTH, train.getTrainLength(routeLocation)); 176 length.put(JSON.UNIT, Setup.getLengthUnit()); 177 jsonLocation.set(JSON.LENGTH, length); 178 jsonLocation.put(JsonOperations.WEIGHT, train.getTrainWeight(routeLocation)); 179 int cars = train.getNumberCarsInTrain(routeLocation); 180 int emptyCars = train.getNumberEmptyCarsInTrain(routeLocation); 181 jsonCars.put(JSON.TOTAL, cars); 182 jsonCars.put(JSON.LOADS, cars - emptyCars); 183 jsonCars.put(JSON.EMPTIES, emptyCars); 184 } else { 185 log.debug("Train terminates in {}", locationName); 186 jsonLocation.put("TrainTerminatesIn", StringEscapeUtils.escapeHtml4(locationName)); 187 } 188 jsonLocation.set(JsonOperations.CARS, jsonCars); 189 locations.add(jsonLocation); 190 } 191 return locations; 192 } 193 194 protected ArrayNode dropEngines(List<Engine> engines, RouteLocation routeLocation) { 195 ArrayNode node = this.mapper.createArrayNode(); 196 for (Engine engine : engines) { 197 if (engine.getRouteDestination() != null && engine.getRouteDestination().equals(routeLocation)) { 198 node.add(this.utilities.getEngine(engine, locale)); 199 } 200 } 201 return node; 202 } 203 204 protected ArrayNode pickupEngines(List<Engine> engines, RouteLocation routeLocation) { 205 ArrayNode node = this.mapper.createArrayNode(); 206 for (Engine engine : engines) { 207 if (engine.getRouteLocation() != null && engine.getRouteLocation().equals(routeLocation)) { 208 node.add(this.utilities.getEngine(engine, locale)); 209 } 210 } 211 return node; 212 } 213 214 // TODO: migrate comments into actual setout/pickup track location spaces 215 private ObjectNode getTrackComments(RouteLocation routeLocation, List<Car> cars) { 216 ObjectNode comments = this.mapper.createObjectNode(); 217 if (routeLocation.getLocation() != null) { 218 List<Track> tracks = routeLocation.getLocation().getTracksByNameList(null); 219 for (Track track : tracks) { 220 ObjectNode jsonTrack = this.mapper.createObjectNode(); 221 // any pick ups or set outs to this track? 222 boolean pickup = false; 223 boolean setout = false; 224 for (Car car : cars) { 225 if (car.getRouteLocation() == routeLocation && car.getTrack() != null && car.getTrack() == track) { 226 pickup = true; 227 } 228 if (car.getRouteDestination() == routeLocation && car.getDestinationTrack() != null 229 && car.getDestinationTrack() == track) { 230 setout = true; 231 } 232 } 233 if (pickup) { 234 jsonTrack.put(JSON.ADD, StringEscapeUtils.escapeHtml4(track.getCommentPickup())); 235 } 236 if (setout) { 237 jsonTrack.put(JSON.REMOVE, StringEscapeUtils.escapeHtml4(track.getCommentSetout())); 238 } 239 if (pickup && setout) { 240 jsonTrack.put(JSON.ADD_AND_REMOVE, StringEscapeUtils.escapeHtml4(track.getCommentBoth())); 241 } 242 if (pickup || setout) { 243 jsonTrack.put(JSON.COMMENT, StringEscapeUtils.escapeHtml4(track.getComment())); 244 comments.set(track.getId(), jsonTrack); 245 } 246 } 247 } 248 return comments; 249 } 250}