ClientObject Tutorial

Introduction

In BBj 2.0 and higher, programmers can embed Java to access the full functionality of the Java language from within a BBj program. So, for example, a program might use the following code to print the current time using embedded (server-side) objects.

Print Time and TimeZone of Server Machine

rem Print time and TimeZone of server machine

use java.util.Date
use java.text.DateFormat

serverDate! = new Date()
serverFormat! = DateFormat.getDateTimeInstance()

print serverFormat!.format(serverDate!)," ",
print serverFormat!.getTimeZone().getDisplayName()

In many configurations, the user display is on a different machine (the client machine) than that on which the BBjInterpreterServer is running (the server machine). Because these machines may be in different time zones, this small program may display results that are different from what the programmer intended. This code is executed within the Java Virtual Machine (JVM) of the server and so it will display the time and time zone of the server machine while the programmer may have wanted to display the local time on the client machine.

Prior to BBj 8.0, there has not been a mechanism by which a program could execute code within the JVM of the client. BBj 8.0 introduces syntax that provides an RMI (Remote Method Invocation) interface that allows the embedded Java code within a BBj program to access objects that reside in the client JVM rather than within the server JVM.

In order to access this RMI capability, append an @ symbol to a class name to indicate that the class will be accessed in the client JVM rather than in the server JVM as shown in the sample below using ClientObjects:

rem Print time and TimeZone of client machine

use java.util.Date
use java.text.DateFormat

clientDate! = new Date@()
clientFormat! = DateFormat@.getDateTimeInstance()

print clientFormat!.format(clientDate!)," ",
print clientFormat!.getTimeZone().getDisplayName()

When this code is executed it will use the time of the client machine to create a new Date Object in the client JVM and will use the locale of the client to create a DateFormat object in the client JVM. The result of running this program will be to display the time and TimeZone of the client machine rather than the time and TimeZone of the server.

When objects are created in the client JVM those objects are referred to as client-side objects. The server-side variables that reference those client-side objects are referred to as ClientObjects. So, for example, when line 40 is executed a client-side instance of Date is created and the variable remoteDate! is a ClientObject that represents an RMI handle to that client-side object. The ClientObject can be manipulated in the same way as a server-side object.

This tutorial provides a short example of how to use ClientObjects. If unfamiliar with the use of Java objects within a BBj program, read the following references about using server-side embedded Java before attempting to use ClientObjects:

Objects in BBj (PowerPoint)

Exponentially Better Applications: Embedded Java and BBj (HTML)

Syntax of ClientObjects

There are three ways in which the @ symbol is used in BBj to indicate that embedded Java is to use ClientObjects; when

  1. Creating a new ClientObject

  2. Invoking a static method on a client-side class

  3. Declaring a value to be a ClientObject

In each case, the @ is appended to the class name. The class name can be a fully qualified class name or it can be an abbreviated class name if the fully qualified class name has appeared in a USE statement.

The following sample shows the various uses of the @ character within a BBj program.

0010 use java.util.HashMap
0020 use java.util.Calendar

0030 REM declaring variables to have ClientObject types
0040 declare HashMap@ map!
0050 declare Calendar@ calendar!
0060 declare java.util.ArrayList@ list!
0070 declare java.text.DateFormat@ dateFormat!

0080 REM creating new instances of ClientObjects
0090 map! = new HashMap@()
0100 list! = new java.util.ArrayList@()

0110 REM invoking static methods on client-side classes
0120 calendar! = Calendar@.getInstance()

0130 dateFormat! = java.text.DateFormat@.getDateTimeInstance()

At lines 040-070 a number of variables are declared to be ClientObjects. At lines 090-110 two client-side objects are created and assigned to ClientObject variables. At lines 120-130, two static methods are called on client-side classes and the return values of those static methods are assigned to ClientObject variables.

Using a ClientObject

The methods of a ClientObject are called in the same way that the methods of other objects are called; by using the standard dot notation. In general, the parameters that are passed when calling a method on a ClientObject must themselves be ClientObjects. If a method accepts a string or a number then it will accept a server-side value (see the ClientObject vs. Server-side Objects section for details).

The return value of a method called on a ClientObject is again generally a ClientObject unless that return value is a number or a string. If the return value is a number or a string then the return value will be a server-side value.

This code sample calls methods on ClientObjects:

use java.util.HashMap
use java.util.Calendar

declare HashMap@ map!
declare Calendar@ calendar!

map! = new HashMap@()
calendar! = Calendar@.getInstance()
print calendar!
print
print calendar!.getTime()
print

map!.put("calendar",calendar!)
map!.put(123,456)

