Simple Java Based ACP

A Simple ACP Example

This example shows how you can send an ACP 2 floats and get the multiplication result of those two floats.

Simple Java Code Example

The following code needs to be developed to support a basic Java ACP.

import com.gesmallworld.core.acp.AcptAcp;
import com.gesmallworld.core.acpt.AcptException;
import com.gesmallworld.core.acpt.BuiltinType;
import com.gesmallworld.core.acpt.StructType;

public class AcpExample  extends AcptAcp {
    /** Returns 0, but could return any int. Must match the definition in 
     * Magik ACP class. 
     * @see com.gesmallworld.core.acp.Acp#getMaxProtocol()
     */
    protected int getMaxProtocol()  { return 0; }

    /** Returns 0, but could return any int. Must match the definition in 
    * Magik ACP class. 
         * @see com.gesmallworld.core.acp.Acp#getMinProtocol()
    */
        protected int getMinProtocol()  { return 0; }

    /** Must match the definition in Magik ACP class. 
         * @see com.gesmallworld.core.acp.Acp#getProgramId()
         */
        protected String getProgramId() { return "acp_example";}
}

The above code shows the basic imports that are required and the mandatory methods that must be written on the Java Class. You need to inherit from AcptAcp class. Everything else is optional and will depend on how you implement your code.

This Class must have a main() method. You could pass in arguments thru the command line if you wanted. This example just calls a new constructor that loops over the doIt() method until false is returned. doIt() actually is the code that starts reading stuff from Magik

    /** No arguments in this example, but some could be past in if needed.
     * @param args
     */
        public static void main(String[] args) throws AcptException {
        new AcpExample();
    }

        /** Basic constructor will loop until a quit code is sent from ACP
         * @throws AcptException
         */
        AcpExample() throws AcptException {
            while (doIt());
        }

    /** reads the operation code from magik to do what is needed.
     * @return boolean - true continue next command, false quit
     * @throws AcptException 
     */
    public boolean doIt() throws AcptException{

        int opCode = ((Integer)getObject(BuiltinType.INT32)).intValue();
        switch (opCode) {
        case -1:
            return false;
        case 1:
            doSimpleMultiplication();
            break;
        case 2:
            doMarshallingMultiplication();
            break;
        }    
        return true; 

    }

As you can see the doIt() method gets an integer object from magik. If it is 1 or 2, it calls the appropriate method. You will also see that an exception is thrown. This is because getObject() throws it and must be handled or passed upward.

In the following code, you will see how 2 floats are read in from Magik and a float is passed back to Magik.

    /** reads 2 floats, and returns the multiplication of the two to magik 
     * @throws AcptException 
     * 
     */
    private void doSimpleMultiplication() throws AcptException {
        // Note things going to/from smallworld are Objects not Primitives 
        // get 2 floats from magik
        float float1 = ((Float)getObject(BuiltinType.FLOAT32)).floatValue();
        float float2 = ((Float)getObject(BuiltinType.FLOAT32)).floatValue();
        float ans = float1 * float2;
        // send the answer back to magik as a Float object. 
        putObject(BuiltinType.FLOAT32,new Float(ans));
    }

You will see that Float Objects are what is passed in and out of Java. These are not primitives. All data is passed to and from Magik as Objects! In the above example you will see how the float is passed back to Magik using the putObject() call.

Simple Magik Code Example

Here we have the corresponding Magik code that it takes to call the ACP.

_pragma(classify_level=basic, topic={acp})
##
## Example ACP communications
##
def_slotted_exemplar(:acp_example,
    {{:classpath,_unset,:readable},
     {:debug?,_unset,:writable}
    },:user_acp)
$
_pragma(classify_level=basic, topic={acp})
acp_example.define_shared_constant(
    :input_class,
    ## Required to be set locally
    acpt_input_stream,_false)
$
_pragma(classify_level=basic, topic={acp})
acp_example.define_shared_constant(
    :output_class,
    ## Required to be set locally
    acpt_output_stream,_false)
$
_pragma(classify_level=basic, topic={acp})
_method acp_example.select_protocol(protocol_number)
    ## Parameters  : protocol_number - not used
    ## Returns     : 0
    ## Function    : 
    ## Methodology : 
    #---------------- HISTORY ---------------------
    # (10/26/2011 - Mark Field, FCSI): Created.
    #----------------------------------------------
    _return 0

_endmethod
$

You will see in the above code that we inherit from user_acp. Above (except for the slots definition) is all the constants and method that is required to be a valid acp object. From here your code can vary from this example depending on your requirements.

Below is some basic initialization code.

_pragma(classify_level=basic, topic={acp})
_method acp_example.new(_optional class_dir,debug?)
    ## Parameters  : class_dir - directory of java classes to use,
    ##                  if unset, classes under module will be used.
    ##               debug? - USed for debugging java code
    ## Returns     : initialized acp_example with started ACP
    ## Function    : 
    ## Methodology : calls clone.init()
    #---------------- HISTORY ---------------------
    # (10/26/2011 - Mark Field, FCSI): Created.
    #----------------------------------------------
    _return _clone.init(class_dir,debug?)
_endmethod
$

_pragma(classify_level=basic, topic={acp})
_method acp_example.init(class_dir,debug?)
    ## Parameters  : class_dir - directory of java classes to use,
    ##                  if unset, classes under module will be used.
    ## Returns     : initialized acp_example with started ACP
    ## Function    : starts the ACP
    ## Methodology : 
    #---------------- HISTORY ---------------------
    # (10/26/2011 - Mark Field, FCSI): Created.
    #----------------------------------------------
    _if class_dir _is _unset 
    _then
        class_dir << sw_module_manager.module(_self.module_name).full_directory +
                     "/classes"
    _endif
    _local core_path << system.getenv("SMALLWORLD_GIS")+
                        "/resources/base/java/lib/geswcore.jar"
    .classpath << core_path+%;+class_dir
    .program_ident << "acp_example"
    .minimum_protocol << 
        .maximum_protocol << 0
    .debug? << debug?
    _self.start_acp()
    _return _self 
_endmethod
$
_pragma(classify_level=basic, topic={acp})
_method acp_example.start_acp(_gather args)
    ## Parameters  : args - whatever args could be passed to super.start-acp()
    ## Returns     : results of super.start_acp()
    ## Function    : sets the command slot and callse super.start_acp()
    ## Methodology : 
    #---------------- HISTORY ---------------------
    # (10/26/2011 - Mark Field, FCSI): Created.
    #----------------------------------------------
    .command <<
        _if .debug? _is _true
        _then
            >> rope.new_with("java",
                "-Xdebug",
                "-Xnoagent",
                "-Djava.compiler=NONE",
                "-Xrunjdwp:transport=dt_socket,server=y,address=3997,suspend=n")
        _else
            >> rope.new_with("java")
        _endif 
    .command.add_all_last({"-classpath",
                           .classpath,
                           "AcpExample"})
    _return _super.start_acp(_scatter args)    
_endmethod
$

