Skip to content

Commit a6804f4

Browse files
authored
Merge pull request #24 from LIonWeb-org/serializationWithConcreteClasses
Initial serialization version
2 parents 764efd4 + 2bfb624 commit a6804f4

File tree

14 files changed

+1414
-26
lines changed

14 files changed

+1414
-26
lines changed

core/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ dependencies {
3434
javadocConfig 'com.jetbrains:mps-openapi:2021.3.1'
3535

3636
javadocConfig 'org.modelix:model-api:1.3.2'
37+
38+
implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1'
3739
}
3840

3941
javadoc {

core/src/main/java/org/lionweb/lioncore/java/metamodel/MetamodelElement.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ public MetamodelElement(@Nullable Metamodel metamodel, @Nullable String simpleNa
2828

2929
public MetamodelElement(@Nullable Metamodel metamodel, @Nullable String simpleName) {
3030
// TODO enforce uniqueness of the name within the Metamodel
31-
Naming.validateSimpleName(simpleName);
3231
this.setMetamodel(metamodel);
3332
this.setSimpleName(simpleName);
3433
}

core/src/main/java/org/lionweb/lioncore/java/model/Node.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,13 @@ public interface Node extends HasFeatureValues {
8181
*/
8282
void addAnnotation(AnnotationInstance instance);
8383

84+
default Object getPropertyValueByName(String propertyName) {
85+
Property property = this.getConcept().getPropertyByName(propertyName);
86+
return getPropertyValue(property);
87+
}
88+
89+
default Object getPropertyValueByID(String propertyID) {
90+
Property property = this.getConcept().getPropertyByID(propertyID);
91+
return getPropertyValue(property);
92+
}
8493
}

core/src/main/java/org/lionweb/lioncore/java/model/impl/M3Node.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,11 @@ public List<Node> getChildren(Containment containment) {
135135

136136
@Override
137137
public void addChild(Containment containment, Node child) {
138-
throw new UnsupportedOperationException();
138+
if (containment.isMultiple()) {
139+
addLinkMultipleValue(containment.getSimpleName(), child, true);
140+
} else {
141+
setLinkSingleValue(containment.getSimpleName(), child, true);
142+
}
139143
}
140144

141145
@Override

core/src/main/java/org/lionweb/lioncore/java/self/LionCore.java

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,12 @@ public static Metamodel getInstance() {
109109
// Now we start adding the features to all the Concepts and ConceptInterfaces
110110

111111
concept.setExtendedConcept(featuresContainer);
112-
concept.addFeature(Property.createRequired("abstract", LionCoreBuiltins.getBoolean()));
113-
concept.addFeature(Reference.createOptional("extended", concept));
114-
concept.addFeature(Reference.createMultiple("implemented", conceptInterface));
112+
concept.addFeature(Property.createRequired("abstract", LionCoreBuiltins.getBoolean(), "LIonCore_M3_Concept_abstract"));
113+
concept.addFeature(Reference.createOptional("extended", concept, "LIonCore_M3_Concept_extends"));
114+
concept.addFeature(Reference.createMultiple("implemented", conceptInterface, "LIonCore_M3_Concept_implements"));
115115

116116
conceptInterface.setExtendedConcept(featuresContainer);
117-
conceptInterface.addFeature(Reference.createMultiple("extended", conceptInterface));
117+
conceptInterface.addFeature(Reference.createMultiple("extended", conceptInterface, "LIonCore_M3_ConceptInterface_extends"));
118118

119119
containment.setExtendedConcept(link);
120120

@@ -128,35 +128,40 @@ public static Metamodel getInstance() {
128128
enumerationLiteral.setExtendedConcept(namespacedEntity);
129129

130130
feature.setExtendedConcept(namespacedEntity);
131-
feature.addFeature(Property.createRequired("optional", LionCoreBuiltins.getBoolean()));
131+
feature.addFeature(Property.createRequired("optional", LionCoreBuiltins.getBoolean(), "LIonCore_M3_Feature_optional"));
132+
feature.addFeature(Property.createRequired("derived", LionCoreBuiltins.getBoolean(), "LIonCore_M3_Feature_derived"));
132133

133134
featuresContainer.setExtendedConcept(metamodelElement);
134135
featuresContainer.addImplementedInterface(namespaceProvider);
135-
featuresContainer.addFeature(Containment.createMultiple("features", feature));
136+
featuresContainer.addFeature(Containment.createMultiple("features", feature, "LIonCore_M3_FeaturesContainer_features"));
136137

137-
link.setExtendedConcept(featuresContainer);
138-
link.addFeature(Property.createRequired("multiple", LionCoreBuiltins.getBoolean()));
139-
link.addFeature(Reference.createRequired("type", featuresContainer));
138+
link.setExtendedConcept(feature);
139+
link.addFeature(Property.createRequired("multiple", LionCoreBuiltins.getBoolean(), "LIonCore_M3_Link_multiple"));
140+
link.addFeature(Reference.createRequired("type", featuresContainer, "LIonCore_M3_Link_type"));
140141

141142
metamodel.addImplementedInterface(namespaceProvider);
143+
metamodel.addFeature(Property.createRequired("qualifiedName", LionCoreBuiltins.getString(), "LIonCore_M3_Metamodel_qualifiedName"));
142144
metamodel.addFeature(Reference.createMultiple("dependsOn", metamodel));
143-
metamodel.addFeature(Containment.createMultiple("elements", metamodelElement));
145+
metamodel.addFeature(Containment.createMultiple("elements", metamodelElement, "LIonCore_M3_Metamodel_elements"));
144146

145147
metamodelElement.setExtendedConcept(namespacedEntity);
146148
metamodel.setAbstract(true);
147149

148150
namespacedEntity.setAbstract(true);
149-
namespacedEntity.addFeature(Property.createRequired("simpleName", LionCoreBuiltins.getString()));
150-
namespacedEntity.addFeature(Property.createRequired("qualifiedName", LionCoreBuiltins.getString()));
151+
namespacedEntity.addFeature(Property.createRequired("simpleName", LionCoreBuiltins.getString(), "LIonCore_M3_NamespacedEntity_simpleName"));
152+
namespacedEntity.addFeature(Property.createRequired("qualifiedName", LionCoreBuiltins.getString(),
153+
"LIonCore_M3_NamespacedEntity_qualifiedName").setDerived(true));
151154

152-
namespaceProvider.addFeature(Property.createRequired("namespaceQualifier", LionCoreBuiltins.getString()));
155+
namespaceProvider.addFeature(Property.createRequired("namespaceQualifier", LionCoreBuiltins.getString(), "LIonCore_M3_NamespaceProvider_namespaceQualifier"));
153156

154157
primitiveType.setExtendedConcept(dataType);
155158

156159
property.setExtendedConcept(feature);
157-
property.addFeature(Reference.createRequired("type", dataType));
160+
property.addFeature(Reference.createRequired("type", dataType, "LIonCore_M3_Property_type"));
158161

159162
reference.setExtendedConcept(link);
163+
164+
checkIDs(INSTANCE);
160165
}
161166
checkIDs(INSTANCE);
162167
return INSTANCE;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.lionweb.lioncore.java.serialization;
2+
3+
import org.lionweb.lioncore.java.metamodel.Concept;
4+
import org.lionweb.lioncore.java.metamodel.Metamodel;
5+
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
9+
/**
10+
* This class is responsible to resolve the Concept associate with a given Concept ID.
11+
* <p>
12+
* While initially just know concepts which have been explicitly registered, in the future it could
13+
* adopt more advanced resolution strategies.
14+
*/
15+
public class ConceptResolver {
16+
private Map<String, Concept> registeredConcepts = new HashMap<>();
17+
18+
public Concept resolveConcept(String conceptID) {
19+
if (registeredConcepts.containsKey(conceptID)) {
20+
return registeredConcepts.get(conceptID);
21+
} else {
22+
throw new RuntimeException("Unable to resolve concept with id " + conceptID);
23+
}
24+
}
25+
26+
public void registerMetamodel(Metamodel metamodel) {
27+
metamodel.getElements().forEach(e -> {
28+
if (e instanceof Concept) {
29+
registerConcept((Concept) e);
30+
}
31+
});
32+
}
33+
34+
private void registerConcept(Concept concept) {
35+
registeredConcepts.put(concept.getID(), concept);
36+
}
37+
}
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
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

Comments
 (0)