Merger.java

package org.andromda.core.common;

import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.andromda.core.configuration.NamespaceProperties;
import org.andromda.core.configuration.Namespaces;
import org.andromda.core.configuration.Property;
import org.andromda.core.mapping.Mapping;
import org.andromda.core.mapping.Mappings;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 * <p>
 * A class that performs the merging abilities for the AndroMDA core. </p>
 * <p>
 * Merging takes place when the {@link NamespaceProperties#MERGE_MAPPINGS_URI} is found within the
 * <code>namespace</code> and merge mappings are used to replace any matching patterns in the given <code>string</code>.
 * </p>
 *
 * @author Chad Brandon
 */
public class Merger
{
    private static final Logger logger = Logger.getLogger(Merger.class);

    /**
     * The shared instance
     */
    private static final Merger instance = new Merger();

    /**
     * Stores the cached merge mappings already found (so we don't need to reconstruct again each time).
     */
    private final Map<String, Mappings> mergeMappingsCache = new LinkedHashMap<String, Mappings>();

    /**
     * Gets the shared Merger instance. Normally you'll want to retrieve the instance through this method.
     *
     * @return the shared instance.
     */
    public static Merger instance()
    {
        return instance;
    }

    /**
     * <p>
     * Retrieves the <em>merged</em> string. The merging takes place when
     * the {@link NamespaceProperties#MERGE_MAPPINGS_URI}is found within the
     * <code>namespace</code> and the merge mappings are used to replace any
     * matching patterns in the given <code>string</code>.
     * </p>
     *
     * @param string the String to be replaced
     * @param namespace This namespace is searched when attempting to find the
     *        {@link NamespaceProperties#MERGE_MAPPINGS_URI}.
     * @return the replaced String.
     */
    public String getMergedString(
        String string,
        final String namespace)
    {
        // avoid any possible infinite recursion with the mergedStringCache
        // check (may need to refactor the mergedStringCache solution)
        if (namespace != null && string != null)
        {
            string = string.replaceAll("\\r", "");
            final Collection<Mappings> mappingInstances = this.getMergeMappings(namespace);
            for (final Mappings mergeMappings : mappingInstances)
            {
                final Collection<Mapping> mappings = mergeMappings.getMappings();
                if (mappings != null && !mappings.isEmpty())
                {
                    for (final Mapping mapping : mappings)
                    {
                        final Collection<String> froms = mapping.getFroms();
                        for (String from : froms)
                       {
                            from = StringUtils.trimToEmpty(from);
                            if (StringUtils.isNotBlank(from) && string.contains(from))
                            {
                                final String to = StringUtils.trimToEmpty(mapping.getTo());
                                string = StringUtils.replace(string, from, to);
                            }
                        }
                    }
                }
            }
        }
        return string;
    }

    /**
     * Retrieves the <em>merged</em> string. The merging takes place when
     * the {@link NamespaceProperties#MERGE_MAPPINGS_URI}is found within the
     * <code>namespace</code> and the merge mappings are used to replace any
     * matching patterns in the given <code>inputStream</code>.
     * </p>
     *
     * @param inputStream the InputStream instance which is first converted
     *        to a String and then merged.
     * @param namespace This namespace is searched when attempting to find the
     *        {@link NamespaceProperties#MERGE_MAPPINGS_URI}.
     * @return the replaced String.
     */
    public String getMergedString(
        final InputStream inputStream,
        final String namespace)
    {
        try
        {
            final String string = IOUtils.toString(inputStream);
            return this.getMergedString(string, namespace);
        }
        catch (final Exception exception)
        {
            throw new MergerException(exception);
        }
        finally
        {
            IOUtils.closeQuietly(inputStream);
        }
    }

    /**
     * Indicates whether or not the given <code>namespace</code>
     * requires a merge.
     * @param namespace the namespace to evaluate.
     * @return true/false
     */
    public boolean requiresMerge(final String namespace)
    {
        boolean requiresMerge = false;
        final Collection<Mappings> mergeMappings = this.getMergeMappings(namespace);
        for (final Mappings mappings : mergeMappings)
        {
            requiresMerge = !mappings.getMappings().isEmpty();
            if (requiresMerge)
            {
                break;
            }
        }
        return requiresMerge;
    }

    /**
     * Attempts to retrieve the Mappings instance for the given <code>mergeMappingsUri</code> belonging to the given
     * <code>namespace</code>.
     *
     * @param namespace the namespace to which the mappings belong.
     * @return the Mappings instance.
     */
    private Collection<Mappings> getMergeMappings(final String namespace)
    {
        final Collection<Mappings> mappings = new ArrayList<Mappings>();
        if (StringUtils.isNotBlank(namespace))
        {
            final Collection<Property> mergeMappingsUris =
                Namespaces.instance().getProperties(namespace, NamespaceProperties.MERGE_MAPPINGS_URI, false);
            if (mergeMappingsUris != null)
            {
                for (final Property mergeMappingsUri : mergeMappingsUris)
                {
                    String mergeMappingsUriValue = (mergeMappingsUri != null) ? mergeMappingsUri.getValue() : null;
                    if (StringUtils.isNotBlank(mergeMappingsUriValue))
                    {
                        Mappings mergeMappings = this.mergeMappingsCache.get(mergeMappingsUriValue);
                        if (mergeMappings == null)
                        {
                            try
                            {
                                mergeMappings = Mappings.getInstance(mergeMappingsUriValue);
                                this.mergeMappingsCache.put(mergeMappingsUriValue, mergeMappings);
                            }
                            catch (Exception exception)
                            {
                                if (ExceptionUtils.getRootCause(exception) instanceof FileNotFoundException)
                                {
                                    if (logger.isDebugEnabled())
                                    {
                                        logger.debug(exception);
                                    }
                                }
                                else
                                {
                                    throw new MergerException(exception);
                                }
                            }
                        }
                        if (mergeMappings != null)
                        {
                            mappings.add(mergeMappings);
                        }
                    }
                }
            }
        }
        return mappings;
    }
}