NEURON + Java User reference

Java from the perspective of NEURON

This file discusses how to use java from NEURON's interpreter. A user level discussion of the converse is in "Writing java classes that communicate with NEURON".

See also:
NEURON + Java Interface programmer reference
which is the javadoc generated information from the neuron.* classes that implement the java to neuron interface. Early development notes are now mostly obsolete except for the important acknowlegement of Fred Howell's original work on the interface.

Loading java classes into NEURON

load_java("java.lang.String")
load_java("java.lang.String", "JString")
This function registers a Java class with the Hoc interpreter. In hoc the class is called java_lang_String or JString respectively. The load_java function returns 1 if the class can be found and registered, 2 if the name is already in use (hopefully because the same class was previously loaded with that name), and 0 (along with an error message, probably ClassNotFoundException).

The same java classes may be loaded more than once with different hoc names.

Java classes are looked for first in the current working directory, then the list of directories or jar files specified in the ${CLASSPATH} environment variable, then the jar files in the ${NEURONHOME}/classes directory, and finally in the directories (or jar files) dynamically added to the neuron/NrnClassLoader.

It should be noted that the java virtual machine's system class loader starts with a java.class.path variable which contains only the ${NEURONHOME}/classes/nrnclsld.jar so that only the basic java classes and the neuron.NrnClassLoader are loaded with the system loader. All other classes are loaded with the neuron.NrnClassLoader which has a dynamically configurable list of directories and jar files which are looked in for classes. This means that if a class is loaded which fails due because it needs a package not in the current classpath, that one can load that prerequisite package and then attempt to reload the dependent class.

For example

load_java("Notepad", "Notepad")
returns 0 and prints the "java.lang.ClassNotFoundException: Notepad" message. Re-executing with a third argument that specifies the path to the class gives
load_java("Notepad", "Notepad", "file:/usr/j2sdk1_3_1/demo/jfc/Notepad/Notepad.jar")
succeeds.

Java objects in NEURON

After a Java class is loaded, instances of the class (objects) are created in the same way as native NEURON objects. e.g
load_java("java.util.Vector", "JVector")
objref jv
jv = new JVector()
print jv // prints JVector[0]
If there is more than one class constructor with args, better performance is obtained using the no arg constructor (if it exists) since that avoids a dynamic lookup of the proper constructor on the basis of the specific hoc argument list used. Also, with overloaded constructors, argument types double, int, and boolean cannot be distinguished and all objects are of type object at the time the constructor selection is made. In practice this is only rarely a problem. e.g.
load_java("java.lang.String", "JString")
objref s
s = new String() // no problem
s = new String("Hello") // no problem
s = new String(s) // have to use s = new String(s.toCharArray)
/home/hines/nrn5/sparc/bin/nrniv: Expecting string argument near line 19
s = new String(s)        
                          ^
        String[2].init(String[1])

Class Methods

The java class methods that can be called from hoc are all of the public static methods and public instance methods (along with all the inherited superclass methods) that take arguments of java type: double int boolean char[] Object and return a value of any of these types or void.
Example
load_java("java.lang.Math", "JMath")
objref jm
jm = new JMath()
jm.ceil(4.3) // prints 5
If such methods are overloaded at the hoc level then the correct method name in hoc is the method name followed by the number of arguments followed by a signature string consisting of the letters 'd', 'i', 'b', 's', or 'o' in the order of the arguments.
Example
load_java("java.lang.Math", "JMath")
objref jm
jm = new JMath()
jm.min2ii(4.1, 3.1) // prints 3
jm.min2dd(4.1, 3.1) // prints 3.1
Attempting to use the overloaded name will print a list of possible disambiguated method names as in:
oc>jm.min(4.1, 3.1)
JMath[0].min Overloaded. Use one of:
        min2ii
        min2dd
/home/hines/nrn5/sparc/bin/nrniv: min Overloaded.  Disambiguate using a
more specific method. near line 10
jm.min(4.1, 3.1)
                 ^
        JMath[0].min(4.1, 3.1)
oc>
Notice that even pure static classes need an object instance in hoc before one can call the static method.

What about unregistered Java objects?

If a java method returns an object that is unregistered (no load_java call that registers that type), hoc encapsulates it as a JavaObject with only two methods, "equals", and "name". JavaObject.equals(o) returns 1 only if the underlying native java object references are the same and JavaObject.name() returns the class name of the underlying java object. When (if) it goes back into java as an argument of another method call, java receives it as its original type. Consider
load_java("java.util.Vector", "JVector")
objref jv, o
jv = new JVector()
o = jv.toString // but the java String is unregistered
oc>print o
JavaObject[0] 
oc> o.name // prints the underlying classname of the java object
        class java.lang.String 
jv.add1o(o) // now put that object into jv
print jv.get(0) // still a JavaObject but not the same hoc version of it.
jv.get(0).equals(o) // but they really are the same
load_java("java.lang.String", "JString")
print jv.get(0).toCharArray // Now it comes back as the proper JString!

What about passing Hoc objects that Java knows nothing about?

In this case java encapsulates it as an opaque HocObject. When it is returned by a method, it is unpacked and reappears as the original hoc object. E.g.
load_java("java.util.Vector", "JVector")
objref jv
jv = new JVector()
jv.add1o(new Graph())
print jv.get(0) //prints Graph[0] (if it was the first vector created)
jv.get(0).size(-100,100,-200,200)

Watch out! Different hoc objects can reference the same java object!

For normal native hoc objects, different internal names of the object (template name followed by an index in brackets) imply distinct object instances. But a java object in hoc is really a reference back to the java object in Java. Thus different internal hoc object names can actually refer to the same java object. Consider
load_java("java.util.Vector", "JV")
objref jv1, jv2
jv1 = new JV()
jv1.add1o(new Vector())
jv1.add1o(jv1)
jv2 = jv1.get(1)
jv1 // prints JV[0]
jv2 // prints JV[1]
jv1 == jv2 /// they are different hoc objects
jv1.equals(jv2) // but they are the same java object
If this causes too much trouble in the future, then the jv1 == jv2 semantics can be changed.