Copyright © 2000-2012, Agent Oriented Software Pty. Ltd.

JACK Intelligent Agents® JACOB Manual

Release 5.5
2 September 2010


Table of Contents

Publisher Information

 Agent Oriented Software Pty. Ltd.  
 P.O. Box 639,  
 Carlton South, Victoria, 3053  
 AUSTRALIA  
   
Phone: +61 3 9349 5055
Fax: +61 3 9349 5088
Web:  http://www.agent-software.com

If you find any errors in this document or would like to suggest improvements, please let us know.

C++ Platforms

The JACOB libraries to interface with C++ applications are not delivered as part of the JACK product and must be purchased separately. The C++ libraries are available precompiled for the following list of platforms. Other platforms can be made available on request.

Operating System Compiled With
Linux g++ 2.95
Linux g++ 3.0
Windows 9x VC++ 6.0 or greater (static/dynamic/multi-threaded)
Windows NT/2000/XP VC++ 6.0 or greater (static/dynamic/multi-threaded)

JACOB C++ libraries

The JACK™ documentation set includes the following manuals and practicals:

Document Description
Agent Manual Describes the JACK programming language and infrastructure. JACK can be used to develop applications involving BDI agents.
Teams Manual Describes the JACK Teams programming language extensions. JACK Teams can be used to develop applications that involve coordinated activity among teams of agents.
Development Environment Manual Describes how to use the JACK Development Environment (JDE). The JDE is a graphical development environment that can be used to develop JACK agent and team-based applications.
JACOB Manual Describes how to use JACOB. JACOB is an object modelling language that can be used for inter-process transport and object initialisation.
WebBot Manual Describes how to use the JACK WebBot to develop JACK enabled web applications.
Design Tool Manual Describes how to use the Design Tool to design and build an application within the JACK Development Environment.
Graphical Plan Editor Manual Describes how to use the Graphical Plan Editor to develop graphical plans within the JACK Development Environment.
JACK Sim Manual Describes how to use the JACK Sim framework for building and running repeatable agent simulations.
Tracing and Logging Manual Describes the tracing and logging tools available with JACK.
Agent Practicals A set of practicals designed to introduce the basic concepts involved in JACK programming.
Teams Practicals A set of practicals designed to introduce the basic concepts involved in Teams programming.


1 Introduction

The JACOB™ Object Modeller (JACOB) is a system providing machine and language independent object structures that can be stored or transmitted.

JACOB provides distributed objects in a similar way to CORBA, COM or Java object serialisation but the emphasis is on simplicity, portability, efficiency and low overheads.

Language specific code can be generated from JACOB object structures, allowing code in the target language to create, manipulate, send and receive JACOB objects easily.

JACOB consists of four major components:

These components are explained in the Functional Components of JACOB chapter of this document.

1.1 Uses of JACOB

JACOB is useful in situations that require data structures that are machine and language independent. Two important uses of JACOB are:

1.1.1 Object Initialisation

JACOB can be used to initialise objects within an application. JACOB-generated code together with user written code allows applications to read and/or write objects to and from a data file or stream.

An example of object initialisation can be found in the Sample Applications chapter.

1.1.2 Object Communication

JACOB may be used to allow communication between different processes. JACOB-generated code together with user written code allows populated objects to be sent and received between different processes.

For example, JACOB may be used to transport data between a Java class and a C++ class, or a JACK Agent and a C++ class. Populated objects are sent and received between the processes via a socket. JACOB code is inserted into each class to allow objects to be sent and/or received.

Figure 1-1: Communication between a JACK Agent and a C++ process using JACOB

An example of transporting objects can be found in the Sample Applications section below.

1.2 Dictionary Files

JACOB object structures that are stored and transported contain objects that are defined in dictionary files, using the JACOB Data Definition Language. A dictionary file defines the structure and legal fields of one or more objects, and usually has an .api extension.


Note: A dictionary associated with a data file may consist of a one or more dictionary files.

1.3 Compiling Dictionaries

Once one or more objects are defined in a dictionary file, the dictionary file is compiled using JACOB Build. JACOB Build can generate either Java or C++ code (Java code is generated by default). Once code is generated, the resulting classes can then be compiled and used for object initialisation and transportation.

1.4 Building Object Structures

JACOB object structures may be created using objects defined in a dictionary, and stored in a data file. Object structures can be created and edited by opening a data file in the JACOB Object Browser, which provides a graphical environment, or a text editor.

JACOB object structures may be used to store objects in applications. The types of objects allowed in an application are defined in a dictionary. The objects used in the application may be stored in one or more data files, which can be edited in the JACOB Object Browser.

1.5 JACOB File Formats

Data files containing JACOB object structures may currently be in one of four formats:

Data files may be converted to another format using the Convert option in JACOB Build. Note that currently only ASCII and Binary formats can be used with C++ code.

JACOB dictionary files can be compiled to generate Java or C++ code. Code in other languages may be generated if a new driver is defined for that language.

1.6 The JACOB Object Browser

The JACOB Object Browser provides a graphical environment which allows users to view or edit JACOB object structures contained in data files. Data files that contain objects defined according to a dictionary file may be created, edited and saved. Objects can be edited by opening a data file and loading the associated dictionary file. New objects, object copies and object references may be added to a data file. The JACOB Object Browser may also be used to delete objects from a data file.

Figure 1-2: The JACOB Object Browser Window

1.7 Using JACOB

To use JACOB to initialise and/or transport data structures, the user must first define the objects in the structure in one or more dictionary files. Objects and their fields are defined in dictionary files using the JACOB Data Definition Language.

After defining objects in one or more dictionary files, the dictionary files must be compiled with JACOB Build.

1.7.1 JACOB Objects

JACOB dictionary files contain definitions of objects and their fields. The contents of a data file or data stream may only include objects of the types that are defined in one or more dictionary files.

An example object MyData is defined below.

    <Class :name "MyData"
        :fields (
            <Field :name "objectID" :type :int>
            <Field :name "objectName" :type :string>
            <Field :name "description" :type :string>
        )
    >

A corresponding MyData object in a data file would be :

    <MyData
        :objectID 12
        :objectName "ExampleObject"
        :description "An example of a MyData object"
    >

1.7.2 Using JACOB to transport objects

Figure 1-3: Development steps for JACOB object transportation

To use JACOB for communication between processes, JACOB-generated code together with user written code must be used to transport JACOB objects. A dictionary written in the JACOB Data Definition Language is compiled into source code for transportable classes using JACOB Build. Either Java or C++ source code may be generated. The source code is then compiled into classes with a standard Java or C++ compiler.

The transportable classes generated are able to read and write object structures to and from streams. Additional code must be written by the user to provide and manage streams for transporting objects. The streams used may be files or sockets, or a JDBC connection (for Java only). The streams may also be any other kind of byte stream interprocess communication infrastructure.

To send objects, code written by the user must be included in either existing or new classes. The code must decide which stream to use, and then use it to send objects.

Code written by the user for receiving objects must also be included in existing or new classes. The code for receiving JACOB objects must decide which dictionary to use (a dictionary may include definitions from multiple dictionary files). The code must also construct and initialise the chosen dictionary. Once a dictionary has been chosen, the code must decide which stream and associate it with the dictionary. The stream may then be used to receive objects.

Once the classes containing the user-written sender and receiver code have been compiled, they can be used to send and receive objects.


2 Packages

Each JACOB component is installed automatically during the installation of JACK. The JACOB components can be found in the following locations:

Component Location
Runtime aos.apib.*
Compiler aos.main.JacobBuild
Browser aos.main.Jacob

Table 2-1: JACOB Packages


3 JACOB Object Browser

3.1 Introduction

The general form of the command to start the browser is

 java aos.main.Jacob [[<data-file>]* [-t <dict-file>]*]*

where <data-file> is a data file or a collection of data files, and <dict-file> is a dictionary or multiple dictionaries associated with the preceding data file(s). If the command ends with one or more data files, the last collection of data files will be associated with the preceding dictionary set (if any). The JACOB Object Browser may also be started with multiple groups of data file collections with associated dictionaries.

Note that both <data-file> and <dict-file> are optional. The browser can also be started as

  java aos.main.Jacob

3.2 The Browser Environment

Objects defined in the JACOB Object Modelling language can be viewed and modified using the JACOB Object Browser. Each object structure consists of two parts:

3.2.1 Menu Bar

The JACOB editor has a menu bar at the top of the work area. The following menus are available from the menu bar.

3.2.1.1 File Menu

Use this menu to perform operations on the current project.

