How to pass java Map to a java function from ballerina?

46 Views Asked by At

I need to pass a java Map to a java function from Ballerina. In order to fill the java map, I need to convert Ballerina string to java.lang.Object type as put method in java map accepts javalang:Object(ie. Generated bal file relevant to java.lang.Object). java:fromString returns a handle. AFAIU handle is a reference to the java object, not the object itself. So any idea how to achieve above requirement?

Here is the I logic need to implement;

javautil:Map? javaMap = balMaptoJavaMap(balMap);

function balMaptoJavaMap(map<string> properties) returns javautil:Map? {
    
    javautil:HashMap hashMap = javautil:newHashMap1();
    if (properties.length() > 0) {
        foreach string prop in properties.keys() {
            javalang:Object|error propKeyObj = prop.ensureType(javalang:Object);
            javalang:Object|error propObj = (properties.get(prop)).ensureType(javalang:Object);
            if (propKeyObj is error || propObj is error) {
                continue;
            }
            _ = hashMap.put(propKeyObj, propObj);
        }
    }
    javautil:Map|error javaMap = java:cast(hashMap);
    if javaMap is error {
        return ();
    }
    return javaMap;
}

In here I am getting TypeCastingErrors for following lines;

javalang:Object|error propKeyObj = prop.ensureType(javalang:Object);

Actual requirement of this line is to convert a bal string to javalang:Object in order to call hashMap.put().

javautil:Map|error javaMap = java:cast(hashMap);

As explained in Bindgen Tool this line shouldn't be giving an error. Or only downcasting is supported through java:cast?

1

There are 1 best solutions below

0
On BEST ANSWER

ensureType and casting simply check if the value belongs to the target type (except with numeric values where it does a conversion). So something like javalang:Object|error propKeyObj = prop.ensureType(javalang:Object); will result in an error because you are checking if prop (which is a Ballerina string value) belongs to javalang:Object, which is not a type definition that includes string.

With generated bindings, you can create the javalang:Object wrapping the handle value.

public function main() {
    javautil:HashMap hashMap = javautil:newHashMap1();
    
    map<string> properties = {
        config: "default",
        enabled: "true"
    };

    foreach [string, string] [k, v] in properties.entries() {
        _ = hashMap.put(new (java:fromString(k)), new (java:fromString(v)));
    }

    // Argument is the same as doing `new javalang:Object(java:fromString("config"))`
    lang:Object configObj = hashMap.get(new (java:fromString("config")));
    handle configHandle = configObj.jObj;
    string? configStr = java:toString(configHandle);
    io:println(configStr); // default

    boolean containsKey = hashMap.containsKey(new (java:fromString("enabled")));
    io:println(containsKey); // true

    io:println(hashMap.containsKey(new (java:fromString("factor")))); // false
}

But you can create a HashMap without using the bindgen tool in Ballerina as well. Simple example with HashMap without code generated using the bindgen tool as follows.

import ballerina/io;
import ballerina/jballerina.java;

function newHashMap() returns handle = @java:Constructor {
    'class: "java.util.HashMap"
} external;

function put(handle receiver, handle key, handle value) returns handle = @java:Method {
    'class: "java.util.HashMap"
} external;

function get(handle receiver, handle key) returns handle = @java:Method {
    'class: "java.util.HashMap"
} external;

function containsKey(handle receiver, handle key) returns boolean = @java:Method {
    'class: "java.util.HashMap"
} external;

public function main() {
    handle hashMap = newHashMap();

    map<string> properties = {
        config: "default",
        enabled: "true"
    };

    foreach [string, string] [k, v] in properties.entries() {
        _ = put(hashMap, java:fromString(k), java:fromString(v));
    }

    handle configHandle = get(hashMap, java:fromString("config"));
    string? configString = java:toString(configHandle);
    io:println(configString); // default

    io:println(containsKey(hashMap, java:fromString("enabled"))); // true

    io:println(containsKey(hashMap, java:fromString("factor"))); // false
}