MetafacadeMapping.java
package org.andromda.core.metafacade;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import org.andromda.core.common.ClassUtils;
import org.andromda.core.profile.Profile;
import org.apache.commons.lang.StringUtils;
/**
* A meta facade mapping class. This class is a child of {@link MetafacadeMappings}
* (that is: instances of this class below to an instance of {@link MetafacadeMappings}).
*
* @author Chad Brandon
* @author Bob Fields
*/
public class MetafacadeMapping
{
/**
* The meta facade for which this mapping applies.
*/
private Class metafacadeClass = null;
/**
* Gets the metafacadeClass for this mapping.
*
* @return Returns the metafacadeClass.
*/
public Class getMetafacadeClass()
{
return metafacadeClass;
}
/**
* Sets the metafacadeClassName for this mapping.
*
* @param metafacadeClassName The name of the metafacade class to set.
*/
public void setMetafacadeClassName(final String metafacadeClassName)
{
try
{
this.metafacadeClass = ClassUtils.loadClass(StringUtils.trimToEmpty(metafacadeClassName));
}
catch (final Throwable throwable)
{
throw new MetafacadeMappingsException(throwable);
}
}
/**
* The names of the mapping classes for which this mapping applies. The {@link #context},{@link #stereotypes}and this
* names make up the identifying key for this mapping.
*/
private Set<String> mappingClassNames = new HashSet<String>();
/**
* Gets the names of the metaobject classes used for this mapping.
*
* @return Returns the mappingClassNames.
*/
protected Set<String> getMappingClassNames()
{
// - if we have a mappingClassName defined, we use it
if (this.mappingClassNames.isEmpty())
{
// - attempt to get the inherited mapping since it doesn't exist on this class
this.mappingClassNames = MetafacadeUtils.getInheritedMappingClassNames(this);
}
return this.mappingClassNames;
}
/**
* Indicates whether or not the mapping class has been present.
*
* @return whether or not the mapping class is present in this mapping.
*/
final boolean isMappingClassNamePresent()
{
return !this.mappingClassNames.isEmpty();
}
/**
* The name of the metaobject class to use for this mapping.
*
* @param mappingClassName The mappingClassName to set.
*/
public void setMappingClassName(final String mappingClassName)
{
if(!StringUtils.isBlank(mappingClassName))
{
this.mappingClassNames.clear();
this.mappingClassNames.add(StringUtils.trimToEmpty(mappingClassName));
}
}
/**
* Whether or not this mapping represents a <code>contextRoot</code>.
*/
private boolean contextRoot = false;
/**
* <p>
* Gets whether or not this mapping represents a <code>contextRoot</code>, by default a mapping is <strong>NOT
* </strong> a contextRoot. You'll want to specify this as true when other metafacades need to be created within the
* context of this metafacade. </p>
*
* @return Returns the contextRoot.
*/
public boolean isContextRoot()
{
return this.contextRoot;
}
/**
* Sets the name of the <code>contextRoot</code> for this mapping.
*
* @param contextRoot The contextRoot to set.
* @see #isContextRoot()
*/
public void setContextRoot(final boolean contextRoot)
{
this.contextRoot = contextRoot;
}
/**
* The stereotypes to which this mapping applies (all stereotypes must be present for this mapping to apply).
*/
private final List<String> stereotypes = new ArrayList<String>();
/**
* Adds a <code>stereotype</code> to the stereotypes.
*
* @param stereotype
*/
public void addStereotype(final String stereotype)
{
this.stereotypes.add(stereotype);
}
/**
* Gets the stereotypes which apply to this mapping.
*
* @return the names of the stereotypes
*/
final List<String> getStereotypes()
{
for (final ListIterator<String> iterator = this.stereotypes.listIterator(); iterator.hasNext();)
{
iterator.set(Profile.instance().get(iterator.next()));
}
return this.stereotypes;
}
/**
* Indicates whether or not this mapping has any stereotypes defined.
*
* @return true/false
*/
final boolean hasStereotypes()
{
return !this.stereotypes.isEmpty();
}
/**
* Used to hold references to language mapping classes.
*/
private final Collection<String> propertyReferences = new LinkedHashSet<String>();
/**
* Adds a mapping property reference. These are used to populate metafacade impl classes with mapping files, etc.
* The property reference applies to the given mapping.
*
* @param reference the name of the reference.
* @see MetafacadeMappings#addPropertyReference(String)
*/
public void addPropertyReference(final String reference)
{
this.propertyReferences.add(reference);
}
/**
* Returns all mapping references for this MetafacadeMapping instance.
* @return this.propertyReferences
*/
public Collection<String> getPropertyReferences()
{
return this.propertyReferences;
}
/**
* Used to hold the properties that should apply to the mapping element.
*/
private PropertyGroup mappingProperties = null;
/**
* Adds a mapping property. This are used to narrow the metafacade to which the mapping can apply. The properties
* must exist and must evaluate to the specified value if given for the mapping to match.
*
* @param name the name of the reference.
* @param value the default value of the property reference.
*/
public void addMappingProperty(
final String name,
final String value)
{
if (value != null)
{
if (this.mappingProperties == null)
{
this.mappingProperties = new PropertyGroup();
// we add the mapping properties to the mappingPropertyGroups
// collection only once
this.mappingPropertyGroups.add(this.mappingProperties);
}
this.mappingProperties.addProperty(new Property(
name,
value));
}
}
/**
* Stores a collection of all property groups added through {@link #addPropertyReferences(java.util.Collection)}. These are
* property groups added from other mappings that return true when executing {@link #match(MetafacadeMapping)}.
*/
private final Collection<PropertyGroup> mappingPropertyGroups = new ArrayList<PropertyGroup>();
/**
* Adds the <code>propertyGroup</code> to the existing mapping property groups within this mapping.
*
* @param propertyGroup a property group for this mapping
*/
final void addMappingPropertyGroup(final PropertyGroup propertyGroup)
{
this.mappingPropertyGroups.add(propertyGroup);
}
/**
* Returns all mapping property groups for this MetafacadeMapping instance.
* @return this.mappingPropertyGroups
*/
final Collection<PropertyGroup> getMappingPropertyGroups()
{
return this.mappingPropertyGroups;
}
/**
* Gets the mapping properties associated this this mapping directly (contained within a {@link
* PropertyGroup}instance).
*
* @return the mapping property group.
*/
final PropertyGroup getMappingProperties()
{
return this.mappingProperties;
}
/**
* Indicates whether or not this mapping contains any mapping properties.
*
* @return true/false
*/
final boolean hasMappingProperties()
{
return this.mappingProperties != null && !this.mappingProperties.getProperties().isEmpty();
}
/**
* Adds all <code>propertyReferences</code> to the property references contained in this MetafacadeMapping
* instance.
*
* @param propertyReferences the property references to add.
*/
public void addPropertyReferences(final Collection<String> propertyReferences)
{
if (propertyReferences != null)
{
this.propertyReferences.addAll(propertyReferences);
}
}
/**
* The context to which this mapping applies.
*/
private String context = "";
/**
* Sets the context to which this mapping applies.
*
* @param context The metafacade context name to set.
*/
public void setContext(final String context)
{
this.context = StringUtils.trimToEmpty(context);
}
/**
* Gets the context to which this mapping applies.
*
* @return the name of the context
*/
final String getContext()
{
return this.context;
}
/**
* Indicates whether or not this mapping has a context.
*
* @return true/false
*/
final boolean hasContext()
{
return StringUtils.isNotBlank(this.context);
}
/**
* The "parent" metafacade mappings;
*/
private MetafacadeMappings mappings;
/**
* Sets the metafacade mappings instance to which this particular mapping belongs. (i.e. the parent) Note, that this
* is populated during the call to {@link MetafacadeMappings#addMapping(MetafacadeMapping)}.
*
* @param mappings the MetacadeMappings instance to which this mapping belongs.
*/
final void setMetafacadeMappings(final MetafacadeMappings mappings)
{
this.mappings = mappings;
}
/**
* Gets the "parent" MetafacadeMappings instance to which this mapping belongs.
*
* @return the parent metafacade mappings instance.
*/
final MetafacadeMappings getMetafacadeMappings()
{
return this.mappings;
}
/**
* Indicates whether or not the <code>mapping</code> matches this mapping. It matches on the following: <ul>
* <li>metafacadeClass</li> <li>mappingClassName</li> <li>stereotypes</li> </ul>
* @param mapping
* @return match this.getMappingClassName().equals(mapping.getMappingClassName())
*/
final boolean match(final MetafacadeMapping mapping)
{
boolean match =
mapping != null && this.getMetafacadeClass().equals(mapping.getMetafacadeClass()) &&
this.getStereotypes().equals(mapping.getStereotypes()) && this.getContext().equals(mapping.getContext());
// - if they match and the mappingClassNames are both non-null, verify they match
if (match && !this.mappingClassNames.isEmpty() && mapping != null && !mapping.mappingClassNames.isEmpty())
{
match = this.getMappingClassNames().equals(mapping.getMappingClassNames());
}
return match;
}
/**
* @see Object#toString()
*/
public String toString()
{
return super.toString() + '[' + this.getMetafacadeClass() + "], mappingClassName[" + this.mappingClassNames +
"], properties[" + this.getMappingProperties() + "], stereotypes" + this.stereotypes + ", context[" +
this.context + "], propertiesReferences" + this.getPropertyReferences();
}
/**
* Represents a group of properties. Properties within a group are evaluated within an 'AND' expression.
* PropertyGroups are evaluated together as an 'OR' expressions (i.e. you 'OR' property groups together, and 'AND'
* properties together).
*
* @see MetafacadeMappings#addMapping(MetafacadeMapping)
*/
static final class PropertyGroup
{
private final Map<String, Property> properties = new LinkedHashMap<String, Property>();
/**
* Adds a property to the internal collection of properties.
*
* @param property the property to add to this group.
*/
final void addProperty(final Property property)
{
final String name = property.getName();
if (!this.properties.containsKey(name))
{
this.properties.put(
name,
property);
}
}
/**
* Gets the currently internal collection of properties.
*
* @return the properties collection.
*/
final Collection<Property> getProperties()
{
return this.properties.values();
}
/**
* @see Object#toString()
*/
public String toString()
{
final StringBuilder toString = new StringBuilder();
char separator = ':';
for (final Iterator<Property> iterator = this.getProperties().iterator(); iterator.hasNext();)
{
final Property property = iterator.next();
toString.append(property.getName());
if (StringUtils.isNotBlank(property.getValue()))
{
toString.append(separator).append(property.getValue());
}
if (iterator.hasNext())
{
toString.append(separator);
}
}
return toString.toString();
}
}
/**
* Stores and provides access to the mapping element's nested <property/>.
*/
static final class Property
{
private String name;
private String value;
/**
* @param name
* @param value
*/
Property(
final String name,
final String value)
{
this.name = StringUtils.trimToEmpty(name);
this.value = value;
}
/**
* Gets the value of the <code>name</code> attribute on the <code>property</code> element.
*
* @return the name
*/
final String getName()
{
return StringUtils.trimToEmpty(this.name);
}
/**
* Gets the value of the <code>value</code> attribute defined on the <code>property</code> element.
*
* @return the value
*/
final String getValue()
{
return StringUtils.trimToEmpty(this.value);
}
}
}