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}