Search code examples
ballerinaballerina-java-interop

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


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?


Solution

  • 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
    }