001package org.andromda.scriptwrappers;
002
003import groovy.lang.GroovyClassLoader;
004import groovy.lang.GroovyObject;
005import java.io.BufferedReader;
006import java.io.File;
007import java.io.FileNotFoundException;
008import java.io.FileReader;
009import java.io.IOException;
010import java.io.Reader;
011import java.lang.reflect.Method;
012import java.util.Arrays;
013import java.util.Iterator;
014import java.util.LinkedHashSet;
015import java.util.Set;
016
017/**
018 * This is a wrapper class for a Groovy script. The generated Java classes contain a private
019 * final copy of this wrapper and delegates all methods to this class so that their Groovy-counterparts
020 * can be executed.
021 *
022 * @author Chad Brandon
023 */
024public class GroovyScriptWrapper
025{
026    private String scriptPath;
027    private Object stub;
028
029    /**
030     * StubClass is always the generated class (not any subclasses),
031     * while stub may be an instance of a subclassed scripted class.
032     * @param stub
033     * @param scriptPath
034     * @throws InstantiationError
035     */
036    public GroovyScriptWrapper(
037        Object stub,
038        String scriptPath)
039        throws InstantiationError
040    {
041        this.stub = stub;
042        this.scriptPath = scriptPath;
043    }
044
045    /**
046     * Invokes the method with the given <code>methodName</code> on the instance.
047     *
048     * @param methodName the name of the method to invoke.
049     * @param args the arguments to pass to the method.
050     * @return the return result of invoking the operation.
051     */
052    public Object invoke(
053        String methodName,
054        Object[] args)
055    {
056        try
057        {
058            final GroovyClassLoader grooveyClassLoader = new GroovyClassLoader(this.getClassLoader());
059            final Class groovyClass =
060                grooveyClassLoader.parseClass(
061                    this.getContents(new File(this.scriptPath)),
062                    scriptPath);
063            final GroovyObject groovyObject = (GroovyObject)groovyClass.newInstance();
064
065            this.copyProperties(
066                this.stub,
067                groovyObject);
068            Object rtn = groovyObject.invokeMethod(
069                methodName,
070                args);
071            grooveyClassLoader.close();
072            return rtn;
073        }
074        catch (final Throwable throwable)
075        {
076            throw new RuntimeException(throwable);
077        }
078    }
079
080    /**
081     * Retrieves the appropriate class loader instance as the parent of the groovyClassLoader.
082     *
083     * @return the class loader instance.
084     */
085    private ClassLoader getClassLoader()
086    {
087        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
088        if (classLoader == null)
089        {
090            classLoader = this.getClass().getClassLoader();
091        }
092        return classLoader;
093    }
094
095    /**
096     * The line separator.
097     */
098    private static final char LINE_SEPARATOR = '\n';
099
100    /**
101     * Loads the resource and returns the contents as a String.
102     *
103     * @param resource the name of the resource.
104     * @return the contents of the resource as a string.
105     * @throws FileNotFoundException
106     */
107    @SuppressWarnings("static-method")
108    private String getContents(final File file)
109        throws FileNotFoundException
110    {
111        Reader resource = new FileReader(file);
112        final StringBuilder contents = new StringBuilder();
113        try
114        {
115            BufferedReader resourceInput = new BufferedReader(resource);
116            for (String line = resourceInput.readLine(); line != null; line = resourceInput.readLine())
117            {
118                contents.append(line).append(LINE_SEPARATOR);
119            }
120            resourceInput.close();
121            resourceInput = null;
122        }
123        catch (final Throwable throwable)
124        {
125            throw new RuntimeException(throwable);
126        }
127        finally
128        {
129            try
130            {
131                resource.close();
132            }
133            catch (IOException ex)
134            {
135                // Ignore
136            }
137        }
138
139        // - return the contents and remove any throws clauses (since groovy doesn't support those)
140        return contents.toString().trim().replaceAll(
141            "\\s+throws\\s+.+\\s+",
142            " ");
143    }
144
145    /**
146     * Copies all properties from the given <code>from</code> instance to the given
147     * <code>to</code> instance.
148     *
149     * @param from the instance from which to copy all properties.
150     * @param to the instance of which to copy all properties.
151     * @throws Exception
152     */
153    @SuppressWarnings("static-method")
154    private void copyProperties(
155        final Object from,
156        final GroovyObject to)
157        throws Exception
158    {
159        final Set methods = new LinkedHashSet();
160        loadSuperMethods(
161            from.getClass(),
162            methods);
163        for (final Iterator iterator = methods.iterator(); iterator.hasNext();)
164        {
165            final Method method = (Method)iterator.next();
166
167            final String methodName = method.getName();
168            final String getPrefix = "get";
169            if (methodName.startsWith(getPrefix) && method.getParameterTypes().length == 0)
170            {
171                String propertyName = methodName.replaceAll(
172                        getPrefix,
173                        "");
174
175                Method setterMethod = null;
176                try
177                {
178                    setterMethod =
179                        from.getClass().getMethod(
180                            "set" + propertyName,
181                            new Class[] {method.getReturnType()});
182                }
183                catch (final Exception exception)
184                {
185                    // - ignore
186                }
187                if (setterMethod != null)
188                {
189                    method.setAccessible(true);
190                    final Object value = method.invoke(
191                            from);
192                    to.invokeMethod(
193                        setterMethod.getName(),
194                        new Object[] {value});
195                }
196            }
197        }
198    }
199
200    /**
201     * Loads all methods from the clazz's super classes.
202     *
203     * @param methods the list to load full of methods.
204     * @param clazz the class to retrieve the methods.
205     * @return the loaded methods.
206     */
207    private static Set loadSuperMethods(
208        final Class clazz,
209        final Set methods)
210    {
211        if (clazz.getSuperclass() != null)
212        {
213            methods.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredMethods()));
214            methods.addAll(loadSuperMethods(
215                    clazz.getSuperclass(),
216                    methods));
217        }
218        return methods;
219    }
220}