Table of Contents
SISC can be used as a scripting language for Java, or Java may be used to provide functionality to Scheme. Such activity is collectively termed 'bridging'. In SISC bridging is accomplished by a Java API for executing Scheme code and evaluating Scheme expressions, and a module that provides Scheme-level access to Java objects and implementation of Java interfaces in Scheme.
Multiple Scheme applications can run within a single SISC runtime. Each application has its own top level environment and evaluator. An application is represented by instances of the sisc.interpreter.AppContext class. A Scheme application can execute in multiple threads. Each thread must have its own dynamic environment, containing entities such as the current input and output ports. Dynamic environments are represented by instances of the sisc.env.DynamicEnvironment class. A thread can be associated with multiple interpreters. The reason for there being multiple interpreters is that internal calls need to be executed by an interpreter different from the one that initiated the internal call so that the state of the latter can be preserved.
A call from Scheme to Java can occur in one of two places:
Within a call from Scheme to Java - we shall call this an “internal call”.
Elsewhere, e.g. as the result of a timer expiration or as part of a thread created by Java - we shall call this an “external call”.
The sisc.interpreter.Context class is the key point where both the above call scenarios are managed.
When making an internal call, one typically wants to do that in an interpreter that shares the same AppContext and DynamicEnvironment as the calling interpreter. One can get hold of such an interpreter using
Interpreter r = Context.enter();
One can determine if a call is internal in the following manner:
Interpreter current = Context.currentInterpreter(); if (current == null) { ...make external call...} else { ...make internal call... }
The calls themselves are made using one of the three eval methods on the sisc.interpreter.Interpreter:
public Value eval(String expr);
Evaluates expressions read from a string, returning the result of the evaluation of the last expression.
public Value eval(InputStream stream,
String sourceId);
Evaluates expressions read from an input stream, returning the result of the evaluation of the last expression. The sourceId identifies the source of the stream for display purposes.
public Value eval(Value val);
This is the same as calling (eval val) in Scheme.
public Value eval(Procedure proc,
Value[] args);
This is the same as calling (proc arg ...) in Scheme.
Several such calls can be made on the same interpreter.
Before returning from the code that is making the internal calls one must call
Context.exit()
in order to release the resources associated with the call context.
The main challenge when making external calls is what application and dynamic environment to make them in. sisc.interpreter.Context maintains an application registry. To register an application call
Context.register("appname", appContext)
The application registry can be used to obtain new interpreters that are associated with specific applications and have a fresh dynamic environment:
Interpreter r = Context.enter("appname");
AppContexts are usually created by calling one of the two primary constructors of the class. The first takes no arguments and results in an application context whose application properties are defined by the Java system properties. The second constructors takes a Properties object as an argument, allowing the Java system properties to be overridden with custom application properties.
After creation, AppContexts are typically modified by loading a heap image into them. Heap images are randomly accessable, and as such must be accessed in their entirety, rather than as a stream. If not loaded from disk, this entails first transfering the image over a stream into memory, then operating on the entire heap in memory. A typical initialization sequence, loading the heap image from disk, would be:
AppContext ctx = new AppContext(); Context.register("myapp", ctx); Interpreter r = Context.enter("myapp"); ctx.loadEnv(r, new SeekableDataInputStream(new BufferedRandomAccessInputStream("sisc.shp","r"))); Context.exit();
Sometimes one may want to use a different mechanism for finding applications and dynamic environments. For instance, threads created from Scheme should probably execute within the application that created them and using a dynamic environment that is cloned from the dynamic environment present when the thread is started. There is therefore a general mechanism for obtaining a new interpreter that uses a specific application and dynamic environment:
Interpreter r = Context.enter(appContext, dynamicEnv);
External calls are made in the same way as internal calls, e.g. using one of the eval methods. Before returning from the code that is making the external calls one must call
Context.exit()
in order to release the resources associated with the call context.
Interpreter.eval() throws a sisc.interpreter.SchemeException when an evaluation causes an exception that is not caught inside the evaluation. When making internal calls the exception can be propagated to the calling interpreter in one of four ways:
by throwing a RuntimeException - this will be reported as"Error in "prim-name: description".
by calling Module.throwPrimException("description") - this will be reported as "Error in prim-name: description".
by calling throwNestedPrimException("description", schemeException) - this will be reported as "Error in prim-name: description\nnested-description".
by calling throwNestedPrimException(schemeException) - this will be reported as "Error in prim-name: exception during nested call\nnested-description".
Scheme code can throw Java exceptions (see the section called “Exception Handling”). The sisc.modules.s2j.Util class contains a static method javaException(schemeException) that extracts the Java exception from a SchemeException, or, if no Java exception is present, returns the SchemeException.
Continuations do not cross the Scheme/Java boundary. In the embedded call scenario invoking a continuation inside the embedded call will not discard the computation of the caller. The embedded call will return when the continuation returns. If the continuation contains the read-eval-print loop the return will never happen. Similarly, capturing a continuation inside a call (embedded or external) will only capture the continuation to point where the call was made.
Capturing and invoking a continuation within the same call works correctly.
Requires: (import s2j)
The High-Level S2J API allows Scheme code to instantiate Java classes, call methods on Java objects, access/modify fields of Java objects and implement Java interfaces that delegate to Scheme code.
Java classes are types in SISC's extensible type system. They are made accessible to Scheme code by one of the following procedures / special forms:
procedure:
(java-class symbol) => jclass
Returns the Java class of name symbol, which can also be the name of a primitive type or an array type.
(java-class '|java.lang.String|) ;=> <jclass> (define <java.io.object-input-string/get-field**> (java-class '|java.io.ObjectInputStream$GetField[][]|))
syntax:
(define-java-class scheme-name [java-name])
Binds scheme-name to the Java class named by java-name, or, if no such parameter is supplied, by the mangled scheme-name.
(define-java-class <jstring> |java.lang.String|) (define-java-class <java.io.object-input-string/get-field**>)
syntax:
(define-java-classes form ...)
where form is of the form scheme-name or (scheme-name java-name ) Creates bindings for several Java classes.
The form expands into several define-java-class forms.
(define-java-classes (<jstring> |java.lang.String|) <java.io.object-input-string/get-field**>)
Mangling of class names allows classes to be identified more schemely, e.g. <java.io.object-input-stream/get-field**> corresponds to the Java type java.io.ObjectInputStream.GetField[][] (note that GetField is a nested class). More formally, mangling of class names checks for the presence of angle brackets (<>) around the scheme-name. If they are present they are stripped. All the identifiers between the dots (.) are passed through field name mangling (see the section called “Fields”). The character following the last dot is upcased. A slash (/) is treated as a nested class indicator; it is replaced with dollar ($ and the character following it is upcased. Trailing stars (*) characters are replaced with pairs of brackets ([]).
There are predicates for determining whether a Scheme value is a Java class or interface. All Java interfaces are also Java classes.
procedure:
(java-class? value) => #t/#f
Returns #t if value is a Java class, #f otherwise.
(java-class? (java-class '|java.lang.String|) ;=> #t (define <java-io-object-input-string/get-field**> (java-class '|java.io.ObjectInputStream$GetField[][]|)) (java-class? <java.io.object-input-string/get-field**>) ;=> #t
procedure:
(java-interface? value) => #t/#f
Returns #t if value is a Java interface, #f otherwise.
(java-interface? (java-class '|java.util.Map|)) ;=> #t (java-interface? (java-class '|java.lang.String|) ;=> #f
Java classes are serializable by the SISC runtime.
Java methods are made accessible to Scheme code as procedures that can invoke any method of a given name on any Java object. Method selection is performed based on the types of the arguments to the procedure call. Static Java methods can be invoked by passing an instance of the appropriate class or an appropriately typed null object (see the section called “Instances”) as the first argument to the procedures.
procedure:
(generic-java-method symbol) => procedure
Returns a procedure that when invoked with a Java object as the first argument and Java values as the remaining arguments, invokes the best matching named method named symbol on the Java object and returns the result.
(generic-java-method '|getURL|) ;=> <jmethod> (define empty-list? (generic-java-method '|isEmptyList|))
syntax:
(define-generic-java-method scheme-name [java-name])
Binds scheme-name to the generic Java method named by java-name, or, if no such parameter is supplied, by the mangled scheme-name.
(define-java-method get-url |getURL|) (define-java-method empty-list?)
syntax:
(define-generic-java-methods form ...)
where form is of the form scheme-name or (scheme-name java-name ) Creates bindings for several generic Java methods.
The form expands into several define-generic-java-method forms.
(define-generic-java-methods (get-url |getURL|) empty-list?)
Method name mangling allows methods to be identified more schemely, e.g. empty-list? corresponds to the Java method name isEmptyList. More formally, mangling of method names removes trailing exclamation marks (!) and replaces trailing question marks (?) with a leading is-. The result of this mangling is passed through field mangling (see the section called “Fields”).
Generic Java methods are serializable by the SISC runtime.
Fields are made accessible to Scheme code as procedures that can get / set any field of a given name on any Java object. If several fields of the same name are present in the object due to the object class's inheritance chain, the most specific field, i.e. the one bottommost in the inheritance hierarchy, is selected. Static Java fields can be accessed / modified by passing an instance of the appropriate class or an appropriately typed null object (see the section called “Instances”) as the first argument to the procedures.
Generic Java field accessors, i.e. procedures that allow Scheme code to obtain the value of a Java field, can be defined as follows:
procedure:
(generic-java-field-accessor symbol) => procedure
Returns a procedure that when invoked with a Java object as the first argument, retrieves the value of the Java field named symbol in the Java object.
(generic-java-field-accessor '|currentURL|) ;=> <jfield> (define :current-input-port (generic-java-field-accessor '|currentInputPort|))
syntax:
(define-generic-java-field-accessor scheme-name [java-name])
Binds scheme-name to the generic Java field accessor for fields named java-name, or, if no such parameter is supplied, the mangled scheme-name.
(define-generic-java-field-accessor :current-url |currentURL|) (define-generic-java-field-accessor :current-input-port)
syntax:
(define-generic-java-field-accessors form ...)
where form is of the form scheme-name or (scheme-name java-name ) Creates bindings for several generic Java field accessors.
The form expands into several define-generic-java-field-accessor forms.
(define-generic-java-field-accessor (:current-url |currentURL|) :current-input-port)
Generic Java field modifiers, i.e. procedures that allow Scheme code to set the value of a Java field, can be defined as follows:
procedure:
(generic-java-field-modifier symbol) => procedure
Returns a procedure that when invoked with a Java object as the first argument and a Java value as the second argument, sets the value of the Java field named symbol in the Java object to that value.
(generic-java-field-modifier '|currentURL|) ;=> <jfield> (define :current-input-port! (generic-java-field-modifier '|currentInputPort|))
syntax:
(define-generic-java-field-modifier scheme-name [java-name])
Binds scheme-name to the generic Java field modifier for fields named java-name, or, if no such parameter is supplied, the mangled scheme-name.
(define-generic-java-field-modifier :current-url! |currentURL|) (define-generic-java-field-modifier :current-input-port!)
syntax:
(define-generic-java-field-modifiers form ...)
where form is of the form scheme-name or (scheme-name java-name ) Creates bindings for several generic Java field modifiers.
The form expands into several define-generic-java-field-modifier forms.
(define-generic-java-field-modifier (:current-url! |currentURL|) :current-input-port!)
The mangling of field names allows fields to be identified more schemely. By convention field accessors should be named with a leading colon (:) followed by the field name, and field with a leading colon (: followed by the field name followed by an exclamation mark (!), e.g. :foo-bar and :foo-bar! are the names of the accessor and modifier for Java fields named fooBar. Mangling of field names upcases any character following a dash (-) and removes all characters that are not legal as part of Java identifiers.
Generic Java field accessors and modifiers are serializable by the SISC runtime.
Scheme code can instantiate Java classes with a call to the following procedure:
procedure:
(java-new jclass jobject ...) => jobject
Selects a constructor of jclass based on the types of the jobjects and calls it, returning the newly created object.
(define-class <java.util.linked-hash-set>) (java-new <java.util.linked-hash-set> (->jint 100)) ;=> <jobject>
There is a predicate for determining whether a value is a Java object:
procedure:
(java-object? value) => #t/#f
Returns #t if value is a Java object, #f otherwise.
Note that, unlike in Java, instances of primitive Java types are considered to be Java objects.
(define-class <java.util.linked-hash-set>) (define hs (java-new <java.util.linked-hash-set> (->jint 100))) (java-object? hs) ;=> #t (java-object? (->jint 100)) ;=> #t
Unlike in Java, null objects are typed. Typed null objects play a key role in invoking static methods an accessing / modifying static fields.
procedure:
(java-null jclass) => jnull
Returns a Java null object of type jclass.
(define-class <java.util.linked-hash-set>) (java-null <java.util.linked-hash-set>) ;=> <jnull>
There is a predicate for determining whether a value is a Java null. All nulls are also Java objects.
procedure:
(java-null? value) => #t/#f
Returns #t if value is a Java null object, #f otherwise.
(define-class <java.util.linked-hash-set>) (java-null? (java-null <java.util.linked-hash-set>)) ;=> #t
For convenience, jnull is bound to the typed null object obtained by calling java-null on java.lang.Object.
Any invocation of a Java method, or access to a Java fields that returns a Java null does so typed based on the declared return / field type.
Comparison of Java objects using eqv? compares the objects using Java's == comparison. equal?, on the other hand, compares the objects using Java's equals method. eq? uses pointer equality on the Scheme objects representing the Java objects and is therefore not generally useful. Applying eq?, eqv? or equal? to a mixture of Java objects and other Scheme values returns #f.
Java objects are only serializable by the SISC runtime if they support Java serialization. Java nulls are always serializable.
Scheme code can create Java arrays with a call to the following procedure:
procedure:
(java-array-new jclass size) => jarray
Creates an array of component type jclass with dimensions size, which can be a number (for a single-dimensional array), or a vector / list of numbers (for multi-dimensional arrays).
(java-array-new <jint> 2) ;=> <jarray> (define-java-class <java.lang.string>) (java-array-new <java.lang.string> '#(2 2 2)) ;=> <jarray>
There is a predicate for determining whether a value is a Java array. All Java arrays are also Java objects.
procedure:
(java-array? value) => #t/#f
Returns #t if value is a Java array, #f otherwise.
(java-array? (java-array-new <jint> 2)) ;=> #t
Elements of arrays are accessed and modified with:
procedure:
(java-array-ref jarray index) => jobject
Returns the element at index index of jarray. index can be a number, for indexing into the first dimension of the array, or vector / list of numbers for multi-dimensional indexing.
(define a (->jarray (map ->jint (iota 10)) <jint>)) (java-array-ref a 1) ;=> <java int 1> (java-array-ref a '(1)) ;=> <java int 1>
procedure:
(java-array-set! jarray index jobject)
Sets the element at index index of jarray to jobject. index can be a number, for indexing into the first dimension of the array, or vector / list of numbers for multi-dimensional indexing.
(define a (->jarray (map ->jint (iota 10)) <jint>)) (java-array-set! a 1 (->jint 2)) (java-array-ref a 1) ;=> <java int 2> (define a (java-array-new <jint> '#(2 2 2))) (java-array-set! a '#(1 1 1) (->jint 1))
The length of a Java array can be determined with
procedure:
(java-array-length jarray) => number
Returns the length of jarray.
(define a (->jarray (map ->jint (iota 10)) <jint>)) (java-array-length a) ;=> 10 (define a (java-array-new <jint> '#(2 3 4))) (java-array-length a) ;=> 2
Scheme vectors and lists can be converted to Java array and vice versa.
procedure:
(->list jarray) => list
Creates a list containing the elements of jarray.
(define a (->jarray (map ->jint (iota 5)) <jint>)) (map ->number (->list a)) ;=> '(0 1 2 3 4)
procedure:
(->vector jarray) => vector
Creates a vector containing the elements of jarray.
(define a (->jarray (map ->jint (iota 5)) <jint>)) (map ->number (vector->list (->vector a))) ;=> '(0 1 2 3 4)
procedure:
(->jarray list-or-vector jclass) => jarray
Creates a one-dimensional array of type jclass and fills it with the values obtained from the Scheme vector or list.
(define a (->jarray (map ->jint (iota 5)) <jint>))
Scheme code cannot create sub-classes of existing Java classes. It is, however, possible to create classes implementing existing Java interfaces. These classes are called proxies. Calling a method on a proxy invokes a user-definable Scheme procedure, based on the name of the method, passing the proxy object and the parameters of the method invocation as arguments. The result of the invocation is returned as the result of the method call.
syntax:
(define-java-proxy signature interfaces method ...)
where signature is of the form (name param ...), interfaces is of the form (interface ...), and method is of the form (define method-name procedure), or (define ( method-name method-arg ...) . body) Creates a proxy generator procedure and binds it to name. A proxy class is created that implements all the interfaces. When the generator is invoked, an instance of the proxy class is returned that delegates all method invocations to the Scheme procedures in the method definition list, based on the names of the methods.
The first kind of definition form defines procedure to be the method handler for the java method named method-name. method-name undergoes name mangling as described in the section called “Methods”. Note that procedure is inside the lexical scope of the generator procedure, so params are accessible inside it.
The second kind of definition form is equivalent to the following first-type form: (define method-name (lambda (method-arg ...) . body)).
If a method is invoked on a proxy for which no method handler exists and error is returned to the caller.
(define-java-classes <java.util.comparator> <java.util.arrays> <java.lang.object>) (define-java-proxy (comparator fn) (<java.util.comparator>) (define (.compare p obj1 obj2) (let ([x (java-unwrap obj1)] [y (java-unwrap obj2)]) (->jint (cond [(fn x y) -1] [(fn y x) +1] [else 0]))))) (define-generic-java-method sort) (define-java-class <java.lang.object>) (define (list-sort fn l) (let ([a (->jarray (map java-wrap l) <java.lang.object>)]) (sort (java-null <java.util.arrays>) a (comparator fn)) (map java-unwrap (->list a)))) (list-sort < '(3 4 2 1)) ;=> '(1 2 3 4) (list-sort string<? '("foo" "bar" "baz")) ;=> '("bar" "baz" "foo")
For convenience, all the primitive Java types, i.e. void, boolean, double, float, long, int, short, byte, char, are predefined and bound to <jvoid>, <jboolean>, <jdouble>, <jfloat>, <jlong>, <jint>, <jshort>, <jbyte>, <jchar>, respectively.
When calling Java methods, invoking Java constructors, accessing or modifying Java fields, no automatic conversion is performed between ordinary Scheme values and Java values. Instead explicit conversion of arguments and results is required. Automatic conversion is not performed for the following reasons:
For some Scheme types, such as numbers, the mapping to Java types is one-to-many, e.g. a Scheme number could be converted to a byte, short, int, etc. This causes ambiguities when automatic conversion of parameters is attempted.
Some Java types have several corresponding Scheme types, e.g. a Java array could be represented as Scheme list or vector - this causes ambiguities when automatic conversion of results is attempted.
Conversion carries an overhead that can be significant. For instance, Java strings have to be copied "by value" to Scheme strings since the former are immutable and the latter aren't. In a chained-call scenario, i.e. where the results of one method invocation are passed as arguments to another, the conversion is unnecessary and a wasted effort.
Conversion breaks the object identity relationship. In a chained-call scenario, the identities of the objects passed to the second call are different from the ones returned by the first. This causes problems if the called Java code relies on the object identity being preserved.
Conversion conflicts with generic procedures. The method selection mechanism employed by generic procedures relies on objects having exactly one type. Automatic conversion effectively gives objects more than one type - their original type and the type of the objects they can be converted to. While it would be technically possible to devise a method selection algorithm that accommodates this, the algorithm would impose a substantial overhead on generic procedure invocation and also make it significantly harder for users to predict which method will be selected when invoking a generic procedure with a particular set of arguments.
Conversion functions are provided for converting instances of primitive Java types to instances of standard Scheme types:
procedure:
(->boolean jboolean) => #t/#f
procedure:
(->character jchar) => character
procedure:
(->number jbyte/jshort/jint/jlong/jfloat/jdouble) => number
Conversion functions also exists for the opposite direction, i.e. converting instances of standard Scheme types to instances of primitive Java types
procedure:
(->jboolean boolean) => jboolean
procedure:
(->jchar character) => jchar
procedure:
(->jbyte number) => jbyte
procedure:
(->jshort number) => jshort
procedure:
(->jint number) => jint
procedure:
(->jlong number) => jlong
Finally, there are conversion functions for converting between Java strings and Scheme strings and symbols:
Scheme values are not Java objects and hence cannot be passed as arguments in Java method or constructor invocations or when setting Java fields. However, all Scheme values are internally represented by instances of classes in the SISC runtime. S2J provides a mechanism to get hold of this internal representation as an S2J Java object. The converse operation is also supported - a Java instance obtained via a Java method or constructor invocation or field access in S2J can be turned into a Scheme value if it is an instance of an appropriate SISC runtime class. These two operations are called "wrapping" and "unwrapping" respectively because conceptually the scheme object is wrapped to make it appear like a Java object and the wrapper is removed in order to recover the original Scheme object.
procedure:
(java-wrap value) => jobject
Returns the Java object that represents the Scheme value in SISC's runtime.
procedure:
(java-unwrap jobject) => value
Returns the Scheme value represented by the jobject. If jobject is not an object representing a Scheme value in SISC's runtime and error is thrown.
(define-java-class <java.lang.object>) (define a (java-array-new <java.lang.object> '#(1))) (java-array-set! a '#(0) (java-wrap 'foo)) (java-unwrap (java-array-ref a '#(0))) ;=> 'foo
Wrapping and unwrapping allows Scheme values to be used in generic (i.e. not type-specific) Java operations, such as those of the Java collection API. It is also frequently used in connection with proxies when Scheme objects are passed back and forth through layers of Java to a Scheme-implemented proxy that manipulates them. Finally, wrapping and unwrapping permit SISC Scheme code to interface to the SISC runtime.
In Java each object is a potential thread synchronization point. Therefore Scheme code needs to be able to synchronize on Java objects in order for it to interoperate properly with Java in a multi-threaded application. This is accomplished by the following procedure:
procedure:
(java-synchronized jobject thunk) => value
Runs thunk in a block synchronized on jobject, returning the result returned by thunk. This is the equivalent to synchronized (jobject) { return thunk(); } in Java.
It is illegal for thunk to invoke continuations that escape thunk, or for code outside thunk to invoke a continuation captured inside thunk.
(define-java-class <java.lang.object>) (define mtx (java-new <java.lang.object>)) (define v 0) (define (inc-v) (java-synchronized mtx (lambda () (set! v (+ v 1)) v))) (define (dec-v) (java-synchronized mtx (lambda () (set! v (- v 1)) v))) (import threading) (begin (parallel inc-v dec-v inc-v inc-v dec-v dec-v) v) ;=> 0
Java exceptions are propagated to scheme and can be caught like any other exception, e.g. with with/fc as defined in the section called “Failure Continuations”. The s2j module exports augmented versions of the print-stack-trace and print-exception functions that handle Java exceptions. For example
(define-generic-java-method char-at) (with/fc (lambda (m e) (print-exception (make-exception m e))) (lambda () (char-at (->jstring "foo") (->jint 3))))
will catch the IndexOutOfBoundsException, print its stack trace and return #f.
In Scheme, Java exceptions can be thrown by raising an error containing the Java exception as the message, e.g.
(define-java-class <java.util.no-such-element-exception>) (error (java-new <java.util.no-such-element-exception>))
or
(throw (make-error (java-new <java.util.no-such-element-exception>)))
If this occurs inside a proxy method (see the section called “Proxies”), the exception is propagated to the invoking Java code.
Invoking [define-]java-class[es], java-new or any of the procedures defined with [define-]generic-java-{method,field-accessor,field-modifier}[s] causes S2J to perform reflection on the named Java class(es), the class passed as the first argument, or the class corresponding to the type first argument passed to the other procedures, respectively. This process collects information about all the constructors, methods and fields of the class and its superclasses/interfaces.
The only class members processed during this automatic reflection are public ones declared in public classes. This almost exactly mimics the visibility rules in Java for code residing in packages other than the one the member is residing in. It is also in line with the default permissions granted to the Java reflection API. There is one rare case where this rule is more restrictive than Java's: public members of package-protected classes are not visible even when accessed via a public sub-class.
Depending on the security settings, the Java reflection API is in fact capable of granting access to any members of any class. However, using this in the automatic reflection performed by S2J would constitute a significant departure from normal Java behaviour and result in unpredictable results to the user. For instance, undocumented private methods would be invoked in preference to documented public methods if the formers type signature provided a better match.
Automatic reflection ignores security exceptions thrown by the Java reflection API, i.e. the class in question will appear to have no constructors, methods and fields. This is designed to cope with situations where the default security settings have been altered in a way that prevents access to members of some (or even all) classes.
In some applications the reflection API permissions depend on the context of the invocation. For instance, in applets it is usually possible to access class member information as part of the initialisation but not after that. Since [define-]java-class[es] triggers automatic reflection, it can be used to control when automatic reflection for specific classes takes place.
This section provides a summary of all the commonly used S2J features, correlating them with the corresponding Java code. It makes use of some functions from the srfi-1, srfi-26 and misc modules
(require-library 'sisc/libs/srfi) (import* srfi-1 fold) (import* srfi-26 cut cute) (import* misc compose)
Table 8.1. Common S2J Usage
Java | Scheme |
---|---|
create bindings for classes, methods and fields | |
n/a | (define-java-classes <foo.bar-baz> <foo.bar-boo>) (define-generic-java-methods get-bar get-baz set-bar! set-baz!) (define-generic-java-field-accessors :bar :baz) (define-generic-java-field-modifiers :bar! :baz!) |
instantiate class | |
foo.BarBaz fooObj = new foo.BarBaz(a, b, c); | (define foo-obj (java-new <foo.bar-baz> a b c)) |
invoke method on instance | |
Object res = fooObj.barBaz(a, b, c) | (define res (bar-baz foo-obj a b c)) |
access field | |
Object res = fooObj.bar; | (define res (:bar foo-obj)) |
modify field | |
fooObj.bar = val; | (:bar! foo-obj val) |
chained field access | |
Object res = fooObj.bar.baz.boo | (define res (fold (cut <> <>) foo-obj (list :bar :baz :boo)))or (define res ((compose :boo :baz :bar) foo-obj))This works equally well for bean fields. |
chained field modification | |
fooObj.bar.baz.boo = moo; | (:boo! (fold (cut <> <>) foo-obj (list :bar :baz)) moo)or (:boo! ((compose :baz :bar) foo-obj) moo)This works equally well for bean fields. |
accessing several fields | |
a = fooObj.bar; b = fooObj.baz; c = fooObj.boo; | (apply (lambda (a b c) ...) (map (cute <> foo-obj) (list :bar :baz :boo)))This works equally well for bean fields. |
modifying several fields | |
fooObj.bar = a; fooObj.baz = b; fooObj.boo = c; | (for-each (cute <> foo-obj <>) (list :bar! :baz! :boo!) (list a b c))This works equally well for bean fields. |
creating an array | |
int[][] ar = new int[2][2]; | (define ar (java-array-new <jint> '(2 2)))This works equally well for bean fields. |
accessing an array element | |
int res = ar[1][1]; | (define res (java-array-ref ar '(1 1))) |
modifying an array element | |
ar[1][1] = val; | (java-array-set! ar '(1 1) val) |
iterating over an array | |
for(int i=0; i<ar.length; i++) ar[i].fooBar(a,b); | (for-each (cute foo-bar <> a b) (->list ar)) |
implementing interfaces | |
public class Foo implements Bar, Baz { private int x; private int y; public Foo(int x, int y) { this.x = x; this.y = y; } public int barMethod(int z) { return x+y+z; } public int bazMethod(int z) { return x+y-z; } } ... Foo fooObj = new Foo(1, 2); | (define-java-proxy (foo x y) (<bar> <baz>) (define (bar-method p z) (->jint (+ x y (->number z)))) (define (baz-method p z) (->jint (+ x y (- (->number z)))))) ... (define foo-obj (foo 1 2)) |
Requires: (import s2j)
The S2J Reflection API lets Scheme code access all the core functions of the Java reflection API. It underpins the High Level S2J Interface (see the section called “High Level Scheme-to-Java Interface”). Normal interaction with Java from Scheme does not require knowledge of this API, just like normal use of Java does not require knowledge of the Java reflection API.
These functions access attributes and members of classes.
procedure:
(java-class-name jclass) => symbol
Returns the name of jclass.
procedure:
(java-class-flags jclass) => list of symbols
Returns the modifiers of jclass, for example public static final.
procedure:
(java-class-declaring-class jclass) => jclass
Returns the Java class in which jclass was declared, or null if it was declared at the top level.
procedure:
(java-class-declared-superclasses jclass) => list of jclass
Returns the direct superclasses of jclass.
For ordinary classes this is the class' superclass followed by all of its interfaces in the order they were specified in the class declaration. For primitive and array types the direct superclass or superclasses reflect the widening conversions performed by Java. For example, <jint>'s superclass is <jlong> and <java.util.array-list[][]> 's superclasses are:
<java.util.abstract-list[][]>
<java.util.list[][]>
<java.util.random-access[][]>
<java.lang.cloneable[][]>
<java.io.serializable[][]>
Note that this behavior is different from the corresponding method in the Java reflection API.
procedure:
(java-class-declared-classes jclass) => list of jclasses/#f
Returns all the classes declared by jclass, or #f if access to this information is prohibited.
procedure:
(java-class-declared-constructors jclass) => list of jconstructor/#f
Returns all the constructors declared by jclass, or #f if access to this information is prohibited.
procedure:
(java-class-declared-methods jclass) => list of jmethods/#f
Returns all the methods declared by jclass, or #f if access to this information is prohibited.
procedure:
(java-class-declared-fields jclass) => list of jfields/#f
Returns all the fields declared by jclass, or #f if access to this information is prohibited.
procedure:
(java-class-precedence-list jclass) => list of jclasses
Returns teh total order of jclass and all direct and indirect superclasses, as determined by the partial orders obtained from calling java-class-declared-superclasses.
The ordering of the classes returned by java-class-declared-superclasses is considered "weak", whereas the ordering of the class itself with respect to its direct superclasses is considered strong. The significance of this is that when computing the class precedence list weak orderings are re-arranged if that is the only way to obtain a total order. By contrast, strong orderings are never rearranged.
The class precedence list is important when comparing types using the type system's compare-types procedure, which is used by the generic procedure method selection algorithm (see the section called “Invoking Generic Procedures”). Since generic Java methods and field accessors/mutators are implemented in terms of generic procedures they are all affected by the class precedence list.
procedure:
(java-constructor? value) => #t/#f
Determines whether value is a Java constructor.
procedure:
(java-constructor-name jconstructor) => symbol
Returns the name of jconstructor.
procedure:
(java-constructor-flags jconstructor) => list of symbols
Returns the modifiers of jconstructor , such as public static final.
procedure:
(java-constructor-declaring-class jconstructor) => jclass
Returns the Java class in which jconstructor was declared.
procedure:
(java-constructor-parameter-types jconstructor) => list of jclasses
Returns the declared types of the parameters of jconstructor.
procedure:
(java-constructor-procedure jconstructor) => procedure
Returns a procedure that when called invokes the constructor with the passed arguments, returning the newly created objected.
procedure:
(java-constructor-method jconstructor) => method
Returns a method suitable for adding to generic procedures that, when called invokes the underlying Java constructor with the passed arguments. The resulting newly created object is returned.
procedure:
(java-method? value) => #t/#f
Determines whether value is a Java method.
procedure:
(java-method-name jmethod) => symbol
Returns the name of jmethod.
procedure:
(java-method-flags jmethod) => list of symbols
Returns the modifiers of jmethod, such as public static final.
procedure:
(java-method-declaring-class jmethod) => jclass
Returns the Java class in which jmethod was declared.
procedure:
(java-method-parameter-types jmethod) => list of jclasses
Returns the declared types of the parameters of jmethod.
procedure:
(java-method-procedure jmethod) => procedure
Returns a procedure that when called invokes the method with the passed arguments, returning the newly created objected.
procedure:
(java-method-method jmethod) => method
Returns a method suitable for adding to generic procedures that, when called invokes the underlying Java method on the object passed as the first argument, and with the remaining arguments passed as parameters. The result of the method invocation is returned. Static methods can be invoked by passing a typed null object as the first parameter to the generic procedure.
procedure:
(java-field? value) => #t/#f
Determines whether value is a Java field.
procedure:
(java-field-name jfield) => symbol
Returns the name of jfield.
procedure:
(java-field-flags jfield) => list of symbols
Returns the modifiers of jfield, such as public static final.
procedure:
(java-field-declaring-class jfield) => jclass
Returns the Java class in which jfield was declared.
procedure:
(java-field-type jfield) => jclass
Returns the declared type of jfield.
procedure:
(java-field-accessor-procedure jfield) => procedure
procedure:
(java-field-modifier-procedure jfield) => procedure
Returns a procedure that when called returns or sets (respectively) the value of the field on the object specified by the first parameter to the invocation. Static fields can be accessed/modified by passing a null object.
procedure:
(java-field-accessor-method jfield) => method
procedure:
(java-field-modifier-method jfield) => method
Returns a method suitable for adding to generic procedures that, when called returns/sets the value of the field on the object specified by the first argument to the generic procedure invocation. Static fields can be accessed/modified by passing a typed null object as the first parameter to the generic procedure.
procedure:
(java-array-class jclass dimensions) => jclass
Returns a class representing the array type that has jclass as the component type and dimensions as the number of dimensions. For example, the following expressions are equivalent:
(java-array-class <jint> 2) (java-class '|int[][]|)
The list of direct superclasses returned by java-class-declared-superclasses for an array class is consistent with the widening conversion performed by Java, e.g. the direct superclasses of java.util.ArrayList[][] are:
java.util.AbstractList[][]
java.util.List[][]
java.util.RandomAccess[][]
java.lang.Cloneable[][]
java.io.Serializable[][]
This is different from what the Java reflection APIs return.
procedure:
(java-proxy-class jinterface) => jclass
Creates a Java class that implements the specified interfaces. The class can be instantiated with an invocation handler, such as the one returned by java-proxy-dispatcher below, that delegates method invocation to Scheme code.
procedure:
(java-proxy-dispatcher alist) => invocation-handler
Creates an invocation handler suitable for use in the instantiation of a proxy (see java-proxy-class above). The keys in alist are Java method names and the values are Scheme procedures.
When a method is invoked on a proxy, the procedure matching the method's name is invoked with the proxy object and the parameters of the method invocation as arguments. The result of the invocation is returned as the result of the method call. If alist does not contain a binding for the method name, an error is signalled.