All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bsh.NameSpace Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * The AIBench Shell Plugin
 * %%
 * Copyright (C) 2006 - 2017 Daniel Glez-Peña and Florentino Fdez-Riverola
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */
/*****************************************************************************
 *                                                                           *
 *  This file is part of the BeanShell Java Scripting distribution.          *
 *  Documentation and updates may be found at http://www.beanshell.org/      *
 *                                                                           *
 *  Sun Public License Notice:                                               *
 *                                                                           *
 *  The contents of this file are subject to the Sun Public License Version  *
 *  1.0 (the "License"); you may not use this file except in compliance with *
 *  the License. A copy of the License is available at http://www.sun.com    * 
 *                                                                           *
 *  The Original Code is BeanShell. The Initial Developer of the Original    *
 *  Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright     *
 *  (C) 2000.  All Rights Reserved.                                          *
 *                                                                           *
 *  GNU Public License Notice:                                               *
 *                                                                           *
 *  Alternatively, the contents of this file may be used under the terms of  *
 *  the GNU Lesser General Public License (the "LGPL"), in which case the    *
 *  provisions of LGPL are applicable instead of those above. If you wish to *
 *  allow use of your version of this file only under the  terms of the LGPL *
 *  and not to allow others to use your version of this file under the SPL,  *
 *  indicate your decision by deleting the provisions above and replace      *
 *  them with the notice and other provisions required by the LGPL.  If you  *
 *  do not delete the provisions above, a recipient may use your version of  *
 *  this file under either the SPL or the LGPL.                              *
 *                                                                           *
 *  Patrick Niemeyer ([email protected])                                           *
 *  Author of Learning Java, O'Reilly & Associates                           *
 *  http://www.pat.net/~pat/                                                 *
 *                                                                           *
 *****************************************************************************/

package bsh;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

