Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 32496eb8aca302d32d738a439423516f4152da8b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
/*******************************************************************************
 * Copyright (c) 2007, 2008 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.gen.internal2;

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.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.db.Column;
import org.eclipse.jpt.db.Schema;
import org.eclipse.jpt.db.Table;
import org.eclipse.jpt.gen.internal2.util.DTPUtil;
import org.eclipse.jpt.gen.internal2.util.FileUtil;
import org.eclipse.jpt.gen.internal2.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__";
	/*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;
	
	//-----------------------------------------
	//---- 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()) {
			/*A hack to set the default to eager so thet app does not throw at runtime for new apps
			 * (in case a lazy strategy is not developed yet). This is done instead of in ORMGenTable.getDefaultFetch 
			 * for backward compatibility.*/
			setProperty(ORMGenTable.DEFAULT_FETCH, ORMGenTable.EAGER_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) {
			System.out.println("***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 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, JptGenPlugin.PLUGIN_ID,
					"Unable to save the ORMGenCustomizer file: "+mFile,ex));
			JptGenPlugin.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 a property value.
	 */
	public String getProperty(String propertyName, String tableName, String colName) {
		String key = getPropKey(propertyName, tableName, colName);
		String value = (String)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;
	}
	/**
	 * 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 = (String)tableNamesIter.next();
			boolean isValid = true;
			
			for (Iterator<Association> assocIter = associations.iterator(); assocIter.hasNext(); ) {
				Association 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 = (String)iter.next();
				Table dbTable = mSchema.getTableNamed( name );
				if (dbTable != null) {
					mTables.put(name, createGenTable(dbTable));
				}
			}
		}
		return (ORMGenTable)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 = (Association)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));
	}
	/**
	 * 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;
		if( mSchema == null )
			return;
		
		/*remove invalid table names*/
		for (int i = mTableNames.size()-1; i >= 0; --i) {
			String tableName = (String)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 = (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*/);
		}
	}
	/**
	 * 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 = (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((String)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 = (ForeignKeyInfo)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*/
			for (int i = 0; i < addedAssociations.size(); ++i) {
				Association association = (Association)addedAssociations.get(i);
				association.setGenerated(false);
			}
			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 )){
					//If the association is existing, don't add.
					//only copy the generated flag
					association.setGenerated( newAssociation.isGenerated() );
					it.remove();
				}
			}
		}
		mAssociations.addAll(addedAssociations);
	}
	private Association computeManyToMany(ORMGenTable table, List<Association> addedAssociations) {
		/*many-to-many associations if:
		 * -addedAssociations contains 2 many-to-one associations to 2 different tables t1 and t2.
		 * -<code>table</code> contains only the primary key columns.
		 * -the primary key of <code>table</code> is the concatenation of its foreign 
		 * 	keys to t1 and t2.*/
		
		if (addedAssociations.size() != 2) {
			return null;
		}
		
		//Assume MTM table should have only two columns that are foreign keys
		if( table.getColumns().size() != 2 ){
			return null;
		}
		
		Association assoc1 = (Association)addedAssociations.get(0);
		Association assoc2 = (Association)addedAssociations.get(1);
		if (assoc1.getCardinality() != Association.MANY_TO_ONE
				|| assoc2.getCardinality() != Association.MANY_TO_ONE) {
			return null;
		}
		ORMGenTable t1 = assoc1.getReferencedTable();
		ORMGenTable t2 = assoc2.getReferencedTable();

//Commented out because the assumption is too restrictive: 
//this check will prevent generating table MTM to itself		
//		if (t1.getName().equals(t2.getName()) || 
//				t1.getName().equals(table.getName()) || t2.getName().equals(table.getName())) {
		if( t1.getName().equals(table.getName()) || t2.getName().equals(table.getName()) ) {
			return null;
		}
		
//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;
	}
}

Back to the top