|
| 1 | +package org.lionweb.lioncore.java.serialization; |
| 2 | + |
| 3 | +import com.google.gson.JsonArray; |
| 4 | +import com.google.gson.JsonElement; |
| 5 | +import com.google.gson.JsonObject; |
| 6 | +import org.lionweb.lioncore.java.metamodel.*; |
| 7 | +import org.lionweb.lioncore.java.model.Node; |
| 8 | +import org.lionweb.lioncore.java.model.impl.M3Node; |
| 9 | +import org.lionweb.lioncore.java.self.LionCore; |
| 10 | + |
| 11 | +import java.util.HashMap; |
| 12 | +import java.util.List; |
| 13 | +import java.util.Map; |
| 14 | +import java.util.stream.Collectors; |
| 15 | + |
| 16 | +/** |
| 17 | + * This class is responsible for unserializing models. |
| 18 | + * |
| 19 | + * The unserialization of each node can be affected by different points: |
| 20 | + * 1. Is the Concept used by the Node known to the unserializer? |
| 21 | + * 2. If the Concept is known, is there a specific class that should be used to unserialize that particular concept? |
| 22 | + * |
| 23 | + * Depending on the answers to these questions the Node could be unserialized using a specific class or the generic |
| 24 | + * DynamicNode class. Moreover, it could have access to a proper Concept or a DynamicConcept. |
| 25 | + * |
| 26 | + * For the initial implementation we consider only the case in which the Concept can be resolved and we have a specific |
| 27 | + * Node subclass to be used for the instantiation of the node. |
| 28 | + */ |
| 29 | +public class JsonSerialization { |
| 30 | + |
| 31 | + private static final String CONCEPT_LABEL = "concept"; |
| 32 | + private static final String ID_LABEL = "id"; |
| 33 | + |
| 34 | + /** |
| 35 | + * This has specific support for LionCore or LionCoreBuiltins. |
| 36 | + */ |
| 37 | + public static JsonSerialization getStandardSerialization() { |
| 38 | + JsonSerialization jsonSerialization = new JsonSerialization(); |
| 39 | + jsonSerialization.conceptResolver.registerMetamodel(LionCore.getInstance()); |
| 40 | + jsonSerialization.nodeInstantiator.registerLionCoreCustomUnserializers(); |
| 41 | + jsonSerialization.primitiveValuesSerialization.registerLionBuiltinsPrimitiveSerializersAndUnserializers(); |
| 42 | + return jsonSerialization; |
| 43 | + } |
| 44 | + |
| 45 | + /** |
| 46 | + * This has no specific support for LionCore or LionCoreBuiltins. |
| 47 | + */ |
| 48 | + public static JsonSerialization getBasicSerialization() { |
| 49 | + JsonSerialization jsonSerialization = new JsonSerialization(); |
| 50 | + return jsonSerialization; |
| 51 | + } |
| 52 | + |
| 53 | + private ConceptResolver conceptResolver; |
| 54 | + private NodeInstantiator nodeInstantiator; |
| 55 | + private PrimitiveValuesSerialization primitiveValuesSerialization; |
| 56 | + |
| 57 | + private Map<String, JsonObject> nodeIdToData = new HashMap<>(); |
| 58 | + private Map<String, Node> nodeIdToNode = new HashMap<>(); |
| 59 | + |
| 60 | + private JsonSerialization() { |
| 61 | + // prevent public access |
| 62 | + conceptResolver = new ConceptResolver(); |
| 63 | + nodeInstantiator = new NodeInstantiator(); |
| 64 | + primitiveValuesSerialization = new PrimitiveValuesSerialization(); |
| 65 | + } |
| 66 | + |
| 67 | + public ConceptResolver getConceptResolver() { |
| 68 | + return conceptResolver; |
| 69 | + } |
| 70 | + |
| 71 | + public NodeInstantiator getNodeInstantiator() { |
| 72 | + return nodeInstantiator; |
| 73 | + } |
| 74 | + |
| 75 | + public PrimitiveValuesSerialization getPrimitiveValuesSerialization() { |
| 76 | + return primitiveValuesSerialization; |
| 77 | + } |
| 78 | + |
| 79 | + public JsonElement serialize(Node node) { |
| 80 | + JsonArray arrayOfNodes = new JsonArray(); |
| 81 | + serialize(node, arrayOfNodes); |
| 82 | + return arrayOfNodes; |
| 83 | + } |
| 84 | + |
| 85 | + private void serialize(Node node, JsonArray arrayOfNodes) { |
| 86 | + arrayOfNodes.add(serializeThisNode(node)); |
| 87 | + node.getChildren().forEach(c -> serialize(c, arrayOfNodes)); |
| 88 | + } |
| 89 | + |
| 90 | + private String serializePropertyValue(Object value) { |
| 91 | + if (value == null) { |
| 92 | + return null; |
| 93 | + } |
| 94 | + return value.toString(); |
| 95 | + } |
| 96 | + |
| 97 | + private JsonObject serializeThisNode(Node node) { |
| 98 | + JsonObject jsonObject = new JsonObject(); |
| 99 | + jsonObject.addProperty(CONCEPT_LABEL, node.getConcept().getID()); |
| 100 | + jsonObject.addProperty(ID_LABEL, node.getID()); |
| 101 | + |
| 102 | + JsonObject properties = new JsonObject(); |
| 103 | + node.getConcept().allProperties().forEach(property -> { |
| 104 | + properties.addProperty(property.getID(), serializePropertyValue(node.getPropertyValue(property))); |
| 105 | + }); |
| 106 | + jsonObject.add("properties", properties); |
| 107 | + |
| 108 | + JsonObject children = new JsonObject(); |
| 109 | + node.getConcept().allContainments().forEach(containment -> { |
| 110 | + JsonArray serializedValue = new JsonArray(); |
| 111 | + node.getChildren(containment).forEach(c -> serializedValue.add(c.getID())); |
| 112 | + children.add(containment.getID(), serializedValue); |
| 113 | + }); |
| 114 | + jsonObject.add("children", children); |
| 115 | + |
| 116 | + JsonObject references = new JsonObject(); |
| 117 | + node.getConcept().allReferences().forEach(reference -> { |
| 118 | + JsonArray serializedValue = new JsonArray(); |
| 119 | + node.getReferredNodes(reference).forEach(c -> serializedValue.add(c.getID())); |
| 120 | + references.add(reference.getID(), serializedValue); |
| 121 | + }); |
| 122 | + jsonObject.add("references", references); |
| 123 | + |
| 124 | + return jsonObject; |
| 125 | + } |
| 126 | + |
| 127 | + private <T extends Node> T populateProperties(T instance, JsonObject jsonObject) { |
| 128 | + if (!jsonObject.has("properties") && jsonObject.get("properties").isJsonObject()) { |
| 129 | + throw new IllegalStateException(); |
| 130 | + } |
| 131 | + JsonObject properties = jsonObject.getAsJsonObject("properties"); |
| 132 | + for (String propertyId : properties.keySet()) { |
| 133 | + Property property = instance.getConcept().getPropertyByID(propertyId); |
| 134 | + if (property == null) { |
| 135 | + throw new IllegalArgumentException("Property with id " + propertyId + " not found in " + instance.getConcept()); |
| 136 | + } |
| 137 | + String serializedValue = properties.get(propertyId).getAsString(); |
| 138 | + String typeID = property.getType().getID(); |
| 139 | + if (typeID == null) { |
| 140 | + throw new IllegalStateException("No Node ID for type " + property.getType()); |
| 141 | + } |
| 142 | + Object unserializedValue = primitiveValuesSerialization.unserialize(typeID, serializedValue); |
| 143 | + instance.setPropertyValue(property, unserializedValue); |
| 144 | + } |
| 145 | + |
| 146 | + return instance; |
| 147 | + } |
| 148 | + |
| 149 | + private void populateLinks(Node node, JsonObject data) { |
| 150 | + if (data.has("children")) { |
| 151 | + JsonObject children = data.get("children").getAsJsonObject(); |
| 152 | + for (String containmentID : children.keySet()) { |
| 153 | + Containment containment = node.getConcept().getContainmentByID(containmentID); |
| 154 | + if (containment == null) { |
| 155 | + throw new IllegalStateException(); |
| 156 | + } |
| 157 | + JsonArray value = children.get(containmentID).getAsJsonArray(); |
| 158 | + for (JsonElement childEl : value.asList()) { |
| 159 | + String childId = childEl.getAsString(); |
| 160 | + Node child = nodeIdToNode.get(childId); |
| 161 | + if (child == null) { |
| 162 | + throw new IllegalArgumentException("Child with ID " + childId + " not found"); |
| 163 | + } |
| 164 | + node.addChild(containment, child); |
| 165 | + } |
| 166 | + } |
| 167 | + } |
| 168 | + if (data.has("references")) { |
| 169 | + JsonObject references = data.get("references").getAsJsonObject(); |
| 170 | + for (String referenceID : references.keySet()) { |
| 171 | + Reference reference = node.getConcept().getReferenceByID(referenceID); |
| 172 | + if (reference == null) { |
| 173 | + throw new IllegalStateException("Reference not found: " + referenceID + " in " + node.getConcept()); |
| 174 | + } |
| 175 | + JsonArray value = references.get(referenceID).getAsJsonArray(); |
| 176 | + for (JsonElement referredEl : value.asList()) { |
| 177 | + String referredId = referredEl.getAsString(); |
| 178 | + Node referred = nodeIdToNode.get(referredId); |
| 179 | + node.addReferredNode(reference, referred); |
| 180 | + //throw new UnsupportedOperationException(containmentID); |
| 181 | + } |
| 182 | + } |
| 183 | + } |
| 184 | + if (data.has("parent")) { |
| 185 | + String parentNodeID = data.get("parent").getAsString(); |
| 186 | + Node parent = nodeIdToNode.get(parentNodeID); |
| 187 | + if (node instanceof M3Node) { |
| 188 | + ((M3Node<M3Node>) node).setParent(parent); |
| 189 | + } |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + public List<Node> unserialize(JsonElement jsonElement) { |
| 194 | + if (jsonElement.isJsonArray()) { |
| 195 | + List<Node> nodes = jsonElement.getAsJsonArray().asList().stream().map(element -> { |
| 196 | + try { |
| 197 | + Node node = unserializeNode(element); |
| 198 | + if (node.getID() == null) { |
| 199 | + throw new IllegalStateException(); |
| 200 | + } |
| 201 | + this.nodeIdToData.put(node.getID(), element.getAsJsonObject()); |
| 202 | + this.nodeIdToNode.put(node.getID(), node); |
| 203 | + return node; |
| 204 | + } catch (Exception e) { |
| 205 | + throw new RuntimeException("Issue while unserializing " + element, e); |
| 206 | + } |
| 207 | + }).collect(Collectors.toList()); |
| 208 | + for (Map.Entry<String, JsonObject> entry : nodeIdToData.entrySet()) { |
| 209 | + populateLinks(nodeIdToNode.get(entry.getKey()), entry.getValue()); |
| 210 | + } |
| 211 | + nodeIdToData.clear(); |
| 212 | + nodeIdToNode.clear();; |
| 213 | + return nodes; |
| 214 | + } else { |
| 215 | + throw new IllegalArgumentException("We expected a Json Array, we got instead: " + jsonElement); |
| 216 | + } |
| 217 | + } |
| 218 | + |
| 219 | + private Node unserializeNode(JsonElement jsonElement) { |
| 220 | + if (jsonElement.isJsonObject()) { |
| 221 | + JsonObject jsonObject = jsonElement.getAsJsonObject(); |
| 222 | + String type = getStringProperty(jsonObject, CONCEPT_LABEL); |
| 223 | + Concept concept = conceptResolver.resolveConcept(type); |
| 224 | + String nodeID = getStringProperty(jsonObject, ID_LABEL); |
| 225 | + Node node = nodeInstantiator.instantiate(concept, jsonObject, nodeID); |
| 226 | + populateProperties(node, jsonObject); |
| 227 | + return node; |
| 228 | + } else { |
| 229 | + throw new IllegalArgumentException("We expected a Json Object, we got instead: " + jsonElement); |
| 230 | + } |
| 231 | + } |
| 232 | + |
| 233 | + private String getStringProperty(JsonObject jsonObject, String propertyName) { |
| 234 | + if (!jsonObject.has(propertyName)) { |
| 235 | + throw new IllegalArgumentException(propertyName + " property not found in " + jsonObject); |
| 236 | + } |
| 237 | + JsonElement value = jsonObject.get(propertyName); |
| 238 | + if (value.isJsonPrimitive() && value.getAsJsonPrimitive().isString()) { |
| 239 | + return value.getAsJsonPrimitive().getAsString(); |
| 240 | + } else { |
| 241 | + throw new IllegalArgumentException(propertyName + " property expected to be a string while it is " + value); |
| 242 | + } |
| 243 | + } |
| 244 | +} |
0 commit comments