/**
 * A namespace in which methods, variables, and imports (class names) live. This
 * is package public because it is used in the implementation of some bsh
 * commands. However for normal use you should be using methods on
 * bsh.Interpreter to interact with your scripts.
 * 

* * A bsh.This object is a thin layer over a NameSpace that associates it with an * Interpreter instance. Together they comprise a Bsh scripted object context. *

* * Note: I'd really like to use collections here, but we have to keep this * compatible with JDK1.1 */ /* * Thanks to Slava Pestov (of jEdit fame) for import caching enhancements. Note: * This class has gotten too big. It should be broken down a bit. */ public class NameSpace implements java.io.Serializable, BshClassManager.Listener, NameSource { private static final long serialVersionUID = 1L; public static final NameSpace JAVACODE = new NameSpace((BshClassManager) null, "Called from compiled Java code."); static { JAVACODE.isMethod = true; } // Begin instance data // Note: if we add something here we should reset it in the clear() // method. /** * The name of this namespace. If the namespace is a method body namespace * then this is the name of the method. If it's a class or class instance * then it's the name of the class. */ private String nsName; private NameSpace parent; private Hashtable variables; private Hashtable methods; protected Hashtable importedClasses; private Vector importedPackages; private Vector importedCommands; private Vector importedObjects; private Vector importedStatic; private String packageName; transient private BshClassManager classManager; // See notes in getThis() private This thisReference; /** Name resolver objects */ private Hashtable names; /** * The node associated with the creation of this namespace. This is used * support getInvocationLine() and getInvocationText(). */ SimpleNode callerInfoNode; /** * Note that the namespace is a method body namespace. This is used for * printing stack traces in exceptions. */ boolean isMethod; /** * Note that the namespace is a class body or class instance namespace. This * is used for controlling static/object import precedence, etc. */ /* * Note: We will ll move this behavior out to a subclass of NameSpace, but * we'll start here. */ boolean isClass; Class classStatic; Object classInstance; void setClassStatic(Class clas) { this.classStatic = clas; importStatic(clas); } void setClassInstance(Object instance) { this.classInstance = instance; importObject(instance); } Object getClassInstance() throws UtilEvalError { if (classInstance != null) return classInstance; if (classStatic != null // || ( getParent()!=null && getParent().classStatic != null ) ) throw new UtilEvalError("Can't refer to class instance from static context."); else throw new InterpreterError("Can't resolve class instance 'this' in: " + this); } /** * Local class cache for classes resolved through this namespace using * getClass() (taking into account imports). Only unqualified class names * are cached here (those which might be imported). Qualified names are * always absolute and are cached by BshClassManager. */ transient private Hashtable classCache; // End instance data // Begin constructors /** * @param parent * the parent namespace of this namespace. Child namespaces * inherit all variables and methods of their parent and can (of * course) override / shadow them. * @param name * the name of the namespace. */ public NameSpace(NameSpace parent, String name) { // Note: in this case parent must have a class manager. this(parent, null, name); } public NameSpace(BshClassManager classManager, String name) { this(null, classManager, name); } public NameSpace(NameSpace parent, BshClassManager classManager, String name) { // We might want to do this here rather than explicitly in // Interpreter // for global (see also prune()) // if ( classManager == null && (parent == null ) ) // create our own class manager? setName(name); setParent(parent); setClassManager(classManager); // Register for notification of classloader change if (classManager != null) classManager.addListener(this); } // End constructors public void setName(String name) { this.nsName = name; } /** * The name of this namespace. If the namespace is a method body namespace * then this is the name of the method. If it's a class or class instance * then it's the name of the class. * * @return the name of this namespace. */ public String getName() { return this.nsName; } /** * Set the node associated with the creation of this namespace. This is used * in debugging and to support the getInvocationLine() and * getInvocationText() methods. * * @param node * the node associated with the creation of this namespace. */ void setNode(SimpleNode node) { callerInfoNode = node; } /** */ SimpleNode getNode() { if (callerInfoNode != null) return callerInfoNode; if (parent != null) return parent.getNode(); else return null; } /** * Resolve name to an object through this namespace. * * @param name * the name of the object. * @param interpreter * the interpreter. * @return an object associated to the provided name in this namespace. * @throws UtilEvalError * if an error occurs during evaluation. */ public Object get(String name, Interpreter interpreter) throws UtilEvalError { CallStack callstack = new CallStack(this); return getNameResolver(name).toObject(callstack, interpreter); } /** * Set the variable through this namespace. This method obeys the * LOCALSCOPING property to determine how variables are set. *

* Note: this method is primarily intended for use internally. If you use * this method outside of the bsh package and wish to set variables with * primitive values you will have to wrap them using bsh.Primitive. *

* *

* Setting a new variable (which didn't exist before) or removing a variable * causes a namespace change. *

* * @param name * the name of the variable. * @param value * the value of the variable. * @param strictJava * specifies whether strict java rules are applied. * @throws UtilEvalError * if an error occurs during evaluation. * @see bsh.Primitive */ public void setVariable(String name, Object value, boolean strictJava) throws UtilEvalError { // if localscoping switch follow strictJava, else recurse boolean recurse = Interpreter.LOCALSCOPING ? strictJava : true; setVariable(name, value, strictJava, recurse); } /** * Set a variable explicitly in the local scope. */ void setLocalVariable(String name, Object value, boolean strictJava) throws UtilEvalError { setVariable(name, value, strictJava, false/* recurse */); } /** * Set the value of a the variable 'name' through this namespace. The * variable may be an existing or non-existing variable. It may live in this * namespace or in a parent namespace if recurse is true. *

* Note: This method is not public and does *not* know about LOCALSCOPING. * Its caller methods must set recurse intelligently in all situations * (perhaps based on LOCALSCOPING). * *

* Note: this method is primarily intended for use internally. If you use * this method outside of the bsh package and wish to set variables with * primitive values you will have to wrap them using bsh.Primitive. * * @see bsh.Primitive *

* Setting a new variable (which didn't exist before) or removing a * variable causes a namespace change. * * @param strictJava * specifies whether strict java rules are applied. * @param recurse * determines whether we will search for the variable in our * parent's scope before assigning locally. * @throws UtilEvalError * if an error occurs during evaluation. */ void setVariable(String name, Object value, boolean strictJava, boolean recurse) throws UtilEvalError { if (variables == null) variables = new Hashtable(); // primitives should have been wrapped if (value == null) throw new InterpreterError("null variable value"); // Locate the variable definition if it exists. Variable existing = getVariableImpl(name, recurse); // Found an existing variable here (or above if recurse allowed) if (existing != null) { try { existing.setValue(value, Variable.ASSIGNMENT); } catch (UtilEvalError e) { throw new UtilEvalError("Variable assignment: " + name + ": " + e.getMessage()); } } else // No previous variable definition found here (or above if // recurse) { if (strictJava) throw new UtilEvalError("(Strict Java mode) Assignment to undeclared variable: " + name); // If recurse, set global untyped var, else set it here. // NameSpace varScope = recurse ? getGlobal() : this; // This modification makes default allocation local NameSpace varScope = this; varScope.variables.put(name, new Variable(name, value, null/* modifiers */)); // nameSpaceChanged() on new variable addition nameSpaceChanged(); } } /** * Remove the variable from the namespace. * * @param name * the name of the variable to be removed. */ public void unsetVariable(String name) { if (variables != null) { variables.remove(name); nameSpaceChanged(); } } /** * Returns the names of variables defined in this namespace. (This does not * show variables in parent namespaces). * * @return the names of variables defined in this namespace. (This does not * show variables in parent namespaces). */ public String[] getVariableNames() { if (variables == null) return new String[0]; else return enumerationToStringArray(variables.keys()); } /** * Returns the names of methods declared in this namespace. (This does not * include methods in parent namespaces). * * @return the names of methods declared in this namespace. (This does not * include methods in parent namespaces). */ public String[] getMethodNames() { if (methods == null) return new String[0]; else return enumerationToStringArray(methods.keys()); } /** * Returns the methods defined in this namespace. (This does not show * methods in parent namespaces). Note: This will probably be renamed * getDeclaredMethods(). * * @return the methods defined in this namespace. (This does not show * methods in parent namespaces). Note: This will probably be * renamed getDeclaredMethods(). */ public BshMethod[] getMethods() { if (methods == null) return new BshMethod[0]; else return flattenMethodCollection(methods.elements()); } private String[] enumerationToStringArray(Enumeration e) { Vector v = new Vector(); while (e.hasMoreElements()) v.addElement(e.nextElement()); String[] sa = new String[v.size()]; v.copyInto(sa); return sa; } /** * Flatten the vectors of overloaded methods to a single array. * * @param e * an enumeration with the vectors of overloaded methods. * @return a single array with the overloaded methods. * @see #getMethods() */ private BshMethod[] flattenMethodCollection(Enumeration e) { Vector v = new Vector(); while (e.hasMoreElements()) { Object o = e.nextElement(); if (o instanceof BshMethod) v.addElement(o); else { Vector ov = (Vector) o; for (int i = 0; i < ov.size(); i++) v.addElement(ov.elementAt(i)); } } BshMethod[] bma = new BshMethod[v.size()]; v.copyInto(bma); return bma; } /** * Returns the parent namespace. Note: this isn't quite the same as * getSuper(). getSuper() returns 'this' if we are at the root namespace. * * @return the parent namespace. Note: this isn't quite the same as * getSuper(). getSuper() returns 'this' if we are at the root * namespace. */ public NameSpace getParent() { return parent; } /** * Returns the parent namespace' This reference or this namespace' This * reference if we are the top. * * @param declaringInterpreter * the declaring interpreter. * @return the parent namespace' This reference or this namespace' This * reference if we are the top. */ public This getSuper(Interpreter declaringInterpreter) { if (parent != null) return parent.getThis(declaringInterpreter); else return getThis(declaringInterpreter); } /** * Returns the top level namespace or this namespace if we are the top. * Note: this method should probably return type bsh.This to be consistent * with getThis(); * * @param declaringInterpreter * the declaring interpreter. * @return the top level namespace or this namespace if we are the top. */ public This getGlobal(Interpreter declaringInterpreter) { if (parent != null) return parent.getGlobal(declaringInterpreter); else return getThis(declaringInterpreter); } /** *

* A This object is a thin layer over a namespace, comprising a bsh object * context. It handles things like the interface types the bsh object * supports and aspects of method invocation on it. *

* *

* The declaringInterpreter is here to support callbacks from Java through * generated proxies. The scripted object "remembers" who created it for * things like printing messages and other per-interpreter phenomenon when * called externally from Java. *

* * @param declaringInterpreter * the declaring interpreter. * @return the This object. */ /* * Note: we need a singleton here so that things like 'this == this' work * (and probably a good idea for speed). * * Caching a single instance here seems technically incorrect, considering * the declaringInterpreter could be different under some circumstances. * (Case: a child interpreter running a source() / eval() command ). However * the effect is just that the main interpreter that executes your script * should be the one involved in call-backs from Java. * * I do not know if there are corner cases where a child interpreter would * be the first to use a This reference in a namespace or if that would even * cause any problems if it did... We could do some experiments to find * out... and if necessary we could cache on a per interpreter basis if we * had weak references... We might also look at skipping over child * interpreters and going to the parent for the declaring interpreter, so * we'd be sure to get the top interpreter. */ This getThis(Interpreter declaringInterpreter) { if (thisReference == null) thisReference = This.getThis(this, declaringInterpreter); return thisReference; } public BshClassManager getClassManager() { if (classManager != null) return classManager; if (parent != null && parent != JAVACODE) return parent.getClassManager(); System.out.println("experiment: creating class manager"); classManager = BshClassManager.createClassManager(null/* interp */); // Interpreter.debug("No class manager namespace:" +this); return classManager; } void setClassManager(BshClassManager classManager) { this.classManager = classManager; } /** * Used for serialization */ public void prune() { // Cut off from parent, we must have our own class manager. // Can't do this in the run() command (needs to resolve stuff) // Should we do it by default when we create a namespace will no // parent of class manager? if (this.classManager == null) // XXX if we keep the createClassManager in // getClassManager then we can axe // this? setClassManager(BshClassManager.createClassManager(null/* interp */)); setParent(null); } public void setParent(NameSpace parent) { this.parent = parent; // If we are disconnected from root we need to handle the def // imports if (parent == null) loadDefaultImports(); } /** * Get the specified variable in this namespace or a parent namespace. *

* Note: this method is primarily intended for use internally. If you use * this method outside of the bsh package you will have to use * Primitive.unwrap() to get primitive values. * * @param name * the name of the variable. * @return the variable value or Primitive.VOID if it is not defined. * @throws UtilEvalError * if an error occurs during evaluation. * @see Primitive#unwrap( Object ) */ public Object getVariable(String name) throws UtilEvalError { return getVariable(name, true); } /** * Get the specified variable in this namespace. * * @param name * the name of the variable. * @param recurse * If recurse is {@code null} then we recursively search through * parent namespaces for the variable. *

* Note: this method is primarily intended for use internally. If * you use this method outside of the bsh package you will have * to use Primitive.unwrap() to get primitive values. *

* * @return the variable value or Primitive.VOID if it is not defined. * @throws UtilEvalError * if an error occurs during evaluation. * @see Primitive#unwrap( Object ) */ public Object getVariable(String name, boolean recurse) throws UtilEvalError { Variable var = getVariableImpl(name, recurse); return unwrapVariable(var); } /** * Locate a variable and return the Variable object with optional recursion * through parent name spaces. *

* If this namespace is static, return only static variables. *

* * @param name * the name of the variable. * @param recurse * If recurse is {@code null} then we recursively search through * parent namespaces for the variable. *

* Note: this method is primarily intended for use internally. If * you use this method outside of the bsh package you will have * to use Primitive.unwrap() to get primitive values. *

* @return the Variable value or {@code null} if it is not defined. * @throws UtilEvalError * if an error occurs during evaluation. */ protected Variable getVariableImpl(String name, boolean recurse) throws UtilEvalError { Variable var = null; // Change import precedence if we are a class body/instance // Get imported first. if (var == null && isClass) var = getImportedVar(name); if (var == null && variables != null) var = (Variable) variables.get(name); // Change import precedence if we are a class body/instance if (var == null && !isClass) var = getImportedVar(name); // try parent if (recurse && (var == null) && (parent != null)) var = parent.getVariableImpl(name, recurse); return var; } /* * Get variables declared in this namespace. */ public Variable[] getDeclaredVariables() { if (variables == null) return new Variable[0]; Variable[] vars = new Variable[variables.size()]; int i = 0; for (Enumeration e = variables.elements(); e.hasMoreElements();) vars[i++] = (Variable) e.nextElement(); return vars; } /** * Unwrap a variable to its value. * * @param var * the variable to unwrap. * @return return the variable value. A {@code null} var is mapped to * Primitive.VOID * @throws UtilEvalError * if an error occurs during evaluation. */ protected Object unwrapVariable(Variable var) throws UtilEvalError { return (var == null) ? Primitive.VOID : var.getValue(); } /** * * @deprecated See #setTypedVariable( String, Class, Object, Modifiers ) * * @param name * the name of the variable. * @param type * the type of the variable. * @param value * the value of the variable. * @param isFinal * whether the variable is final or not. * @throws UtilEvalError * if an error occurs during evaluation. */ public void setTypedVariable(String name, Class type, Object value, boolean isFinal) throws UtilEvalError { Modifiers modifiers = new Modifiers(); if (isFinal) modifiers.addModifier(Modifiers.FIELD, "final"); setTypedVariable(name, type, value, modifiers); } /** * Declare a variable in the local scope and set its initial value. Value * may be null to indicate that we would like the default value for the * variable type. (e.g. 0 for integer types, null for object types). An * existing typed variable may only be set to the same type. If an untyped * variable of the same name exists it will be overridden with the new typed * var. The set will perform a Types.getAssignableForm() on the value if * necessary. * *

* Note: this method is primarily intended for use internally. If you use * this method outside of the bsh package and wish to set variables with * primitive values you will have to wrap them using bsh.Primitive. * * @see bsh.Primitive * * @param name * the name of the variable. * @param value * if value is {@code null}, you'll get the default value for the * type. * @param type * the type of the variable. * @param modifiers * may be {@code null}. * @throws UtilEvalError * if an error occurs during evaluation. */ public void setTypedVariable(String name, Class type, Object value, Modifiers modifiers) throws UtilEvalError { // checkVariableModifiers( name, modifiers ); if (variables == null) variables = new Hashtable(); // Setting a typed variable is always a local operation. Variable existing = getVariableImpl(name, false/* recurse */); // Null value is just a declaration // Note: we might want to keep any existing value here instead // of reset /* * // Moved to Variable if ( value == null ) value = * Primitive.getDefaultValue( type ); */ // does the variable already exist? if (existing != null) { // Is it typed? if (existing.getType() != null) { // If it had a different type throw error. // This allows declaring the same var again, but // not with // a different (even if assignable) type. if (existing.getType() != type) { throw new UtilEvalError("Typed variable: " + name + " was previously declared with type: " + existing.getType()); } else { // else set it and return existing.setValue(value, Variable.DECLARATION); return; } } // Careful here: // else fall through to override and install the new // typed version } // Add the new typed var variables.put(name, new Variable(name, type, value, modifiers)); } /** * Disallow static vars outside of a class. * * Note: this is primarily for internal use. * * @param name * is here just to allow the error message to use it. * @param method * the method to set. * @throws UtilEvalError * if an error occurs during evaluation. * * @see Interpreter#source( String ) * @see Interpreter#eval( String ) */ public void setMethod(String name, BshMethod method) throws UtilEvalError { // checkMethodModifiers( method ); if (methods == null) methods = new Hashtable(); Object m = methods.get(name); if (m == null) methods.put(name, method); else if (m instanceof BshMethod) { Vector v = new Vector(); v.addElement(m); v.addElement(method); methods.put(name, v); } else // Vector ((Vector) m).addElement(method); } /** * Returns the bsh method matching the specified signature declared in this * name space or a parent. * * @param name * the name of the method. * @param sig * the arguments signature. * @return the BshMethod or null if not found. * @see #getMethod( String, Class [], boolean ) * @see #getMethod( String, Class [] ) * @throws UtilEvalError * if an error occurs during evaluation. */ public BshMethod getMethod(String name, Class[] sig) throws UtilEvalError { return getMethod(name, sig, false/* declaredOnly */); } /** * Returns the bsh method matching the specified signature declared in this * name space or a parent. *

* Note: this method is primarily intended for use internally. If you use * this method outside of the bsh package you will have to be familiar with * BeanShell's use of the Primitive wrapper class. *

* * @param name * the method name. * @param sig * the method signature. * @param declaredOnly * if true then only methods declared directly in this namespace * will be found and no inherited or imported methods will be * visible. * @return the BshMethod or null if not found * @throws UtilEvalError * if an error occurs during evaluation. * @see bsh.Primitive */ public BshMethod getMethod(String name, Class[] sig, boolean declaredOnly) throws UtilEvalError { BshMethod method = null; // Change import precedence if we are a class body/instance // Get import first. if (method == null && isClass && !declaredOnly) method = getImportedMethod(name, sig); Object m = null; if (method == null && methods != null) { m = methods.get(name); // m contains either BshMethod or Vector of BshMethod if (m != null) { // unwrap BshMethod[] ma; if (m instanceof Vector) { Vector vm = (Vector) m; ma = new BshMethod[vm.size()]; vm.copyInto(ma); } else ma = new BshMethod[] { (BshMethod) m }; // Apply most specific signature matching Class[][] candidates = new Class[ma.length][]; for (int i = 0; i < ma.length; i++) candidates[i] = ma[i].getParameterTypes(); int match = Reflect.findMostSpecificSignature(sig, candidates); if (match != -1) method = ma[match]; } } if (method == null && !isClass && !declaredOnly) method = getImportedMethod(name, sig); // try parent if (!declaredOnly && (method == null) && (parent != null)) return parent.getMethod(name, sig); return method; } /** * Import a class name. Subsequent imports override earlier ones. * * @param name * the name of the class to import. */ public void importClass(String name) { if (importedClasses == null) importedClasses = new Hashtable(); importedClasses.put(Name.suffix(name, 1), name); nameSpaceChanged(); } /** * Subsequent imports override earlier ones. * * @param name * the name of the package to import. */ public void importPackage(String name) { if (importedPackages == null) importedPackages = new Vector(); // If it exists, remove it and add it at the end (avoid memory // leak) if (importedPackages.contains(name)) importedPackages.remove(name); importedPackages.addElement(name); nameSpaceChanged(); } /** * Import scripted or compiled BeanShell commands in the following package * in the classpath. You may use either "/" path or "." package notation. * e.g. importCommands("/bsh/commands") or importCommands("bsh.commands") * are equivalent. If a relative path style specifier is used then it is * made into an absolute path by prepending "/". * * @param name * the name of the commands to import. */ public void importCommands(String name) { if (importedCommands == null) importedCommands = new Vector(); // dots to slashes name = name.replace('.', '/'); // absolute if (!name.startsWith("/")) name = "/" + name; // remove trailing (but preserve case of simple "/") if (name.length() > 1 && name.endsWith("/")) name = name.substring(0, name.length() - 1); // If it exists, remove it and add it at the end (avoid memory // leak) if (importedCommands.contains(name)) importedCommands.remove(name); importedCommands.addElement(name); nameSpaceChanged(); } /** *

* A command is a scripted method or compiled command class implementing a * specified method signature. Commands are loaded from the classpath and * may be imported using the importCommands() method. *

* *

* This method searches the imported commands packages for a script or * command object corresponding to the name of the method. If it is a script * the script is sourced into this namespace and the BshMethod for the * requested signature is returned. If it is a compiled class the class is * returned. (Compiled command classes implement static invoke() methods). *

* *

* The imported packages are searched in reverse order, so that later * imports take priority. Currently only the first object (script or class) * with the appropriate name is checked. If another, overloaded form, is * located in another package it will not currently be found. This could be * fixed. *

* * @param name * is the name of the desired command method * @param argTypes * is the signature of the desired command method. * @param interpreter * the interpreter. * @return a BshMethod, Class, or {@code null} if no such command is found. * @throws UtilEvalError * if loadScriptedCommand throws UtilEvalError i.e. on errors * loading a script that was found */ public Object getCommand(String name, Class[] argTypes, Interpreter interpreter) throws UtilEvalError { if (Interpreter.DEBUG) Interpreter.debug("getCommand: " + name); BshClassManager bcm = interpreter.getClassManager(); if (importedCommands != null) { // loop backwards for precedence for (int i = importedCommands.size() - 1; i >= 0; i--) { String path = (String) importedCommands.elementAt(i); String scriptPath; if (path.equals("/")) scriptPath = path + name + ".bsh"; else scriptPath = path + "/" + name + ".bsh"; Interpreter.debug("searching for script: " + scriptPath); InputStream in = bcm.getResourceAsStream(scriptPath); if (in != null) return loadScriptedCommand(in, name, argTypes, scriptPath, interpreter); // Chop leading "/" and change "/" to "." String className; if (path.equals("/")) className = name; else className = path.substring(1).replace('/', '.') + "." + name; Interpreter.debug("searching for class: " + className); Class clas = bcm.classForName(className); if (clas != null) return clas; } } if (parent != null) return parent.getCommand(name, argTypes, interpreter); else return null; } protected BshMethod getImportedMethod(String name, Class[] sig) throws UtilEvalError { // Try object imports if (importedObjects != null) for (int i = 0; i < importedObjects.size(); i++) { Object object = importedObjects.elementAt(i); Class clas = object.getClass(); Method method = Reflect.resolveJavaMethod(getClassManager(), clas, name, sig, false/* onlyStatic */); if (method != null) return new BshMethod(method, object); } // Try static imports if (importedStatic != null) for (int i = 0; i < importedStatic.size(); i++) { Class clas = (Class) importedStatic.elementAt(i); Method method = Reflect.resolveJavaMethod(getClassManager(), clas, name, sig, true/* onlyStatic */); if (method != null) return new BshMethod(method, null/* object */); } return null; } protected Variable getImportedVar(String name) throws UtilEvalError { // Try object imports if (importedObjects != null) for (int i = 0; i < importedObjects.size(); i++) { Object object = importedObjects.elementAt(i); Class clas = object.getClass(); Field field = Reflect.resolveJavaField(clas, name, false/* onlyStatic */); if (field != null) return new Variable(name, field.getType(), new LHS(object, field)); } // Try static imports if (importedStatic != null) for (int i = 0; i < importedStatic.size(); i++) { Class clas = (Class) importedStatic.elementAt(i); Field field = Reflect.resolveJavaField(clas, name, true/* onlyStatic */); if (field != null) return new Variable(name, field.getType(), new LHS(field)); } return null; } /** * Load a command script from the input stream and find the BshMethod in the * target namespace. * * @param in * the input stream that contains the script. * @param name * the name of the command. * @param argTypes * the type of the arguments. * @param resourcePath * the resource path. * @param interpreter * the interpreter. * @throws UtilEvalError * on error in parsing the script or if the the method is not * found after parsing the script. */ /* * If we want to support multiple commands in the command path we need to * change this to not throw the exception. */ private BshMethod loadScriptedCommand(InputStream in, String name, Class[] argTypes, String resourcePath, Interpreter interpreter) throws UtilEvalError { try { interpreter.eval(new InputStreamReader(in), this, resourcePath); } catch (EvalError e) { /* * Here we catch any EvalError from the interpreter because we are * using it as a tool to load the command, not as part of the * execution path. */ Interpreter.debug(e.toString()); throw new UtilEvalError("Error loading script: " + e.getMessage()); } // Look for the loaded command BshMethod meth = getMethod(name, argTypes); /* * if ( meth == null ) throw new UtilEvalError("Loaded resource: " + * resourcePath + "had an error or did not contain the correct method" * ); */ return meth; } /** * Helper that caches class. * * @param name * name of the class in the cache. * @param c * class to be cached. */ void cacheClass(String name, Class c) { if (classCache == null) { classCache = new Hashtable(); // cacheCount++; // debug } classCache.put(name, c); } /** * Load a class through this namespace taking into account imports. The * class search will proceed through the parent namespaces if necessary. * * @param name * the name of the class. * @return {@code null} if not found. * @throws UtilEvalError * if an evaluation error occurs during evaluation. */ public Class getClass(String name) throws UtilEvalError { Class c = getClassImpl(name); if (c != null) return c; else // implement the recursion for getClassImpl() if (parent != null) return parent.getClass(name); else return null; } /** * Implementation of getClass() * * Load a class through this namespace taking into account imports. *

* * Check the cache first. If an unqualified name look for imported class or * package. Else try to load absolute name. *

* * This method implements caching of unqualified names (normally imports). * Qualified names are cached by the BshClassManager. Unqualified absolute * class names (e.g. unpackaged Foo) are cached too so that we don't go * searching through the imports for them each time. * * @param name * the name of the class. * @return {@code null} if not found. * @throws UtilEvalError * if an evaluation error occurs during evaluation. */ private Class getClassImpl(String name) throws UtilEvalError { Class c = null; // Check the cache if (classCache != null) { c = (Class) classCache.get(name); if (c != null) return c; } // Unqualified (simple, non-compound) name boolean unqualifiedName = !Name.isCompound(name); // Unqualified name check imported if (unqualifiedName) { // Try imported class if (c == null) c = getImportedClassImpl(name); // if found as imported also cache it if (c != null) { cacheClass(name, c); return c; } } // Try absolute c = classForName(name); if (c != null) { // Cache unqualified names to prevent import check again if (unqualifiedName) cacheClass(name, c); return c; } // Not found if (Interpreter.DEBUG) Interpreter.debug("getClass(): " + name + " not found in " + this); return null; } /** * Try to make the name into an imported class. This method takes into * account only imports (class or package) found directly in this NameSpace * (no parent chain). * * @param name * name of the class to import. * @return the imported class. * @throws UtilEvalError * if an evaluation error occurs during evaluation. */ private Class getImportedClassImpl(String name) throws UtilEvalError { // Try explicitly imported class, e.g. import foo.Bar; String fullname = null; if (importedClasses != null) fullname = (String) importedClasses.get(name); // not sure if we should really recurse here for explicitly // imported // class in parent... if (fullname != null) { /* * Found the full name in imported classes. */ // Try to make the full imported name Class clas = classForName(fullname); // Handle imported inner class case if (clas == null) { // Imported full name wasn't found as an // absolute class // If it is compound, try to resolve to an inner // class. // (maybe this should happen in the // BshClassManager?) if (Name.isCompound(fullname)) try { clas = getNameResolver(fullname).toClass(); } catch (ClassNotFoundException e) { /* * not a class */ } else if (Interpreter.DEBUG) Interpreter.debug("imported unpackaged name not found:" + fullname); // If found cache the full name in the // BshClassManager if (clas != null) { // (should we cache info in not a class // case too?) getClassManager().cacheClassInfo(fullname, clas); return clas; } } else return clas; // It was explicitly imported, but we don't know what it // is. // should we throw an error here?? return null; } /* * Try imported packages, e.g. "import foo.bar.*;" in reverse order of * import... (give later imports precedence...) */ if (importedPackages != null) for (int i = importedPackages.size() - 1; i >= 0; i--) { String s = ((String) importedPackages.elementAt(i)) + "." + name; Class c = classForName(s); if (c != null) return c; } BshClassManager bcm = getClassManager(); /* * Try super import if available Note: we do this last to allow * explicitly imported classes and packages to take priority. This * method will also throw an error indicating ambiguity if it exists... */ if (bcm.hasSuperImport()) { String s = bcm.getClassNameByUnqName(name); if (s != null) return classForName(s); } return null; } private Class classForName(String name) { return getClassManager().classForName(name); } /** * Implements NameSource * * @return all variable and method names in this and all parent namespaces */ public String[] getAllNames() { Vector vec = new Vector(); getAllNamesAux(vec); String[] names = new String[vec.size()]; vec.copyInto(names); return names; } /** * Helper for implementing NameSource * * @param vec * a vector of auxiliary names. */ protected void getAllNamesAux(Vector vec) { Enumeration varNames = variables.keys(); while (varNames.hasMoreElements()) vec.addElement(varNames.nextElement()); Enumeration methodNames = methods.keys(); while (methodNames.hasMoreElements()) vec.addElement(methodNames.nextElement()); if (parent != null) parent.getAllNamesAux(vec); } Vector nameSourceListeners; /** * Implements NameSource Add a listener who is notified upon changes to * names in this space. * * @param listener * the listener to be added. */ public void addNameSourceListener(NameSource.Listener listener) { if (nameSourceListeners == null) nameSourceListeners = new Vector(); nameSourceListeners.addElement(listener); } /** * Perform "import *;" causing the entire classpath to be mapped. This can * take a while. * * @throws UtilEvalError * if an evaluation error occurs during evaluation. */ public void doSuperImport() throws UtilEvalError { getClassManager().doSuperImport(); } public String toString() { return "NameSpace: " + (nsName == null ? super.toString() : nsName + " (" + super.toString() + ")") + (isClass ? " (isClass) " : "") + (isMethod ? " (method) " : "") + (classStatic != null ? " (class static) " : "") + (classInstance != null ? " (class instance) " : ""); } /* * For serialization. Don't serialize non-serializable objects. */ private synchronized void writeObject(java.io.ObjectOutputStream s) throws IOException { // clear name resolvers... don't know if this is necessary. names = null; s.defaultWriteObject(); } /** * Invoke a method in this namespace with the specified args and interpreter * reference. No caller information or call stack is required. The method * will appear as if called externally from Java. * * @param methodName * the name of the method to invoke. * @param args * the arguments of the method. * @param interpreter * the interpreter. * @return the result of the invocation. * @throws EvalError * if an evaluation error occurs during evaluation. * @see bsh.This#invokeMethod(String, Object[], Interpreter, CallStack, * SimpleNode, boolean) */ public Object invokeMethod(String methodName, Object[] args, Interpreter interpreter) throws EvalError { return invokeMethod(methodName, args, interpreter, null, null); } /** * This method simply delegates to This.invokeMethod(); * * @param methodName * the name of the method to invoke. * @param args * the arguments of the method. * @param interpreter * the interpreter. * @param callstack * the call stack related. If callStack is null a new CallStack * will be created and initialized with this namespace. * @param callerInfo * information of the caller. * @return the invocation result. * @throws EvalError * if an error occurs during evaluation. * @see bsh.This#invokeMethod(String, Object[], Interpreter, CallStack, * SimpleNode, boolean) */ public Object invokeMethod(String methodName, Object[] args, Interpreter interpreter, CallStack callstack, SimpleNode callerInfo) throws EvalError { return getThis(interpreter).invokeMethod(methodName, args, interpreter, callstack, callerInfo, false/* declaredOnly */); } /** * Clear all cached classes and names. */ public void classLoaderChanged() { nameSpaceChanged(); } /** * Clear all cached classes and names. */ public void nameSpaceChanged() { classCache = null; names = null; } /** * Import standard packages. Currently: * *

	 * importClass("bsh.EvalError");
	 * importClass("bsh.Interpreter");
	 * importPackage("javax.swing.event");
	 * importPackage("javax.swing");
	 * importPackage("java.awt.event");
	 * importPackage("java.awt");
	 * importPackage("java.net");
	 * importPackage("java.util");
	 * importPackage("java.io");
	 * importPackage("java.lang");
	 * importCommands("/bsh/commands");
	 * 
*/ public void loadDefaultImports() { /** * Note: the resolver looks through these in reverse order, per * precedence rules... so for max efficiency put the most common ones * later. */ importClass("bsh.EvalError"); importClass("bsh.Interpreter"); importPackage("javax.swing.event"); importPackage("javax.swing"); importPackage("java.awt.event"); importPackage("java.awt"); importPackage("java.net"); importPackage("java.util"); importPackage("java.io"); importPackage("java.lang"); importCommands("/bsh/commands"); } /** *

* This is the factory for Name objects which resolve names within this * namespace (e.g. toObject(), toClass(), toLHS()). *

* *

* This was intended to support name resolver caching, allowing Name objects * to cache info about the resolution of names for performance reasons. * However this not proven useful yet. *

* *

* We'll leave the caching as it will at least minimize Name object * creation. *

* *

* (This method would be called getName() if it weren't already used for the * simple name of the NameSpace) *

* *

* This method was public for a time, which was a mistake. Use get() * instead. *

* * @param ambigname * an ambiguous name. * @return an unambiguous name. */ Name getNameResolver(String ambigname) { if (names == null) names = new Hashtable(); Name name = (Name) names.get(ambigname); if (name == null) { name = new Name(this, ambigname); names.put(ambigname, name); } return name; } public int getInvocationLine() { SimpleNode node = getNode(); if (node != null) return node.getLineNumber(); else return -1; } public String getInvocationText() { SimpleNode node = getNode(); if (node != null) return node.getText(); else return ""; } /** * This is a helper method for working inside of bsh scripts and commands. * In that context it is impossible to see a ClassIdentifier object for what * it is. Attempting to access a method on a ClassIdentifier will look like * a static method invocation. * * This method is in NameSpace for convenience (you don't have to import * bsh.ClassIdentifier to use it ); * * @param ci * the class identifier. * @return the corresponding class. */ public static Class identifierToClass(ClassIdentifier ci) { return ci.getTargetClass(); } /** * Clear all variables, methods, and imports from this namespace. If this * namespace is the root, it will be reset to the default imports. * * @see #loadDefaultImports() */ public void clear() { variables = null; methods = null; importedClasses = null; importedPackages = null; importedCommands = null; importedObjects = null; if (parent == null) loadDefaultImports(); classCache = null; names = null; } /** *

* Import a compiled Java object's methods and variables into this * namespace. When no scripted method / command or variable is found locally * in this namespace method / fields of the object will be checked. Objects * are checked in the order of import with later imports taking precedence. *

* * @param obj * the object to import. */ /* * Note: this impor pattern is becoming common... could factor it out into * an importedObject Vector class. */ public void importObject(Object obj) { if (importedObjects == null) importedObjects = new Vector(); // If it exists, remove it and add it at the end (avoid memory // leak) if (importedObjects.contains(obj)) importedObjects.remove(obj); importedObjects.addElement(obj); nameSpaceChanged(); } public void importStatic(Class clas) { if (importedStatic == null) importedStatic = new Vector(); // If it exists, remove it and add it at the end (avoid memory // leak) if (importedStatic.contains(clas)) importedStatic.remove(clas); importedStatic.addElement(clas); nameSpaceChanged(); } /** * Set the package name for classes defined in this namespace. Subsequent * sets override the package. * * @param packageName * the name of the package. */ void setPackage(String packageName) { this.packageName = packageName; } String getPackage() { if (packageName != null) return packageName; if (parent != null) return parent.getPackage(); return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy