/jEdit/tags/jedit-4-3-pre5/bsh/NameSpace.java
Java | 1578 lines | 773 code | 179 blank | 626 comment | 212 complexity | a1083c282c5f9784f7d0575143e31e1b MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
- /*****************************************************************************
- * *
- * 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 (pat@pat.net) *
- * Author of Learning Java, O'Reilly & Associates *
- * http://www.pat.net/~pat/ *
- * *
- *****************************************************************************/
- package bsh;
- import java.util.*;
- import java.io.InputStream;
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.io.IOException;
- import java.lang.reflect.Method;
- import java.lang.reflect.Field;
- /**
- 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.
- <p>
- 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.
- <p>
- 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
- {
- 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 importedObjects;
- private Vector importedStatic;
- private Vector importedCommands;
- 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
- /**
- @parent the parent namespace of this namespace. Child namespaces
- inherit all variables and methods of their parent and can (of course)
- override / shadow them.
- */
- 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.
- */
- 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.
- */
- 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.
- */
- public Object get( String name, Interpreter interpreter )
- throws UtilEvalError
- {
- CallStack callstack = new CallStack( this );
- return getNameResolver( name ).toObject( callstack, interpreter );
- }
- public void setVariable(String name, Object value) throws UtilEvalError
- {
- setVariable(name,value,false);
- }
- /**
- Set the variable through this namespace.
- This method obeys the LOCALSCOPING property to determine how variables
- are set.
- <p>
- 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
- <p>
- 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.
- */
- 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.
- <p>
- 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).
- <p>
- 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
- <p>
- 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.
- */
- 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 ) {
- // don't break jEdit core and plugins!
- //throw new InterpreterError("null variable value");
- unsetVariable(name);
- return;
- }
- // 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.
- */
- public void unsetVariable( String name )
- {
- variables.remove( name );
- nameSpaceChanged();
- }
- /**
- Get 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() );
- }
- /**
- Get 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() );
- }
- /**
- Get 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.
- @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;
- }
- /**
- Get 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;
- }
- /**
- Get 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 );
- }
- /**
- Get 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();
- */
- 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.
- <p>
- 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.
- */
- /*
- 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.
- <p>
- 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.
- @see Primitive#unwrap( Object )
- @return The variable value or Primitive.VOID if it is not defined.
- */
- public Object getVariable( String name )
- throws UtilEvalError
- {
- return getVariable( name, true );
- }
- /**
- Get the specified variable in this namespace.
- @param recurse If recurse is true then we recursively search through
- parent namespaces for the variable.
- <p>
- 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.
- @see Primitive#unwrap( Object )
- @return The variable value or Primitive.VOID if it is not defined.
- */
- 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.
- <p/>
- If this namespace is static, return only static variables.
- @return the Variable value or null if it is not defined
- */
- 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.
- @return return the variable value. A null var is mapped to
- Primitive.VOID
- */
- protected Object unwrapVariable( Variable var )
- throws UtilEvalError
- {
- return (var == null) ? Primitive.VOID : var.getValue();
- }
- /**
- @deprecated See #setTypedVariable( String, Class, Object, Modifiers )
- */
- 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.
- <p>
- 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 value If value is null, you'll get the default value for the type
- @param modifiers may be null
- */
- 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 ) );
- }
- /**
- Dissallow static vars outside of a class
- @param name is here just to allow the error message to use it
- protected void checkVariableModifiers( String name, Modifiers modifiers )
- throws UtilEvalError
- {
- if ( modifiers!=null && modifiers.hasModifier("static") )
- throw new UtilEvalError(
- "Can't declare static variable outside of class: "+name );
- }
- */
- /**
- Note: this is primarily for internal use.
- @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 );
- }
- /**
- @see #getMethod( String, Class [], boolean )
- @see #getMethod( String, Class [] )
- */
- public BshMethod getMethod( String name, Class [] sig )
- throws UtilEvalError
- {
- return getMethod( name, sig, false/*declaredOnly*/ );
- }
- /**
- Get the bsh method matching the specified signature declared in
- this name space or a parent.
- <p>
- 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.
- @see bsh.Primitive
- @return the BshMethod or null if not found
- @param declaredOnly if true then only methods declared directly in this
- namespace will be found and no inherited or imported methods will
- be visible.
- */
- 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
- */
- public void importClass(String name)
- {
- if ( importedClasses == null )
- importedClasses = new Hashtable();
- importedClasses.put( Name.suffix(name, 1), name );
- nameSpaceChanged();
- }
- /**
- subsequent imports override earlier ones
- */
- 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();
- }
- static class CommandPathEntry
- {
- String path;
- Class clas;
- CommandPathEntry(String path, Class clas)
- {
- this.path = path;
- this.clas = clas;
- }
- }
- /**
- Adds a URL to the command path.
- */
- public void addCommandPath(String path, Class clas)
- {
- if(importedCommands == null)
- importedCommands = new Vector();
- if(!path.endsWith("/"))
- path = path + "/";
- importedCommands.addElement(new CommandPathEntry(path,clas));
- }
- /**
- Remove a URLfrom the command path.
- */
- public void removeCommandPath(String path, Class clas)
- {
- if(importedCommands == null)
- return;
- for(int i = 0; i < importedCommands.size(); i++)
- {
- CommandPathEntry entry = (CommandPathEntry)importedCommands
- .elementAt(i);
- if(entry.path.equals(path) && entry.clas == clas)
- {
- importedCommands.removeElementAt(i);
- return;
- }
- }
- }
- /**
- Looks up a command.
- */
- public InputStream getCommand(String name)
- {
- if(importedCommands != null)
- {
- String extName = name + ".bsh";
- for(int i = importedCommands.size() - 1; i >= 0; i--)
- {
- CommandPathEntry entry = (CommandPathEntry)importedCommands
- .elementAt(i);
- InputStream in = entry.clas.getResourceAsStream(entry.path + extName);
- if(in != null)
- return in;
- }
- }
- if(parent == null)
- return null;
- else
- return parent.getCommand(name);
- }
- /**
- 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.
- <p/>
- 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).
- <p/>
- 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.
- <p/>
- @return a BshMethod, Class, or null if no such command is found.
- @param name is the name of the desired command method
- @param argTypes is the signature of the desired command method.
- @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();
- InputStream in = getCommand( name );
- if ( in != null )
- return loadScriptedCommand(
- in, name, argTypes, name, interpreter );
- /* // Chop leading "/" and change "/" to "."
- String className;
- if ( path.equals("/") )
- className = name;
- else
- className = path.substring(1).replace('/','.') +"."+name;
- 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.
- @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.
- */
- 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.
- @return null if not found.
- */
- 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.
- <p>
- Check the cache first. If an unqualified name look for imported
- class or package. Else try to load absolute name.
- <p>
- 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.
- @return null if not found.
- */
- 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).
- */
- 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
- */
- 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.
- */
- 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.
- */
- 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.
- <p>
- @see bsh.This.invokeMethod(
- String methodName, Object [] args, Interpreter interpreter,
- CallStack callstack, SimpleNode callerInfo, 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();
- <p>
- @see bsh.This.invokeMethod(
- String methodName, Object [] args, Interpreter interpreter,
- CallStack callstack, SimpleNode callerInfo )
- */
- 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:
- <pre>
- 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");
- addCommandPath("/bsh/commands",getClass());
- </pre>
- */
- 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");
- addCommandPath("/bsh/commands",getClass());
- }
- /**
- This is the factory for Name objects which resolve names within
- this namespace (e.g. toObject(), toClass(), toLHS()).
- <p>
- 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.
- <p>
- We'll leave the caching as it will at least minimize Name object
- creation.
- <p>
- (This method would be called getName() if it weren't already used for
- the simple name of the NameSpace)
- <p>
- This method was public for a time, which was a mistake.
- Use get() instead.
- */
- 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 "<invoked from Java code>";
- }
- /**
- 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 );
- */
- 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.
- <p/>
- */
- /*
- 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.
- */
- void setPackage( String packageName )
- {
- this.packageName = packageName;
- }
- String getPackage()
- {
- if ( packageName != null )
- return packageName;
- if ( parent != null )
- return parent.getPackage();
-
- return null;
- }
- }