View Javadoc
1   package org.andromda.core.metafacade;
2   
3   import java.lang.reflect.Constructor;
4   import java.util.Arrays;
5   import java.util.Collection;
6   import java.util.HashMap;
7   import java.util.HashSet;
8   import java.util.Map;
9   import java.util.Set;
10  import org.andromda.core.common.ClassUtils;
11  import org.andromda.core.common.Introspector;
12  import org.andromda.core.configuration.Namespaces;
13  import org.apache.commons.collections.CollectionUtils;
14  import org.apache.commons.collections.Predicate;
15  import org.apache.log4j.Logger;
16  
17  /**
18   * Contains static utility methods for dealing with metafacade instances.
19   *
20   * @author Chad Brandon
21   * @author Bob Fields
22   */
23  final class MetafacadeUtils
24  {
25      /**
26       * Indicates whether or not the mapping properties (present on the mapping, if any) are valid on the
27       * <code>metafacade</code>.
28       *
29       * @param metafacade the metafacade instance on which the properties will be validated.
30       * @param mapping the MetafacadeMapping instance that contains the properties.
31       * @return true/false
32       */
33      static boolean propertiesValid(
34          final MetafacadeBase metafacade,
35          final MetafacadeMapping mapping)
36      {
37          boolean valid = false;
38          final Collection<MetafacadeMapping.PropertyGroup> propertyGroups = mapping.getMappingPropertyGroups();
39          if (propertyGroups != null && !propertyGroups.isEmpty())
40          {
41              try
42              {
43                  if (getLogger().isDebugEnabled())
44                  {
45                      getLogger().debug(
46                          "evaluating " + propertyGroups.size() + " property groups(s) on metafacade '" + metafacade +
47                                  '\'');
48                  }
49                  final Introspector introspector = Introspector.instance();
50                  for (final MetafacadeMapping.PropertyGroup propertyGroup : propertyGroups)
51                  {
52                      for (final MetafacadeMapping.Property property : propertyGroup.getProperties())
53                      {
54                          valid = introspector.containsValidProperty(
55                                  metafacade,
56                                  property.getName(),
57                                  property.getValue());
58                          if (getLogger().isDebugEnabled())
59                          {
60                              getLogger().debug(
61                                  "property '" + property.getName() + "', with value '" + property.getValue() +
62                                  "' on metafacade '" + metafacade + "', evaluated to --> '" + valid + '\'');
63                          }
64  
65                          // - if the property is invalid, we break out
66                          //   of the loop (since we're evaluating with 'AND')
67                          if (!valid)
68                          {
69                              break;
70                          }
71                      }
72  
73                      // - we break on the first true value since
74                      //   property groups are evaluated as 'OR'
75                      if (valid)
76                      {
77                          break;
78                      }
79                  }
80              }
81              catch (final Throwable throwable)
82              {
83                  if (getLogger().isDebugEnabled())
84                  {
85                      getLogger().debug(
86                          "An error occurred while " + "evaluating properties on metafacade '" + metafacade +
87                          "', setting valid to 'false'",
88                          throwable);
89                  }
90                  valid = false;
91              }
92              if (getLogger().isDebugEnabled())
93              {
94                  getLogger().debug(
95                      "completed evaluating " + propertyGroups.size() + " properties on metafacade '" + metafacade +
96                      "' with a result of --> '" + valid + '\'');
97              }
98          }
99          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 }