Difference between revisions of "Guide:Java integration"

From IokeWiki
Jump to: navigation, search
(Implementing and extending Java types)
Line 1: Line 1:
 +
= Java integration =
 +
 
Java classes can be accessed by their full name, separated with colon instead of dots:
 
Java classes can be accessed by their full name, separated with colon instead of dots:
  

Revision as of 16:56, 5 April 2009

Java integration

Java classes can be accessed by their full name, separated with colon instead of dots:

h = java:util:HashMap new

You can invoke methods as expected, although the rules for this are a bit intricate. Specifically, the rules are that when calling methods that take any kind of primitive arguments (most of the types in java.lang), these values will be unwrapped. That means for a method taking an int, you can send in a wrapped Java Integer, but you can also send in an Ioke Number Rational, and it will be unwrapped correctly. You can send Text and Symbol to methods taking Strings.

If you send in data to a method taking Object, you will not get the expected result in all cases. Specifically, in these cases Ioke will unwrap wrapped Java objects, but will not coerce the primitive types. So doing h put("foo", "bar") will not coerce Text into java.util.String. However, if you have a java.lang.String that has been modified from the Ioke side - adding cells for example, then that String will be unwrapped before sent to the method call. , The return value from a Java invocation will never be modified, except that null will always be changed into nil, and Java booleans will be transformed into Ioke booleans. This means that if you wrap a Java object, do some modifications to it, and then let it go through a Java call, what you will get back is probably not the wrapped version of that object. Of course, later on if you try to call a method on the object, you will still use the wrapped data.

The general rule for overloading is that overloaded methods will be sorted from the most specific to the most general.

Casts

Ioke Java integration supporting casts in the same way as Java does. The main difference is that the cast will be evaluated at runtime, not compile time. The name to cast to will be unevaluated. Valid names are either Object, String, any Java class name where dots are separated with colons, and a few special ones to handle primitive values. These names are byte, int, integer, long, short, char, character, float, double and boolean. Casting looks like this:

x = Foo new
x bar((short) 123, (org:something:Interface)anObj)

Class methods

Methods that belong to the Java class java.lang.Class are treated a bit differently from the way other Java methods work. The reason for this is a bit intricate to explain, but basically comes down to the dichotomy between the way mimicking works in Ioke, and the way inheritance works in Java.

The easiest way to explain this might be to take a look at java.util.ArrayList. In Java the hierarchy looks like this (somewhat simplified)

instance >> ArrayList -> AbstractList -> AbstractCollection -> Object
                      -> Serializable
                      -> Cloneable
                      -> Iterable
                      -> Collection
                      -> List
                      -> RandomAccess

Note here that all capitalized words are names of classes. The Class itself is not part of the inheritance structure, because Java has two namespaces. In Ioke, the above hierarchy basically ends up looking like this:

instance -> ArrayList -> Class
                      -> AbstractList -> Class
                                      -> AbstractCollection -> Class
                                                            -> Object -> Class
                      -> Serializable -> Class
                      -> Cloneable -> Class
                      -> Iterable -> Class
                      -> Collection -> Class
                      -> List -> Class
                      -> RandomAccess -> Class

This is slightly simplified too, but the basic idea is that Class has to be part of the same inheritance chain, because there only exists one namespace in Ioke.

And that is why there is a need to have these methods named differently. So, for example, say that you want to invoke the toString method on java:util:HashMap. To do that, you do it like this: java:util:HashMap class:toString. Note that you can actually use the class: methods on instances of a class too. The result will be the same as if the receiver had been that class instead of the instance.

Fields

Accessing Java fields can be done by prepending the field name with field:. The exact mechanics of this is that a Java field will result in one or two Ioke methods. That means that the fields are not represented as cells directly on the object. If the field is not final, a setter will be also be generated for it. The setter follows the same rules as invocations of regular methods, with regards to casting and choice of unpacking of arguments.

There is a slight gotcha with this scheme. If you try to set a field that is final, you will end up overwriting the accessor for that field - so it's important to be really careful to not set final fields. In the long run, it might be an idea to implement a setter that signals a condition when this happens, but doing that gives the impression that you can set the value - since the setter is there.

Ioke doesn't care if a field is private or protected. All fields are accessible.

A small example - say that you have a java object in foo. This Java object has two fields called oneThing and anotherThing. Then you can work with it like this:

foo field:oneThing println
foo field:anotherThing = "Bar"
System out println(foo field:anotherThing)

