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