KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > scripting > jruby > JRubyScriptUtils


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.scripting.jruby;
18
19 import java.lang.reflect.Array JavaDoc;
20 import java.lang.reflect.InvocationHandler JavaDoc;
21 import java.lang.reflect.Method JavaDoc;
22 import java.lang.reflect.Proxy JavaDoc;
23 import java.util.Collections JavaDoc;
24 import java.util.List JavaDoc;
25
26 import org.jruby.Ruby;
27 import org.jruby.RubyArray;
28 import org.jruby.RubyException;
29 import org.jruby.RubyNil;
30 import org.jruby.ast.ClassNode;
31 import org.jruby.ast.Colon2Node;
32 import org.jruby.ast.NewlineNode;
33 import org.jruby.ast.Node;
34 import org.jruby.exceptions.JumpException;
35 import org.jruby.exceptions.RaiseException;
36 import org.jruby.javasupport.JavaEmbedUtils;
37 import org.jruby.runtime.builtin.IRubyObject;
38
39 import org.springframework.aop.support.AopUtils;
40 import org.springframework.core.NestedRuntimeException;
41 import org.springframework.util.ClassUtils;
42
43 /**
44  * Utility methods for handling JRuby-scripted objects.
45  *
46  * <p>Note: As of Spring 2.0.4, this class requires JRuby 0.9.8 or higher.
47  *
48  * @author Rob Harrop
49  * @author Juergen Hoeller
50  * @author Rick Evans
51  * @since 2.0
52  */

