Namespaces.java

package org.andromda.core.configuration;

import java.io.Serializable;
import java.net.URL;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.andromda.core.common.ExceptionUtils;
import org.andromda.core.namespace.NamespaceComponent;
import org.andromda.core.namespace.NamespaceRegistry;
import org.andromda.core.namespace.PropertyDefinition;
import org.apache.log4j.Logger;

/**
 * Directory of configurable Namespace objects. Namespace objects are used for configuring AndroMDA
 * namespaces.
 *
 * @author Chad Brandon
 * @author Bob Fields
 * @see org.andromda.core.configuration.Namespace
 */
public class Namespaces
    implements Serializable
{
    private static final long serialVersionUID = 34L;

    /**
     * The logger instance.
     */
    private static final Logger logger = Logger.getLogger(Namespaces.class);

    /**
     * This is passed as the cartridge name for the {@link #getProperty} method if we wish to use a 'default' Namespace
     * for Plugins. This is so we don't need to define a specific mapping for each Plugin if we don't want. If a
     * namespaceName exists with a specific Plugin name, then that will be used instead of the 'default'
     */
    public static final String DEFAULT = "default";

    /**
     * Stores all namespaces.
     */
    private final Map<String, Namespace> namespaces = new LinkedHashMap<String, Namespace>();

    /**
     * The shared instance.
     */
    private static Namespaces instance = null;

    /**
     * Returns the singleton instance of this Namespaces
     *
     * @return instance.
     */
    public static Namespaces instance()
    {
        if (instance == null)
        {
            instance = new Namespaces();
        }
        return instance;
    }

    /**
     * Gets the namespaces registered in this namespaces instance.
     *
     * @return all namespaces.
     */
    public Collection<Namespace> getNamespaces()
    {
        return this.namespaces.values();
    }

    /**
     * Adds a namespace to this collection of namespaces.
     *
     * @param namespace the Namespace to add to this instance.
     */
    public void addNamespace(final Namespace namespace)
    {
        this.namespaces.put(
            namespace.getName(),
            namespace);
    }

    /**
     * Adds all <code>namespaces</code> to this instance.
     *
     * @param namespaces the array of namespaces to add.
     */
    public void addNamespaces(final Namespace[] namespaces)
    {
        if (namespaces != null && namespaces.length > 0)
        {
            final int namespaceNumber = namespaces.length;
            for (int ctr = 0; ctr < namespaceNumber; ctr++)
            {
                this.addNamespace(namespaces[ctr]);
            }
        }
    }

    /**
     * Gets the Namespace with the corresponding <code>namespaceName</code>.
     *
     * @param namespaceName
     * @return the found Namespace
     */
    public Namespace getNamespace(final String namespaceName)
    {
        return namespaces.get(namespaceName);
    }

    /**
     * Indicates if the namespace is present within this instance.
     *
     * @param namespaceName the name of the namespace.
     * @return true/false
     */
    public boolean namespacePresent(final String namespaceName)
    {
        return this.getNamespace(namespaceName) != null;
    }

    /**
     * Retrieves a property from the Namespace with the namespaceName. If the <code>ignore</code> attribute of the
     * Property instance is set to <code>true</code> then lookup of the property will not be attempted and null will
     * just be returned instead. If the property is not found and <code>ignore</code> is not <code>true</code> a warning
     * message is logged.
     *
     * @param namespaceName name of the Plugin to which the namespace applies
     * @param propertyName  name of the namespace property to find.
     * @return String the namespace property value.
     */
    public Property getProperty(
        final String namespaceName,
        final String propertyName)
    {
        final Collection<Property> properties = this.getProperties(
            namespaceName,
            propertyName);
        return properties == null || properties.isEmpty() ?
            null : properties.iterator().next();
    }

    /**
     * Retrieves a property from the Namespace with the namespaceName. If the <code>ignore</code> attribute of the
     * Property instance is set to <code>true</code> then lookup of the property will not be attempted and null will
     * just be returned instead. If the property is not found and <code>ignore</code> is not <code>true</code> a warning
     * message is logged.
     *
     * @param namespaceName name of the Plugin to which the namespace applies
     * @param propertyName  name of the namespace property to find.
     * @return String the namespace property value.
     */
    public Collection<Property> getProperties(
        final String namespaceName,
        final String propertyName)
    {
        return this.getProperties(
            namespaceName,
            propertyName,
            true);
    }

    /**
     * Retrieves a property from the Namespace with the namespaceName. If the <code>ignore</code> attribute of the
     * Property instance is set to <code>true</code> then lookup of the property will not be attempted and null will
     * just be returned instead.
     *
     * @param namespaceName name of the Plugin to which the namespace applies
     * @param propertyName  name of the namespace property to find.
     * @param showWarning   true/false if we'd like to display a warning if the property/namespace can not be found.
     * @return the collection of properties.
     */
    public Property getProperty(
        final String namespaceName,
        final String propertyName,
        final boolean showWarning)
    {
        final Collection<Property> properties = this.getProperties(
            namespaceName,
            propertyName,
            showWarning);
        return properties == null || properties.isEmpty() ?
            null : properties.iterator().next();
    }

    /**
     * Retrieves a property from the Namespace with the namespaceName. If the <code>ignore</code> attribute of the
     * Property instance is set to <code>true</code> then lookup of the property will not be attempted and null will
     * just be returned instead.
     *
     * @param namespaceName name of the Plugin to which the namespace applies
     * @param propertyName  name of the namespace property to find.
     * @param showWarning   true/false if we'd like to display a warning if the property/namespace can not be found.
     * @return the collection of properties.
     */
    public Collection<Property> getProperties(
        final String namespaceName,
        final String propertyName,
        final boolean showWarning)
    {
        ExceptionUtils.checkEmpty(
            "namespaceName",
            namespaceName);
        ExceptionUtils.checkEmpty(
            "propertyName",
            propertyName);

        Collection<Property> property = null;
        final Namespace namespace = this.namespaces.get(namespaceName);
        if (namespace != null)
        {
            property = namespace.getProperties(propertyName);
        }

        // - since we couldn't find a Namespace for the specified cartridge,
        //   try to lookup the default
        Namespace defaultNamespace = null;
        if (property == null)
        {
            /*if (logger.isDebugEnabled())
            {
                logger.debug("no namespace with name '" + namespaceName + "' found, looking for '" + Namespaces.DEFAULT + '\'');
            }*/
            defaultNamespace = this.namespaces.get(Namespaces.DEFAULT);
            if (defaultNamespace != null)
            {
                property = defaultNamespace.getProperties(propertyName);
            }
        }

        if (namespace == null && defaultNamespace == null && showWarning)
        {
            logger.warn(
                "WARNING! No '" + DEFAULT + "' or '" + namespaceName + "' namespace found, " +
                "--> please define a namespace with at least one of these names, if you would like " +
                "to ignore this message, define the namespace with " + "ignore set to 'true'");
        }
        else if (property == null && showWarning)
        {
            logger.warn(
                "WARNING! Namespaces '" + DEFAULT + "' and '" + namespaceName + "' have no property '" + propertyName +
                "' defined --> please define this property in AT LEAST ONE of these two namespaces. " +
                " If you want to 'ignore' this message, add the property to the namespace with ignore set to 'true'");
        }
        return property;
    }

    /**
     * Retrieves all property definitions for the given namespace.
     *
     * @param namespaceName the name of the namespace.
     * @return the list of properties contained in the namespace.
     */
    public PropertyDefinition[] getPropertyDefinitions(final String namespaceName)
    {
        final NamespaceRegistry registry = this.getRegistry(namespaceName);
        return registry == null ? new PropertyDefinition[0] : registry.getPropertyDefinitions();
    }

    /**
     * Stores the namespace registries
     */
    private final Map<String, NamespaceRegistry> registries = new LinkedHashMap<String, NamespaceRegistry>();

    /**
     * Gets all available namespace registries (these are namespaces
     * which have been discovered but are not necessarily configured).
     *
     * @return the collection of namespace registries
     */
    public Collection<NamespaceRegistry> getNamespaceRegistries()
    {
        return this.registries.values();
    }

    /**
     * Adds a namespace registry to this instance.  Namespace registries contain
     * property definitions that are defined within a {@link NamespaceRegistry}
     * descriptor (used to describe {@link NamespaceComponent}) instances.
     *
     * @param registry the {@link NamespaceRegistry} instance to add.
     */
    public void addRegistry(final NamespaceRegistry registry)
    {
        if (registry != null)
        {
            // - first add the registry directly under its own name
            this.registries.put(
                registry.getName(),
                registry);

            // - if the registry is shared, we add the registry to the default namespace as well
            if (registry.isShared())
            {
                NamespaceRegistry defaultRegistry = this.getRegistry(Namespaces.DEFAULT);
                if (defaultRegistry == null)
                {
                    defaultRegistry = registry;
                }
                else
                {
                    defaultRegistry.addPropertyDefinitions(registry.getPropertyDefinitions());
                }
                this.registries.put(
                    Namespaces.DEFAULT,
                    defaultRegistry);
            }
        }
    }

    /**
     * Indicates if the given <code>namespace</code> is
     * shared or not.
     *
     * @param namespace the namespace to check.
     * @return true/false.
     */
    public boolean isShared(final String namespace)
    {
        final NamespaceRegistry registry = this.getRegistry(namespace);
        return registry != null && registry.isShared();
    }

    /**
     * Attempts to get the value of a property from the given
     * <code>namespace</code> with the given <code>name</code> by first attempting
     * to retrieve it from the namespace and if no property is defined
     * in the namespace we retrieve the default value (if one is defined).
     *
     * @param namespace the namespace for which to retreive the value.
     * @param name the name of the value to retrieve.
     * @return the value (or null if one couldn't be retrieved).
     */
    public String getPropertyValue(
        final String namespace,
        final String name)
    {
        final PropertyDefinition definition = this.getPropertyDefinition(
                namespace,
                name);
        if (definition == null)
        {
            throw new NamespacesException("Property '" + name + "' is not registered in either the '" + namespace +
                "' or '" + Namespaces.DEFAULT + "' namespaces");
        }
        final String defaultValue = definition.getDefaultValue();
        boolean warning = defaultValue == null && definition.isRequired();
        final Property property = this.getProperty(
                namespace,
                name,
                warning);
        return property != null && !property.isIgnore() ? property.getValue() : defaultValue;
    }

    /**
     * Attempts to retrieve the resource root of the namespace. The resource root is the directory
     * or archive root which contains all namespace resources.
     *
     * @param namespace the namespace of which to retrieve the resource.
     * @return the resource or null if it could not be found.
     */
    public URL[] getResourceRoots(final String namespace)
    {
        final NamespaceRegistry registry = this.getRegistry(namespace);
        if (registry == null)
        {
            throw new NamespacesException('\'' + namespace + "' is not a registered namespace");
        }

        final URL[] resourceRoots = registry.getResourceRoots();
        if (resourceRoots == null || resourceRoots.length == 0)
        {
            throw new NamespacesException("No resource root(s) could be retrieved for namespace '" + namespace + '\'');
        }
        return resourceRoots;
    }

    /**
     * Indicates whether or not the <code>component</code> is present within the given
     * <code>namespace</code>
     * @param namespace the name of the namespace.
     * @param component the name of the component type.
     * @return true/false
     */
    public boolean isComponentPresent(
        final String namespace,
        final String component)
    {
        boolean present = false;
        final NamespaceRegistry registry = this.getRegistry(namespace);
        if (namespace != null && component != null && registry != null)
        {
            final String[] components = registry.getRegisteredComponents();
            final int numberOfComponents = components.length;
            for (int ctr = 0; ctr < numberOfComponents; ctr++)
            {
                if (component.equals(components[ctr]))
                {
                    present = true;
                    break;
                }
            }
        }
        return present;
    }

    /**
     * Attempts to get the value of a property from the given
     * <code>namespace</code> with the given <code>name</code> by first attempting
     * to retreive it from the namespace and if no property is defined
     * in the namespace we retrieve the default value (if one is defined).
     *
     * @param namespace the namespace for which to retreive the value.
     * @param name the name of the value to retrieve.
     * @return the value (or null if one couldn't be retrieved).
     */
    private PropertyDefinition getPropertyDefinition(
        final String namespace,
        final String name)
    {
        final NamespaceRegistry registry = this.getRegistry(namespace);
        PropertyDefinition definition = null;
        if (registry != null)
        {
            definition = registry.getPropertyDefinition(name);
        }
        if (definition == null)
        {
            final NamespaceRegistry defaultRegistry = this.getRegistry(Namespaces.DEFAULT);
            if (defaultRegistry != null)
            {
                definition = defaultRegistry.getPropertyDefinition(name);
            }
        }
        return definition;
    }

    /**
     * Retrieves the namespace registry for the given namespace, or returns null
     * if it doesn't exist.
     *
     * @param namespace the namespace name.
     * @return the registry, or null if not found.
     */
    public NamespaceRegistry getRegistry(final String namespace)
    {
        return this.registries.get(namespace);
    }

    /**
     * Clears out the current namespaces.
     */
    public void clear()
    {
        this.namespaces.clear();
    }
}