bsh.NameSpace Maven / Gradle / Ivy
/*
* #%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;
}
}