001package org.andromda.core.metafacade;
002
003import java.lang.reflect.Constructor;
004import java.util.Arrays;
005import java.util.Collection;
006import java.util.HashMap;
007import java.util.HashSet;
008import java.util.Map;
009import java.util.Set;
010import org.andromda.core.common.ClassUtils;
011import org.andromda.core.common.Introspector;
012import org.andromda.core.configuration.Namespaces;
013import org.apache.commons.collections.CollectionUtils;
014import org.apache.commons.collections.Predicate;
015import org.apache.log4j.Logger;
016
017/**
018 * Contains static utility methods for dealing with metafacade instances.
019 *
020 * @author Chad Brandon
021 * @author Bob Fields
022 */
023final class MetafacadeUtils
024{
025    /**
026     * Indicates whether or not the mapping properties (present on the mapping, if any) are valid on the
027     * <code>metafacade</code>.
028     *
029     * @param metafacade the metafacade instance on which the properties will be validated.
030     * @param mapping the MetafacadeMapping instance that contains the properties.
031     * @return true/false
032     */
033    static boolean propertiesValid(
034        final MetafacadeBase metafacade,
035        final MetafacadeMapping mapping)
036    {
037        boolean valid = false;
038        final Collection<MetafacadeMapping.PropertyGroup> propertyGroups = mapping.getMappingPropertyGroups();
039        if (propertyGroups != null && !propertyGroups.isEmpty())
040        {
041            try
042            {
043                if (getLogger().isDebugEnabled())
044                {
045                    getLogger().debug(
046                        "evaluating " + propertyGroups.size() + " property groups(s) on metafacade '" + metafacade +
047                                '\'');
048                }
049                final Introspector introspector = Introspector.instance();
050                for (final MetafacadeMapping.PropertyGroup propertyGroup : propertyGroups)
051                {
052                    for (final MetafacadeMapping.Property property : propertyGroup.getProperties())
053                    {
054                        valid = introspector.containsValidProperty(
055                                metafacade,
056                                property.getName(),
057                                property.getValue());
058                        if (getLogger().isDebugEnabled())
059                        {
060                            getLogger().debug(
061                                "property '" + property.getName() + "', with value '" + property.getValue() +
062                                "' on metafacade '" + metafacade + "', evaluated to --> '" + valid + '\'');
063                        }
064
065                        // - if the property is invalid, we break out
066                        //   of the loop (since we're evaluating with 'AND')
067                        if (!valid)
068                        {
069                            break;
070                        }
071                    }
072
073                    // - we break on the first true value since
074                    //   property groups are evaluated as 'OR'
075                    if (valid)
076                    {
077                        break;
078                    }
079                }
080            }
081            catch (final Throwable throwable)
082            {
083                if (getLogger().isDebugEnabled())
084                {
085                    getLogger().debug(
086                        "An error occurred while " + "evaluating properties on metafacade '" + metafacade +
087                        "', setting valid to 'false'",
088                        throwable);
089                }
090                valid = false;
091            }
092            if (getLogger().isDebugEnabled())
093            {
094                getLogger().debug(
095                    "completed evaluating " + propertyGroups.size() + " properties on metafacade '" + metafacade +
096                    "' with a result of --> '" + valid + '\'');
097            }
098        }
099        return valid;
100    }
101
102    /**
103     * Constructs a new <code>metafacade</code> from the given
104     * <code>metafacadeClass</code> and <code>mappingObject</code>.
105     *
106     * @param metafacadeClass the metafacade class.
107     * @param mappingObject the object to which the metafacade is mapped.
108     * @param context
109     * @return the new metafacade.
110     * @throws Exception if any error occurs during metafacade creation
111     */
112    static MetafacadeBase constructMetafacade(
113        final Class metafacadeClass,
114        final Object mappingObject,
115        final String context)
116        throws Exception
117    {
118        if (getLogger().isDebugEnabled())
119        {
120            getLogger().debug(
121                "constructing metafacade from class '" + metafacadeClass + "' mapping object '" + mappingObject +
122                "', and context '" + context + '\'');
123        }
124        final Constructor constructor = metafacadeClass.getDeclaredConstructors()[0];
125        return (MetafacadeBase)constructor.newInstance(mappingObject, context);
126    }
127
128    /**
129     * Compute the set of UML implementation class names that are in both parameters sets. If class A1 is in set1 and class A2 is in set2 with A1 extending A2, A1
130     * must be on the result set.
131     *
132     * @param nameSet1 the first set
133     * @param nameSet2 the second set
134     * @return the intersection of the 2 sets.
135     */
136    private static Set<String> intersection(Set<String> nameSet1, Set<String> nameSet2)
137    {
138       Set<String> results = new HashSet<String>();
139       if(nameSet1 == null || nameSet2 == null)
140       {
141           return results;
142       }
143
144       Map<Class<?>, String> classesToName = new HashMap<Class<?>, String>();
145       Set<Class<?>> classesSet1 = getClassesFromNames(nameSet1, classesToName);
146       Set<Class<?>> classesSet2 = getClassesFromNames(nameSet2, classesToName);
147
148       for(final Class<?> classToCheck : classesSet1)
149       {
150           Set<Class> classToCheckInterfaces = new HashSet<Class>(Arrays.asList(classToCheck.getInterfaces()));
151           for(final Class<?> expectedResult : classesSet2)
152           {
153               if(classToCheck.isAssignableFrom(expectedResult))
154               {
155                   results.add(classesToName.get(expectedResult));
156               }
157               else if(expectedResult.isAssignableFrom(classToCheck))
158               {
159                   results.add(classesToName.get(classToCheck));
160               }
161               else {
162                   // Check at the interface level
163                   boolean expectedResultNotCompliant = CollectionUtils.exists(classToCheckInterfaces, new Predicate()
164                   {
165                        public boolean evaluate(Object object)
166                        {
167                            Class<?> classToCheckInterface = (Class<?>)object;
168                            return !classToCheckInterface.isAssignableFrom(expectedResult);
169                        }
170                   });
171                   if (!expectedResultNotCompliant) {
172                       results.add(classesToName.get(expectedResult));
173                   } else {
174                       Set<Class> expectedResultInterfaces = new HashSet<Class>(Arrays.asList(expectedResult.getInterfaces()));
175                       boolean classToCheckNotCompliant = CollectionUtils.exists(expectedResultInterfaces, new Predicate()
176                       {
177                            public boolean evaluate(Object object)
178                            {
179                                Class<?> expectedResultInterface = (Class<?>)object;
180                                return !expectedResultInterface.isAssignableFrom(classToCheck);
181                            }
182                       });
183                       if (!classToCheckNotCompliant) {
184                           results.add(classesToName.get(classToCheck));
185                       }
186                   }
187               }
188           }
189       }
190       return results;
191    }
192
193    /**
194     * Convert a set of class names into a set of class objects. The mapping between the class object and the class name is kept into the classesToName map. If the class name cannot
195     * be directly converted in a class, try to remove a prefix starting with a $. This is necessary because implementation classes for UML 1.4 are created dynamically and are not
196     * available at the time this method is called, one must then instantiate the class object for the interface of the implementation class.
197     *
198     * @param nameSet the set of class names.
199     * @param classesToName the map that must keep the relation between class object and class names.
200     * @return the set of class objects.
201     */
202    private static Set<Class<?>> getClassesFromNames(Set<String> nameSet, Map<Class<?>, String> classesToName)
203    {
204        final Set<Class<?>> classesSet = new HashSet<Class<?>>();
205        for(final String name : nameSet)
206        {
207            try
208            {
209                Class<?> cl = Class.forName(name);
210                classesToName.put(cl, name);
211                classesSet.add(cl);
212            }
213            catch (ClassNotFoundException e1)
214            {
215                // Workaround for UML 1.4, where implementation class, ending with $Impl are created dynamically and are not available at the time this method is called.
216                try
217                {
218                    String instanciatedName = name;
219                    if(instanciatedName.endsWith("$Impl"))
220                    {
221                        instanciatedName = name.substring(0, name.lastIndexOf("$Impl"));
222                    }
223                    Class<?> cl = Class.forName(instanciatedName);
224                    classesToName.put(cl, name);
225                    classesSet.add(cl);
226                }
227                catch (ClassNotFoundException e2)
228                {
229                    throw new RuntimeException(e2);
230                }
231            }
232        }
233        return classesSet;
234    }
235
236    /**
237     * Retrieves the inherited mapping class names for the given metafacade class by traveling
238     * up the inheritance hierarchy to find the ones that have the mapping class name declared.
239     *
240     * @param metafacadeClass the metafacade class to retrieve the mapping class names.
241     * @param metafacadeImpls lookup for metafacade implementation names.
242     * @param mappingInstances mapping classes for common metafacades.
243     * @return the inherited mapping class names for the given metafacade class.
244     */
245    private static Set<String> getInheritedMappingClassNames(final Class metafacadeClass, final MetafacadeImpls metafacadeImpls, final Map<Class, Set<String>> mappingInstances )
246    {
247        Set<String> result = null;
248        if(metafacadeClass != null)
249        {
250            if(metafacadeClass.isInterface())
251            {
252                result = mappingInstances.get(metafacadeImpls.getMetafacadeImplClass(metafacadeClass.getName()));
253            }
254            if(result == null)
255            {
256                for(Class currentInterface : metafacadeClass.getInterfaces())
257                {
258                    Set<String> subInheritedMappingClassNames = getInheritedMappingClassNames(currentInterface, metafacadeImpls, mappingInstances);
259                    if(subInheritedMappingClassNames != null)
260                    {
261                        if(result == null)
262                        {
263                            result = subInheritedMappingClassNames;
264                        }
265                        else
266                        {
267                            result = intersection(result, subInheritedMappingClassNames);
268                        }
269                    }
270                }
271            }
272            if(result == null)
273            {
274                result = getInheritedMappingClassNames(metafacadeClass.getSuperclass(), metafacadeImpls, mappingInstances);
275            }
276        }
277        return result;
278    }
279
280    /**
281     * Retrieves the inherited mapping class names for the given <code>mapping</code> by traveling
282     * up the inheritance hierarchy to find the ones that have the mapping class name declared.
283     *
284     * @param mapping the {@link MetafacadeMapping} instance for which we'll retrieve its mapping classes.
285     * @return the names of the mapping classes.
286     */
287    public static Set<String> getInheritedMappingClassNames(final MetafacadeMapping mapping)
288    {
289        final Class metafacadeClass = mapping.getMetafacadeClass();
290        final MetafacadeImpls metafacadeImpls = MetafacadeImpls.instance();
291        final Map<Class, Set<String>> mappingInstances = MetafacadeMappings.getAllMetafacadeMappingInstances();
292        final Set<String> className = getInheritedMappingClassNames(metafacadeClass, metafacadeImpls, mappingInstances);
293        if (className == null || className.isEmpty())
294        {
295            throw new MetafacadeMappingsException("No mapping class could be found for '" + metafacadeClass.getName() +
296                    '\'');
297        }
298        MetafacadeUtils.getLogger().debug("inheritedMappingClassName " + metafacadeClass.getName() + "=" + className);
299        return className;
300    }
301
302    /**
303     * Indicates whether or not a metafacade model facade is present within the
304     * given namespace
305     *
306     * @param namespace the namespace to check.
307     * @return true/false
308     */
309    public static boolean isMetafacadeModelPresent(final String namespace)
310    {
311        boolean  present = false;
312        if (ClassUtils.isClassOfTypePresent(
313            Namespaces.instance().getResourceRoots(namespace),
314            ModelAccessFacade.class))
315        {
316            present = true;
317        }
318        return present;
319    }
320
321    /**
322     * Gets the logger instance which should be used for logging output within this class.
323     *
324     * @return the logger instance.
325     */
326    private static Logger getLogger()
327    {
328        return MetafacadeFactory.getInstance().getLogger();
329    }
330}