001package org.andromda.core.metafacade;
002
003import java.io.Serializable;
004import java.net.URL;
005import java.util.ArrayList;
006import java.util.Arrays;
007import java.util.Collection;
008import java.util.LinkedHashMap;
009import java.util.List;
010import java.util.Map;
011import org.andromda.core.common.ClassUtils;
012import org.andromda.core.common.Constants;
013import org.andromda.core.common.ExceptionUtils;
014import org.andromda.core.common.ResourceUtils;
015import org.andromda.core.configuration.Namespaces;
016import org.andromda.core.namespace.NamespaceRegistry;
017import org.apache.commons.lang.StringUtils;
018
019/**
020 * Discovers all metafacade interfaces and implementation classes in each namespace registry. This class is
021 * then used to retrieve both the appropriate metafacade interface and/or metafacade implementation class based
022 * on one or the other.
023 *
024 * @author Chad Brandon
025 * @author Bob Fields
026 */
027public class MetafacadeImpls
028    implements Serializable
029{
030    private static final long serialVersionUID = 34L;
031
032    /**
033     * The shared instance.
034     */
035    private static final MetafacadeImpls instance = new MetafacadeImpls();
036
037    /**
038     * Stores each metafacade classes instance keyed by namespace.
039     */
040    private final Map<String, MetafacadeClasses> metafacadeClasses = new LinkedHashMap<String, MetafacadeClasses>();
041
042    /**
043     * Returns the shared instance of this class.
044     *
045     * @return MetafacadeImpls the shared instance.
046     */
047    public static MetafacadeImpls instance()
048    {
049        return instance;
050    }
051
052    /**
053     * The current model type to which metafacade class retrieval applies.
054     */
055    private String metafacadeModelNamespace;
056
057    /**
058     * Sets the current model type to which this instance's metafacade class retrieval
059     * should apply.
060     *
061     * @param metafacadeModelNamespace the namespace that has the metafacade model implementation.
062     */
063    public void setMetafacadeModelNamespace(final String metafacadeModelNamespace)
064    {
065        this.metafacadeModelNamespace = metafacadeModelNamespace;
066    }
067
068    /**
069     * The extension for the metafacade implementation files.
070     */
071    private static final String METAFACADE_IMPLEMENTATION_SUFFIX =
072        MetafacadeConstants.METAFACADE_IMPLEMENTATION_SUFFIX + ClassUtils.CLASS_EXTENSION;
073
074    /**
075     * Discovers and loads all metafacade implementation classes and interfaces in each available namespace registry into
076     * each given namespace in the <code>modelTypeNamespaces</code> list.
077     * Note that this method must be called before any metafacade implementation classes will be able to be retrieved
078     * when calling {@link #getMetafacadeClass(String)}or {@link #getMetafacadeImplClass(String)}.
079     *
080     * @param metafacadeModelNamespaces a list of each namespace containing a metafacade model facade implementation.
081     */
082    public void discover(final String[] metafacadeModelNamespaces)
083    {
084        ExceptionUtils.checkNull(
085            "modelTypes",
086            metafacadeModelNamespaces);
087        final List<String> modelNamespaces = new ArrayList<String>(Arrays.asList(metafacadeModelNamespaces));
088        for (final String modelNamespace : metafacadeModelNamespaces)
089        {
090            if (modelNamespace != null)
091            {
092                // - remove the current model type so that we don't keep out the namespace
093                //   that stores the metafacade model
094                modelNamespaces.remove(modelNamespace);
095
096                MetafacadeClasses metafacadeClasses = this.metafacadeClasses.get(modelNamespace);
097                if (metafacadeClasses == null)
098                {
099                    metafacadeClasses = new MetafacadeClasses();
100                    this.metafacadeClasses.put(
101                            modelNamespace,
102                            metafacadeClasses);
103                }
104                metafacadeClasses.clear();
105                try
106                {
107                    final Namespaces namespacesConfiguration = Namespaces.instance();
108                    for (final NamespaceRegistry namespaceRegistry : namespacesConfiguration.getNamespaceRegistries())
109                    {
110                        final String namespaceRegistryName = namespaceRegistry.getName();
111                        if (!modelNamespaces.contains(namespaceRegistryName))
112                        {
113                            this.registerMetafacadeClasses(
114                                    metafacadeClasses,
115                                    namespacesConfiguration,
116                                    namespaceRegistry);
117                        }
118                    }
119                }
120                catch (final Throwable throwable)
121                {
122                    throw new MetafacadeImplsException(throwable);
123                }
124
125                // - add the metafacade model namespace back
126                modelNamespaces.add(modelNamespace);
127            }
128        }
129    }
130
131    /**
132     * Registers the metafacade classes for the given <code>namespaceRegistry</code>.
133     *
134     * @param metafacadeClasses the metafacade classes instance to store the registered metafacade classes.
135     * @param namespaces the namespaces from which we retrieve the additional namespace information.
136     * @param namespaceRegistry the registry from which we retrieve the classes.
137     */
138    private void registerMetafacadeClasses(
139        final MetafacadeClasses metafacadeClasses,
140        final Namespaces namespaces,
141        final NamespaceRegistry namespaceRegistry)
142    {
143        final String namespaceRegistryName = namespaceRegistry.getName();
144        if (namespaces.isComponentPresent(
145                namespaceRegistryName,
146                Constants.COMPONENT_METAFACADES))
147        {
148            final URL[] namespaceRoots = namespaceRegistry.getResourceRoots();
149            if (namespaceRoots != null && namespaceRoots.length > 0)
150            {
151                for (final URL namespaceRoot : namespaceRoots)
152                {
153                    final Collection<String> contents = ResourceUtils.getDirectoryContents(
154                            namespaceRoot,
155                            false,
156                            null);
157                    for (final String path : contents)
158                    {
159                        if (path.endsWith(METAFACADE_IMPLEMENTATION_SUFFIX))
160                        {
161                            final String typeName =
162                                    StringUtils.replace(
163                                            ResourceUtils.normalizePath(path).replace(
164                                                    '/',
165                                                    '.'),
166                                            ClassUtils.CLASS_EXTENSION,
167                                            "");
168                            Class implementationClass = null;
169                            try
170                            {
171                                implementationClass = ClassUtils.loadClass(typeName);
172                            }
173                            catch (final Exception exception)
174                            {
175                                // - ignore
176                            }
177                            if (implementationClass != null &&
178                                    MetafacadeBase.class.isAssignableFrom(implementationClass))
179                            {
180                                final List<Class> allInterfaces = ClassUtils.getInterfaces(implementationClass);
181                                if (!allInterfaces.isEmpty())
182                                {
183                                    final Class interfaceClass = allInterfaces.iterator().next();
184                                    final String implementationClassName = implementationClass.getName();
185                                    final String interfaceClassName = interfaceClass.getName();
186                                    metafacadeClasses.metafacadesByImpls.put(
187                                            implementationClassName,
188                                            interfaceClassName);
189                                    metafacadeClasses.implsByMetafacades.put(
190                                            interfaceClassName,
191                                            implementationClassName);
192                                }
193                            }
194                        }
195                    }
196                }
197            }
198        }
199    }
200
201    /**
202     * Attempts to retrieve the metafacade classes instance with the current active namespace
203     * and throws an exception if one can not be found.
204     *
205     * @return the metafacade classes instance.
206     */
207    private MetafacadeClasses getMetafacadeClasses()
208    {
209        final MetafacadeClasses classes = this.metafacadeClasses.get(this.metafacadeModelNamespace);
210        if (classes == null)
211        {
212            throw new MetafacadeImplsException("Namespace '" + this.metafacadeModelNamespace + "' is not a registered metafacade model facade namespace");
213        }
214        return classes;
215    }
216
217    /**
218     * Retrieves the metafacade class from the passed in <code>metafacadeImplClass</code>. Will return a
219     * MetafacadeImplsException if a metafacade class can not be found for the <code>metafacadeImplClass</code>
220     *
221     * @param metafacadeImplClass the name of the metafacade implementation class.
222     * @return the metafacade Class
223     */
224    public Class getMetafacadeClass(final String metafacadeImplClass)
225    {
226        ExceptionUtils.checkEmpty(
227            "metafacadeImplClass",
228            metafacadeImplClass);
229        return this.getMetafacadeClasses().getMetafacadeClass(metafacadeImplClass);
230    }
231
232    /**
233     * Retrieves the metafacade implementation class from the passed in <code>metafacadeClass</code>. Will return a
234     * MetafacadeImplsException if a metafacade implementation class can not be found for the
235     * <code>metafacadeClass</code>
236     *
237     * @param metafacadeClass the name of the metafacade class.
238     * @return the metafacade implementation Class
239     */
240    public Class getMetafacadeImplClass(final String metafacadeClass)
241    {
242        ExceptionUtils.checkEmpty(
243            "metafacadeClass",
244            metafacadeClass);
245        return this.getMetafacadeClasses().getMetafacadeImplClass(metafacadeClass);
246    }
247
248    /**
249     * Stores the metafacade interface and implementation classes.
250     */
251    static final class MetafacadeClasses
252    {
253        /**
254         * Stores all <code>metafacade</code> implementation classes keyed by <code>metafacade</code> interface class.
255         */
256        Map<String, String> implsByMetafacades = new LinkedHashMap<String, String>();
257
258        /**
259         * Stores all <code>metafacade</code> interface classes keyed by <code>metafacade</code> implementation class.
260         */
261        Map<String, String> metafacadesByImpls = new LinkedHashMap<String, String>();
262
263        /**
264         * Retrieves the metafacade class from the passed in <code>metafacadeImplClass</code>. Will return a
265         * MetafacadeImplsException if a metafacade class can not be found for the <code>metafacadeImplClass</code>
266         *
267         * @param metafacadeImplClass the name of the metafacade implementation class.
268         * @return the metafacade Class
269         */
270        Class getMetafacadeClass(final String metafacadeImplClass)
271        {
272            ExceptionUtils.checkEmpty(
273                "metafacadeImplClass",
274                metafacadeImplClass);
275            Class metafacadeClass = null;
276            try
277            {
278                final String metafacadeClassName = this.metafacadesByImpls.get(metafacadeImplClass);
279                if (StringUtils.isEmpty(metafacadeClassName))
280                {
281                    throw new MetafacadeImplsException("Can not find a metafacade interface for --> '" +
282                        metafacadeImplClass + "', check your classpath");
283                }
284                metafacadeClass = ClassUtils.loadClass(metafacadeClassName);
285            }
286            catch (final Throwable throwable)
287            {
288                throw new MetafacadeImplsException(throwable);
289            }
290            return metafacadeClass;
291        }
292
293        /**
294         * Retrieves the metafacade implementation class from the passed in <code>metafacadeClass</code>. Will return a
295         * MetafacadeImplsException if a metafacade implementation class can not be found for the
296         * <code>metafacadeClass</code>
297         *
298         * @param metafacadeClass the name of the metafacade class.
299         * @return the metafacade implementation Class
300         */
301        Class getMetafacadeImplClass(final String metafacadeClass)
302        {
303            ExceptionUtils.checkEmpty(
304                "metafacadeClass",
305                metafacadeClass);
306            Class metafacadeImplementationClass = null;
307            try
308            {
309                final String metafacadeImplementationClassName = this.implsByMetafacades.get(metafacadeClass);
310                if (StringUtils.isEmpty(metafacadeImplementationClassName))
311                {
312                    throw new MetafacadeImplsException("Can not find a metafacade implementation class for --> '" +
313                        metafacadeClass + "' check your classpath");
314                }
315                metafacadeImplementationClass = ClassUtils.loadClass(metafacadeImplementationClassName);
316            }
317            catch (final Throwable throwable)
318            {
319                throw new MetafacadeImplsException(throwable);
320            }
321            return metafacadeImplementationClass;
322        }
323
324        /**
325         * Clears each map of any classes it contains.
326         */
327        void clear()
328        {
329            this.metafacadesByImpls.clear();
330            this.implsByMetafacades.clear();
331        }
332
333        /**
334         * @see Object#toString()
335         */
336        public String toString()
337        {
338            return super.toString() + '[' + this.metafacadesByImpls + ']';
339        }
340    }
341}