====== Symbiosis with Java ======
AmbientTalk is entirely implemented in Java and runs on top of the JVM. Java provides an extensive class library that can be accessed from within AmbientTalk. In other words, Java classes can be instantiated and messages can be sent to Java objects from within AmbientTalk.
The reverse, accessing AmbientTalk objects from within Java, is also possible. AmbientTalk objects are represented in Java as objects implementing a certain Java interface.
This chapter explains how to program using this "symbiotic relationship" between AmbientTalk and Java. By means of this "symbiosis" AmbientTalk can use the extensive class library from Java and Java can benefit from AmbientTalk's superior concurrency abstractions.
===== Built-in Conversions =====
When AmbientTalk values and Java values cross language boundaries (e.g. when they are passed as arguments of or return values from a method that is invoked from the other language), these values are converted. AmbientTalk features a number of built-in conversions that maps AmbientTalk's native language values onto JVM native data types and vice versa. The following tables show the conversion rules for going from Java to AmbientTalk and from AmbientTalk to Java.
^ Java value : type ^ AmbientTalk value ^
| null: Object | nil |
| n: int | a number n |
| d: double | a fraction d |
| b: boolean | a boolean b |
| s: String | a text s |
| array: T[] | a table (of converted values) |
| e: Exception | an exception e |
| c: Class | class wrapper( c ) |
| o: ATObject | o |
| o: Type | java wrapper(o : Type) |
^ AmbientTalk value ^ Java value : type ^
| nil | null: Object |
| a number n | n: int |
| a fraction f | f: double |
| a boolean b | b: boolean |
| a text t | t: String |
| a table t | t: T[] |
| an exception exc | exc : Exception |
| a class wrapper( c ) | c : Class |
| a java wrapper(obj : Type) | obj : Type |
| any object o | AT wrapper (o) : I |
Note that non-native Java or AmbientTalk objects are represented in the other language by means of "wrappers". The task of these "wrappers" is to represent an object written in one language to act as if it were an object written directly in the other language.
In the last conversion rule of the second table, ''I'' represents a Java interface type. ''I'' is determined by a static type declaration in Java.
===== Accessing Java from within AmbientTalk =====
==== Accessing Java classes ====
Any class that can be loaded from the class path of the JVM running the AmbientTalk interpreter is accessible from within an AmbientTalk program. Classes are loaded by sending messages to a special object named ''jlobby'', which is defined in the top-level scope. To load a Java class, one selects it as if it were a field of ''jlobby''. For example, referencing the ''java.util.Vector'' class from within AmbientTalk can be accessed with:
>def Vector := jlobby.java.util.Vector
>>
A class is represented as a normal AmbientTalk object. All ''public static'' methods defined on the class can be invoked like normal methods on its "wrapped" object representation.
If you want to load your own Java class make sure that the .class file is in the classpath of the JVM running AmbientTalk. By default, the iat script adds the standard $CLASSPATH classpath.
Please do not name your own Java packages starting with a capital letter. Otherwise, AmbientTalk will confuse the package with a Java class.
==== Creating Java objects ====
Java classes can be instantiated in AmbientTalk similar to how AmbientTalk objects are instantiated, i.e. by sending ''new'' to the wrapper for the class, which returns a wrapped instance of the Java class. Arguments to new are passed as arguments to
the Java constructor. Here is how to create a new vector in AmbientTalk:
>def aVector := Vector.new()
>>
If the Java class defines multiple constructors, the one being invoked depends on the number of arguments passed to ''new''. For example, the Vector class also has a constructor that takes an initial capacity as its argument. This constructor can be called using an integer as the argument of new:
>aVector := Vector.new(30)
>>
==== Invoking methods on Java objects ====
Java objects are represented as AmbientTalk objects whose field and method slots correspond to ''public'' instance-level (non-static) fields and methods defined on the Java object's class. These are accessed or invoked as if they were plain AmbientTalk slots. In the example, this means that all public methods and fields of the ''Vector'' class are accessible from within AmbientTalk. For example, to add elements to the vector we can simply invoke the Java Vector's ''add'' method on the AmbientTalk wrapper object.
>1.to: 10 do: { |i| aVector.add(i) }
>>nil
>aVector
>>
When an AmbientTalk object sends a message to a wrapped Java object, the following algorithm is applied:
* if a message is sent to a wrapper for a **class** object, only static fields or methods of the Java class are considered.
* If the message is sent to an **instance** wrapper, only non-static fields or methods of the Java class of the wrapped object are considered.
* If the AmbientTalk selector uniquely identifies a method (i.e. no overloading on the method name is performed in Java), the matching method is invoked. All AmbientTalk arguments are converted to Java objects. This is done by wrapping them into Java objects in the case of custom objects or by converting them to native Java values if possible (e.g. for the different number types and strings). The Java return value is mapped back to an AmbientTalk value.
==== Overloading ====
In Java methods can be overloaded based on the number of arguments and the static types of the arguments. Invoking an overloaded method from within AmbientTalk requires special consideration because it does not have the notion of static types.
If the Java method is overloaded based on arity (i.e. each overloaded method takes a different number of arguments), the number of arguments in the AmbientTalk invocation can be used to identify a unique Java method. Hence, overloading based on arity does not require special attention from the AmbientTalk programmer. If the Java method is overloaded based solely on argument types, the interpreter may derive that the actual arguments can only be converted from AmbientTalk to the appropriate Java types for one of the matching overloaded signatures. Again, if only one match remains, the unique match is invoked. In the remaining case in which the actual AmbientTalk arguments satisfy more than one overloaded method signature, the symbiotic invocation fails with a runtime exception. It is then the AmbientTalk programmer's responsibility to provide explicit type information in the method invocation.
Selection of the correct overloaded method is done using a special ''cast'' method defined on wrapped Java methods. Consider the method ''java.util.Vector.remove''. It is overloaded as ''remove(int)'', which takes the index of the element that needs to be removed, and as ''remove(Object)'', which takes the element that needs to be removed itself as argument. The method overloaded on ''int'' can be invoked as follows:
>def remove := aVector.&remove
>>
>remove.cast(jlobby.java.lang.Integer.TYPE)(0)
>>1
>aVector
>>
In the example above the expression ''aVector.&remove'' returns a closure representing all ''remove'' methods defined on the ''Vector'' class. This closure understands the message ''cast'' which, given the types of the formal parameters, returns a closure that represents only the Java methods whose signature adheres to those types. Types are represented as classes. In the above example, we use the class ''jlobby.java.lang.Integer.TYPE'' to identify the type ''int''. We subsequently invoke the casted method with an argument (''0'') to remove the first element of the vector. The ''remove(Object)'' method can be invoked similarly, as follows:
>remove.cast(jlobby.java.lang.Object)(3)
>>true
>aVector
>>
===== Accessing AmbientTalk from within Java =====
==== Invoking AmbientTalk methods in Java ====
Besides calling Java methods from within AmbientTalk it is also possible to call AmbientTalk methods from within Java. To illustrate this consider the code snippet shown below. The **SymbiosisDemo** class is loaded and an instance is assigned to **javaDemo** variable. The method **run** is invoked on this object and an AmbientTalk object **atObject** is passed as its argument.
def SymbiosisDemo := jlobby.at.tutorial.SymbiosisDemo;
def showSymbiosis() {
def javaDemo := SymbiosisDemo.new();
def atObject := object: {
def ping() {
system.println("ping!");
javaDemo.run2(self);
};
def pong() {
system.println("pong!");
42
}
};
javaDemo.run(atObject);
};
self
When an AmbientTalk object is passed as an argument to a Java method expecting an object of an interface type, the AmbientTalk object will appear to Java objects as a regular Java object implementing that interface. In the example the Java interface **PingPong** contains the two methods **ping** and **pong** that were defined in **atObject**. This interface is also the type of the Java method **run**. Hence, messages sent to this wrapped AmbientTalk object appear as regular Java method invocations on an interface type. Also, note the return type of the methods **ping** and **pong**: since the AmbientTalk implementation of **pong** returns an integer, which is also the return value of the method **ping**.
package at.tutorial;
public class SymbiosisDemo {
public interface PingPong {
public int ping();
public int pong();
}
public int run(PingPong pp) {
return pp.ping();
}
public int run2(PingPong pp) {
return pp.pong();
}
}
If Java invokes a method declared in an interface with an overloaded method signature, all overloaded invocations are transformed into the same method invocation on the AmbientTalk object. In other words, the AmbientTalk object does not take the types into consideration. However, if the Java method is overloaded based on arity, the AmbientTalk programmer can take this into account in the parameter list of the corresponding AmbientTalk method, by means of a variable-argument list or optional parameters. Otherwise, the Java invocation may fail because of an arity mismatch.
>def test := /.at.tutorial.symbiosis;
>test.showSymbiosis();
ping!
pong!
>>42
==== Starting an AmbientTalk interpreter from Java ====
So far, the examples have illustrated how to reuse Java code from within AmbientTalk. They have shown how to access Java classes, instantiate them and invoke methods on the resulting objects. Moreover, AmbientTalk objects can be passed as parameters to such Java methods, provided that the method expects an interface type. Whereas the ability to reuse Java code from within AmbientTalk provides means to e.g. build applications which offer a graphical user interface or which talk to a database, it is also often useful to embed AmbientTalk in an existing Java application.
Embedding AmbientTalk in an application, requires one to start an AmbientTalk virtual machine, a task performed by the EmbeddableAmbientTalk class. This is an abstract class. To create a new embeddable AmbientTalk virtual machine, you need to create a subclass that implements its abstract methods. These methods, such as ''handleParseError'' and ''handleATException'', should define appropriate error-handling code to be executed when an AmbientTalk script crashes.
Once an instance of an EmbeddableAmbientTalk subclass is created, the AmbientTalk VM can be started by sending it the ''initialize'' message. The corresponding method expects the following arguments in order to be able to correctly initialize the resulting virtual machine and evaluation actor: a parsed version of the init file, a set of fields which should be visible in every actor created on the virtual machine and the network on which the virtual machine will broadcast its presence. The example below shows the default settings:
EmbeddableAmbientTalk vm = new MyEmbeddableAmbientTalk();
vm.initialize(
NATParser.parse(
initFile.getName(),
Evaluator.loadContentOfFile(initFile)),
new SharedActorField[] {
vm.computeSystemObject(arguments),
vm.computeWorkingDirectory(),
vm.computeObjectPath(objectPath) },
"AmbientTalk");
The code excerpt also illustrates that the EmbeddableAmbientTalk class provides methods to create definitions for fields such as ''system'' which by default offers I/O through the console and provides access to the program arguments, ''~'' which allows addressing source files located in the working directory (i.e. the directory from which the Java application was started) and the object path (''lobby'' or ''/'') which allows loading files situated in a library path.
Once the virtual machine is properly initialized, the embedding program can start to evaluate AmbientTalk code. The EmbeddableAmbientTalk class provides two methods to do this, namely ''evalAndPrint'' and ''evalAndWrap''. The former method can be used to write the result of evaluating the code to a PrintStream, which is used for instance to build the Interactive AmbientTalk (iat) shell. The latter can be used to return the resulting object, albeit wrapped as an object adhering to a particular interface. This wrapped object can then be used further by the Java application. In the example below we create a controller instance in a model-view-controller application by evaluating an AmbientTalk source file. This controller will take care of the distribution aspects and will be sent messages by the views when they request changes:
public interface Controller {
public void executeEvent(ApplicationEvent evt);
public void executeEventWithoutUndo(ApplicationEvent evt);
public void undo();
}
...
private Controller controller =
(Controller) vm.evalAndWrap(
Evaluator.loadContentsOfFile("controller.at"),
Controller.class);
The corresponding AmbientTalk code should then return an object which implements the three methods to modify the model, and can be used to detect other reachable controllers with which it can exchange ApplicationEvents.
When starting an AmbientTalk virtual machine from a Java application, the resulting system is inherently multithreaded. The wrappers created by the ''evalAndWrap'' method will ensure that the Java code cannot break the concurrency properties of AmbientTalk. Moreover, by default this wrapper will ensure that the Java thread waits for the result of evaluating the AmbientTalk code which prevents concurrent access on possible Java objects used by the evaluated code. For more detailed information on this topic we refer to our [[http://soft.vub.ac.be/Publications/2007/vub-prog-tr-07-15.pdf|ICDL2007 paper]].