53 public abstract class JRubyScriptUtils {
54
55     /**
56      * Create a new JRuby-scripted object from the given script source,
57      * using the default {@link ClassLoader}.
58      * @param scriptSource the script source text
59      * @param interfaces the interfaces that the scripted Java object is to implement
60      * @return the scripted Java object
61      * @throws JumpException in case of JRuby parsing failure
62      * @see ClassUtils#getDefaultClassLoader()
63      */

64     public static Object JavaDoc createJRubyObject(String JavaDoc scriptSource, Class JavaDoc[] interfaces) throws JumpException {
65         return createJRubyObject(scriptSource, interfaces, ClassUtils.getDefaultClassLoader());
66     }
67
68     /**
69      * Create a new JRuby-scripted object from the given script source.
70      * @param scriptSource the script source text
71      * @param interfaces the interfaces that the scripted Java object is to implement
72      * @param classLoader the {@link ClassLoader} to create the script proxy with
73      * @return the scripted Java object
74      * @throws JumpException in case of JRuby parsing failure
75      */

76     public static Object JavaDoc createJRubyObject(String JavaDoc scriptSource, Class JavaDoc[] interfaces, ClassLoader JavaDoc classLoader) {
77         Ruby ruby = initializeRuntime();
78
79         Node scriptRootNode = ruby.parse(scriptSource, "", null);
80         IRubyObject rubyObject = ruby.eval(scriptRootNode);
81
82         if (rubyObject instanceof RubyNil) {
83             String JavaDoc className = findClassName(scriptRootNode);
84             rubyObject = ruby.evalScript("\n" + className + ".new");
85         }
86         // still null?
87
if (rubyObject instanceof RubyNil) {
88             throw new IllegalStateException JavaDoc("Compilation of JRuby script returned RubyNil: " + rubyObject);
89         }
90
91         return Proxy.newProxyInstance(classLoader, interfaces, new RubyObjectInvocationHandler(rubyObject, ruby));
92     }
93
94     /**
95      * Initializes an instance of the {@link org.jruby.Ruby} runtime.
96      */

97     private static Ruby initializeRuntime() {
98         return JavaEmbedUtils.initialize(Collections.EMPTY_LIST);
99     }
100
101     /**
102      * Given the root {@link Node} in a JRuby AST will locate the name of the
103      * class defined by that AST.
104      * @throws IllegalArgumentException if no class is defined by the supplied AST
105      */

106     private static String JavaDoc findClassName(Node rootNode) {
107         ClassNode classNode = findClassNode(rootNode);
108         if (classNode == null) {
109             throw new IllegalArgumentException JavaDoc("Unable to determine class name for root node '" + rootNode + "'");
110         }
111         Colon2Node node = (Colon2Node) classNode.getCPath();
112         return node.getName();
113     }
114
115     /**
116      * Find the first {@link ClassNode} under the supplied {@link Node}.
117      * @return the found <code>ClassNode</code>, or <code>null</code>
118      * if no {@link ClassNode} is found
119      */

120     private static ClassNode findClassNode(Node node) {
121         if (node instanceof ClassNode) {
122             return (ClassNode) node;
123         }
124         List JavaDoc children = node.childNodes();
125         for (int i = 0; i < children.size(); i++) {
126             Node child = (Node) children.get(i);
127             if (child instanceof ClassNode) {
128                 return (ClassNode) child;
129             } else if (child instanceof NewlineNode) {
130                 NewlineNode nn = (NewlineNode) child;
131                 Node found = findClassNode(nn.getNextNode());
132                 if (found instanceof ClassNode) {
133                     return (ClassNode) found;
134                 }
135             }
136         }
137         for (int i = 0; i < children.size(); i++) {
138             Node child = (Node) children.get(i);
139             Node found = findClassNode(child);
140             if (found instanceof ClassNode) {
141                 return (ClassNode) found;
142             }
143         }
144         return null;
145     }
146
147
148     /**
149      * InvocationHandler that invokes a JRuby script method.
150      */

151     private static class RubyObjectInvocationHandler implements InvocationHandler JavaDoc {
152
153         private final IRubyObject rubyObject;
154
155         private final Ruby ruby;
156
157         public RubyObjectInvocationHandler(IRubyObject rubyObject, Ruby ruby) {
158             this.rubyObject = rubyObject;
159             this.ruby = ruby;
160         }
161
162         public Object JavaDoc invoke(Object JavaDoc proxy, Method JavaDoc method, Object JavaDoc[] args) throws Throwable JavaDoc {
163             if (AopUtils.isEqualsMethod(method)) {
164                 return (isProxyForSameRubyObject(args[0]) ? Boolean.TRUE : Boolean.FALSE);
165             }
166             if (AopUtils.isHashCodeMethod(method)) {
167                 return new Integer JavaDoc(this.rubyObject.hashCode());
168             }
169             if (AopUtils.isToStringMethod(method)) {
170                 return "JRuby object [" + this.rubyObject + "]";
171             }
172             try {
173                 IRubyObject[] rubyArgs = convertToRuby(args);
174                 IRubyObject rubyResult =
175                         this.rubyObject.callMethod(this.ruby.getCurrentContext(), method.getName(), rubyArgs);
176                 return convertFromRuby(rubyResult, method.getReturnType());
177             }
178             catch (RaiseException ex) {
179                 throw new JRubyExecutionException(ex);
180             }
181         }
182
183         private boolean isProxyForSameRubyObject(Object JavaDoc other) {
184             if (!Proxy.isProxyClass(other.getClass())) {
185                 return false;
186             }
187             InvocationHandler JavaDoc ih = Proxy.getInvocationHandler(other);
188             return (ih instanceof RubyObjectInvocationHandler &&
189                     this.rubyObject.equals(((RubyObjectInvocationHandler) ih).rubyObject));
190         }
191
192         private IRubyObject[] convertToRuby(Object JavaDoc[] javaArgs) {
193             if (javaArgs == null || javaArgs.length == 0) {
194                 return new IRubyObject[0];
195             }
196             IRubyObject[] rubyArgs = new IRubyObject[javaArgs.length];
197             for (int i = 0; i < javaArgs.length; ++i) {
198                 rubyArgs[i] = JavaEmbedUtils.javaToRuby(this.ruby, javaArgs[i]);
199             }
200             return rubyArgs;
201         }
202
203         private Object JavaDoc convertFromRuby(IRubyObject rubyResult, Class JavaDoc returnType) {
204             Object JavaDoc result = JavaEmbedUtils.rubyToJava(this.ruby, rubyResult, returnType);
205             if (result instanceof RubyArray && returnType.isArray()) {
206                 result = convertFromRubyArray(((RubyArray) result).toJavaArray(), returnType);
207             }
208             return result;
209         }
210
211         private Object JavaDoc convertFromRubyArray(IRubyObject[] rubyArray, Class JavaDoc returnType) {
212             Class JavaDoc targetType = returnType.getComponentType();
213             Object JavaDoc javaArray = Array.newInstance(targetType, rubyArray.length);
214             for (int i = 0; i < rubyArray.length; i++) {
215                 IRubyObject rubyObject = rubyArray[i];
216                 Array.set(javaArray, i, convertFromRuby(rubyObject, targetType));
217             }
218             return javaArray;
219         }
220     }
221
222
223     /**
224      * Exception thrown in response to a JRuby {@link RaiseException}
225      * being thrown from a JRuby method invocation.
226      * <p>Introduced because the <code>RaiseException</code> class does not
227      * have useful {@link Object#toString()}, {@link Throwable#getMessage()},
228      * and {@link Throwable#printStackTrace} implementations.
229      */

230     public static class JRubyExecutionException extends NestedRuntimeException {
231
232         /**
233          * Create a new <code>JRubyException</code>,
234          * wrapping the given JRuby <code>RaiseException</code>.
235          * @param ex the cause (must not be <code>null</code>)
236          */

237         public JRubyExecutionException(RaiseException ex) {
238             super(buildMessage(ex), ex);
239         }
240
241         private static String JavaDoc buildMessage(RaiseException ex) {
242             RubyException rubyEx = ex.getException();
243             return (rubyEx != null && rubyEx.message != null) ? rubyEx.message.toString() : "Unexpected JRuby error";
244         }
245     }
246
247 }
248
Popular Tags