/*
* JavaInvoke.java --
*
* This class implements the common routines used by the java::*
* commands to access the Java Reflection API.
*
* Copyright (c) 1997 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and
* redistribution of this file, and for a DISCLAIMER OF ALL
* WARRANTIES.
*
* RCS: @(#) $Id: JavaInvoke.java,v 1.25 2006/06/13 06:52:47 mdejong Exp $
*
*/
package tcl.lang;
import tcl.lang.reflect.*;
import java.lang.reflect.*;
import java.util.*;
import java.beans.*;
/**
* This class implements the common routines used by the java::*
* commands to create Java objects, call Java methods and access fields
* and properties. It also has auxiliary routines for converting between
* TclObject's and Java Object's.
*/
class JavaInvoke {
// We need to use empty array Object[0] a lot. We keep a static copy
// and re-use it to avoid garbage collection.
static private Object EMPTY_ARGS[] = new Object[0];
/*
*-----------------------------------------------------------------------------
*
* newInstance --
*
* Call the specified constructor.
*
* Results:
* When successful, the object created by the constructor.
*
* Side effects:
* The constructor can cause arbitrary side effects.
*
*-----------------------------------------------------------------------------
*/
static TclObject
newInstance(
Interp interp, // Current interpreter.
TclObject signature, // Constructor signature.
TclObject[] argv, // Arguments.
int startIdx, // Index of the first argument in argv to
// pass to the constructor.
int count) // Number of arguments to pass to the
// constructor.
throws
TclException // Standard Tcl exception.
{
// Some built-in types have wrapper classes that behave in
// unexpected ways. For example, the Boolean constructor
// is overloaded to accept either a boolean primitive or
// a String value. The FuncSig module prefers method signatures
// that accept a String, but the version that accepts a String
// does not match Tcl's number parsing semantics. Fix this problem
// by explicitly invoking the wrapper constructor that accepts
// a primitive type so that the type conversion logic in this
// module is used to pass a Tcl value to a Java primitive argument.
if (count == 1) {
final String sig = signature.toString();
if (sig.equals("Boolean") ||
sig.equals("java.lang.Boolean")) {
signature = TclString.newInstance("java.lang.Boolean boolean");
} else if (sig.equals("Integer") ||
sig.equals("java.lang.Integer")) {
signature = TclString.newInstance("java.lang.Integer int");
} else if (sig.equals("Byte") ||
sig.equals("java.lang.Byte")) {
signature = TclString.newInstance("java.lang.Byte byte");
} else if (sig.equals("Short") ||
sig.equals("java.lang.Short")) {
signature = TclString.newInstance("java.lang.Short short");
} else if (sig.equals("Character") ||
sig.equals("java.lang.Character")) {
signature = TclString.newInstance("java.lang.Character char");
} else if (sig.equals("Long") ||
sig.equals("java.lang.Long")) {
signature = TclString.newInstance("java.lang.Long long");
} else if (sig.equals("Float") ||
sig.equals("java.lang.Float")) {
signature = TclString.newInstance("java.lang.Float float");
} else if (sig.equals("Double") ||
sig.equals("java.lang.Double")) {
signature = TclString.newInstance("java.lang.Double double");
}
}
FuncSig sig = FuncSig.get(interp, null, signature, argv, startIdx,
count, false);
Object javaObj = call(interp, sig.pkgInvoker, signature, sig.func,
null, argv, startIdx, count);
return ReflectObject.newInstance(interp, sig.targetCls, javaObj);
}
/*
*-----------------------------------------------------------------------------
*
* callMethod --
*
* Call the specified instance or static method of the given object.
*
* Results:
* When successful, this method returns the Java object that the
* Java method would have returned. If the Java method has a void
* return type then null is returned.
*
* Side effects:
* The method can cause arbitrary side effects.
*
*-----------------------------------------------------------------------------
*/
static TclObject
callMethod(
Interp interp, // Current interpreter.
TclObject reflectObj, // The object whose method to invoke.
TclObject signature, // Method signature.
TclObject argv[], // Arguments.
int startIdx, // Index of the first argument in argv[] to
// pass to the method.
int count, // Number of arguments to pass to the
// method.
boolean convert) // Whether the value should be converted
// into Tcl objects of the closest types.
throws
TclException
{
Object javaObj = ReflectObject.get(interp, reflectObj);
Class javaCl = ReflectObject.getClass(interp, reflectObj);
FuncSig sig = FuncSig.get(interp, javaCl, signature, argv,
startIdx, count, false);
Method method = (Method) sig.func;
Class rtype = method.getReturnType();
if (!PkgInvoker.isAccessible(rtype)) {
throw new TclException(interp, "Return type \"" +
JavaInfoCmd.getNameFromClass(rtype) +
"\" is not accessible");
}
Object result = call(interp, sig.pkgInvoker, signature, method, javaObj,
argv, startIdx, count);
if (rtype == Void.TYPE) {
return null;
} else {
return wrap(interp, rtype, result, convert);
}
}
/*
*-----------------------------------------------------------------------------
*
* callStaticMethod --
*
* Call the specified static method of the given object.
*
* Results:
* When successful, this method returns the Java object that the
* Java method would have returned. If the Java method has a void
* return type then null is returned.
*
* Side effects:
* The method can cause arbitrary side effects.
*
*-----------------------------------------------------------------------------
*/
static TclObject
callStaticMethod(
Interp interp, // Current interpreter.
TclObject classObj, // Class whose static method to invoke.
TclObject signature, // Method signature.
TclObject argv[], // Arguments.
int startIdx, // Index of the first argument in argv[] to
// pass to the method.
int count, // Number of arguments to pass to the
// method.
boolean convert) // Whether the value should be converted
// into Tcl objects of the closest types.
throws
TclException
{
Class cls = ClassRep.get(interp, classObj);
FuncSig sig = FuncSig.get(interp, cls, signature, argv,
startIdx, count, true);
Method method = (Method) sig.func;
Class rtype = method.getReturnType();
if (!PkgInvoker.isAccessible(rtype)) {
throw new TclException(interp, "Return type \"" +
JavaInfoCmd.getNameFromClass(rtype) +
"\" is not accessible");
}
Object result = call(interp, sig.pkgInvoker, signature, method, null,
argv, startIdx, count);
if (rtype == Void.TYPE) {
return null;
} else {
return wrap(interp, method.getReturnType(), result, convert);
}
}
/*
*-----------------------------------------------------------------------------
*
* call --
*
* Call the constructor, instance method, or static method with
* the given parameters. Check the parameter types and perform
* TclObject to JavaObject conversion.
*
* Results:
* The object created by the constructor, or the return value
* of the method call.
*
* Side effects:
* The constructor/method call may have arbitrary side effects.
*
*-----------------------------------------------------------------------------
*/
static Object
call(
Interp interp,
PkgInvoker invoker, // The PkgInvoked used to invoke the
// method or constructor.
TclObject signature, // For formatting error message.
Object func, // The Constructor or Method to call.
Object obj, // The object associated with an instace
// method call. Should be null for
// constructor calls and static method
// calls.
TclObject argv[], // Argument list.
int startIdx, // Index of the first argument in argv[] to
// pass to the method or constructor.
int count) // Number of arguments to pass to the
// method or constructor.
throws
TclException // Standard Tcl exception.
{
Class paramTypes[];
Constructor cons = null;
Method method = null;
int i;
boolean isConstructor = (func instanceof Constructor);
if (isConstructor) {
cons = (Constructor) func;
paramTypes = cons.getParameterTypes();
} else {
method = (Method) func;
paramTypes = method.getParameterTypes();
}
if (count != paramTypes.length) {
throw new TclException(interp, "wrong # args for calling " +
(isConstructor ? "constructor" : "method") +
" \"" + signature + "\"");
}
Object args[];
if (count == 0) {
args = EMPTY_ARGS;
} else {
args = new Object[count];
for (i = 0; i < count; i++) {
args[i] = convertTclObject(interp, paramTypes[i],
argv[i + startIdx]);
}
}
try {
final boolean debug = false;
Object result;
if (isConstructor) {
result = invoker.invokeConstructor(cons, args);
} else {
result = invoker.invokeMethod(method, obj, args);
}
if (debug) {
System.out.println("result object from invocation is \""
+ result + "\"");
}
return result;
} catch (InstantiationException e) {
throw new TclRuntimeError("unexpected abstract class: " +
e.getMessage());
} catch (IllegalAccessException e) {
throw new TclRuntimeError("unexpected inaccessible ctor or method: " +
e.getMessage());
} catch (IllegalArgumentException e) {
throw new TclRuntimeError("unexpected IllegalArgumentException: " +
e.getMessage());
} catch (InvocationTargetException e) {
Throwable te = e.getTargetException();
if (te instanceof TclException) {
interp.setResult(te.getMessage());
throw (TclException) te;
} else {
throw new ReflectException(interp, te);
}
}
}
/*
*-----------------------------------------------------------------------------
*
* getField --
*
* Returns the value of a field in the given object.
*
* Results:
* When successful, returns an array: Object result[2]. result[0]
* is the value of the field; result[1] is the type of the field.
*
* Side effects:
* None.
*
*-----------------------------------------------------------------------------
*/
static final TclObject
getField(
Interp interp, // Current interpreter.
TclObject classOrObj, // Class or object whose field to get.
TclObject signature, // Signature of the field.
boolean convert) // Whether the value should be converted
// into Tcl objects of the closest types.
throws
TclException // Standard Tcl exception.
{
return getsetField(interp, classOrObj, signature, null, convert, true);
}
/*
*-----------------------------------------------------------------------------
*
* setField --
*
* Sets the value of a field in the given object.
*
* Results:
* None.
*
* Side effects:
* When successful, the field is set to the given value.
*
*-----------------------------------------------------------------------------
*/
static final void
setField(
Interp interp, // Current interpreter.
TclObject classOrObj, // Class or object whose field to get.
TclObject signature, // Signature of the field.
TclObject value) // New value for the field.
throws
TclException // Standard Tcl exception.
{
getsetField(interp, classOrObj, signature, value, false, false);
}
/*
*-----------------------------------------------------------------------------
*
* getsetField --
*
* Gets or sets the field in the given object.
*
* Results:
* None.
*
* Side effects:
* When successful, the field is set to the given value if isget
* is false.
*
*-----------------------------------------------------------------------------
*/
static TclObject
getsetField(
Interp interp, // Current interpreter.
TclObject classOrObj, // Class or object whose field to get.
TclObject signature, // Signature of the field.
TclObject value, // New value for the field.
boolean convert, // Whether the value should be converted
// into Tcl objects of the closest types.
boolean isget)
throws
TclException // Standard Tcl exception.
{
Class cls = null;
Object obj = null;
boolean isStatic = false;
try {
obj = ReflectObject.get(interp, classOrObj);
} catch (TclException e) {
try {
cls = ClassRep.get(interp, classOrObj);
} catch (TclException e1) {
throw new TclException(interp, "unknown class or object \"" +
classOrObj + "\"");
}
isStatic = true;
if (!PkgInvoker.isAccessible(cls)) {
JavaInvoke.notAccessibleError(interp, cls);
}
}
if (!isStatic) {
if (obj == null) {
throw new TclException(interp,
"can't access fields in a null object reference");
}
cls = ReflectObject.getClass(interp, classOrObj);
}
// Check for the special case where the field is named "class"
// which has a special meaning and is enforced by the javac compiler.
// If found, return the java.lang.Class object for the named class.
if (isStatic && isget && signature.toString().equals("class")) {
return wrap(interp, Class.class, cls, false);
}
FieldSig sig = FieldSig.get(interp, signature, cls);
Field field = sig.field;
if (isStatic && (!(Modifier.isStatic(field.getModifiers())))) {
throw new TclException(interp,
"can't access an instance field without an object");
}
Class ftype = field.getType();
if (!PkgInvoker.isAccessible(field.getType())) {
throw new TclException(interp, "Field type \"" +
JavaInfoCmd.getNameFromClass(ftype) +
"\" is not accessible");
}
if (!isget && Modifier.isFinal(field.getModifiers())) {
throw new TclException(interp, "can't set final field \"" +
signature + "\"");
}
try {
if (isget) {
return wrap(interp, ftype,
sig.pkgInvoker.getField(field, obj), convert);
} else {
Object javaValue = convertTclObject(interp, ftype,
value);
sig.pkgInvoker.setField(field, obj, javaValue);
return null;
}
} catch (IllegalArgumentException e) {
throw new TclRuntimeError("unexpected IllegalArgumentException: " +
e.getMessage());
} catch (IllegalAccessException e) {
throw new TclRuntimeError("unexpected IllegalAccessException: " +
e.getMessage());
}
}
/*
*-----------------------------------------------------------------------------
*
* getProperty --
*
* Returns the value of a property in the given object.
*
* Results:
* When successful, returns a the value of the property inside
* a TclObject
*
* Side effects:
* None.
*
*-----------------------------------------------------------------------------
*/
static TclObject
getProperty(
Interp interp, // Current interpreter.
TclObject reflectObj, // The object whose property to query.
TclObject propName, // The name of the property to query.
boolean convert) // Whether the value should be converted
// into Tcl objects of the closest types.
throws
TclException // A standard Tcl exception.
{
Object javaObj = ReflectObject.get(interp, reflectObj);
if (javaObj == null) {
throw new TclException(interp, "can't get property from null object");
}
Class javaClass = ReflectObject.getClass(interp, reflectObj);
PropertySig sig = PropertySig.get(interp, javaClass, propName);
Method readMethod = sig.desc.getReadMethod();
if (readMethod == null) {
throw new TclException(interp, "can't get write-only property \"" +
propName + "\"");
}
try {
return wrap(interp, readMethod.getReturnType(),
sig.pkgInvoker.invokeMethod(readMethod, javaObj,
EMPTY_ARGS), convert);
} catch (IllegalAccessException e) {
throw new TclRuntimeError("unexpected inaccessible readMethod: " +
e.getMessage());
} catch (IllegalArgumentException e) {
throw new TclRuntimeError("unexpected IllegalArgumentException: " +
e.getMessage());
} catch (InvocationTargetException e) {
throw new ReflectException(interp, e);
}
}
/*
*-----------------------------------------------------------------------------
*
* setProperty --
*
* Returns the value of a property in the given object.
*
* Results:
* None.
*
* Side effects:
* When successful, the property will have the new value.
*
*-----------------------------------------------------------------------------
*/
static void
setProperty(
Interp interp, // Current interpreter.
TclObject reflectObj, // The object whose property to query.
TclObject propName, // The name of the property to query.
TclObject value) // Whether the value should be converted
// into Tcl objects of the closest types.
throws
TclException // A standard Tcl exception.
{
Object javaObj = ReflectObject.get(interp, reflectObj);
if (javaObj == null) {
throw new TclException(interp, "can't set property in null object");
}
Class javaClass = ReflectObject.getClass(interp, reflectObj);
PropertySig sig = PropertySig.get(interp,javaClass,propName);
Method writeMethod = sig.desc.getWriteMethod();
Class type = sig.desc.getPropertyType();
if (writeMethod == null) {
throw new TclException(interp, "can't set read-only property \"" +
propName + "\"");
}
Object args[] = new Object[1];
args[0] = convertTclObject(interp, type, value);
try {
sig.pkgInvoker.invokeMethod(writeMethod, javaObj, args);
} catch (IllegalAccessException e) {
throw new TclRuntimeError("unexpected inaccessible writeMethod: " +
e.getMessage());
} catch (IllegalArgumentException e) {
throw new TclRuntimeError("unexpected IllegalArgumentException: " +
e.getMessage());
} catch (InvocationTargetException e) {
throw new ReflectException(interp, e);
}
}
/*
*-----------------------------------------------------------------------------
*
* getClassByName --
*
* Returns Class object identified by the string name. We allow
* abbreviation of the java.lang.* class if there is no ambiguity:
* e.g., if there is no class whose fully qualified name is "String",
* then "String" means java.lang.String. Inner classes are supported
* both with fully qualified names and imported class names.
*
* Results:
* If successful, The Class object identified by the string name.
*
* Side effects:
* None.
*
*-----------------------------------------------------------------------------
*/
static Class
getClassByName(
Interp interp, // Interp used by TclClassLoader
String clsName) // String name of the class.
throws
TclException // If the class cannot be found or loaded.
{
Class result = null;
int dimension;
final boolean debug = false;
if (debug) {
System.out.println("JavaInvoke.getClassByName(\"" + clsName + "\")");
}
// If the string is of the form className[][]..., strip out the trailing
// []s and record the dimension of the array.
StringBuffer prefix_buf = new StringBuffer(64);
StringBuffer suffix_buf = new StringBuffer(64);
StringBuffer clsName_buf = new StringBuffer(clsName);
String lname;
int clsName_len;
for (dimension = 0; true ; dimension++) {
clsName_len = clsName_buf.length();
if ((clsName_len > 2) &&
(clsName_buf.charAt(clsName_len - 2) == '[') &&
(clsName_buf.charAt(clsName_len - 1) == ']')) {
clsName_buf.setLength(clsName_len - 2);
prefix_buf.append('[');
} else {
break;
}
}
boolean package_name_exception = false;
if (true) {
clsName = clsName_buf.toString(); // Use shortened form of name
// Search for the char '.' in the name. If '.' is in
// the name then we know it is not a builtin type.
if (clsName.indexOf('.') == -1) {
if (dimension > 0) {
boolean isPrimitive = true;
if (clsName.equals("int")) {
prefix_buf.append('I');
} else if (clsName.equals("boolean")) {
prefix_buf.append('Z');
} else if (clsName.equals("long")) {
prefix_buf.append('J');
} else if (clsName.equals("float")) {
prefix_buf.append('F');
} else if (clsName.equals("double")) {
prefix_buf.append('D');
} else if (clsName.equals("byte")) {
prefix_buf.append('B');
} else if (clsName.equals("short")) {
prefix_buf.append('S');
} else if (clsName.equals("char")) {
prefix_buf.append('C');
} else {
isPrimitive = false;
}
if (isPrimitive) {
try {
return Class.forName(prefix_buf.toString());
} catch (ClassNotFoundException e) {
throw new TclRuntimeError(
"unexpected ClassNotFoundException: " +
e.getMessage());
}
}
// Otherwise, not a primitive array type
prefix_buf.append('L');
suffix_buf.append(';');
} else {
if (clsName.equals("int")) {
return Integer.TYPE;
} else if (clsName.equals("boolean")) {
return Boolean.TYPE;
} else if (clsName.equals("long")) {
return Long.TYPE;
} else if (clsName.equals("float")) {
return Float.TYPE;
} else if (clsName.equals("double")) {
return Double.TYPE;
} else if (clsName.equals("byte")) {
return Byte.TYPE;
} else if (clsName.equals("short")) {
return Short.TYPE;
} else if (clsName.equals("char")) {
return Character.TYPE;
}
}
// Use TclClassLoader defined on a per-interp basis.
TclClassLoader tclClassLoader = (TclClassLoader) interp.getClassLoader();
try {
lname = prefix_buf + clsName + suffix_buf;
if (debug) {
System.out.println("attempting load of \"" + lname + "\"");
}
result = tclClassLoader.loadClass(lname);
} catch (ClassNotFoundException e) {
result = null;
} catch (PackageNameException e) {
// Should not be possible to catch a PackageNameException
// here since the class name above should contain no '.' chars.
throw new TclRuntimeError("unexpected PackageNameException :" +
e.getMessage());
}
if (result == null) {
// If the class loader can not find the class then check with
// the "import" feature to see if the given clsName maps to
// a fully qualified class name.
boolean inJavaLang = false;
String fullyqualified = JavaImportCmd.getImport(interp, clsName);
// If we do not find a fully qualified name in the import table
// then try to fully qualify the class with the java.lang prefix
if (fullyqualified == null) {
inJavaLang = true;
fullyqualified = "java.lang." + clsName;
}
// If the class starts with "java." and it can't be
// loaded with the system class loader, then a
// PackageNameException is raised.
try {
lname = prefix_buf + fullyqualified + suffix_buf;
if (debug) {
System.out.println("attempting load of \"" + lname + "\"");
}
result = tclClassLoader.loadClass(lname);
} catch (ClassNotFoundException e) {
result = null;
} catch (PackageNameException e) {
// If loading a class from java.lang package fails
// and we fully qualified the class name with the
// java.lang prefix, then don't emit a special
// error message related to the package name.
if (inJavaLang) {
// No-op
} else {
package_name_exception = true;
}
result = null;
}
if (debug) {
if (result == null) {
System.out.println("load failed");
} else {
System.out.println("load worked");
}
}
}
} else {
// clsName contains a '.' character. It is either a fully
// qualified toplevel class name or an inner class name.
// Note that use of a '$' to indicate an inner class is
// supported only for backwards compatibility and
// works only with a fully qualified class name.
TclClassLoader tclClassLoader = (TclClassLoader) interp.getClassLoader();
if (dimension > 0) {
prefix_buf.append("L");
suffix_buf.append(";");
}
try {
lname = prefix_buf + clsName + suffix_buf;
if (debug) {
System.out.println("attempting load of \"" + lname + "\"");
}
result = tclClassLoader.loadClass(lname);
} catch (ClassNotFoundException e) {
result = null;
} catch (PackageNameException e) {
package_name_exception = true;
result = null;
}
if (debug) {
if (result == null) {
System.out.println("load failed");
} else {
System.out.println("load worked");
}
}
if ((result == null) && (clsName.indexOf('$') == -1)) {
// Toplevel class with fully qualified name not found.
// Search for an inner class with this name. This
// search is tricky because inner classes can be
// nested inside other inner classes. Find a containing
// class that exists, then search for an inner class
// relative to the containing class. Old style inner class
// names that contain a literal '$' character are not searched.
ArrayList parts = new ArrayList(5);
int si = 0;
int clsNameLength = clsName.length();
for (int i=0; i <= clsNameLength; i++) {
if ((i == clsNameLength) || (clsName.charAt(i) == '.')) {
parts.add( clsName.substring(si, i) );
si = i + 1;
}
}
if (debug) {
System.out.println("clsName parts is " + parts);
}
// Search for a contanining class, construct inner
// class name if a contanining class was found.
String toplevel = null;
String inner = null;
boolean load_inner = false;
for (int i = parts.size() - 1; i > 0; i--) {
StringBuffer sb;
sb = new StringBuffer(64);
for (int bi=0; bi < i; bi++) {
sb.append( parts.get(bi) );
sb.append( '.' );
}
if ((sb.length() > 0) && (sb.charAt(sb.length() - 1) == '.')) {
sb.setLength(sb.length() - 1);
}
toplevel = sb.toString();
sb = new StringBuffer(64);
for (int ai=i; ai < parts.size(); ai++) {
sb.append( parts.get(ai) );
sb.append( '$' );
}
if ((sb.length() > 0) && (sb.charAt(sb.length() - 1) == '$')) {
sb.setLength(sb.length() - 1);
}
inner = sb.toString();
if (debug) {
System.out.println("loop " + i + ":");
System.out.println("toplevel is " + toplevel);
System.out.println("inner is " + inner);
}
try {
lname = prefix_buf + toplevel + suffix_buf;
if (debug) {
System.out.println("attempting load of \"" + lname + "\"");
}
result = tclClassLoader.loadClass(lname);
} catch (ClassNotFoundException e) {
// Not an enclosing toplevel class, raise TclException
result = null;
} catch (PackageNameException e) {
package_name_exception = true;
result = null;
}
if (debug) {
if (result == null) {
System.out.println("load failed");
} else {
System.out.println("load worked");
}
}
if (result != null) {
// Containing class was loaded, break out of
// this loop and load the inner class by name.
load_inner = true;
break;
} else if ((toplevel.indexOf('.') == -1)) {
// The toplevel class was not loaded, it could
// be an imported class name. Check the import
// table for this class name. Don't bother
// loading an imported name since the class
// had to exist to be imported in the first place.
if (debug) {
System.out.println("checking import table for \"" + toplevel + "\"");
}
String fullyqualified = JavaImportCmd.getImport(interp, toplevel);
if (debug) {
if (fullyqualified == null) {
System.out.println("was not imported");
} else {
System.out.println("was imported as \"" + fullyqualified + "\"");
}
}
if (fullyqualified != null) {
load_inner = true;
toplevel = fullyqualified;
break;
} else {
// Not an imported toplevel class. Check to
// see if the class is in the java.lang package.
fullyqualified = "java.lang." + toplevel;
try {
lname = prefix_buf + fullyqualified + suffix_buf;
if (debug) {
System.out.println("attempting load of \"" + lname + "\"");
}
result = tclClassLoader.loadClass(lname);
} catch (ClassNotFoundException e) {
result = null;
} catch (PackageNameException e) {
result = null;
}
if (debug) {
if (result == null) {
System.out.println("load failed");
} else {
System.out.println("load worked");
}
}
if (result != null) {
load_inner = true;
toplevel = fullyqualified;
break;
}
}
}
}
if (load_inner) {
// If enclosing class exists, attempt to load inner class.
try {
lname = prefix_buf + toplevel + "$" + inner + suffix_buf;
if (debug) {
System.out.println("attempting load of \"" + lname + "\"");
}
result = tclClassLoader.loadClass(lname);
} catch (ClassNotFoundException e) {
// Not an inner class, raise TclException
result = null;
} catch (PackageNameException e) {
package_name_exception = true;
result = null;
}
if (debug) {
if (result == null) {
System.out.println("load failed");
} else {
System.out.println("load worked");
}
}
} // end if (load_inner)
}
}
} // end if (true) block
if ((result == null) && package_name_exception) {
if (debug) {
System.out.println("throwing TclException because of PackageNameException");
}
throw new TclException(interp,
"cannot load new class into java or tcl package");
}
if (result == null) {
if (debug) {
System.out.println("throwing unknown class TclException");
}
throw new TclException(interp, "unknown class \"" + clsName_buf + "\"");
}
return result;
}
/*
*----------------------------------------------------------------------
*
* convertJavaObject --
*
* Converts the java.lang.Object into a Tcl object and return
* TclObject that holds the reult. Primitive data types
* are converted into primitive Tcl data types. Otherwise,
* a ReflectObject wrapper is created for the object so that it
* can be later accessed with the Reflection API.
*
* Results:
* The TclObject representation of the Java object.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static TclObject
convertJavaObject(
Interp interp, // Current interpreter.
Class cls, // The class of the Java Object
Object javaObj) // The java.lang.Object to convert to a TclObject.
throws TclException
{
if (javaObj == null) {
if (cls == String.class) {
return TclString.newInstance("");
} else {
return ReflectObject.newInstance(interp, cls, javaObj);
}
} else if ((cls == Integer.TYPE) || (cls == Integer.class)) {
return TclInteger.newInstance(((Integer) javaObj).intValue());
} else if ((cls == Long.TYPE) || (cls == Long.class)) {
// A long can not be represented as a TclInteger
return TclString.newInstance(javaObj.toString());
} else if ((cls == Short.TYPE) || (cls == Short.class)) {
return TclInteger.newInstance(((Short) javaObj).intValue());
} else if ((cls == Byte.TYPE) || (cls == Byte.class)) {
return TclInteger.newInstance(((Byte) javaObj).intValue());
} else if ((cls == Double.TYPE) || (cls == Double.class)) {
return TclDouble.newInstance(((Double) javaObj).doubleValue());
} else if ((cls == Float.TYPE) || (cls == Float.class)) {
return TclDouble.newInstance(((Float) javaObj).doubleValue());
} else if ((cls == Boolean.TYPE) || (cls == Boolean.class)) {
return TclBoolean.newInstance(((Boolean) javaObj).booleanValue());
} else if ((cls == Character.TYPE) || (cls == Character.class)) {
return TclString.newInstance(((Character) javaObj).toString());
} else if (cls == String.class) {
return TclString.newInstance((String)javaObj);
} else {
return ReflectObject.newInstance(interp, cls, javaObj);
}
}
/*
*-----------------------------------------------------------------------------
*
* convertTclObject --
*
* Converts a Tcl object to a Java Object of the required type.
*
* Results:
* An Object of the required type.
*
* Side effects:
* None.
*
*-----------------------------------------------------------------------------
*/
static final Object
convertTclObject(
Interp interp, // Current interpreter.
Class type, // Convert to this type.
TclObject tclObj) // From this Tcl object.
throws
TclException // If conversion fails.
{
Object javaObj = null;
Class javaClass = null;
boolean isReflectObj = false;
try {
javaObj = ReflectObject.get(interp, tclObj);
javaClass = ReflectObject.getClass(interp, tclObj);
isReflectObj = true;
} catch (TclException e) {
interp.resetResult();
}
if (! isReflectObj) {
// tclObj a Tcl "primitive" value. We try convert it to the
// corresponding primitive value in Java.
//
// To optimize performance, the following "if" statements are
// arranged according to (my guesstimation of) the frequency
// that a certain type is used.
if (type == String.class) {
return tclObj.toString();
} else if (type == Object.class) {
return tclObj.toString();
} else if ((type == Integer.TYPE) || (type == Integer.class)) {
// If an object is already a TclInteger type, then pass
// the existing value directly. Otherwise, parse the
// number as a Java int and see if it can be represented
// as a Java int. This logic will raise an exception
// when a number can't be represented as a 32bit signed int.
// Tcl's weird number parsing rules will wrap the integer
// around and calling code can't detect an overflow.
int jint = parseJavaInt(interp, tclObj);
return new Integer(jint);
} else if ((type == Boolean.TYPE) || (type == Boolean.class)) {
return new Boolean(TclBoolean.get(interp, tclObj));
} else if ((type == Long.TYPE) || (type == Long.class)) {
// If an object is already a TclInteger type, then pass
// the existing value directly. Otherwise, parse the
// number as a Java long. Raise a TclException if the
// number is not an integer or is outside the long bounds.
long jlong = parseJavaLong(interp, tclObj);
return new Long(jlong);
} else if ((type == Float.TYPE) || (type == Float.class)) {
// Tcl stores floating point numbers as doubles,
// so we just need to check to see if the value
// is outside the float bounds. Invoking a Java
// method should not automatically lose precision.
double jdouble = TclDouble.get(interp, tclObj);
float jfloat = (float) jdouble;
if ((jdouble == Double.NaN) ||
(jdouble == Double.NEGATIVE_INFINITY) ||
(jdouble == Double.POSITIVE_INFINITY)) {
// No-op
} else if ((jdouble != 0.0) &&
((Math.abs(jdouble) > (double) Float.MAX_VALUE) ||
(Math.abs(jdouble) < (double) Float.MIN_VALUE))) {
throw new TclException(interp,
"double value too large to represent in a float");
}
return new Float(jfloat);
} else if ((type == Double.TYPE) || (type == Double.class)) {
return new Double(TclDouble.get(interp, tclObj));
} else if ((type == Byte.TYPE) || (type == Byte.class)) {
// Parse a Java int, then check valid byte range.
int jint = parseJavaInt(interp, tclObj);
if ((jint < Byte.MIN_VALUE) || (jint > Byte.MAX_VALUE)) {
throw new TclException(interp,
"integer value too large to represent in a byte");
}
return new Byte((byte) jint);
} else if ((type == Short.TYPE) || (type == Short.class)) {
// Parse a Java int, then check valid byte range.
int jint = parseJavaInt(interp, tclObj);
if ((jint < Short.MIN_VALUE) || (jint > Short.MAX_VALUE)) {
throw new TclException(interp,
"integer value too large to represent in a short");
}
return new Short((short) jint);
} else if ((type == Character.TYPE) || (type == Character.class)) {
String str = tclObj.toString();
if (str.length() != 1) {
throw new TclException(interp, "expected character but got \""
+ tclObj + "\"");
}
return new Character(str.charAt(0));
} else if (type == TclObject.class) {
// Pass a non ReflectObject TclObject directly to a Java method.
return tclObj;
} else {
throw new TclException(interp, "\"" + tclObj +
"\" is not an object handle of class \"" +
JavaInfoCmd.getNameFromClass(type) +
"\"");
}
} else {
// The TclObject is a ReflectObject that contains javaObj. We
// check to see if javaObj can be converted to the required
// type. If javaObj is a wrapper for a primitive type then
// we check to see if the object is an instanceof the type.
if (isAssignable(type, javaClass)) {
return javaObj;
}
if (type.isPrimitive()) {
if (type == Boolean.TYPE) {
if (javaObj instanceof Boolean) {
return javaObj;
}
}
else if (type == Character.TYPE) {
if (javaObj instanceof Character) {
return javaObj;
}
}
else if (type == Byte.TYPE) {
if (javaObj instanceof Byte) {
return javaObj;
}
}
else if (type == Short.TYPE) {
if (javaObj instanceof Short) {
return javaObj;
}
}
else if (type == Integer.TYPE) {
if (javaObj instanceof Integer) {
return javaObj;
}
}
else if (type == Long.TYPE) {
if (javaObj instanceof Long) {
return javaObj;
}
}
else if (type == Float.TYPE) {
if (javaObj instanceof Float) {
return javaObj;
}
}
else if (type == Double.TYPE) {
if (javaObj instanceof Double) {
return javaObj;
}
}
else if (type == Void.TYPE) {
// void is not a valid type for conversions
}
}
// Pass TclObject that contains the ReflectObject directly.
if (type == TclObject.class) {
return tclObj;
}
throw new TclException(interp, "expected object of type " +
JavaInfoCmd.getNameFromClass(type) +
" but got \"" + tclObj + "\" (" +
((javaClass == null) ? "null" :
JavaInfoCmd.getNameFromClass(
javaClass )) +
")");
}
}
/*
*-----------------------------------------------------------------------------
*
* wrap --
*
* Wraps a Java Object into a TclObject according to whether the
* convert flag is set.
*
* Results:
* The TclObject that wraps the Java Object.
*
* Side effects:
* None.
*
*-----------------------------------------------------------------------------
*/
private static final TclObject
wrap(
Interp interp, // Current interpreter.
Class cls, // The class of the Java Object
Object javaObj, // The Java Object to wrap.
boolean convert) // Whether the value should be converted
// into Tcl objects of the closest types.
throws TclException
{
if (convert) {
return convertJavaObject(interp, cls, javaObj);
} else {
return ReflectObject.newInstance(interp, cls, javaObj);
}
}
/*
*-----------------------------------------------------------------------------
*
* isAssignable --
*
* Return true if the argument object can be assigned to
* convert flag is set.
*
* Results:
* The TclObject that wraps the Java Object.
*
* Side effects:
* None.
*
*-----------------------------------------------------------------------------
*/
static final boolean
isAssignable(
Class to_cls, // The class we want to assign to
Class from_cls) // The class we want to assign from (can be null)
{
// A primitive type can not be assigned the null value, but it
// can be assigned to any type derived from Object.
if (from_cls == null) {
if (to_cls.isPrimitive()) {
return false;
} else {
return true;
}
} else {
if ((to_cls == from_cls) || to_cls.isAssignableFrom(from_cls)) {
return true;
} else {
return false;
}
}
}
/*
*-----------------------------------------------------------------------------
*
* notAccessibleError --
*
* Raise a specific TclException when a class that is not accessible
* is found.
*
* Results:
* None.
*
* Side effects:
* None.
*
*-----------------------------------------------------------------------------
*/
static void
notAccessibleError(Interp interp, Class cls)
throws TclException
{
throw new TclException(interp,
"Class \"" +
JavaInfoCmd.getNameFromClass(cls) +
"\" is not accessible");
}
/*
*-----------------------------------------------------------------------------
*
* isInnerClass --
*
* Return true is a class is either an inner class or an inner interface.
* This is true only for classes defined inside other classes.
*
* Results:
* None.
*
* Side effects:
* None.
*
*-----------------------------------------------------------------------------
*/
static boolean
isInnerClass(Class cls)
throws TclException
{
String cname = cls.getName();
if (cname.indexOf('$') == -1) {
return false;
} else {
return true;
}
}
/*
*-----------------------------------------------------------------------------
*
* parseJavaInt --
*
* Parse a Java int type from a TclObject. Unlike the rest of Tcl,
* this method will raise an error when an integer value is not
* in the range Integer.MIN_VALUE to Integer.MAX_VALUE. This is
* the range of a 32bit signed number as defined by Java. Tcl parses
* integers as 32bit unsigned numbers and wraps values outside the
* valid range. This method will catch the case of an integer outside
* of the valid range and raise a TclException so that a bogus value
* is not passed to Java.
*
* Results:
* Returns an int value or raises a TclException to indicate that
* the number can't be parsed as a Java int.
*
* Side effects:
* None.
*
*-----------------------------------------------------------------------------
*/
static int
parseJavaInt(
Interp interp,
TclObject obj)
throws TclException
{
// No point in reparsing a "pure" integer.
if (obj.hasNoStringRep() && obj.isIntType()) {
return TclInteger.get(interp, obj);
}
String srep = obj.toString();
String s = srep;
int len = s.length();
char c;
int startInd, endInd;
boolean isNegative = false;
// Trim whitespace off front of string
int i = 0;
while (i < len && (((c = s.charAt(i)) == ' ') ||
Character.isWhitespace(c))) {
i++;
}
if (i >= len) {
throw new TclException(interp, "expected integer but got \"" + s + "\"");
}
startInd = i;
// Trim whitespace off end of string
endInd = len - 1;
while (endInd > startInd && (((c = s.charAt(endInd)) == ' ') ||
Character.isWhitespace(c))) {
endInd--;
}
// Check for optional '-' sign, needed for hex and octal parse.
c = s.charAt(i);
if (c == '-') {
isNegative = true;
i++;
}
if (i >= (endInd + 1)) {
throw new TclException(interp, "expected integer but got \"" + s + "\"");
}
// Check for hex or octal string prefix characters
int radix = Character.MIN_RADIX - 1; // An invalid value
c = s.charAt(i);
if (c == '0' && len > 1) {
// Either hex or octal
i++;
c = s.charAt(i);
if (len > 2 && (c == 'x' || c == 'X')) {
// Parse as hex
radix = 16;
i++;
} else {
// Parse as octal
radix = 8;
}
// Create string that contains a leading negative sign followed
// by the radix letters, leaving out the radix prefix.
// For example, "-0xFF" is parsed as "-FF".
if (isNegative) {
s = "-" + s.substring(i, endInd + 1);
} else {
s = s.substring(i, endInd + 1);
}
} else {
// Parse as decimal integer
if ((startInd > 0) || (endInd < (len - 1))) {
s = s.substring(startInd, endInd + 1);
}
radix = 10;
}
if (s.length() == 0) {
throw new TclException(interp,
"expected integer but got \"" + srep + "\"");
}
int ival;
try {
ival = Integer.parseInt(s, radix);
} catch (NumberFormatException nfe) {
// If one of the letters is not a valid radix character, then
// the number is not a valid. Otherwise, the number must be
// an integer value that is outside the valid range.
for (i=0; i < s.length(); i++) {
c = s.charAt(i);
if (i == 0 && c == '-') {
continue; // Skip minus sign
}
if (Character.digit(c, radix) == -1) {
throw new TclException(interp,
"expected integer but got \"" + srep + "\"");
}
}
throw new TclException(interp, "integer value too large to represent in a int");
}
return ival;
}
/*
*-----------------------------------------------------------------------------
*
* parseJavaLong --
*
* Parse a Java long type from a TclObject. Tcl may not support
* 64 bit integers (Jacl does not), so this method needs to be used
* to determine if a string can be parsed into a long and if the
* result is in the range Long.MIN_VALUE to Long.MAX_VALUE.
* This method will catch the case of an long outside of
* the valid range and raise a TclException so that a bogus value
* is not passed to Java.
*
* Results:
* Returns a long value or raises a TclException to indicate that
* the number can't be parsed as a Java long.
*
* Side effects:
* None.
*
*-----------------------------------------------------------------------------
*/
static long
parseJavaLong(
Interp interp,
TclObject obj)
throws TclException
{
// No point in reparsing a "pure" integer.
if (obj.hasNoStringRep() && obj.isIntType()) {
return (long) TclInteger.get(interp, obj);
}
String srep = obj.toString();
String s = srep;
int len = s.length();
char c;
int startInd, endInd;
boolean isNegative = false;
// Trim whitespace off front of string
int i = 0;
while (i < len && (((c = s.charAt(i)) == ' ') ||
Character.isWhitespace(c))) {
i++;
}
if (i >= len) {
throw new TclException(interp, "expected integer but got \"" + s + "\"");
}
startInd = i;
// Trim whitespace off end of string
endInd = len - 1;
while (endInd > startInd && (((c = s.charAt(endInd)) == ' ') ||
Character.isWhitespace(c))) {
endInd--;
}
// Check for optional '-' sign, needed for hex and octal parse.
c = s.charAt(i);
if (c == '-') {
isNegative = true;
i++;
}
if (i >= (endInd + 1)) {
throw new TclException(interp, "expected integer but got \"" + s + "\"");
}
// Check for hex or octal string prefix characters
int radix = Character.MIN_RADIX - 1; // An invalid value
c = s.charAt(i);
if (c == '0' && len > 1) {
// Either hex or octal
i++;
c = s.charAt(i);
if (len > 2 && (c == 'x' || c == 'X')) {
// Parse as hex
radix = 16;
i++;
} else {
// Parse as octal
radix = 8;
}
// Create string that contains a leading negative sign followed
// by the radix letters, leaving out the radix prefix.
// For example, "-0xFF" is parsed as "-FF".
if (isNegative) {
s = "-" + s.substring(i, endInd + 1);
} else {
s = s.substring(i, endInd + 1);
}
} else {
// Parse as decimal integer
if ((startInd > 0) || (endInd < (len - 1))) {
s = s.substring(startInd, endInd + 1);
}
radix = 10;
}
if (s.length() == 0) {
throw new TclException(interp,
"expected integer but got \"" + srep + "\"");
}
long lval;
try {
lval = Long.parseLong(s, radix);
} catch (NumberFormatException nfe) {
// If one of the letters is not a valid radix character, then
// the number is not a valid. Otherwise, the number must be
// an integer value that is outside the valid range.
for (i=0; i < s.length(); i++) {
c = s.charAt(i);
if (i == 0 && c == '-') {
continue; // Skip minus sign
}
if (Character.digit(c, radix) == -1) {
throw new TclException(interp,
"expected integer but got \"" + srep + "\"");
}
}
throw new TclException(interp, "integer value too large to represent in a long");
}
return lval;
}
} // end JavaInvoke