View Javadoc
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 }