001package org.andromda.core.metafacade;
002
003import java.io.Serializable;
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.Collection;
007import java.util.HashMap;
008import java.util.HashSet;
009import java.util.Iterator;
010import java.util.LinkedHashMap;
011import java.util.LinkedHashSet;
012import java.util.List;
013import java.util.Map;
014import java.util.Set;
015import org.andromda.core.common.AndroMDALogger;
016import org.andromda.core.common.ClassUtils;
017import org.andromda.core.common.ComponentContainer;
018import org.andromda.core.common.ExceptionUtils;
019import org.andromda.core.configuration.Namespace;
020import org.andromda.core.configuration.Namespaces;
021import org.andromda.core.namespace.BaseNamespaceComponent;
022import org.apache.commons.lang.StringUtils;
023import org.apache.log4j.Logger;
024
025/**
026 * The Metafacade mapping class. Used to map <code>metafacade</code> objects
027 * to <code>metamodel</code> objects.
028 *
029 * @author Chad Brandon
030 * @author Bob Fields
031 * @see MetafacadeMapping
032 * @see org.andromda.core.common.XmlObjectFactory
033 */
034public class MetafacadeMappings
035    extends BaseNamespaceComponent
036    implements Serializable
037{
038    private static final long serialVersionUID = 34L;
039
040    /**
041     * Holds the references to the child MetafacadeMapping instances.
042     */
043    private final Collection<MetafacadeMapping> mappings = new ArrayList<MetafacadeMapping>();
044
045    /**
046     * Holds the namespace MetafacadeMappings. This are child MetafacadeMappings
047     * keyed by namespace name.
048     */
049    private final Map<String, MetafacadeMappings> namespaceMetafacadeMappings = new HashMap<String, MetafacadeMappings>();
050
051    /**
052     * The default meta facade to use when there isn't a mapping found.
053     */
054    private Class defaultMetafacadeClass = null;
055
056    /**
057     * Constructs a new instance of this class.
058     *
059     * @return MetafacadeMappings
060     */
061    public static MetafacadeMappings newInstance()
062    {
063        return new MetafacadeMappings();
064    }
065
066    /**
067     * Adds a MetafacadeMapping instance to the set of current mappings.
068     *
069     * @param mapping the MetafacadeMapping instance.
070     */
071    public void addMapping(final MetafacadeMapping mapping)
072    {
073        ExceptionUtils.checkNull(
074            "mapping",
075            mapping);
076        ExceptionUtils.checkNull(
077            "mapping.metafacadeClass",
078            mapping.getMetafacadeClass());
079        mapping.setMetafacadeMappings(this);
080
081        // find any mappings that match, if they do we add the properties
082        // from that mapping to the existing matched mapping (so we only
083        // have one mapping containing properties that can be 'OR'ed together).
084        final MetafacadeMapping foundMapping =
085            this.findMapping(
086                new Condition()
087                {
088                    public boolean evaluate(final MetafacadeMapping object)
089                    {
090                        return mapping.match(object);
091                    }
092                });
093        if (foundMapping != null)
094        {
095            foundMapping.addMappingPropertyGroup(mapping.getMappingProperties());
096        }
097        else
098        {
099            this.mappings.add(mapping);
100            this.mappingsByMetafacadeClass.put(
101                this.getMetafacadeInterface(mapping.getMetafacadeClass()),
102                mapping);
103        }
104    }
105
106    /**
107     * Gets the class of the metafacade interface that belongs to the given
108     * <code>metafacadeClass</code>.
109     * @param metafacadeClass
110     *
111     * @return the metafacade interface Class.
112     */
113    public Class getMetafacadeInterface(final Class metafacadeClass)
114    {
115        Class metafacadeInterface = null;
116        if (metafacadeClass != null)
117        {
118            metafacadeInterface = metafacadeClass;
119            final List<Class> interfaces = ClassUtils.getAllInterfaces(metafacadeClass);
120            if (interfaces != null && !interfaces.isEmpty())
121            {
122                metafacadeInterface = interfaces.iterator().next();
123            }
124        }
125        return metafacadeInterface;
126    }
127
128    /**
129     * Stores mappings by the metafacade class so that we can retrieve the
130     * inherited metafacade classes.
131     */
132    private final Map<Class, MetafacadeMapping> mappingsByMetafacadeClass = new HashMap<Class, MetafacadeMapping>();
133
134    /**
135     * Copies all data from <code>mappings<code> to this instance.
136     *
137     * @param mappings the mappings to add
138     */
139    private void copyMappings(final MetafacadeMappings mappings)
140    {
141        ExceptionUtils.checkNull(
142            "mappings",
143            mappings);
144        for (final MetafacadeMapping mapping : mappings.mappings)
145        {
146            this.addMapping(mapping);
147        }
148        final Collection<String> propertyReferences = mappings.getPropertyReferences();
149        if (propertyReferences != null && !propertyReferences.isEmpty())
150        {
151            this.propertyReferences.addAll(propertyReferences);
152        }
153        this.defaultMetafacadeClass = mappings.defaultMetafacadeClass;
154    }
155
156    /**
157     * Contains references to properties populated in the Namespaces.
158     */
159    private final Collection<String> propertyReferences = new LinkedHashSet<String>();
160
161    /**
162     * Gets all property references defined in this mappings instance.
163     *
164     * @return the map of property references (names and values).
165     */
166    public Collection<String> getPropertyReferences()
167    {
168        return this.propertyReferences;
169    }
170
171    /**
172     * <p> Retrieves the MetafacadeMapping belonging to the unique
173     * <code>key</code> created from the <code>mappingObject</code>'s
174     * class, <code>context</code> and given <code>stereotypes</code>. It's
175     * <strong>IMPORTANT </strong> to note that contexts have a higher priority
176     * than stereotypes. This allows us to retrieve mappings based on the
177     * following combinations:
178     * <ul>
179     * <li>A single stereotype no context</li>
180     * <li>A single stereotype with a context</li>
181     * <li>metafacade properties no context</li>
182     * <li>metafacade properties with a context</code>
183     * <li>multiple stereotypes no context</li>
184     * <li>multiple stereotypes with a context</li>
185     * </ul>
186     * </p>
187     * <p> NOTE: mapping properties are inherited from super metafacades.
188     * </p>
189     *
190     * @param mappingObject an instance of the class to which the mapping
191     *        applies.
192     * @param stereotypes the stereotypes to check.
193     * @param context the context within the namespace for which the mapping
194     *        applies (has 'root' in the name because of the fact that we also
195     *        search the context inheritance hierarchy started with this 'root'
196     *        context).
197     * @return MetafacadeMapping (or null if none was found matching the
198     *         criteria).
199     */
200    protected MetafacadeMapping getMapping(
201        final Object mappingObject,
202        final String context,
203        final Collection<String> stereotypes)
204    {
205        MetafacadeMapping mapping = this.getMapping(
206                null,
207                mappingObject,
208                context,
209                stereotypes);
210        if (mapping == null)
211        {
212            final Collection<String> hierarchy = this.getMappingObjectHierarchy(mappingObject);
213            if (hierarchy != null && !hierarchy.isEmpty())
214            {
215                for (final Iterator<String> iterator = hierarchy.iterator(); iterator.hasNext() && mapping == null;)
216                {
217                    mapping =
218                        this.getMapping(
219                            iterator.next(),
220                            mappingObject,
221                            context,
222                            stereotypes);
223                }
224            }
225        }
226        return mapping;
227    }
228
229    /**
230     * The cache containing the hierarchies for each mapping object so that we
231     * don't need to retrieve more than once.
232     */
233    private final Map<Object, Collection<String>> mappingObjectHierarchyCache = new HashMap<Object, Collection<String>>();
234
235    /**
236     * The pattern used for substituting the package name.
237     */
238    private static final String METAFACADE_PACKAGE_REPLACE_PATTERN = "\\{0\\}";
239
240    /**
241     * The pattern used for substituting the metafacade name.
242     */
243    private static final String METAFACADE_NAME_REPLACE_PATTERN = "\\{1\\}";
244
245    /**
246     * Retrieves the hierarchy of class names of the given
247     * <code>mappingObject</code>.
248     *
249     * @param mappingObject the object from which to retrieve the hierarchy.
250     * @return a list containing all inherited class names.
251     */
252    protected Collection<String> getMappingObjectHierarchy(final Object mappingObject)
253    {
254        Collection<String> hierarchy = this.mappingObjectHierarchyCache.get(mappingObject);
255        if (hierarchy == null)
256        {
257            // - we construct the mapping object name from the metafacade interface
258            //  (using the underlying UML implementation name pattern).
259            final String pattern = this.getMetaclassPattern();
260            if (StringUtils.isNotBlank(pattern))
261            {
262                hierarchy = new ArrayList<String>();
263                List<Class> metafacadeInterfaces = ClassUtils.getAllInterfaces(mappingObject.getClass());
264                for (final Class metafacadeInterface : metafacadeInterfaces)
265                {
266                    final String packageName = ClassUtils.getPackageName(metafacadeInterface);
267                    final String name = ClassUtils.getShortClassName(metafacadeInterface);
268
269                    // - replace references {0} with the package name and
270                    //   references of {1} with the name of the class
271                    final String metafacadeImplementationName =
272                        pattern != null
273                        ? pattern.replaceAll(
274                            METAFACADE_PACKAGE_REPLACE_PATTERN,
275                            packageName).replaceAll(
276                            METAFACADE_NAME_REPLACE_PATTERN,
277                            name) : metafacadeInterface.getName();
278                    hierarchy.add(metafacadeImplementationName);
279                }
280                this.mappingObjectHierarchyCache.put(
281                    mappingObject,
282                    hierarchy);
283            }
284        }
285        return hierarchy;
286    }
287
288    /**
289     * <p>
290     * Stores the mappings which are currently "in process" (within the
291     * {@link #getMapping(Object, String, Collection)}. This means the mapping
292     * is being processed by the {@link #getMapping(Object, String, Collection)}
293     * operation. We store these "in process" mappings in order to keep track of
294     * the mappings currently being evaluated so we avoid stack over flow errors
295     * {@link #getMapping(Object, String, Collection)}when finding mappings
296     * that are mapped to super metafacade properties.
297     * </p>
298     * <p>
299     * Note: visibility is defined as <code>protected</code> in order to
300     * improve inner class access performance.
301     * </p>
302     */
303    protected final Collection<MetafacadeMapping> inProcessMappings = new ArrayList<MetafacadeMapping>();
304
305    /**
306     * <p>
307     * Stores the metafacades which are currently "in process" (within the
308     * {@link #getMapping(Object, String, Collection)}. This means the
309     * metafacade being processed by the {@link #getMapping(Object, String,
310     * Collection)}operation. We store these "in process" metafacades in order
311     * to keep track of the metafacades currently being evaluated so we avoid
312     * stack over flow errors {@link #getMapping(Object, String, Collection)}when
313     * finding metafacades that are mapped to super metafacade properties.
314     * </p>
315     * <p>
316     * Note: visibility is defined as <code>protected</code> in order to
317     * improve inner class access performance.
318     * </p>
319     */
320    protected final Collection<MetafacadeBase> inProcessMetafacades = new ArrayList<MetafacadeBase>();
321
322    /**
323     * <p>
324     * Retrieves the MetafacadeMapping belonging to the unique <code>key</code>
325     * created from the <code>mappingObject</code>'s class,
326     * <code>context</code> and given <code>stereotypes</code>. It's
327     * <strong>IMPORTANT </strong> to note that contexts have a higher priority
328     * than stereotypes. This allows us to retrieve mappings based on the
329     * following combinations:
330     * <ul>
331     * <li>A single stereotype no context</li>
332     * <li>A single stereotype with a context</li>
333     * <li>metafacade properties no context</li>
334     * <li>metafacade properties with a context</li>
335     * <li>multiple stereotypes no context</li>
336     * <li>multiple stereotypes with a context</li>
337     * </ul>
338     * </p>
339     * <p>
340     * NOTE: mapping properties are inherited from super metafacades.
341     * </p>
342     *
343     * @param mappingClassName the name of the mapping class to use instead of
344     *        the actual class name taken from the <code>mappingObject</code>.
345     *        If null then the class name from the <code>mappingObject</code>
346     *        is used.
347     * @param mappingObject an instance of the class to which the mapping
348     *        applies.
349     * @param stereotypes the stereotypes to check.
350     * @param context the context within the namespace for which the mapping
351     *        applies (has 'root' in the name because of the fact that we also
352     *        search the context inheritance hierarchy started with this 'root'
353     *        context).
354     * @return MetafacadeMapping (or null if none was found matching the
355     *         criteria).
356     */
357    private MetafacadeMapping getMapping(
358        final String mappingClassName,
359        final Object mappingObject,
360        final String context,
361        final Collection<String> stereotypes)
362    {
363        final String metaclassName = mappingClassName != null ? mappingClassName : mappingObject.getClass().getName();
364
365        // - Verify we can at least find the meta class, so we don't perform the
366        //   rest of the search for nothing
367        final boolean validMetaclass =
368            this.findMapping(
369                new Condition()
370                {
371                    public boolean evaluate(final MetafacadeMapping mapping)
372                    {
373                        return mapping.getMappingClassNames().contains(metaclassName);
374                    }
375                }) != null;
376        MetafacadeMapping mapping = null;
377        if (validMetaclass)
378        {
379            final boolean emptyStereotypes = stereotypes == null || stereotypes.isEmpty();
380
381            // - first try to find the mapping by context and stereotypes
382            if (context != null && !emptyStereotypes)
383            {
384                mapping =
385                    this.findMapping(
386                        new Condition()
387                        {
388                            public boolean evaluate(final MetafacadeMapping mapping)
389                            {
390                                boolean valid = false;
391                                if (mapping.getMappingClassNames().contains(metaclassName) && mapping.hasContext() &&
392                                    mapping.hasStereotypes() && !mapping.hasMappingProperties())
393                                {
394                                    valid =
395                                        getContextHierarchy(context).contains(mapping.getContext()) &&
396                                        stereotypes!=null && stereotypes.containsAll(mapping.getStereotypes());
397                                }
398                                return valid;
399                            }
400                        });
401            }
402
403            // - check for context and metafacade properties
404            if (mapping == null && context != null)
405            {
406                mapping =
407                    this.findMapping(
408                        new Condition()
409                        {
410                            public boolean evaluate(final MetafacadeMapping mapping)
411                            {
412                                boolean valid = false;
413                                if (mapping.getMappingClassNames().contains(metaclassName) && !mapping.hasStereotypes() &&
414                                    mapping.hasContext() && mapping.hasMappingProperties() &&
415                                    !inProcessMappings.contains(mapping))
416                                {
417                                    if (getContextHierarchy(context).contains(mapping.getContext()))
418                                    {
419                                        inProcessMappings.add(mapping);
420                                        final MetafacadeBase metafacade =
421                                            MetafacadeFactory.getInstance().createMetafacade(
422                                                mappingObject,
423                                                mapping);
424                                        inProcessMetafacades.add(metafacade);
425
426                                        // reset the "in process" mappings
427                                        inProcessMappings.clear();
428                                        valid =
429                                            MetafacadeUtils.propertiesValid(
430                                                metafacade,
431                                                mapping);
432                                    }
433                                }
434                                return valid;
435                            }
436                        });
437            }
438
439            // - check just the context alone
440            if (mapping == null && context != null)
441            {
442                mapping =
443                    this.findMapping(
444                        new Condition()
445                        {
446                            public boolean evaluate(final MetafacadeMapping mapping)
447                            {
448                                boolean valid = false;
449                                if (mapping.getMappingClassNames().contains(metaclassName) && mapping.hasContext() &&
450                                    !mapping.hasStereotypes() && !mapping.hasMappingProperties())
451                                {
452                                    valid = getContextHierarchy(context).contains(mapping.getContext());
453                                }
454                                return valid;
455                            }
456                        });
457            }
458
459            // check only stereotypes
460            if (mapping == null && !emptyStereotypes)
461            {
462                mapping =
463                    this.findMapping(
464                        new Condition()
465                        {
466                            public boolean evaluate(final MetafacadeMapping mapping)
467                            {
468                                boolean valid = false;
469                                if (mapping.getMappingClassNames().contains(metaclassName) && mapping.hasStereotypes() &&
470                                    !mapping.hasContext() && !mapping.hasMappingProperties())
471                                {
472                                    valid = stereotypes!=null && stereotypes.containsAll(mapping.getStereotypes());
473                                }
474                                return valid;
475                            }
476                        });
477            }
478
479            // - now check for metafacade properties
480            if (mapping == null)
481            {
482                mapping =
483                    this.findMapping(
484                        new Condition()
485                        {
486                            public boolean evaluate(final MetafacadeMapping mapping)
487                            {
488                                boolean valid = false;
489                                if (mapping.getMappingClassNames().contains(metaclassName) && !mapping.hasStereotypes() &&
490                                    !mapping.hasContext() && mapping.hasMappingProperties() &&
491                                    (!inProcessMappings.contains(mapping)))
492                                {
493                                    inProcessMappings.add(mapping);
494                                    final MetafacadeBase metafacade =
495                                        MetafacadeFactory.getInstance().createMetafacade(
496                                            mappingObject,
497                                            mapping);
498                                    inProcessMetafacades.add(metafacade);
499
500                                    // reset the "in process" mappings
501                                    inProcessMappings.clear();
502                                    valid =
503                                        MetafacadeUtils.propertiesValid(
504                                            metafacade,
505                                            mapping);
506                                }
507                                return valid;
508                            }
509                        });
510            }
511
512            // - finally find the mapping with just the class
513            if (mapping == null)
514            {
515                mapping =
516                    this.findMapping(
517                        new Condition()
518                        {
519                            public boolean evaluate(final MetafacadeMapping mapping)
520                            {
521                                return mapping.getMappingClassNames().contains(metaclassName) && !mapping.hasContext() &&
522                                !mapping.hasStereotypes() && !mapping.hasMappingProperties();
523                            }
524                        });
525            }
526        }
527
528        // - if it's still null, try with the parent
529        if (mapping == null && this.getParent() != null)
530        {
531            mapping =
532                this.getParent().getMapping(
533                    metaclassName,
534                    mappingObject,
535                    context,
536                    stereotypes);
537        }
538
539        // - reset the "in process" metafacades
540        this.inProcessMetafacades.clear();
541        return mapping;
542    }
543
544    /**
545     * Finds the first mapping in the internal {@link #mappings} collection that
546     * matches the given condition.
547     *
548     * @param condition the condition
549     * @return the found mapping instance
550     */
551    private MetafacadeMapping findMapping(final Condition condition)
552    {
553        MetafacadeMapping found = null;
554        for (final MetafacadeMapping mapping : this.mappings)
555        {
556            if (condition.evaluate(mapping))
557            {
558                found = mapping;
559                break;
560            }
561        }
562        return found;
563    }
564
565    /**
566     * Provides a means to evaluate whether or not a condition is true.
567     */
568    static interface Condition
569    {
570        /**
571         * @param mapping
572         * @return true/false
573         */
574        public boolean evaluate(final MetafacadeMapping mapping);
575    }
576
577    /**
578     * <p>
579     * Loads all property references into the given <code>mapping</code>
580     * inherited from any super metafacade of the given mapping's metafacade.
581     * </p>
582     *
583     * @param mapping the MetafacadeMapping to which we'll add the inherited
584     *        property references.
585     */
586    private void loadInheritedPropertyReferences(final MetafacadeMapping mapping)
587    {
588        if (mapping != null)
589        {
590            final Class[] interfaces = this.getInterfacesReversed(mapping.getMetafacadeClass().getName());
591            if (interfaces != null && interfaces.length > 0)
592            {
593                for (final Class metafacadeClass : interfaces)
594                {
595                    final MetafacadeMapping contextMapping =
596                            this.mappingsByMetafacadeClass.get(metafacadeClass);
597                    if (contextMapping != null)
598                    {
599                        // add all property references
600                        mapping.addPropertyReferences(contextMapping.getPropertyReferences());
601                    }
602                }
603            }
604        }
605    }
606
607    /**
608     * The cache containing the hierarchies for each context so that we don't
609     * need to retrieve more than once.
610     */
611    private final Map<String, List<String>> contextHierarchyCache = new HashMap<String, List<String>>();
612
613    /**
614     * Retrieves all inherited contexts (including the root <code>context</code>)
615     * from the given <code>context</code> and returns a list containing all
616     * of them. Note that the visibility of this operation is protected to
617     * improve inner class access performance.
618     *
619     * @param context the root contexts
620     * @return a list containing all inherited contexts
621     */
622    protected final List<String> getContextHierarchy(final String context)
623    {
624        List<String> contexts = this.contextHierarchyCache.get(context);
625        if (contexts == null)
626        {
627            final List<Class> interfaces = ClassUtils.getInterfaces(context);
628            contexts = new ArrayList<String>(interfaces.size());
629            for (Class anInterface : interfaces)
630            {
631                contexts.add(anInterface.getName());
632            }
633            this.contextHierarchyCache.put(
634                context,
635                contexts);
636        }
637        return contexts;
638    }
639
640    /**
641     * The cache of interfaces for the given className in reversed order.
642     */
643    private final Map<String, Class[]> reversedInterfaceArrayCache = new HashMap<String, Class[]>();
644
645    /**
646     * Gets the interfaces for the given <code>className</code> in reverse
647     * order.
648     *
649     * @param className the name of the class for which to retrieve the
650     *        interfaces
651     * @return the array containing the reversed interfaces.
652     */
653    private Class[] getInterfacesReversed(final String className)
654    {
655        Class[] interfaces = this.reversedInterfaceArrayCache.get(className);
656        if (interfaces == null)
657        {
658            interfaces = ClassUtils.getInterfacesReversed(className);
659            this.reversedInterfaceArrayCache.put(
660                className,
661                interfaces);
662        }
663        return interfaces;
664    }
665
666    /**
667     * Adds a language mapping reference. This are used to populate metafacade
668     * impl classes with mapping files (such as those that map from model types
669     * to Java, JDBC, SQL types, etc). If its added here as opposed to each
670     * child MetafacadeMapping, then the reference will apply to all mappings.
671     *
672     * @param reference the name of the reference.
673     */
674    public void addPropertyReference(final String reference)
675    {
676        this.propertyReferences.add(reference);
677    }
678
679    /**
680     * <p> Attempts to get the MetafacadeMapping identified by the given
681     * <code>mappingClass</code>,<code>context</code> and
682     * <code>stereotypes<code>, from the mappings for the given <code>namespace</code>. If it can <strong>not</strong>
683     * be found, it will search the default mappings and return that instead. </p>
684     * <p>
685     * <strong>IMPORTANT:</strong> The <code>context</code> will take precedence over any <code>stereotypes</code> with
686     * the mapping. </p>
687     *
688     * @param mappingObject the meta object for the mapping we are trying to find.
689     * @param namespace the namespace (i.e. a cartridge, name, etc.)
690     * @param context to which the mapping applies (note this takes precedence over stereotypes).
691     * @param stereotypes collection of stereotype names.  We'll check to see if the mapping for the given
692     *                    <code>mappingClass</code> is defined for it.
693     * @return mapping
694     */
695    public MetafacadeMapping getMetafacadeMapping(
696        final Object mappingObject,
697        final String namespace,
698        final String context,
699        final Collection<String> stereotypes)
700    {
701        if (this.getLogger().isDebugEnabled())
702        {
703            this.getLogger().debug(
704                "performing 'MetafacadeMappings.getMetafacadeMapping' with mappingObject '" + mappingObject +
705                "', stereotypes '" + stereotypes + "', namespace '" + namespace + "' and context '" + context + '\'');
706        }
707
708        MetafacadeMapping mapping = null;
709
710        final MetafacadeMappings mappings = this.getNamespaceMappings(namespace);
711
712        // first try the namespace mappings
713        if (mappings != null)
714        {
715            // - set the parent namespace
716            mappings.parentNamespace = this.getNamespace();
717            mapping =
718                mappings.getMapping(
719                    mappingObject,
720                    context,
721                    stereotypes);
722        }
723
724        // - if we've found a namespace mapping, try to get any shared mappings
725        //   that this namespace mapping may extend and copy over any property
726        //   references from the shared mapping to the namespace mapping.
727        if (mapping != null)
728        {
729            final Collection<String> propertyReferences = mapping.getPropertyReferences();
730            final MetafacadeMapping defaultMapping = this.getMapping(
731                    mappingObject,
732                    context,
733                    stereotypes);
734            if (defaultMapping != null)
735            {
736                Collection<String> defaultPropertyReferences = defaultMapping.getPropertyReferences();
737                final Class metafacadeInterface =
738                    this.metafacadeClasses.getMetafacadeClass(mapping.getMetafacadeClass().getName());
739                final Class defaultMetafacadeInterface =
740                    this.metafacadeClasses.getMetafacadeClass(defaultMapping.getMetafacadeClass().getName());
741                if (defaultMetafacadeInterface.isAssignableFrom(metafacadeInterface))
742                {
743                    mapping.addPropertyReferences(defaultPropertyReferences);
744
745                    // add the namespace property references back so
746                    // that the default ones don't override the
747                    // namespace specific ones.
748                    mapping.addPropertyReferences(propertyReferences);
749                }
750            }
751        }
752
753        // if the namespace mappings weren't found, try the default
754        if (mapping == null)
755        {
756            if (this.getLogger().isDebugEnabled())
757            {
758                this.getLogger().debug("namespace mapping not found --> finding default");
759            }
760            mapping =
761                this.getMapping(
762                    mappingObject,
763                    context,
764                    stereotypes);
765        }
766
767        if (this.getLogger().isDebugEnabled())
768        {
769            this.getLogger().debug("found mapping --> '" + mapping + '\'');
770        }
771        return mapping;
772    }
773
774    /**
775     * Gets the MetafacadeMappings instance belonging to the
776     * <code>namespace</code>.
777     *
778     * @param namespace the namespace name to check.
779     * @return the found MetafacadeMappings.
780     */
781    private MetafacadeMappings getNamespaceMappings(final String namespace)
782    {
783        return this.namespaceMetafacadeMappings.get(namespace);
784    }
785
786    /**
787     * Stores the possible parents of this metafacade mappings instance (i.e. mappings for uml-1.4, emf-uml2, etc).
788     */
789    private Map<String, MetafacadeMappings> parents = new HashMap<String, MetafacadeMappings>();
790
791    /**
792     * Retrieves the appropriate parent based on the current {@link #getNamespace()}.
793     *
794     * @return the parent metafacade mappings.
795     */
796    private MetafacadeMappings getParent()
797    {
798        return this.parents.get(this.parentNamespace);
799    }
800
801    /**
802     * Adds a MetafacadeMappings instance to the namespace metafacade mappings
803     * of this instance.
804     *
805     * @param namespace the namespace name to which the <code>mappings</code>
806     *        will belong.
807     * @param mappings the MetafacadeMappings instance to add.
808     */
809    private void addNamespaceMappings(
810        final String namespace,
811        final MetafacadeMappings mappings)
812    {
813        if (mappings != null)
814        {
815            // - set the parent by its namespace (the parent is different depending on the current metafacade model namespace)
816            mappings.parents.put(
817                this.getNamespace(),
818                this);
819            this.namespaceMetafacadeMappings.put(
820                namespace,
821                mappings);
822        }
823    }
824
825    /**
826     * Initializes this mappings instance, which includes discovery of all
827     * metafacade mappings instances on the classpath.
828     */
829    public void initialize()
830    {
831        final List<String> modelTypeNamespaces = new ArrayList<String>();
832        final Collection<MetafacadeMappings> metafacades = ComponentContainer.instance().findComponentsOfType(MetafacadeMappings.class);
833        for (final MetafacadeMappings mappings : metafacades)
834        {
835            final String namespace = mappings.getNamespace();
836            if (MetafacadeUtils.isMetafacadeModelPresent(namespace))
837            {
838                modelTypeNamespaces.add(namespace);
839            }
840        }
841
842        final String[] modelNamespaces = modelTypeNamespaces.toArray(new String[modelTypeNamespaces.size()]);
843        MetafacadeImpls.instance().discover(modelNamespaces);
844        this.initializeMappings(modelNamespaces);
845    }
846
847    /**
848     * Registers all namespace properties in the shared {@link MetafacadeFactory} instance.
849     */
850    final void registerAllProperties()
851    {
852        // - register all namespace property references defined in the descriptors
853        final Namespaces namespaces = Namespaces.instance();
854        for (Namespace namespace1 : namespaces.getNamespaces())
855        {
856            final String mappingsNamespace = namespace1.getName();
857
858            // - add the default mappings
859            final Collection<MetafacadeMapping> mappings = new ArrayList<MetafacadeMapping>(this.mappings);
860            final MetafacadeMappings metafacadeMappings = this.getNamespaceMappings(mappingsNamespace);
861
862            // - add all the references from the default namespace
863            final Collection<String> propertyReferences = new ArrayList<String>(this.propertyReferences);
864
865            // - if we have namespace mappings, add them
866            if (metafacadeMappings != null)
867            {
868                mappings.addAll(metafacadeMappings.mappings);
869                propertyReferences.addAll(metafacadeMappings.propertyReferences);
870            }
871
872            for (final MetafacadeMapping mapping : mappings)
873            {
874                final String metafacadeInterface =
875                        this.metafacadeClasses.getMetafacadeClass(mapping.getMetafacadeClass().getName()).getName();
876
877                // - first register the references defined globally in the
878                // descriptor for each interface
879                // in the hierarchy
880                final Class[] interfaces = this.getInterfacesReversed(metafacadeInterface);
881                for (final Class anInterface : interfaces)
882                {
883                    this.registerProperties(
884                            mappingsNamespace,
885                            propertyReferences,
886                            anInterface.getName());
887                }
888
889                // - next register the references defined only within each mapping
890                // - remember to first load the inherited property references
891                //   into the mapping
892                this.loadInheritedPropertyReferences(mapping);
893                this.registerProperties(
894                        mappingsNamespace,
895                        mapping.getPropertyReferences(),
896                        metafacadeInterface);
897            }
898        }
899    }
900
901    /**
902     * The name of the metaclass pattern.
903     */
904    private String metaclassPattern;
905
906    /**
907     * First attempts to retrieve the metaclass pattern from this instance, and
908     * if not found, attempts to retrieve it from the parent instance (since the
909     * parent instance should always have been set at least once from a shared
910     * metafacades instance).
911     *
912     * @return the metaclass pattern.
913     */
914    private String getMetaclassPattern()
915    {
916        if (this.metaclassPattern == null && this.getParent() != null)
917        {
918            this.metaclassPattern = this.getParent().metaclassPattern;
919        }
920        return this.metaclassPattern;
921    }
922
923    /**
924     * Sets the pattern of the metaclass implementations based on a metaclass
925     * interface name. This should only be set on a metafacade mappings
926     * instances that is marked as shared.
927     *
928     * @param metaclassPattern the pattern for the meta classes.
929     */
930    public void setMetaclassPattern(final String metaclassPattern)
931    {
932        this.metaclassPattern = metaclassPattern;
933    }
934
935    /**
936     * Initializes all the metafacade mapping instances under the appropriate model type (defined
937     * in the <code>modelTypes</code> collection.
938     *
939     * @param metafacadeModelNamespaces a list of each namespace containing a metafacade model facade implementation.
940     */
941    private void initializeMappings(final String[] metafacadeModelNamespaces)
942    {
943        ExceptionUtils.checkNull(
944            "modelTypes",
945            metafacadeModelNamespaces);
946        final Collection<MetafacadeMappings> metafacades = ComponentContainer.instance().findComponentsOfType(MetafacadeMappings.class);
947
948        // - we need to load up the allMetafacadeMappingInstances before we do
949        //   anything else
950        for (final MetafacadeMappings mappings : metafacades)
951        {
952            for (final MetafacadeMapping mapping : mappings.mappings)
953            {
954                if (mapping.isMappingClassNamePresent())
955                {
956                    Set<String> mappingClassNames = MetafacadeMappings.allMetafacadeMappingInstances.get(mapping.getMetafacadeClass());
957                    if (mappingClassNames == null)
958                    {
959                        mappingClassNames = new HashSet<String>();
960                        MetafacadeMappings.allMetafacadeMappingInstances.put(mapping.getMetafacadeClass(), mappingClassNames);
961                    }
962
963                    mappingClassNames.addAll(mapping.getMappingClassNames());
964                }
965            }
966        }
967
968        final List<String> modelNamespaces = new ArrayList<String>(Arrays.asList(metafacadeModelNamespaces));
969        try
970        {
971            final Namespaces namespaces = Namespaces.instance();
972            for (final String modelNamespace : metafacadeModelNamespaces)
973            {
974                if (modelNamespace != null)
975                {
976                    // - remove the current model type so that we don't keep out the namespace
977                    //   that stores the metafacade model
978                    modelNamespaces.remove(modelNamespace);
979
980                    MetafacadeMappings modelMetafacadeMappings =
981                            this.modelMetafacadeMappings.get(modelNamespace);
982                    if (modelMetafacadeMappings == null)
983                    {
984                        modelMetafacadeMappings = MetafacadeMappings.newInstance();
985
986                        // - set the namespace
987                        modelMetafacadeMappings.setNamespace(modelNamespace);
988                        this.modelMetafacadeMappings.put(
989                                modelNamespace,
990                                modelMetafacadeMappings);
991                    }
992
993                    for (final MetafacadeMappings mappings : metafacades)
994                    {
995                        final String namespace = mappings.getNamespace();
996
997                        if (!modelNamespaces.contains(namespace))
998                        {
999                            // - if we have 'shared' mappings or only a single set available, they are copied
1000                            //   to this mappings instance.
1001                            if (namespaces.isShared(namespace) || metafacades.size() == 1)
1002                            {
1003                                // - copy over any 'shared' mappings to this root instance
1004                                modelMetafacadeMappings.copyMappings(mappings);
1005
1006                                // - set the metaclass pattern from the 'shared' or single
1007                                //   instance of metafacades
1008                                final String metaclassPattern = mappings.metaclassPattern;
1009                                if (StringUtils.isNotBlank(metaclassPattern))
1010                                {
1011                                    modelMetafacadeMappings.setMetaclassPattern(mappings.metaclassPattern);
1012                                }
1013                            }
1014                            else
1015                            {
1016                                // add all others as namespace mappings
1017                                modelMetafacadeMappings.addNamespaceMappings(
1018                                        namespace,
1019                                        mappings);
1020                            }
1021                        }
1022                    }
1023
1024                    // - add the metafacade model namespace back
1025                    modelNamespaces.add(modelNamespace);
1026                    if (StringUtils.isBlank(modelMetafacadeMappings.getNamespace()))
1027                    {
1028                        throw new MetafacadeMappingsException(
1029                                "No shared metafacades found, please check your classpath, at least " +
1030                                        "one set of metafacades must be marked as 'shared'");
1031                    }
1032                    if (StringUtils.isBlank(modelMetafacadeMappings.metaclassPattern))
1033                    {
1034                        throw new MetafacadeMappingsException("At least one set of metafacades marked as shared " +
1035                                "must have the 'metaclassPattern' attribute defined");
1036                    }
1037                }
1038            }
1039        }
1040        catch (final Throwable throwable)
1041        {
1042            throw new MetafacadeMappingsException(throwable);
1043        }
1044        this.getLogger().debug("initializeMappings " + " size=" + MetafacadeMappings.allMetafacadeMappingInstances.size());
1045    }
1046
1047    /**
1048     * Stores all metafacade mapping instances
1049     */
1050    private static final Map<Class, Set<String>> allMetafacadeMappingInstances = new HashMap<Class, Set<String>>();
1051
1052    /**
1053     * Stores every metafacade mapping instance, this is used from
1054     * {@link MetafacadeUtils#getInheritedMappingClassNames(MetafacadeMapping)}.
1055     *
1056     * @return all metafacade mapping instances.
1057     */
1058    static Map<Class, Set<String>> getAllMetafacadeMappingInstances()
1059    {
1060        return allMetafacadeMappingInstances;
1061    }
1062
1063    /**
1064     * The shared metafacade impls instance.
1065     */
1066    private MetafacadeImpls metafacadeClasses = MetafacadeImpls.instance();
1067
1068    /**
1069     * Stores the metafacadeMapping instances by model type.
1070     */
1071    private Map<String, MetafacadeMappings> modelMetafacadeMappings = new LinkedHashMap<String, MetafacadeMappings>();
1072
1073    /**
1074     * Should be used used instead of "this", retrieves the appropriate
1075     * metafacade mappings instance based on the current model type.
1076     *
1077     * @param metafacadeModelNamespace the namespace that contains a metafacade model facade implementation.
1078     * @return the {@link MetafacadeMappings} instance.
1079     */
1080    public MetafacadeMappings getModelMetafacadeMappings(final String metafacadeModelNamespace)
1081    {
1082        final MetafacadeMappings instance =
1083                this.modelMetafacadeMappings.get(metafacadeModelNamespace);
1084        if (instance == null)
1085        {
1086            throw new MetafacadeMappingsException("Namespace '" + metafacadeModelNamespace +
1087                "' is not a registered metafacade model namespace");
1088        }
1089        return instance;
1090    }
1091
1092    /**
1093     * Stores the namespace of the parent mappings.
1094     */
1095    private String parentNamespace;
1096
1097    /**
1098     * Gets the defaultMetafacadeClass, first looks for it in the namespace
1099     * mapping, if it can't find it it then takes the default mappings, setting.
1100     * @param namespace mapping to check for defaultMetafacadeClass
1101     * @return Returns the defaultMetafacadeClass.
1102     */
1103    final Class getDefaultMetafacadeClass(final String namespace)
1104    {
1105        Class defaultMetafacadeClass = null;
1106        MetafacadeMappings mappings = this.getNamespaceMappings(namespace);
1107        if (mappings != null)
1108        {
1109            defaultMetafacadeClass = mappings.defaultMetafacadeClass;
1110        }
1111        if (defaultMetafacadeClass == null)
1112        {
1113            defaultMetafacadeClass = this.defaultMetafacadeClass;
1114        }
1115        return defaultMetafacadeClass;
1116    }
1117
1118    /**
1119     * Sets the default metafacade class to use if no other is found for the
1120     * mapping class.
1121     *
1122     * @param defaultMetafacadeClass the default metafacade class.
1123     */
1124    public void setDefaultMetafacadeClass(final String defaultMetafacadeClass)
1125    {
1126        try
1127        {
1128            this.defaultMetafacadeClass = ClassUtils.loadClass(StringUtils.trimToEmpty(defaultMetafacadeClass));
1129        }
1130        catch (final Throwable throwable)
1131        {
1132            throw new MetafacadeMappingsException(throwable);
1133        }
1134    }
1135
1136    /**
1137     * Retrieves all child {@link MetafacadeMapping} instances belonging to this
1138     * metafacade mappings instance.
1139     *
1140     * @return the collection of {@link MetafacadeMapping} instances
1141     */
1142    protected Collection<MetafacadeMapping> getMappings()
1143    {
1144        return this.mappings;
1145    }
1146
1147    /**
1148     * Registers the defined property references properties in the metafacade
1149     * factory.
1150     *
1151     * @param propertyReferences the property references to register.
1152     * @param metafacadeName the name of the metafacade under which to register
1153     *        the properties.
1154     * @param namespace the namespace of the property reference.
1155     */
1156    final void registerProperties(
1157        final String namespace,
1158        final Collection<String> propertyReferences,
1159        final String metafacadeName)
1160    {
1161        final MetafacadeFactory factory = MetafacadeFactory.getInstance();
1162        for (final String reference : propertyReferences)
1163        {
1164            final String value = Namespaces.instance().getPropertyValue(
1165                    namespace,
1166                    reference);
1167            if (value != null)
1168            {
1169                if (this.getLogger().isDebugEnabled())
1170                {
1171                    this.getLogger().debug(
1172                            "setting context property '" + reference + "' with value '" + value + "' for namespace '" +
1173                                    namespace + "' on metafacade '" + metafacadeName + '\'');
1174                }
1175            }
1176            factory.registerProperty(
1177                    namespace,
1178                    metafacadeName,
1179                    reference,
1180                    value);
1181        }
1182    }
1183
1184    /**
1185     * Performs shutdown procedures for the factory. This should be called
1186     * <strong>ONLY</code> when {@link MetafacadeFactory#shutdown()}is called.
1187     */
1188    final void shutdown()
1189    {
1190        this.mappings.clear();
1191        this.inProcessMappings.clear();
1192        this.inProcessMetafacades.clear();
1193        this.namespaceMetafacadeMappings.clear();
1194        this.propertyReferences.clear();
1195        this.mappingObjectHierarchyCache.clear();
1196        this.mappingsByMetafacadeClass.clear();
1197        this.contextHierarchyCache.clear();
1198        this.reversedInterfaceArrayCache.clear();
1199        for (final MetafacadeMappings metafacadeMappings : this.modelMetafacadeMappings.values())
1200        {
1201            metafacadeMappings.shutdown();
1202        }
1203        this.modelMetafacadeMappings.clear();
1204    }
1205
1206    /**
1207     * Returns the logger instance to be used for logging within this class.
1208     *
1209     * @return the plugin logger
1210     */
1211    private Logger getLogger()
1212    {
1213        return AndroMDALogger.getNamespaceLogger(this.getNamespace());
1214    }
1215
1216    /**
1217     * @see Object#toString()
1218     */
1219    public String toString()
1220    {
1221        return super.toString() + '[' + this.getNamespace() + ']';
1222    }
1223}