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(JSON.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 = routeLocation.getSplitName(); 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, 103 StringEscapeUtils.escapeHtml4(routeLocation.getLocation().getCommentWithColor())); 104 locationNode.put(JSON.NAME, routeLocation.getLocation().getId()); 105 jsonLocation.set(JSON.LOCATION, locationNode); 106 jsonLocation.put(JSON.COMMENT, StringEscapeUtils.escapeHtml4(routeLocation.getCommentWithColor())); 107 // engine change or helper service? 108 if (train.getSecondLegOptions() != Train.NO_CABOOSE_OR_FRED) { 109 ArrayNode options = this.mapper.createArrayNode(); 110 if (routeLocation == train.getSecondLegStartRouteLocation()) { 111 if ((train.getSecondLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) { 112 options.add(JSON.ADD_HELPERS); 113 } else if ((train.getSecondLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE 114 || (train.getSecondLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) { 115 options.add(JSON.CHANGE_CABOOSE); 116 } else if ((train.getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 117 options.add(JSON.CHANGE_ENGINES); 118 } 119 } 120 if (routeLocation == train.getSecondLegEndRouteLocation()) { 121 options.add(JSON.REMOVE_HELPERS); 122 } 123 jsonLocation.set(JSON.OPTIONS, options); 124 } 125 if (train.getThirdLegOptions() != Train.NO_CABOOSE_OR_FRED) { 126 ArrayNode options = this.mapper.createArrayNode(); 127 if (routeLocation == train.getThirdLegStartRouteLocation()) { 128 if ((train.getThirdLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) { 129 options.add(JSON.ADD_HELPERS); 130 } else if ((train.getThirdLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE 131 || (train.getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) { 132 options.add(JSON.CHANGE_CABOOSE); 133 } else if ((train.getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 134 options.add(JSON.CHANGE_ENGINES); 135 } 136 } 137 if (routeLocation == train.getThirdLegEndRouteLocation()) { 138 options.add(JSON.ADD_HELPERS); 139 } 140 jsonLocation.set(JSON.OPTIONS, options); 141 } 142 143 ObjectNode engines = this.mapper.createObjectNode(); 144 engines.set(JSON.ADD, pickupEngines(engineList, routeLocation)); 145 engines.set(JSON.REMOVE, dropEngines(engineList, routeLocation)); 146 jsonLocation.set(JSON.ENGINES, engines); 147 148 // block cars by destination 149 // caboose or FRED is placed at end of the train 150 // passenger cars are already blocked in the car list 151 // passenger cars with negative block numbers are placed at 152 // the front of the train, positive numbers at the end of 153 // the train. 154 ArrayNode pickups = this.mapper.createArrayNode(); 155 for (RouteLocation destination : route) { 156 for (Car car : carList) { 157 if (isNextCar(car, routeLocation, destination)) { 158 pickups.add(this.utilities.getCar(car, locale)); 159 } 160 } 161 } 162 jsonCars.set(JSON.ADD, pickups); 163 // car set outs 164 ArrayNode setouts = this.mapper.createArrayNode(); 165 for (Car car : carList) { 166 if (car.getRouteDestination() == routeLocation) { 167 setouts.add(this.utilities.getCar(car, locale)); 168 } 169 } 170 jsonCars.set(JSON.REMOVE, setouts); 171 172 jsonLocation.set(JSON.TRACK, this.getTrackComments(routeLocation, carList)); 173 174 if (routeLocation != train.getTrainTerminatesRouteLocation()) { 175 jsonLocation.put(JSON.TRAIN_DIRECTION, routeLocation.getTrainDirection()); 176 ObjectNode length = this.mapper.createObjectNode(); 177 length.put(JSON.LENGTH, train.getTrainLength(routeLocation)); 178 length.put(JSON.UNIT, Setup.getLengthUnit()); 179 jsonLocation.set(JSON.LENGTH, length); 180 jsonLocation.put(JSON.WEIGHT, train.getTrainWeight(routeLocation)); 181 int cars = train.getNumberCarsInTrain(routeLocation); 182 int emptyCars = train.getNumberEmptyCarsInTrain(routeLocation); 183 jsonCars.put(JSON.TOTAL, cars); 184 jsonCars.put(JSON.LOADS, cars - emptyCars); 185 jsonCars.put(JSON.EMPTIES, emptyCars); 186 } else { 187 log.debug("Train terminates in {}", locationName); 188 jsonLocation.put("TrainTerminatesIn", StringEscapeUtils.escapeHtml4(locationName)); 189 } 190 jsonLocation.set(JsonOperations.CARS, jsonCars); 191 locations.add(jsonLocation); 192 } 193 return locations; 194 } 195 196 protected ArrayNode dropEngines(List<Engine> engines, RouteLocation routeLocation) { 197 ArrayNode node = this.mapper.createArrayNode(); 198 for (Engine engine : engines) { 199 if (engine.getRouteDestination() != null && engine.getRouteDestination().equals(routeLocation)) { 200 node.add(this.utilities.getEngine(engine, locale)); 201 } 202 } 203 return node; 204 } 205 206 protected ArrayNode pickupEngines(List<Engine> engines, RouteLocation routeLocation) { 207 ArrayNode node = this.mapper.createArrayNode(); 208 for (Engine engine : engines) { 209 if (engine.getRouteLocation() != null && engine.getRouteLocation().equals(routeLocation)) { 210 node.add(this.utilities.getEngine(engine, locale)); 211 } 212 } 213 return node; 214 } 215 216 // TODO: migrate comments into actual setout/pickup track location spaces 217 private ObjectNode getTrackComments(RouteLocation routeLocation, List<Car> cars) { 218 ObjectNode comments = this.mapper.createObjectNode(); 219 if (routeLocation.getLocation() != null) { 220 List<Track> tracks = routeLocation.getLocation().getTracksByNameList(null); 221 for (Track track : tracks) { 222 ObjectNode jsonTrack = this.mapper.createObjectNode(); 223 jsonTrack.put(JSON.ADD, StringEscapeUtils.escapeHtml4(track.getCommentPickupWithColor())); 224 jsonTrack.put(JSON.REMOVE, StringEscapeUtils.escapeHtml4(track.getCommentSetoutWithColor())); 225 jsonTrack.put(JSON.ADD_AND_REMOVE, StringEscapeUtils.escapeHtml4(track.getCommentBothWithColor())); 226 jsonTrack.put(JSON.COMMENT, StringEscapeUtils.escapeHtml4(track.getComment())); 227 comments.set(track.getId(), jsonTrack); 228 } 229 } 230 return comments; 231 } 232}