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 &lt;property/&gt;.
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}