package org.milyn.ect.ecore;
import static org.milyn.ect.ecore.SmooksMetadata.ANNOTATION_TYPE_KEY;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.util.ExtendedMetaData;
import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
import org.milyn.edisax.model.internal.Component;
import org.milyn.edisax.model.internal.Description;
import org.milyn.edisax.model.internal.Edimap;
import org.milyn.edisax.model.internal.Field;
import org.milyn.edisax.model.internal.MappingNode;
import org.milyn.edisax.model.internal.Segment;
import org.milyn.edisax.model.internal.SegmentGroup;
import org.milyn.edisax.model.internal.ValueNode;
/**
* Utility class that convert EDI model to ECore model elements
*
* @author zubairov
*
*/
public class ECoreConversionUtils {
/**
* Supported data types for conversion
*
*/
private static final EDataType ETYPES[] = { XMLTypePackage.Literals.STRING,
XMLTypePackage.Literals.LONG, XMLTypePackage.Literals.DECIMAL,
XMLTypePackage.Literals.FLOAT };
private static ExtendedMetaData metadata = ExtendedMetaData.INSTANCE;
private static final Log log = LogFactory.getLog(ECoreConversionUtils.class);
/**
* Converting {@link Segment} to {@link EClass}
*
* @param segment
* @return
*/
public static EClass segmentToEClass(Segment segment) {
EClass clazz = segmentGroupToEClass(segment);
annotate(clazz, SmooksMetadata.SEGCODE, segment.getSegcode());
annotate(clazz, "segcodePattern", segment.getSegcodePattern()
.toString());
annotate(clazz, "truncable", String.valueOf(segment.isTruncatable()));
annotate(clazz, "ignoreUnmappedFields",
String.valueOf(segment.isIgnoreUnmappedFields()));
annotate(clazz, "description", segment.getDescription());
annotate(clazz, SmooksMetadata.ANNOTATION_TYPE_KEY,
SmooksMetadata.SEGMENT_TYPE);
return clazz;
}
/**
* This method transforms {@link Edimap} to {@link EPackage} where classes
* related to this {@link Edimap} will be stored
*
* @param mapModel
* @return
*/
public static EPackage mappingModelToEPackage(Edimap mapModel) {
final EPackage pkg = EcoreFactory.eINSTANCE.createEPackage();
Description desc = mapModel.getDescription();
pkg.setName(desc.getName().toLowerCase());
pkg.setNsPrefix(desc.getName().toLowerCase());
pkg.setNsURI(desc.getNamespace());
if (mapModel.getSrc() != null) {
annotate(pkg, "src", mapModel.getSrc().toASCIIString());
}
annotate(pkg, "description.name", mapModel.getDescription().getName());
annotate(pkg, "description.version", mapModel.getDescription()
.getVersion());
annotate(pkg, "delimeters.segment", mapModel.getDelimiters()
.getSegment());
annotate(pkg, "delimeters.component", mapModel.getDelimiters()
.getComponent());
annotate(pkg, "delimeters.field", mapModel.getDelimiters().getField());
annotate(pkg, "delimeters.fieldRepeat", mapModel.getDelimiters()
.getFieldRepeat());
annotate(pkg, "delimeters.escape", mapModel.getDelimiters().getEscape());
annotate(pkg, "delimeters.ignoreCLRF",
String.valueOf(mapModel.getDelimiters().ignoreCRLF()));
return pkg;
}
/**
* Converts {@link Segment} to {@link EReference}
*
* @param segment
* @param refClass
* @return
*/
public static EReference segmentToEReference(Segment segment,
EClass refClass) {
EReference reference = segmentGroupToEReference(segment, refClass);
annotate(reference, SmooksMetadata.ANNOTATION_TYPE_KEY,
SmooksMetadata.SEGMENT_TYPE);
annotate(reference, SmooksMetadata.SEGCODE, segment.getSegcode());
String name = segment.getSegcode();
char lastChar = reference.getName().charAt(reference.getName().length() - 1);
if (Character.isDigit(lastChar)) {
name = name + lastChar;
}
reference.setName(name);
return reference;
}
/**
* Converting {@link SegmentGroup} to {@link EClass}
*
* @param grp
* @return
*/
public static EClass segmentGroupToEClass(SegmentGroup grp) {
EClass clazz = EcoreFactory.eINSTANCE.createEClass();
clazz.setName(toJavaName(grp.getXmltag(), true));
addMappingInformation(clazz, grp);
return clazz;
}
private static void addMappingInformation(EClass clazz, MappingNode node) {
if (node.getDocumentation() != null) {
annotate(clazz, "documentation", node.getDocumentation());
}
metadata.setName(clazz, clazz.getName());
metadata.setContentKind(clazz, ExtendedMetaData.ELEMENT_ONLY_CONTENT);
}
private static void addMappingInformation(EStructuralFeature ref,
MappingNode node) {
metadata.setName(ref, node.getXmltag());
metadata.setFeatureKind(ref, ExtendedMetaData.ELEMENT_FEATURE);
setTargetNamespace(ref);
}
/**
* Annotate given {@link EModelElement} with smooks anntation with given key
* and value
*
* @param element
* @param key
* @param value
*/
private static void annotate(EModelElement element, String key, String value) {
if (!StringUtils.isEmpty(value)) {
EAnnotation annotation = element
.getEAnnotation(SmooksMetadata.ANNOTATION_TYPE);
if (annotation == null) {
annotation = EcoreFactory.eINSTANCE.createEAnnotation();
annotation.setSource(SmooksMetadata.ANNOTATION_TYPE);
element.getEAnnotations().add(annotation);
}
annotation.getDetails().put(key, value);
}
}
/**
* Convert {@link SegmentGroup} into {@link EReference} to the given
* {@link EClass}
*
* @param grp
* @param refClass
* @return
*/
public static EReference segmentGroupToEReference(SegmentGroup grp,
EClass refClass) {
EReference reference = EcoreFactory.eINSTANCE.createEReference();
reference.setContainment(true);
reference.setName(toJavaName(grp.getXmltag(), false));
reference.setEType(refClass);
reference.setLowerBound(grp.getMinOccurs());
reference.setUpperBound(grp.getMaxOccurs());
addMappingInformation(reference, grp);
annotate(reference, "minOccurs", String.valueOf(grp.getMinOccurs()));
annotate(reference, "maxOccurs", String.valueOf(grp.getMaxOccurs()));
annotate(reference, SmooksMetadata.ANNOTATION_TYPE_KEY,
SmooksMetadata.SEGMENT_GROUP_TYPE);
annotate(reference, SmooksMetadata.SEGCODE, grp.getSegcode());
return reference;
}
/**
* Converting a {@link Field} to {@link EAttribute} Works only for
* {@link Field} where {@link Field#getComponents()} is empty
*
* @param field
* @return
*/
public static EAttribute fieldToEAttribute(Field field) {
if (!field.getComponents().isEmpty()) {
throw new IllegalArgumentException(
"Can't convert field with components to "
+ "EAttribute, use fieldToEReference");
}
EAttribute attr = EcoreFactory.eINSTANCE.createEAttribute();
attr.setName(toJavaName(field.getXmltag(), false));
attr.setLowerBound(field.isRequired() ? 1 : 0);
attr.setUpperBound(1);
if (field.getTypeClass() != null) {
attr.setEType(toEType(field.getTypeClass()));
} else {
log.warn("Field " + field.getXmltag()
+ " has no type! Setting it's type to String");
attr.setEType(XMLTypePackage.Literals.STRING);
}
addMappingInformation(attr, field);
annotateField(field, attr);
return attr;
}
/**
* Add field specific annotations
*
* @param field
* @param attr
*/
private static void annotateField(Field field, EModelElement attr) {
annotate(attr, "truncable", String.valueOf(field.isTruncatable()));
annotate(attr, "required", String.valueOf(field.isRequired()));
annotate(attr, SmooksMetadata.ANNOTATION_TYPE_KEY,
SmooksMetadata.FIELD_TYPE);
annotateValueNode(attr, field);
}
/**
* This method creates a new {@link EReference} to the {@link Field} that
* contains multiple {@link Component}.
*
* For that purpose new {@link EClass} will be created and
* {@link EReference} will refer to it
*
* @param field
* @param classes
* @return
*/
public static EReference fieldToEReference(Field field,
Map<String, EClass> classes) {
EClass newClass = fieldToEClass(field);
if (!classes.containsKey(newClass.getName())) {
classes.put(newClass.getName(), newClass);
} else {
newClass = classes.get(newClass.getName());
}
for (Component component : field.getComponents()) {
EStructuralFeature attribute = componentToEAttribute(component);
if (newClass.getEStructuralFeature(attribute.getName()) == null) {
newClass.getEStructuralFeatures().add(attribute);
}
}
EReference result = EcoreFactory.eINSTANCE.createEReference();
result.setContainment(true);
result.setName(toJavaName(field.getXmltag(), false));
result.setLowerBound(field.isRequired() ? 1 : 0);
result.setUpperBound(1);
result.setEType(newClass);
annotateField(field, result);
addMappingInformation(result, field);
return result;
}
/**
* Converts {@link Component} to {@link EAttribute}
*
* @param component
* @return
*/
private static EStructuralFeature componentToEAttribute(Component component) {
if (!component.getSubComponents().isEmpty()) {
throw new IllegalArgumentException(
"Sub-components are not supported yet for component "
+ component.getXmltag());
}
EAttribute result = EcoreFactory.eINSTANCE.createEAttribute();
result.setName(toJavaName(component.getXmltag(), false));
result.setLowerBound(component.isRequired() ? 1 : 0);
result.setUpperBound(1);
result.setEType(toEType(component.getTypeClass()));
annotate(result, "truncable", String.valueOf(component.isTruncatable()));
annotate(result, "required", String.valueOf(component.isRequired()));
annotate(result, ANNOTATION_TYPE_KEY, SmooksMetadata.COMPONENT_TYPE);
annotateValueNode(result, component);
addMappingInformation(result, component);
return result;
}
private static EClassifier toEType(Class<?> typeClass) {
if (typeClass == null) {
typeClass = String.class;
}
for (EDataType type : ETYPES) {
if (type.getInstanceClass() == typeClass) {
return type;
}
}
throw new IllegalArgumentException("Type for type class " + typeClass
+ " is not supported");
}
/**
* Creating a new {@link EClass} based on the information from {@link Field}
* used in case we have a complex {@link Field} and we need to create a
* class for it.
*
* @param field
* @return
*/
private static EClass fieldToEClass(Field field) {
String classifierName = toJavaName(field.getXmltag(), true);
if (field.getNodeTypeRef() != null) {
classifierName += "_" + field.getNodeTypeRef();
}
EClass newClass = EcoreFactory.eINSTANCE.createEClass();
newClass.setName(classifierName);
addMappingInformation(newClass, field);
annotate(newClass, ANNOTATION_TYPE_KEY, SmooksMetadata.FIELD_TYPE);
annotateValueNode(newClass, field);
return newClass;
}
private static void annotateValueNode(EModelElement element,
ValueNode valueNode) {
annotate(element, "datatype", valueNode.getDataType());
annotate(element, "maxLength", String.valueOf(valueNode.getMaxLength()));
annotate(element, "minLength", String.valueOf(valueNode.getMinLength()));
if (valueNode.getDecoder() != null) {
annotate(element, "decoder", valueNode.getDecoder().getClass()
.getCanonicalName());
} else {
annotate(element, "decoder", "");
}
}
/**
* Convert tricky names to JavaNames with CamelCase etc
*
* @param name
* @return
*/
public static String toJavaName(String name, boolean className) {
name = name.replaceAll("__", "_");
StringBuilder result = new StringBuilder();
boolean cap = className;
for (int i = 0; i < name.length(); i++) {
char ch = name.charAt(i);
if ('_' == ch || '.' == ch) {
cap = true;
} else {
if (cap) {
result.append(Character.toUpperCase(ch));
cap = false;
} else {
result.append(Character.toLowerCase(ch));
}
}
}
return result.toString();
}
/**
* Creates a droot root class
*
* @param rootClass
* @return
*/
public static EClass createDocumentRoot(EClass rootClass) {
EClass clazz = EcoreFactory.eINSTANCE.createEClass();
clazz.setName("DocumentRoot");
metadata.setDocumentRoot(clazz);
if (rootClass != null) {
EReference reference = EcoreFactory.eINSTANCE.createEReference();
clazz.getEStructuralFeatures().add(reference);
reference.setEType(rootClass);
reference.setName(metadata.getName(rootClass));
metadata.setFeatureKind(reference, ExtendedMetaData.ELEMENT_FEATURE);
setTargetNamespace(reference);
reference.setContainment(true);
}
return clazz;
}
private static void setTargetNamespace(EStructuralFeature element) {
EAnnotation eAnnotation = element
.getEAnnotation(ExtendedMetaData.ANNOTATION_URI);
eAnnotation.getDetails().put("namespace", "##targetNamespace");
}
}