package util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EcoreFactory;
/**
* Specific helper that is used from templates that need
* database stuff.
*
* @author patrik.nordwall
*/
public class DatabaseGenerationHelper extends EcoreGenerationHelper {
private static final String ID_COLUMN_NAME = "id";
private Map<String, String> databaseTypeNameMap = new HashMap<String, String>();
public DatabaseGenerationHelper() {
initDatabaseTypeMap();
}
public List<EClass> getClassesInCreateOrder(EPackage ePackage) {
List<EClass> packageClasses = getClasses(ePackage);
List<EClass> databaseClasses = new ArrayList<EClass>();
for (EClass eClass : packageClasses) {
addClassRecursive(eClass, databaseClasses);
}
return databaseClasses;
}
private void addClassRecursive(EClass eClass, List<EClass> databaseClasses) {
// skip repository classes
if (eClass.getName().endsWith("Repository")) {
return;
}
if (databaseClasses.contains(eClass)) {
// already added
return;
}
// we must have the referenced super class first, due to foreign key
if (!getExtends(eClass).isEmpty()) {
EClass superClass = getExtends(eClass).get(0);
addClassRecursive(superClass, databaseClasses);
}
// we must have the referenced classes first, due to foreign keys
for (EReference ref : getAllOneReferences(eClass)) {
EClass refClass = ref.getEReferenceType();
if (!databaseClasses.contains(refClass)) {
addClassRecursive(refClass, databaseClasses);
}
}
if (!databaseClasses.contains(eClass)) {
databaseClasses.add(eClass);
}
}
private void initDatabaseTypeMap() {
databaseTypeNameMap.put("EBoolean", "CHAR(1)");
databaseTypeNameMap.put("EInt", "INTEGER");
databaseTypeNameMap.put("EDate", "DATE");
databaseTypeNameMap.put("EBigDecimal", "DOUBLE");
// length of VARCHAR can be specified with 'database_type' annotation
databaseTypeNameMap.put("EString", "VARCHAR(100)");
// TODO more
}
public String getDatabaseType(EAttribute eAttribute) {
return getDatabaseType(eAttribute, false);
}
public String getDatabaseType(EAttribute eAttribute, boolean ignorePrimaryKey) {
String databaseTypeAnnotation = getAnnotation(eAttribute, "database_type");
String databaseType;
if (databaseTypeAnnotation == null) {
String typeName = eAttribute.getEAttributeType().getName();
databaseType = databaseTypeNameMap.get(typeName);
} else {
databaseType = databaseTypeAnnotation;
}
String nullableAnnotation = getAnnotation(eAttribute, "nullable");
if (nullableAnnotation == null || nullableAnnotation.equalsIgnoreCase("false")) {
databaseType += " NOT NULL";
}
if (!ignorePrimaryKey) {
if (eAttribute.getName().equalsIgnoreCase(ID_COLUMN_NAME)) {
if (databaseType.startsWith("INTEGER")) {
databaseType += " AUTO_INCREMENT";
}
databaseType += " PRIMARY KEY";
}
}
return databaseType;
}
public String getDatabaseName(ENamedElement element) {
String name = element.getName();
// TODO here we could replace camel notation with underscore
return name.toUpperCase();
}
public String getForeignKeyName(EReference ref) {
EClass referencedClass = ref.getEReferenceType();
return getForeignKeyName(referencedClass);
}
public String getForeignKeyName(EClass referencedClass) {
if (getIdAttribute(referencedClass) == null) {
throw new IllegalArgumentException("Referenced class " + referencedClass.getName() +
" doesn't contain 'id' attribute");
}
String name = referencedClass.getName() + "_" + ID_COLUMN_NAME;
return name.toUpperCase();
}
public EAttribute getIdAttribute(EClass eClass) {
return getAttributeWithName(ID_COLUMN_NAME, eClass);
}
private EAttribute getAttributeWithName(String name, EClass eClass) {
for (EAttribute attribute : getAttributes(eClass)) {
if (attribute.getName().equals(name)) {
return attribute;
}
}
// not found
return null;
}
public String getForeignKeyType(EReference ref) {
EClass referencedClass = ref.getEReferenceType();
return getForeignKeyType(referencedClass);
}
public String getForeignKeyType(EClass referencedClass) {
EAttribute idAttribute = getAttributeWithName(ID_COLUMN_NAME, referencedClass);
checkIdAttribute(referencedClass, idAttribute);
String type = getDatabaseType(idAttribute, true);
return type;
}
public String getForeignKeyConstraint(EReference ref) {
EClass referencedClass = ref.getEReferenceType();
return getForeignKeyConstraint(referencedClass);
}
public String getForeignKeyConstraint(EClass referencedClass) {
String constraint = "CONSTRAINT FOREIGN KEY (" +
getForeignKeyName(referencedClass) + ") REFERENCES " +
getDatabaseName(referencedClass) + "(ID)";
return constraint;
}
private void checkIdAttribute(EClass referencedClass, EAttribute idAttribute) {
if (idAttribute == null) {
throw new IllegalArgumentException("Referenced class " + referencedClass.getName() +
" doesn't contain 'id' attribute");
}
}
public List<EClass> resolveManyToManyRelations(EPackage ePackage) {
// first, find all many-to-many references
Set<EReference> manyToManyReferences = new HashSet<EReference>();
for (EClass eClass : getClasses(ePackage)) {
for (EReference ref : getAllManyReferences(eClass)) {
EReference opposite = ref.getEOpposite();
if (isManyToMany(ref) && !manyToManyReferences.contains(opposite)) {
manyToManyReferences.add(ref);
}
}
}
// then, create an EClass for each many-to-many reference
List<EClass> manyToManyClasses = new ArrayList<EClass>();
for (EReference ref : manyToManyReferences) {
EReference opposite = ref.getEOpposite();
EcoreFactory ecoreFactory = EcoreFactory.eINSTANCE;
EClass eClass = ecoreFactory.createEClass();
String name = getManyToManyJoinTableName(ref);
eClass.setName(name.toUpperCase());
EReference ref1 = ecoreFactory.createEReference();
ref1.setEType(ref.getEReferenceType());
ref1.setUpperBound(1);
eClass.getEStructuralFeatures().add(ref1);
EReference ref2 = ecoreFactory.createEReference();
ref2.setEType(opposite.getEReferenceType());
ref2.setUpperBound(1);
eClass.getEStructuralFeatures().add(ref2);
manyToManyClasses.add(eClass);
}
return manyToManyClasses;
}
public String getManyToManyJoinTableName(EReference ref) {
EReference opposite = ref.getEOpposite();
String name1 = getDatabaseName(ref.getEReferenceType());
String name2 = getDatabaseName(opposite.getEReferenceType());
// order them in some well defined way, like alphabetic order
if (name1.compareTo(name2) < 0) {
return name1 + "_" + name2;
} else {
return name2 + "_" + name1;
}
}
/**
* Inverse attribute for many-to-many associations.
*/
public boolean isInverse(EReference ref) {
return getManyToManyJoinTableName(ref).startsWith(
getDatabaseName(ref.getEReferenceType()) + "_");
}
public boolean isManyToMany(EReference ref) {
EReference opposite = ref.getEOpposite();
return ((opposite != null) && isManyReference(opposite));
}
}
|