package gov.nasa.jpl.mbee.mdk.emf;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.*;
import com.nomagic.ci.persistence.IAttachedProject;
import com.nomagic.magicdraw.core.Project;
import com.nomagic.magicdraw.core.ProjectUtilities;
import com.nomagic.magicdraw.esi.EsiUtils;
import com.nomagic.uml2.ext.jmi.helpers.ModelHelper;
import com.nomagic.uml2.ext.jmi.helpers.StereotypesHelper;
import com.nomagic.uml2.ext.magicdraw.auxiliaryconstructs.mdmodels.Model;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.*;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Package;
import com.nomagic.uml2.ext.magicdraw.compositestructures.mdinternalstructures.Connector;
import com.nomagic.uml2.ext.magicdraw.mdprofiles.Stereotype;
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.stream.MDKCollectors;
import gov.nasa.jpl.mbee.mdk.json.JacksonUtils;
import gov.nasa.jpl.mbee.mdk.util.Utils;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
public class EMFExporter implements BiFunction<Element, Project, ObjectNode> {
@Override
public ObjectNode apply(Element element, Project project) {
return convert(element, project);
}
private static ObjectNode convert(Element element, Project project) {
return convert(element, project, false);
}
private static ObjectNode convert(Element element, Project project, boolean nestedValueSpecification) {
if (element == null) {
return null;
}
ObjectNode objectNode = JacksonUtils.getObjectMapper().createObjectNode();
for (PreProcessor preProcessor : PreProcessor.values()) {
if (nestedValueSpecification && preProcessor == PreProcessor.VALUE_SPECIFICATION) {
continue;
}
try {
objectNode = preProcessor.getFunction().apply(element, project, objectNode);
} catch (RuntimeException e) {
e.printStackTrace();
System.out.println("EXCEPTION: " + element.getHumanName() + " | " + element.getID() + " in " + project.getName());
}
if (objectNode == null) {
return null;
}
}
for (EStructuralFeature eStructuralFeature : element.eClass().getEAllStructuralFeatures()) {
ExportFunction function = Arrays.stream(EStructuralFeatureOverride.values())
.filter(override -> override.getPredicate().test(element, eStructuralFeature)).map(EStructuralFeatureOverride::getFunction)
.findAny().orElse(DEFAULT_E_STRUCTURAL_FEATURE_FUNCTION);
try {
objectNode = function.apply(element, project, eStructuralFeature, objectNode);
} catch (RuntimeException e) {
e.printStackTrace();
System.err.println(element);
}
if (objectNode == null) {
return null;
}
}
return objectNode;
}
public static String getEID(EObject eObject) {
if (eObject == null) {
return null;
}
if (!(eObject instanceof Element)) {
return EcoreUtil.getID(eObject);
}
Element element = (Element) eObject;
Project project = Project.getProject(element);
// custom handling of elements with non-fixed ids in local projects
if (element instanceof Model && project.getPrimaryModel() == element) {
return Converters.getIProjectToIdConverter().apply(project.getPrimaryProject()) + MDKConstants.PRIMARY_MODEL_ID_SUFFIX;
}
if (element instanceof InstanceSpecification && ((InstanceSpecification) element).getStereotypedElement() != null) {
return getEID(((InstanceSpecification) element).getStereotypedElement()) + MDKConstants.APPLIED_STEREOTYPE_INSTANCE_ID_SUFFIX;
}
/*if (eObject instanceof TimeExpression && ((TimeExpression) eObject).get_timeEventOfWhen() != null) {
return getEID(((TimeExpression) eObject).get_timeEventOfWhen()) + MDKConstants.TIME_EXPRESSION_ID_SUFFIX;
}*/
if (element instanceof ValueSpecification && ((ValueSpecification) element).getOwningSlot() != null) {
ValueSpecification slotValue = (ValueSpecification) element;
return getEID(slotValue.getOwningSlot()) + MDKConstants.SLOT_VALUE_ID_SEPARATOR + slotValue.getOwningSlot().getValue().indexOf(slotValue) + "-" + slotValue.eClass().getName().toLowerCase();
}
if (element instanceof Slot) {
Slot slot = (Slot) element;
if (slot.getOwningInstance() != null && ((Slot) element).getDefiningFeature() != null) {
return getEID(slot.getOwner()) + MDKConstants.SLOT_ID_SEPARATOR + getEID(slot.getDefiningFeature());
}
}
// // eObject is in local primary model OR TWC online copy of a local mount
// // NOTE: assumes that project.getLocationURI().isFile() === !project.isRemote()
// IProject iProject = ProjectUtilities.getAttachedProject(element);
// if ((iProject == null && !project.isRemote())
// || (iProject != null && iProject.getLocationURI().isFile())) {
// return element.getLocalID();
// }
return element.getLocalID();
}
private static void dumpUMLPackageLiterals() {
for (Field field : UMLPackage.Literals.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) {
try {
field.setAccessible(true);
Object o = field.get(null);
System.out.println(field.getName() + ": " + o);
if (o instanceof EReference) {
EReference eReference = (EReference) o;
System.out.println(" --- " + eReference.getEReferenceType() + " : " + eReference.getEReferenceType().getInstanceClass());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
private enum PreProcessor {
APPLIED_STEREOTYPE(
(element, project, objectNode) -> {
if (element instanceof Model && !element.equals(project.getPrimaryModel())) {
return objectNode;
}
ArrayNode applied = StereotypesHelper.getStereotypes(element).stream().map(stereotype -> TextNode.valueOf(getEID(stereotype))).collect(MDKCollectors.toArrayNode());
objectNode.set("_appliedStereotypeIds", applied);
return objectNode;
}
),
ATTACHED_PROJECT(
(element, project, objectNode) -> ProjectUtilities.isElementInAttachedProject(element) && !(element instanceof Model) ? null : objectNode
),
COMMENT(
(element, project, objectNode) -> {
if (!(element instanceof Comment)) {
return objectNode;
}
Comment comment = (Comment) element;
return comment.getAnnotatedElement().size() == 1 && comment.getAnnotatedElement().iterator().next() == comment.getOwner() ? null : objectNode;
}
),
/*CONNECTOR_END(
(element, project, objectNode) -> element instanceof ConnectorEnd ? null : objectNode
),
DIAGRAM(
(element, project, objectNode) -> element instanceof Diagram ? null : objectNode
),*/
DIAGRAM_TYPE(
(element, project, objectNode) -> {
if (element instanceof Diagram) {
objectNode.put(MDKConstants.DIAGRAM_TYPE_KEY, ((Diagram) element).get_representation() != null ? ((Diagram) element).get_representation().getType() : null);
}
return objectNode;
}
),
DOCUMENTATION(
(element, project, objectNode) -> {
if (element instanceof Model && !element.equals(project.getPrimaryModel())) {
return objectNode;
}
objectNode.put("documentation", ModelHelper.getComment(element));
return objectNode;
}
),
MOUNT(
(element, project, objectNode) -> {
if (!(element instanceof Model)
|| element.equals(project.getPrimaryModel())
|| objectNode.has(MDKConstants.MOUNTED_ELEMENT_ID_KEY)) {
return objectNode;
}
Model model = (Model) element;
IAttachedProject attachedProject = ProjectUtilities.getAttachedProjects(project.getPrimaryProject()).stream().filter(ap -> ProjectUtilities.isAttachedProjectRoot(model, ap)).findAny().orElse(null);
if (attachedProject == null) {
return null;
}
boolean isRemote = ProjectUtilities.isRemote(attachedProject) && !attachedProject.getLocationURI().isFile();
objectNode.put(MDKConstants.MOUNTED_ELEMENT_ID_KEY, Converters.getElementToIdConverter().apply(project.getPrimaryModel()));
objectNode.put(MDKConstants.MOUNTED_ELEMENT_PROJECT_ID_KEY, Converters.getIProjectToIdConverter().apply(attachedProject.getPrimaryProject()));
//objectNode.put(MDKConstants.NAME_KEY, EsiUtils.getCurrentBranch(attachedProject).getName());
String branchName;
EsiUtils.EsiBranchInfo esiBranchInfo = null;
if (isRemote && (esiBranchInfo = EsiUtils.getCurrentBranch(attachedProject)) == null) {
return null;
}
if (!isRemote || (branchName = esiBranchInfo.getName()) == null || branchName.equals("trunk")) {
branchName = "master";
}
objectNode.put(MDKConstants.REF_ID_KEY, branchName);
objectNode.put(MDKConstants.TWC_VERSION_KEY, isRemote ? ProjectUtilities.versionToInt(ProjectUtilities.getVersion(attachedProject).getName()) : -1);
objectNode.put(MDKConstants.URI_KEY, attachedProject.getProjectDescriptor().getLocationUri().toString());
return objectNode;
}
),
SITE_CHARACTERIZATION(
(element, project, objectNode) -> {
if (element instanceof Model) {
return objectNode;
}
if (element instanceof Package) {
objectNode.put("_isSite", Utils.isSiteChar(project, (Package) element));
}
return objectNode;
}
),
SYNC(
(element, project, objectNode) -> element == null || Converters.getElementToIdConverter().apply(element).endsWith(MDKConstants.SYNC_SYSML_ID_SUFFIX) ||
element.getOwner() != null && Converters.getElementToIdConverter().apply(element.getOwner()).endsWith(MDKConstants.SYNC_SYSML_ID_SUFFIX) ? null : objectNode
),
// TWC_ID is disabled indefinitely, due to our inability to update the ID and associated issues
// TWC_ID(
// (element, project, objectNode) -> {
// if (project.isRemote()) {
// objectNode.put(MDKConstants.TWC_ID_KEY, element.getID());
// }
// return objectNode;
// }
// ),
TYPE(
(element, project, objectNode) -> {
String type = element instanceof Model && !element.equals(project.getPrimaryModel()) ? "Mount" : element.eClass().getName();
objectNode.put(MDKConstants.TYPE_KEY, type);
return objectNode;
}
),
VALUE_SPECIFICATION(
(element, project, objectNode) -> {
Element e = element;
do {
if (e instanceof ValueSpecification) {
return null;
}
} while ((e = e.getOwner()) != null);
return objectNode;
}
),
VIEW(
(element, project, objectNode) -> {
Stereotype viewStereotype = Utils.getViewStereotype(project);
if (viewStereotype == null || !StereotypesHelper.hasStereotypeOrDerived(element, viewStereotype)) {
return objectNode;
}
Constraint viewConstraint = Utils.getViewConstraint(element);
if (viewConstraint == null) {
return objectNode;
}
objectNode.set(MDKConstants.CONTENTS_KEY, DEFAULT_SERIALIZATION_FUNCTION.apply(viewConstraint.getSpecification(), project, null));
return objectNode;
}
);
private TriFunction<Element, Project, ObjectNode, ObjectNode> function;
PreProcessor(TriFunction<Element, Project, ObjectNode, ObjectNode> function) {
this.function = function;
}
public TriFunction<Element, Project, ObjectNode, ObjectNode> getFunction() {
return function;
}
}
private static final SerializationFunction DEFAULT_SERIALIZATION_FUNCTION = (object, project, eStructuralFeature) -> {
if (object == null) {
return NullNode.getInstance();
}
else if (object instanceof Collection) {
ArrayNode arrayNode = JacksonUtils.getObjectMapper().createArrayNode();
try {
for (Object o : ((Collection<?>) object)) {
JsonNode serialized = EMFExporter.DEFAULT_SERIALIZATION_FUNCTION.apply(o, project, eStructuralFeature);
if (serialized == null && o != null) {
// failed to serialize; taking the conservative approach and returning entire thing as null
return NullNode.getInstance();
}
arrayNode.add(serialized);
}
} catch (UnsupportedOperationException e) {
e.printStackTrace();
System.err.println("Object: " + object.getClass());
}
return arrayNode;
}
else if (object instanceof ValueSpecification) {
return convert((ValueSpecification) object, project, true);
//return fillValueSpecification((ValueSpecification) object);
}
else if (eStructuralFeature instanceof EReference && object instanceof EObject) {
return EMFExporter.DEFAULT_SERIALIZATION_FUNCTION.apply(getEID(((EObject) object)), project, eStructuralFeature);
}
else if (object instanceof String) {
return TextNode.valueOf((String) object);
}
else if (object instanceof Boolean) {
return BooleanNode.valueOf((boolean) object);
}
else if (object instanceof Integer) {
return IntNode.valueOf((Integer) object);
}
else if (object instanceof Double) {
return DoubleNode.valueOf((Double) object);
}
else if (object instanceof Long) {
return LongNode.valueOf((Long) object);
}
else if (object instanceof Short) {
return ShortNode.valueOf((Short) object);
}
else if (object instanceof Float) {
return FloatNode.valueOf((Float) object);
}
else if (object instanceof BigInteger) {
return BigIntegerNode.valueOf((BigInteger) object);
}
else if (object instanceof BigDecimal) {
return DecimalNode.valueOf((BigDecimal) object);
}
else if (object instanceof byte[]) {
return BinaryNode.valueOf((byte[]) object);
}
else if (eStructuralFeature.getEType() instanceof EDataType) {
return TextNode.valueOf(EcoreUtil.convertToString((EDataType) eStructuralFeature.getEType(), object));
//return ((Enumerator) object).getLiteral();
}
// if we get here we have no idea what to do with this object
return NullNode.getInstance();
};
private static final ExportFunction DEFAULT_E_STRUCTURAL_FEATURE_FUNCTION = (element, project, eStructuralFeature, objectNode) -> {
if (!eStructuralFeature.isChangeable() || eStructuralFeature.isVolatile() || eStructuralFeature.isTransient() || eStructuralFeature.isUnsettable() || eStructuralFeature.isDerived() || eStructuralFeature.getName().startsWith("_")) {
return EMFExporter.EMPTY_E_STRUCTURAL_FEATURE_FUNCTION.apply(element, project, eStructuralFeature, objectNode);
}
return EMFExporter.UNCHECKED_E_STRUCTURAL_FEATURE_FUNCTION.apply(element, project, eStructuralFeature, objectNode);
};
private static final ExportFunction UNCHECKED_E_STRUCTURAL_FEATURE_FUNCTION = (element, project, eStructuralFeature, objectNode) -> {
Object value = element.eGet(eStructuralFeature);
JsonNode serializedValue = DEFAULT_SERIALIZATION_FUNCTION.apply(value, project, eStructuralFeature);
if (value != null && serializedValue == null) {
System.err.println("[EMF] Failed to serialize " + eStructuralFeature + " for " + element + ": " + value + " - " + value.getClass());
return objectNode;
}
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" : "");
}
objectNode.put(key, serializedValue);
return objectNode;
};
private static final ExportFunction EMPTY_E_STRUCTURAL_FEATURE_FUNCTION = (element, project, eStructuralFeature, objectNode) -> objectNode;
private enum EStructuralFeatureOverride {
ID(
(element, eStructuralFeature) -> eStructuralFeature == element.eClass().getEIDAttribute(),
(element, project, eStructuralFeature, objectNode) -> {
/*if (element instanceof ValueSpecification && !(element instanceof TimeExpression)) {
return objectNode;
}*/
objectNode.put(MDKConstants.ID_KEY, getEID(element));
return objectNode;
}
),
OWNER(
(element, eStructuralFeature) -> UMLPackage.Literals.ELEMENT__OWNER == eStructuralFeature,
(element, project, eStructuralFeature, objectNode) -> {
Element owner = element.getOwner();
/*if (element instanceof ValueSpecification || owner instanceof ValueSpecification) {
return objectNode;
}*/
//UNCHECKED_E_STRUCTURAL_FEATURE_FUNCTION.apply(element, project, UMLPackage.Literals.ELEMENT__OWNER, objectNode);
// safest way to prevent circular references, like with ValueSpecifications
objectNode.put(MDKConstants.OWNER_ID_KEY, element instanceof Model ? Converters.getIProjectToIdConverter().apply(project.getPrimaryProject()) : getEID(owner));
return objectNode;
}
),
OWNING(
(element, eStructuralFeature) -> eStructuralFeature.getName().startsWith("owning"),
EMPTY_E_STRUCTURAL_FEATURE_FUNCTION
),
OWNED(
(element, eStructuralFeature) -> eStructuralFeature.getName().startsWith("owned") && !eStructuralFeature.isOrdered(),
EMPTY_E_STRUCTURAL_FEATURE_FUNCTION
),
NESTED(
(element, eStructuralFeature) -> eStructuralFeature.getName().startsWith("nested"),
EMPTY_E_STRUCTURAL_FEATURE_FUNCTION
),
PACKAGED_ELEMENT(
(element, eStructuralFeature) -> UMLPackage.Literals.PACKAGE__PACKAGED_ELEMENT == eStructuralFeature || UMLPackage.Literals.COMPONENT__PACKAGED_ELEMENT == eStructuralFeature,
EMPTY_E_STRUCTURAL_FEATURE_FUNCTION
),
DIRECTED_RELATIONSHIP__SOURCE(
(element, eStructuralFeature) -> UMLPackage.Literals.DIRECTED_RELATIONSHIP__SOURCE == eStructuralFeature,
(element, project, eStructuralFeature, objectNode) -> {
objectNode.set(MDKConstants.DERIVED_KEY_PREFIX + eStructuralFeature.getName() + MDKConstants.IDS_KEY_SUFFIX, DEFAULT_SERIALIZATION_FUNCTION.apply(element.eGet(eStructuralFeature), project, eStructuralFeature));
return objectNode;
}
),
DIRECTED_RELATIONSHIP__TARGET(
(element, eStructuralFeature) -> UMLPackage.Literals.DIRECTED_RELATIONSHIP__TARGET == eStructuralFeature,
(element, project, eStructuralFeature, objectNode) -> {
objectNode.set(MDKConstants.DERIVED_KEY_PREFIX + eStructuralFeature.getName() + MDKConstants.IDS_KEY_SUFFIX, DEFAULT_SERIALIZATION_FUNCTION.apply(element.eGet(eStructuralFeature), project, eStructuralFeature));
return objectNode;
}
),
CONNECTOR__END(
(element, eStructuralFeature) -> eStructuralFeature == UMLPackage.Literals.CONNECTOR__END,
(element, project, eStructuralFeature, objectNode) -> {
Connector connector = (Connector) element;
// TODO Stop using Strings @donbot
List<List<Object>> propertyPaths = connector.getEnd().stream()
.map(connectorEnd -> StereotypesHelper.hasStereotype(connectorEnd, "NestedConnectorEnd") ? StereotypesHelper.getStereotypePropertyValue(connectorEnd, "NestedConnectorEnd", "propertyPath") : null)
.map(elements -> {
if (elements == null) {
return new ArrayList<>(1);
}
List<Object> list = new ArrayList<>(elements.size() + 1);
for (Object o : elements) {
list.add(o instanceof ElementValue ? ((ElementValue) o).getElement() : o);
}
return list;
}).collect(Collectors.toList());
for (int i = 0; i < propertyPaths.size(); i++) {
propertyPaths.get(i).add(connector.getEnd().get(i).getRole());
}
objectNode.set("_propertyPathIds", DEFAULT_SERIALIZATION_FUNCTION.apply(propertyPaths, project, eStructuralFeature));
return DEFAULT_E_STRUCTURAL_FEATURE_FUNCTION.apply(element, project, eStructuralFeature, objectNode);
}
),
VALUE_SPECIFICATION__EXPRESSION(
(element, eStructuralFeature) -> eStructuralFeature == UMLPackage.Literals.VALUE_SPECIFICATION__EXPRESSION,
/*(element, project, eStructuralFeature, objectNode) -> {
Expression expression = null;
Object object = element.eGet(UMLPackage.Literals.VALUE_SPECIFICATION__EXPRESSION);
if (object instanceof Expression) {
expression = (Expression) object;
}
objectNode.put(UMLPackage.Literals.VALUE_SPECIFICATION__EXPRESSION.getName() + MDKConstants.ID_KEY_SUFFIX, expression != null ? expression.getID() : null);
return objectNode;
}*/
EMPTY_E_STRUCTURAL_FEATURE_FUNCTION
),
UML_CLASS(
(element, eStructuralFeature) -> eStructuralFeature == UMLPackage.Literals.CLASSIFIER__UML_CLASS || eStructuralFeature == UMLPackage.Literals.PROPERTY__UML_CLASS || eStructuralFeature == UMLPackage.Literals.OPERATION__UML_CLASS,
EMPTY_E_STRUCTURAL_FEATURE_FUNCTION
),
MOUNT(
(element, eStructuralFeature) -> element instanceof Model,
(element, project, eStructuralFeature, objectNode) -> (objectNode.get(MDKConstants.TYPE_KEY).asText().equals("Mount") ? EMPTY_E_STRUCTURAL_FEATURE_FUNCTION : DEFAULT_E_STRUCTURAL_FEATURE_FUNCTION).apply(element, project, eStructuralFeature, objectNode)
);
private BiPredicate<Element, EStructuralFeature> predicate;
private ExportFunction function;
EStructuralFeatureOverride(BiPredicate<Element, EStructuralFeature> predicate, ExportFunction function) {
this.predicate = predicate;
this.function = function;
}
public BiPredicate<Element, EStructuralFeature> getPredicate() {
return predicate;
}
public ExportFunction getFunction() {
return function;
}
}
@FunctionalInterface
interface SerializationFunction {
JsonNode apply(Object object, Project project, EStructuralFeature eStructuralFeature);
}
@FunctionalInterface
interface ExportFunction {
ObjectNode apply(Element element, Project project, EStructuralFeature eStructuralFeature, ObjectNode objectNode);
}
}