001package org.andromda.core.metafacade; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.HashSet; 006import java.util.Iterator; 007import java.util.LinkedHashMap; 008import java.util.LinkedHashSet; 009import java.util.List; 010import java.util.ListIterator; 011import java.util.Map; 012import java.util.Set; 013import org.andromda.core.common.ClassUtils; 014import org.andromda.core.profile.Profile; 015import org.apache.commons.lang.StringUtils; 016 017/** 018 * A meta facade mapping class. This class is a child of {@link MetafacadeMappings} 019 * (that is: instances of this class below to an instance of {@link MetafacadeMappings}). 020 * 021 * @author Chad Brandon 022 * @author Bob Fields 023 */ 024public class MetafacadeMapping 025{ 026 /** 027 * The meta facade for which this mapping applies. 028 */ 029 private Class metafacadeClass = null; 030 031 /** 032 * Gets the metafacadeClass for this mapping. 033 * 034 * @return Returns the metafacadeClass. 035 */ 036 public Class getMetafacadeClass() 037 { 038 return metafacadeClass; 039 } 040 041 /** 042 * Sets the metafacadeClassName for this mapping. 043 * 044 * @param metafacadeClassName The name of the metafacade class to set. 045 */ 046 public void setMetafacadeClassName(final String metafacadeClassName) 047 { 048 try 049 { 050 this.metafacadeClass = ClassUtils.loadClass(StringUtils.trimToEmpty(metafacadeClassName)); 051 } 052 catch (final Throwable throwable) 053 { 054 throw new MetafacadeMappingsException(throwable); 055 } 056 } 057 058 /** 059 * The names of the mapping classes for which this mapping applies. The {@link #context},{@link #stereotypes}and this 060 * names make up the identifying key for this mapping. 061 */ 062 private Set<String> mappingClassNames = new HashSet<String>(); 063 064 /** 065 * Gets the names of the metaobject classes used for this mapping. 066 * 067 * @return Returns the mappingClassNames. 068 */ 069 protected Set<String> getMappingClassNames() 070 { 071 // - if we have a mappingClassName defined, we use it 072 if (this.mappingClassNames.isEmpty()) 073 { 074 // - attempt to get the inherited mapping since it doesn't exist on this class 075 this.mappingClassNames = MetafacadeUtils.getInheritedMappingClassNames(this); 076 } 077 return this.mappingClassNames; 078 } 079 080 /** 081 * Indicates whether or not the mapping class has been present. 082 * 083 * @return whether or not the mapping class is present in this mapping. 084 */ 085 final boolean isMappingClassNamePresent() 086 { 087 return !this.mappingClassNames.isEmpty(); 088 } 089 090 /** 091 * The name of the metaobject class to use for this mapping. 092 * 093 * @param mappingClassName The mappingClassName to set. 094 */ 095 public void setMappingClassName(final String mappingClassName) 096 { 097 if(!StringUtils.isBlank(mappingClassName)) 098 { 099 this.mappingClassNames.clear(); 100 this.mappingClassNames.add(StringUtils.trimToEmpty(mappingClassName)); 101 } 102 } 103 104 /** 105 * Whether or not this mapping represents a <code>contextRoot</code>. 106 */ 107 private boolean contextRoot = false; 108 109 /** 110 * <p> 111 * Gets whether or not this mapping represents a <code>contextRoot</code>, by default a mapping is <strong>NOT 112 * </strong> a contextRoot. You'll want to specify this as true when other metafacades need to be created within the 113 * context of this metafacade. </p> 114 * 115 * @return Returns the contextRoot. 116 */ 117 public boolean isContextRoot() 118 { 119 return this.contextRoot; 120 } 121 122 /** 123 * Sets the name of the <code>contextRoot</code> for this mapping. 124 * 125 * @param contextRoot The contextRoot to set. 126 * @see #isContextRoot() 127 */ 128 public void setContextRoot(final boolean contextRoot) 129 { 130 this.contextRoot = contextRoot; 131 } 132 133 /** 134 * The stereotypes to which this mapping applies (all stereotypes must be present for this mapping to apply). 135 */ 136 private final List<String> stereotypes = new ArrayList<String>(); 137 138 /** 139 * Adds a <code>stereotype</code> to the stereotypes. 140 * 141 * @param stereotype 142 */ 143 public void addStereotype(final String stereotype) 144 { 145 this.stereotypes.add(stereotype); 146 } 147 148 /** 149 * Gets the stereotypes which apply to this mapping. 150 * 151 * @return the names of the stereotypes 152 */ 153 final List<String> getStereotypes() 154 { 155 for (final ListIterator<String> iterator = this.stereotypes.listIterator(); iterator.hasNext();) 156 { 157 iterator.set(Profile.instance().get(iterator.next())); 158 } 159 return this.stereotypes; 160 } 161 162 /** 163 * Indicates whether or not this mapping has any stereotypes defined. 164 * 165 * @return true/false 166 */ 167 final boolean hasStereotypes() 168 { 169 return !this.stereotypes.isEmpty(); 170 } 171 172 /** 173 * Used to hold references to language mapping classes. 174 */ 175 private final Collection<String> propertyReferences = new LinkedHashSet<String>(); 176 177 /** 178 * Adds a mapping property reference. These are used to populate metafacade impl classes with mapping files, etc. 179 * The property reference applies to the given mapping. 180 * 181 * @param reference the name of the reference. 182 * @see MetafacadeMappings#addPropertyReference(String) 183 */ 184 public void addPropertyReference(final String reference) 185 { 186 this.propertyReferences.add(reference); 187 } 188 189 /** 190 * Returns all mapping references for this MetafacadeMapping instance. 191 * @return this.propertyReferences 192 */ 193 public Collection<String> getPropertyReferences() 194 { 195 return this.propertyReferences; 196 } 197 198 /** 199 * Used to hold the properties that should apply to the mapping element. 200 */ 201 private PropertyGroup mappingProperties = null; 202 203 /** 204 * Adds a mapping property. This are used to narrow the metafacade to which the mapping can apply. The properties 205 * must exist and must evaluate to the specified value if given for the mapping to match. 206 * 207 * @param name the name of the reference. 208 * @param value the default value of the property reference. 209 */ 210 public void addMappingProperty( 211 final String name, 212 final String value) 213 { 214 if (value != null) 215 { 216 if (this.mappingProperties == null) 217 { 218 this.mappingProperties = new PropertyGroup(); 219 220 // we add the mapping properties to the mappingPropertyGroups 221 // collection only once 222 this.mappingPropertyGroups.add(this.mappingProperties); 223 } 224 this.mappingProperties.addProperty(new Property( 225 name, 226 value)); 227 } 228 } 229 230 /** 231 * Stores a collection of all property groups added through {@link #addPropertyReferences(java.util.Collection)}. These are 232 * property groups added from other mappings that return true when executing {@link #match(MetafacadeMapping)}. 233 */ 234 private final Collection<PropertyGroup> mappingPropertyGroups = new ArrayList<PropertyGroup>(); 235 236 /** 237 * Adds the <code>propertyGroup</code> to the existing mapping property groups within this mapping. 238 * 239 * @param propertyGroup a property group for this mapping 240 */ 241 final void addMappingPropertyGroup(final PropertyGroup propertyGroup) 242 { 243 this.mappingPropertyGroups.add(propertyGroup); 244 } 245 246 /** 247 * Returns all mapping property groups for this MetafacadeMapping instance. 248 * @return this.mappingPropertyGroups 249 */ 250 final Collection<PropertyGroup> getMappingPropertyGroups() 251 { 252 return this.mappingPropertyGroups; 253 } 254 255 /** 256 * Gets the mapping properties associated this this mapping directly (contained within a {@link 257 * PropertyGroup}instance). 258 * 259 * @return the mapping property group. 260 */ 261 final PropertyGroup getMappingProperties() 262 { 263 return this.mappingProperties; 264 } 265 266 /** 267 * Indicates whether or not this mapping contains any mapping properties. 268 * 269 * @return true/false 270 */ 271 final boolean hasMappingProperties() 272 { 273 return this.mappingProperties != null && !this.mappingProperties.getProperties().isEmpty(); 274 } 275 276 /** 277 * Adds all <code>propertyReferences</code> to the property references contained in this MetafacadeMapping 278 * instance. 279 * 280 * @param propertyReferences the property references to add. 281 */ 282 public void addPropertyReferences(final Collection<String> propertyReferences) 283 { 284 if (propertyReferences != null) 285 { 286 this.propertyReferences.addAll(propertyReferences); 287 } 288 } 289 290 /** 291 * The context to which this mapping applies. 292 */ 293 private String context = ""; 294 295 /** 296 * Sets the context to which this mapping applies. 297 * 298 * @param context The metafacade context name to set. 299 */ 300 public void setContext(final String context) 301 { 302 this.context = StringUtils.trimToEmpty(context); 303 } 304 305 /** 306 * Gets the context to which this mapping applies. 307 * 308 * @return the name of the context 309 */ 310 final String getContext() 311 { 312 return this.context; 313 } 314 315 /** 316 * Indicates whether or not this mapping has a context. 317 * 318 * @return true/false 319 */ 320 final boolean hasContext() 321 { 322 return StringUtils.isNotBlank(this.context); 323 } 324 325 /** 326 * The "parent" metafacade mappings; 327 */ 328 private MetafacadeMappings mappings; 329 330 /** 331 * Sets the metafacade mappings instance to which this particular mapping belongs. (i.e. the parent) Note, that this 332 * is populated during the call to {@link MetafacadeMappings#addMapping(MetafacadeMapping)}. 333 * 334 * @param mappings the MetacadeMappings instance to which this mapping belongs. 335 */ 336 final void setMetafacadeMappings(final MetafacadeMappings mappings) 337 { 338 this.mappings = mappings; 339 } 340 341 /** 342 * Gets the "parent" MetafacadeMappings instance to which this mapping belongs. 343 * 344 * @return the parent metafacade mappings instance. 345 */ 346 final MetafacadeMappings getMetafacadeMappings() 347 { 348 return this.mappings; 349 } 350 351 /** 352 * Indicates whether or not the <code>mapping</code> matches this mapping. It matches on the following: <ul> 353 * <li>metafacadeClass</li> <li>mappingClassName</li> <li>stereotypes</li> </ul> 354 * @param mapping 355 * @return match this.getMappingClassName().equals(mapping.getMappingClassName()) 356 */ 357 final boolean match(final MetafacadeMapping mapping) 358 { 359 boolean match = 360 mapping != null && this.getMetafacadeClass().equals(mapping.getMetafacadeClass()) && 361 this.getStereotypes().equals(mapping.getStereotypes()) && this.getContext().equals(mapping.getContext()); 362 363 // - if they match and the mappingClassNames are both non-null, verify they match 364 if (match && !this.mappingClassNames.isEmpty() && mapping != null && !mapping.mappingClassNames.isEmpty()) 365 { 366 match = this.getMappingClassNames().equals(mapping.getMappingClassNames()); 367 } 368 return match; 369 } 370 371 /** 372 * @see Object#toString() 373 */ 374 public String toString() 375 { 376 return super.toString() + '[' + this.getMetafacadeClass() + "], mappingClassName[" + this.mappingClassNames + 377 "], properties[" + this.getMappingProperties() + "], stereotypes" + this.stereotypes + ", context[" + 378 this.context + "], propertiesReferences" + this.getPropertyReferences(); 379 } 380 381 /** 382 * Represents a group of properties. Properties within a group are evaluated within an 'AND' expression. 383 * PropertyGroups are evaluated together as an 'OR' expressions (i.e. you 'OR' property groups together, and 'AND' 384 * properties together). 385 * 386 * @see MetafacadeMappings#addMapping(MetafacadeMapping) 387 */ 388 static final class PropertyGroup 389 { 390 private final Map<String, Property> properties = new LinkedHashMap<String, Property>(); 391 392 /** 393 * Adds a property to the internal collection of properties. 394 * 395 * @param property the property to add to this group. 396 */ 397 final void addProperty(final Property property) 398 { 399 final String name = property.getName(); 400 if (!this.properties.containsKey(name)) 401 { 402 this.properties.put( 403 name, 404 property); 405 } 406 } 407 408 /** 409 * Gets the currently internal collection of properties. 410 * 411 * @return the properties collection. 412 */ 413 final Collection<Property> getProperties() 414 { 415 return this.properties.values(); 416 } 417 418 /** 419 * @see Object#toString() 420 */ 421 public String toString() 422 { 423 final StringBuilder toString = new StringBuilder(); 424 char separator = ':'; 425 for (final Iterator<Property> iterator = this.getProperties().iterator(); iterator.hasNext();) 426 { 427 final Property property = iterator.next(); 428 toString.append(property.getName()); 429 if (StringUtils.isNotBlank(property.getValue())) 430 { 431 toString.append(separator).append(property.getValue()); 432 } 433 if (iterator.hasNext()) 434 { 435 toString.append(separator); 436 } 437 } 438 return toString.toString(); 439 } 440 } 441 442 /** 443 * Stores and provides access to the mapping element's nested <property/>. 444 */ 445 static final class Property 446 { 447 private String name; 448 private String value; 449 450 /** 451 * @param name 452 * @param value 453 */ 454 Property( 455 final String name, 456 final String value) 457 { 458 this.name = StringUtils.trimToEmpty(name); 459 this.value = value; 460 } 461 462 /** 463 * Gets the value of the <code>name</code> attribute on the <code>property</code> element. 464 * 465 * @return the name 466 */ 467 final String getName() 468 { 469 return StringUtils.trimToEmpty(this.name); 470 } 471 472 /** 473 * Gets the value of the <code>value</code> attribute defined on the <code>property</code> element. 474 * 475 * @return the value 476 */ 477 final String getValue() 478 { 479 return StringUtils.trimToEmpty(this.value); 480 } 481 } 482}