1 package org.andromda.metafacades.uml;
2
3 import java.security.MessageDigest;
4 import java.security.NoSuchAlgorithmException;
5 import java.text.Collator;
6 import java.util.Collection;
7 import java.util.Collections;
8 import java.util.Comparator;
9 import java.util.List;
10 import org.apache.commons.collections.CollectionUtils;
11 import org.apache.commons.collections.Predicate;
12 import org.apache.commons.lang.StringUtils;
13
14 /**
15 * A class containing utilities for metafacade manipulation.
16 *
17 * @author Chad Brandon
18 * @author Wouter Zoons
19 * @author Bob Fields
20 */
21 public class MetafacadeUtils
22 {
23 /**
24 * Checks to see if the element is the specified type and if so casts it to the object and returns it, otherwise it
25 * returns null.
26 *
27 * @param element the element to check.
28 * @param type the Class type.
29 * @return the element has the given type or null.
30 */
31 public static Object getElementAsType(
32 final Object element,
33 final Class type)
34 {
35 Object elementAsType = null;
36 if (element != null && type != null)
37 {
38 final Class elementClass = element.getClass();
39 if (type.isAssignableFrom(elementClass))
40 {
41 elementAsType = element;
42 }
43 }
44 return elementAsType;
45 }
46
47 /**
48 * Filters out the model elements from the <code>modelElements</code> collection that don't have the specified
49 * <code>stereotype</code>
50 *
51 * @param modelElements the model elements to filter.
52 * @param stereotype the stereotype that a model element must have in order to stay remain within the
53 * <code>modelElements</code> collection.
54 */
55 public static void filterByStereotype(
56 final Collection modelElements,
57 final String stereotype)
58 {
59 // Should be able to type the Collection as <ModelElementFacade>, but compilation failure results.
60 if (StringUtils.isNotBlank(stereotype))
61 {
62 CollectionUtils.filter(
63 modelElements,
64 new Predicate()
65 {
66 public boolean evaluate(Object object)
67 {
68 return ((ModelElementFacade)object).hasStereotype(stereotype);
69 }
70 });
71 }
72 }
73
74 /**
75 * Filters out the model elements from the <code>modelElements</code> collection that are not of (or do not inherit
76 * from) the specified type <code>type</code>
77 *
78 * @param modelElements the model elements to filter.
79 * @param type the type of Class.
80 */
81 public static void filterByType(
82 final Collection modelElements,
83 final Class type)
84 {
85 if (type != null)
86 {
87 CollectionUtils.filter(
88 modelElements,
89 new Predicate()
90 {
91 public boolean evaluate(Object object)
92 {
93 return type.isAssignableFrom(object.getClass());
94 }
95 });
96 }
97 }
98
99 /**
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 }