| /******************************************************************************* |
| * Copyright (c) 2007, 2010 Oracle. 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: |
| * Oracle - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.jpt.jpa.gen.internal; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.ObjectInputStream; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jpt.common.utility.internal.StringTools; |
| import org.eclipse.jpt.jpa.db.Column; |
| import org.eclipse.jpt.jpa.db.Schema; |
| import org.eclipse.jpt.jpa.db.Table; |
| import org.eclipse.jpt.jpa.gen.internal.util.DTPUtil; |
| import org.eclipse.jpt.jpa.gen.internal.util.FileUtil; |
| import org.eclipse.jpt.jpa.gen.internal.util.ForeignKeyInfo; |
| import org.eclipse.jpt.jpa.gen.internal.util.StringUtil; |
| |
| /** |
| * Contains the information used to customize the database schema to ORM entity |
| * generation. |
| * |
| * <p>The customization settings are mainly exposed in the form of |
| * properties. There are no assumptions in this class about the meaning of the |
| * property names. Properties can be associated to specific tables and table |
| * columns, or globally for any table and/or column. |
| * |
| * <p>Subclass can implement the sets of abstract methods to provide ORM vendor |
| * specific properties. |
| * |
| */ |
| public abstract class ORMGenCustomizer implements java.io.Serializable |
| { |
| private final static long serialVersionUID = 1; |
| |
| /** |
| * A value passed for the table name argument to get/setProperty |
| * indicating that the value applies to any table. |
| */ |
| public static final String ANY_TABLE = "__anyTable__"; |
| public static final String GENERATE_DDL_ANNOTATION = "generateDDLAnnotations"; |
| /*the string used in the property name in mProps to indicate |
| * a null table value.*/ |
| private static final String NULL_TABLE = ""; |
| /*the string used in the property name in mProps to indicate |
| * a null column value.*/ |
| private static final String NULL_COLUMN = ""; |
| |
| /*This version number is written in the header of the customization stream |
| * and read at de-serialization time, if it is different then the file is invalidated. |
| */ |
| private static final int FILE_VERSION = 2; |
| |
| private static final String UPDATE_CONFIG_FILE = "updateConfigFile"; |
| |
| private transient Schema mSchema; |
| private transient File mFile; |
| |
| private List<String> mTableNames; |
| /*key: table name, value: ORMGenTable object.*/ |
| private transient Map<String , ORMGenTable> mTables; |
| /*the <code>Association</code> objects sorted by their "from" |
| * table name. Includes all association derived from foreign keys |
| * in user selected tables. Since some of the foreign keys may point to table |
| * user does not select, this list may be different from mValidAssociations |
| */ |
| private List<Association> mAssociations; |
| /* |
| * List of valid associations within the user selected tables |
| */ |
| private transient List<Association> mValidAssociations; |
| private transient boolean mInvalidForeignAssociations; |
| |
| /*the property name is in the form $tableName.$columnName.$propertyName. |
| * Where tableName could be NULL_TABLE or ANY_TABLE |
| * and columnName could be NULL_COLUMN*/ |
| private Map<String, String> mProps = new java.util.HashMap<String, String>(); |
| |
| private transient DatabaseAnnotationNameBuilder databaseAnnotationNameBuilder = DatabaseAnnotationNameBuilder.Default.INSTANCE; |
| |
| private boolean mUpdatePersistenceXml = true; |
| |
| //----------------------------------------- |
| //---- abstract methods |
| //----------------------------------------- |
| /** |
| * Returns all the primary key generator schemes. |
| * This can return any strings as far as the Velocity template |
| * processor understand them. |
| */ |
| public abstract List<String> getAllIdGenerators(); |
| /** |
| * Returns the string representing the developer-assigned id generator. |
| * This can return any strings as far as the Velocity template |
| * processor understand them. |
| */ |
| public abstract String getNoIdGenerator(); |
| /** |
| * Returns the string representing the identity id generator. |
| * This can return any strings as far as the Velocity template |
| * processor understand them. |
| */ |
| public abstract String getIdentityIdGenerator(); |
| /** |
| * Returns the strings representing the sequence generators. |
| * This can return any strings as far as the Velocity template |
| * processor understand them. |
| */ |
| public abstract Set<String> getSequenceIdGenerators(); |
| /** |
| * Returns a property type from the given database column. |
| * This can return any strings as far as the Velocity template |
| * processor understand them. |
| */ |
| public abstract String getPropertyTypeFromColumn(Column column) ; |
| /** |
| * Returns all the strings representing property types. |
| * This can return any strings as far as the Velocity template |
| * processor understand them. |
| */ |
| public abstract String[] getAllPropertyTypes(); |
| /** |
| * Returns all the strings representing property mapping kinds. |
| * This can return any strings as far as the Velocity template |
| * processor understand them. |
| */ |
| public abstract String[] getAllMappingKinds(); |
| /** |
| * Returns the basic (default) property mapping kind. |
| * This can return any strings as far as the Velocity template |
| * processor understand them. |
| */ |
| public abstract String getBasicMappingKind(); |
| /** |
| * Returns the id (primary key) property mapping kind. |
| * This can return any strings as far as the Velocity template |
| * processor understand them. |
| */ |
| public abstract String getIdMappingKind(); |
| /** |
| * Interacts with the user to edit the cascade of the given |
| * role. |
| * This method should also call <code>AssociationRole.setCascade</code>. |
| * |
| * @return false if the user interaction is cancelled. |
| */ |
| public abstract boolean editCascade(AssociationRole role); |
| |
| //----------------------------------------- |
| //----------------------------------------- |
| |
| /** |
| * @param file The file that contains the customization settings. |
| * The file is created if necessary when the <code>save</code> |
| * method is called. |
| */ |
| public void init( File file, Schema schema) { |
| this.mSchema = schema; |
| mFile = file; |
| |
| if (!file.exists()) { |
| setProperty(ORMGenTable.DEFAULT_FETCH, ORMGenTable.DEFAULT_FETCH, ORMGenCustomizer.ANY_TABLE, null); |
| return; |
| } |
| InputStream istream = null; |
| ObjectInputStream ois = null; |
| try |
| { |
| //read it in a file first to speedup deserialization |
| byte[] bytes = FileUtil.readFile(file); |
| istream = new ByteArrayInputStream(bytes); |
| ois = new ObjectInputStream(istream); |
| |
| FileHeader header = (FileHeader)ois.readObject(); |
| if (header.mVersion == FILE_VERSION) { |
| ORMGenCustomizer customizer = (ORMGenCustomizer)ois.readObject(); |
| restore(customizer); |
| } |
| } catch (Exception ex) { |
| JptJpaGenPlugin.logException("***ORMGenCustomizer.load failed "+file, ex); |
| } |
| finally |
| { |
| if (ois != null) |
| { |
| try |
| { |
| ois.close(); |
| } catch (IOException e) { |
| } |
| } |
| |
| if (istream != null) |
| { |
| try |
| { |
| istream.close(); |
| } catch (IOException e) { |
| } |
| } |
| } |
| } |
| |
| public File getFile(){ |
| return this.mFile; |
| } |
| |
| public void setSchema(Schema schema){ |
| this.mSchema = schema; |
| } |
| |
| public Schema getSchema(){ |
| return mSchema; |
| } |
| |
| /** |
| * Empty constructor needed by the deserialization. |
| */ |
| public ORMGenCustomizer() { |
| super(); |
| } |
| |
| /** |
| * Saves the customization file. |
| * The file is created if necessary. |
| */ |
| public void save() throws IOException { |
| //System.out.println("---ORMGenCustomizer.save: " + mFile); |
| if (!mFile.exists() && !mFile.createNewFile()) { |
| return; |
| } |
| java.io.FileOutputStream fos = null; |
| java.io.ObjectOutputStream oos = null; |
| boolean deleteIt = true; |
| try { |
| fos = new java.io.FileOutputStream(mFile); |
| oos = new java.io.ObjectOutputStream(fos); |
| FileHeader header = new FileHeader(); |
| oos.writeObject(header); |
| oos.writeObject(this); |
| deleteIt = false; |
| } catch (Exception ex) { |
| //deleteIt is true, so the cache is not saved. |
| CoreException ce = new CoreException(new Status(IStatus.ERROR, JptJpaGenPlugin.PLUGIN_ID, |
| "Unable to save the ORMGenCustomizer file: "+mFile,ex)); |
| JptJpaGenPlugin.logException( ce ); |
| } finally { |
| try { |
| if (oos!=null) oos.close(); |
| if (fos!=null) fos.close(); |
| if (deleteIt) { |
| mFile.delete(); |
| } |
| } catch (java.io.IOException ex2) {} |
| } |
| } |
| |
| public DatabaseAnnotationNameBuilder getDatabaseAnnotationNameBuilder() { |
| return this.databaseAnnotationNameBuilder; |
| } |
| public void setDatabaseAnnotationNameBuilder(DatabaseAnnotationNameBuilder databaseAnnotationNameBuilder) { |
| if (databaseAnnotationNameBuilder == null) { |
| throw new NullPointerException("database annotation name builder is required"); //$NON-NLS-1$ |
| } |
| this.databaseAnnotationNameBuilder = databaseAnnotationNameBuilder; |
| } |
| |
| /** |
| * Returns {@link #GENERATE_DDL_ANNOTATION} indicating whether |
| * the optional DDL parameters like length, nullable, unqiue, etc should be generated |
| * in @Column annotation. |
| * defaults to false. |
| */ |
| public boolean isGenerateDDLAnnotations() { |
| return "true".equals(getProperty(GENERATE_DDL_ANNOTATION, ANY_TABLE, null)); //defaults to false |
| } |
| |
| /** |
| * Returns a property value. |
| */ |
| public String getProperty(String propertyName, String tableName, String colName) { |
| String key = getPropKey(propertyName, tableName, colName); |
| String value = mProps.get(key); |
| /*if the key does not exist and it is a table property then |
| * get the default table property.*/ |
| if (value == null && tableName != null && colName == null && !tableName.equals(ANY_TABLE)) { |
| value = getProperty(propertyName, ANY_TABLE, colName); |
| } |
| return value; |
| } |
| /** |
| * Changes a property value. |
| * |
| * @param value The new value, could be null. |
| */ |
| public void setProperty(String propertyName, String value, String tableName, String colName) { |
| String key = getPropKey(propertyName, tableName, colName); |
| if (value != null) { |
| mProps.put(key, value); |
| } else { |
| mProps.remove(key); |
| } |
| } |
| /** |
| * Same as {@link #getProperty(String, String, String)} but |
| * converts the value to boolean. |
| */ |
| public boolean getBooleanProperty(String propertyName, String tableName, String colName) { |
| String value = getProperty(propertyName, tableName, colName); |
| return "true".equals(value); |
| } |
| /** |
| * Changes a table boolean property value. |
| */ |
| public void setBooleanProperty(String propertyName, boolean value, String tableName, String colName) { |
| setProperty(propertyName, value ? "true" : "false", tableName, colName); |
| } |
| /** |
| * Returns the names of the tables to generate. |
| */ |
| @SuppressWarnings("unchecked") |
| public List<String> getTableNames() { |
| return mTableNames != null ? mTableNames : java.util.Collections.EMPTY_LIST; |
| } |
| |
| /** |
| * Returns the fetch type annotation member value, or empty string |
| * if none. |
| * Empty string is returned instead of null because Velocity does not like null |
| * when used in #set. |
| */ |
| public String genFetch(ORMGenTable table) { |
| return ""; |
| } |
| /** |
| * Called when the table user selection is changed in the |
| * generation wizard. |
| */ |
| public void setTableNames(List<String> tableNames) { |
| mTableNames = tableNames; |
| mTables = null; |
| mValidAssociations = null; //recompute |
| mInvalidForeignAssociations = true; //make sure foreign associations from newly added tables are computed. |
| } |
| /** |
| * Returns the table names to be generated. |
| * This might be different from <code>getTableNames</code> if there |
| * are many-to-many join tables and are not contributing |
| * in any other associations. |
| */ |
| public List<String> getGenTableNames() { |
| List<String> names = getTableNames(); |
| List<String> result = new java.util.ArrayList<String>(names.size()); |
| |
| /*filter out join tables*/ |
| List<Association> associations = getAssociations(); |
| for (Iterator<String> tableNamesIter = names.iterator(); tableNamesIter.hasNext(); ) { |
| String tableName = tableNamesIter.next(); |
| boolean isValid = true; |
| |
| for (Iterator<Association> assocIter = associations.iterator(); assocIter.hasNext(); ) { |
| Association association = assocIter.next(); |
| if (!association.isGenerated()) { |
| continue; |
| } |
| if (tableName.equals(association.getReferrerTableName()) |
| || tableName.equals(association.getReferencedTableName())) { |
| isValid = true; |
| break; |
| } |
| if (tableName.equals(association.getJoinTableName())) { |
| isValid = false; |
| } |
| } |
| if (isValid) { |
| result.add(tableName); |
| } |
| } |
| return result; |
| } |
| /** |
| * Returns an <code>ORMGenTable</code> object given its name, or |
| * null if none. |
| */ |
| public ORMGenTable getTable(String tableName) { |
| if (mTables == null) { |
| mTables = new java.util.HashMap<String, ORMGenTable>(mTableNames.size()); |
| } |
| |
| if(mTableNames!=null && mSchema!=null){ |
| for (Iterator<String> iter = mTableNames.iterator(); iter.hasNext(); ) { |
| String name = iter.next(); |
| Table dbTable = mSchema.getTableNamed( name ); |
| if (dbTable != null) { |
| mTables.put(name, createGenTable(dbTable)); |
| } |
| } |
| } |
| return mTables.get(tableName); |
| } |
| /** |
| * Returns the <code>Association</code> objects sorted by their "from" |
| * table name. |
| */ |
| public List<Association> getAssociations(){ |
| return getAssociations(true/*validOnly*/); |
| } |
| /** |
| * Adds the given association. |
| */ |
| public void addAssociation(Association association) { |
| getAssociations(false/*validOnly*/).add(association); |
| if (mValidAssociations != null) { |
| mValidAssociations.add(association); |
| } |
| |
| } |
| /** |
| * Deletes the given association. |
| */ |
| public void deleteAssociation(Association association) { |
| boolean removed = getAssociations(false/*validOnly*/).remove(association); |
| assert(removed); |
| |
| if (mValidAssociations != null) { |
| removed = mValidAssociations.remove(association); |
| assert(removed); |
| } |
| } |
| /** |
| * Returns true if an association similar to the given association |
| * already exists. |
| * This is decided based only on the association tables and columns. |
| */ |
| public boolean similarAssociationExists(Association association) { |
| try { |
| for (Iterator<Association> iter = getAssociations(false/*validOnly*/).iterator(); iter.hasNext(); ) { |
| Association association2 = iter.next(); |
| if (!association.getReferrerTableName().equals(association2.getReferrerTableName()) |
| || !association.getReferencedTableName().equals(association2.getReferencedTableName()) |
| || !StringUtil.equalObjects(association.getJoinTableName(), association2.getJoinTableName()) |
| || !association.getReferrerColumnNames().equals(association2.getReferrerColumnNames()) |
| || !association.getReferencedColumnNames().equals(association2.getReferencedColumnNames()) |
| ) { |
| continue; |
| } |
| /*the 2 association have the same referrer, referenced and join table*/ |
| if (association.getJoinTableName() == null) { |
| return true; |
| } |
| if (association.getReferrerJoinColumnNames().equals(association2.getReferrerJoinColumnNames()) |
| && association.getReferencedJoinColumnNames().equals(association2.getReferencedJoinColumnNames())) { |
| return true; |
| } |
| } |
| } catch (Exception e) { |
| return false; |
| } |
| return false; |
| } |
| /** |
| * Creates the <code>ORMGenTable</code> instance. |
| */ |
| public ORMGenTable createGenTable(Table dbTable) { |
| return new ORMGenTable(dbTable, this); |
| } |
| /** |
| * Creates the <code>ORMGenColumn</code> instance. |
| */ |
| protected ORMGenColumn createGenColumn(Column dbCol) { |
| return new ORMGenColumn(dbCol, this); |
| } |
| /** |
| * Returns true of the underlying persistence specs require the "many" side |
| * of an association to be the owner (like EJB3). |
| */ |
| protected boolean manySideIsAssociationOwner() { |
| return false; |
| } |
| public boolean isUpdateConfigFile() { |
| return !"false".equals(getProperty(UPDATE_CONFIG_FILE, null, null)); //defaults to true |
| } |
| public void setUpdateConfigFile(boolean value) { |
| if (value) { //default is true |
| setProperty(UPDATE_CONFIG_FILE, null, null, null); //remove it |
| } else { |
| setBooleanProperty(UPDATE_CONFIG_FILE, value, null, null); |
| } |
| } |
| |
| //----------------------------------------- |
| //---- Velocity templates methods |
| //----------------------------------------- |
| /** |
| * Returns a getter method name given a property name. |
| */ |
| public String propertyGetter(String propertyName) { |
| return "get"+StringUtil.initUpper(propertyName); |
| } |
| /** |
| * Returns a setter method name given a property name. |
| */ |
| public String propertySetter(String propertyName) { |
| return "set"+StringUtil.initUpper(propertyName); |
| } |
| public String quote(String s) { |
| return StringUtil.quote(s, '"'); |
| } |
| public String quote(boolean b) { |
| return quote(String.valueOf(b)); |
| } |
| public String quote(int i) { |
| return quote(String.valueOf(i)); |
| } |
| public String convertToJavaStringLiteral(String s) { |
| return StringTools.convertToJavaStringLiteral(s); |
| } |
| /** |
| * Appends an annotation member name and value to an existing annotation. |
| * |
| * @param s The annotation members string. |
| * |
| * @param memberValue The member value, if null or empty strings then |
| * nothing is appened. |
| * |
| * @param whether to double quote the member value. |
| */ |
| public String appendAnnotation(String s, String memberName, String memberValue, boolean quote) { |
| if (memberValue == null || memberValue.length() == 0) { |
| return s; |
| } |
| StringBuffer buffer = new StringBuffer(s); |
| if (buffer.length() != 0) { |
| buffer.append(", "); |
| } |
| buffer.append(memberName); |
| buffer.append('='); |
| if (quote) { |
| buffer.append('"'); |
| } |
| buffer.append(memberValue); |
| if (quote) { |
| buffer.append('"'); |
| } |
| return buffer.toString(); |
| } |
| public boolean isJDK1_5() { |
| return true; |
| } |
| |
| //----------------------------------------- |
| //---- private methods |
| //----------------------------------------- |
| /** |
| * Restores the customization settings from the given |
| * (persisted) customizer. |
| */ |
| private void restore(ORMGenCustomizer customizer) { |
| mTableNames = customizer.mTableNames; |
| mAssociations = customizer.mAssociations; |
| mProps = customizer.mProps; |
| mUpdatePersistenceXml = customizer.mUpdatePersistenceXml; |
| if( mSchema == null ) |
| return; |
| |
| /*remove invalid table names*/ |
| for (int i = mTableNames.size()-1; i >= 0; --i) { |
| String tableName = mTableNames.get(i); |
| if (mSchema.getTableNamed( tableName) == null) { |
| mTableNames.remove(i); |
| } |
| } |
| if( mAssociations!=null ){ |
| /*restore the associations*/ |
| for (Iterator<Association> iter = mAssociations.iterator(); iter.hasNext(); ) { |
| Association association = iter.next(); |
| association.restore(this); |
| } |
| /*add the foreign keys associations just in case the tables changed since |
| * the last time the state was persisted. Pass checkExisting true so that the |
| * associations restored above are not overwritten.*/ |
| addForeignKeyAssociations(true/*checkExisting*/); |
| // sort on restore |
| sortAssociations( mAssociations ); |
| } |
| } |
| /** |
| * Returns the key in mProps corresponding to the specified |
| * propertyName, table and column. |
| */ |
| private String getPropKey(String propertyName, String tableName, String colName) { |
| if (tableName == null) { |
| tableName = NULL_TABLE; |
| } |
| if (colName == null) { |
| colName = NULL_COLUMN; |
| } |
| return tableName + '.' + colName + '.' + propertyName; |
| } |
| /** |
| * Returns the associations that are valid for the |
| * current tables. |
| */ |
| private List<Association> getAssociations(boolean validOnly){ |
| if (mAssociations == null) { |
| mAssociations = new java.util.ArrayList<Association>(); |
| |
| addForeignKeyAssociations(false/*checkExisting*/); |
| } else if (mInvalidForeignAssociations) { |
| mInvalidForeignAssociations = false; |
| |
| addForeignKeyAssociations(true/*checkExisting*/); |
| } |
| List<Association> associations; |
| if (validOnly) { |
| if (mValidAssociations == null) { |
| /*filter out the invalid associations*/ |
| mValidAssociations = new ArrayList<Association>(mAssociations.size()); |
| for (int i = 0, n = mAssociations.size(); i < n; ++i) { |
| Association association = mAssociations.get(i); |
| if (association.isValid()) { |
| mValidAssociations.add(association); |
| } |
| } |
| } |
| associations = mValidAssociations; |
| } else { |
| associations = mAssociations; |
| } |
| return associations; |
| } |
| private void addForeignKeyAssociations(boolean checkExisting) { |
| List<String> tableNames = getTableNames(); |
| for (Iterator<String> iter = tableNames.iterator(); iter.hasNext(); ) { |
| ORMGenTable table = getTable(iter.next()); |
| addForeignKeyAssociations(table, checkExisting); |
| } |
| } |
| private void addForeignKeyAssociations(ORMGenTable table, boolean checkExisting) { |
| if(table==null) |
| return; |
| |
| |
| List<ForeignKeyInfo> fKeys = null; |
| |
| try{ |
| fKeys = DTPUtil.getForeignKeys(table.getDbTable()); |
| }catch(Exception ise){ |
| //workaround Dali bug for now |
| return; |
| } |
| |
| if( fKeys.size()==0 ) |
| return; |
| |
| List<Association> addedAssociations = new java.util.ArrayList<Association>(); |
| for (Iterator<ForeignKeyInfo> iter = fKeys.iterator(); iter.hasNext(); ) { |
| ForeignKeyInfo fki = iter.next(); |
| ORMGenTable referencedTable = getTable(fki.getReferencedTableName()); |
| if (referencedTable == null) { |
| continue; |
| } |
| Association association = new Association(this, table.getName(), fki.getReferrerColumnNames() |
| , referencedTable.getName(), fki.getReferencedColumnNames()); |
| association.computeCardinality(); |
| //Defer the check of similarAssociationExists after computeManyToMany() |
| //otherwise the MTM association will not computed correctly in some cases. |
| //if (checkExisting && similarAssociationExists(association)) { |
| // continue; |
| //} |
| addedAssociations.add(association); |
| } |
| |
| Association m2m = computeManyToMany(table, addedAssociations); |
| if (m2m != null) { |
| /*do not generate the 2 many-to-one*/ |
| addedAssociations.clear(); |
| addedAssociations.add(0, m2m); |
| } |
| //remove the association if already existing |
| Iterator<Association> it = addedAssociations.iterator(); |
| while( it.hasNext() ){ |
| Association newAssociation = it.next(); |
| for( Association association : mAssociations ){ |
| if( newAssociation.equals( association )){ |
| it.remove(); |
| break; |
| } |
| } |
| } |
| mAssociations.addAll(addedAssociations); |
| } |
| private Association computeManyToMany(ORMGenTable table, List<Association> addedAssociations) { |
| /** many-to-many associations if: |
| * - addedAssociations contains 2 many-to-one associations |
| * - tables t1 and t2 does NOT have to be different( for self-MTM-self situation) |
| * - <code>table</code> contains only the foreign key columns. |
| * |
| * Note: following restrictions have been removed: |
| * -table has only two columns |
| * -t1 and t2 must be different |
| * -the primary key of <code>table</code> is the concatenation of its foreign |
| * keys to t1 and t2.*/ |
| |
| if (addedAssociations.size() != 2) { |
| return null; |
| } |
| |
| //MTM table should have two MTO relations to orginal tables |
| Association assoc1 = addedAssociations.get(0); |
| Association assoc2 = addedAssociations.get(1); |
| if (assoc1.getCardinality() != Association.MANY_TO_ONE |
| || assoc2.getCardinality() != Association.MANY_TO_ONE) { |
| return null; |
| } |
| |
| //MTM table should only include foreign key columns |
| for( ORMGenColumn col : table.getColumns()){ |
| if( !col.isForeignKey()) |
| return null; |
| } |
| |
| |
| ORMGenTable t1 = assoc1.getReferencedTable(); |
| ORMGenTable t2 = assoc2.getReferencedTable(); |
| |
| if( t1.getName().equals(table.getName()) || t2.getName().equals(table.getName()) ) { |
| return null; |
| } |
| |
| //Make a guess which table is the owning side of the MTM relation |
| //See https://bugs.eclipse.org/bugs/show_bug.cgi?id=268445 |
| //Logic borrowed from DTPTableWrapper.getJoinTableOwningForeignKey() |
| if( !table.getName().equals(t1.getName() + "_" + t2.getName() ) ) { |
| //swap t1 and t2 |
| ORMGenTable t3 = t1; |
| t1=t2; |
| t2=t3; |
| //swap assoc1 and assoc2 |
| Association assoc3=assoc1; |
| assoc1=assoc2; |
| assoc2=assoc3; |
| } |
| |
| //Commented out because the assumption is too restrictive: |
| //this check will prevent generating MTM mapping table not having |
| //primary key defined |
| // List pkNames = DTPUtil.getPrimaryKeyColumnNames(table.getDbTable()); |
| // if (pkNames.size() != table.getColumnNames().size()) { |
| // return null; |
| // } |
| // List fkNames = new java.util.ArrayList(assoc1.getReferrerColumnNames()); //clone because we modify by addAll below |
| // fkNames.addAll(assoc2.getReferrerColumnNames()); |
| // if (!CollectionUtil.equalsIgnoreOrder(pkNames, fkNames)) { |
| // return null; |
| // } |
| Association m2m = new Association(this, t1.getName()/*referrerTableName*/, assoc1.getReferencedColumnNames()/*referrerColNames*/ |
| , t2.getName()/*referencedTableName*/, assoc2.getReferencedColumnNames()/*referencedColNames*/ |
| , table.getName(), assoc1.getReferrerColumnNames()/*referrerJoinColNames*/, assoc2.getReferrerColumnNames()/*referencedJoinColNames*/); |
| m2m.setCustom(false); |
| return m2m; |
| } |
| |
| //--------------------------------------------------- |
| //---- FileHeader class ----------------------------- |
| //--------------------------------------------------- |
| /** |
| * The header of the customization file. |
| */ |
| private static class FileHeader implements java.io.Serializable |
| { |
| private static final long serialVersionUID = 1L; |
| /** |
| * Should be argument-less because it is used in |
| * the de-serialization process. |
| */ |
| public FileHeader() { |
| mVersion = FILE_VERSION; |
| } |
| int mVersion; |
| } |
| |
| private void sortAssociations( List< Association > list ) { |
| Collections.sort( list, new Comparator< Association >() { |
| public int compare( Association lhs, Association rhs ) { |
| // sort by referrer table name first... |
| int test = lhs.getReferrerTableName().compareTo( rhs.getReferrerTableName() ); |
| if( test != 0 ) |
| return test; |
| // then by referenced table name... |
| test = lhs.getReferencedTableName().compareTo( rhs.getReferencedTableName() ); |
| if( test != 0 ) |
| return test; |
| // if referrer and referenced tables are the same, they should |
| // appear next to each other |
| return 0; |
| } |
| } ); |
| } |
| public boolean updatePersistenceXml() { |
| return mUpdatePersistenceXml; |
| } |
| public void setUpdatePersistenceXml(boolean updatePersistenceXml) { |
| this.mUpdatePersistenceXml = updatePersistenceXml; |
| } |
| } |