This page contains a complete, although brief, description of DJBinder Basic version. You can also visit the DJBinder Quick Guide to develop your first dynamic application on the spot. This page is divided in the following chapters :
Overview
In Java the only way to create a link between a class and an interface is to use the implements
statement. For example the following line :
class Person implements Employee {...}
indicates that any Person object offers all the services defined in the Employee interface. The methods of this interface must be implemented within the definition of the Person class.
Assume that later on you want to add the notion of citizenship and therefore you decide to add a new Citizen interface to the same Person class :
class Person implements Employee, Citizen {...}
This way of doing is OK but it has three problems :
- You need the right to access and modify the source file of the Person class.
- You have to handle several versions of the Person class. Specially if there are some persistent objects.
- You have to deliver the new class file to all the sites where your application is executed.
These problems are mainly due to the modification of the original source file to add the new interface. This modification could be avoid by using the Java inheritance mechanism : instead of adding the Citizen interface directly to the Person class you could add it to a derived class.
class Person2 extends Person implements Citizen {...}
The inheritance mechanism solves the problems mentioned above but generates some new ones :
- You have to change the statements
new Person(...);
by new Person2(...);
, at least for the objects that need to behave like citizens. This modification can touch several source files.
- Other classes that already derive from Person do not take advantage of the new citizenship services.
DJBinder solves these problems in an elegant and innovative way : It allows to creates runtime links between interfaces and classes. In other words it is possible to attach interface implementations to a class without recompiling it. DJBinder magic resides in the fact that interfaces implemented by special abstract classes can dynamically be associated to a different class at runtime. DJBinder achieves this within the strictest respect of Java syntax by using a naming convention. The naming convention groups classes into clusters. Each cluster has a main class that is the only class able to create objects, plus other abstract classes used to implement additional interfaces. Interfaces implemented by these classes are considered for almost all-practical effects, such as cast and instanceof operations, as directly implemented by the main class of the cluster.
Continuing the Person example, you can create the following class to implement the Citizen interface :
public abstract class DI_Person__Citizen implements Citizen {...}
If this class is available in the runtime environment then the following two expressions would initialize the c variable with a value different of null
.
Person p = new Person("John", "Smith", 23) ;
Citizen c = (p instanceof Citizen ? (Citizen) p : null) ;
From the view point of the classes using the Citizen interface there is not difference between an interface dynamically implemented and an interface implemented directly by the Person class. Furthermore, existing classes do not have to be recompiled to take advantage of new dynamically implemented interfaces.
The chapter about dynamic interface implementations explains how DI_Person__Citizen can implement the Citizen interface for instances of the Person class. The most important fact is that the source file of the Person class does not have to be modified.
DJBinder is not a new dialect of Java. It is fully compatible with the Java 2 platform. It works with the Java standard tools and it does not require special pre-compilers, compilers, virtual machines nor development kits. DJBinder specifies a naming convention just like the JavaBeans specifies the name of the property accessor methods, or the Java serialization mechanism specifies that a class must implement a Serializable marker interface to be serialized.
Dynamic interface implementations
An interface is dynamically implemented when it is not directly implemented by the class used to create the objects. For example, objects created by the following statement :
new Person ("John", "Smith", 23) ;
support the interfaces implemented by the Person class, but with DJBinder they also support the interfaces implemented by other classes. These classes are said to belong to the same cluster and must respect the following naming convention :
- They must belong to the same package (the chapter about Sealed Packages explains an exception to this rule).
- The base name is divided in 4 sections :
- DI_
- the base name of the class that creates the objects, also called main class. In this case Person.
- __
- the base name of the interface being dynamically implemented.
A last condition is that these classes must be public and abstract. In a cluster only the main class can be used in a new
statement.
For example, if Citizen, ComponentListener and FocusListener are interfaces, then the following classes are valid dynamic implementations for the Person class :
public abstract class DI_Person__Citizen implements Citizen {...}
public abstract class DI_Person__ComponentListerner implements java.awt.event.ComponentListener {...}
public abstract class DI_Person__FocusListerner implements java.awt.event.FocusListener {...}
If two interfaces have the same base name but belong to different packages they have to be dynamically implemented by the same DI_* class. This risk of collision is not considered important enough to justify a more complex naming convention.
DJBinder allows a sort of multiple-inheritance without the problems traditionally associated with this mechanism. Each DI_* class can derive from a different super class, in other words each dynamic implementation can take advantage of other implementations already developed. For example if Person class dynamically implements ComponentListener and FocusListener then the corresponding DI_* classes can derived from ComponentAdapter and FocusAdapter :
import java.awt.event.* ;
public abstract class DI_Person__ComponentListerner extends ComponentAdapter implements ComponentListener {...}
public abstract class DI_Person__FocusListerner extends FocusAdapter implements FocusListener {...}
Although DI_* classes are abstract to concretize the fact that they can not be used in a new
statement, they are automatically instantiated by DJBinder when needed. Particularly when an object is casted into the corresponding interface. For example the following code will create an instance of DI_Person__Citizen :
Person p = new Person ("John", "Smith", 23) ;
Citizen c = (Citizen) p ;
After creating the DI_Person__Citizen instance DJBinder calls the void init(Object)
method which can be used to initialize the member fields. Normally the init
method, like other methods of the DI_* class, needs to access the members of the main object. This can be done by casting the this
reference, for more details see the chapter about Accessing members of the main class.
DJBinder warranties that every time that an object is casted into a given interface, the same instance of the corresponding DI_* class is used. For example, the following code will print "Casted into citizen" only twice, once for object p1 and another time for object p2.
abstract class DI_Person__Citizen implements Citizen
{
public void init (Object reserved)
{
System.out.println ("Casted into citizen") ;
}
// ...
}
// ...
public class test
{
public static void main (String[] args)
{
Citizen c1, c11, c2, c21 ;
Person p1 = new Person ("John", "Smith", 23) ;
c1 = (Citizen) p1 ; // prints "Casted into citizen"
c11 = (Citizen) p1 ;
Person p2 = new Person ("Bill", "Lennon", 15) ;
c2 = (Citizen) p2 ; // prints "Casted into citizen"
c21 = (Citizen) p2 ;
}
}
Java operations : casting, instanceof, object equality
Previous chapters have explained how DJBinder allows to cast an object of type T into an interface I, even if the T class does not directly implement the I interface. The only condition is to have a class named DI_T__I present in the runtime environment.
This is a powerful mechanism but it would be incomplete without the corresponding instanceof
statement. Therefore DJBinder warranties that if the expression :
(I) p
does not throw an exception, then the statement :
(p instanceof I)
returns true
. And the other way around if (I)p
throws an exception then (p instanceof I)
returns false
.
Concerning object equality in Java it is expected that a reference to an object is equal to another reference to the same object independently of the reference type. For example, the following expression should always be true
(if the casts are valid) :
(I1) p == (I2) p
DJBinder warranties this behavior even when one, or both, of the references correspond to DI_* instances.
In short, the use of dynamic interface implementations does not break the consistency between the cast operation, the instanceof
operation and the objects equality.
Accessing members of the main class
When an interface is dynamically implemented its methods are defined in a class different of the class used to create the object. Sometimes these methods need to access the members of the main class of the objet. The this
reference can be cast into the main class of the object to access the public and package members.
For example the dynamic implementation of Citizen interface can access the public getAge() method of the Person class in the following way :
public abstract class DI_Person__Citizen implements Citizen
{
private boolean canVote_ = false ;
public void init(Object reserved)
{
int age = ((Person)(Object)this).getAge() ;
canVote_ = (age >= 18 ? true : false) ;
}
// ...
}
Java does not accept to cast this
directly into Person, that is why a middle cast into Object is used.
By default DJBinder does not allow to access protected and private members of the main class, but there is a way to declare that some protected and private members can be accessed from DI_* classes. The declaration is done within special DA_* classes (DA stands for DynamicAuthorization). Members of a T class become accessible from all DI_T__* classes if they are declared as public members of the DA_T class.
DA_* classes should be considered as a new visibility scope, they allow to declare a member as accessible from all dynamic interface implementations of a class, just like the public
, protected
and private
keywords are used to declare that a member is accessible or not from another class.
For example if the Person class has a private salary_ field and a protected setSalary method then it is possible to declare these members as accessible from all DI_Person__* classes using the following DA_Person class definition :
public class Person
{
//...
private int salary_ ;
protected void setSalary(int newSalary) {...}
//...
}
// ...
abstract class DA_Person
{
public int salary_ ;
public abstract void setSalary(int s) ;
}
Methods of a DA_* class should be declared as abstract and the name and type of the members must be exactly the same one declared in the main class. The only possible difference is the visibility modifier.
The this
reference must be casted into DA_Person to access these members from the DI_Person__Citizen class. For example the method calculateTax of the Citizen interface could be defined in the following way :
public abstract class DI_Person__Citizen
{
// ...
private float taxRate_ ;
public int calculateTax ()
{
return ((DA_Person)(Object)this).salary_ * taxRate_ ;
}
// ...
}
Another possible way is to use a variable of type DA_Person:
public abstract class DI_Person__Citizen
{
// ...
private float taxRate_ ;
private DA_Person daThis_ ;
public void init (Object reserved)
{
daThis_ = (DA_Person)(Object)this ;
}
public int calculateTax ()
{
return daThis_.salary_ * taxRate_ ;
}
// ...
}
It is important to highlight the fact that the members actually accessed are the ones of the Person class, even if the type of the reference is DA_Person. In this aspect, as in some others, dynamic implementations are similar to inner classes. The greatest difference being that inner classes have to be defined in the same source file as the main class.
DJBinder warranties that DA_Person references are used only within DI_Person__* classes. A runtime exception is thrown if they are used somewhere else.
Serialization and Cloning
In the DJBinder Basic version the serialization and cloning of dynamic interface implementations is not fully supported. If an object is serialized, only the fields of its main class are included in the resulting stream. When the object will be rebuilt from the data contained in the stream, the dynamic interface implementations will not be automatically recreated. They are going to be created from scratch at each cast operation. The init
method will be re-executed and the previous value of the member fields will be lost.
An object that has dynamic interface implementations cannot be cloned. An exception is thrown if the clone()
method is called for an object with dynamic interface implementations. The reason is that DJBinder does not make the difference between the DI_* instances of the original object and the DI_* instances of the new object and therefore some wear behavior could happen if the cloning were allowed.
These two Java functionalities will be fully supported in the DJBinder Professional version that will be available in the second semester 2000.
Running applications
To activate DJBinder when running a Java application you must execute the amslib.djbinder.Start class with the following arguments :
- main class of the application
- first argument of the application
- second argument of the application
- other arguments ...
For example if demo.Smallest is a Java application that prints the smallest number among a series of 4 numbers then it can be run with the following command line (under Windows):
c:\java\myclasses> c:\jdk1.2\bin\java.exe amslib.djbinder.Start demo.Smallest 13 45 26 8
This example uses the JDK1.2 of SUN, but the mechanism is the same with any other Java virtual machine.
You must grant all the security authorizations to the djbinder.jar file or install it as an extension of the Java virtual machine. To install the djbinder.jar file as an extension you have to copy it to a special directory :
under unix: {JRE_DIRECTORY}/lib/ext
under Windows: {JRE_DIRECTORY}\lib\ext
You can find more information about installing extensions in the following documents of java.sun.com : The Java Extensions Mechanism and its tutorial.
Running applets
Follow this check list to run an applet using DJBinder :
- Be sure that your browser or applet runner uses a virtual machine compatible with the JDK1.2. If that is not the case you can install the Plug-In functionality of java.sun.com, that warranties that you always have the most recent version of the JDK. See JavaTM Plug-in for more information about the Plug-In functionality.
- In the HTML page run the amslib.djbinder.AppletStart applet with the following parameter :
<param name="APPLET_NAME" value="yourAppletName">
- Also include all the parameters needed by your applet.
- Place the djbinder.jar file in a directory accessible during the applet execution. A trivial solution is to place it in the same directory as your applet.
- Use the ARCHIVE parameter to indicate the URL of the djbinder.jar file.
For example to run the DrawTest applet furnished as a demo with the standard JDK1.2 of java.sun.com you can use the following HTML page. This page was generated by the Plug-In converter tool :
<html>
<head>
<title>Draw Test (1.1)</title>
</head>
<body>
<h1>Draw Test (1.1)</h1>
<hr>
<!--"CONVERTED_APPLET"-->
<!-- CONVERTER VERSION 1.0 -->
<OBJECT classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" WIDTH = 400 HEIGHT = 400 codebase="http://java.sun.com/products/plugin/1.2/jinstall-12-win32.cab#Version=1,2,0,0">
<PARAM NAME = CODE VALUE = AppletStart.class >
<PARAM NAME = ARCHIVE VALUE = "djbinder.jar" >
<PARAM NAME="type" VALUE="application/x-java-applet;version=1.2">
<PARAM NAME = "APPLET_NAME" VALUE ="DrawTest">
<COMMENT>
<EMBED type="application/x-java-applet;version=1.2" java_CODE = AppletStart.class java_ARCHIVE = "djbinder.jar" WIDTH = 400 HEIGHT = 400 APPLET_NAME = "DrawTest" pluginspage="http://java.sun.com/products/plugin/1.2/plugin-install.html"><NOEMBED></COMMENT>
alt="Your browser understands the <APPLET> tag but isn't running the applet, for some reason."
Your browser is completely ignoring the <APPLET> tag!
</NOEMBED></EMBED>
</OBJECT>
<!--
<APPLET CODE = AppletStart.class ARCHIVE = "djbinder.jar" WIDTH = 400 HEIGHT = 400 >
<PARAM NAME = "APPLET_NAME" VALUE ="DrawTest">
alt="Your browser understands the <APPLET> tag but isn't running the applet, for some reason."
Your browser is completely ignoring the <APPLET> tag!
</APPLET>
-->
<!--"END_CONVERTED_APPLET"-->
<hr>
</body>
</html>
This demo applet does not use any dynamic interface implementation but if you run this HTML page you would verify that DJBinder does not affect the normal execution of existing Java programs.
You can also use this HTML page to run your own applets, you only need to change DrawTest by the name of your applet and optionally add other parameters.
Application life cycle
There are basically two kind of modifications done to a program after it has been delivered :
- Corrective modifications. They are done to correct some problems found during the productive life of the program. They are usually quite common and expensive at the beginning of the program life, but fortunately the number fells down after the main problems have been corrected.
- Functional enhancements. They are done because new needs are discovered or because the environment has changed and the program needs to communicate with other entities or using different formats.
A big problem about functional enhancements is that they can touch some parts of the program where corrective modifications have became rare. A well known fact about programming is that each new line added to a program has a high probability to generate a new problem. Therefore, an enhancement functionality can generate some new problems.
DJBinder approach is to use dynamically implemented interfaces for the functional enhancements. In this way very few existing source files have to be modified and the risk to generate new problems is reduced. In other words functional enhancements are done by the creation of new files instead of taking the risk of modifying existing ones.
This approach is resumed by : "Write Once, Run Forever", which means that delivered source files should be modified the least as possible. Excepting of course for the necessary corrective modifications.
top of this page