Figure 3-1: The JACOB Object Browser File Menu

3.2.1.2 Option Menu

Use this menu to change JACOB Editor options.

Figure 3-2: The JACOB Object Browser Option Menu

When this options is on, a list of multiple objects with unique identity numbers is written. A list of multiple objects that is read or written may have references between the objects. Note that this option is not supported for binary files.


Note: The best way to write multiple objects is to wrap them in a parent container.

3.2.1.3 Help Menu

Use this menu to access the About JACOB screen.

Figure 3-3: The JACOB Object Browser Help Menu

3.2.2 Work Area

Objects and their contents are manipulated in the work area of the JACOB browser. The work area is depicted below.

Figure 3-4: The JACOB Object Browser Work Area

The work area consists of two panes: the Object Tree pane and the Object Field pane. These panes are explained below.


Note: The divider between each of the panes can be moved back and forth as required to resize the panes.

3.2.2.1 Object Tree Pane

The left pane in the main work area is the Object Tree pane. This pane is depicted below.

Figure 3-5: The Object Tree Pane

This is where new object files appear when loaded or created. Once a dictionary is added to a data file, the browser in the Object Tree pane can be used to navigate the object structure.

3.2.2.2 Object Field Pane

The right pane in the main work area is the Object Field pane. This pane is depicted below.

Figure 3-6: The Object Field Pane

This is where the fields that an object contains are displayed and manipulated. These can include text and numeric fields as well as pointers to other objects.

The arrow in the top left-hand corner is used to go up a level in an object structure.

3.2.3 Context-sensitive Menus

Context-sensitive menus are available extensively throughout the editor environment. These menus are invoked by moving the pointer over an item and clicking the right mouse button. This presents options that are relevant to the current object or work space in the current context.


Note: On some platforms there is no right button on the mouse. On these platforms, context-sensitive menus are invoked by pressing a key and the mouse button in combination.

The context-sensitive menus introduce some new functionality and duplicate some options available in the File menu.

3.3 Using the Browser

3.3.1 Creating Data Files

Selecting New from the file menu creates a new Data File which appears in the Object Tree pane. Initially this file is unstructured and empty.

Adding a dictionary to an empty file such as this will allow new objects to be declared.

3.3.2 Loading Data Files

Data files can be loaded by selecting Open from the file menu. These files appear in the Object Tree pane, but their structure is not known so they cannot be browsed or edited until a dictionary is added to them.

When loading data files, make sure that the file name filter in the dialog is set to All Files so that data files are visible.

3.3.3 Adding a Dictionary to a Data File

Invoking the context-sensitive menu for a Data File presents the menu below.

Figure 3-7: Add Dictionary in a data file context-sensitive menu

In order to populate a Data File one or more dictionary (.api) files must be attached to it. This is achieved by selecting Add Dictionary from the above menu and loading the appropriate file from disk.

This dictionary defines what objects and fields exist in each data file, and describes how they are structured. Once an appropriate dictionary has been added to a data file, its structure and contents can be viewed in the browser.

Once a set of dictionary files has been attached to a Data File, the set cannot be edited. The only way to change a set of associated dictionary files is to close the Data File, open it again and then add dictionary files.


Note: The dictionary is applied only for the current session and must be added to the data file each time it is loaded.

3.3.4 Adding Objects to Data Files

A sub-menu named Add Top-Level Object is available in the context-sensitive menu for a Data File. An example of this menu is depicted below.

Figure 3-8: Add Top-Level Object in a data file context-sensitive menu


Note: This option is also available in the context-sensitive menu for top-level objects.

This menu displays all of the top-level objects that are declared in the current dictionary file in alphabetical order. Selecting any of these objects adds a new object of that type to the current data file.

Additionally, either a reference to or a copy of the currently selected object can be added to the data file by selecting Add Reference or Add Copy (respectively).

Objects can contain fields and object pointers, which are references to other entities. Object pointers are initially null, but can be made to point to newly created objects.

In the example above, if Address is selected, an object of that type would be added to the current data file. The Address object has three fields; Name, Email and Phone. It also has a pointer to a Comments object which can be added optionally to include extra information about a person.

See the section entitled Replacing Object Pointers for details on how to make object pointers non-null.

3.3.5 Adding Data to Objects

Once an object has been added to a data file, its fields can be modified. In the example above, an Address object is added to the data file, which would result in the following appearing in the Object Field pane:

Figure 3-9: An Address object in the Object Field pane

Any changes made to the above fields are mirrored in the Object Tree pane if the dictionary file declares these fields as visible there.

Clicking on the Comments object pointer will display any comments pertaining to this address book entry. Clicking the arrow in the top left-hand corner of the Object Field pane will return to the previous fields view.

3.3.6 Replacing Object Pointers

If an object pointer is null, it must to be replaced with a reference to an object before data can be added to it. This can be achieved by invoking the context-sensitive menu on the object pointer and selecting Refer to New. This menu is depicted below.

Figure 3-10: Refer to New in an object pointer context-sensitive menu

This menu displays objects of the correct type that can be added in place of the current value.

In the example in Figure 2.10, the Comments object pointer can be set to point to a newly created Comments object. Once created, the contents of this object can be changed and saved to disk like any other field.

In addition to this, an object pointer can be replaced with a pointer to the object currently selected in the Object Tree pane by selecting Refer to Selection. This would allow, for example, the object pointer for two different Address entries to point to the same Comments object. Further, an object pointer can be replaced with a pointer to a copy of the currently selected object by selecting Refer to Copy.

3.3.7 Removing Objects

To remove an object pointer, invoke the context-sensitive menu on an object pointer and select Remove Object. This menu is depicted below.

Figure 3-11: Remove Object in an object pointer context-sensitive menu

This deletes the reference to that object and makes the object pointer null.

3.3.8 Aggregate Objects

An aggregate object is a 'container' into which other objects can be placed. Aggregate objects can be empty, or they can contain one or more objects.

Figure 3-12: An unexpanded aggregate object

An 'empty' aggregate object does not have an expansion control next to it; in the image above, 'directives' and 'implements' are empty aggregate objects.

To put something into an aggregate object, open the contextual menu for the aggregate object.

Figure 3-13: Insert New in an aggregate context-sensitive menu

With the Insert New submenu, the user can put in a new object of any type the aggregate can hold. Alternatively, you can choose Insert Reference or Insert Copy to put either a reference to or a copy of the currently selected object into the aggregate object.

The user can expand an aggregate object to see its contents. Each object contained in an aggregate object behaves just as if it were a top-level object. The user can also use the contextual menu for an object contained in an aggregate object to add further objects to the aggregate.

Figure 3-14: Add New in an object context-sensitive menu

The Add New, Add Reference and Add Copy menu items behave much the same as the Insert New, Insert Reference and Insert Copy menu items in the contextual menu for the aggregate object itself. However, whereas the Insert items add an object at the top of the aggregate's list, the Add items place the newly added object immediately after the object from whose contextual menu the item was chosen. For example, in the image above, if you choose Add New and Field, the new field will be placed after the hard_lab field already in the aggregate.

Items can be deleted from aggregate objects by choosing Delete from the item's contextual menu. You cannot, however, delete the aggregate objects themselves (except by deleting the top-level object of which they are a part).

3.3.9 Deleting Top-Level Objects

To delete a top-level object, invoke the context-sensitive menu on an object and select Delete. This menu is depicted below.

Figure 3-15: Delete in an object context-sensitive menu

This causes the selected object to be deleted from the current Data File.

3.3.10 Reverting to Saved Data Files

If all changes from an editing session need to be discarded, then invoke the context-sensitive menu on a Data File in the Object Tree pane and select Re-Open. This menu is depicted below.

Figure 3-16: Re-Open in a data file context-sensitive menu

This option allows the version of the data file most recently saved to disk to be loaded into the editor. A dialog appears to confirm that this action is what was intended.


Note: If OK is clicked, all changes are discarded and the original file is reloaded.

3.3.11 Saving Data Files

Selecting Save from the file menu or the context-sensitive menu on a Data File, causes the current data file to be saved to disk. If the data file was newly created, the user is given the opportunity to change the default name before saving the file to disk.

The editor automatically creates backup files by copying the original file to a file with a .bak extension. The current file is then written to disk.

3.3.12 Closing Data Files

Selecting Close from the context-sensitive menu on a Data File closes the current data file. The user is given the option to save any changes before closing the file.

3.3.13 Exiting the Editor

Selecting Exit from the File menu causes the JACOB editor to quit. The user is given the option to save any changes before exiting the editor.


