The org.bzdev.lang package
For specific topics, please seeThe Callable interface
Starting with the initial version of Java (1.0), Java has provided an interface namedRunnable
with a single method named
run()
. This is used to provide the code that a thread will
run. The earliest version of the BZDev class library (not publicly
released) included a corresponding interface named Callable
with a zero argument method named call()
. The interface
Callable
is a synonym for Runnable
in terms
of functionality, but was added for two reasons:
- to document that one was providing a method to be called, not the code that a thread to run.
- so that one could discriminate between code that should be run in
a thread from code that could be called. In particular, the
org.bzdev.devqsim package allows both to be scheduled and needed to
be able to tell the difference between the two cases at compile time.
Similarly, the org.bzdev.scripting package contains a class that uses
the
Callable
interface.
Callable
. While one would prefer that the names were
different, the chances of a name conflict are small: the
discrete-event simulation classes runs only one thread at a time, and it
is not advisable to use multithreading with scripting unless one can
ensure that only a single thread runs at any particular time.
The Callable
interface provided by this class library does not
take any arguments and does not return a value. There are, however, several
related interfaces:
CallableArgs<Args>
. This interface provides a method namedcall
that does not return a value and that takes any number of arguments of typeArgs
.- CallableArgsReturns<T,Args>. This interface provides a method
named
call
that returns a value of type T and that takes any number of arguments of typeArgs
. - CallableReturns<T>. This interface provides a method
named
call
that This interface provides a method namedcall
that returns a value of type T and that does not take any arguments. ExceptionedCallable
. interface provided by this class library does not take any arguments and does not return a value. It may throw an exception (any subclass of java.lang.Exception).ExceptionedCallableArgs<Args>
. This interface provides a method namedcall
that does not return a value and that takes any number of arguments of typeArgs
. It may throw an exception (any subclass of java.lang.Exception).- ExceptionedCallableArgsReturns<T,Args>. This interface
provides a method named
call
that returns a value of type T and that takes any number of arguments of typeArgs
. It may throw an exception (any subclass of java.lang.Exception). - ExceptionedCallableReturns<T>. This interface provides a
method named
call
that returns a value of type T and that does not take any arguments. It may throw an exception (any subclass of java.lang.Exception).
Library loading
The class ResourceLibLoader supports looking up and loading a resource containing a shared library. A naming convention is used to allow the correct resource to be obtained for a given version of the operating system and a given machine architecture.
Dynamic Methods
Normally Java looks up an object's method at run time but bases the lookup on the method's signature (specifically the types of the method's arguments), which is determined at compile time. While this restriction is desirable for performance reasons, it is sometimes easier to develop software if dynamic methods are supported, in which the argument types are determined at run time. The org.bzdev.lang package supports dynamic methods by using annotations (staring with release 6 of the JDK, annotations are supported by the Java compiler). The mechanism is efficient, but with two restrictions: dynamic methods cannot be static methods and the use of generic types is not supported. The primary reason for not allowing generic types is that type-erasure is not compatible with the dispatch methods provided by "helper" classes.
Dynamic methods are supported by 6 annotation types (only 3 or 4 are needed for the simplest cases). The necessary types are
- DynamicMethod. This annotation tags a method specified in a class or an interface as a dynamic method, proving the most general type for the methods (which can have return values and throw exceptions as desired). It takes the class name of a "helper" class as its argument (the helper class will be generated automatically). A dynamic method may not be a static method.
- DMethodImpl. This annotation tags a method as being one that implements a dynamic method for specific argument types, and takes the name of the helper class used in the DynamicMethod annotation as an argument (the class name serves as a key to match dynamic method implementations with the corresponding dynamic method). These methods may be declared to be static.
- DMethodContext or DMethodContexts. Each class that implements a dynamic method needs a "local helper" class for that dynamic method. A DMethodContext annotates the class that implements a dynamic method and, using the dynamic method's helper class as a key, provides the corresponding local helper class. If a class implements multiple dynamic methods, The DMethodContexts method should be used, which takes an array of DMethodContext annotations as its argument. A class annotated with DMethodContext or DMethodContexts must be declared to be static if the class is an inner class.
The two remaining annotations provide additional control:
- DMethodOptions allows one to control the locking used by the helper class, to allow optimizations for single-threaded programs, to adjust cache sizes, and to enable tracing of method searches. The locking-mode values allow one to specify no locking, a mutex lock, a read-write lock, or a system default (normally a mutex but this can be changed at compile time).
- DMethodOrder is used when dynamic methods are dispatched based on multiple arguments. It controls the search order for arguments and determines which arguments should use compile-time types rather than run-time types.
The implementation of a dynamic method should call its helper's dispatch
method, returning a value of the return type is not void. The first argument
to dispatch
must be this
, followed by the arguments
declared in the method. Each class that implements a dynamic method has
a DMethodContext annotation, specifying a local helper, for that method,
and must call the local helper's register
method in a static
initializer, calling register
with no arguments. If the
system class loader is set to org.bzdev.lang.DMClassLoader, the
register
method will be called automatically. One can change
the system class loader to DMClassLoader by setting the following option
for the java command:
java -Djava.system.class.loader=org.bzdev.lang.DMClassLoader ...
For hints on how to use the compiler and what to include in JAR
files, see the documentation for DynamicMethod
.
An example for the use of a dynamic method when the dynamic method is specified in a class is the following (note: when referring to a helper in a different package, the fully qualified class name must be used):
public class Foo {
@DynamicMethod("FooIncrHelper")
public Number incr(Number x) throws Exception {
return FooIncrHelper.getHelper().dispatch(this, x);
}
}
@DMethodContext(helper="FooIncrHelper", localHelper="BarIncrHelper")
public class Bar extends Foo {
static {
BarIncrHelper.register();
}
@DMethodImpl("FooIncrHelper")
public Number doIncr(Integer x) thows Exception {
return Integer.valueOf(x + 1);
}
}
The call to dispatch will throw a runtime exception named
MethodNotPresentException>
if no method that matches the arguments is found. This exception can be
caught to implement the default behavior for the dispatch method and is
located in the org.bzdev.lang package.
*
If incr
was declared in an interface, an implementation must
appear in the least specific class(es) that implement that interface.
For example:
public interface Incrementer {
@DynamicMethod("FooIncrementerHelper")
public Number incr(Number x) throws Exception;
}
@DMethodContext(helper="FooIncrementerHelper",
localHelper="FooIncrHelper")
public class Foo implements Incrementer {
static {
FooIncrHelper.register();
}
public Number incr(Number x) {
return FooIncrementerHelper.getHelper().dispatch(this, x);
}
@DMethodImpl("IncrementerHelper")
public Number doIncr(Integer x) thows Exception {
return Integer.valueOf(x + 1);
}
}
@DMethodContext(helper="IncrementerHelper", localHelper="BarIncrHelper")
public class Bar extends Foo {
static {
BarIncrHelper.register();
}
@DMethodImpl("IncrementerHelper")
public Number doIncr(Bar x) thows Exception {
return Integer.valueOf(x + 1);
}
}
For compilation, see the instructions in the BZDev library description.
Background
Several other approaches have been used to create similar capabilities.
One approach that is used when dynamic methods are not supported
in a language is to use the visitor design pattern. The idea is to define
two interfaces such as Visitor
and Visitable
defined as follows:
interface Visitor {
void visit(Foo obj);
void visit(Bar obj);
}
interface Visitable {
void accept(Visitor arg);
}
then Foo
is defined as follows:
class Foo implements Visitable {
void accept(Visitor arg) {
arg.visit(this);
}
}
class Bar extends Foo {
void accept(Visitor arg) {
arg.visit(this);
}
}
class SomeVisitor implements Visitor {
void visit(Foo obj) {
...
}
void visit(Bar obj) {
...
}
}
For mimicking dynamic methods, the accept
method is
written as given above: it merely calls the visit method with an
argument of this
, whose type matches the type of the
class in which the accept
method is defined. The
accept
method is defined in all subclasses as well
(subclasses of Foo
in this example), so that the type
at run time is known in the call to visit
. When
accept
is called on an object of type Foo, even if
referenced by a variable declared to be Visitable
, the
accept
method that is called is the one defined for
Foo
. The accept
method of
Foo
then calls its argument's visit
method, passing it an argument of type Foo
.
The downside to the use of this design pattern is that every time a
new class that implements
An alternative approach, which generates byte codes at runtime and
loads those into the Java virtual machine, has also been proposed
(Rémi Fora, Etienne Duris, and Gilles Roussel,
"Reflection-based implementation of Java extensions: the
double-dispatch use-case"). This latter approach is far more
efficient (and comparable to the approach described below) but may
fail when a sandbox security model is in use due to restrictions on
generating new classes at run time (e.g., in applets and assuming no
changes to the Java language). Security managers, however, have been
removed from recent versions of Java.
The
A subclass of Foo that implements the dynamic method
If class Foo also provided a method annotated with
The look-up can be based on more than one
argument, and the default behavior is to do nothing, which
eliminates the coding required when using the visitor design
pattern when a change to the
For the single argument case, the overhead over a trivial implementation
(a couple of method calls, and an
The equivalent of the visitor design pattern example above can be
implemented using dynamic methods. For example:
The example above
assumes the use of a dynamic-method-aware class loader such as
In the example above, if all classes for which
For additional information about dynamic methods, read the documentation
for
Visitable
is defined, one
must add a method to the Visitor
interface and all
Visitor
classes must then implement that new method,
even if there is really nothing for the method to do. In addition,
as mentioned above, an accept method must be defined in all classes
that implement Visitable and their subclasses, which is tedious
when there are a number of subclasses. In addition, the interface
and class definitions are typically in separate files. Attempts to
mitigate these limitations have included the use of the Java
reflection API
(Jens Palsberg and C. Barry Jay, "The Essence of the Visitor
Pattern"); however, the run-time cost is substantial: what would
take 1.17 seconds using the visitor design pattern took 4 minutes,
59.93 seconds using the reflection API, an increase in running time
of about a factor of 300 for trivial methods.
DynamicMethod
annotation and some related
annotations provides a design pattern that addresses some of these
issues by allowing method look-up to be performed more efficiently
at run time, with all the application code created at compile time
so that there are no issues with security managers. A simple
design pattern must be followed. One declares a dynamic method
using a DynamicMethod
annotation: for example,
This annotation declares that a generated class named FooIncrHelper
will provide a method called dispatch that implements a dynamic
method. The arguments for dispatch in the helper class are
public class Foo {
@DynamicMethod("FooIncrHelper")
public Number incr(Number x) throws Exception {
return FooIncrHelper.dispatch(this, x);
}
}
this
to indicate the current instance, followed by the
arguments for the method incr
. If the DynamicMethod
annotation appeared in an interface declaration, then a class
implementing the method and calling the helper's dispatch method
must appear in the first subclass of Object implementing the
interface.
incr
will
then use a DMethodImpl annotation to tag a method implementing the dynamic
method for particular argument types (the dynamic method implementing incr
will have a different name). The subclass itself will be annotated with a
DMethodContext annotation, which provides the name of a subclass-specific
helper class (the localHelper attribute) and associates it with the helper
for the dynamic method incr. For example,
The static method
@DMethodContext(helper="FooIncrHelper", localHelper="BarIncrHelper")
public class Bar extends Foo {
static {
BarIncrHelper.register();
}
@DMethodImpl("FooIncrHelper")
public Number doIncr(Integer x) throws Exception {
return Integer.valueOf(x + 1);
}
}
register()
provided by the local-helper
class must be called when a class is initialized and when the annotation
@DMethodContext
is used. With earlier versions
of Java, a class loader provided by the org.bzdev.lang package could
call this method so that an explicit call would not be needed,but
unfortunately that class loader is not work when Java modules are
used.
@DMethodImpl("FooIncrHelper")
, Foo itself would have to
have a DMethodContext annotation with its helper attribute set to
FooIncrHelper
and its localHelper attribute set to some
other class name. When multiple @DMethodContext annotations are
needed for a class, the annotation @DMethodContexts should be
used - @DMethodContexts has a single 'value' defined - an
array of @DMethodContext. The design decision to annotate the
class with the local helper data rather than the individual method was
due in part for supporting the class loader
DMClassLoader
, which removes the
need for the static initializers that call register() methods of
the local helper classes.
Visitor
interface is
made. The order in which argument types are searched can be controlled
with a DMethodOrder annotation, applied to method definition or
declaration annotated by the DynamicMethod annotation.
In addition, One may use a throws
clause in the method
definitions and declarations.
if
statement using the
instanceof
operator) is roughly a factor of 15, which can
be reduced to a factor of 10 by setting an option to turn off locking
for cases in which objects using a specific class are accessed by only
one thread at a time. This assumes the method called performs a trivial
operation - the overhead will not be noticeable if the method called
does anything substantial. The implementation uses "helper"
classes to dispatch method calls at run-time. These classes are
automatically generated by the java compiler via an annotation processor.
For the two-argument case, the overhead is approximately factor of 34
with locking and 29 without locking (in both cases, the running time is
higher than in the single-argument case).
The
interface Visitor {
@DynamicMethod("VisitorHelper")
void visit(Object arg);
}
@DMethodContext(helper = "VistorHelper",
localHelper = "SomeVisitorHelper")
class SomeVisitor implements Visitor {
static {
SomeVisitorHelper.register();
}
void visit(Object arg) {
VisitorHelper.getHelper().dispatch(this, arg);
}
@DMethodImpl("VisitorHelper")
void doVisit1(Foo arg) {
...
}
@DMethodImpl("VisitorHelper")
void doVisit2(Bar arg) {
...
}
}
Visitable
interface is not needed, and explicit
methods can be added only for those argument classes (Foo
and Bar
in this example) for which some action is needed.
The Visitor
interface does not have to be modified, and
the visit
method is not defined in subclasses of
SomeVisitor
. The method doVisit(Foo)
does not have to be defined in subclasses of SomeVisitor
unless it has to be coded differently in the subclass.
If a default action is needed, one can add a declaration such as
that will match any object passed as an argument. Alternatively, one
may change the implementation of the
@DMethodImpl("VisitorHelper")
void doVisit(Object arg) {
return; // do nothing.
}
visit
method to
catch an exception that indicates that the dispatch mechanism could
not find a method to invoke:
It is better in this case to define the
void visit(Object arg) {
try {
VisitorHelper.getHelper().dispatch(this, arg);
} catch (MethodNotPresentException e) {
}
}
doVisit
method
with an argument of type Object
, as one of the
doVisit
methods may call a different dynamic method
that throws MethodNotPresentException
due to a
programming error, in which case the partial execution of some
methods may put the application into an illegal state.
org.bzdev.lang.DMClassLoader
. If
such a class loader is not used, then a static initializer has to be
added to the SomeVisitor class:
The call to the register() simply forces the SomeVisitorHelper class
to be initialized: during initialization, SomeVisitorHelper will
register itself with VisitorHelper to create tables that allow the
correct method to be looked up at run time. The general rule is that
whenever a class is annotated with a @DMethodContext or
@DMethodContexts annotation, for each localHelper attribute
provided, there must be call to each helper's static method register()
in the class' initializer.
@DMethodContext(helper = "VistorHelper",
localHelper = "SomeVisitorHelper")
class SomeVisitor implements Visitor {
static {
SomeVisitorHelper.register();
}
...
}
visit
can be called are subclasses of SomeVisitor
, then
the interface definition is not needed and the code can be written
as follows:
Subclasses can implement
@DMethodContext(helper = "VistorHelper",
localHelper = "SomeVisitorHelper")
class SomeVisitor {
@DynamicMethod("VisitorHelper")
void visit(Object arg) {
VisitorHelper.getHelper().dispatch(this, arg);
}
@DMethodImpl("VisitorHelper")
void doVisit(Foo arg) {
...
}
@DMethodImpl("VisitorHelper")
void doVisit(Bar arg) {
...
}
}
doVisit
methods, annotated
with @DMethodImpl("VisitorHelper")
, and
subclasses that implement this method must of course have a
@DMethodContext
annotation for the subclass with
a unique localHelper
class name. For a similar example
where a register() is called explicitly, one might define the
interface (the complete file is shown)
package dmtest;
import org.bzdev.lang.annotations.*;
public interface Visitor {
@DynamicMethod("VisitorHelper")
void visit(Object arg);
}
The output when the program is run is
package dmtest.a;
import org.bzdev.lang.annotations.*;
@DMethodContext(helper="VisitorHelper", localHelper="SomeVisitorHelper")
public class SomeVisitor implements Visitor {
static {
SomeVisitorHelper.register();
}
public void visit(Object arg) {
VisitorHelper.getHelper().dispatch(this, arg);
}
@DMethodImpl("VisitorHelper")
void doVisit1(Double arg) {
System.out.println("double = " + arg.toString());
}
@DMethodImpl("VisitorHelper")
void doVisit2(Integer arg) {
System.out.println("integer = " + arg.toString());
}
public static void main(String argv[]) {
Object obj1 = Double.valueOf(10.0);
Object obj2 = Integer.valueOf(20);
SomeVisitor sv = new SomeVisitor();
sv.visit(obj1);
sv.visit(obj2);
}
}
By contrast, the following program will fail at compile time:
double = 10.0
integer = 20
Without dynamic methods, one would have to add a method such as
package dmtest.a;
import org.bzdev.lang.annotations.*;
public class SomeVisitor2 {
public void visit(Double arg) {
System.out.println("double = " + arg.toString());
}
public void visit(Integer arg) {
System.out.println("integer = " + arg.toString());
}
public static void main(String argv[]) {
Object obj1 = Double.valueOf(10.0);
Object obj2 = Integer.valueOf(20);
SomeVisitor sv = new SomeVisitor();
sv.visit(obj1);
sv.visit(obj2);
}
}
which is error prone due to the need to make a change in multiple
places.
public void visit(Object arg) {
if (arg instanceof Double) visit((Double) arg);
else if (arg instanceof Integer) visit((Integer) arg);
}
Miscellaneous classes
Finally, there are several miscellaneous classes:
Other classes specify permissions that the user may wish to grant.
ClassFinder
. This class provides static methods that
can recover information about a class given its name.
MathOps
. This class provides static methods that compute
floors or round numbers towards 0 or ±∞.