001    /*
002     $Id: GroovyEngine.java 4166 2006-10-25 07:07:33Z paulk $
003    
004     Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005    
006     Redistribution and use of this software and associated documentation
007     ("Software"), with or without modification, are permitted provided
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    package org.codehaus.groovy.bsf;
047    
048    import groovy.lang.Closure;
049    import groovy.lang.GroovyShell;
050    import org.apache.bsf.BSFDeclaredBean;
051    import org.apache.bsf.BSFException;
052    import org.apache.bsf.BSFManager;
053    import org.apache.bsf.util.BSFEngineImpl;
054    import org.apache.bsf.util.BSFFunctions;
055    import org.codehaus.groovy.runtime.InvokerHelper;
056    
057    import java.util.Vector;
058    
059    /**
060     * A BSF Engine for the <a href="http://groovy.codehaus.org/">Groovy</a>
061     * scripting language.
062     * <p/>
063     * It's derived from the Jython / JPython engine
064     *
065     * @author James Strachan
066     */
067    public class GroovyEngine extends BSFEngineImpl {
068        protected GroovyShell shell;
069    
070        /*
071         * Convert a non java class name to a java classname
072         * This is used to convert a script name to a name
073         * that can be used as a classname with the script is
074         * loaded in GroovyClassloader#load()
075         * The method simply replaces any invalid characters
076         * with "_".
077         */
078        private String convertToValidJavaClassname(String inName) {
079            if (inName == null || inName.equals("")) {
080                return "_";
081            }
082            StringBuffer output = new StringBuffer(inName.length());
083            boolean firstChar = true;
084            for (int i = 0; i < inName.length(); ++i) {
085                char ch = inName.charAt(i);
086                if (firstChar && !Character.isJavaIdentifierStart(ch)) {
087                    ch = '_';
088                } else if (!firstChar
089                        && !(Character.isJavaIdentifierPart(ch) || ch == '.')) {
090                    ch = '_';
091                }
092                firstChar = (ch == '.');
093                output.append(ch);
094            }
095            return output.toString();
096        }
097    
098        /**
099         * Allow an anonymous function to be declared and invoked
100         */
101        public Object apply(String source, int lineNo, int columnNo, Object funcBody, Vector paramNames,
102                            Vector arguments) throws BSFException {
103            Object object = eval(source, lineNo, columnNo, funcBody);
104            if (object instanceof Closure) {
105                // lets call the function
106                Closure closure = (Closure) object;
107                return closure.call(arguments.toArray());
108            }
109            return object;
110        }
111    
112        /**
113         * Call the named method of the given object.
114         */
115        public Object call(Object object, String method, Object[] args) throws BSFException {
116            return InvokerHelper.invokeMethod(object, method, args);
117        }
118    
119        /**
120         * Evaluate an expression.
121         */
122        public Object eval(String source, int lineNo, int columnNo, Object script) throws BSFException {
123            try {
124                source = convertToValidJavaClassname(source);
125                return getEvalShell().evaluate(script.toString(), source);
126            } catch (Exception e) {
127                throw new BSFException(BSFException.REASON_EXECUTION_ERROR, "exception from Groovy: " + e, e);
128            }
129        }
130    
131        /**
132         * Execute a script.
133         */
134        public void exec(String source, int lineNo, int columnNo, Object script) throws BSFException {
135            try {
136                // use evaluate to pass in the BSF variables
137                source = convertToValidJavaClassname(source);
138                getEvalShell().evaluate(script.toString(), source);
139            } catch (Exception e) {
140                throw new BSFException(BSFException.REASON_EXECUTION_ERROR, "exception from Groovy: " + e, e);
141            }
142        }
143    
144        /**
145         * Initialize the engine.
146         */
147        public void initialize(BSFManager mgr, String lang, Vector declaredBeans) throws BSFException {
148            super.initialize(mgr, lang, declaredBeans);
149    
150            // create a shell
151            shell = new GroovyShell(mgr.getClassLoader());
152    
153            // register the mgr with object name "bsf"
154            shell.setVariable("bsf", new BSFFunctions(mgr, this));
155    
156            int size = declaredBeans.size();
157            for (int i = 0; i < size; i++) {
158                declareBean((BSFDeclaredBean) declaredBeans.elementAt(i));
159            }
160        }
161    
162        /**
163         * Declare a bean
164         */
165        public void declareBean(BSFDeclaredBean bean) throws BSFException {
166            shell.setVariable(bean.name, bean.bean);
167        }
168    
169        /**
170         * Undeclare a previously declared bean.
171         */
172        public void undeclareBean(BSFDeclaredBean bean) throws BSFException {
173            shell.setVariable(bean.name, null);
174        }
175    
176        /**
177         * @return a newly created GroovyShell using the same variable scope but a new class loader
178         */
179        protected GroovyShell getEvalShell() {
180            return new GroovyShell(shell);
181        }
182    }