View Javadoc

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