/* * Copyright 2004,2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.bsf.util; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import org.apache.bsf.BSFEngine; import org.apache.bsf.BSFException; import org.apache.bsf.BSFManager; /** * This class contains utilities that language integrators can use * when implementing the BSFEngine interface. * * @author Sanjiva Weerawarana * @author Sam Ruby * @author Rony G. Flatscher (added addEventListenerReturningEventInfos) */ public class EngineUtils { // the BSF class loader that knows how to load from the a specific // temp directory static BSFClassLoader bsfCL; // ---rgf, 2003-02-13, determine whether changing accessibility of Methods is possible static boolean bMethodHasSetAccessible=false; static { Class mc=Method.class; // get the "Method" class object Class arg[]={boolean.class}; // define an array with the primitive "boolean" pseudo class object try { mc.getMethod("setAccessible", arg ); // is this method available? bMethodHasSetAccessible=true; // no exception, hence method exists } catch (Exception e) { bMethodHasSetAccessible=false;// exception occurred, hence method does not exist } } /** * Add a script as a listener to some event coming out of an object. The * first two args identify the src of the event and the event set * and the rest identify the script which should be run when the event * fires. * * @param bean event source * @param eventSetName name of event set from event src to bind to * @param filter filter for events * @param engine BSFEngine which can run this script * @param manager BSFManager of the above engine * @param source (context info) the source of this expression * (e.g., filename) * @param lineNo (context info) the line number in source for expr * @param columnNo (context info) the column number in source for expr * @param script the script to execute when the event occurs * * @exception BSFException if anything goes wrong while running the script */ public static void addEventListener (Object bean, String eventSetName, String filter, BSFEngine engine, BSFManager manager, String source, int lineNo, int columnNo, Object script) throws BSFException { BSFEventProcessor ep = new BSFEventProcessor (engine, manager, filter, source, lineNo, columnNo, script); try { ReflectionUtils.addEventListener (bean, eventSetName, ep); } catch (Exception e) { e.printStackTrace (); throw new BSFException (BSFException.REASON_OTHER_ERROR, "ouch while adding event listener: " + e, e); } } /** * Add a script as a listener to some event coming out of an object. The * first two args identify the src of the event and the event set * and the rest identify the script which should be run when the event * fires. The processing will use the engine's apply() method. * * @param bean event source * @param eventSetName name of event set from event src to bind to * @param filter filter for events * @param engine BSFEngine which can run this script * @param manager BSFManager of the above engine * @param source (context info) the source of this expression (e.g., filename) * @param lineNo (context info) the line number in source for expr * @param columnNo (context info) the column number in source for expr * @param script the script to execute when the event occurs * @param dataFromScriptingEngine * this contains any object supplied by the scripting engine and gets sent * back with the supplied script, if the event occurs. * This could be used e.g. for indicating to the scripting engine which * scripting engine object/routine/function/procedure * should be ultimately informed of the event occurrence. * * @exception BSFException if anything goes wrong while running the script */ public static void addEventListenerReturningEventInfos ( Object bean, String eventSetName, String filter, BSFEngine engine, BSFManager manager, String source, int lineNo, int columnNo, Object script, Object dataFromScriptingEngine ) throws BSFException { BSFEventProcessorReturningEventInfos ep = new BSFEventProcessorReturningEventInfos (engine, manager, filter, source, lineNo, columnNo, script, dataFromScriptingEngine ); try { ReflectionUtils.addEventListener (bean, eventSetName, ep); } catch (Exception e) { e.printStackTrace (); throw new BSFException (BSFException.REASON_OTHER_ERROR, "ouch while adding event listener: " + e, e); } } /** * Finds and invokes a method with the given signature on the given * bean. The signature of the method that's invoked is first taken * as the types of the args, but if that fails, this tries to convert * any primitive wrapper type args to their primitive counterparts * to see whether a method exists that way. If it does, done. * * @param bean the object on which to invoke the method * @param methodName name of the method * @param args arguments to be given to the method * * @return the result of invoking the method, if any * * @exception BSFException if something goes wrong */ public static Object callBeanMethod (Object bean, String methodName, Object[] args) throws BSFException { Class[] argTypes = null; // determine arg types. note that a null argtype // matches any object type if (args != null) { argTypes = new Class[args.length]; for (int i = 0; i < args.length; i++) { argTypes[i] = (args[i] == null) ? null : args[i].getClass (); } } // we want to allow a static call to occur on an object, similar // to what Java allows. So isStaticOnly is set to false. boolean isStaticOnly = false; Class beanClass = (bean instanceof Class) ? (Class)bean : bean.getClass (); // now try to call method with the right signature try { Method m; try { m = MethodUtils.getMethod (beanClass, methodName, argTypes, isStaticOnly); } catch (NoSuchMethodException e) { // ok, so that didn't work - now try converting any primitive // wrapper types to their primitive counterparts try { // if args is null the NullPointerException will get caught // below and the right thing'll happen .. ugly but works for (int i = 0; i < args.length; i++) { if (args[i] instanceof Number) { if (args[i] instanceof Byte) argTypes[i] = byte.class; else if (args[i] instanceof Integer) argTypes[i] = int.class; else if (args[i] instanceof Long) argTypes[i] = long.class; else if (args[i] instanceof Float) argTypes[i] = float.class; else if (args[i] instanceof Double ) argTypes[i] = double.class; else if (args[i] instanceof Short ) argTypes[i] = short.class; } else if (args[i] instanceof Boolean) argTypes[i] = boolean.class; else if (args[i] instanceof Character) argTypes[i] = char.class; } m = MethodUtils.getMethod (beanClass, methodName, argTypes, isStaticOnly); } catch (Exception e2) { // throw the original throw e; } } // call it, and return the result try { return m.invoke (bean, args); } catch (Exception e) // 2003-02-23, --rgf, maybe an IllegalAccessException? { if (e instanceof IllegalAccessException && bMethodHasSetAccessible && Modifier.isPublic(m.getModifiers()) ) // if a public method allow access to it { m.setAccessible(true); // allow unconditional access to method return m.invoke (bean, args); } // re-throw the exception throw e; } } catch (Exception e) { // something went wrong while invoking method Throwable t = (e instanceof InvocationTargetException) ? ((InvocationTargetException)e).getTargetException () : null; throw new BSFException (BSFException.REASON_OTHER_ERROR, "method invocation failed: " + e + ((t==null) ? "" : (" target exception: " + t)), t); } } /** * Creates a new bean. The signature of the constructor that's invoked * is first taken as the types of the args, but if that fails, this tries * to convert any primitive wrapper type args to their primitive * counterparts to see whether a method exists that way. If it does, done. * * @param className fully qualified name of class to instantiate * @param args array of constructor args (or null if none) * * @return the created bean * * @exception BSFException if something goes wrong (@see * org.apache.cs.util.MethodUtils for the real * exceptions that can occur). */ public static Object createBean (String className, Object args[]) throws BSFException { Bean obj; Class[] argTypes = null; if (args != null) { argTypes = new Class[args.length]; for (int i = 0; i < args.length; i++) { argTypes[i] = (args[i] != null) ? args[i].getClass () : null; } } try { try { obj = ReflectionUtils.createBean (null, className, argTypes, args); return obj.value; } catch (NoSuchMethodException me) { // ok, so that didn't work - now try converting any primitive // wrapper types to their primitive counterparts try { // if args is null the NullPointerException will get caught // below and the right thing'll happen .. ugly but works for (int i = 0; i < args.length; i++) { if (args[i] instanceof Number) argTypes[i] = byte.class; else if (args[i] instanceof Boolean) argTypes[i] = boolean.class; else if (args[i] instanceof Character) argTypes[i] = char.class; } obj = ReflectionUtils.createBean (null, className, argTypes, args); return obj.value; } catch (Exception e) { // throw the previous exception throw me; } } } catch (Exception e) { throw new BSFException (BSFException.REASON_OTHER_ERROR, e.getMessage (), e); } } /** * Given a class return the type signature string fragment for it. * That is, return "I" for int, "J" for long, ... etc.. * * @param cl class object for whom the signature fragment is needed. * * @return the string representing the type signature */ public static String getTypeSignatureString (Class cl) { if (cl.isPrimitive ()) { if (cl == boolean.class) return "Z"; else if (cl == byte.class) return "B"; else if (cl == char.class) return "C"; else if (cl == short.class) return "S"; else if (cl == int.class) return "I"; else if (cl == long.class) return "J"; else if (cl == float.class) return "F"; else if (cl == double.class) return "D"; else return "V"; } else { StringBuffer sb = new StringBuffer ("L"); sb.append (cl.getName ()); sb.append (";"); return sb.toString().replace ('.', '/'); } } /** * Load a class using the class loader of given manager. If that fails * try using a class loader that loads from the tempdir of the manager. * * @param mgr BSFManager who's classLoader and tempDir props are * consulted * @param name name of the class to load * * @return the loaded class * * @exception BSFException if something goes wrong. */ public static Class loadClass (BSFManager mgr, String name) throws BSFException { ClassLoader classLoader = mgr.getClassLoader (); try { return (classLoader == null) ? // Class.forName (name) Thread.currentThread().getContextClassLoader().loadClass (name) : classLoader.loadClass (name); } catch (ClassNotFoundException e) { // try to load it from the temp dir using my own class loader try { if (bsfCL == null) bsfCL = new BSFClassLoader (); bsfCL.setTempDir (mgr.getTempDir ()); return bsfCL.loadClass (name); } catch (ClassNotFoundException e2) { throw new BSFException (BSFException.REASON_OTHER_ERROR, "unable to load class '" + name + "':" + e, e); } } } }