001package org.andromda.metafacades.uml; 002 003import java.security.MessageDigest; 004import java.security.NoSuchAlgorithmException; 005import java.text.Collator; 006import java.util.Collection; 007import java.util.Collections; 008import java.util.Comparator; 009import java.util.List; 010import org.apache.commons.collections.CollectionUtils; 011import org.apache.commons.collections.Predicate; 012import org.apache.commons.lang.StringUtils; 013 014/** 015 * A class containing utilities for metafacade manipulation. 016 * 017 * @author Chad Brandon 018 * @author Wouter Zoons 019 * @author Bob Fields 020 */ 021public class MetafacadeUtils 022{ 023 /** 024 * Checks to see if the element is the specified type and if so casts it to the object and returns it, otherwise it 025 * returns null. 026 * 027 * @param element the element to check. 028 * @param type the Class type. 029 * @return the element has the given type or null. 030 */ 031 public static Object getElementAsType( 032 final Object element, 033 final Class type) 034 { 035 Object elementAsType = null; 036 if (element != null && type != null) 037 { 038 final Class elementClass = element.getClass(); 039 if (type.isAssignableFrom(elementClass)) 040 { 041 elementAsType = element; 042 } 043 } 044 return elementAsType; 045 } 046 047 /** 048 * Filters out the model elements from the <code>modelElements</code> collection that don't have the specified 049 * <code>stereotype</code> 050 * 051 * @param modelElements the model elements to filter. 052 * @param stereotype the stereotype that a model element must have in order to stay remain within the 053 * <code>modelElements</code> collection. 054 */ 055 public static void filterByStereotype( 056 final Collection modelElements, 057 final String stereotype) 058 { 059 // Should be able to type the Collection as <ModelElementFacade>, but compilation failure results. 060 if (StringUtils.isNotBlank(stereotype)) 061 { 062 CollectionUtils.filter( 063 modelElements, 064 new Predicate() 065 { 066 public boolean evaluate(Object object) 067 { 068 return ((ModelElementFacade)object).hasStereotype(stereotype); 069 } 070 }); 071 } 072 } 073 074 /** 075 * Filters out the model elements from the <code>modelElements</code> collection that are not of (or do not inherit 076 * from) the specified type <code>type</code> 077 * 078 * @param modelElements the model elements to filter. 079 * @param type the type of Class. 080 */ 081 public static void filterByType( 082 final Collection modelElements, 083 final Class type) 084 { 085 if (type != null) 086 { 087 CollectionUtils.filter( 088 modelElements, 089 new Predicate() 090 { 091 public boolean evaluate(Object object) 092 { 093 return type.isAssignableFrom(object.getClass()); 094 } 095 }); 096 } 097 } 098 099 /** 100 * Filters out the model elements from the <code>modelElements</code> collection that are of (or inherit from) the 101 * specified type <code>type</code> 102 * 103 * @param modelElements the model elements to filter. 104 * @param type the type of Class. 105 */ 106 public static void filterByNotType( 107 final Collection modelElements, 108 final Class type) 109 { 110 if (type != null) 111 { 112 CollectionUtils.filter( 113 modelElements, 114 new Predicate() 115 { 116 public boolean evaluate(Object object) 117 { 118 return !type.isAssignableFrom(object.getClass()); 119 } 120 }); 121 } 122 } 123 124 /** 125 * <p/>Returns a consistent name for a relation, independent from the end of the relation one is looking at. </p> 126 * <p/>In order to guarantee consistency with relation names, they must appear the same whichever angle (ie entity) 127 * that you come from. For example, if you are at Customer end of a relationship to an Address then your relation 128 * may appear with the name Customer-Address. But if you are in the Address entity looking at the Customer then you 129 * will get an error because the relation will be called Address-Customer. A simple way to guarantee that both ends 130 * of the relationship have the same name is merely to use alphabetical ordering. </p> 131 * 132 * @param roleName name of role in relation 133 * @param targetRoleName name of target role in relation 134 * @param separator character used to separate words 135 * @return uniform mapping name (in alphabetical order) 136 */ 137 public static String toRelationName( 138 final String roleName, 139 final String targetRoleName, 140 final String separator) 141 { 142 if (roleName.compareTo(targetRoleName) <= 0) 143 { 144 return (roleName + separator + targetRoleName); 145 } 146 return (targetRoleName + separator + roleName); 147 } 148 149 /** 150 * Sorts given metafacades by their fully qualified name. 151 * 152 * @param metafacades the collection of model elements to sort. 153 */ 154 public static void sortByFullyQualifiedName(final List metafacades) 155 { 156 Collections.sort( 157 metafacades, 158 new FullyQualifiedNameComparator()); 159 } 160 161 /** 162 * Used to sort operations by <code>fullyQualifiedName</code>. 163 */ 164 private static final class FullyQualifiedNameComparator 165 implements Comparator 166 { 167 private final Collator collator = Collator.getInstance(); 168 169 /** */ 170 FullyQualifiedNameComparator() 171 { 172 collator.setStrength(Collator.PRIMARY); 173 } 174 175 public int compare( 176 final Object objectA, 177 final Object objectB) 178 { 179 final ModelElementFacade a = (ModelElementFacade)objectA; 180 final ModelElementFacade b = (ModelElementFacade)objectB; 181 return collator.compare( 182 a.getFullyQualifiedName() != null ? a.getFullyQualifiedName() : "", 183 b.getFullyQualifiedName() != null ? b.getFullyQualifiedName() : ""); 184 } 185 } 186 187 /** 188 * Creates a typed argument list with the given <code>arguments</code>. If the <code>withArgumentNames</code> 189 * flag is true, the argument names are included in the list. 190 * 191 * @param arguments the arguments from which to create the list. 192 * @param withArgumentNames whether or not to include the argument names. 193 * @param modifier 194 * @return arguments.iterator().getGetterSetterTypeName() 195 */ 196 public static String getTypedArgumentList( 197 final Collection<ParameterFacade> arguments, 198 final boolean withArgumentNames, 199 final String modifier) 200 { 201 final StringBuilder buffer = new StringBuilder(); 202 boolean commaNeeded = false; 203 for (ParameterFacade parameter : arguments) 204 { 205 String type = null; 206 ClassifierFacade classifier = parameter.getType(); 207 if (classifier != null) 208 { 209 // Takes multiplicity and templating into account 210 type = parameter.getGetterSetterTypeName(); 211 } 212 213 if (commaNeeded) 214 { 215 buffer.append(", "); 216 } 217 if (StringUtils.isNotBlank(modifier)) 218 { 219 buffer.append(modifier); 220 buffer.append(' '); 221 } 222 buffer.append(type); 223 if (withArgumentNames) 224 { 225 buffer.append(' '); 226 buffer.append(parameter.getName()); 227 } 228 commaNeeded = true; 229 } 230 return buffer.toString(); 231 } 232 233 /** 234 * Creates a typed argument list with the given <code>arguments</code>. If the <code>withArgumentNames</code> 235 * flag is true, the argument names are included in the list. 236 * 237 * @param name 238 * @param arguments the arguments from which to create the list. 239 * @param withArgumentNames whether or not to include the argument names. 240 * @param argumentModifier 241 * @return getTypedArgumentList(arguments, withArgumentNames, argumentModifier) 242 */ 243 public static String getSignature( 244 final String name, 245 Collection<ParameterFacade> arguments, 246 final boolean withArgumentNames, 247 final String argumentModifier) 248 { 249 final StringBuilder signature = new StringBuilder(name); 250 signature.append('('); 251 signature.append(getTypedArgumentList( 252 arguments, 253 withArgumentNames, 254 argumentModifier)); 255 signature.append(')'); 256 return signature.toString(); 257 } 258 259 private static final String at = "@"; 260 private static final char period = '.'; 261 private static final char underscore = '_'; 262 /** 263 * Changes andromda standard tag format Strings to EMF standard format Strings 264 * (must be a valid Java identifier). Used for backwards compatibility with UML14 conventions. 265 * For example, @andromda.whatever becomes andromda_whatever. 266 * 267 * @param name 268 * @return getTypedArgumentList(arguments, withArgumentNames, argumentModifier) 269 */ 270 public static String getEmfTaggedValue(String name) 271 { 272 if (name==null) 273 { 274 return name; 275 } 276 if (name.startsWith(at)) 277 { 278 name = name.substring(1); 279 } 280 name = name.replace(period, underscore); 281 return name; 282 } 283 284 /** 285 * Changes EMF standard tag format Strings to AndroMDA standard format Strings. 286 * Used for backwards compatibility with UML14 conventions. 287 * For example, andromda_whatever becomes @andromda.whatever. 288 * 289 * @param name 290 * @return getTypedArgumentList(arguments, withArgumentNames, argumentModifier) 291 */ 292 public static String getUml14TaggedValue(String name) 293 { 294 if (name==null) 295 { 296 return name; 297 } 298 if (!name.startsWith(at)) 299 { 300 name = at+name; 301 } 302 name = name.replace(underscore, period); 303 return name; 304 } 305 306 /** 307 * Calculates the serial version UID of this classifier based on the 308 * signature of the classifier (name, visibility, attributes and methods). 309 * The algorithm is inspired by 310 * {@link java.io.ObjectStreamClass#getSerialVersionUID()}. 311 * 312 * The value should be stable as long as the classifier remains unchanged 313 * and should change as soon as there is any change in the signature of the 314 * classifier. 315 * @param object 316 * 317 * @return the serial version UID of this classifier. 318 */ 319 public static long calculateDefaultSUID(ClassifierFacade object) 320 { 321 // class name 322 StringBuilder buffer = new StringBuilder(object.getName()); 323 324 // generalizations 325 for (GeneralizableElementFacade generalizableElementFacade : object.getAllGeneralizations()) 326 { 327 ClassifierFacade classifier = (ClassifierFacade) generalizableElementFacade; 328 buffer.append(classifier.getName()); 329 } 330 331 // declared fields 332 for (AttributeFacade attribute : object.getAttributes()) 333 { 334 buffer.append(attribute.getName()); 335 buffer.append(attribute.getVisibility()); 336 buffer.append(attribute.getType().getName()); 337 } 338 339 // operations 340 for (OperationFacade operation : object.getOperations()) 341 { 342 buffer.append(operation.getName()); 343 buffer.append(operation.getVisibility()); 344 buffer.append(operation.getReturnType().getName()); 345 for (final ParameterFacade parameter : operation.getArguments()) 346 { 347 buffer.append(parameter.getName()); 348 buffer.append(parameter.getType().getName()); 349 } 350 } 351 final String signature = buffer.toString(); 352 353 long serialVersionUID = 0L; 354 try 355 { 356 MessageDigest md = MessageDigest.getInstance("SHA"); 357 byte[] hashBytes = md.digest(signature.getBytes()); 358 359 long hash = 0; 360 for (int ctr = Math.min(hashBytes.length, 8) - 1; ctr >= 0; ctr--) 361 { 362 hash = (hash << 8) | (hashBytes[ctr] & 0xFF); 363 } 364 serialVersionUID = hash; 365 } 366 catch (final NoSuchAlgorithmException ignore) 367 { 368 // ignore exception 369 } 370 371 return serialVersionUID; 372 } 373}