001package org.andromda.core.configuration;
002
003import java.io.Serializable;
004import java.net.URL;
005import java.util.Collection;
006import java.util.LinkedHashMap;
007import java.util.Map;
008import org.andromda.core.common.ExceptionUtils;
009import org.andromda.core.namespace.NamespaceComponent;
010import org.andromda.core.namespace.NamespaceRegistry;
011import org.andromda.core.namespace.PropertyDefinition;
012import org.apache.log4j.Logger;
013
014/**
015 * Directory of configurable Namespace objects. Namespace objects are used for configuring AndroMDA
016 * namespaces.
017 *
018 * @author Chad Brandon
019 * @author Bob Fields
020 * @see org.andromda.core.configuration.Namespace
021 */
022public class Namespaces
023    implements Serializable
024{
025    private static final long serialVersionUID = 34L;
026
027    /**
028     * The logger instance.
029     */
030    private static final Logger logger = Logger.getLogger(Namespaces.class);
031
032    /**
033     * This is passed as the cartridge name for the {@link #getProperty} method if we wish to use a 'default' Namespace
034     * for Plugins. This is so we don't need to define a specific mapping for each Plugin if we don't want. If a
035     * namespaceName exists with a specific Plugin name, then that will be used instead of the 'default'
036     */
037    public static final String DEFAULT = "default";
038
039    /**
040     * Stores all namespaces.
041     */
042    private final Map<String, Namespace> namespaces = new LinkedHashMap<String, Namespace>();
043
044    /**
045     * The shared instance.
046     */
047    private static Namespaces instance = null;
048
049    /**
050     * Returns the singleton instance of this Namespaces
051     *
052     * @return instance.
053     */
054    public static Namespaces instance()
055    {
056        if (instance == null)
057        {
058            instance = new Namespaces();
059        }
060        return instance;
061    }
062
063    /**
064     * Gets the namespaces registered in this namespaces instance.
065     *
066     * @return all namespaces.
067     */
068    public Collection<Namespace> getNamespaces()
069    {
070        return this.namespaces.values();
071    }
072
073    /**
074     * Adds a namespace to this collection of namespaces.
075     *
076     * @param namespace the Namespace to add to this instance.
077     */
078    public void addNamespace(final Namespace namespace)
079    {
080        this.namespaces.put(
081            namespace.getName(),
082            namespace);
083    }
084
085    /**
086     * Adds all <code>namespaces</code> to this instance.
087     *
088     * @param namespaces the array of namespaces to add.
089     */
090    public void addNamespaces(final Namespace[] namespaces)
091    {
092        if (namespaces != null && namespaces.length > 0)
093        {
094            final int namespaceNumber = namespaces.length;
095            for (int ctr = 0; ctr < namespaceNumber; ctr++)
096            {
097                this.addNamespace(namespaces[ctr]);
098            }
099        }
100    }
101
102    /**
103     * Gets the Namespace with the corresponding <code>namespaceName</code>.
104     *
105     * @param namespaceName
106     * @return the found Namespace
107     */
108    public Namespace getNamespace(final String namespaceName)
109    {
110        return namespaces.get(namespaceName);
111    }
112
113    /**
114     * Indicates if the namespace is present within this instance.
115     *
116     * @param namespaceName the name of the namespace.
117     * @return true/false
118     */
119    public boolean namespacePresent(final String namespaceName)
120    {
121        return this.getNamespace(namespaceName) != null;
122    }
123
124    /**
125     * Retrieves a property from the Namespace with the namespaceName. If the <code>ignore</code> attribute of the
126     * Property instance is set to <code>true</code> then lookup of the property will not be attempted and null will
127     * just be returned instead. If the property is not found and <code>ignore</code> is not <code>true</code> a warning
128     * message is logged.
129     *
130     * @param namespaceName name of the Plugin to which the namespace applies
131     * @param propertyName  name of the namespace property to find.
132     * @return String the namespace property value.
133     */
134    public Property getProperty(
135        final String namespaceName,
136        final String propertyName)
137    {
138        final Collection<Property> properties = this.getProperties(
139            namespaceName,
140            propertyName);
141        return properties == null || properties.isEmpty() ?
142            null : properties.iterator().next();
143    }
144
145    /**
146     * Retrieves a property from the Namespace with the namespaceName. If the <code>ignore</code> attribute of the
147     * Property instance is set to <code>true</code> then lookup of the property will not be attempted and null will
148     * just be returned instead. If the property is not found and <code>ignore</code> is not <code>true</code> a warning
149     * message is logged.
150     *
151     * @param namespaceName name of the Plugin to which the namespace applies
152     * @param propertyName  name of the namespace property to find.
153     * @return String the namespace property value.
154     */
155    public Collection<Property> getProperties(
156        final String namespaceName,
157        final String propertyName)
158    {
159        return this.getProperties(
160            namespaceName,
161            propertyName,
162            true);
163    }
164
165    /**
166     * Retrieves a property from the Namespace with the namespaceName. If the <code>ignore</code> attribute of the
167     * Property instance is set to <code>true</code> then lookup of the property will not be attempted and null will
168     * just be returned instead.
169     *
170     * @param namespaceName name of the Plugin to which the namespace applies
171     * @param propertyName  name of the namespace property to find.
172     * @param showWarning   true/false if we'd like to display a warning if the property/namespace can not be found.
173     * @return the collection of properties.
174     */
175    public Property getProperty(
176        final String namespaceName,
177        final String propertyName,
178        final boolean showWarning)
179    {
180        final Collection<Property> properties = this.getProperties(
181            namespaceName,
182            propertyName,
183            showWarning);
184        return properties == null || properties.isEmpty() ?
185            null : properties.iterator().next();
186    }
187
188    /**
189     * Retrieves a property from the Namespace with the namespaceName. If the <code>ignore</code> attribute of the
190     * Property instance is set to <code>true</code> then lookup of the property will not be attempted and null will
191     * just be returned instead.
192     *
193     * @param namespaceName name of the Plugin to which the namespace applies
194     * @param propertyName  name of the namespace property to find.
195     * @param showWarning   true/false if we'd like to display a warning if the property/namespace can not be found.
196     * @return the collection of properties.
197     */
198    public Collection<Property> getProperties(
199        final String namespaceName,
200        final String propertyName,
201        final boolean showWarning)
202    {
203        ExceptionUtils.checkEmpty(
204            "namespaceName",
205            namespaceName);
206        ExceptionUtils.checkEmpty(
207            "propertyName",
208            propertyName);
209
210        Collection<Property> property = null;
211        final Namespace namespace = this.namespaces.get(namespaceName);
212        if (namespace != null)
213        {
214            property = namespace.getProperties(propertyName);
215        }
216
217        // - since we couldn't find a Namespace for the specified cartridge,
218        //   try to lookup the default
219        Namespace defaultNamespace = null;
220        if (property == null)
221        {
222            /*if (logger.isDebugEnabled())
223            {
224                logger.debug("no namespace with name '" + namespaceName + "' found, looking for '" + Namespaces.DEFAULT + '\'');
225            }*/
226            defaultNamespace = this.namespaces.get(Namespaces.DEFAULT);
227            if (defaultNamespace != null)
228            {
229                property = defaultNamespace.getProperties(propertyName);
230            }
231        }
232
233        if (namespace == null && defaultNamespace == null && showWarning)
234        {
235            logger.warn(
236                "WARNING! No '" + DEFAULT + "' or '" + namespaceName + "' namespace found, " +
237                "--> please define a namespace with at least one of these names, if you would like " +
238                "to ignore this message, define the namespace with " + "ignore set to 'true'");
239        }
240        else if (property == null && showWarning)
241        {
242            logger.warn(
243                "WARNING! Namespaces '" + DEFAULT + "' and '" + namespaceName + "' have no property '" + propertyName +
244                "' defined --> please define this property in AT LEAST ONE of these two namespaces. " +
245                " If you want to 'ignore' this message, add the property to the namespace with ignore set to 'true'");
246        }
247        return property;
248    }
249
250    /**
251     * Retrieves all property definitions for the given namespace.
252     *
253     * @param namespaceName the name of the namespace.
254     * @return the list of properties contained in the namespace.
255     */
256    public PropertyDefinition[] getPropertyDefinitions(final String namespaceName)
257    {
258        final NamespaceRegistry registry = this.getRegistry(namespaceName);
259        return registry == null ? new PropertyDefinition[0] : registry.getPropertyDefinitions();
260    }
261
262    /**
263     * Stores the namespace registries
264     */
265    private final Map<String, NamespaceRegistry> registries = new LinkedHashMap<String, NamespaceRegistry>();
266
267    /**
268     * Gets all available namespace registries (these are namespaces
269     * which have been discovered but are not necessarily configured).
270     *
271     * @return the collection of namespace registries
272     */
273    public Collection<NamespaceRegistry> getNamespaceRegistries()
274    {
275        return this.registries.values();
276    }
277
278    /**
279     * Adds a namespace registry to this instance.  Namespace registries contain
280     * property definitions that are defined within a {@link NamespaceRegistry}
281     * descriptor (used to describe {@link NamespaceComponent}) instances.
282     *
283     * @param registry the {@link NamespaceRegistry} instance to add.
284     */
285    public void addRegistry(final NamespaceRegistry registry)
286    {
287        if (registry != null)
288        {
289            // - first add the registry directly under its own name
290            this.registries.put(
291                registry.getName(),
292                registry);
293
294            // - if the registry is shared, we add the registry to the default namespace as well
295            if (registry.isShared())
296            {
297                NamespaceRegistry defaultRegistry = this.getRegistry(Namespaces.DEFAULT);
298                if (defaultRegistry == null)
299                {
300                    defaultRegistry = registry;
301                }
302                else
303                {
304                    defaultRegistry.addPropertyDefinitions(registry.getPropertyDefinitions());
305                }
306                this.registries.put(
307                    Namespaces.DEFAULT,
308                    defaultRegistry);
309            }
310        }
311    }
312
313    /**
314     * Indicates if the given <code>namespace</code> is
315     * shared or not.
316     *
317     * @param namespace the namespace to check.
318     * @return true/false.
319     */
320    public boolean isShared(final String namespace)
321    {
322        final NamespaceRegistry registry = this.getRegistry(namespace);
323        return registry != null && registry.isShared();
324    }
325
326    /**
327     * Attempts to get the value of a property from the given
328     * <code>namespace</code> with the given <code>name</code> by first attempting
329     * to retrieve it from the namespace and if no property is defined
330     * in the namespace we retrieve the default value (if one is defined).
331     *
332     * @param namespace the namespace for which to retreive the value.
333     * @param name the name of the value to retrieve.
334     * @return the value (or null if one couldn't be retrieved).
335     */
336    public String getPropertyValue(
337        final String namespace,
338        final String name)
339    {
340        final PropertyDefinition definition = this.getPropertyDefinition(
341                namespace,
342                name);
343        if (definition == null)
344        {
345            throw new NamespacesException("Property '" + name + "' is not registered in either the '" + namespace +
346                "' or '" + Namespaces.DEFAULT + "' namespaces");
347        }
348        final String defaultValue = definition.getDefaultValue();
349        boolean warning = defaultValue == null && definition.isRequired();
350        final Property property = this.getProperty(
351                namespace,
352                name,
353                warning);
354        return property != null && !property.isIgnore() ? property.getValue() : defaultValue;
355    }
356
357    /**
358     * Attempts to retrieve the resource root of the namespace. The resource root is the directory
359     * or archive root which contains all namespace resources.
360     *
361     * @param namespace the namespace of which to retrieve the resource.
362     * @return the resource or null if it could not be found.
363     */
364    public URL[] getResourceRoots(final String namespace)
365    {
366        final NamespaceRegistry registry = this.getRegistry(namespace);
367        if (registry == null)
368        {
369            throw new NamespacesException('\'' + namespace + "' is not a registered namespace");
370        }
371
372        final URL[] resourceRoots = registry.getResourceRoots();
373        if (resourceRoots == null || resourceRoots.length == 0)
374        {
375            throw new NamespacesException("No resource root(s) could be retrieved for namespace '" + namespace + '\'');
376        }
377        return resourceRoots;
378    }
379
380    /**
381     * Indicates whether or not the <code>component</code> is present within the given
382     * <code>namespace</code>
383     * @param namespace the name of the namespace.
384     * @param component the name of the component type.
385     * @return true/false
386     */
387    public boolean isComponentPresent(
388        final String namespace,
389        final String component)
390    {
391        boolean present = false;
392        final NamespaceRegistry registry = this.getRegistry(namespace);
393        if (namespace != null && component != null && registry != null)
394        {
395            final String[] components = registry.getRegisteredComponents();
396            final int numberOfComponents = components.length;
397            for (int ctr = 0; ctr < numberOfComponents; ctr++)
398            {
399                if (component.equals(components[ctr]))
400                {
401                    present = true;
402                    break;
403                }
404            }
405        }
406        return present;
407    }
408
409    /**
410     * Attempts to get the value of a property from the given
411     * <code>namespace</code> with the given <code>name</code> by first attempting
412     * to retreive it from the namespace and if no property is defined
413     * in the namespace we retrieve the default value (if one is defined).
414     *
415     * @param namespace the namespace for which to retreive the value.
416     * @param name the name of the value to retrieve.
417     * @return the value (or null if one couldn't be retrieved).
418     */
419    private PropertyDefinition getPropertyDefinition(
420        final String namespace,
421        final String name)
422    {
423        final NamespaceRegistry registry = this.getRegistry(namespace);
424        PropertyDefinition definition = null;
425        if (registry != null)
426        {
427            definition = registry.getPropertyDefinition(name);
428        }
429        if (definition == null)
430        {
431            final NamespaceRegistry defaultRegistry = this.getRegistry(Namespaces.DEFAULT);
432            if (defaultRegistry != null)
433            {
434                definition = defaultRegistry.getPropertyDefinition(name);
435            }
436        }
437        return definition;
438    }
439
440    /**
441     * Retrieves the namespace registry for the given namespace, or returns null
442     * if it doesn't exist.
443     *
444     * @param namespace the namespace name.
445     * @return the registry, or null if not found.
446     */
447    public NamespaceRegistry getRegistry(final String namespace)
448    {
449        return this.registries.get(namespace);
450    }
451
452    /**
453     * Clears out the current namespaces.
454     */
455    public void clear()
456    {
457        this.namespaces.clear();
458    }
459}