Java Interface Library
Aus ExpeccoWiki
Inhaltsverzeichnis |
Introduction
The Java Interface Library consists of blocks to interact with Java objects inside an external JavaVM (also: inside an Android Mobile device) and a programmatic interface. The Java interface is realized by a so called "bridge", which is similar in operation to the dotNET bridge .
Programmatic Interface
Access to Java objects, classes and programs is done via a framework called "Java-Bridge". This framework implements transparent forwarding of function calls to Java objects, as existent in a local or remote Java virtual machine (JVM). Also, return values, callBacks and exception information are passed back from the Java program to expecco. A proxy-object mechanism, which catches all function calls, wraps the arguments and sends a datagram to the other bridge side. Thus, remote procedure calls are almost completely transparent to the Smalltalk/JavaScript code inside expecco. In your elementary code, you can write function calls as if they were to a local object.
In addition to what existing blocks of the Java Interface Library provide, programmatic access to Java objects is sometimes required to perform special functions. The following information is useful if you want to write your own elementary Java-blocks, or if you have to enhance the existing library by adding application-specific interface blocks.
Initializing / Releasing the Bridge
Before any communication can take place between expecco and any Java object, the Java side of the bridge has to be started, and a communication has to be established.
All of the bridges classes are in the JAVA namespace.
The main interface class is "Java", in the "JAVA" namespace:
java := JAVA::Java newWithServer.
or (in JavaScript):
java = JAVA::Java.newWithServer();
This starts the Java-side of the bridge, and establishes a connection to the JavaVM.
Close the bridge if the bridge is no longer needed. Release the bridge with:
java.closeBridge();
which terminates the connection.
Loading Applications
By accessing a classloader, additional classes, or applications as contained in JAR files can be loaded.
This can be used to add a JAR file to the class path:
java bridgeSide addJarToClassSearchPath:'pathToJarFile'.
Java Package import and Class access
The import of required packages is done indirectly, when one of the package's classes is accessed for the very first time. Or in other words, if you access a class and the package of the class is not imported, then it is for the next class access in the same package and you don't need to specify the package again for the next class you use from the same package.
This code access the class JFrame from the "javax.swing" package for the first time. The whole class name in Java would be "javax.swing.JFrame". Note that the dots are replaced by underscores and the full class name is a message send to the Java handle.
java javax_swing_JFrame.
After that call the JavaVM now knows the package "javax.swing". Now we can access all classes of that package without specifying the package before the class name. For example we now want to access the class "javax.swing.JButton". The following code shows how to do this now in short form.
java JButton.
ATTENTION: If a classes short form exists more than one time on the Java side then you have to specify the full name of the class you want to use. Else the first matching class will be taken and thats probably not the class you was looking for.
Instantiating a Class
Object instances are created via the "new"-message, sent to a proxy of a Java class. You can get this proxy as described in the previous section. For example we want to create a new instance of a JFrame and a JButton.
frame := java javax_swing_JFrame new.
button := java JButton new.
The first statement results in an proxy object which represents the JFrame instance, this also does the import of the "javax.swing" package indirectly. The second line results in a proxy for a new JButton object.
Calling Methods
A method call is done by a message send to a proxy object where the message is the method name to call. For example to call the "setVisible(boolean isVisible)" method on a JFrame object. First instantiate such an object and send the message to that proxy.
frame setVisible:true.
To call a method with more than one parameter like "setSize(int width,int height)" on a JFrame we can write this.
frame setSize:300 anyWord:200.
ATTENTION: The "anyWord:" in the message can be anything you want. In this example it could be useful to name it to height. (frame setSize:300 height:200)
Or for a method with 4 parameters it could look like this.
frame setBounds:100 y:50 width:300 height:200.
ATTENTION: Also here the "y: width: height:" can be named as you want.
Calling static Methods
Calling a static method on an object is not different from calling non static methods. In the most cases you will call a static method not on an object but directly on the class. This is done by just calling the method on the class object. For example we want to call the static method "isDefaultLookAndFeelDecorated()" of the JFrame class.
java javax_swing_JFrame isDefaultLookAndFeelDecorated.
or if the package is already loaded:
java JFrame isDefaultLookAndFeelDecorated.
Accessing Fields
Accessing a field of a Java object or class is not different from calling methods. To access a field of an object or a class we send the message to the object proxy where the message is the field name of the Java class. To access the field of a class the field must be static. For example we want to get the value of the static field "EXIT_ON_CLOSE" of the JFrame class.
value := java javax_swing_JFrame EXIT_ON_CLOSE.
or if the package is already loaded:
value := java JFrame EXIT_ON_CLOSE.
Value now holds the integer value of the "EXIT_ON_CLOSE" constant.
Let us imagine that a JFrame object would have a field named "myField". To access this field we would write something like this.
value := frame myField.
Value will now hold what ever "myField" returns. This could be another object or any primitive.
Callbacks from Java
Sometimes we want to execute a small piece of smalltalk code during the execution of the Java code. For example if an observer notifies the smalltalk side or if a button on a Java GUI was pressed. The following code registers a smalltalk code block as a callback for an mouse pressed event of a button.
listener := MouseListener new.
listener mousePressed:[ Transcript showCR:'Hello from Java' ].
This creates the callback and the listener, which can now be registered on a button:
button addMouseListener:listener.
When we now press the button the smalltalk block will be executed and writes "Hello from Java" on the smalltalk console.
Or another example with an observer would look like this.
observer := java Observer new.
observer update:[:sourceObservable :argument|
argument notNil ifTrue:[ Transcript showCR:('I was notifyed with: ',argument toString) ]
ifFalse:[ Transcript showCR:'I was notifyed and the argument was nil' ].
].
myObservable addObserver:observer.
When the observable calls "notifyObservers" the smalltalk block will be executed.
Remote Dynamic Code Injection
Sometimes we might want to execute code without the overhead of many rpc-messages being sent over the bridge, especially if we want to do something time critical, or want to make execution time measurements. For this, it is possible to load a class at runtime into the JavaVM. The code of the class will first be sent to the JavaVM, which will compile and load the generated byte code. Then, the class can be accessed via the bridge just like any other preloaded class.
The following example specifies the class code in a smalltalk string and inject the code into the JavaVM. Then an instance of that new class will be instantiated and finally a method of the object instance will be invoked:
classCode := '
package myPackage.test;
import java.awt.BorderLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Calendar;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;;
public class MyInjectedClass {
public void createSampleApp(){
JFrame frame=new JFrame("Hello im injected");
frame.setLayout(new BorderLayout());
JButton button=new JButton("click me");
final JLabel label=new JLabel("Text");
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
label.setText(Calendar.getInstance().getTime().toString());
}
});
frame.add(label,BorderLayout.NORTH);
frame.add(button,BorderLayout.SOUTH);
frame.setSize(300, 300);
frame.setVisible(true);
}
}
'.
java RemoteClassInject injectClassCode:'myPackage.test' className:'MyInjectedClass ' classCode:classCode.
java myPackage_test_MyInjectedClass new createSampleApp.
After the code has been injected, a new instance of "MyInjectedClass" is created and the "createSampleApp" method is called. Because the example implements a GUI, a Java window will open, containing a label and a button. Clicking the button displays the current time in the label.
Redefining dynamic injected classes
The Java bridge makes it possible to even redefine any previously injected class during runtime, without a need to shut down and restart the application. Any new instance created after the re-injection will have the new defined behavior. Static methods will be affected immediately after a re-inject. Notice that existing old instances are not affected by a changed class definition - only new instances will be.
Here is an example where a class named "MyCounter" with a static method "raiseCount" will be redefined. Initially the method increments a counter by 1. After redefining and re-injecting the method, the behavior will change to decrement the counter by 1.
currentCount:=0.
packageName := 'myPackage.test'.
className := 'MyCounter'.
"/defining MyCounter
classCode := '
package %1;
public class %2 {
private static int count=%3;
public static int raiseCount(){
return count+=1;
}
}
'bindWith:packageName with:className with:currentCount.
java RemoteClassInject injectClassCode:packageName className:className classCode:classCode.
"/starting a thread which calls the raiseCount method of the class MyCounter
proc:=[
[true]whileTrue:[
currentCount := java myPackage_test_MyCounter raiseCount.
Transcript showCR:currentCount.
Delay waitForSeconds:1.
].
]forkAt:7.
Delay waitForSeconds:10.
"/redefining MyCounter
classCode := '
package %1;
public class %2 {
private static int count=%3;
public static int raiseCount(){
return count-=1;
}
}
'bindWith:packageName with:className with:currentCount.
java RemoteClassInject injectClassCode:packageName className:className classCode:classCode.
Delay waitForSeconds:10.
proc terminate.
Code Sources for Class injection
There are 3 possible types of class code sources which we can use to inject the code. In the examples before we directly used a simple "String" in the smalltalk code as a code source. We also can use a "*.java" file as source. Just read the content of the file into a string and use it as in the previous examples.
To inject a "*.class" file use the following code:
java RemoteClassInject injectClassFile:packageName className:className classCode:classCode.
Examples
This example creates a JFrame with one button on it. Then a Mouselistener is created and registered on the button. When the button is pressed the title of the window changes to "onPressed" and the transcript shows the message "onPressed". When the button is released, the title of the window changes to "onReleased" and the transcript shows "onReleased". If the button lost the mouse focus then the bridge will exit.
|java frame button listener|
"/getting a bridge
java := JAVA::JavaBridge startNew.
"/creating a new frame and button object
frame := java javax_swing_JFrame new.
button := java JButton new:'Click'.
"/creating a new mouse listener and adding callback blocks
listener := java MouseListener new.
listener mousePressed:[ Transcript showCR:'onPress'. frame setTitle:'onPressed' ].
listener mouseReleased:[ Transcript showCR:'onReleased'. frame setTitle:'onReleased' ].
listener mouseExited:[ Transcript showCR:'onExited'. java exitJava. ].
"/register the mouse listener on the button and make the frame with button visible
button addMouseListener:listener.
frame add:button.
frame setSize:300 y:100.
frame setVisible: true.
Now nearly the same example with injected code but first without callback.
| java packageName className classCode|
"/getting a bridge
java := JAVA::JavaBridge startNew.
"/defining a java class in a string
packageName := 'myPackage.test'.
className := 'MyExampleClass'.
classCode := '
package %1;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class %2{
public static boolean doJavaExit=false;
public static void start(){
final JFrame frame=new JFrame();
JButton button=new JButton("Click");
MouseListener listener=new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
frame.setTitle("onReleased");
}
@Override
public void mousePressed(MouseEvent e) {
frame.setTitle("onPressed");
}
@Override
public void mouseExited(MouseEvent e) {
doJavaExit=true;
}
};
button.addMouseListener(listener);
frame.add(button);
frame.setSize(300, 100);
frame.setVisible(true);
}
}
'bindWith:packageName with:className.
"/injecting the class into the java vm
java RemoteClassInject injectClassCode:packageName className:className classCode:classCode.
"/calling the static start method on the injected class
java myPackage_test_MyExampleClass start.
"/idle until the field doJavaExit in the injected class returns true.
[ MyExampleClass doJavaExit ]whileFalse:[
Delay waitForSeconds:1.
].
"/exit the java vm
java exitJava.
Now the same code but with a callback to smalltalk via an Java observer.
| java packageName className classCode observer |
"/getting a bridge
java := JAVA::JavaBridge startNew.
"/defining a java class in a string
packageName := 'myPackage.test'.
className := 'MyExampleClass'.
classCode := '
package %1;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Observable;
import javax.swing.JButton;
import javax.swing.JFrame;
public class MyExampleClass extends Observable{
public static MyExampleClass anInstanceOfMe=new MyExampleClass();
public static void main(){
anInstanceOfMe.start();
}
public void start(){
final JFrame frame=new JFrame();
JButton button=new JButton("Click");
MouseListener listener=new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
frame.setTitle("onReleased");
MyExampleClass.anInstanceOfMe.setChanged();
MyExampleClass.anInstanceOfMe.notifyObservers("onReleased");
}
@Override
public void mousePressed(MouseEvent e) {
frame.setTitle("onPressed");
MyExampleClass.anInstanceOfMe.setChanged();
MyExampleClass.anInstanceOfMe.notifyObservers("onPressed");
}
@Override
public void mouseExited(MouseEvent e) {
MyExampleClass.anInstanceOfMe.setChanged();
MyExampleClass.anInstanceOfMe.notifyObservers("onExited");
}
};
button.addMouseListener(listener);
frame.add(button);
frame.setSize(300, 100);
frame.setVisible(true);
}
}
'bindWith:packageName with:className.
"/injecting the class into the java vm
java RemoteClassInject injectClassCode:packageName className:className classCode:classCode.
"/calling the static main method on the injected class
java myPackage_test_MyExampleClass main.
"/creating a java observer proxy on in smalltalk
observer := java Observer new.
"/implement the update method which is called by an observable, the code in the block will then be executed
observer update:[:observable :argument|
Transcript showCR:argument.
"/do the exit if the argument is equal 'onExited'
(argument=#onExited) ifTrue:[ java exitJava ].
].
"/register the observer to the observable we want to be notifyed from
MyExampleClass anInstanceOfMe addObserver:observer.
Back to Online Documentation.