package gov.nasa.jpl.mbee.mdk.emf;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.nomagic.magicdraw.core.Project;
import com.nomagic.magicdraw.openapi.uml.ModelElementsManager;
import com.nomagic.magicdraw.openapi.uml.ReadOnlyElementException;
import com.nomagic.uml2.ext.jmi.helpers.ModelHelper;
import com.nomagic.uml2.ext.jmi.reflect.AbstractRepository;
import com.nomagic.uml2.ext.magicdraw.auxiliaryconstructs.mdmodels.Model;
import com.nomagic.uml2.ext.magicdraw.auxiliaryconstructs.mdtemplates.ParameterableElement;
import com.nomagic.uml2.ext.magicdraw.auxiliaryconstructs.mdtemplates.TemplateParameter;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.*;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Package;
import com.nomagic.uml2.ext.magicdraw.impl.UMLFactoryImpl;
import com.nomagic.uml2.ext.magicdraw.metadata.UMLFactory;
import com.nomagic.uml2.ext.magicdraw.metadata.UMLPackage;
import gov.nasa.jpl.mbee.mdk.api.function.TriFunction;
import gov.nasa.jpl.mbee.mdk.api.incubating.MDKConstants;
import gov.nasa.jpl.mbee.mdk.api.incubating.convert.Converters;
import gov.nasa.jpl.mbee.mdk.api.incubating.convert.JsonToElementFunction;
import gov.nasa.jpl.mbee.mdk.json.ImportException;
import gov.nasa.jpl.mbee.mdk.json.ReferenceException;
import gov.nasa.jpl.mbee.mdk.util.Changelog;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.ecore.*;
import org.eclipse.emf.ecore.util.EcoreUtil;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Created by igomes on 9/19/16.
*/
public class EMFImporter implements JsonToElementFunction {
protected List<PreProcessor> preProcessors;
protected List<EStructuralFeatureOverride> eStructuralFeatureOverrides;
@Override
public Changelog.Change<Element> apply(ObjectNode objectNode, Project project, Boolean strict) throws ImportException, ReadOnlyElementException {
return convert(objectNode, project, strict);
}
private synchronized Changelog.Change<Element> convert(ObjectNode objectNode, Project project, Boolean strict) throws ImportException, ReadOnlyElementException {
JsonNode jsonNode = objectNode.get(MDKConstants.ID_KEY);
/*if (jsonNode == null || !jsonNode.isTextual()) {
return null;
}*/
Element element = jsonNode != null && jsonNode.isTextual() ? getIdToElementConverter().apply(jsonNode.asText(), project) : null;
Changelog.ChangeType changeType = element != null && !project.isDisposed(element) ? Changelog.ChangeType.UPDATED : Changelog.ChangeType.CREATED;
try {
for (PreProcessor preProcessor : getPreProcessors()) {
element = preProcessor.getFunction().apply(objectNode, project, strict, element);
if (element == null) {
return null;
}
}
if (element.eClass() == null) {
return null;
}
for (EStructuralFeature eStructuralFeature : element.eClass().getEAllStructuralFeatures()) {
final Element finalElement = element;
ImportFunction function = getEStructuralFeatureOverrides().stream().filter(override -> override.getPredicate()
.test(objectNode, eStructuralFeature, project, strict, finalElement)).map(EStructuralFeatureOverride::getFunction)
.findAny().orElse(DEFAULT_E_STRUCTURAL_FEATURE_FUNCTION);
element = function.apply(objectNode, eStructuralFeature, project, strict, element);
if (element == null) {
return null;
}
}
} catch (RuntimeException e) {
throw new ImportException(element, jsonNode, e.getMessage(), e);
}
return new Changelog.Change<>(element, changeType);
}
protected List<PreProcessor> getPreProcessors() {
if (preProcessors == null) {
preProcessors = Arrays.asList(PreProcessor.CREATE, PreProcessor.EDITABLE, PreProcessor.DOCUMENTATION, PreProcessor.SYSML_ID_VALIDATION);
}
return preProcessors;
}
public static class PreProcessor {
public static final PreProcessor
CREATE = getCreatePreProcessor(Converters.getIdToElementConverter()),
EDITABLE = new PreProcessor(
(objectNode, project, strict, element) -> {
if (!element.isEditable()) {
throw new ReadOnlyElementException(element);
}
return element;
}
),
DOCUMENTATION = new PreProcessor(
(objectNode, project, strict, element) -> {
JsonNode jsonNode = objectNode.get("documentation");
if (jsonNode != null && jsonNode.isTextual()) {
ModelHelper.setComment(element, jsonNode.asText());
}
return element;
}
),
SYSML_ID_VALIDATION = new PreProcessor(
(objectNode, project, strict, element) -> {
JsonNode jsonNode = objectNode.get(MDKConstants.ID_KEY);
if (jsonNode == null || !jsonNode.isTextual()) {
return element;
}
String id = jsonNode.asText();
if (id.startsWith(MDKConstants.HIDDEN_ID_PREFIX)) {
return null;
}
return element;
}
);
static PreProcessor getCreatePreProcessor(BiFunction<String, Project, Element> idToElementConverter) {
return new PreProcessor(
(objectNode, project, strict, element) -> {
if (element != null) {
return element;
}
JsonNode jsonNode = objectNode.get(MDKConstants.TYPE_KEY);
if (jsonNode == null || !jsonNode.isTextual()) {
return null;
}
String type = jsonNode.asText();
/*if (type.equals("View") || type.equals("Document")) {
type = "Class";
}*/
if (type.equals(UMLPackage.Literals.DIAGRAM.getName())) {
JsonNode diagramTypeJsonNode = objectNode.get(MDKConstants.DIAGRAM_TYPE_KEY);
if (diagramTypeJsonNode == null || !diagramTypeJsonNode.isTextual()) {
return null;
}
JsonNode ownerJsonNode = objectNode.get(MDKConstants.OWNER_ID_KEY);
if (ownerJsonNode == null || !ownerJsonNode.isTextual()) {
return null;
}
// TODO CHANGE ME @donbot
Element owner = idToElementConverter.apply(ownerJsonNode.asText(), project);
if (owner == null || !(owner instanceof Namespace)) {
return null;
}
try {
return ModelElementsManager.getInstance().createDiagram(diagramTypeJsonNode.asText(), (Namespace) owner);
} catch (ReadOnlyElementException e) {
throw new ImportException(element, objectNode, e.getMessage(), e);
}
}
EClassifier eClassifier = UMLPackage.eINSTANCE.getEClassifier(type);
if (!(eClassifier instanceof EClass)) {
return null;
}
AbstractRepository initialRepository = (UMLFactory.eINSTANCE instanceof UMLFactoryImpl) ? ((UMLFactoryImpl) UMLFactory.eINSTANCE).getRepository() : null;
EObject eObject;
try {
UMLFactory.eINSTANCE.setRepository(project.getRepository());
eObject = UMLFactory.eINSTANCE.create((EClass) eClassifier);
}
finally {
UMLFactory.eINSTANCE.setRepository(initialRepository);
}
if (!(eObject instanceof Element)) {
return null;
}
return (Element) eObject;
}
);
}
private PreProcessorFunction function;
PreProcessor(PreProcessorFunction function) {
this.function = function;
}
public PreProcessorFunction getFunction() {
return function;
}
}
private static final Function<EStructuralFeature, String> KEY_FUNCTION = eStructuralFeature -> {
String key = eStructuralFeature.getName();
if (eStructuralFeature instanceof EReference && EObject.class.isAssignableFrom(((EReference) eStructuralFeature).getEReferenceType().getInstanceClass())
&& !ValueSpecification.class.isAssignableFrom(((EReference) eStructuralFeature).getEReferenceType().getInstanceClass())) {
key += "Id" + (eStructuralFeature.isMany() ? "s" : "");
}
return key;
};
private final DeserializationFunction DEFAULT_DESERIALIZATION_FUNCTION = (key, jsonNode, ignoreMultiplicity, objectNode, eStructuralFeature, project, strict, element) -> {
if (jsonNode == null || jsonNode instanceof NullNode) {
return null;
}
else if (!ignoreMultiplicity && eStructuralFeature.isMany()) {
if (!(jsonNode instanceof ArrayNode)) {
if (strict) {
throw new ImportException(element, objectNode, "Expected ArrayNode for key \"" + key + "\" in JSON, but instead got " + jsonNode.getClass().getSimpleName() + ".");
}
return null;
}
Collection<Object> collection = eStructuralFeature.isUnique() ? new UniqueEList<>() : new BasicEList<>();
for (JsonNode nestedJsonNode : jsonNode) {
Object deserialized = this.DEFAULT_DESERIALIZATION_FUNCTION.apply(key, nestedJsonNode, true, objectNode, eStructuralFeature, project, strict, element);
if (deserialized == null && !nestedJsonNode.isNull()) {
if (strict) {
throw new ImportException(element, objectNode, "Failed to deserialize " + eStructuralFeature + " for " + element + ": " + jsonNode + " - " + jsonNode.getClass());
}
continue;
}
if (deserialized != null && !eStructuralFeature.getEType().getInstanceClass().isAssignableFrom(deserialized.getClass())) {
if (strict) {
throw new ImportException(element, objectNode, "Expected a " + eStructuralFeature.getEType().getInstanceClass().getSimpleName() + " upon deserializing \"" + key + "\", but instead got a " + deserialized.getClass());
}
continue;
}
collection.add(deserialized);
}
return collection;
}
else if (eStructuralFeature instanceof EReference) {
EReference eReference = (EReference) eStructuralFeature;
if (ValueSpecification.class.isAssignableFrom(eReference.getEReferenceType().getInstanceClass()) && jsonNode instanceof ObjectNode) {
Changelog.Change<Element> change = this.convert((ObjectNode) jsonNode, project, strict);
return change != null ? change.getChanged() : null;
}
if (!jsonNode.isTextual()) {
if (strict) {
throw new ReferenceException(element, objectNode, "Expected a String for key \"" + key + "\" in JSON, but instead got a " + jsonNode.getClass().getSimpleName() + ".");
}
return null;
}
String id = jsonNode.asText();
Element referencedElement = getIdToElementConverter().apply(id, project);
System.out.println("[LOOKUP] " + id + " -> " + referencedElement);
if (referencedElement == null) {
if (strict) {
throw new ReferenceException(element, objectNode, "Could not find referenced element " + id + " in model for key \"" + key + "\" in JSON.");
}
return null;
}
if (!eReference.getEReferenceType().getInstanceClass().isAssignableFrom(referencedElement.getClass())) {
if (strict) {
throw new ReferenceException(element, objectNode, "Expected a " + eReference.getEReferenceType().getInstanceClass().getSimpleName() + " for key \"" + key + "\" in JSON, but instead got a " + referencedElement.getClass().getSimpleName() + ".");
}
return null;
}
return referencedElement;
}
else if (eStructuralFeature.getEType() instanceof EDataType && jsonNode.isTextual()) {
return EcoreUtil.createFromString((EDataType) eStructuralFeature.getEType(), jsonNode.asText());
}
else if (jsonNode.isTextual()) {
return jsonNode.asText();
}
else if (jsonNode.isBoolean()) {
return jsonNode.asBoolean();
}
else if (jsonNode.isInt()) {
return jsonNode.asInt();
}
else if (jsonNode.isDouble()) {
jsonNode.asDouble();
}
else if (jsonNode.isLong()) {
return jsonNode.asLong();
}
else if (jsonNode.isShort()) {
return jsonNode.shortValue();
}
else if (jsonNode.isFloat()) {
return jsonNode.floatValue();
}
else if (jsonNode.isBigInteger()) {
return jsonNode.bigIntegerValue();
}
else if (jsonNode.isBigDecimal()) {
return jsonNode.decimalValue();
}
else if (jsonNode.isBinary()) {
try {
return jsonNode.binaryValue();
} catch (IOException e) {
throw new ImportException(element, objectNode, "Failed to deserialize binary value.", e);
}
}
// if we get here we have no idea what to do with this object
return null;
};
private final ImportFunction DEFAULT_E_STRUCTURAL_FEATURE_FUNCTION = (objectNode, eStructuralFeature, project, strict, element) -> {
if (!eStructuralFeature.isChangeable() || eStructuralFeature.isVolatile() || eStructuralFeature.isTransient() || eStructuralFeature.isUnsettable() || eStructuralFeature.isDerived() || eStructuralFeature.getName().startsWith(MDKConstants.DERIVED_KEY_PREFIX)) {
return EMFImporter.EMPTY_E_STRUCTURAL_FEATURE_FUNCTION.apply(objectNode, eStructuralFeature, project, strict, element);
}
return this.UNCHECKED_E_STRUCTURAL_FEATURE_FUNCTION.apply(objectNode, eStructuralFeature, project, strict, element);
};
private final ImportFunction UNCHECKED_E_STRUCTURAL_FEATURE_FUNCTION = (objectNode, eStructuralFeature, project, strict, element) -> {
String key = KEY_FUNCTION.apply(eStructuralFeature);
JsonNode jsonNode = objectNode.get(key);
if (jsonNode == null) {
/*if (strict) {
throw new ImportException(element, objectNode, "Required key \"" + key + "\" missing from JSON.");
}*/
return element;
}
Object deserialized = DEFAULT_DESERIALIZATION_FUNCTION.apply(key, jsonNode, false, objectNode, eStructuralFeature, project, strict, element);
if (deserialized == null && !jsonNode.isNull()) {
if (strict) {
throw new ImportException(element, objectNode, "Failed to deserialize " + eStructuralFeature + " for " + element + ": " + jsonNode + " - " + jsonNode.getClass());
}
return element;
}
if (eStructuralFeature.isMany() && !(deserialized instanceof Collection)) {
if (strict) {
throw new ImportException(element, objectNode, "Expected a Collection for key \"" + key + "\" in JSON, but instead got a " + jsonNode.getClass().getSimpleName() + ".");
}
return element;
}
try {
EMFImporter.UNCHECKED_SET_E_STRUCTURAL_FEATURE_FUNCTION.apply(deserialized, eStructuralFeature, element);
} catch (ClassCastException | IllegalArgumentException e) {
if (strict) {
throw new ImportException(element, objectNode, "An unexpected exception occurred while setting " + eStructuralFeature + " of type " + eStructuralFeature.getEType() + " for " + element + " to " + jsonNode, e);
}
return element;
}
return element;
};
@SuppressWarnings("unchecked")
private static final TriFunction<Object, EStructuralFeature, Element, Element> UNCHECKED_SET_E_STRUCTURAL_FEATURE_FUNCTION = (object, eStructuralFeature, element) -> {
Object currentValue = element.eGet(eStructuralFeature);
if (object == null && currentValue == null) {
return element;
}
if (object != null && currentValue != null && (object == currentValue || object.equals(currentValue))) {
return element;
}
if (eStructuralFeature.isMany() && object instanceof Collection && currentValue instanceof Collection) {
Collection<Object> currentCollection = (Collection<Object>) currentValue;
Collection<Object> newCollection = (Collection<Object>) object;
/*if (eStructuralFeature.isOrdered() && object instanceof List && currentValue instanceof List && currentValue.equals(object)) {
return element;
}
if (!eStructuralFeature.isOrdered()) {
CollectionUtils.isEqualCollection()
}*/
currentCollection.clear();
currentCollection.addAll(newCollection);
return element;
}
element.eSet(eStructuralFeature, object);
return element;
};
private static final ImportFunction EMPTY_E_STRUCTURAL_FEATURE_FUNCTION = (objectNode, eStructuralFeature, project, strict, element) -> element;
protected static class EStructuralFeatureOverride {
public static final EStructuralFeatureOverride
ID = new EStructuralFeatureOverride(
(objectNode, eStructuralFeature, project, strict, element) -> eStructuralFeature == element.eClass().getEIDAttribute(),
(objectNode, eStructuralFeature, project, strict, element) -> {
JsonNode jsonNode = objectNode.get(MDKConstants.ID_KEY);
if (jsonNode == null || !jsonNode.isTextual()) {
/*if (strict) {
throw new ImportException(element, objectNode, "Element JSON has missing/malformed ID.");
}
return null;*/
return element;
}
try {
boolean initialCanResetIDForObject = project.getCounter().canResetIDForObject();
project.getCounter().setCanResetIDForObject(true);
UNCHECKED_SET_E_STRUCTURAL_FEATURE_FUNCTION.apply(jsonNode.asText(), element.eClass().getEIDAttribute(), element);
project.getCounter().setCanResetIDForObject(initialCanResetIDForObject);
} catch (IllegalArgumentException e) {
throw new ImportException(element, objectNode, "Unexpected illegal argument exception. See logs for more information.", e);
}
return element;
}
),
OWNER = getOwnerEStructuralFeatureOverride(Converters.getIdToElementConverter());
private ImportPredicate importPredicate;
private ImportFunction importFunction;
public EStructuralFeatureOverride(ImportPredicate importPredicate, ImportFunction importFunction) {
this.importPredicate = importPredicate;
this.importFunction = importFunction;
}
public ImportPredicate getPredicate() {
return importPredicate;
}
public ImportFunction getFunction() {
return importFunction;
}
protected static EStructuralFeatureOverride getOwnerEStructuralFeatureOverride(BiFunction<String, Project, Element> idToElementConverter) {
return new EStructuralFeatureOverride(
(objectNode, eStructuralFeature, project, strict, element) -> UMLPackage.Literals.ELEMENT__OWNER == eStructuralFeature,
(objectNode, eStructuralFeature, project, strict, element) -> {
if (element instanceof Model || element instanceof ValueSpecification) {
return element;
}
JsonNode jsonNode = objectNode.get(MDKConstants.OWNER_ID_KEY);
if (jsonNode == null || !jsonNode.isTextual()) {
if (strict) {
throw new ImportException(element, objectNode, "Element JSON has missing/malformed owner.");
}
return null;
}
String owningElementId = jsonNode.asText();
Element owningElement = idToElementConverter.apply(owningElementId, project);
if (element instanceof Package
&& (jsonNode = objectNode.get(MDKConstants.ID_KEY)) != null && jsonNode.isTextual() && jsonNode.asText().startsWith(MDKConstants.HOLDING_BIN_ID_PREFIX)
&& owningElementId.equals(Converters.getIProjectToIdConverter().apply(project.getPrimaryProject()))) {
((Package) element).setOwningPackage(project.getPrimaryModel());
return element;
}
if (owningElement == null) {
if (strict) {
JsonNode sysmlIdNode = objectNode.get(MDKConstants.ID_KEY);
throw new ImportException(element, objectNode, "Owner for element " + (sysmlIdNode != null && sysmlIdNode.isTextual() ? sysmlIdNode.asText("<>") : "<>") + " not found: " + jsonNode + ".");
}
}
try {
//element.setOwner(owningElement);
if (element instanceof PackageableElement && owningElement instanceof Package) {
((PackageableElement) element).setOwningPackage((Package) owningElement);
}
else if (element instanceof ParameterableElement && owningElement instanceof TemplateParameter) {
((ParameterableElement) element).setOwningTemplateParameter((TemplateParameter) owningElement);
}
else if (element instanceof Slot && owningElement instanceof InstanceSpecification) {
((Slot) element).setOwningInstance((InstanceSpecification) owningElement);
}
else if (element instanceof InstanceSpecification
&& ((jsonNode = objectNode.get(KEY_FUNCTION.apply(UMLPackage.Literals.INSTANCE_SPECIFICATION__STEREOTYPED_ELEMENT))) != null && jsonNode.isTextual()
|| (jsonNode = objectNode.get(MDKConstants.ID_KEY)) != null && jsonNode.isTextual() && jsonNode.asText().endsWith(MDKConstants.APPLIED_STEREOTYPE_INSTANCE_ID_SUFFIX))) {
((InstanceSpecification) element).setStereotypedElement(owningElement);
}
else {
element.setOwner(owningElement);
}
} catch (IllegalArgumentException e) {
System.out.println("ELEMENT: " + element + (element != null ? " " + Converters.getElementToIdConverter().apply(element) : ""));
System.out.println("OWNER: " + owningElement + (owningElement != null ? " " + Converters.getElementToIdConverter().apply(owningElement) : ""));
throw new ImportException(element, objectNode, "Unexpected illegal argument exception. See logs for more information.", e);
}
return element;
}
);
}
}
protected List<EStructuralFeatureOverride> getEStructuralFeatureOverrides() {
if (eStructuralFeatureOverrides == null) {
eStructuralFeatureOverrides = Arrays.asList(EStructuralFeatureOverride.ID, EStructuralFeatureOverride.OWNER);
}
return eStructuralFeatureOverrides;
}
protected BiFunction<String, Project, Element> getIdToElementConverter() {
return Converters.getIdToElementConverter();
}
@FunctionalInterface
public interface PreProcessorFunction {
Element apply(ObjectNode objectNode, Project project, boolean strict, Element element) throws ImportException, ReadOnlyElementException;
}
@FunctionalInterface
interface DeserializationFunction {
Object apply(String key, JsonNode jsonNode, boolean ignoreMultiplicity, ObjectNode objectNode, EStructuralFeature eStructuralFeature, Project project, boolean strict, Element element) throws ImportException, ReadOnlyElementException;
}
@FunctionalInterface
protected interface ImportFunction {
Element apply(ObjectNode objectNode, EStructuralFeature eStructuralFeature, Project project, boolean strict, Element element) throws ImportException, ReadOnlyElementException;
}
@FunctionalInterface
interface ImportPredicate {
boolean test(ObjectNode objectNode, EStructuralFeature eStructuralFeature, Project project, boolean strict, Element element);
}
}