1 package org.devaki.nextobjects.util;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.FileOutputStream;
23 import java.io.ObjectInputStream;
24 import java.io.ObjectOutputStream;
25 import java.util.Vector;
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.devaki.nextobjects.workspace.models.ConceptualModel;
29 import org.devaki.nextobjects.workspace.models.PhysicalModel;
30 import org.devaki.nextobjects.workspace.models.columns.Column;
31 import org.devaki.nextobjects.workspace.models.objects.Association;
32 import org.devaki.nextobjects.workspace.models.objects.AssociationLink;
33 import org.devaki.nextobjects.workspace.models.objects.Constraint;
34 import org.devaki.nextobjects.workspace.models.objects.InheritanceLink;
35 import org.devaki.nextobjects.workspace.models.objects.Entity;
36 import org.devaki.nextobjects.workspace.models.objects.Table;
37 import org.devaki.nextobjects.workspace.models.graphics.TableView;
38 /***
39 * The class responsible of all the CDM2PDM work.
40 *
41 * @see http://www.devaki.org/transformation.html
42 * @author <a href="mailto:eflorent@devaki.org">Emmanuel Florent</a>
43 * @author gregorybr@users.sourceforge.net
44 * @TODO check/fix N.I. abstract
45 *
46 */
47 public final class MeriseTransform
48 {
49 /***
50 * The logger from the log4j project
51 */
52 private static Log logger =
53 LogFactory.getLog(MeriseTransform.class.getName());
54 /***
55 * Dummy constructor
56 */
57 private MeriseTransform()
58 {
59 }
60 /***
61 * Generate a PDM from the current CDM *
62 * @param pConceptualModel the context model
63 * @return the physical model
64 */
65 public static PhysicalModel cdm2pdm(final ConceptualModel pConceptualModel)
66 {
67 /***
68 * Temporary CDM where we are going to pick all the objects.
69 */
70 ConceptualModel anotherMerise = null;
71 /***
72 * The returned physical model
73 */
74 PhysicalModel db = new PhysicalModel("--///--");
75 /***
76 * The temp file.
77 */
78 File tmpFile = null;
79 logger.warn("Using EXPERIMENTAL merise transform");
80
81
82 try
83 {
84 tmpFile = File.createTempFile("erd", "cdm");
85 }
86 catch (Exception ioex)
87 {
88 logger.error(
89 "Unable to write temp-file - check space left on device:");
90 logger.error(ioex.getMessage());
91 }
92 if (CDMVerifier.verify(pConceptualModel))
93 {
94 try
95 {
96 ObjectOutputStream flux =
97 new ObjectOutputStream(new FileOutputStream(tmpFile));
98 flux.writeObject(pConceptualModel);
99 flux.flush();
100 flux.close();
101 }
102 catch (Exception ioex)
103 {
104 logger.error("Unable to save temporary file " + tmpFile);
105 }
106
107 try
108 {
109 ObjectInputStream flux =
110 new ObjectInputStream(new FileInputStream(tmpFile));
111 anotherMerise = (ConceptualModel) flux.readObject();
112 flux.close();
113 tmpFile.delete();
114 }
115 catch (Exception ioex)
116 {
117 logger.error("I can't understand file : " + ioex);
118 }
119
120 anotherMerise.setDevelopers(pConceptualModel.getDevelopers());
121 anotherMerise.setBuild(pConceptualModel.getBuild());
122 anotherMerise.setContributors(pConceptualModel.getContributors());
123 anotherMerise.setDependencies(pConceptualModel.getDependencies());
124 anotherMerise.setLicenses(pConceptualModel.getLicenses());
125 anotherMerise.setMailingLists(pConceptualModel.getMailingLists());
126 anotherMerise.setProperties(pConceptualModel.getProperties());
127 if (anotherMerise != null)
128 {
129 ModelMan.newPhysicalDatamodel(db);
130 ModelMan.setCurrentModel(db);
131 MeriseTransform.initDatabase(db, anotherMerise);
132 MeriseTransform.applyRule00(db, anotherMerise);
133 ModelMan.resizeModelObjects(db);
134 db.getModelView().repaint();
135 }
136 else
137 {
138 logger.error("Giving up :(");
139 }
140 }
141 else
142 {
143
144 logger.error("I can't continue because the model is wrong.");
145 }
146 db.getRedoLog().clear();
147 return db;
148 }
149 /***
150 * Utility function to take all needed attribute of a pdm into a cdm.
151 *
152 * @param pDatabase context model
153 * @param pMerise context model
154 * @return physical model
155 * @task implements generate a pdm filename/path
156 */
157 private static PhysicalModel initDatabase(
158 final PhysicalModel pDatabase,
159 final ConceptualModel pMerise)
160 {
161 pDatabase.setName(pMerise.getName());
162 pDatabase.setId(pMerise.getId());
163 pDatabase.setDefaultIdMethod(pMerise.getDefaultIdMethod());
164 pDatabase.setDefaultJavaType(pMerise.getDefaultJavaType());
165 pDatabase.setPackageName(pMerise.getPackageName());
166 pDatabase.setBaseClass(pMerise.getBaseClass());
167 pDatabase.setBasePeer(pMerise.getBasePeer());
168 pDatabase.setDefaultJavaNamingMethod(
169 pMerise.getDefaultJavaNamingMethod());
170 pDatabase.setHeavyIndexing(pMerise.getHeavyIndexing());
171 pDatabase.setDescription(pMerise.getDescription());
172 pDatabase.setNotes(pMerise.getNotes());
173 pDatabase.setCompany(pMerise.getCompany());
174 pDatabase.setProjectURL(pMerise.getProjectURL());
175 pDatabase.setCreateDatabaseUrl(pMerise.getCreateDatabaseUrl());
176 pDatabase.setBuildDatabaseUrl(pMerise.getBuildDatabaseUrl());
177 pDatabase.setSchema(pMerise.getSchema());
178 pDatabase.setDatabaseHost(pMerise.getDatabaseHost());
179 pDatabase.setDatabasePassword(pMerise.getDatabasePassword());
180 pDatabase.setDatabaseUser(pMerise.getDatabaseUser());
181 pDatabase.setDbType(pMerise.getDbType());
182 pDatabase.setProjectURL(pMerise.getProjectURL());
183 pDatabase.setShortDescription(pMerise.getShortDescription());
184 pDatabase.setLogo(pMerise.getLogo());
185 pDatabase.setInceptionYear(pMerise.getInceptionYear());
186 pDatabase.setOrganizationName(pMerise.getOrganizationName());
187 pDatabase.setOrganizationLogo(pMerise.getOrganizationLogo());
188 pDatabase.setOrganizationUrl(pMerise.getOrganizationUrl());
189 pDatabase.setSiteAddress(pMerise.getSiteAdress());
190 pDatabase.setDistributionDirectory(pMerise.getDistributionDirectory());
191 pDatabase.setSiteDirectory(pMerise.getSiteDirectory());
192 pDatabase.setIssueTrackingUrl(pMerise.getIssueTrackingUrl());
193 pDatabase.setIssueTrackingUrl(pMerise.getIssueTrackingUrl());
194 pDatabase.setAlternateProjectId(pMerise.getAlternateProjectId());
195 pDatabase.setParentProject(pMerise.getParentProject());
196 pDatabase.setDevelopers(pMerise.getDevelopers());
197 pDatabase.setBuild(pMerise.getBuild());
198 pDatabase.setContributors(pMerise.getContributors());
199 pDatabase.setDependencies(pMerise.getDependencies());
200 pDatabase.setLicenses(pMerise.getLicenses());
201 pDatabase.setMailingLists(pMerise.getMailingLists());
202 pDatabase.setProperties(pMerise.getProperties());
203 return pDatabase;
204 }
205 /***
206 * Construct a new 'Table' object
207 * @param pDatabase the database
208 * @param pObject the entity give'n as argument.
209 * @return the table
210 */
211 private static Table initTable(
212 final PhysicalModel pDatabase,
213 final Entity pObject)
214 {
215 Table t = new Table(pDatabase);
216 t.setName(pObject.getName());
217 t.setJavaName(pObject.getJavaName());
218 t.setCode(pObject.getCode());
219 t.setIdMethod(pObject.getIdMethod());
220 t.setSkipSql(pObject.getSkipSql());
221 t.setBaseClass(pObject.getBaseClass());
222 t.setBasePeer(pObject.getBasePeer());
223 t.setAlias(pObject.getAlias());
224 t.setJavaNamingMethod(pObject.getJavaNamingMethod());
225 t.setHeavyIndexing(pObject.getHeavyIndexing());
226 t.setDescription(pObject.getDescription());
227 t.setNotes(pObject.getNotes());
228 return t;
229 }
230 /***
231 * Rule 0: each entity become a table.
232 * Also act as Rules iteration starting points
233 *
234 * @param pDatabase the physical model
235 * @param pMerise the conceptual model
236 * @return the model
237 */
238 private static PhysicalModel applyRule00(
239 final PhysicalModel pDatabase,
240 final ConceptualModel pMerise)
241 {
242 Entity swapEntity;
243 Table swapTable;
244 for (int i = 0; i < pMerise.getEntities().size(); i++)
245 {
246 swapEntity = (Entity) pMerise.getEntities().elementAt(i);
247 swapTable = initTable(pDatabase, swapEntity);
248 swapEntity.setSubsequentTable(swapTable);
249 if (swapEntity.getData() != null)
250 {
251 swapTable.setData(new Vector(swapEntity.getData()));
252 }
253 swapTable.getObjectView().setLocation(
254 swapEntity.getObjectView().getLocation());
255 ((TableView) swapTable.getObjectView()).setOldLocation(
256 swapEntity.getObjectView().getLocation());
257 swapTable.getObjectView().setSize(
258 swapEntity.getObjectView().getSize());
259 ModelMan.addTable(pDatabase, swapTable);
260 }
261 for (int i = 0; i < pMerise.getAssociations().size(); i++)
262 {
263 switch (getCardType(pMerise.getAssociationAt(i)))
264 {
265 case ConceptualModel.ASSO_11 :
266 applyRule11(pDatabase, pMerise.getAssociationAt(i));
267 break;
268 case ConceptualModel.ASSO_1N :
269 applyRule1N(pDatabase, pMerise.getAssociationAt(i));
270 break;
271 case ConceptualModel.ASSO_NM :
272 applyRuleNN(pDatabase, pMerise.getAssociationAt(i));
273 break;
274 default :
275 logger.error("Impossible rule");
276 break;
277 }
278 }
279
280 for (int i = 0; i < pMerise.getInheritanceLinks().size(); i++)
281 {
282 InheritanceLink iLnk =
283 new InheritanceLink(
284 pMerise,
285 ((Entity) pMerise.getInheritanceLinkAt(i).getChildClass())
286 .getSubsequentTable(),
287 ((Entity) pMerise.getInheritanceLinkAt(i).getParentClass())
288 .getSubsequentTable());
289 ModelMan.addInheritanceLink(pDatabase, iLnk);
290 }
291 return pDatabase;
292 }
293 /***
294 * Rule 2 : In case of 1:1 relation, table must share the same key.
295 *
296 * @param theDatabase context model
297 * @param pAsso context association
298 */
299 private static void applyRule11(
300 final PhysicalModel theDatabase,
301 final Association pAsso)
302 {
303 int cardIs =
304 ((AssociationLink) pAsso.getAssociationLinks().elementAt(0))
305 .getCard();
306 int isCard =
307 ((AssociationLink) pAsso.getAssociationLinks().elementAt(1))
308 .getCard();
309
310
311 if (cardIs == ConceptualModel.ASSO_11
312 && isCard == ConceptualModel.ASSO_11)
313 {
314 applyFusion(theDatabase, pAsso);
315 }
316 else
317 {
318 applyRule11a(theDatabase, pAsso);
319 }
320 }
321 /***
322 * All the table of this association are going to be fusioned.
323 * this is most often due all _11_ relation in this association.
324 *
325 * @param pDatabase context model
326 * @param pAsso context association
327 */
328 private static void applyFusion(
329 final PhysicalModel pDatabase,
330 final Association pAsso)
331 {
332
333 for (int i = 0; i < pAsso.getData().size(); i++)
334 {
335 Column tmpColumn =
336 new Column((Column) pAsso.getData().elementAt(i));
337 pAsso.getEntityAt(0).getSubsequentTable().getData().addElement(
338 tmpColumn);
339 }
340
341 for (int j = 1; j < pAsso.countMyAssociationLinks(); j++)
342 {
343
344 for (int i = 0; i < pAsso.getEntityAt(j).getData().size(); i++)
345 {
346 Column tmpColumn =
347 new Column(
348 (Column) pAsso.getEntityAt(j).getData().elementAt(i));
349 pAsso.getEntityAt(0).getSubsequentTable().getData().addElement(
350 tmpColumn);
351 }
352
353 ModelMan.removeTable(
354 pDatabase,
355 pAsso.getEntityAt(j).getSubsequentTable());
356
357 String newCode =
358 pAsso.getEntityAt(0).getSubsequentTable().getCode()
359 + "_"
360 + pAsso.getEntityAt(j).getSubsequentTable().getCode();
361 pAsso.getEntityAt(0).getSubsequentTable().setCode(newCode);
362 pAsso.getEntityAt(0).getSubsequentTable().setName(newCode);
363 }
364 }
365 /***
366 * Rule 2 : In case of 1:1 relation, table must share the same key.
367 * this could have be donne differently, by reversing right/left but also
368 * by keeping 2 different primary key. IOn that case this can cause
369 * performance loss due to index multiplication.
370 *
371 * @param pDatabase context model
372 * @param pAsso context association
373 *
374 */
375 private static void applyRule11a(
376 final PhysicalModel pDatabase,
377 final Association pAsso)
378 {
379 Column oneId;
380 int cardIs =
381 ((AssociationLink) pAsso.getAssociationLinks().elementAt(0))
382 .getCard();
383 int isCard =
384 ((AssociationLink) pAsso.getAssociationLinks().elementAt(1))
385 .getCard();
386 int zeroSide = 0;
387 int oneSide = 1;
388 if (cardIs == ConceptualModel.ASSO_11)
389 {
390 oneSide = 0;
391 zeroSide = 1;
392 }
393 else if (isCard == ConceptualModel.ASSO_11)
394 {
395 oneSide = 1;
396 zeroSide = 0;
397 }
398
399 oneId = new Column((Column) pAsso.getEntityAt(oneSide).getIdentifier());
400 Column zeroId =
401 new Column((Column) pAsso.getEntityAt(zeroSide).getIdentifier());
402 Column sameId = new Column(oneId);
403 sameId.setPrimaryKey(false);
404
405 pAsso
406 .getEntityAt(zeroSide)
407 .getSubsequentTable()
408 .getData()
409 .removeElement(
410 zeroId);
411 pAsso.getEntityAt(zeroSide).getSubsequentTable().getData().addElement(
412 sameId);
413 Vector v1 = new Vector(1);
414 v1.add(oneId);
415 Vector v0 = new Vector(1);
416 v0.addElement(sameId);
417
418
419
420
421
422
423
424
425
426
427
428 Constraint newCst2 =
429 new Constraint(
430 pDatabase,
431 pAsso.getEntityAt(zeroSide).getSubsequentTable(),
432 v0,
433 pAsso.getEntityAt(oneSide).getSubsequentTable(),
434 v1);
435 ModelMan.addConstraint(pDatabase, newCst2);
436
437
438 for (int i = 0; i < pAsso.getData().size(); i++)
439 {
440 oneId = new Column((Column) pAsso.getData().elementAt(i));
441 if (isCard == ConceptualModel.ASSO_01
442 || cardIs == ConceptualModel.ASSO_01)
443 {
444 oneId.setRequired(false);
445 }
446 pAsso
447 .getEntityAt(oneSide)
448 .getSubsequentTable()
449 .getData()
450 .addElement(
451 oneId);
452 }
453 if (pAsso.getCardAt(oneSide) == ConceptualModel.ASSO_01
454 || pAsso.getCardAt(zeroSide) == ConceptualModel.ASSO_01)
455 {
456 oneId.setRequired(false);
457 }
458 else
459 {
460 oneId.setRequired(true);
461 }
462 }
463 /***
464 * This transformation rule deal with 1 --> * relation.
465 *
466 * Rule 3 : : In the case of entities connected by associations of the 1:n
467 * type, each table has its own key, but the key of the entity side 0,n (or
468 * 1,n) migrates towards the table side 0,1 (or 1,1) and becomes a foreign
469 * key (secondary index).
470 *
471 * @param pDatabase Database context
472 * @param pAsso Association to be treated
473 *
474 */
475 private static void applyRule1N(
476 final PhysicalModel pDatabase,
477 final Association pAsso)
478 {
479
480
481
482 int oneSide, nSide;
483 Column newField;
484
485
486
487 Constraint newCst = null;
488
489
490
491
492 if (pAsso.getCardAt(1) == ConceptualModel.ASSO_0N
493 || pAsso.getCardAt(1) == ConceptualModel.ASSO_1N)
494 {
495 oneSide = 0;
496 nSide = 1;
497 }
498 else
499 {
500 oneSide = 1;
501 nSide = 0;
502 }
503
504 Vector pIdent = pAsso.getEntityAt(nSide).getAllIdentifier();
505 for (int j = 0; j < pIdent.size(); j++)
506 {
507 newField = new Column((Column) pIdent.get(j));
508 pAsso
509 .getEntityAt(oneSide)
510 .getSubsequentTable()
511 .getData()
512 .addElement(
513 newField);
514 if (j == 0)
515 {
516 Vector vfk = new Vector();
517 vfk.addElement(pAsso.getEntityAt(nSide).getIdentifier());
518 Vector vlc = new Vector();
519 vlc.addElement(newField);
520 newCst =
521 new Constraint(
522 pDatabase,
523 pAsso.getEntityAt(oneSide).getSubsequentTable(),
524 vlc,
525 pAsso.getEntityAt(nSide).getSubsequentTable(),
526 vfk);
527 ModelMan.addConstraint(pDatabase, newCst);
528 }
529 newField.setPrimaryKey(false);
530 if (pAsso.getCardAt(oneSide) == ConceptualModel.ASSO_01)
531 {
532 newField.setRequired(false);
533 }
534 else
535 {
536 newField.setRequired(true);
537 }
538
539 newField.setAutoIncrement(false);
540 }
541
542
543 for (int i = 0; i < pAsso.getData().size(); i++)
544 {
545 pAsso
546 .getEntityAt(oneSide)
547 .getSubsequentTable()
548 .getData()
549 .addElement(
550 new Column((Column) pAsso.getData().elementAt(i)));
551 }
552 }
553 /***
554 * Rule 4 : In the case of entities connected by associations of the n:m
555 * type, an intermediate Table known as "Link Table", must be created, and
556 * must have as primary key a conjunction of the primary keys of the two
557 * tables for which it is connected.
558 *
559 * @param pDatabase The context database
560 * @param pAsso The association to be treated.
561 *
562 */
563 private static void applyRuleNN(
564 final PhysicalModel pDatabase,
565 final Association pAsso)
566 {
567 /***
568 * The table to be created.
569 */
570 Table newTable = new Table(pDatabase);
571 newTable.setName(pAsso.getName());
572 newTable.setCode(pAsso.getCode());
573 newTable.setName(pAsso.getName());
574 newTable.setJavaName(pAsso.getJavaName());
575 newTable.setCode(pAsso.getCode());
576 newTable.setIdMethod("none");
577 newTable.setSkipSql(pAsso.getSkipSql());
578
579 newTable.setBaseClass(pAsso.getBaseClass());
580 newTable.setBasePeer(pAsso.getBasePeer());
581 newTable.setAlias(pAsso.getAlias());
582 newTable.setJavaNamingMethod(pAsso.getJavaNamingMethod());
583 newTable.setHeavyIndexing(pAsso.getHeavyIndexing());
584 newTable.setDescription(pAsso.getDescription());
585 newTable.setDescription(pAsso.getDescription());
586 newTable.getObjectView().setLocation(
587 pAsso.getObjectView().getLocation());
588
589
590 for (int i = 0; i < pAsso.getData().size(); i++)
591 {
592 newTable.getData().addElement(
593 new Column((Column) pAsso.getData().elementAt(i)));
594 }
595 /***
596 * Column we create
597 */
598 Column newField;
599 /***
600 * Constraint to be created(s)
601 */
602 Constraint newCst = null;
603 for (int i = 0; i < pAsso.getAssociationLinks().size(); i++)
604 {
605 Vector pIdent = pAsso.getAllIdentifierAt(i);
606 for (int j = 0; j < pIdent.size(); j++)
607 {
608 newField = new Column((Column) pIdent.get(j));
609 newField.setPrimaryKey(true);
610
611 newField.setRequired(
612 (pAsso.getCardAt(i) == ConceptualModel.ASSO_11
613 || pAsso.getCardAt(i) == ConceptualModel.ASSO_1N));
614 newField.setAutoIncrement(false);
615 newTable.getData().addElement(newField);
616 if (j == 0)
617 {
618 Vector vlk = new Vector();
619 Vector vfk = new Vector();
620 vlk.addElement(newField);
621 vfk.addElement(pIdent.get(j));
622 newCst =
623 new Constraint(
624 pDatabase,
625 newTable,
626 vlk,
627 pAsso.getEntityAt(i).getSubsequentTable(),
628 vfk);
629 ModelMan.addConstraint(pDatabase, newCst);
630 }
631 }
632 }
633 ModelMan.addTable(pDatabase, newTable);
634 }
635 /***
636 * return the type of the relation (11,1N,1N)
637 * so we know wich Merise rule to apply for
638 * a given association (cardinalities)
639 *
640 * card_is 01 11 0N 1N (right)
641 * is_card --------------------------------
642 * 01 | 11 11 1N 1N
643 * 11 | 11 11 1N 1N
644 * 0N | 1N 1N NM NM
645 * 1N | 1N 1N NM NM
646 * (left)
647 * @param theAssociation the association to indentify
648 * @return the card type
649 */
650 public static int getCardType(final Association theAssociation)
651 {
652 int cardIs =
653 ((AssociationLink) theAssociation
654 .getAssociationLinks()
655 .elementAt(0))
656 .getCard();
657 int isCard =
658 ((AssociationLink) theAssociation
659 .getAssociationLinks()
660 .elementAt(1))
661 .getCard();
662 int[][] results =
663 {
664 {
665 ConceptualModel.ASSO_11,
666 ConceptualModel.ASSO_11,
667 ConceptualModel.ASSO_1N,
668 ConceptualModel.ASSO_1N },
669 {
670 ConceptualModel.ASSO_11,
671 ConceptualModel.ASSO_11,
672 ConceptualModel.ASSO_1N,
673 ConceptualModel.ASSO_1N },
674 {
675 ConceptualModel.ASSO_1N,
676 ConceptualModel.ASSO_1N,
677 ConceptualModel.ASSO_NM,
678 ConceptualModel.ASSO_NM },
679 {
680 ConceptualModel.ASSO_1N,
681 ConceptualModel.ASSO_1N,
682 ConceptualModel.ASSO_NM,
683 ConceptualModel.ASSO_NM }
684 };
685 int cardType = results[cardIs][isCard];
686
687 if (theAssociation.getAssociationLinks().size() > 2)
688 {
689 cardType = ConceptualModel.ASSO_NM;
690 }
691 return cardType;
692 }
693 }