4 JACOB Object Modelling

4.1 JACOB Data Definition Language

This section describes the JACOB Data Definition Language in Extended Backus-Naur Form (BNF) and describes the purpose of each of the fields and keywords.

4.1.1 Syntax Definition

In this syntax definition, non-terminal tokens are written as words within angle brackets, like <Object>, and square and curly brackets mark repeated (zero or more) and optional (zero or one) parts respectively. Terminal tokens are enclosed by single-quotes, and a vertical bar separates alternatives.

The JACOB Data Definition Language has a syntax as defined below.

<Stream> ::= [ <Object> ] <EOF>

<Object> ::= '<' <Ident> { '=' <Number> } { <Object> } [ <Field> ] '>' | '<' '&' <Number> '>'

<Field> ::= <Fieldname> <Value>

<Value> ::= <Number> | <String> | <Object> | <Aggregation>

<Aggregation> ::= '(' [ <Object> ] ')'

The lexical level defines <Ident>, <Fieldname>, <Number>, <String> and <EOF> as follows:

There may also be Java-style comments interspersed with definitions in the usual way, i.e. each comment is treated as a token separator.

All string values to be used as identifiers in generated code must adhere to the appropriate identifier syntax of the target language, and must not be any of its reserved words.

A JACOB Data Definition Language file is itself a sequence of objects in the language described above. There are six different types of objects to use:

4.1.1.1 Class

Objects of type Class are definitions of new classes. The fields of such objects are:

4.1.1.2 Field

Objects of type Field are used for defining the fields of a class. The fields of a Field object are:

4.1.1.3 Enum

Objects of type Enum are definitions of enumeration types. The fields of such objects are:

4.1.1.4 Member

Objects of type Member are used for defining the members of an enumeration. The fields of a Member object are:

4.1.1.5 Code

Objects of type Code are portions of target language source code to be copied to the output 'in place'. The fields of a Code object are:

A Code object is a Directive object.

4.1.1.6 Include

Objects of type Include declare files containing portions of target language source code to be copied to the output (usually) 'in place'. The fields of an Include object are:

A Include object is a Directive object.


5 Running JACOB

JACOB is used to compile *.api files. The compilation procedure results in the generation of C++ or JAVA files which are then compiled using a standard C++ or JAVA compiler (as appropriate). The resulting object code defines the JACOB objects.

5.1 JACOB Build

The general form of the command to compile *.api files is

        java aos.main.JacobBuild <dict-file>

where <dict-file> is a dictionary file. JACOB Build may also be run with the command line options below.

5.2 Command Line Options

5.2.1 -Wlang

Displays language warnings.

usage : -Wlang

arguments : none

default value (option not used) : no language warning errors are reported

5.2.2 -lang

Sets the default output language of JACOB. The language may either be Java or C++. Another language may be used if a driver for that language is created.

usage : -lang string

default value : java

5.2.3 -pkg

When Java code is generated, this identifies the location of JACOB classes for the generated code.

usage : -pkg string

default value : aos.apib

5.2.4 -syntax

This defines the file defining the location of the class that initialises JACOB. This effectively sets the syntax of the JACOB language by allowing an alternative definition of the default language file.

Another language, such as XML or SQL may be used if a class that initialises the language exists.

usage : -syntax string

default value : aos.apib.apic.boot.DefReader

5.2.5 -dj

This option identifies the directory where generated files are placed.

usage : -dj string

5.2.6 -dos

Files generated have the extension .cpp instead of .C. Note that this option must be used with the -lang cxx option.

usage: -dos

This option is not used by default.

5.2.7 -v

Displays the version of the JACOB Compiler.

usage: -v or -version

5.2.8 -h

Displays the command line options that may be used with the JACOB Compiler.

usage: -h or -help

5.2.9 -convert

Converts data from one format to another format.

usage: -convert -v mode infile outfile typedict

-v (optional): verbose mode prints messages about opening files, and reading and writing objects.

mode is an inmode letter immediately followed by an outmode letter. e.g. AX converts ASCII to XML.

a,A = ASCII; x,X = XML; b,B = Binary; u,U = unspecified (input only)

baxu uses the read/writeMultipleObjects() interface. AXBU uses the read/writeObject() interface.

Note that the readMultipleObjects() interface is not supported for Binary.

typedict (optional): a compiled Init__XXX.class file or any JACOB definition file. Note that the .class extension should not be included.


6 Functional Components of JACOB

6.1 Compiler

The JACOB Compiler processes one or more definition files, and generates code modules to link in with an application that intends to use, read and write the classes defined through the definition files. The compiler is a Java application that takes one or more definition files as input, and generates one or more Java or C++ files as output.

6.1.1 JACOB Data Definition Language

The definition files input to the compiler are defined using the JACOB Data Definition Language. The syntax is defined in Extended Baccus-Naur Form (EBNF) in the section entitled JACOB Object Modelling.

6.1.2 Mappings in JAVA

For generating Java code, the field type mappings are as described in the section entitled JACOB Object Modelling.

All Enum definitions result in separate classes; one for each Enum class. Within that class, each enumeration member is layed out as a public static final data member. Fields of type :enum are translated into int values.

Each Class definition results in its own Java file containing the corresponding Java class definition. In addition, each class definition is associated with a StreamerSupport extension class that contains the code for reading and writing objects of the type defined. These StreamerSupport extension classes are non-public classes appended to the Java files defining the data classes.

One additional class is also generated for the definition source file. The class is named by adding the prefix Init__ to the input file name. This class includes all the initialisation needed for using the definition set in an application. Further, when compiling this class with javac, all classes concerned are compiled automatically.

Inline source code fragments from Code directives for Java are copied to the output file as they occur among the fields.

The default aggregation is an array. The Aggregate interface includes methods for adding elements and constructing an Enumeration for accessing elements in order.

6.1.3 Mappings in C++

For generating C++ code, the field type mappings are as described in the section entitled JACOB Object Modelling, with the exception of the :bool type, which is mapped into unsigned char such that 0 represents :false and 1 represents :true.

Each Enum definition is mapped into a class definition as in Java, but the member set is also reconstructed as an anonymous public enum declaration within the class.

All classes of a definition source file are layed out together in a single C++ source file pair (separating headers from implementation).

The generated code further includes an additional class that includes all the initialisation needed for using the definition set in an application.

Inline source code fragments from Code directives for C++ are copied to the output header file essentially as they occur among the fields; in particular, within a public: segment.

The default aggregation is a class extending JACOB_Aggregate. This includes methods for adding elements and constructing a JACOB_Enumeration for accessing the elements in order.

6.2 Writer

The Writer is the combined functionality of code libraries and generated code and enables the mapping of an object structure onto a stream representation so that the same object structure is reconstructed when the reader is applied to the stream (see below).

The Writer functionality involves the processing of an in-core data structure for generating an output stream in one of several formats. The algorithm for this is a plain depth-first tree traversal that is implemented partly by pre-compiled library code and partly by the code generated for the class definitions. The latter is contained within the StreamerSupport extension classes, and each such class is therefore (made) capable of accessing the transportable fields. (In C++ code the class is called JACOB_StreamerSupport.)

6.2.1 Java Writer Classes

There is a generic class for output streaming, named OutStream, and there are four separate extensions of this:

6.2.2 C++ Writer Classes

The generic C++ class for output streaming is JACOB_OutStream. This class currently has the separate extensions JACOB_AsciiOutStream for ASCII file representation and JACOB_BinaryOutStream for binary file representation.

6.2.3 Writing Objects

The writing of objects is generated as part of the StreamerSupport or JACOB_StreamerSupport derivations as straightforward depth-first tree traversal. The AsciiOutStream or JACOB_AsciiOutStream identifies and keeps track of objects written out (including the one in progress); upon the second and subsequent occurrence of an object its identification number is written out, preceded by an & character instead of traversing the object. The identification number is unique within the object structure.

6.3 Reader

The Reader is the combined functionality of code libraries and generated code, and enables the processing of an object stream and the reconstruction of the corresponding runtime object structures it represents. The Reader functionality involves the processing of an input stream in one of several formats into a corresponding in-core data structure, by using a TypeDict of definitions regarding which objects the input stream may contain.

6.3.1 Java Reader Classes

There is a generic class for input streaming, named InStream, and there are separate extensions of this, AsciiInStream for the ASCII file representation described earlier, JDBCInStream for JDBC file representation, XMLInStream for XML file representation and BinaryInStream for a compact binary file representation. The InStream constructor uses reflection to find the class of an object if it is not known.

To open a JACOB stream regardless of the format, use the static method

        InStream.open(InputStream stream, TypeDict dict)

which will inspect the start of the stream, and return the correct derivation of InStream after resetting the input stream.

6.3.2 C++ Reader Classes

The generic C++ class for input streaming is named JACOB_InStream. This class currently has the separate extensions JACOB_AsciiInStream for ASCII file representation and JACOB_BinaryInStream for binary file representation.

To open a JACOB stream, the format must first be determined. The correct derivation of JACOB_InStream can then be opened (e.g. a new JACOB_AsciiInStream for ASCII data).


Note: When reading from or writing to data streams in Windows, a corresponding JACOB function must be registered. These functions are: fileRead, fileWrite, socketRead and socketWrite. An example of registering a read or write function can be seen in the C++ examples in the Sample Applications section.

6.3.3 Reading Objects

Reading is invoked by a stream method readObject. This reader recognises the <Object> introduction phrase (the angle bracket and the class name of the object to come in the input stream). This is sufficient to construct a blank object of the indicated type, and invoke the read method of the associated StreamerSupport (or JACOB_StreamerSupport for C++).

6.4 Initialisation

The Initialisation is the combined functionality of code libraries, generated code, and user-written code by which the reading/writing capability is enabled in an application program.

The JACOB functionality is enabled in an application, apart from any linking of object files, by constructing a TypeDict object. The TypeDict object defines the mapping between class names occurring in transport streams and actual class definitions generated by the JACOB utility.

When JACOB processes a definition file, it also constructs a definition group class with a static method addGroup(TypeDict d), which at runtime adds all the definition classes of the definition file into the given dictionary (TypeDict). The generated definition group class is named after the definition file.

The application program thus constructs and builds the TypeDict object or objects as part of its initialisation. The TypeDict object is then passed to the reader method of the input stream for reading the input file.

6.4.1 Dictionary file

A dictionary file defines the object(s) to be manipulated. When JACOB is run on an .api file, say filename.api, two files are generated. If Java code is generated, one of the files is called Init__filename.java. If C++ code is generated, one of the files is a header file, containing an Init__filename class definition.

The Init_filename code implements the dictionary specification for the object. Objects are read using the dictionary specified during the input of the stream. The Sample Application section contains examples demonstrating the implementation of dictionaries.

6.5 JACOB File Formats

Apart from the simple ASCII language described earlier, JACOB can read and write files in several other formats. These formats encode exactly the same information but may be preferred for other reasons.

There are currently four supported JACOB file formats:


Note: Only the ASCII and Binary formats are currently supported for C++.

6.5.1 ASCII

ASCII is a concise and readable format which can be edited by hand quite easily if necessary. The input streamer is called AsciiInStream and the output streamer is called AsciiOutStream.

6.5.2 Binary

The binary format is an extremely compact object representation. It can be used when the speed of object transfer is paramount or if it is desirable to make the stream not human-readable. The input streamer is called BinaryInStream and the output streamer is called BinaryOutStream.

6.5.3 JDBC

The JDBC format is a representation of data in tabular form such as a relational database or spreadsheet. JACOB objects in JDBC format are in the form of multiple tables. These tables are in generic form. If multiple JACOB object structures are stored in a database with other data, they are identified as distinct JACOB object structures by 'stream identifiers'. Stream identifiers are used to uniquely identify JACOB objects transported in a JDBC output streamer.

The input streamer for JACOB objects in JDBC format is called JDBCInStream and the output streamer is called JDBCOutStream. Both of these classes use methods from the JDBCSupport class.

See the JDBC API documentation (http://java.sun.com/products/jdbc) for further information.

6.5.4 XML

The XML format is very similar to the default ASCII representation.

Where the ASCII representation would write:

        <Derived
            <InBetween
                <BaseClass :name "ash">
                :ibw 999>
            :x 2
            :base "foobar"
            :y 100
            :doc  <Other :foo 10>
        >

The XML representation would be:

        <?xml version="1.0"?>
        <jacob version="3.0">

        <object type="Derived" id="0">
            <base type="InBetween">
                <base type="BaseClass">
                    <field name="doc">
                        <object type="Other" id="1">
                            <field name="foo">10</field>
                        </object>
                    </field>
                    <field name="name">ash</field>
                    <field name="base">foobar</field>
                </base>

                <field name="ibw">999</field>
            </base>

            <field name="x">2</field>
        </object>

The XML format is useful if you want to use XML tools to manipulate the JACOB objects or object definitions. The input streamer is called XMLInStream and the output streamer is called XMLOutStream.


7 Sample Applications

JACOB was designed to be used for two primary application types:

  1. An object initialisation tool, allowing flexible and consistent initialisation of data objects.
  2. An object communication device, allowing populated objects to be sent between different processes.

The first example provides code used to initialise an array of objects using a data file and JACOB.

The second example describes code contained in the JACK distribution defining a client/server system that sends and receives objects in either binary or ASCII. There is code for the client and the server in both C++ and Java.

7.1 Object Initialisation example

One of the most common uses of JACOB is as a utility to input data directly into an application. This section lists and describes some simple code that uses JACOB generated code to read data into an application. Java code for this example is described in the Java development steps section. C++ code is included with the JACOB C++ distribution, and is described in the C++ development steps section.

7.1.1 Development steps

The procedure used consists of the following steps:

  1. The object structure must be defined in an .api file using the JACOB Data Definition Language.
  2. The .api file must be compiled using JACOB.
  3. The .java or .pp files generated by the compilation of each .api file must be compiled using a standard Java or C++ compiler.
  4. The application input code must be written using the JACOB components (described below).
  5. The data file(s) to be read by the application must be written in the appropriate format.
  6. The application can be used.

7.1.2 Waypoint Initialiser code and description

The example in this section reads a list of waypoints from a data file and stores them in the form of an array of objects of type Waypoint. The data is then written to the screen, written to an ASCII file, and written to a binary file. The ASCII and binary files could be read in using the JACOB code by another application, or at a different time by the current application. This code could of course be combined with components of the client/server code and the object read in could be sent to other processes.

7.1.2.1 Java development steps

Step One: The waypoint object is defined in the file named waypoint.api below.

   <Code :lang "java">

   <Class :name "Waypoint"
      :fields (
         <Field :name "x" :type :int>
         <Field :name "y" :type :int>
         <Field :name "altitude" :type :int>
         <Field :name "speed" :type :int>
         <Field :name "id" :type :bool :value "true">
      )
   >

Note that the embedded 'package' statement should be changed to the actual package being built. Alternatively, this line can be removed entirely if an empty (unnamed) package is being built.

Step Two: This must be compiled using the command

   java aos.main.JacobBuild waypoint.api

This will generate two files, Waypoint.java and Init__waypoint.java.

Step Three: These two files must be compiled using the command

   javac Waypoint.java Init__waypoint.java

Step Four: The following is a printout of a class called WaypointInitialiser that reads a list of waypoints in the form of the Waypoint class. The waypoints are stored in an array, printed to the screen, and written to an ASCII file and a binary file.

Lines 1 to 5 import the required classes.

Lines 7 to 10 identify the class.

Line 11 declares a Base class type.

Line 13 declares an object of type TypeDict. This is the JACOB dictionary declaration.

Lines 16 to 17 declare the variable waypoints as type Vector.

Lines 20 to 21 initialise the JACOB dictionary using the JACOB generated class Init__waypoint. This defines the waypoint class.

Lines 24 to 70 are the main body of the code. The entire code is contained in the try beginning from line 24. While this is not textbook Java, the method was chosen as it simplifies the main body of the code.

Lines 27 to 30 define a JACOB ASCII input stream as the data file fighter_1.dat. Note that the stream is associated with the dictionary.

Lines 39 to 42 define the ASCII and binary output streams used for output to the ASCII and binary files.

Lines 46 to 56 read the data file.

Line 46 controls the loop. Objects are read from the data file until there are no more objects to read.

Lines 49 to 52 store each waypoint read in in the Vector waypoints.

Lines 54 and 55 write each waypoint object to the binary and ASCII stream as they are read from the input file.

Lines 58 to 62 store the Vector waypoints in an array of type Waypoint, w.

Lines 63 to 70 extract the data from the array w and writes it to the screen

Source code for the application Waypoint Initialiser is listed below.

 1  import aos.apib.apic.boot.*;
 2  import aos.apib.*;
 3  import java.io.*;
 4  import java.util.Vector;
 5  import java.util.Enumeration;
 6
 7  public class WaypointInitialiser {
 8    public static void
 9    main(String[] args)
10    {
11      Base input_base;
12
13      TypeDict dict;
14
15      // declare the vector waypoints
16      Vector waypoints;
17      waypoints = new Vector();
18
19      //initialise dictionary
20      dict = new TypeDict();
21      dict.initialize("Init__waypoint");
22
23      // Read in waypoint objects from data file
24      try {
25        // Set up input and output streams
26        // Data file
27        InputStream data_file =
28            new FileInputStream("fighter_1.dat");
29        InStream in = new AsciiInStream(new BufferedReader(
30            new InputStreamReader(data_file)),dict);
31
32        // Binary output
33        OutputStream binary_output_file = new BufferedOutputStream(
34            new FileOutputStream("waypoint_out_file_Binary.txt"));
35        BinaryOutStream binary_out =
36           new BinaryOutStream(binary_output_file);
37
38        // ASCII output
39         OutputStream ASCII_output_file = new BufferedOutputStream(
40             new FileOutputStream("waypoint_out_file_Ascii.txt"));
41         AsciiOutStream ASCII_out =
42             new AsciiOutStream(ASCII_output_file);
43
44         // read objects from data file and store them, and write
45         // them to an ASCII and a binary file
46         while((input_base  = in.readObject()) != null)
47         {
48           // store waypoints in the vector waypoints
49           if (input_base instanceof Waypoint)
50           {
51             waypoints.addElement(input_base);
52           }
53           // write objects to ASCII and binary file
54           binary_out.writeObject(input_base);
55           ASCII_out.writeObject(input_base);
56         }
57         // write data stored in waypoints vector to the screen
58         Waypoint [] w = new Waypoint[waypoints.size()];
59         int i = 0;
60         for(Enumeration e = waypoints.elements();
61             e.hasMoreElements();)
62           w[i++]= (Waypoint)e.nextElement();
63         for (int ii=0; ii<w.length; ii++) {
64           int waypoint_number = ii+1;
65           System.out.println("Waypoint " + waypoint_number + ":"
66               + "   x = " + w[ii].x
67               + "   y = " + w[ii].y
68               + "   altitude = " + w[ii].altitude
69               + "   speed = " + w[ii].speed);
70         }
71       }
72       catch (Throwable e) {
73         System.err.println("Caught "+e);
74         e.printStackTrace();
75         // close binary_out and ASCII_out if you want to see the
76         // partially generated output files for debugging purposes
77         System.exit(1);
78       }
79     }
80  }

Step Five: A sample data file containing a list of six waypoints, fighter_1.dat, is listed below.

   <Waypoint :x 10 :y 20 :altitude 1000 :speed 100 :id :false>
   <Waypoint :x 40 :y 20 :altitude 1000 :speed 100 :id :false>
   <Waypoint :x 50 :y 60 :altitude 1000 :speed 100 :id :false>
   <Waypoint :x 100 :y 200 :altitude 10000 :speed 100 :id :true>
   <Waypoint :x 150 :y 300 :altitude 10000 :speed 100 :id :true>
   <Waypoint :x 200 :y 200 :altitude 10000 :speed 100 :id :true>

When the above code is executed:

The code has demonstrated the reading of an ASCII file, and the writing of ASCII and binary files. The reading of binary files is also possible using a similar method to the ASCII read.

7.1.2.2 C++ development steps

The code in this example has been tested under Linux and VC++ but it may still require some changes, depending on the C++ environment and where the JACOB libraries are installed.

The C++ object initialisation example may be compiled with the provided Makefile by typing 'make', or by following the steps below.

Step One: The waypoint object is defined in the file named waypoint.api below.

    <Class :name "Waypoint"
        :fields (
            <Field :name "x" :type :int>
            <Field :name "y" :type :int>
            <Field :name "altitude" :type :int>
            <Field :name "speed" :type :int>
            <Field :name "id" :type :bool :value "true">
        )
    >

Note that this file is the same as the waypoint.api file in Step One of the Java development steps section apart from the Code object. JacobBuild will ignore a Code object if the :lang field does not match the language of the source code being generated.

Step Two: The waypoint.api file must be compiled using the command

    java aos.main.JacobBuild -dos -lang cxx waypoint.api

which generates two files, waypoint.h and waypoint.cpp.

Note that using the -dos command line option creates .cpp files instead of .C files.

Step Three: The generated C++ file must be compiled, either with the provided Makefile, using an IDE such as Visual Studio or using the command

    g++ -c -Wall -I../.. waypoint.cpp

Step Four: The following is a printout of a file called WaypointInitialiser.cpp that reads a list of waypoints in the form of the waypoint class. The waypoints are stored in an array that is printed to the screen and written to an ASCII file and a binary file.

Lines 1 to 15 include the required header files.

Lines 17 to 19 are the application ID.

Lines 20 to 24 declare the Streams and other parameters used in the application.

Lines 25 to 27 declare the files used in the application.

Line 23 declares a JACOB_Base class type.

Line 24 declares a an object of type JACOB_TypeDict. This is the JACOB dictionary declaration.

Lines 39 to 40 construct and initialise the JACOB dictionary using the JACOB generated class Init__waypoint. This defines the waypoint class.

Line 62 defines a JACOB ASCII input stream as the data file fighter_1.dat. Note that the stream is associated with the dictionary.

Lines 108 to 111 define the ASCII and binary output streams used for output to the ASCII and binary files.

Lines 115 to 129 read the data file.

Line 124 stores each waypoint read in the vector waypoints.

Lines 127 to 128 write each waypoint object to the binary and ASCII stream as they are read from the input file.

Lines 140 to 152 extract the data from vector and write it to the screen.

Source code for the application WaypointInitialiser is provided below.

  1  #include <jacob/AsciiOutStream.h>
  2  #include <jacob/AsciiInStream.h>
  3  #include <jacob/BinaryOutStream.h>
  4  #include <jacob/BinaryInStream.h>
  5  #include "jacob/JACOB.h"
  6
  7  #ifndef _WIN32
  8  #include <fcntl.h>
  9  #endif
 10
 11  #include <stdio.h>
 12  #include <stdlib.h>
 13  #include <vector.h>
 14
 15  #include "waypoint.h"
 16
 17  int
 18  main(int ac)
 19  {
 20      JACOB_BinaryOutStream    *binary_out = 0;
 21      JACOB_AsciiOutStream     *ascii_out = 0;
 22      JACOB_InStream           *data_file = 0;
 23      JACOB_Base               *input_base = 0;
 24      JACOB_TypeDict           *dict = 0;
 25      char   *filename = "fighter_1.dat";
 26      char   *binaryfilename = "waypoint_out_file_Binary.txt";
 27      char   *asciifilename = "waypoint_out_file_Ascii.txt";
 28
 29  #ifdef _WIN32
 30          WSADATA foo;
 31          WSAStartup(1, &foo);
 32  #endif
 33
 34      int infd = 0;
 35
 36      vector<Waypoint*> waypoints;
 37
 38      //initialise dictionary
 39      dict = new JACOB_TypeDict();
 40      Init__waypoint::init(dict);
 41
 42  #ifdef _WIN32
 43      infd = (int)CreateFile(filename, GENERIC_READ,
 44                 FILE_SHARE_READ, NULL, OPEN_ALWAYS,
 45                 FILE_ATTRIBUTE_NORMAL, NULL);
 46      if ((RW_HANDLE)infd == INVALID_HANDLE_VALUE)
 47      {
 48          fprintf(stderr,
 49                  "OpenFile: GetLastError() => %d\n",
 50                  GetLastError());
 51          exit(1);
 52      }
 53  #else
 54      infd = open(filename, O_RDONLY);
 55      if(infd < 0)
 56      {
 57          fprintf(stderr, "failed to open %s\n", filename);
 58          exit(1);
 59      }
 60  #endif
 61
 62      data_file = new JACOB_AsciiInStream(infd, dict);
 63
 64      data_file->registerReadFunction(JACOB_InStream::fileRead);
 65
 66  #ifdef _WIN32
 67      int binary_output_file =  (int)CreateFile(binaryfilename,
 68          GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS,
 69          FILE_ATTRIBUTE_NORMAL, NULL);
 70      if ((RW_HANDLE)infd == INVALID_HANDLE_VALUE)
 71      {
 72          fprintf(stderr,
 73                  "OpenFile: GetLastError() => %d\n", i
 74                  GetLastError());
 75          exit(1);
 76      }
 77
 78      int ascii_output_file =  (int)CreateFile(asciifilename,
 79          GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS,
 80          FILE_ATTRIBUTE_NORMAL, NULL);
 81      if ((RW_HANDLE)infd == INVALID_HANDLE_VALUE)
 82      {
 83          fprintf(stderr,
 84                  "OpenFile: GetLastError() => %d\n",
 85                  GetLastError());
 86          exit(1);
 87      }
 88  #else
 89      // open file for Binary output, create file if not present
 90      int binary_output_file = open(binaryfilename,
 91              O_WRONLY | O_CREAT, 0644);
 92
 93      if(binary_output_file < 0)
 94      {
 95          fprintf(stderr, "failed to open %s\n", binaryfilename);
 96          exit(1);
 97      }
 98
 99      // open file for ASCII output, create file if not present
100      int ascii_output_file = open(asciifilename,
101              O_WRONLY | O_CREAT, 0644);
102
103      if(ascii_output_file < 0)
104      {
105          fprintf(stderr, "failed to open %s\n", asciifilename);
106      }
107  #endif
108      binary_out =
109          new JACOB_BinaryOutStream(binary_output_file);
110      ascii_out =
111          new JACOB_AsciiOutStream(ascii_output_file);
112
113      // read objects from data file, store them, and write
114      // them to an ASCII and a binary file
115      for(;;)
116      {
117          input_base = data_file->readObject();
118          if(input_base == 0) break;
119
120          Waypoint *waypt =
121              reinterpret_cast<Waypoint*>(input_base);
122
123          // store waypoints in the vector waypoints
124          waypoints.push_back(waypt);
125
126          // write objects to ASCII and binary file
127          binary_out->writeObject(input_base);
128          ascii_out->writeObject(input_base);
129      }
130      close(infd);
131      close(binary_output_file);
132      close(ascii_output_file);
133
134      delete binary_out;
135      delete ascii_out;
136
137      delete input_base;
138
139      // write data stored in waypoints vector to the screen
140      for(int i=0; i < (int)waypoints.size(); i++)
141      {
142          int waypoint_number = i+1;
143          fprintf(stderr,
144                  "Waypoint %i:  x= %d  y = %d
145                   altitude = %d  speed = %d \n",
146                  waypoint_number, waypoints[i]->x,
147                  waypoints[i]->y,
148                  waypoints[i]->altitude,
149                  waypoints[i]->speed);
150
151          delete waypoints[i];
152      }
153
154      return 0;
155  }

Step Five: The application can be compiled using

g++ -c -Wall -I../.. WaypointInitialiser.cpp
g++ -o WaypointInitialiser WaypointInitialiser.o -L../../lib/linux/g++-2.95 -ljacob

A sample data file containing a list of six waypoints, fighter_1.dat, is listed in Step Five of the Java development steps section.

When the above code is executed:

The code has demonstrated the reading of an ASCII file, and the writing of ASCII and binary files. The reading and writing of binary files is also possible using similar methods to the ASCII reading and writing methods.

7.2 Object Communication example

The object communication capability of JACOB allows the sending and receiving of data objects between JAVA and C++ classes. One of the major potential uses of this code is to implement rapid data transport between C++ and JAVA code. This section lists and describes some simple code that demonstrates this capability.

Both Java and C++ code for a simple client and server system is included with the JACOB C++ distribution. Java code for this example is described in the Java development steps section. C++ code is described in the C++ development steps section.

7.2.1 Development steps

  1. The data object structure(s) must be defined in an .api file (or multiple .api files if desired) using the JACOB Data Definition Language.
  2. The .api file(s) must be compiled using JACOB.
  3. The .java or .cpp files generated by the compilation of each .api file must be compiled using a standard Java or C++ compiler.
  4. The communication code can be written to use the compiled dictionaries to send and receive the defined objects.
  5. The application can be used.

7.2.2 Client/Server code and description

The example described in this section consists of two applications, a server and a client. The server receives data objects from a client and sends the data back to the client. The client sends a specified data object to the server (and receives the object back again) a specified number of times. This code can be used as test code to obtain timing information for data object sending. The data object can be sent or received in either ASCII or binary form, or in XML form (Java only).

7.2.2.1 Java development steps

Step One: Some test data objects are defined in the file msg.api. This file is partially reproduced below.


Note: The excluded code (not printed below) contains more data objects exhibiting some of the more complex features of the JACOB Data Definition Language.

msg.api

<Class
    :name "RealSimple"
    :tostring "%T@%I %(val)"
    :fields     (
        <Field
            :name "val"
            :type :int
        >
    )
>

<Class
    :name       "Test"
    :fields     (
        <Field
            :name "val"
            :type :double
        >
    )
>

<Class
    :name       "Test10"
    :fields     (
        <Field
            :name "val0"
            :type :int
        >
        <Field
            :name "val1"
            :type :int
        >
        <Field
            :name "val2"
            :type :int
        >
        <Field
            :name "val3"
            :type :int
        >
        <Field
            :name "val4"
            :type :int
        >
        <Field
            :name "val5"
            :type :int
        >
        <Field
            :name "val6"
            :type :int
        >
        <Field
            :name "val7"
            :type :int
        >
        <Field
            :name "val8"
            :type :int
        >
        <Field
            :name "val9"
            :type :int
        >
    )
>
    :
    :
   (The remainder of the file has not been printed)
    :

The three data objects defined above are very simple but can be used to illustrate this application, as will be shown later in this section in Step Five.

Step Two: This must be compiled using the command

   java aos.main.JacobBuild msg.api

This will generate several files. For each of the objects (ObjectName) defined in the msg.api file it will generate an ObjectName.java. It will also generate an Init__msg.java file.

Step Three: These generated java files must be compiled, for example using the command

   javac *.java

Step Four: The server and client code are printed and explained below.

Server

The server receives data objects sent to it by a client, and sends the objects back to the client.

Lines 1 to 3 consist of the required imports.

Lines 5 to 9 are the class and application ID.

Line 10 declares an object of type TypeDict (the dictionary).

Lines 11 to 14 declare the Streams and other parameters used in the class.

Lines 16 to 22 check the number of command line arguments.

Line 24 constructs the new dictionary (TypeDict).

Line 25 initialises the dictionary.

Lines 27 to 34 initialise the socket with the user specified port number.

Lines 35 to 73 contain the code that reads the objects and writes them in the appropriate type (binary, ASCII or XML). This code loops forever, accepting a connection and reading all objects present on the stream.

Line 67 declares an object of type Base.

Lines 68 and 69 read the data object and then write the data object back to the client.

Server code

  1  import aos.apib.*;
  2  import java.io.*;
  3  import java.net.*;
  4
  5  public class Server
  6  {
  7    public static void
  8    main(String[] args)
  9    {
 10      TypeDict          dict;
 11      Socket            sock;
 12      OutStream         out = null;
 13      InStream          in = null;
 14      ServerSocket      serv = null;
 15
 16      if ( args.length != 4 ) {
 17        System.err.println( "usage:  java Server "+
 18            "<Init__*> <port> <send-format> <recv-format>");
 19        System.err.println("       <send-format> = A, B or X");
 20        System.err.println("       <recv-format> = A, B or X");
 21        System.exit(1);
 22      }
 23
 24      dict = new TypeDict();
 25      dict.initialize(args[0]);
 26
 27      try {
 28        serv = new ServerSocket(Integer.parseInt(args[1]));
 29      }
 30      catch (Exception e) {
 31       System.err.println("Can't accept the connection");
 32        e.printStackTrace();
 33        System.exit(1);
 34      }
 35      for (;;) {
 36        // Loop forever, accepting a connection and reading
 37        // all objects present on the stream.
 38        try {
 39          System.err.println("waiting for a connection...");
 40          sock = serv.accept();
 41          if ( args[2].equalsIgnoreCase("A") )
 42            out = new AsciiOutStream(sock.getOutputStream());
 43          else if ( args[2].equalsIgnoreCase("B") )
 44            out = new BinaryOutStream(sock.getOutputStream());
 45          else if ( args[2].equalsIgnoreCase("X") )
 46            out = new XMLOutStream(sock.getOutputStream());
 47          else {
 48            System.err.println("Bad send format");
 49            System.exit(10);
 50          }
 51          if ( args[3].equalsIgnoreCase("A") )
 52            in = new AsciiInStream(sock.getInputStream(),dict);
 53          else if ( args[3].equalsIgnoreCase("B") )
 54            in = new BinaryInStream(sock.getInputStream(),dict);
 56          else if ( args[3].equalsIgnoreCase("X") )
 57            in = new XMLInStream(sock.getInputStream(),dict);
 58          else {
 59            System.err.println("Bad recv format");
 60            System.exit(10);
 61          }
 62        }
 63        catch (Exception e) {
 64          e.printStackTrace();
 65          System.exit(1);
 66        }
 67        Base   obj;
 68        while ( (obj = in.readObject()) != null ) {
 69          out.writeObject(obj);
 70        }
 71        System.err.println("no more objects to read");
 72      }
 73    }
 74  }

Client

The client sends a specified data object to the server (and receives the object back again) a specified number of times.

Lines 1 to 3 consist of the required imports.

Lines 5 to 8 are the class and application ID.

Line 10 declares an object of type TypeDict (the dictionary).

Lines 11 to 14 declare the Streams and other parameters used in the class.

Lines 16 to 29 check the number of command line arguments.

Line 31 constructs the dictionary.

Line 32 initialises the dictionary.

Lines 34 to 46 use the command line arguments to set the host, input file, port number and the number of times a data object is to be sent by the client.

Lines 50 to 61 set up the input stream to read in the data object.

Line 62 reads the data object into obj.

Lines 67 to 93 set up the socket (using the user-specified port number and host), and the input and output streams (ASCII, binary or XML streams as defined by the command line arguments).

Line 95 records the time at the start.

Lines 97 to 106 write the data object to the output stream and read the data object when it is returned. This is repeated the number of times specified by the command line argument.

Lines 107 to 110 print the time taken to send and receive the data object the specified number of times.

Client code

   1  import aos.apib.*;
   2  import java.io.*;
   3  import java.net.*;
   4
   5  public class Client
   6  {
   7    public static void
   8    main(String[] args)
   9    {
  10      TypeDict        dict;
  11      Socket          sock;
  12      OutStream       out = null;
  13      InStream        in = null;
  14      ServerSocket    serv = null;
  15
  16      if ( args.length != 7 ) {
  17        System.err.println(
  18            "usage: java Client <Init__*> "+
  19            "<host> <port> <file> <count> <send> <recv>");
  20        System.err.println(
  21            "   <send> = A, B or X (Ascii, Binary, XML)");
  22        System.err.println(
  23            "   <recv> = A, B or X");
  24        System.err.println(
  25            "   <file> contains a JACOB object");
  26        System.err.println(
  27            "      if <file> = \"-\", reads from stdin");
  28        System.exit(1);
  29      }
  30
  31      dict = new TypeDict();
  32      dict.initialize(args[0]);
  33
  34      int     count = 0;
  35      int     port = 0;
  36      String  host = args[1];
  37      String  file = args[3];
  38
  39      try {
  40        port  = Integer.parseInt(args[2]);
  41        count = Integer.parseInt(args[4]);
  42      }
  43      catch (Exception e) {
  44        System.err.println("Bad port or count given");
  45        System.exit(10);
  46      }
  47
  48      int tot = count;
  49
  50      try {
  51        if ( file.equals("-") )
  52          in = new AsciiInStream(new BufferedReader(
  53              new InputStreamReader(System.in)), dict);
  54        else
  55          in = new AsciiInStream(
  56              new BufferedReader(new FileReader(file)),dict);
  57      }
  58      catch (Exception e) {
  59        e.printStackTrace();
  60        System.exit(1);
  61      }
  62      Base obj = in.readObject();
  63      if (obj == null) {
  54        System.err.println("Failed to read an object to send");
  65        System.exit(1);
  66      }
  67      try {
  68        sock = new Socket(host, port);
  69        if ( args[5].equalsIgnoreCase("A") )
  70          out = new AsciiOutStream(sock.getOutputStream());
  71        else if ( args[5].equalsIgnoreCase("B") )
  72          out = new BinaryOutStream(sock.getOutputStream());
  73        else if ( args[5].equalsIgnoreCase("X") )
  74          out = new XMLOutStream(sock.getOutputStream());
  75        else {
  76          System.err.println("Bad send format");
  77          System.exit(10);
  78        }
  79        if ( args[6].equalsIgnoreCase("A") )
  80          in = new AsciiInStream(sock.getInputStream(),dict);
  81        else if ( args[6].equalsIgnoreCase("B") )
  82          in = new BinaryInStream(sock.getInputStream(),dict);
  83        else if ( args[6].equalsIgnoreCase("X") )
  84          in = new XMLInStream(sock.getInputStream(),dict);
  85        else {
  86          System.err.println("Bad recv format");
  87          System.exit(10);
  88        }
  89      }
  90      catch (Exception e) {
  91        e.printStackTrace();
  92        System.exit(1);
  93      }
  94
  95      long start_t = System.currentTimeMillis();
  96
  97      while (count > 0) {
  98        count--;
  99        out.writeObject(obj);
 100        obj = in.readObject();
 101        if (obj == null) {
 102          System.err.println(
 103              "Error with "+count+" messages to be read");
 104           break;
 105         }
 106      }
 107      System.err.println(
 108          (System.currentTimeMillis()-start_t)+
 109          " ms to send and recv "+(tot-count)+" messages");
 110    }
 111
 112  }

Step Five: The application can be compiled using

 javac *.java

If the data is being sent to the server in ASCII form and being sent back to the client in ASCII form then the server may be started using the following command

 java Server Init__msg 8888 A A

The last two command line arguments specify the form in which the data object is sent and received. This can be either A (ASCII), B (binary) or X (XML). Note that the server can be started in its own window or it can be executed in the background.

The client may be started using the following command

 java Client Init__msg localhost 8888 data.RealSimple 10 A A

This also specifies that the data is being sent and received to/from the server in ASCII form. data.RealSimple is the file that contains the data. For example it could contain

<RealSimple :val 10>

7.2.2.2 C++ development steps

The code in this example has been tested under Linux and VC++ but it may still require some changes depending on the C++ environment and where the JACOB libraries are installed.

The C++ object communication example may be compiled with the provided Makefile by typing 'make', or by following the steps below. Further information about running this example is provided in the readme file in the example directory.

Step One: Some test data objects are defined in the file msg.api. Part of this file is reproduced in Step One of the Java development steps section.

Step Two: The msg.api file must be compiled using the command

   java aos.main.JacobBuild -dos -lang cxx msg.api

This will generate two files, msg.h and msg.cpp.

Step Three: The generated C++ file must be compiled, either with the provided Makefile or using the command

   g++ -c -Wall -I.. msg.cpp

Step Four: The server and client code are printed and explained below.

Server

The server receives data objects sent to it by a client, and sends the objects back to the client.

Lines 1 to 9 include the required header files.

Lines 14 to 20 declare the Streams and other parameters used in the application.

Line 16 declares an object of type JACOB_TypeDict (the dictionary).

Line 17 declares an object of type JACOB_Base.

Lines 27 to 30 check the number of command line arguments.

Lines 31 constructs the new dictionary (JACOB_TypeDict).

Line 32 initialises the dictionary.

Line 34 initialises the socket with the user specified port number.

Lines 35 to 82 contain the code that reads the objects and writes them in the appropriate type (binary or ASCII). This code loops while a connection is accepted.

Lines 71 and 77 read the data object and write the data object back to the client. This code loops forever, reading all objects present on the stream.

Server code

  1  #include <jacob/AsciiOutStream.h>
  2  #include <jacob/AsciiInStream.h>
  3  #include <jacob/BinaryOutStream.h>
  4  #include <jacob/BinaryInStream.h>
  5  #include <jacob/socket.h>
  6
  7  #include <stdlib.h>
  8
  9  #include "msg.h"
 10
 11  int
 12  main(int ac, char **av)
 13  {
 14      JACOB_OutStream     *x;
 15      JACOB_InStream      *y;
 16      JACOB_TypeDict      *d;
 17      JACOB_Base          *b;
 18      int                 fd;
 19      JACOB_ServerSocket  *c;
 20      int                 port;
 21
 22  #ifdef _WIN32
 23      WSADATA foo;
 24      WSAStartup(1, &foo);
 25  #endif
 26
 27      if (ac != 3) {
 28          fprintf(stderr, "usage: server <port> [AB][AB]\n");
 29          exit(1);
 30      }
 31      d = new JACOB_TypeDict();
 32      Init__msg::init(d);
 33      sscanf(av[1], "%d", &port);
 34      c = new JACOB_ServerSocket( port);
 35      while ((fd = c->Accept()) >= 0) {
 36          if (av[2][0] == 'A') {
 37              y = new JACOB_AsciiInStream(fd, d);
 38              fprintf(stderr,
 39                  "server: opened input stream in ascii mode\n");
 40          } else if (av[2][0] == 'B') {
 41              y = new JACOB_BinaryInStream(fd, d);
 42              fprintf(stderr,
 43                  "server: opened input stream in binary mode\n");
 44          } else {
 45              fprintf(stderr,
 46                  "%c not a valid input stream type\n", av[2][0]);
 47              exit(1);
 48          }
 49
 50          if (av[2][1] == 'A') {
 51              x = new JACOB_AsciiOutStream(fd);
 52              fprintf(stderr,
 53                  "server: opened output stream in ascii mode\n");
 54          } else if (av[2][1] == 'B') {
 55              x = new JACOB_BinaryOutStream(fd);
 56              fprintf(stderr,
 57                 "server: opened output stream in binary mode\n");
 58          } else {
 59              fprintf(stderr,
 60                 "%c not a valid output stream type\n", av[2][1]);
 61              exit(1);
 62          }
 63
 64          // Needed under Windows
 65          // (doesn't hurt under UNIX but not necessary).
 66          y->registerReadFunction(JACOB_InStream::socketRead);
 67          x->registerWriteFunction(JACOB_OutStream::socketWrite);
 68
 69          int msgs = 0;
 70
 71          for (;;) {
 72              b = y->readObject();
 73              if (b == 0) break;
 74              msgs++;
 75              x->writeObject(b);
 76              delete b;
 77          }
 78          close(fd);
 79          fprintf(stderr, "received %d messages\n", msgs);
 80          delete x;
 81          delete y;
 82      }
 83      return 0;
 84   }

Client

The client sends a specified data object to the server (and receives the object back again) a specified number of times.

Lines 1 to 15 include the required header files.

Lines 20 to 32 declare the Streams and other parameters used in the application.

Line 22 declares an object of type JACOB_TypeDict (the dictionary).

Lines 41 to 65 check the number of command line arguments.

Lines 42 to 58 use the sixth command line argument to set the input file.

Lines 66 to 68 use the command line arguments to set the port number and the number of times a data object is to be sent by the client.

Line 69 initialises the socket with the user specified port number and host.

Line 72 constructs the dictionary (JACOB_TypeDict).

Line 73 initialises the dictionary.

Lines 75 to 80 set up the input stream to read in the data object.

Line 82 reads the data object into b.

Lines 91 to 116 set up the input and output streams (ASCII or Binary streams as defined by the command line arguments).

Line 122 records the time at the start.

Lines 125 to 131 write the data object to the output stream and read the data object when it is returned.

Line 140 prints the time taken to send and receive the data object the specified number of times.

Client code

  1  #include <jacob/AsciiOutStream.h>
  2  #include <jacob/AsciiInStream.h>
  3  #include <jacob/BinaryOutStream.h>
  4  #include <jacob/BinaryInStream.h>
  5  #include "jacob/JACOB.h"
  6  #include "jacob/socket.h"
  7
  8  #ifndef _WIN32
  9  #include <sys/time.h>
 10  #include <fcntl.h>
 11  #endif
 12
 13  #include <stdlib.h>
 14
 15  #include "msg.h"
 16
 17  int
 18  main(int ac, char **av)
 19  {
 20      JACOB_OutStream     *x = 0;
 21      JACOB_InStream      *y = 0;
 22      JACOB_TypeDict      *d = 0;
 23      JACOB_Base          *b = 0;
 24      int                 i;
 25      int                 fd;
 26      JACOB_ClientSocket  *c = 0;
 27      int                 port;
 28      int                 n;
 29
 30  #ifndef _WIN32
 31      struct timeval      t1, t2;
 32  #endif
 33
 34  #ifdef _WIN32
 35      WSADATA foo;
 36      WSAStartup(1, &foo);
 37  #endif
 38
 39      int infd = 0;
 40
 41      if (ac == 6) {
 42  #ifdef _WIN32
 43          infd = (int)CreateFile(av[5], GENERIC_READ,
 44              FILE_SHARE_READ, NULL, OPEN_ALWAYS,
 45              FILE_ATTRIBUTE_NORMAL, NULL);
 46          if ((RW_HANDLE)infd == INVALID_HANDLE_VALUE) {
 47              fprintf(stderr, "OpenFile: GetLastError() => %d\n",
 48                  GetLastError());
 49              exit(1);
 50          }
 51  #else
 52          infd = open(av[5], 0);
 53          if ( infd < 0 ) {
 54              fprintf(stderr, "failed to open %s\n", av[5]);
 55              perror(av[5]);
 56              exit(1);
 57          }
 58  #endif
 59          ac = 5;
 60      }
 61      if (ac != 5) {
 62          fprintf(stderr,
 63              "usage: client host port count [AB][AB] [file]\n");
 64          exit(1);
 65      }
 66      sscanf(av[2], "%d", &port);
 67      sscanf(av[3], "%d", &i);
 68      n = i;
 69      c = new JACOB_ClientSocket(av[1], port);
 70      fd = c->Connect();
 71      if (fd < 0) exit(1);
 72      d = new JACOB_TypeDict();
 73      Init__msg::init(d);
 74
 75      y = new JACOB_AsciiInStream(infd, d);
 76
 77      fprintf(stderr, "infd=%d\n", infd);
 78      // probably passed a real file on the cmd line
 79      if ( infd > 0 )
 80          y->registerReadFunction(JACOB_InStream::fileRead);
 81
 82      b = y->readObject();
 83      delete y;
 84      if ( b == 0 ) {
 85          fprintf(stderr,
 86              "failed to read a valid object - try again\n");
 87          exit(1);
 88      }
 89      fprintf(stderr, "object was read ok\n");
 90
 91      if (av[4][0] == 'A') {
 92          y = new JACOB_AsciiInStream(fd, d);
 93          fprintf(stderr,
 94              "client: opened input stream in ascii mode\n");
 95      } else if (av[4][0] == 'B') {
 96          y = new JACOB_BinaryInStream(fd, d);
 97          fprintf(stderr,
 98              "client: opened input stream in binary mode\n");
 99      } else {
100          fprintf(stderr,
101              "%c not a valid input stream type\n", av[4][0]);
102          exit(1);
103      }
104      if (av[4][1] == 'A') {
105          x = new JACOB_AsciiOutStream(fd);
106          fprintf(stderr,
107              "client: opened output stream in ascii mode\n");
108      } else if (av[4][1] == 'B') {
109          x = new JACOB_BinaryOutStream(fd);
110          fprintf(stderr,
111         "client: opened output stream in binary mode\n");
112      } else {
113          fprintf(stderr,
114              "%c not a valid output stream type\n", av[4][1]);
115          exit(1);
116      }
117
118      y->registerReadFunction(JACOB_InStream::socketRead);
119      x->registerWriteFunction(JACOB_OutStream::socketWrite);
120
121  #ifndef _WIN32
122      gettimeofday(&t1, 0);
123  #endif
124      int count = 0;
125      for (;i > 0; i--) {
126          x->writeObject(b);
127          delete b;
128          b = y->readObject();
129          if (b == 0) break;
130          count++;
131      }
132      fprintf(stderr, "sent %d messages\n", count);
133  #ifndef _WIN32
134      gettimeofday(&t2, 0);
135      long j;
136      j = t2.tv_sec - t1.tv_sec;
137      j *= 1000;
138      j += (t2.tv_usec - t1.tv_usec)/1000;
139      fprintf(stderr,
140     "Took %ld ms to send/recv %d msgs\n", j, (n - i)*2);
141  #endif
142      return 0;
143  }

Step Five: The application can be compiled using

   g++ -c -Wall -I../.. client.cpp
   g++ -o client client.o msg.o -L../../lib/linux/g++-2.95 -ljacob
   g++ -c -Wall -I../.. server.cpp
   g++ -o server server.o msg.o -L../../lib/linux/g++-2.95 -ljacob

If the data is being sent to the server in ASCII form and being sent back to the client in ASCII form, the server may be started using the following command

   ./server 5555 AA

The last command line argument specifies the form in which the data object is sent and received. This can be either A (ASCII) or B (binary).

Note that the server can be started in its own window or it can be executed in the background.

The client may be started using the following command

   ./client localhost 5555 200 AA xx.dat

This also specifies that the data is being sent and received to/from the server in ASCII form. xx.dat is the file that contains the data. For example, it could contain

   <RealSimple :val 8>

The client may also be started without a data file. Objects may be entered manually, by typing them in to the same window the client is running in. e.g. type <RealSimple :val 8> and press Enter.