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}