/**
* <copyright>
*
* Copyright (c) 2009, 2010 Springsite BV (The Netherlands) and others
* 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:
* Martin Taal - Initial API and implementation
*
* </copyright>
*
* $Id: ORMNamingStrategy.java,v 1.9 2011/08/26 05:34:12 mtaal Exp $
*/
package org.eclipse.emf.texo.orm.annotator;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import org.eclipse.emf.common.util.URI;
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.EStructuralFeature;
import org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl;
import org.eclipse.emf.ecore.util.FeatureMapUtil;
import org.eclipse.emf.texo.generator.GeneratorUtils;
import org.eclipse.emf.texo.orm.ormannotations.EPackageORMAnnotation;
/**
* The default naming strategy for entity and database schema naming.
*
* @author <a href="mtaal@elver.org">Martin Taal</a>
*/
public class ORMNamingStrategy {
private static char[] removables = new char[] { 'u', 'o', 'a', 'e', 'i', 'U', 'O', 'A', 'E', 'I' };
private EPackageORMAnnotation ePackageORMAnnotation = null;
private Properties nameDictionary = null;
private Set<String> sqlReservedWords = null;
protected String renameReservedWord(String name) {
if (sqlReservedWords == null) {
try {
sqlReservedWords = new HashSet<String>();
final InputStream is = ORMNamingStrategy.class.getResourceAsStream("reserved-sql-words.txt");
final Properties props = new Properties();
props.load(is);
is.close();
for (Object key : props.keySet()) {
sqlReservedWords.add((String) key);
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
if (sqlReservedWords.contains(name.toUpperCase())) {
return getRenamePrefix() + name;
}
return name;
}
protected String getRenamePrefix() {
return "T_";
}
/**
* @return true if the orm generation should fill in table and column names
*/
public boolean isGenerateAllDBSchemaNames() {
return getePackageORMAnnotation().isGenerateFullDbSchemaNames();
}
/**
* Create a (unique) entity name for an eclass.
*
* @param eClass
* the eClass for which to generate a unique name
* @return as a default the eClass name is returned, possibly prefixed with the EPackage name space prefix
* @see EPackageORMAnnotation#isEnforceUniqueNames()
* @see EPackage#getNsPrefix()
*/
public String getEntityName(EClass eClass) {
return makeSafe(getUniqueMakingPrefix(eClass) + eClass.getName());
}
/**
* Computes the join column name for a subclass table to its parent table. As a default the name parent_id is used.
*
* @param eClass
* the EClass for which to generate the join column name.
* @return the name of the join column
* @see EPackageORMAnnotation#getColumnNamePrefix()
*/
public String getPrimaryKeyJoinColumn(EClass eClass) {
String localName = getDictionariedName(eClass, "parent"); //$NON-NLS-1$
if (localName == null) {
localName = getEntityName(eClass) + "_parent_id"; //$NON-NLS-1$
}
return processName(localName, ePackageORMAnnotation.getColumnNamePrefix());
}
/**
* Computes the table name for the table which stores this EClass.
*
* @param eClass
* @return as a default the entity name is returned.
* @see #getEntityName(EClass)
*/
public String getTableName(EClass eClass) {
String localName = getDictionariedName(eClass, null);
if (localName == null) {
localName = getEntityName(eClass);
}
return processName(localName, ePackageORMAnnotation.getTableNamePrefix());
}
/**
* Computes the name of the element column in a collection element table.
*
* @param eFeature
* the EStructuralFeature which has a collection table
* @return the name of the element column, as a default the term 'element' is returned.
* @see EPackageORMAnnotation#getColumnNamePrefix()
*/
public String getCollectionElementColumnName(EStructuralFeature eFeature) {
String localName = getDictionariedName(eFeature, "element"); //$NON-NLS-1$
if (localName == null) {
localName = "element"; //$NON-NLS-1$
}
return processName(localName, ePackageORMAnnotation.getColumnNamePrefix());
}
/**
* Returns the name of the index or order column used in join tables to model lists and maps.
*
* @param eFeature
* the EStructuralFeature which contains the name
* @return the name of the index/order column, possibly prefixed with the column name prefix
* @see EPackageORMAnnotation#getColumnNamePrefix()
*/
public String getIndexColumnName(EStructuralFeature eFeature) {
String localName = getDictionariedName(eFeature, "index"); //$NON-NLS-1$
if (localName == null) {
if (ePackageORMAnnotation.isEnforceUniqueNames()) {
localName = getEntityName(eFeature.getEContainingClass()) + "_" + eFeature.getName() + "_ind"; //$NON-NLS-1$
} else {
localName = eFeature.getName() + "_ind"; //$NON-NLS-1$
}
}
return processName(localName, ePackageORMAnnotation.getColumnNamePrefix());
}
/**
* Computes the name of the join table used to model the EStructuralFeature in the relational db.
*
* @param eFeature
* @return the concatenation of the entity name and the EStructuralFeature's name
* @see EPackageORMAnnotation#getTableNamePrefix()
*/
public String getJoinTableName(EStructuralFeature eFeature) {
String localName = getDictionariedName(eFeature, "joinTable"); //$NON-NLS-1$
if (localName == null) {
localName = getEntityName(eFeature.getEContainingClass()) + "_" + eFeature.getName(); //$NON-NLS-1$
}
// add a postfix to make it unique, as often a nameclash occurs with the
// featuremap entry table
if (FeatureMapUtil.isFeatureMap(eFeature)) {
localName += "_FM";
}
return processName(localName, ePackageORMAnnotation.getTableNamePrefix());
}
/**
* Used to compute the join column of a many-to-one association.
*
* @param eFeature
* the many-to-one association
* @return the column name of the foreign key column
* @see EPackageORMAnnotation#getColumnNamePrefix()
*/
public String getForeignKeyColumnName(EStructuralFeature eFeature) {
String localName = getDictionariedName(eFeature, "foreignKeyColumn"); //$NON-NLS-1$
if (localName == null) {
if (ePackageORMAnnotation.isEnforceUniqueNames()) {
localName = getEntityName(eFeature.getEContainingClass()) + "_" + eFeature.getName();
} else {
localName = eFeature.getName();
}
}
return processName(localName, ePackageORMAnnotation.getColumnNamePrefix());
}
/**
* Used to compute the join column name in a join table. The join column points to the owning entity of the
* association.
*
* @param eFeature
* the EStructuralFeature which models the one-to-many or many-to-many association
* @return the column name
* @see EPackageORMAnnotation#getColumnNamePrefix()
*/
public String getJoinColumnName(EStructuralFeature eFeature) {
String localName = getDictionariedName(eFeature, "joinColumn"); //$NON-NLS-1$
if (localName == null) {
if (ePackageORMAnnotation.isEnforceUniqueNames() || eFeature instanceof EReference
&& eFeature.getEType() == eFeature.getEContainingClass()) {
localName = getEntityName(eFeature.getEContainingClass()) + "_" + eFeature.getName();
} else {
localName = getEntityName(eFeature.getEContainingClass()) + "_id";
}
}
return processName(localName, ePackageORMAnnotation.getColumnNamePrefix());
}
/**
* Used to compute the inverse join column name in a join table. The inverse join column points to the non-owning
* entity of the association.
*
* @param eReference
* the EReference which models the one-to-many or many-to-many association
* @return the column name
* @see EPackageORMAnnotation#getColumnNamePrefix()
*/
public String getInverseJoinColumnName(EReference eReference) {
String localName = getDictionariedName(eReference, "inverseJoinColumn"); //$NON-NLS-1$
if (localName == null) {
// pointers to ourselves gives duplicate columns
if (ePackageORMAnnotation.isEnforceUniqueNames()
|| eReference.getEReferenceType() == eReference.getEContainingClass()) {
localName = eReference.getName() + "_" + getEntityName(eReference.getEReferenceType());
} else if (eReference.getEOpposite() != null) {
return getJoinColumnName(eReference.getEOpposite());
} else {
localName = getEntityName(eReference.getEReferenceType()) + "_id"; //$NON-NLS-1$
}
}
return processName(localName, ePackageORMAnnotation.getColumnNamePrefix());
}
/**
* @param eAttribute
* @return the entity name of the feature map mapping.
*/
public String getFeatureMapEntityName(EAttribute eAttribute) {
return getUniqueMakingPrefix(eAttribute.getEContainingClass()) + eAttribute.getEContainingClass().getName()
+ "_" + eAttribute.getName(); //$NON-NLS-1$
}
/**
* Compute the column name for a standard primitive type column.
*
* @param eFeature
* @return the column name of the basic column
* @see EPackageORMAnnotation#getColumnNamePrefix()
*/
public String getColumnName(EStructuralFeature eFeature) {
String localName = getDictionariedName(eFeature, null);
if (localName == null) {
localName = eFeature.getName();
}
return processName(localName, ePackageORMAnnotation.getColumnNamePrefix());
}
private String getUniqueMakingPrefix(EClass eClass) {
if (ePackageORMAnnotation.isUniqueEntityNames()) {
return eClass.getEPackage().getNsPrefix() + "_"; //$NON-NLS-1$
}
return ""; //$NON-NLS-1$
}
// prevent illegal characters in table names and such
protected String makeSafe(String toBeMadeSafe) {
char[] chrs = toBeMadeSafe.toCharArray();
char[] targetChrs = new char[chrs.length];
int i = 0;
for (char chr : chrs) {
if (i > 0 && '0' <= chr && chr <= '9') {
targetChrs[i++] = chr;
} else if ('a' <= chr && chr <= 'z') {
targetChrs[i++] = chr;
} else if ('A' <= chr && chr <= 'Z') {
targetChrs[i++] = chr;
} else if (chr == '_') {
targetChrs[i++] = chr;
} else {
targetChrs[i++] = '_';
}
}
return new String(targetChrs);
}
public EPackageORMAnnotation getePackageORMAnnotation() {
return ePackageORMAnnotation;
}
public void setePackageORMAnnotation(EPackageORMAnnotation ePackageORMAnnotation) {
this.ePackageORMAnnotation = ePackageORMAnnotation;
}
private String getSafePrefixStr(String value) {
if (value == null) {
return ""; //$NON-NLS-1$
}
return value;
}
protected String getDictionariedName(ENamedElement eNamedElement, String qualifier) {
final String dictionaryName = getNameDictionary().getProperty(getPropertyName(eNamedElement, qualifier));
if (GeneratorUtils.isNotEmptyAndNotNull(dictionaryName)) {
return dictionaryName;
}
return null;
}
private Properties getNameDictionary() {
if (nameDictionary != null) {
return nameDictionary;
}
nameDictionary = new Properties();
if (GeneratorUtils.isNotEmptyAndNotNull(getePackageORMAnnotation().getNameDictionaryPropertyFile())
&& getePackageORMAnnotation().getNameDictionaryPropertyFile().trim().length() > 0) {
URI uri = getePackageORMAnnotation().getEPackage().eResource().getURI();
uri = uri.trimSegments(1);
uri = uri.appendSegment(getePackageORMAnnotation().getNameDictionaryPropertyFile());
try {
final InputStream is = new ExtensibleURIConverterImpl().createInputStream(uri);
if (is != null) {
nameDictionary.load(is);
is.close();
}
} catch (IOException e) {
// just ignore
System.err.println(e);
}
}
return nameDictionary;
}
protected String getPropertyName(ENamedElement eNamedElement, String qualifier) {
String searchName = eNamedElement.getName();
if (eNamedElement instanceof EStructuralFeature) {
searchName = ((EStructuralFeature) eNamedElement).getEContainingClass().getName() + "." + searchName; //$NON-NLS-1$
}
if (GeneratorUtils.isNotEmptyAndNotNull(qualifier)) {
searchName += "." + qualifier; //$NON-NLS-1$
}
if (GeneratorUtils.isNotEmptyAndNotNull(searchName)) {
if (searchName.length() == 1) {
return searchName.toLowerCase();
}
return searchName.substring(0, 1).toLowerCase() + searchName.substring(1);
}
return searchName;
}
protected String processName(String name, String prefix) {
String localPrefix = getSafePrefixStr(prefix);
String localName = makeSafe(name);
// assume that with a prefix no rename is needed
if (localPrefix.trim().length() == 0) {
localName = renameReservedWord(localName);
}
if (getePackageORMAnnotation().isLowerCasedNames()) {
localName = localName.toLowerCase();
localPrefix = localPrefix.toLowerCase();
} else if (getePackageORMAnnotation().isUpperCasedNames()) {
localName = localName.toUpperCase();
localPrefix = localPrefix.toUpperCase();
}
final int maxSqlLength = getePackageORMAnnotation().getMaximumSqlNameLength();
if (localName.length() > maxSqlLength - localPrefix.length()) {
localName = trunc(localName, maxSqlLength - localPrefix.length());
}
return localPrefix + localName;
}
protected String trunc(String name, int targetLength) {
String correctedName = name;
// now do vowel truncation preserving the first character, and starting from the end
char correctedNameFirstChar = correctedName.charAt(0);
String correctedNameTail = correctedName.substring(1);
int truncSize = 1 + correctedNameTail.length() - targetLength;
if (truncSize <= 0) {
return name;
}
final StringBuilder sb = new StringBuilder();
final char[] chrs = correctedNameTail.toCharArray();
final char[] removes = getRemovableCharacters();
for (int i = chrs.length - 1; i >= 0; i--) {
final char chr = chrs[i];
boolean add = true;
if (truncSize > 0) {
for (char vowel : removes) {
if (vowel == chr) {
add = false;
break;
}
}
}
if (add) {
sb.insert(0, chr);
} else {
truncSize--;
}
}
if (truncSize == 0) {
return correctedNameFirstChar + sb.toString();
}
// still failed do length truncation
return doLengthTruncation(correctedNameFirstChar + correctedNameTail, targetLength);
}
protected String doLengthTruncation(String correctedName, int targetLength) {
// failed do length truncation with the remainder
final int underscore = correctedName.lastIndexOf('_');
if (underscore == -1) {
return correctedName.substring(0, targetLength);
}
// now do the complex logic to truncate different parts
final String[] parts = correctedName.split("_"); //$NON-NLS-1$
int maxLength = -1;
for (String part : parts) {
if (part.length() > maxLength && part.length() > 0) {
maxLength = part.length();
}
}
// can this ever happen
int totalLength = correctedName.length();
while (maxLength > 1 && totalLength > targetLength) {
totalLength = 0;
int newMax = 0;
for (int i = 0; i < parts.length; i++) {
if (parts[i].length() == maxLength) {
parts[i] = parts[i].substring(0, maxLength - 1);
}
if (parts[i].length() > newMax) {
newMax = parts[i].length();
}
totalLength += parts[i].length();
}
totalLength += parts.length - 1; // count the underscores
maxLength = newMax;
}
final StringBuffer result = new StringBuffer();
for (String part : parts) {
if (result.length() > 0) {
result.append("_"); //$NON-NLS-1$
}
result.append(part);
}
return result.toString();
}
/**
* Return the characters to remove, the character removal is done in order of the returned array. This method is
* provided to be overridden to pass a custom set of removable characters.
*/
protected char[] getRemovableCharacters() {
return removables;
}
}