1 package org.andromda.metafacades.uml;
2
3 import java.text.CharacterIterator;
4 import java.text.StringCharacterIterator;
5 import java.util.ArrayList;
6 import java.util.Collection;
7 import java.util.Collections;
8 import java.util.LinkedHashSet;
9 import java.util.List;
10 import java.util.Random;
11 import org.andromda.core.common.ExceptionUtils;
12 import org.apache.commons.lang.StringEscapeUtils;
13 import org.apache.commons.lang.StringUtils;
14 import org.apache.log4j.Logger;
15
16 /**
17 * Utilities for dealing with entity metafacades
18 *
19 * @author Chad Brandon
20 * @author Bob Fields
21 */
22 public class EntityMetafacadeUtils
23 {
24 /**
25 * The logger instance.
26 */
27 private static final Logger LOGGER = Logger.getLogger(EntityMetafacadeUtils.class);
28
29 /**
30 * <p/> Converts a string following the Java naming conventions to a
31 * database attribute name. For example convert customerName to
32 * CUSTOMER_NAME.
33 * </p>
34 *
35 * @param modelElementName the string to convert
36 * @param separator character used to separate words
37 * @return string converted to database attribute format
38 */
39 public static String toSqlName(
40 String modelElementName,
41 Object separator)
42 {
43 ExceptionUtils.checkEmpty(
44 "modelElementName",
45 modelElementName);
46
47 StringBuilder sqlName = new StringBuilder();
48 StringCharacterIterator iterator = new StringCharacterIterator(StringUtils.uncapitalize(modelElementName));
49
50 for (char character = iterator.first(); character != CharacterIterator.DONE; character = iterator.next())
51 {
52 if (Character.isUpperCase(character))
53 {
54 sqlName.append(separator);
55 }
56 character = Character.toUpperCase(character);
57 sqlName.append(character);
58 }
59 return StringEscapeUtils.escapeSql(sqlName.toString());
60 }
61
62 /**
63 * Gets the SQL name. (i.e. column name, table name, etc.). If it can't find
64 * the corresponding tagged value with the specified <code>name</code>,
65 * then it uses the element name by default and just returns that.
66 *
67 * @param prefix the optional prefix to add to the sql name (i.e. table name
68 * prefix, etc.).
69 * @param element from which to retrieve the SQL name.
70 * @param name the name of the tagged value.
71 * @param nameMaxLength if this is not null, then the name returned will be
72 * trimmed to this length (if it happens to be longer).
73 * @param separator character used to separate words
74 * @param shortenSqlNamesMethod Method of shortening SQL Names, i.e. removeVowels
75 * @return the SQL name as a String.
76 */
77 public static String getSqlNameFromTaggedValue(
78 String prefix,
79 ModelElementFacade element,
80 String name,
81 Short nameMaxLength,
82 Object separator,
83 Object shortenSqlNamesMethod)
84 {
85 return getSqlNameFromTaggedValue(
86 prefix,
87 element,
88 name,
89 nameMaxLength,
90 null,
91 separator,
92 shortenSqlNamesMethod);
93 }
94
95 /**
96 * Gets the SQL name. (i.e. column name, table name, etc.). If it can't find
97 * the corresponding tagged value with the specified <code>name</code>,
98 * then it uses the element name by default and just returns that.
99 *
100 * @param element from which to retrieve the SQL name.
101 * @param name the name of the tagged value.
102 * @param nameMaxLength if this is not null, then the name returned will be
103 * trimmed to this length (if it happens to be longer).
104 * @param suffix the optional suffix to add to the sql name (i.e. foreign
105 * key suffix, etc.)
106 * @param separator character used to separate words
107 * @param shortenSqlNamesMethod Method of shortening SQL Names, i.e. removeVowels
108 * @return the SQL name as a String.
109 */
110 public static String getSqlNameFromTaggedValue(
111 ModelElementFacade element,
112 String name,
113 Short nameMaxLength,
114 String suffix,
115 Object separator,
116 Object shortenSqlNamesMethod)
117 {
118 return getSqlNameFromTaggedValue(
119 null,
120 element,
121 name,
122 nameMaxLength,
123 suffix,
124 separator,
125 shortenSqlNamesMethod);
126 }
127
128 /**
129 * Gets the SQL name. (i.e. column name, table name, etc.). If it can't find
130 * the corresponding tagged value with the specified <code>name</code>,
131 * then it uses the element name by default and just returns that.
132 *
133 * @param element from which to retrieve the SQL name.
134 * @param name the name of the tagged value.
135 * @param nameMaxLength if this is not null, then the name returned will be
136 * trimmed to this length (if it happens to be longer).
137 * @param separator character used to separate words
138 * @param shortenSqlNamesMethod Method of shortening SQL Names, i.e. removeVowels
139 * @return the SQL name as a String.
140 */
141 public static String getSqlNameFromTaggedValue(
142 ModelElementFacade element,
143 String name,
144 Short nameMaxLength,
145 Object separator,
146 Object shortenSqlNamesMethod)
147 {
148 return getSqlNameFromTaggedValue(
149 null,
150 element,
151 name,
152 nameMaxLength,
153 null,
154 separator,
155 shortenSqlNamesMethod);
156 }
157
158 /**
159 * Gets the SQL name. (i.e. column name, table name, etc.). If it can't find
160 * the corresponding tagged value with the specified <code>name</code>,
161 * then it uses the element name by default and just returns that.
162 *
163 * @param prefix the optional prefix to add to the sql name (i.e. table name
164 * prefix, etc.).
165 * @param element from which to retrieve the SQL name.
166 * @param name the name of the tagged value.
167 * @param nameMaxLength if this is not null, then the name returned will be
168 * trimmed to this length (if it happens to be longer).
169 * @param suffix the optional suffix to add to the sql name (i.e. foreign
170 * key suffix, etc.)
171 * @param separator character used to separate words
172 * @param shortenSqlNamesMethod Method of shortening SQL Names, i.e. removeVowels
173 * @return the SQL name as a String.
174 */
175 public static String getSqlNameFromTaggedValue(
176 String prefix,
177 final ModelElementFacade element,
178 String name,
179 final Short nameMaxLength,
180 String suffix,
181 final Object separator,
182 final Object shortenSqlNamesMethod)
183 {
184 if (element != null)
185 {
186 Object value = element.findTaggedValue(name);
187 StringBuilder buffer = new StringBuilder(StringUtils.trimToEmpty((String)value));
188 if (StringUtils.isEmpty(buffer.toString()))
189 {
190 // if we can't find the tagValue then use the
191 // element name for the name
192 buffer = new StringBuilder(toSqlName(
193 element.getName(),
194 separator));
195 suffix = StringUtils.trimToEmpty(suffix);
196 prefix = StringUtils.trimToEmpty(prefix);
197 if (nameMaxLength != null)
198 {
199 final short maxLength = (short)(nameMaxLength.shortValue() - suffix.length() - prefix.length());
200 buffer =
201 new StringBuilder(
202 EntityMetafacadeUtils.ensureMaximumNameLength(
203 buffer.toString(),
204 Short.valueOf(maxLength),
205 (String)shortenSqlNamesMethod));
206 }
207 if (StringUtils.isNotBlank(prefix))
208 {
209 buffer.insert(
210 0,
211 prefix);
212 }
213 if (StringUtils.isNotBlank(suffix))
214 {
215 buffer.append(suffix);
216 }
217 }
218 name = buffer.toString();
219 }
220 return name;
221 }
222
223 private static List<Entity> sortedEntities;
224 /**
225 * Puts non-abstract entities in order based on associations. To be used in constructors and
226 * tests so that entities may be created and deleted in the proper order, without
227 * violating key constraints in the database
228 *
229 * @param entities the entity list to be sorted.
230 * @param ascending true for entities with no associations first.
231 * @return the sorted entity list.
232 */
233 public static List<Entity> sortEntities(
234 final Collection<Entity> entities,
235 boolean ascending)
236 {
237 // Initially holds entities with no outgoing relations. Add related entities to the end
238 // Multiple andromda invocations - entity list is cached - check size
239 List<Entity> sorted = new ArrayList<Entity>();
240 if (sortedEntities==null)
241 {
242 sortedEntities = new ArrayList<Entity>();
243 }
244 if (entities == null || entities.isEmpty() ||
245 (sortedEntities!=null && !sortedEntities.isEmpty() && sortedEntities.size() == entities.size()
246 && sortedEntities.contains(entities.iterator().next())))
247 {
248 sorted = sortedEntities;
249 }
250 else
251 {
252 // Clear left-over entities from last time.
253 sortedEntities.clear();
254 // Move entities into the sorted list step by step
255 List<Entity> toBeMoved = new ArrayList<Entity>();
256 List<Entity> unsorted = new ArrayList<Entity>();
257 for (Entity entity : entities)
258 {
259 // Remove abstract entities from the list: We won't be testing or creating abstract entities
260 if (entity.isAbstract())
261 {
262 toBeMoved.add(entity);
263 }
264 else
265 {
266 //Collection<AssociationEndFacade> ends = entity.getNavigableConnectingEnds();
267 //System.out.println(entity.getName() + " Nav ends=" + ends.size());
268 // Put the entities with no associations first in the sorted list
269 if (entity.getNavigableConnectingEnds().size()==0)
270 {
271 sorted.add(entity);
272 //System.out.println(entity.getName() + " No associations");
273 }
274 else
275 {
276 unsorted.add(entity);
277 }
278 }
279 }
280 // Prevent ConcurrentModificationException by using a temp removal list
281 for (Entity entity : toBeMoved)
282 {
283 entities.remove(entity);
284 }
285 /*// Holds entities with relations after first pass. Second pass sorts the entities
286 for (Entity entity : entities)
287 {
288 Collection<AssociationEndFacade> ends = entity.getNavigableConnectingEnds();
289 //System.out.println(entity.getName() + " Nav ends=" + ends.size());
290 // Put the entities with no associations first in the sorted list
291 if (ends.size()==0)
292 {
293 sorted.add(entity);
294 //System.out.println(entity.getName() + " No associations");
295 }
296 else
297 {
298 unsorted.add(entity);
299 }
300 }*/
301 /*for (Entity entity : sorted)
302 {
303 System.out.println(entity.getName() + " Sorted First");
304 }
305 String moved = " ToBeMoved: ";
306 for (Entity entity : unsorted)
307 {
308 moved += entity.getName() + " ";
309 }
310 System.out.println(sorted.size() + " Sorted " + unsorted.size() + " Unsorted " + moved);*/
311 // Work backwards from unsorted list, moving entities to sorted list until none remain
312 // Since all associations must be owned by one side, all will eventually be moved to sorted list
313 int moveCount = 1; // Stop looping if no more to be moved, prevent infinite loop on circular relationships
314 while (unsorted.size() > 0 && moveCount > 0)
315 {
316 toBeMoved = new ArrayList<Entity>();
317 for (Entity entity : unsorted)
318 {
319 Collection<AssociationEndFacade> ends = entity.getNavigableConnectingEnds();
320 // See if any navigable connecting ends are not owned by this entity
321 boolean createFirst = true;
322 for (AssociationEndFacade end : ends)
323 {
324 ClassifierFacade entityEnd = end.getType();
325 // Owning relations are sorted after entities on the opposite end
326 int referencedPosition = unsorted.lastIndexOf(entityEnd);
327 //System.out.println(entity.getName() + " -> " + entityEnd.getName() + " refPos=" + referencedPosition + " isOwningEnd " + UMLMetafacadeUtils.isOwningEnd(end));
328 if (referencedPosition > -1 && UMLMetafacadeUtils.isOwningEnd(end) && !entityEnd.getFullyQualifiedName().equals(entity.getFullyQualifiedName()))
329 {
330 // Other Entity side of an owned relationship must be created first
331 createFirst = false;
332 break;
333 }
334 }
335 if (createFirst)
336 {
337 toBeMoved.add(entity);
338 //System.out.println(entity.getName() + " Added to toBeMoved");
339 }
340 }
341 moveCount = toBeMoved.size();
342 /*moved = " ToBeMoved: ";
343 for (Entity entity : toBeMoved)
344 {
345 moved += entity.getName() + " ";
346 }*/
347 //System.out.println(sorted.size() + " Sorted " + unsorted.size() + " Unsorted " + toBeMoved.size() + moved);
348 for (Entity entity : toBeMoved)
349 {
350 unsorted.remove(entity);
351 sorted.add(entity);
352 }
353 }
354 if (moveCount==0 && unsorted.size() > 0)
355 {
356 // There are unresolvable circular relationships
357 String circular = "Circular relationships between Entities:";
358 for (Entity entity : unsorted)
359 {
360 circular += " " + entity.getName();
361 }
362 LOGGER.error(circular);
363 // Add them to the sorted list anyway and hope for the best...
364 }
365 /*int moves = -1;
366 for (Entity entity : unsorted)
367 {
368 if (!entity.isAbstract())
369 {
370 Collection<AssociationEndFacade> ends = entity.getAssociationEnds();
371 System.out.println(entity.getName() + " Sorting " + ends.size() + " Ends");
372 // Test each association to see if incoming or outgoing. sort outgoing before incoming.
373 for (AssociationEndFacade end : ends)
374 {
375 AssociationEndFacade otherEnd = end.getOtherEnd();
376 if (otherEnd.getType() instanceof Entity)
377 {
378 // otherEnd is actually the association/type on this entity
379 Entity entityEnd = (Entity)otherEnd.getType();
380 // Incoming relations are sorted after other entities
381 // Aggregation and Composition always owns the end
382 // One to Many association many end comes last
383 int thisPosition = unsorted.lastIndexOf(entity);
384 int referencedPosition = unsorted.lastIndexOf(entityEnd);
385 // Determine if this end is the relationship owner
386 //boolean primary = BooleanUtils.toBoolean(
387 // ObjectUtils.toString(end.findTaggedValue("andromda_persistence_associationEnd_primary")));
388 System.out.println(entity.getName() + "=" + thisPosition + " " + entityEnd.getName() + "=" + referencedPosition + " prop=" + otherEnd.getName() + " owned=" + isOwningEnd(otherEnd));
389 //System.out.println(entity.getName() + "=" + thisPosition + " " + entityEnd.getName() + "=" + referencedPosition + " prop=" + end.getName() + " AggE=" + end.isAggregation() + " CompE=" + end.isComposition() + " OAgg=" + otherEnd.isAggregation() + " OComp=" + otherEnd.isComposition() + " Many=" + end.isMany() + " One2One=" + end.isOne2One() + " end=" + end + " other=" + otherEnd);
390 // This owning end should be created after the other side Entity
391 if (thisPosition > -1 && referencedPosition > -1)
392 {
393 if (isOwningEnd(otherEnd) && thisPosition < referencedPosition)
394 {
395 // Move the locations of the two List entries if referenced entity is higher in the list
396 // Avoid ConcurrentModificationEx by operating on temp List
397 unsorted.remove(entityEnd);
398 unsorted.add(unsorted.lastIndexOf(entity), entityEnd);
399 System.out.println(entityEnd.getName() + " moved in front of " + entity.getName());
400 moves += 1;
401 }
402 else if (!isOwningEnd(otherEnd) && thisPosition > referencedPosition)
403 {
404 // Move the locations of the two List entries if referenced entity is higher in the list
405 unsorted.remove(end);
406 unsorted.add(unsorted.lastIndexOf(entityEnd), entity);
407 System.out.println(entity.getName() + " moved behind " + entityEnd.getName());
408 moves += 1;
409 }
410 }
411 }
412 }
413 }
414 }*/
415 sorted.addAll(unsorted);
416 sortedEntities = sorted;
417 }
418 if (!ascending)
419 {
420 Collections.reverse(sorted);
421 }
422 return sorted;
423 }
424
425 /**
426 * Gets the execution priority of a specific entity, based on dependency/owning relationships,
427 * so that TestNG unit tests can be put in the proper order across all entities and CRUD methods
428 *
429 * @param entity the entity to be prioritized.
430 * @return int the entity priority.
431 */
432 public static int getPriority(
433 final Entity entity)
434 {
435 int priority = 0;
436 if (sortedEntities!=null && !sortedEntities.isEmpty())
437 {
438 priority = sortedEntities.indexOf(entity);
439 if (priority < 1)
440 {
441 priority = 0;
442 }
443 else
444 {
445 // Change from zero based to one based, allow 10 additional test methods between the standard tests.
446 priority = priority*10 + 10;
447 }
448 }
449 return priority;
450 }
451
452 /**
453 * <p/> Trims the passed in value to the maximum name length.
454 * </p>
455 * If no maximum length has been set then this method does nothing.
456 *
457 * @param name the name length to check and trim if necessary
458 * @param nameMaxLength if this is not null, then the name returned will be
459 * trimmed to this length (if it happens to be longer).
460 * @param method Method of shortening SQL Names, i.e. removeVowels
461 * @return String the string to be used as SQL type
462 */
463 public static String ensureMaximumNameLength(
464 String name,
465 Short nameMaxLength,
466 String method)
467 {
468 if (StringUtils.isNotBlank(name) && nameMaxLength != null)
469 {
470 short max = nameMaxLength.shortValue();
471 if(method != null && method.equalsIgnoreCase(UMLMetafacadeProperties.SHORTEN_SQL_NAMES_METHOD_REMOVE_VOWELS))
472 {
473 while(name.length() > max)
474 {
475 final String[] vowels = new String[]{"A","E","I","O","U","a","e","i","o","u"};
476 final int lastVowelPos = StringUtils.lastIndexOfAny(name, vowels);
477 if(lastVowelPos == -1)
478 {
479 break; //no more vowels
480 }
481 else
482 {
483 name = name.substring(0,lastVowelPos)+name.substring(lastVowelPos+1);
484 }
485 }
486 }
487
488 if (name.length() > max)
489 {
490 name = name.substring(
491 0,
492 max);
493 }
494 }
495 return name;
496 }
497
498 /**
499 * Gets all identifiers for an entity. If 'follow' is true, and if
500 * no identifiers can be found on the entity, a search up the
501 * inheritance chain will be performed, and the identifiers from
502 * the first super class having them will be used. If no
503 * identifiers exist, a default identifier will be created if the
504 * allowDefaultIdentifiers property is set to true.
505 *
506 * @param entity the entity for which to retrieve the identifiers
507 * @param follow a flag indicating whether or not the inheritance hierarchy
508 * should be followed
509 * @return the collection of entity identifier attributes.
510 */
511 public static Collection<ModelElementFacade> getIdentifiers(
512 final Entity entity,
513 final boolean follow)
514 {
515 //final Collection<ModelElementFacade> properties = entity.getAllProperties();
516 final Collection<ModelElementFacade> identifiers = new ArrayList<ModelElementFacade>();
517 // TODO Entity.getAttributes returns List<? extends AttributeFacade>, currently unchecked conversion
518 final Collection<AttributeFacade> attributes = new ArrayList<AttributeFacade>(entity.getAttributes());
519 MetafacadeUtils.filterByStereotype(
520 attributes,
521 UMLProfile.STEREOTYPE_IDENTIFIER);
522 identifiers.addAll(attributes);
523 final Collection<AssociationEndFacade> associations = new ArrayList<AssociationEndFacade>(entity.getNavigableConnectingEnds(follow));
524 MetafacadeUtils.filterByStereotype(
525 associations,
526 UMLProfile.STEREOTYPE_IDENTIFIER);
527 identifiers.addAll(associations);
528 /*MetafacadeUtils.filterByStereotype(
529 properties,
530 UMLProfile.STEREOTYPE_IDENTIFIER);*/
531 /*// Find identifiers of association identifiers otherEnd
532 for (AssociationEndFacade associationEnd : associations)
533 {
534 ClassifierFacade classifier = associationEnd.getType();
535 if (classifier instanceof Entity)
536 {
537 Collection<ModelElementFacade> entityIdentifiers = getIdentifiers((Entity)classifier, true);
538 identifiers.addAll(entityIdentifiers);
539 }
540 } */
541 // Reorder join columns if order is specified - must match FK column order
542 final Collection<ModelElementFacade> sortedIdentifiers = new ArrayList<ModelElementFacade>();
543 String joinOrder = (String)entity.findTaggedValue(UMLProfile.TAGGEDVALUE_PERSISTENCE_JOINCOLUMN_ORDER);
544 if (StringUtils.isNotBlank(joinOrder))
545 {
546 //System.out.println(entity.getName() + " getIdentifiers " + joinOrder + " identifiers=" + identifiers.size());
547 String[] joinList = StringUtils.split(joinOrder, " ,;|");
548 for (String column : joinList)
549 {
550 for (ModelElementFacade facade : identifiers)
551 {
552 if (facade instanceof EntityAssociationEnd)
553 {
554 EntityAssociationEnd assoc = (EntityAssociationEnd)facade;
555 if (assoc.getColumnName().equalsIgnoreCase(column))
556 {
557 sortedIdentifiers.add(assoc);
558 //System.out.println(entity.getName() + " added " + assoc);
559 }
560 }
561 else if (facade instanceof EntityAttribute)
562 {
563 EntityAttribute attr = (EntityAttribute)facade;
564 if (attr.getColumnName().equalsIgnoreCase(column))
565 {
566 sortedIdentifiers.add(attr);
567 //System.out.println(entity.getName() + " added " + attr);
568 }
569 }
570 }
571 }
572 //System.out.println(entity.getName() + " getIdentifiers " + joinOrder + " sorted=" + sortedIdentifiers.size() + " " + identifiers.size());
573 // Add remaining identifiers not found in joincolumn ordered list
574 // .contains() does not work correctly for EntityAttribute
575 for (ModelElementFacade facade : identifiers)
576 {
577 boolean contains = false;
578 for (ModelElementFacade sorted : sortedIdentifiers)
579 {
580 if (sorted.getFullyQualifiedName().equals(facade.getFullyQualifiedName()))
581 {
582 contains = true;
583 //System.out.println(entity.getName() + " contains " + facade.getName() + " facade=" + facade);
584 }
585 }
586 if (!contains)
587 {
588 sortedIdentifiers.add(facade);
589 //System.out.println(entity.getName() + " added " + facade.getName() + " facade=" + facade);
590 }
591 }
592 //System.out.println(entity.getName() + " getIdentifiers " + joinOrder + " sorted=" + sortedIdentifiers.size() + " " + + identifiers.size());
593 }
594 else
595 {
596 sortedIdentifiers.addAll(identifiers);
597 }
598 if (sortedIdentifiers.isEmpty() && follow && entity.getGeneralization() instanceof Entity)
599 {
600 sortedIdentifiers.addAll(getIdentifiers((Entity)entity.getGeneralization(), follow));
601 }
602 return sortedIdentifiers;
603 }
604
605 /**
606 * Gets all identifier attributes for an entity. If 'follow' is true, and if
607 * no identifiers can be found on the entity, a search up the
608 * inheritance chain will be performed, and the identifiers from
609 * the first super class having them will be used. All identifier association
610 * relationships are traversed to find the identifying attributes of the related
611 * associationEnd classifiers, in addition to the primitive/wrapped identifiers
612 * on the Entity itself.
613 *
614 * @param entity the entity for which to retrieve the identifiers
615 * @param follow a flag indicating whether or not the inheritance hierarchy
616 * should be followed
617 * @return the collection of entity identifier attributes.
618 */
619 public static Collection<ModelElementFacade> getIdentifierAttributes(
620 final Entity entity,
621 final boolean follow)
622 {
623 Collection<ModelElementFacade> identifierAttributes = new ArrayList<ModelElementFacade>();
624 final Collection<ModelElementFacade> identifiers = EntityMetafacadeUtils.getIdentifiers(entity, follow);
625 // Find identifiers of association identifiers otherEnd
626 for (ModelElementFacade identifier : identifiers)
627 {
628 if (identifier instanceof AssociationEndFacade)
629 {
630 AssociationEndFacade associationEnd = (AssociationEndFacade)identifier;
631 ClassifierFacade classifier = associationEnd.getType();
632 if (classifier instanceof Entity)
633 {
634 Collection<ModelElementFacade> entityIdentifiers = getIdentifierAttributes((Entity)classifier, true);
635 identifierAttributes.addAll(entityIdentifiers);
636 }
637 }
638 else
639 {
640 identifierAttributes.add(identifier);
641 }
642 }
643 if (identifiers.isEmpty() && follow && entity.getGeneralization() instanceof Entity)
644 {
645 identifierAttributes.addAll(getIdentifiers((Entity)entity.getGeneralization(), follow));
646 }
647 // Reorder join columns if order is specified - must match FK column order
648 String joinOrder = (String)entity.findTaggedValue("andromda_persistence_joincolumn_order");
649 /*System.out.println(entity.getName() + " getIdentifierAttributes " + joinOrder + " tags=" + entity.getTaggedValues().size() + " identifierAttr=" + identifierAttributes.size());
650 if (entity.getTaggedValues().size() > 1)
651 {
652 for ( TaggedValueFacade value : entity.getTaggedValues())
653 {
654 System.out.println(entity.getName() + " tag name=" + value.getName() + " value=" + value);
655 }
656 }*/
657 if (StringUtils.isNotBlank(joinOrder))
658 {
659 final Collection<ModelElementFacade> sortedIdentifiers = new ArrayList<ModelElementFacade>();
660 String[] joinList = StringUtils.split(joinOrder, " ,;|");
661 for (String column : joinList)
662 {
663 for (ModelElementFacade facade : identifierAttributes)
664 {
665 if (facade instanceof EntityAssociationEnd)
666 {
667 EntityAssociationEnd assoc = (EntityAssociationEnd)facade;
668 if (assoc.getColumnName().equalsIgnoreCase(column))
669 {
670 sortedIdentifiers.add(assoc);
671 }
672 }
673 else if (facade instanceof EntityAttribute)
674 {
675 EntityAttribute attr = (EntityAttribute)facade;
676 if (attr.getColumnName().equalsIgnoreCase(column))
677 {
678 sortedIdentifiers.add(attr);
679 }
680 }
681 }
682 }
683 //System.out.println(entity.getName() + " getIdentifierAttributes " + joinOrder + identifiers.size());
684 // Add remaining identifiers not found in joincolumn ordered list
685 if (sortedIdentifiers.size() < identifierAttributes.size())
686 for (ModelElementFacade facade : identifierAttributes)
687 {
688 if (!sortedIdentifiers.contains(facade))
689 {
690 sortedIdentifiers.add(facade);
691 }
692 }
693 identifierAttributes = sortedIdentifiers;
694 }
695 else
696 {
697 identifiers.addAll(identifierAttributes);
698 }
699 return identifierAttributes;
700 }
701
702 /**
703 * Constructs a sql type name from the given <code>mappedName</code> and
704 * <code>columnLength</code>.
705 *
706 * @param typeName the actual type name (usually retrieved from a mappings
707 * file, i.e. NUMBER(19).
708 * @param columnLength the length of the column.
709 * @return the new name construct
710 */
711 public static String constructSqlTypeName(
712 final String typeName,
713 final String columnLength)
714 {
715 String value = typeName;
716 if (StringUtils.isNotBlank(typeName))
717 {
718 final char beginChar = '(';
719 final char endChar = ')';
720 final int beginIndex = value.indexOf(beginChar);
721 final int endIndex = value.indexOf(endChar);
722 if (beginIndex != -1 && endIndex != -1 && endIndex > beginIndex)
723 {
724 String replacement = value.substring(
725 beginIndex,
726 endIndex) + endChar;
727 value = StringUtils.replace(
728 value,
729 replacement,
730 beginChar + columnLength + endChar);
731 }
732 else
733 {
734 value = value + beginChar + columnLength + endChar;
735 }
736 }
737 return value;
738 }
739
740 /**
741 * Constructs and returns the foreign key constraint name for the given <code>associationEnd</code>, <code>suffix</code>, <code>sqlNameSeparator</code>
742 * and <code>maxLengthProperty</code>.
743 *
744 * @param associationEnd the association end for which to construct the constraint name.
745 * @param suffix the suffix appended to the constraint name (if not limited by length).
746 * @param sqlNameSeparator the SQL name separator to use (i.e. '_').
747 * @param maxLengthProperty the numeric value stored as a string indicating the max length the constraint may be.
748 * @param shortenSqlNamesMethod Method of shortening SQL Names, i.e. removeVowels
749 * @return the constructed foreign key constraint name.
750 */
751 public static String getForeignKeyConstraintName(EntityAssociationEnd associationEnd, String suffix, String sqlNameSeparator, String maxLengthProperty, String shortenSqlNamesMethod)
752 {
753 String constraintName;
754
755 final Object taggedValueObject = associationEnd.findTaggedValue(
756 UMLProfile.TAGGEDVALUE_PERSISTENCE_FOREIGN_KEY_CONSTRAINT_NAME);
757 if (taggedValueObject == null)
758 {
759 // we construct our own foreign key constraint name here
760 StringBuilder buffer = new StringBuilder();
761
762 final ClassifierFacade type = associationEnd.getOtherEnd().getType();
763 if (type instanceof Entity)
764 {
765 Entity entity = (Entity)type;
766 buffer.append(entity.getTableName());
767 }
768 else
769 {
770 // should not happen
771 buffer.append(type.getName().toUpperCase());
772 }
773
774 buffer.append(sqlNameSeparator);
775 buffer.append(associationEnd.getColumnName());
776 constraintName = buffer.toString();
777
778 // we take into consideration the maximum length allowed
779 final short maxLength = (short)(Short.valueOf(maxLengthProperty).shortValue() - suffix.length());
780 buffer = new StringBuilder(EntityMetafacadeUtils.ensureMaximumNameLength(constraintName, Short.valueOf(maxLength), shortenSqlNamesMethod));
781 buffer.append(suffix);
782 constraintName = EntityMetafacadeUtils.getUniqueForeignKeyConstraintName(buffer.toString());
783 }
784 else
785 {
786 // use the tagged value
787 constraintName = taggedValueObject.toString();
788 }
789 return constraintName;
790 }
791
792 /**
793 * An internal static cache for foreign key names (allows us to keep track
794 * of which ones have been used). Its not great that its static, but for now
795 * this is the easiest way to enforce this.
796 */
797 private static Collection<String> foreignKeyConstraintNameCache = new ArrayList<String>();
798
799 /**
800 * Retrieves a unique foreign key constraint name given the proposedName. Compares the proposedName
801 * against any foreign key names already stored in an internal collection.
802 *
803 * @param proposedName the proposed foreign key name.
804 * @return the unique foreign key name.
805 */
806 private static String getUniqueForeignKeyConstraintName(String proposedName)
807 {
808 final char[] characters = proposedName.toCharArray();
809 int numericValue = 0;
810 for (int ctr = 0; ctr < characters.length; ctr++)
811 {
812 numericValue = numericValue + Character.getNumericValue(characters[0]);
813 }
814 return getUniqueForeignKeyConstraintName(proposedName, new Random(numericValue));
815 }
816
817 /**
818 * Retrieves a unique foreign key constraint name given the proposedName. Compares the proposedName
819 * against any foreign key names already stored in an internal collection.
820 *
821 * @param proposedName the proposed foreign key name.
822 * @param random the Random number generator to use for enforcing uniqueness.
823 * @return the unique foreign key name.
824 */
825 private static String getUniqueForeignKeyConstraintName(String proposedName, final Random random)
826 {
827 String name;
828 if (foreignKeyConstraintNameCache.contains(proposedName))
829 {
830 final char[] characters = proposedName.toCharArray();
831 int randomInt = random.nextInt(characters.length);
832 char randomChar = Character.toUpperCase(characters[randomInt]);
833 proposedName = proposedName.substring(0, proposedName.length() - 1) + randomChar;
834 name = getUniqueForeignKeyConstraintName(proposedName, random);
835 }
836 else
837 {
838 name = proposedName;
839 foreignKeyConstraintNameCache.add(name);
840 }
841 return name;
842 }
843
844 /**
845 * Clears out the foreign key cache.
846 */
847 public static void clearForeignKeyConstraintNameCache()
848 {
849 foreignKeyConstraintNameCache.clear();
850 }
851
852 /**
853 * Finds the top level package in the model containing any classes or entity classes.
854 * Can be used to define a groupId for a model, or in code generation scripts where top level package
855 * is the starting point for further operations.
856 * @param classifiers Entities in the current model
857 * @param entityOnly true to find the top package containing entities
858 * @return Package at the top level
859 */
860 public static PackageFacade getTopLevelPackage(LinkedHashSet<ClassifierFacade> classifiers, boolean entityOnly)
861 {
862 PackageFacade pkgFacade = null;
863 if (classifiers == null || classifiers.isEmpty()) return pkgFacade;
864 //System.out.println("getTopLevelPackage classifiers=" + classifiers.size());
865 List<PackageFacade> packages = new ArrayList<PackageFacade>();
866 for (ClassifierFacade classifier : classifiers)
867 {
868 //System.out.println("getTopLevelPackage classifier=" + classifier);
869 if (!classifier.isDataType())
870 {
871 if (!entityOnly || classifier instanceof Entity)
872 {
873 if (classifier.getStereotypeNames().size() > 0 && !packages.contains(classifier.getPackage()))
874 {
875 packages.add((PackageFacade)classifier.getPackage());
876 //System.out.println("getTopLevelPackage add " + ((PackageFacade)classifier.getPackage()).getFullyQualifiedName() + " package=" + classifier.getPackage());
877 }
878 }
879 }
880 }
881 if (packages.size()>0)
882 {
883 pkgFacade = packages.get(0);
884 // Find the shortest name in package list containing the other names
885 for (PackageFacade pkg : packages)
886 {
887 //System.out.println("getTopLevelPackage fqn=" + pkgFacade.getFullyQualifiedName() + " " + pkg.getFullyQualifiedName());
888 if (pkgFacade.getFullyQualifiedName().indexOf(pkg.getFullyQualifiedName()) > 0)
889 {
890 pkgFacade = pkg;
891 }
892 else if (pkg.getFullyQualifiedName().indexOf(pkgFacade.getFullyQualifiedName()) > 0)
893 {
894 // Shortest name is there already
895 }
896 else
897 {
898
899 }
900 }
901 //System.out.println("getTopLevelPackage pkgFacade=" + pkgFacade.getFullyQualifiedName());
902 }
903 return pkgFacade;
904 }
905 }