View Javadoc
1   package org.andromda.scriptwrappers;
2   
3   import java.lang.reflect.Method;
4   import java.util.Arrays;
5   import java.util.Iterator;
6   import java.util.LinkedHashSet;
7   import java.util.Set;
8   import bsh.EvalError;
9   import bsh.Interpreter;
10  import bsh.Primitive;
11  
12  /**
13   * This is a wrapper class for a BeanShell script. The generated Java classes contain a private
14   * final copy of this wrapper and delegates all methods to this class so that their BeanShell-counterparts
15   * can be executed.
16   *
17   * @author Chad Brandon
18   */
19  public class BshScriptWrapper
20  {
21      private final Interpreter interpreter;
22      private String scriptPath;
23      private Object stub;
24  
25      /**
26       * StubClass is always the generated class (not any subclasses),
27       * while stub may be an instance of a subclassed scripted class.
28       * @param stub
29       * @param scriptPath
30       * @throws InstantiationError
31       */
32      public BshScriptWrapper(
33          Object stub,
34          String scriptPath)
35          throws InstantiationError
36      {
37          this.stub = stub;
38          this.scriptPath = scriptPath;
39          this.interpreter = initialize(
40                  stub,
41                  scriptPath);
42      }
43  
44      @SuppressWarnings("unused")
45      private BshScriptWrapper()
46      {
47          this.interpreter = null;
48      }
49  
50      /**
51       * Initializes the interpreter.
52       *
53       * @param stub the stub class.
54       * @param scriptPath the path to the script file.
55       * @return the initialized interpreter.
56       */
57      private final Interpreter initialize(
58          Object stub,
59          String scriptPath)
60      {
61          final Interpreter interpreter = new Interpreter();
62          interpreter.setClassLoader(stub.getClass().getClassLoader());
63          return interpreter;
64      }
65  
66      /**
67       * Invokes the method with the given <code>methodName</code> on the  instance.
68       *
69       * @param methodName the name of the method to invoke.
70       * @param args the arguments to pass to the method.
71       * @return the return result of invoking the operation.
72       */
73      public Object invoke(
74          String methodName,
75          Object[] args)
76      {
77          try
78          {
79              try
80              {
81                  final Class stubClass = stub.getClass();
82                  this.interpreter.source(scriptPath);
83                  this.interpreter.set(
84                      "instance",
85                      interpreter.eval(" new " + stubClass.getName() + "();"));
86                  this.interpreter.set(
87                      "stub",
88                      stub);
89  
90                  // - copy any properties
91                  this.interpreter.eval(BshScriptWrapper.class.getName() + ".copyProperties(stub, instance);");
92              }
93              catch (final Exception exception)
94              {
95                  exception.printStackTrace();
96                  throw new InstantiationError("Problems instantiating script '" + scriptPath + "':" + exception);
97              }
98              final StringBuilder arguments = new StringBuilder();
99              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 }