001package org.andromda.scriptwrappers;
002
003import java.lang.reflect.Method;
004import java.util.Arrays;
005import java.util.Iterator;
006import java.util.LinkedHashSet;
007import java.util.Set;
008import bsh.EvalError;
009import bsh.Interpreter;
010import bsh.Primitive;
011
012/**
013 * This is a wrapper class for a BeanShell script. The generated Java classes contain a private
014 * final copy of this wrapper and delegates all methods to this class so that their BeanShell-counterparts
015 * can be executed.
016 *
017 * @author Chad Brandon
018 */
019public class BshScriptWrapper
020{
021    private final Interpreter interpreter;
022    private String scriptPath;
023    private Object stub;
024
025    /**
026     * StubClass is always the generated class (not any subclasses),
027     * while stub may be an instance of a subclassed scripted class.
028     * @param stub
029     * @param scriptPath
030     * @throws InstantiationError
031     */
032    public BshScriptWrapper(
033        Object stub,
034        String scriptPath)
035        throws InstantiationError
036    {
037        this.stub = stub;
038        this.scriptPath = scriptPath;
039        this.interpreter = initialize(
040                stub,
041                scriptPath);
042    }
043
044    @SuppressWarnings("unused")
045    private BshScriptWrapper()
046    {
047        this.interpreter = null;
048    }
049
050    /**
051     * Initializes the interpreter.
052     *
053     * @param stub the stub class.
054     * @param scriptPath the path to the script file.
055     * @return the initialized interpreter.
056     */
057    private final Interpreter initialize(
058        Object stub,
059        String scriptPath)
060    {
061        final Interpreter interpreter = new Interpreter();
062        interpreter.setClassLoader(stub.getClass().getClassLoader());
063        return interpreter;
064    }
065
066    /**
067     * Invokes the method with the given <code>methodName</code> on the  instance.
068     *
069     * @param methodName the name of the method to invoke.
070     * @param args the arguments to pass to the method.
071     * @return the return result of invoking the operation.
072     */
073    public Object invoke(
074        String methodName,
075        Object[] args)
076    {
077        try
078        {
079            try
080            {
081                final Class stubClass = stub.getClass();
082                this.interpreter.source(scriptPath);
083                this.interpreter.set(
084                    "instance",
085                    interpreter.eval(" new " + stubClass.getName() + "();"));
086                this.interpreter.set(
087                    "stub",
088                    stub);
089
090                // - copy any properties
091                this.interpreter.eval(BshScriptWrapper.class.getName() + ".copyProperties(stub, instance);");
092            }
093            catch (final Exception exception)
094            {
095                exception.printStackTrace();
096                throw new InstantiationError("Problems instantiating script '" + scriptPath + "':" + exception);
097            }
098            final StringBuilder arguments = new StringBuilder();
099            if (args != null)
100            {
101                for (int ctr = 1; ctr <= args.length; ctr++)
102                {
103                    final String argument = "$" + ctr;
104                    this.interpreter.set(
105                        argument,
106                        args[ctr - 1]);
107                    arguments.append(argument);
108                    if (ctr != args.length)
109                    {
110                        arguments.append(", ");
111                    }
112                }
113            }
114
115            Object returnValue = this.interpreter.eval("instance." + methodName + '(' + arguments + ");");
116
117            if (returnValue instanceof bsh.Primitive)
118            {
119                returnValue = Primitive.unwrap(returnValue);
120            }
121
122            return returnValue;
123        }
124        catch (EvalError exception)
125        {
126            throw new RuntimeException(exception);
127        }
128    }
129
130    /**
131     * Copies all properties from the given <code>from</code> instance to the given
132     * <code>to</code> instance.
133     *
134     * @param from the instance from which to copy all properties.
135     * @param to the instance of which to copy all properties.
136     * @throws Exception
137     */
138    protected static void copyProperties(
139        final Object from,
140        final Object to)
141        throws Exception
142    {
143        final Set<Method> methods = new LinkedHashSet<Method>();
144        loadSuperMethods(
145            from.getClass(),
146            methods);
147        for (final Iterator iterator = methods.iterator(); iterator.hasNext();)
148        {
149            final Method method = (Method)iterator.next();
150
151            final String methodName = method.getName();
152            final String getPrefix = "get";
153            if (methodName.startsWith(getPrefix) && method.getParameterTypes().length == 0)
154            {
155                String propertyName = methodName.replaceAll(
156                        getPrefix,
157                        "");
158
159                Method setterMethod = null;
160                try
161                {
162                    setterMethod =
163                        from.getClass().getMethod(
164                            "set" + propertyName,
165                            new Class[] {method.getReturnType()});
166                }
167                catch (final Exception exception)
168                {
169                    // - ignore
170                }
171                if (setterMethod != null)
172                {
173                    method.setAccessible(true);
174                    final Object value = method.invoke(from);
175                    setterMethod.invoke(
176                        to,
177                        value);
178                }
179            }
180        }
181    }
182
183    /**
184     * Loads all methods from the clazz's super classes.
185     *
186     * @param methods the list to load full of methods.
187     * @param clazz the class to retrieve the methods.
188     * @return the loaded methods.
189     */
190    private static Set loadSuperMethods(
191        final Class clazz,
192        final Set methods)
193    {
194        if (clazz.getSuperclass() != null)
195        {
196            methods.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredMethods()));
197            methods.addAll(loadSuperMethods(
198                    clazz.getSuperclass(),
199                    methods));
200        }
201        return methods;
202    }
203}