Skip to content

Commit

Permalink
Add support for Serialization / deserialization of POJO
Browse files Browse the repository at this point in the history
  • Loading branch information
vrichard12 authored and sbegaudeau committed Jan 11, 2024
1 parent 72ac65d commit 63ed710
Show file tree
Hide file tree
Showing 10 changed files with 349 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*******************************************************************************/
package org.eclipse.sirius.emfjson.utils;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
Expand All @@ -22,6 +23,7 @@
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -38,6 +40,7 @@
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
Expand Down Expand Up @@ -910,21 +913,55 @@ private EObject loadEGenericType(JsonObject jsonObject) {
private void deserializeEAttribute(EAttribute eAttribute, JsonElement jsonElement, EObject eObject) {
EDataType dataType = eAttribute.getEAttributeType();
if (!eAttribute.isMany()) {
String newValue = this.getAsFlexibleString(jsonElement);
Object value = this.tryCreateDataTypeFromString(dataType, newValue);
Object value = null;
if (this.isPojo(dataType)) {
value = new Gson().fromJson(jsonElement, eAttribute.getEType().getInstanceClass());
} else {
String newValue = this.getAsFlexibleString(jsonElement);
value = this.tryCreateDataTypeFromString(dataType, newValue);
}
this.helper.setValue(eObject, eAttribute, value);
} else {
JsonArray asJsonArray = this.getAsFlexibleArray(jsonElement);
Object eGet = this.helper.getValue(eObject, eAttribute);
if (eGet instanceof Collection<?>) {
for (JsonElement jElement : asJsonArray) {
Object value = this.tryCreateDataTypeFromString(dataType, jElement.getAsString());
Object value = null;
if (this.isPojo(dataType)) {
value = new Gson().fromJson(jElement, eAttribute.getEType().getInstanceClass());
} else {
value = this.tryCreateDataTypeFromString(dataType, jElement.getAsString());
}
this.helper.setValue(eObject, eAttribute, value);
}
}
}
}

/**
* List of the datatypes defined by the {@link EcorePackage}.
*/
private static final List<EDataType> ECORE_PACKAGE_DATA_TYPES = Arrays.asList(EcorePackage.eINSTANCE.getEString(), EcorePackage.eINSTANCE.getEBoolean(), EcorePackage.eINSTANCE.getEBooleanObject(),
EcorePackage.eINSTANCE.getEInt(), EcorePackage.eINSTANCE.getEIntegerObject(), EcorePackage.eINSTANCE.getEBigDecimal(), EcorePackage.eINSTANCE.getEBigInteger(),
EcorePackage.eINSTANCE.getEByte(), EcorePackage.eINSTANCE.getEByteArray(), EcorePackage.eINSTANCE.getEByteObject(), EcorePackage.eINSTANCE.getEChar(),
EcorePackage.eINSTANCE.getECharacterObject(), EcorePackage.eINSTANCE.getEDate(), EcorePackage.eINSTANCE.getEDouble(), EcorePackage.eINSTANCE.getEDoubleObject(),
EcorePackage.eINSTANCE.getEFloat(), EcorePackage.eINSTANCE.getEFloatObject(), EcorePackage.eINSTANCE.getELong(), EcorePackage.eINSTANCE.getELongObject(),
EcorePackage.eINSTANCE.getEShort(), EcorePackage.eINSTANCE.getEShortObject());

/**
* Tests if the given {@link EDataType} is a POJO.
*
* @param dataType
* A {@link EDataType} to test
* @return true if the given {@link EDataType} is not one of the {@link EcorePackage} DataTypes nor an
* {@link EEnum}.
*/
private boolean isPojo(EDataType dataType) {
return !ECORE_PACKAGE_DATA_TYPES.contains(dataType) && //
dataType.eClass() != EcorePackage.eINSTANCE.getEEnum() && //
dataType.getInstanceClass() != null;
}

/**
* Try to create a {@link EDataType} instance from a serialized form of the value. If the serialization is not
* compatible with the given EDataType, returns the serialized form of the value unchanged.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
*******************************************************************************/
package org.eclipse.sirius.emfjson.utils;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
Expand Down Expand Up @@ -1035,22 +1037,46 @@ private JsonElement serializeEDataType(EObject eObject, EAttribute eAttribute) {

Object value = this.helper.getValue(eObject, eAttribute);
EFactory eFactoryInstance = eAttribute.getEType().getEPackage().getEFactoryInstance();
boolean isPojo = eAttribute.getEAttributeType().getInstanceClass() != null;
if (eAttribute.isMany()) {
JsonArray jsonArray = new JsonArray();
if (value instanceof Collection) {
Collection<?> collection = (Collection<?>) value;
for (Object object : collection) {
jsonArray.add(new JsonPrimitive(eFactoryInstance.convertToString(eAttribute.getEAttributeType(), object)));
String stringValue = eFactoryInstance.convertToString(eAttribute.getEAttributeType(), object);
if (isPojo) {
if (object == null) {
jsonArray.add(JsonNull.INSTANCE);
} else if (object.toString().equals(stringValue)) {
// EFactory.convertToString() has not been overridden
jsonArray.add(new Gson().toJsonTree(object));
} else {
jsonArray.add(new JsonPrimitive(stringValue));
}
} else {
jsonArray.add(new JsonPrimitive(stringValue));
}
}
}
jsonElement = jsonArray;
} else {
String stringValue = eFactoryInstance.convertToString(eAttribute.getEAttributeType(), value);

if (stringValue == null) {
stringValue = ""; //$NON-NLS-1$
if (isPojo) {
if (value == null) {
jsonElement = JsonNull.INSTANCE;
} else if (value.toString().equals(stringValue)) {
// EFactory.convertToString() has not been overridden
jsonElement = new Gson().toJsonTree(value);
} else {
jsonElement = new JsonPrimitive(stringValue);
}
} else {
if (stringValue == null) {
stringValue = ""; //$NON-NLS-1$
}
jsonElement = new JsonPrimitive(stringValue);
}
jsonElement = new JsonPrimitive(stringValue);

}

return jsonElement;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,20 @@ public void testMultipleSerialiationDataType() {
this.testLoad("NodeMultipleCustomDataType.xmi"); //$NON-NLS-1$
}

/**
* Test deserialization of POJO EDataType EAttribute monovalued.
*/
@Test
public void testLoadSingleValueAttributePojoDataType() {
this.testLoad("NodeSingleValueAttributePojoDataType.xmi"); //$NON-NLS-1$
}

/**
* Test deserialization of POJO EDataType EAttribute multivalued.
*/
@Test
public void testLoadMultiValuedAttributePojoDataType() {
this.testLoad("NodeMultiValuedAttributePojoDataType.xmi"); //$NON-NLS-1$
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,20 @@ public void testMultipleSerialiationDataType() {
this.testSave("NodeMultipleCustomDataType.xmi"); //$NON-NLS-1$
}

/**
* Test serialization of POJO EDataType EAttribute monovalued.
*/
@Test
public void testSaveSingleValueAttributePojoDataType() {
this.testSave("NodeSingleValueAttributePojoDataType.xmi"); //$NON-NLS-1$
}

/**
* Test serialization of POJO EDataType EAttribute multivalued.
*/
@Test
public void testSaveMultiValuedAttributePojoDataType() {
this.testSave("NodeMultiValuedAttributePojoDataType.xmi"); //$NON-NLS-1$
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package model;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*******************************************************************************
* Copyright (c) 2023 Obeo. All rights reserved. This program and the accompanying materials are made available under
* the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors: Obeo - initial API and implementation
*******************************************************************************/

public class TestPojoDataTypeImpl {

/**
* A String value.
*/
private String stringValue = null;

/**
* An integer value.
*/
private int intValue = 0;

/**
* Not serialized.
*/
private transient int transientIntValue = 0;

/**
* Empty constructor for Json serialization.
*
*/
public TestPojoDataTypeImpl() {

}

/**
* Returns the stringValue.
*
* @return The stringValue
*/
public String getStringValue() {
return this.stringValue;
}

/**
* Sets the stringValue.
*
* @param stringValue
* The stringValue to set
*/
public void setStringValue(String stringValue) {
this.stringValue = stringValue;
}

/**
* Returns the intValue.
*
* @return The intValue
*/
public int getIntValue() {
return this.intValue;
}

/**
* Sets the intValue.
*
* @param intValue
* The intValue to set
*/
public void setIntValue(int intValue) {
this.intValue = intValue;
}

/**
* Returns the transientIntValue.
*
* @return The transientIntValue
*/
public int getTransientIntValue() {
return this.transientIntValue;
}

/**
* Sets the transientIntValue.
*
* @param transientIntValue
* The transientIntValue to set
*/
public void setTransientIntValue(int transientIntValue) {
this.transientIntValue = transientIntValue;
}

/**
* Used by the XMI serialization. This doesn't produce as a Json string on purpose, not to interfere with the Json
* serialization.<br>
*
* @return A string representing this {@link TestPojoDataTypeImpl} instance.
*/
@Override
public String toString() {
return String.format("TestPojoDataTypeImpl('%s', %d)", this.stringValue.replaceAll("'", "&apos;"), Integer.valueOf(this.intValue)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}

/**
* Used by the XMI deserialization. This doesn't take a Json string on purpose, not to interfere with the Json
* deserialization.
*
* @param serialized
* The serialized form of a {@link TestPojoDataTypeImpl} as produced by
* {@link TestPojoDataTypeImpl#toString()}.
* @return A new instance of {@link TestPojoDataTypeImpl} if the given serialized form is consistent, null
* otherwise.
*/
public static TestPojoDataTypeImpl valueOf(String serialized) {
TestPojoDataTypeImpl value = null;
Matcher matcher = Pattern.compile("TestPojoDataTypeImpl\\('([^']*)', ([0-9]+)\\)").matcher(serialized); //$NON-NLS-1$
if (matcher.matches()) {
value = new TestPojoDataTypeImpl();
value.setStringValue(matcher.group(1).replaceAll("&apos;", "'")); //$NON-NLS-1$ //$NON-NLS-2$
value.setIntValue(Integer.parseInt(matcher.group(2)));
}
return value;
}

/**
* {@inheritDoc}
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.intValue;
result = prime * result + ((this.stringValue == null) ? 0 : this.stringValue.hashCode());
return result;
}

/**
* {@inheritDoc}
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (this.getClass() != obj.getClass())
return false;
TestPojoDataTypeImpl other = (TestPojoDataTypeImpl) obj;
if (this.intValue != other.intValue)
return false;
if (this.stringValue == null) {
if (other.stringValue != null)
return false;
} else if (!this.stringValue.equals(other.stringValue))
return false;
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
<eStructuralFeatures xsi:type="ecore:EAttribute" name="singleShortAttribute" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EShort"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="singleShortObjectAttribute"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EShortObject"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="singleTestPojoDataTypeAttribute"
eType="#//TestPojoDataType"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="NodeMultiValuedAttribute" eSuperTypes="#//Node">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="multiStringAttribute" upperBound="-1"
Expand Down Expand Up @@ -105,6 +107,8 @@
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EShort"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="multiShortObjectAttribute"
upperBound="-1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EShortObject"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="multiPojoDataTypeAttribute"
upperBound="-1" eType="#//TestPojoDataType"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="NodeSingleValueReference" eSuperTypes="#//Node">
<eStructuralFeatures xsi:type="ecore:EReference" name="singleValuedReference"
Expand Down Expand Up @@ -171,4 +175,5 @@
<eClassifiers xsi:type="ecore:EClass" name="OtherClass">
<eTypeParameters name="aTypeParameter"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EDataType" name="TestPojoDataType" instanceClassName="model.TestPojoDataTypeImpl"/>
</ecore:EPackage>
Loading

0 comments on commit 63ed710

Please sign in to comment.