The upcoming 2.6 release will include a user request for direct Java object access. The intention would be that JBasic can both create and manipulate real Java objects as part of a user’s program. Additionally, external Java objects created by a program using JBasic in embedded mode can be passed into JBasic for use or manipulation. This makes JBasic more useful for scripting in a mid-tier environment such as an application server.
This will involve several phases. This first part involves creating Java objects using the NEW() function and manipulating public fields in the object. The NEW() when called with a string argument assumes that is a class name, and creates a new instance of the class. The resulting Value has many of the characteristics of a RECORD, but the members are actually field names. Since JBasic is case insenstive, all field names are converted to uppercase, which means that aField and AField will be ambiguious - not yet sure how to handle this. Object nesting works, so A.B.C will set field C of object B in object A.
You can print a Java object, but nested objects are printed as OBJECT(class.name) rather than recursively formatted. This is because there are no rules in Java to prevent referential loops, and the recursive formatter would stack-crash.
The next steps are to allow method calls using the existing OBJDATA->METHOD() syntax, which will require argument marshalling and return type binding. Also, need to add a method to JBasic that lets an object created outside of JBasic be passed in when run in embedded mode.
I’ve also started adding signalling to the Java object handling code, so ridiculous or illegal operations are caught when attempted on a Java object value.
This is all implemented by a new Value type of ObjectValue, which is a subclass. It does not permit some operations such as using the entire object in an expression; only the members can be referenced in exprssions. This was moved to a new package (org.fernwood.jbasic.value) because eventually the Value object itself will need to be refactored into discrete types. However, this is a way off since there are cases where the object handle is retained regardless of type (referential expression objects on the stack) that will need to be re- evaluated in the context of refactored Values.
You can create Java objects at will, call many (but not all) methods, and use the method invocations as function calls to get back JBasic data.
The above example also shows fetching a keySet from a TreeMap and then calling it’s toArray() method, which demonstrates Java returning objects that can themselves be used for Java interface calls. It also shows that when an object returns an Array object of some kind, JBasic will try to turn it into an array of JBasic variables as the result.
Because access to Java objects is a security risk (the caller could create a Java File object and start manipulating the local file system directly) you must now have the JAVA privilege if you are running JBasic in multi-user mode and using the NEW() function to create a Java object. This permission check is not done for cases where a Java object is passed directly into the JBasic session from an enclosing program; it is that program’s responsibility to determine if this should be permitted in multi-user mode.
One additional note about performance; the use of Java objects should be restricted to cases where it is necessary for integration with the environment in which JBasic runs. Access to JBasic objects is done by using Java Reflection, which is not particularly fast. By way of example, use of a JBasic wrapper around the StringBuffer class to manage a mutable string is about 20 times slower than just using a JBasic string variable for the same purpose. Similarly, using a HashMap to create a table of values with key strings is about 15 times slower than using a JBasic RECORD data type (which is itself implemented with a HashMap). So don’t go wild creating Java objects for fun; only use them when they are really needed.
The most important part of Java-centric exception handling is to let Java exceptions be detected by the running JBasic program. This now triggers a JBasic error of OBJEXCEPT where the signal parameter is the text of the underlying Java exception. While I was there, I did a little work cleaning up the general error reporting; too many error conditions were reported as INVOBJOP (invalid object operation) when they should be reported as INVOBJMETH (invalid object method).
Along the way, I noticed a rather awful security hole in JBasic. The array SYS$PACKAGES contains the strings used to construct package names when searching for statement handlers to invoke for compilation or execution. This is how user-written statements and functions are looked up by the compiler. However, it was possible for the user to modify this value and potentially cause the loading of statement handlers not intended by the person running the server. The change is to make this array read-only; the only thing that can change it is a call to the addPackage() method in the session object, so it’s only available to the Java program that started the session.
Finally, I updated the user’s guide a little bit to make it clearer about how Java objects are used, and included some extra examples. I also updated the SERVER START documentation to explain the SHELL= option that lets you start a server where each connection automatically runs a JBasic program. Finally, I updated the documentation to describe the JAVA privilege that is required to allow Java object reference creation for remote users.
Java method invocation searches each available method that matches the name and compares the classes of the items in the parameter list to see if the passed objects or scalar types can be successfully cast to match the parameters of each method signature. This means that not only do subclassed objects work correctly, but JBasic now correctly differentiates between methods with the same name but different signatures.
The NEW() function accepts additional parameters that are used to formulate a constructor call to create an object of the given class. The same logic that works for method calls works for identifying valid constructors, and the constructor with matching argument lists is called.
If a method call returns an actual JBasic object, then the result is now correctly returned as the actual function result rather than a JBasic object wrapper around a JBasic Value.
The SYS$PACKAGES array is used to search for valid class paths when a partial class name is given to the NEW() function. For example, if the package list contains the strings “java.lang” and “java.util”, then an attempt to create a NEW(“Vector”) resoles to a java.util.Vector and a NEW(“StringBuffer”) resolves to “java.lang.StringBuffer”. This array is marked READONLY by default to prevent corruption or security risks, but the new SET PACKAGE command can be used to add or remove path names from the list (remote or sandbox users must have the “JAVA” privilege to use this feature).