Note that static fields work the same way - so you can access the Java streams directly like this, for example:

java:lang:System field:out println("some text")

Importing

Ioke can import classes just as Java can. The way to do it looks a bit different, though. Also, in Ioke an import only means that a local name will be assigned to a class. It will not necessarily be globally visible, unless you import in a global scope. It's also important to keep in mind that imports will not happen if they might shadow or overwrite a name. If you want that, you will have to do a manual assignment instead.

There are three ways of using the method import. The first one is simply to import one class with the same name as it already has:

;; these are all equivalent
HashMap = java:util:HashMap
import(java:util:HashMap)
import java:util:HashMap

;; you can also import directly into a scope
foo = Origin mimic
foo import(java:util:LinkedHashMap)
foo LinkedHashMap new

Note that you can only give one class to import when using the above way.

The second way to import is when you want to rename the imports. This allow you to import several classes from different packages and also rename them at the same time. This is done using keyword arguments to import:

;; these are all equivalent
Foo = java:util:HashMap
Bar = java:lang:String
import(Foo: java:util:HashMap, Bar: java:lang:String)

;; you can do the same in a new scope:
foo = Origin mimic
foo import(Foo: java:util:HashMap, Bar: java:lang:String)
foo Foo new

The third way allow you to import several classes from the same package. Note that to use this way, you need to provide at least one class name, in addition to the package name:

;; these are the same as the following import
HashMap = java:util:HashMap
LinkedList = java:util:LinkedList
ArrayList = java:util:ArrayList
import(:java:util, :HashMap, :LinkedList, :ArrayList)

;; and just as above, you can do it in a scope too
foo = Origin mimic
foo import(:java:util, :HashMap, :LinkedList, :ArrayList)
foo LinkedList new

Java native arrays

In general, working with Java native arrays work exactly like you would expect. The main difference is how you create them. For working with native arrays of primitive types, you can use the primitive type names, which are: java:byte, java:short, java:char, java:int, java:long, java:float and java:double. When setting or getting values from these arrays, they will work exactly like the coercions for regular Java methods.

You can either create a new empty array, or you can initialize it based on an existing Ioke list:

x = java:short[5] new
x = java:short[] from([1,2,3,4,5])

The first version creates a JavaArrayProxyCreator with dimension one, and length 5. The call to new will generate the actual array from this. You can create multidimensional arrays by adding new pairs of square brackets.

In the second example, the call to square-brackets without an argument, will return the type of that array class. We then call from on that type, which will take an Ioke list, and generate an equivalent Java native array.

You can set and get values in Java arrays, just as you would with Ioke lists:

x = java:char[10] new
x[5] = 10
x[6] = 13
x[5] println

Finally, Java native arrays implement each, and mimic Mixins Enumerable, which means that most things you expect to be able to do with them will work correctly.

Adding jar-files to the CLASSPATH

If you dynamically want to add new jar-files to the CLASSPATH, you can do that using use. There are two ways of doing it - you can either be implicit or explicit, depending on if the module name is unique to a jar-file or not:

use("the_jar_file")
use("the_jar_file.jar")
use jar("the_jar_file")

Implementing and extending Java types

Ioke E supports implementing Java interfaces from Ioke, and extending Java classes. This machinery is all done with the method integrate.

The integrate method takes one or more Java types (it doesn't matter if it's classes or interfaces, but it can only take at most one class), and then returns a new class that is magic in that it will proxy back all calls to the Ioke object, if it defines a method with the corresponding name. This scheme works for most things, but there are some things to keep in mind too. The most important one is that if you override a method that is overloaded in Java-land, it is the responsibility of the Ioke method to handle all the overloads.

Using it is very simple:

IokeRunnable = integrate(java:lang:Runnable)
IokeRunnable run = method("running!" println)
java:lang:Thread new(IokeRunnable new)

Note that you have to use the new method to create new instances of this object. In this way it behaves exactly like regular objects in Java integration. And just in the same way, doing mimic will not copy the internal Java object, just the outwards Ioke object.

Integration takes care of handling coercion of arguments and return values into what Java expects.

Coercing lexical blocks to interfaces

Ioke contains a mechanism that will automatically try to coerce anything code-like into an interface or abstract class if no other matching overload can be found for a method. So, that means you can do something like this, and it will work as expected:

javax:swing:JButton new addActionListener(fn(e, "button pressed" println))