001package org.andromda.ant.task;
002
003import java.io.FileNotFoundException;
004import java.net.URL;
005import java.util.Map;
006import org.andromda.core.AndroMDA;
007import org.andromda.core.common.ResourceUtils;
008import org.apache.commons.lang.StringUtils;
009import org.apache.commons.lang.exception.ExceptionUtils;
010import org.apache.tools.ant.BuildException;
011import org.apache.tools.ant.taskdefs.MatchingTask;
012
013/**
014 * <p/> This class wraps the AndroMDA model processor so that AndroMDA can be
015 * used as an Ant task. Represents the <code>&lt;andromda&gt;</code> custom
016 * task which can be called from an Ant build script.
017 * </p>
018 *
019 * @author <a href="http://www.mbohlen.de">Matthias Bohlen </a>
020 * @author <a href="http://www.amowers.com">Anthony Mowers </a>
021 * @author Chad Brandon
022 * @see org.andromda.core.AndroMDA
023 */
024public class AndroMDAGenTask
025    extends MatchingTask
026{
027    /**
028     * Initialize the context class loader.
029     */
030    static
031    {
032        initializeContextClassLoader();
033    }
034
035    /**
036     * Stores the configuration URI.
037     */
038    private URL configurationUri;
039
040    /**
041     * Sets the URI to the configuration file.
042     *
043     * @param configurationUri
044     */
045    public void setConfigurationUri(final URL configurationUri)
046    {
047        this.configurationUri = configurationUri;
048    }
049
050    /**
051     * <p/>
052     * Starts the generation of source code from an object model. </p>
053     * <p/>
054     * This is the main entry point of the application when running Ant. It is called by ant whenever the surrounding
055     * task is executed (which could be multiple times). </p>
056     *
057     * @throws BuildException if something goes wrong
058     */
059    public void execute()
060        throws BuildException
061    {
062        // initialize before the execute as well in case we
063        // want to execute more than once
064        initializeContextClassLoader();
065        try
066        {
067            if (this.configurationUri == null)
068            {
069                throw new BuildException("Configuration is not a valid URI --> '" + this.configurationUri + '\'');
070            }
071            final AndroMDA andromda = AndroMDA.newInstance();
072            if (andromda != null)
073            {
074                andromda.run(
075                    this.replaceProperties(ResourceUtils.getContents(configurationUri)));
076                andromda.shutdown();
077            }
078        }
079        catch (Throwable throwable)
080        {
081            final Throwable cause = ExceptionUtils.getCause(throwable);
082            if (cause != null)
083            {
084                throwable = cause;
085            }
086            if (throwable instanceof FileNotFoundException)
087            {
088                throw new BuildException("No configuration could be loaded from --> '" + configurationUri + '\'');
089            }
090            throw new BuildException(throwable);
091        }
092        finally
093        {
094            // Set the context class loader back ot its system class loaders
095            // so that any processes running after won't be trying to use
096            // the ContextClassLoader for this class.
097            Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
098        }
099    }
100
101    /**
102     * Replaces all properties having the style
103     * <code>${some.property}</code> with the value
104     * of the specified property if there is one.
105     *
106     * @param string the fileContents to perform replacement on.
107     * @return replaced properties
108     */
109    protected String replaceProperties(String string)
110    {
111        @SuppressWarnings("unchecked")
112        final Map<String, Object> properties = this.getProject().getProperties();
113        if (properties != null)
114        {
115            for (Map.Entry<String, Object> entry : properties.entrySet())
116            {
117                final String name = entry.getKey();
118                final String value = (String)entry.getValue();
119                final String property = "${" + name + '}';
120                string = StringUtils.replace(string, property, value);
121            }
122        }
123
124        // remove any left over property references
125        string = this.removePropertyReferences(string);
126        return string;
127    }
128
129    /**
130     * The property reference pattern.
131     */
132    private static final String PROPERTY_REFERENCE = "\\$\\{.*\\}";
133
134    /**
135     * Removes any ${some.property} type references from the string
136     * and returns the modifed string.
137     * @param string the string from which to remove the property
138     *        references
139     *
140     * @return the modified string.
141     */
142    public String removePropertyReferences(String string)
143    {
144        if (string != null)
145        {
146            string = string.replaceAll(PROPERTY_REFERENCE, "");
147        }
148        return string;
149    }
150
151    /**
152     * Set the context class loader so that any classes using it (the contextContextClassLoader) have access to the
153     * correct loader.
154     */
155    private static void initializeContextClassLoader()
156    {
157        Thread.currentThread().setContextClassLoader(AndroMDAGenTask.class.getClassLoader());
158    }
159}