001package org.andromda.metafacades.uml; 002 003import java.text.CharacterIterator; 004import java.text.StringCharacterIterator; 005import java.util.ArrayList; 006import java.util.Collection; 007import java.util.Collections; 008import java.util.LinkedHashSet; 009import java.util.List; 010import java.util.Random; 011import org.andromda.core.common.ExceptionUtils; 012import org.apache.commons.lang.StringEscapeUtils; 013import org.apache.commons.lang.StringUtils; 014import org.apache.log4j.Logger; 015 016/** 017 * Utilities for dealing with entity metafacades 018 * 019 * @author Chad Brandon 020 * @author Bob Fields 021 */ 022public class EntityMetafacadeUtils 023{ 024 /** 025 * The logger instance. 026 */ 027 private static final Logger LOGGER = Logger.getLogger(EntityMetafacadeUtils.class); 028 029 /** 030 * <p/> Converts a string following the Java naming conventions to a 031 * database attribute name. For example convert customerName to 032 * CUSTOMER_NAME. 033 * </p> 034 * 035 * @param modelElementName the string to convert 036 * @param separator character used to separate words 037 * @return string converted to database attribute format 038 */ 039 public static String toSqlName( 040 String modelElementName, 041 Object separator) 042 { 043 ExceptionUtils.checkEmpty( 044 "modelElementName", 045 modelElementName); 046 047 StringBuilder sqlName = new StringBuilder(); 048 StringCharacterIterator iterator = new StringCharacterIterator(StringUtils.uncapitalize(modelElementName)); 049 050 for (char character = iterator.first(); character != CharacterIterator.DONE; character = iterator.next()) 051 { 052 if (Character.isUpperCase(character)) 053 { 054 sqlName.append(separator); 055 } 056 character = Character.toUpperCase(character); 057 sqlName.append(character); 058 } 059 return StringEscapeUtils.escapeSql(sqlName.toString()); 060 } 061 062 /** 063 * Gets the SQL name. (i.e. column name, table name, etc.). If it can't find 064 * the corresponding tagged value with the specified <code>name</code>, 065 * then it uses the element name by default and just returns that. 066 * 067 * @param prefix the optional prefix to add to the sql name (i.e. table name 068 * prefix, etc.). 069 * @param element from which to retrieve the SQL name. 070 * @param name the name of the tagged value. 071 * @param nameMaxLength if this is not null, then the name returned will be 072 * trimmed to this length (if it happens to be longer). 073 * @param separator character used to separate words 074 * @param shortenSqlNamesMethod Method of shortening SQL Names, i.e. removeVowels 075 * @return the SQL name as a String. 076 */ 077 public static String getSqlNameFromTaggedValue( 078 String prefix, 079 ModelElementFacade element, 080 String name, 081 Short nameMaxLength, 082 Object separator, 083 Object shortenSqlNamesMethod) 084 { 085 return getSqlNameFromTaggedValue( 086 prefix, 087 element, 088 name, 089 nameMaxLength, 090 null, 091 separator, 092 shortenSqlNamesMethod); 093 } 094 095 /** 096 * Gets the SQL name. (i.e. column name, table name, etc.). If it can't find 097 * the corresponding tagged value with the specified <code>name</code>, 098 * then it uses the element name by default and just returns that. 099 * 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}