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