In the start_acp() method above, you will see how you can start the ACP in debug mode. This allows you to attach to the Java process (thru port# 3997 on the local machine) with any Java debugger you have. Note you may need a patch for pre 4.X releases of Smallworld because the debug mode has more noise on the ACP stream that Magik now ignores.

You will note the command rope is basically separated at white spaces. But you could build your command using white spaces in the elements if you wish.

The following code actually sends data to and returns the value from the ACP

_pragma(classify_level=basic, topic={acp})
_method acp_example.simple_multiply(f1,f2)
    ## Parameters  : f1 - float to multiple with
    ##               f2 - another float
    ## Returns     : float - f1 * f2 thru the acp
    ## Function    : uses self's ACP functionality to call the
    ## multiplication in ACP
    ## Methodology : 
    #---------------- HISTORY ---------------------
    # (10/26/2011 - Mark Field, FCSI): Created.
    #----------------------------------------------
    # Send command op of 1 - simple multiply
    _self.put_object(1,acpt_type.int32)
    #put the 2 floats
    _self.put_object(f1,acpt_type.float32)
    _self.put_object(f2,acpt_type.float32)
    # flush the stream
    _self.flush()
    # return the results.
    _return _self.get_object(acpt_type.float32)
_endmethod
$

You will note in the above the put_object() and get_object() calls. These correspond to the Java getObject() and putObject() calls. A put_object from Magik requires a getObject in Java. You will be debugging and run across this issue many times during your development. It is the nature of the beast and you should pay close attention to the calls.

Running Simple Example

Load the acp_example module (see attached ZIP file for complete hierarchy) and run the following at the Magik prompt.

MagikSF> !acp <<acp_example.new()
$
MagikSF> !acp.simple_multiply(6.0,6.0)
$
36.0000000000

Don't forget that a call to start_acp() was done in the init() code, so we didn't need to run it in this example.

Data Marshalling Example

The above example was sending data back and forth individually. This can be very cumbersome for large amount of data and complex data. The better/faster way of transmitting data back and forth to the ACP is thru the use of "Data Marshalling." Data Marshalling is basically passing structures to and from the ACP. And you can wrap these structures into vectors, so instead of passing 1 object at a time, you can pass in many objects. The performance is very good and ACPs should be used whenever you need to do some real number crunching.

Below is an example of passing back and forth a simple data object.

Data Marshalling Example Java Code

The following is the ACP code that is added to the AcpExample object defined above.

    /** reads a data object multiples the data of the data object, and 
     * returns the data object to magik. 
     * @throws AcptException 
     * 
     */
    private void doMarshallingMultiplication() throws AcptException {
        // get an example data object from magik
        StructType dataType = AcpExampleData.acpType();
        AcpExampleData data = (AcpExampleData)getObject(dataType);
        data.multiply();
        // put the object back to magik
        putObject(dataType,data);
    }

Above you will see that instead of 2 floats being passed into the ACP, one AcpExampleData object is passed into the ACP. And we pass the AcpExampleData back to Magik. Below is the AcpExampleData.

import com.gesmallworld.core.acpt.AcptException;
import com.gesmallworld.core.acpt.BuiltinType;
import com.gesmallworld.core.acpt.ElDef;
import com.gesmallworld.core.acpt.StructType;

public class AcpExampleData {
    /** This is a slot in the corresponding Magik Object. This is required 
     * not to be unset.
     */
    public Float  swFloat1 = null;
    /** This is a slot in the corresponding Magik Object. This is required 
     * not to be unset.
     */
    public Float  swFloat2 = null;
    /** This is a slot in the corresponding Magik Object. This can be unset
     * and will be filled out to return this object back to magik.
     */
    public Float  swAnswer = null;

    /** Basic constructor, does nothing.
     * 
     */
    public AcpExampleData () {
    }

    /** This defines the object structure of data being passed to/from
     * magik. 
     * @return THe object structure
     */
    public static StructType acpType() {
        StructType t = null;
        try {
            // Note the order is the same order as the acpt_type definition
            // in magik. Note the answer can be unset.
            t = new StructType(AcpExampleData.class, 
                    new ElDef[] {
                        new ElDef("swFloat1",BuiltinType.FLOAT32,false),
                        new ElDef("swFloat2",BuiltinType.FLOAT32,false),
                        new ElDef("swAnswer",BuiltinType.FLOAT32,true)
            });
        } catch (AcptException e) {
            System.err.print(e);
        };
        return t;
    }

    /** populates the swAnswer field by multiplying swFloat1 & swFloat2
     * 
     */
    public void multiply(){
        // because things are passed in as objects, need to convert to 
        // primitives and store as object again.
        swAnswer = new Float(swFloat1.floatValue() * swFloat2.floatValue());
    }
}

You will see that we created a class that has public fields. These fields must be public for the ACP to populate them, otherwise, you will get strange looking errors. There is a static method acpType() that is used to define the structures being passed to and from Magik. You will see the StructType has the class AcpExampleData.class as an argument. This basically says this structure will populate and return that type of object. Note the 1st argument in the ElDef definitions is the field names.

This structure must be of the same shape, order, and options as those defined on the Magik side. Again this is another area to be very careful when editing as this is where many bugs and many long nights debugging will drive you crazy.

This class has a simple multiply() method that multiplies the 2 floats and stores them on swAnswer that will be used by Magik.

Data Marshalling Example Magik Code

The following Magik code was added to the acp_example exemplar to transmit the acp_example_data object to and from the ACP.

_pragma(classify_level=basic, topic={acp})
_method acp_example.marshalling_multiply(f1,f2)
    ## Parameters  : f1 - float to multiply with
    ##               f2 - other float
    ## Returns     : 2 acp_example_data objects, one with answer
    ## and without the answer
    ## Function    : calls the ACP with a acp_example_data object
    ## Methodology : 
    #---------------- HISTORY ---------------------
    # (10/26/2011 - Mark Field, FCSI): Created.
    #----------------------------------------------
    # get an example data object.
    _local data << acp_example_data.new(f1,f2)
    # Send command op of 2 - marshall multiply
    _self.put_object(2,acpt_type.int32)
    #put the data object        
    _self.put_object(data,data.acpt_type)
    # flush the stream
    _self.flush()
    # get the results.
    _local ans << _self.get_object(data.acpt_type)
    _return ans,data
_endmethod
$

Again you will notice that we have a put_object and get_object of an acp_example_data object. This corresponds to the Java side getObject and putObject calls. You will see that object type is a constant on acp_example_data and is shown below.

_pragma(classify_level=basic, topic={acp})
##
## New Exemplar
##
def_slotted_exemplar(:acp_example_data,
    {{:float1,_unset},
     {:float2,_unset},
     {:answer,_unset}
    })
$
_pragma(classify_level=basic, topic={acp})
acp_example_data.define_slot_access(
    :float1,
    ## Must be writable for ACP. A float to multiply with
    :writable)
$
_pragma(classify_level=basic, topic={acp})
acp_example_data.define_slot_access(
    :float2,
    ## Must be writable for ACP. A float to multiply with
    :writable)
$
_pragma(classify_level=basic, topic={acp})
acp_example_data.define_slot_access(
    :answer,
    ## Must be writable for ACP. The answer calculated by ACP
    :writable)
$
_pragma(classify_level=basic, topic={acp})
acp_example_data.define_shared_constant(
    :acpt_type,
    ## The data translation definition to/from ACP. Order and
    ## options must match the ACP definiition
    acpt_type.new_slotted_type(
        acp_example_data,
        {{:float1,acpt_type.float32,:unset?,_false},
         {:float2,acpt_type.float32,:unset?,_false},
         {:answer,acpt_type.float32,:unset?,_true}
        },:name,"acp_example_data"),
    _false)
$

_pragma(classify_level=basic, topic={acp})
_method acp_example_data.new(f1,f2)
    ## Parameters  : f1 - float to multiply by
    ##               f2 - float with 
    ## Returns     : initialized acp_example_data
    ## Function    : 
    ## Methodology : 
    #---------------- HISTORY ---------------------
    # (10/26/2011 - Mark Field, FCSI): Created.
    #----------------------------------------------
    _return _clone.init(f1,f2)

_endmethod
$

_pragma(classify_level=basic, topic={acp})
_method acp_example_data.init(f1,f2)
    ## Parameters  : f1 - float to multiply by
    ##               f2 - float with 
    ## Returns     : initialized acp_example_data
    ## Function    : stores f1 & f2 onto slots
    ## Methodology : 
    #---------------- HISTORY ---------------------
    # (10/26/2011 - Mark Field, FCSI): Created.
    #----------------------------------------------
    .float1 << f1
    .float2 << f2
    _return _self 
_endmethod
$

Above you see the definition of the structure that is passed to/from the ACP. You will notice the acp_example_data class is a argument to the new_slotted_type() method. Also note the 1st element of each definition is the name of the slot. Note the slots have to be writable for the ACP to populate them.

Running Data Marshalling Example

Assuming you still have !acp defined and the haven't called a !acp.close() you can run the following.

MagikSF> debug_print(!acp.marshalling_multiply(6.0,7.0))
$
acp_example_data (:slotted )
acp_example_data!float1    6.00000000000 
acp_example_data!float2    7.00000000000 
acp_example_data!answer    42.0000000000

You will see that the answer slot is populated by the ACP.

Now marshal your objects together and send them to the ACP for processing!

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License