print map!.get("calendar")
print
print map!.get(123)

Running the code sample results in output similar to the following.


[CLIENTOBJECT] @139f593
java.util.GregorianCalendar[time=1178801482531,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo
[id="America/Denver",offset=-25200000,dstSavings=3600000,useDaylight=true,transitions=157,lastRule=java.util.SimpleTimeZone
[id=America/Denver,offset=25200000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1
, startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1, 
minimalDaysInFirstWeek=1,ERA=1,YEAR=2007,MONTH=4,WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=10,DAY_OF_YEAR=130,DAY_OF_WEEK=5, 
DAY_OF_WEEK_IN_MONTH=2,AM_PM=0, HOUR=6,HOUR_OF_DAY=6,MINUTE=51,SECOND=22,MILLISECOND=531,ZONE_OFFSET=-5200000,DST_OFFSET=3600000]

[CLIENTOBJECT] @1976073
Thu May 10 06:51:22 MDT 2007

[CLIENTOBJECT] @139f593
java.util.GregorianCalendar[time=1178801482531,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo 
[id="America/Denver",offset=-25200000,dstSavings=3600000,useDaylight=true,transitions=157,lastRule=java.util.SimpleTimeZone 
[id=America/Denver,offset=25200000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1
, startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1, 
minimalDaysInFirstWeek=1,ERA=1,YEAR=2007,MONTH=4,WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=10,DAY_OF_YEAR=130,DAY_OF_WEEK=5, 
DAY_OF_WEEK_IN_MONTH=2,AM_PM=0,HOUR=6,HOUR_OF_DAY=6,MINUTE=51,SECOND=22,MILLISECOND=531,ZONE_OFFSET=-5200000,DST_OFFSET=3600000]

456

The toString() value of a ClientObject consists of the string "[CLIENTOBJECT]" followed by an ID followed by the toString() value of the client-side object.

Notice that looking at the output above, calendar! is a ClientObject and that the return value of calendar!.getTime() is also a ClientObject. If we place calendar! into a client-side HashMap and then retrieve it, we will receive the same ClientObject. And if we place the server-side number 456 into the client-side HashMap and then retrieve it, we will receive the server-side number.

The field values of a client-side object can also be accessed through the ClientObject and the static fields of a client-side class can be accessed by addressing that class using the @ symbol as shown below.

use java.awt.Color
declare Color@ blue!
blue! = Color@.BLUE
print blue!

ClientObject vs. Server-side Objects

It is important to understand the difference between a ClientObject and a server-side Object. Any Object that was created using the '@' notation is a ClientObject. Any Object that was created without the '@' notation is a server-side Object. BBjAPI is a server-side Object. Any Object that is obtained through calls on BBjAPI is a server-side Object. In particular, all BBjControls are server-side Objects even though they 'represent' GUI Objects that exist on the client.

In general, the parameters passed to a ClientObject must be ClientObjects while the parameters passed to a server-side object must be server-side objects.

The exceptions to this general rule are:

  1. Strings and Numbers are always server-side values. Strings and Numbers may be passed as parameters to methods of ClientObjects.

  2. The method BBjWindow::addWrappedJComponent (which is a method call on a server-side object) accepts as a parameter a ClientObject.

  3. A BBjControl (which is a server-side object) can be passed as a parameter to a ClientObject if that ClientObject represents a client-side swing component.

  4. A BBj CustomObject (which is server-side object) can be registered as an event listener on a ClientObject that represents a swing component

These exceptions allow a program to place objects that extend javax.awt.JComponent@ (ie any client-side JComponent) onto a BBjWindow as well as to place BBjControls onto JComponents and to respond to events that occur on JComponents. All these use cases are demonstrated in the following sections.

Similarly, the return value of a method invocation on a ClientObject is itself a ClientObject and the return value of a method invocation on a server-side Object is a server-side Object except that:

  1. Strings and Numbers are always server-side values.

  2. BBjBarChart, BBjLineChart, and BBjPieChart (which are server-side Objects) each have a method getClientChart() which returns a ClientObject.

Placing a JComponent into a BBjWindow

BBj 8.0 provides a new BBjWindow::addWrappedJComponent method that allows the program to create a BBjControl that contains a client-side Java Component. In order to place a client-side JComponent onto a BBjWindow, the program first creates a ClientObject that represents the JComponent and then 'wraps' the Object in a BBjWrappedJComponent by calling addWrappedJComponent.

jbutton.png

The following code creates a JButton with a thick green border (shown above) and places it onto a BBjWindow.

use javax.swing.border.LineBorder
use java.awt.Color
use javax.swing.JButton

REM declare server side variables
declare BBjSysGui sysGui!
declare BBjWindow window!
declare BBjControl BBjControl!

REM declare ClientObject variables
declare Color@ green!
declare LineBorder@ border!
declare JButton@ JButton!

REM create a window
sysGui! = BBjapi().openSysGui("X0")
window! = sysGui!.addWindow(100,100,140,100,"")

REM create a ClientObject that represents a
REM client side JButton with a green border
green! = Color@.GREEN
border! = new LineBorder@(green!,10)
JButton! = new JButton@()
JButton!.setBorder(border!)

REM add ClientObject as a wrapped component on the window
BBjControl! = window!.addWrappedJComponent(101,50,50,100,60,JButton!)

REM manipulate BBjControl! as a BBjControl
BBjControl!.setText("JButton")
BBjControl!.setLocation(20,20)

escape

Registering a BBj Callback as a Listener on a JComponent

A BBj CustomObject may be registered as an event listener on a ClientObject by calling the add<some>Listener method of the ClientObject.

The CustomObject must have methods that 'correspond' to the methods of <some>Listener. Each of the corresponding methods of the CustomObject must have the same name as the method of the <some>Listener and must accept ClientObjects where the <some>Listener accepts events.

So, for example, the following code sample registers a CustomObject named Listener as a PropertyChangeListener on a JSplitPane. A PropertyChangeListener has a single method

void PropertyChange(PropertyChangeEvent event)

so the CustomObject, Listener, must have a corresponding method

void PropertyChange(PropertyChangeEvent@event)

with the same name but accepting a PropertyChangeEvent@ rather than a PropertyChangeEvent.

This sample adds a JSplitPane to a BBjWindow and registers a listener:

use java.beans.PropertyChangeEvent
use javax.swing.JPanel
use javax.swing.JSplitPane
use java.awt.Dimension

declare BBjSysGui sysgui!
declare BBjWindow window!
declare JSplitPane@ splitPane!
declare JPanel@ panel!
declare Listener listener!

REM create a window
width = 500
height = 400
open (unt)"X0"
sysgui! = BBjapi().getSysGui()
window! = sysgui!.addWindow(100,100,width,height,"JSplitPane")

REM add a JPanel as a wrappedComponent
panel! = new JPanel@()
window!.addWrappedJComponent(2222,0,0,width,height,panel!)

REM create a JSplitPane and place it into the JPanel
splitPane! = new JSplitPane@()
dimension! = new Dimension@(width, height)
splitPane!.setPreferredSize(dimension!)
splitPane!.setContinuousLayout(1)
orientation = JSplitPane.HORIZONTAL_SPLIT
splitPane!.setOrientation(orientation)
panel!.add(splitPane!)
panel!.validate()

REM create a CustomObject and register it as a
REM PropertyChangeListener on the JSplitPane
listener! = new Listener()
splitPane!.addPropertyChangeListener(listener!)

window!.setCallback(window!.ON_CLOSE,"eoj")

process_events

eoj:
release

class public Listener
  method public void propertyChange(PropertyChangeEvent@ p_event!)
    print p_event!.getPropertyName(),
    print " = ",
    print p_event!.getNewValue()
  methodend
classend

The screen that this code sample generates appears as:

jsplit_output.png

Its console output appears as:

jsplit_console-output.png

Mixing ClientObjects and BBjControls to Provide a BBjWindow with a LayoutManager

We are now in a position to build a program that provides an interesting mix of JComponents and BBjContols. The following sample provides a BBjWindow that contains a JSplitPane. The left side of the JSplitPane contains both BBjControls and JComponents. In addition, the left panel has a layout manager so that all the controls (both the JComponents and the BBjControls) change sizes as the separator of the JSplitPane is moved.

This code sample shows a mix of JComponents and BBjControls with a LayoutManager:

use java.beans.PropertyChangeEvent
use javax.swing.JPanel
use javax.swing.JSplitPane
use javax.swing.JFormattedTextField
use java.awt.Dimension
use java.awt.GridLayout
use java.awt.event.FocusEvent

declare BBjSysGui sysgui!
declare BBjWindow window!
declare BBjButton button!
declare JSplitPane@ splitPane!
declare JPanel@ panel!
declare Listener listener!

REM open sysgui and add a window
sysgui! = BBjapi().openSysGui("X0")
window! = sysgui!.addWindow(100,100,500,400,"BBjWindow",$00010003$)
panel! = new JPanel@()

REM create some BBjControls
inputn! = window!.addInputN(102,10,10,90,30)
inputd! = window!.addInputD(103,10,10,90,30)
navigator! = window!.addNavigator(104,10,10,90,30,"BBjNavigator")
button! = window!.addButton(1,10,10,90,30,"BBjButton")

gosub gettysburg
cedit! = window!.addCEdit(106,10,10,200,300,gettysburg$,$0002$)

REM add a wrapped JPanel to the BBjWindow and
REM place a JSplitPane into that JPanel
window!.addWrappedJComponent(2222,0,0,500,400,panel!)
splitPane! = new JSplitPane@()
dimension! = new Dimension@(window!.getWidth(),window!.getHeight())
splitPane!.setPreferredSize(dimension!)
splitPane!.setContinuousLayout(1)
splitPane!.setOrientation(JSplitPane@.HORIZONTAL_SPLIT)

REM create a JPanel that will become the left side panel
REM give it a GridLayout
layout! = new GridLayout@(6,1)
leftPanel! = new JPanel@(layout!)

REM add a JComponent to the left panel
jEdit! = new JFormattedTextField@()
jEdit!.setText("JTextField 220")
jEdit!.setBounds(10,10,90,30)
jEdit!.setVisible(1)
leftPanel!.add(jEdit!)

REM place most of the BBjComponents into the left panel
leftPanel!.add(inputn!)
leftPanel!.add(inputd!)
leftPanel!.add(navigator!)
leftPanel!.add(button!)

REM place the remaining BBJComponent into the panels
splitPane!.add(leftPanel!,JSplitPane@.LEFT)
splitPane!.add(cedit!,JSplitPane@.RIGHT)

REM place the JSplitPane into the wrapped JPanel
REM and validate the wrapped JPanel
panel!.add(splitPane!)
panel!.validate()

REM create a listener. Register it to listen for
REM PropertyChangeEvents on the splitPane and for
REM FocusEvents on the jEdit
listener! = new Listener()
splitPane!.addPropertyChangeListener(listener!)
jEdit!.addFocusListener(listener!)

REM set some BBj callbacks
window!.setCallback(window!.ON_CLOSE,"eoj")
window!.setCallback(window!.ON_RESIZE,"resize")
button!.setCallback(button!.ON_BUTTON_PUSH,listener!,"buttonPush")

process_events

eoj:
release

resize:
  event! = sysgui!.getLastEvent()
  dimension! = new Dimension@(event!.getWidth(), event!.getHeight())
  splitPane!.setPreferredSize(dimension!)
return

class public Listener

  method public void propertyChange( PropertyChangeEvent@ p_event!)
    target! = p_event!.getSource()
    print p_event!.getPropertyName()," = ",
    print p_event!.getNewValue()
  methodend

  method public void buttonPush(BBjButtonPushEvent event!)
    print "button pushed"
  methodend

  method public void focusGained(FocusEvent@ p_event!)
    print "GainedFocus: ",
    print p_event!.getSource().getClass()
  methodend

  method public void focusLost(FocusEvent@ p_event!)
    print "LostFocus: ",
    print p_event!.getSource().getClass()
  methodend

classend

gettysburg:
  gettysburg$ = "Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal."+
: $0a$+$0a$+ "Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. "+
:            "We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this."+$0a$+$0a$+ 
:            "But, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. "+
:            "The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced."+
:            "It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- "+
:            "that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth."
:            +$0a$
return

This sample generates this screen:

layoutmgr_output.png

If the user moved the separator, the screen might look like this:

layoutmgr_output-moved-separator.png

In addition to managing the Layout of the left panel, this sample also reports FocusGain and FocusLoss whenever the JTextField gains or loses focus and reports PropertyChangeEvents whenever the user moves the separator of the JSplitPane.

Requirements for Class files Used to Create ClientObjects

In order to create a ClientObject the Java Class file that defines the client-side Object must be visible to the server as well as the client.

In addition, if the Java Class file is not found in a 'privileged' jar file, then BBj will generate a 'nag' message unless the server is running with a DVK license. A jar file is privileged if it is part of the JVM Java Runtime Environment or if it is a 'registered' jar.

The charts.jar file shipped with BBj is a registered jar file. Developers can register their own jar files using Java's static method com.basis.jarRegistrationService.client.JarRegistrar.registerJar.

For example, register a Jar within a BBj program as follows:

use com.basis.jarRegistrationService.client.JarRegistrar
inputJar$ = "someUnregisteredJar.jar"
outputJar$ = "registered.jar"
JarRegistrar.registerJar(inputJar$,outputJar$)

For a full description of JarRegistration, refer to the JarRegistrar Class.

Once a jar file has been registered, it can also be signed without invalidating the BBj registration. However, any change to the files within the jar will invalidate the registration.