You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
GraalVM version or commit id if built from source:
On PopOs: 22.0.1-graalce and GraalJS 24.0.1
On Debian: graalvm-ce-java17-22.3.0 with JS module
CE or EE: CE
JDK version:
On Pop OS: openjdk 22.0.1 2024-04-16
On Debian: java 17.0.9 2023-10-17 LTS
OS and OS Version: Tested on Debian GNU/Linux 11 (bullseye) server and Pop!_OS 22.04 LTS local machine
Architecture: x86_64
The output of java -Xinternalversion:
on Pop OS: OpenJDK 64-Bit Server VM (22.0.1+8-jvmci-b01) for linux-amd64 JRE (22.0.1+8-jvmci-b01), built on 2024-03-14T14:46:36Z by "buildslave" with gcc 13.2.0
on Debian: Java HotSpot(TM) 64-Bit Server VM (17.0.9+11-LTS-jvmci-23.0-b21) for linux-amd64 JRE (17.0.9+11-LTS-jvmci-23.0-b21), built on Oct 11 2023 14:34:18 by "buildslave" with gcc 11.2.0
Have you verified this issue still happens when using the latest snapshot?
Latest downloadable version on main site
Describe the issue
I want to be able to call from javascript java methods that take Records as arguments.
I don't think GraalJS/VM handles this natively so I wrote a generic function to do it that I use for each Record type I want to use. I uses reflection to convert each record component into its target type thanks to Value.as().
It is working fine except for generic types, most common case is when a record has a property which type is a List of an other record.
I couldn't find a way to use Value.as in this case, it has two signatures, one with a Class, but I'm loosing the type parameter so I end up with a List of wrong typed elements, the other signature is with a TypeLiteral, but I can't dynamically create an instance of this class (or at least I did not figure how).
You'll find hereunder a snippet that shows what is my issue, along with the output of its execution.
At the end it fails as it tries to instanciate a record instance with a wrongly typed argument.
I'm no reflection expert, so maybe there's something that I missed somewhere.
Or maybe there's a simpler way to achieve what I'm trying to do?
If so let me know!
Thanks
Additional question:
From the output we can see that my sub record is converted not when I call Value.as() on the parent list, but when I first access the converted list element to print it. Is there any lazy evaluation mecanism involved here?
Code snippet or code repository that reproduces the issue
packageorg.example;
importorg.graalvm.polyglot.Context;
importorg.graalvm.polyglot.HostAccess;
importorg.graalvm.polyglot.TypeLiteral;
importorg.graalvm.polyglot.Value;
importjava.lang.reflect.Constructor;
importjava.lang.reflect.InvocationTargetException;
importjava.lang.reflect.ParameterizedType;
importjava.lang.reflect.RecordComponent;
importjava.util.List;
publicclassValueAsWithGenericCollectionTest {
public record MainRecord(List<SubRecord> subRecords) {}
public record SubRecord(StringsomeValue) {}
privatestaticfinalList<Class<? extendsRecord>> API_RECORDS = List.of(
MainRecord.class,
SubRecord.class
);
publicstaticvoidmain(String[] args) {
HostAccess.BuilderhostAccessBuilder = HostAccess.newBuilder().
allowPublicAccess(true).
allowAllImplementations(true).
allowAllClassImplementations(true).
allowArrayAccess(true).allowListAccess(true).allowBufferAccess(true).
allowIterableAccess(true).allowIteratorAccess(true).allowMapAccess(true).
allowAccessInheritance(true);
API_RECORDS.forEach(recordClass -> addRecordTargetTypeMapping(recordClass, hostAccessBuilder));
try (ContextjsContext = Context.newBuilder("js")
.allowIO(true)
.allowExperimentalOptions(true).option("js.nashorn-compat", "true") // only to access java getters and setters with just the property name
.option("js.ecmascript-version", "2021")
.option("js.intl-402", "true") // before GraalVM 23.1, was set by default to false
.allowHostAccess(hostAccessBuilder.build())
.build()) {
ValueAsWithGenericCollectionTestjavaApi = newValueAsWithGenericCollectionTest();
jsContext.getBindings("js").putMember("javaApi", javaApi);
jsContext.eval("js", """ const mainData = { subRecords: [ { someValue: "val1" }, { someValue: "val2" } ] }; const firstValue = javaApi.getFirstSubRecordValue(mainData); console.log(firstValue); """);
}
}
publicStringgetFirstSubRecordValue(MainRecordmainRecord) {
returnmainRecord.subRecords.get(0).someValue();
}
privatestatic <RextendsRecord> voidaddRecordTargetTypeMapping(Class<R> recordClass, HostAccess.Builderbuilder) {
builder.targetTypeMapping(Value.class, recordClass, null, value -> convertToRecord(value, recordClass));
}
privatestatic <RextendsRecord> RconvertToRecord(Valuevalue, Class<R> recordClass) {
if (value == null || value.isNull()) returnnull;
RecordComponent[] recordComponents = recordClass.getRecordComponents();
Object[] params = newObject[recordComponents.length];
for (inti = 0; i < recordComponents.length; i++) {
RecordComponentcomponent = recordComponents[i];
StringcomponentName = component.getName();
// this is demo code, not handling case where there is no valueValuepValue = value.getMember(componentName);
System.out.println("[" + componentName + "] Source value class: " + pValue.getClass());
// I guess that due to type erasure, we loose the actual parameter typeClass<?> recordCompType = component.getType();
System.out.println("[" + componentName + "] Record component class: " + recordCompType);
// I can get actual type arguments through this, but this is not a Class instance, and I see no way to build an instance of TypeLiteral from itif (MainRecord.class.equals(recordClass)) {
ParameterizedTypegenericType = (ParameterizedType) component.getGenericType();
System.out.println("[" + componentName + "] Record component generic type: " + genericType);
}
// this would work, but I want a generic behavior, not single class one:if (MainRecord.class.equals(recordClass)) {
List<SubRecord> targetValueFromStaticTypeLiteral = pValue.as(newTypeLiteral<List<SubRecord>>() {});
System.out.println("[" + componentName + "] Static converted class: " + targetValueFromStaticTypeLiteral.getClass());
System.out.println("[" + componentName + "] Static converted sub element class: " + targetValueFromStaticTypeLiteral.get(0).getClass());
}
// I'm getting List<Truc> instead of List<SubRecord>ObjecttargetValue = pValue.as(recordCompType);
System.out.println("[" + componentName + "] Generic converted class: " + targetValue.getClass());
if (MainRecord.class.equals(recordClass)) {
System.out.println("[" + componentName + "] Generic converted sub element class: " + ((List<?>) targetValue).get(0).getClass());
}
params[i] = targetValue;
}
try {
Constructor<?> constructor = recordClass.getConstructors()[0];
@SuppressWarnings("unchecked")
RrecordInstance = (R) constructor.newInstance(params);
System.out.println("Record instance: " + recordInstance);
returnrecordInstance;
} catch (InstantiationException | IllegalAccessException | InvocationTargetExceptione) {
thrownewRuntimeException("could not deserialize record of class " + recordClass.getSimpleName(), e);
}
}
}
Execution output
[subRecords] Source value class: class org.graalvm.polyglot.Value
[subRecords] Record component class: interface java.util.List
[subRecords] Record component generic type: java.util.List<org.example.ValueAsWithGenericCollectionTest$SubRecord>
[subRecords] Static converted class: class com.oracle.truffle.polyglot.PolyglotList
[someValue] Source value class: class org.graalvm.polyglot.Value
[someValue] Record component class: class java.lang.String
[someValue] Generic converted class: class java.lang.String
Record instance: SubRecord[someValue=val1]
[subRecords] Static converted sub element class: class org.example.ValueAsWithGenericCollectionTest$SubRecord
[subRecords] Generic converted class: class com.oracle.truffle.polyglot.PolyglotList
[subRecords] Generic converted sub element class: class com.oracle.truffle.polyglot.PolyglotMap
Record instance: MainRecord[subRecords=[object Array]]
Exception in thread "main" class com.oracle.truffle.polyglot.PolyglotMap cannot be cast to class org.example.ValueAsWithGenericCollectionTest$SubRecord (com.oracle.truffle.polyglot.PolyglotMap and org.example.ValueAsWithGenericCollectionTest$SubRecord are in unnamed module of loader 'app')
at org.example.ValueAsWithGenericCollectionTest.getFirstSubRecordValue(ValueAsWithGenericCollectionTest.java:64)
at <js> :program(Unnamed:8:127-166)
at org.graalvm.polyglot.Context.eval(Context.java:428)
at org.example.ValueAsWithGenericCollectionTest.main(ValueAsWithGenericCollectionTest.java:49)
Caused by host exception: java.lang.ClassCastException: class com.oracle.truffle.polyglot.PolyglotMap cannot be cast to class org.example.ValueAsWithGenericCollectionTest$SubRecord (com.oracle.truffle.polyglot.PolyglotMap and org.example.ValueAsWithGenericCollectionTest$SubRecord are in unnamed module of loader 'app')
The text was updated successfully, but these errors were encountered:
Describe GraalVM and your environment :
On PopOs: 22.0.1-graalce and GraalJS 24.0.1
On Debian: graalvm-ce-java17-22.3.0 with JS module
On Pop OS: openjdk 22.0.1 2024-04-16
On Debian: java 17.0.9 2023-10-17 LTS
java -Xinternalversion
:on Pop OS: OpenJDK 64-Bit Server VM (22.0.1+8-jvmci-b01) for linux-amd64 JRE (22.0.1+8-jvmci-b01), built on 2024-03-14T14:46:36Z by "buildslave" with gcc 13.2.0
on Debian: Java HotSpot(TM) 64-Bit Server VM (17.0.9+11-LTS-jvmci-23.0-b21) for linux-amd64 JRE (17.0.9+11-LTS-jvmci-23.0-b21), built on Oct 11 2023 14:34:18 by "buildslave" with gcc 11.2.0
Have you verified this issue still happens when using the latest snapshot?
Latest downloadable version on main site
Describe the issue
I want to be able to call from javascript java methods that take Records as arguments.
I don't think GraalJS/VM handles this natively so I wrote a generic function to do it that I use for each Record type I want to use. I uses reflection to convert each record component into its target type thanks to Value.as().
It is working fine except for generic types, most common case is when a record has a property which type is a List of an other record.
I couldn't find a way to use Value.as in this case, it has two signatures, one with a Class, but I'm loosing the type parameter so I end up with a List of wrong typed elements, the other signature is with a TypeLiteral, but I can't dynamically create an instance of this class (or at least I did not figure how).
You'll find hereunder a snippet that shows what is my issue, along with the output of its execution.
At the end it fails as it tries to instanciate a record instance with a wrongly typed argument.
I'm no reflection expert, so maybe there's something that I missed somewhere.
Or maybe there's a simpler way to achieve what I'm trying to do?
If so let me know!
Thanks
Additional question:
From the output we can see that my sub record is converted not when I call Value.as() on the parent list, but when I first access the converted list element to print it. Is there any lazy evaluation mecanism involved here?
Code snippet or code repository that reproduces the issue
Execution output
The text was updated successfully, but these errors were encountered: