001package org.andromda.core.common;
002
003import java.io.FileNotFoundException;
004import java.io.InputStream;
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.LinkedHashMap;
008import java.util.Map;
009import org.andromda.core.configuration.NamespaceProperties;
010import org.andromda.core.configuration.Namespaces;
011import org.andromda.core.configuration.Property;
012import org.andromda.core.mapping.Mapping;
013import org.andromda.core.mapping.Mappings;
014import org.apache.commons.io.IOUtils;
015import org.apache.commons.lang.StringUtils;
016import org.apache.log4j.Logger;
017
018/**
019 * <p>
020 * A class that performs the merging abilities for the AndroMDA core. </p>
021 * <p>
022 * Merging takes place when the {@link NamespaceProperties#MERGE_MAPPINGS_URI} is found within the
023 * <code>namespace</code> and merge mappings are used to replace any matching patterns in the given <code>string</code>.
024 * </p>
025 *
026 * @author Chad Brandon
027 */
028public class Merger
029{
030    private static final Logger logger = Logger.getLogger(Merger.class);
031
032    /**
033     * The shared instance
034     */
035    private static final Merger instance = new Merger();
036
037    /**
038     * Stores the cached merge mappings already found (so we don't need to reconstruct again each time).
039     */
040    private final Map<String, Mappings> mergeMappingsCache = new LinkedHashMap<String, Mappings>();
041
042    /**
043     * Gets the shared Merger instance. Normally you'll want to retrieve the instance through this method.
044     *
045     * @return the shared instance.
046     */
047    public static Merger instance()
048    {
049        return instance;
050    }
051
052    /**
053     * <p>
054     * Retrieves the <em>merged</em> string. The merging takes place when
055     * the {@link NamespaceProperties#MERGE_MAPPINGS_URI}is found within the
056     * <code>namespace</code> and the merge mappings are used to replace any
057     * matching patterns in the given <code>string</code>.
058     * </p>
059     *
060     * @param string the String to be replaced
061     * @param namespace This namespace is searched when attempting to find the
062     *        {@link NamespaceProperties#MERGE_MAPPINGS_URI}.
063     * @return the replaced String.
064     */
065    public String getMergedString(
066        String string,
067        final String namespace)
068    {
069        // avoid any possible infinite recursion with the mergedStringCache
070        // check (may need to refactor the mergedStringCache solution)
071        if (namespace != null && string != null)
072        {
073            string = string.replaceAll("\\r", "");
074            final Collection<Mappings> mappingInstances = this.getMergeMappings(namespace);
075            for (final Mappings mergeMappings : mappingInstances)
076            {
077                final Collection<Mapping> mappings = mergeMappings.getMappings();
078                if (mappings != null && !mappings.isEmpty())
079                {
080                    for (final Mapping mapping : mappings)
081                    {
082                        final Collection<String> froms = mapping.getFroms();
083                        for (String from : froms)
084                       {
085                            from = StringUtils.trimToEmpty(from);
086                            if (StringUtils.isNotBlank(from) && string.contains(from))
087                            {
088                                final String to = StringUtils.trimToEmpty(mapping.getTo());
089                                string = StringUtils.replace(string, from, to);
090                            }
091                        }
092                    }
093                }
094            }
095        }
096        return string;
097    }
098
099    /**
100     * Retrieves the <em>merged</em> string. The merging takes place when
101     * the {@link NamespaceProperties#MERGE_MAPPINGS_URI}is found within the
102     * <code>namespace</code> and the merge mappings are used to replace any
103     * matching patterns in the given <code>inputStream</code>.
104     * </p>
105     *
106     * @param inputStream the InputStream instance which is first converted
107     *        to a String and then merged.
108     * @param namespace This namespace is searched when attempting to find the
109     *        {@link NamespaceProperties#MERGE_MAPPINGS_URI}.
110     * @return the replaced String.
111     */
112    public String getMergedString(
113        final InputStream inputStream,
114        final String namespace)
115    {
116        try
117        {
118            final String string = IOUtils.toString(inputStream);
119            return this.getMergedString(string, namespace);
120        }
121        catch (final Exception exception)
122        {
123            throw new MergerException(exception);
124        }
125        finally
126        {
127            IOUtils.closeQuietly(inputStream);
128        }
129    }
130
131    /**
132     * Indicates whether or not the given <code>namespace</code>
133     * requires a merge.
134     * @param namespace the namespace to evaluate.
135     * @return true/false
136     */
137    public boolean requiresMerge(final String namespace)
138    {
139        boolean requiresMerge = false;
140        final Collection<Mappings> mergeMappings = this.getMergeMappings(namespace);
141        for (final Mappings mappings : mergeMappings)
142        {
143            requiresMerge = !mappings.getMappings().isEmpty();
144            if (requiresMerge)
145            {
146                break;
147            }
148        }
149        return requiresMerge;
150    }
151
152    /**
153     * Attempts to retrieve the Mappings instance for the given <code>mergeMappingsUri</code> belonging to the given
154     * <code>namespace</code>.
155     *
156     * @param namespace the namespace to which the mappings belong.
157     * @return the Mappings instance.
158     */
159    private Collection<Mappings> getMergeMappings(final String namespace)
160    {
161        final Collection<Mappings> mappings = new ArrayList<Mappings>();
162        if (StringUtils.isNotBlank(namespace))
163        {
164            final Collection<Property> mergeMappingsUris =
165                Namespaces.instance().getProperties(namespace, NamespaceProperties.MERGE_MAPPINGS_URI, false);
166            if (mergeMappingsUris != null)
167            {
168                for (final Property mergeMappingsUri : mergeMappingsUris)
169                {
170                    String mergeMappingsUriValue = (mergeMappingsUri != null) ? mergeMappingsUri.getValue() : null;
171                    if (StringUtils.isNotBlank(mergeMappingsUriValue))
172                    {
173                        Mappings mergeMappings = this.mergeMappingsCache.get(mergeMappingsUriValue);
174                        if (mergeMappings == null)
175                        {
176                            try
177                            {
178                                mergeMappings = Mappings.getInstance(mergeMappingsUriValue);
179                                this.mergeMappingsCache.put(mergeMappingsUriValue, mergeMappings);
180                            }
181                            catch (Exception exception)
182                            {
183                                if (ExceptionUtils.getRootCause(exception) instanceof FileNotFoundException)
184                                {
185                                    if (logger.isDebugEnabled())
186                                    {
187                                        logger.debug(exception);
188                                    }
189                                }
190                                else
191                                {
192                                    throw new MergerException(exception);
193                                }
194                            }
195                        }
196                        if (mergeMappings != null)
197                        {
198                            mappings.add(mergeMappings);
199                        }
200                    }
201                }
202            }
203        }
204        return mappings;
205    }
206}