diff --git a/.gitignore b/.gitignore
index 098af53bc4..b70cc9f0e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@ node_modules/
lerna-debug.log
.DS_Store
.idea
+.vs
/dist
diff --git a/packages/jsii-calc/lib/compliance.ts b/packages/jsii-calc/lib/compliance.ts
index 81e7c5739d..82a37aa5bb 100644
--- a/packages/jsii-calc/lib/compliance.ts
+++ b/packages/jsii-calc/lib/compliance.ts
@@ -1583,3 +1583,20 @@ export class ConsumersOfThisCrazyTypeSystem {
return { a: obj.a, b: obj.b, c: obj.c };
}
}
+
+
+//
+// Ensure the JSII kernel can pass "this" out to JSII remotes from within the constructor (this is dirty, but possible)
+///
+export abstract class PartiallyInitializedThisConsumer {
+ public abstract consumePartiallyInitializedThis(obj: ConstructorPassesThisOut, dt: Date, ev: AllTypesEnum): string;
+}
+
+export class ConstructorPassesThisOut {
+ constructor(consumer: PartiallyInitializedThisConsumer) {
+ const result = consumer.consumePartiallyInitializedThis(this, new Date(0), AllTypesEnum.ThisIsGreat);
+ if (result !== 'OK') {
+ throw new Error(`Expected OK but received ${result}`);
+ }
+ }
+}
diff --git a/packages/jsii-calc/package.json b/packages/jsii-calc/package.json
index 0a9caf90ce..6c1019c133 100644
--- a/packages/jsii-calc/package.json
+++ b/packages/jsii-calc/package.json
@@ -36,11 +36,13 @@
],
"dependencies": {
"@scope/jsii-calc-base": "^0.8.0",
+ "@scope/jsii-calc-base-of-base": "^0.8.0",
"@scope/jsii-calc-lib": "^0.8.0",
"jsii-calc-bundled": "^0.8.0"
},
"peerDependencies": {
"@scope/jsii-calc-base": "^0.8.0",
+ "@scope/jsii-calc-base-of-base": "^0.8.0",
"@scope/jsii-calc-lib": "^0.8.0"
},
"devDependencies": {
diff --git a/packages/jsii-calc/test/assembly.jsii b/packages/jsii-calc/test/assembly.jsii
index 20fac29cd7..dc9f5f0401 100644
--- a/packages/jsii-calc/test/assembly.jsii
+++ b/packages/jsii-calc/test/assembly.jsii
@@ -84,6 +84,30 @@
},
"version": "0.8.0"
},
+ "@scope/jsii-calc-base-of-base": {
+ "peer": true,
+ "targets": {
+ "dotnet": {
+ "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace",
+ "packageId": "Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId"
+ },
+ "java": {
+ "maven": {
+ "artifactId": "calculator-base-of-base",
+ "groupId": "software.amazon.jsii.tests"
+ },
+ "package": "software.amazon.jsii.tests.calculator.baseofbase"
+ },
+ "js": {
+ "npm": "@scope/jsii-calc-base-of-base"
+ },
+ "python": {
+ "distName": "scope.jsii-calc-base-of-base",
+ "module": "scope.jsii_calc_base_of_base"
+ }
+ },
+ "version": "0.8.0"
+ },
"@scope/jsii-calc-lib": {
"dependencies": {
"@scope/jsii-calc-base": {
@@ -1250,6 +1274,23 @@
}
]
},
+ "jsii-calc.ConstructorPassesThisOut": {
+ "assembly": "jsii-calc",
+ "fqn": "jsii-calc.ConstructorPassesThisOut",
+ "initializer": {
+ "initializer": true,
+ "parameters": [
+ {
+ "name": "consumer",
+ "type": {
+ "fqn": "jsii-calc.PartiallyInitializedThisConsumer"
+ }
+ }
+ ]
+ },
+ "kind": "class",
+ "name": "ConstructorPassesThisOut"
+ },
"jsii-calc.Constructors": {
"assembly": "jsii-calc",
"fqn": "jsii-calc.Constructors",
@@ -3327,6 +3368,45 @@
],
"name": "OverrideReturnsObject"
},
+ "jsii-calc.PartiallyInitializedThisConsumer": {
+ "abstract": true,
+ "assembly": "jsii-calc",
+ "fqn": "jsii-calc.PartiallyInitializedThisConsumer",
+ "initializer": {
+ "initializer": true
+ },
+ "kind": "class",
+ "methods": [
+ {
+ "abstract": true,
+ "name": "consumePartiallyInitializedThis",
+ "parameters": [
+ {
+ "name": "obj",
+ "type": {
+ "fqn": "jsii-calc.ConstructorPassesThisOut"
+ }
+ },
+ {
+ "name": "dt",
+ "type": {
+ "primitive": "date"
+ }
+ },
+ {
+ "name": "ev",
+ "type": {
+ "fqn": "jsii-calc.AllTypesEnum"
+ }
+ }
+ ],
+ "returns": {
+ "primitive": "string"
+ }
+ }
+ ],
+ "name": "PartiallyInitializedThisConsumer"
+ },
"jsii-calc.Polymorphism": {
"assembly": "jsii-calc",
"fqn": "jsii-calc.Polymorphism",
@@ -4500,5 +4580,5 @@
}
},
"version": "0.8.0",
- "fingerprint": "WIFIhqgEwUDjCsDr7gliujhqcKZQSkDP+NstBfmdvZU="
+ "fingerprint": "J0bhm0cu1dlTAyMfBlAMWCVXZf6e8k2pHkmxye6P7Wk="
}
diff --git a/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/Amazon.JSII.Runtime.IntegrationTests.csproj b/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/Amazon.JSII.Runtime.IntegrationTests.csproj
index bb130050b1..59dea45c8f 100644
--- a/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/Amazon.JSII.Runtime.IntegrationTests.csproj
+++ b/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/Amazon.JSII.Runtime.IntegrationTests.csproj
@@ -7,20 +7,20 @@
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
diff --git a/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs b/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs
index 8b35565b87..7c3e510f5f 100644
--- a/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs
+++ b/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs
@@ -892,10 +892,31 @@ public void EraseUnsetDataValues()
Assert.True(EraseUndefinedHashValues.DoesKeyExist(opts, "option1"));
Assert.False(EraseUndefinedHashValues.DoesKeyExist(opts, "option2"));
-
+
Assert.Equal(new Dictionary { ["prop2"] = "value2" }, EraseUndefinedHashValues.Prop1IsNull());
Assert.Equal(new Dictionary { [ "prop1"] = "value1" }, EraseUndefinedHashValues.Prop2IsUndefined());
}
+
+ [Fact(DisplayName = Prefix + nameof(ObjectIdDoesNotGetReallocatedWhenTheConstructorPassesThisOut), Skip = "Currently broken")]
+ public void ObjectIdDoesNotGetReallocatedWhenTheConstructorPassesThisOut()
+ {
+ var reflector = new PartiallyInitializedThisConsumerImpl();
+ var obj = new ConstructorPassesThisOut(reflector);
+
+ Assert.NotNull(obj);
+ }
+
+ class PartiallyInitializedThisConsumerImpl : PartiallyInitializedThisConsumer
+ {
+ public override String ConsumePartiallyInitializedThis(ConstructorPassesThisOut obj, DateTime dt, AllTypesEnum ev)
+ {
+ Assert.NotNull(obj);
+ Assert.Equal(new DateTime(0), dt);
+ Assert.Equal(AllTypesEnum.ThisIsGreat, ev);
+
+ return "OK";
+ }
+ }
class NumberReturner : DeputyBase, IIReturnsNumber
{
public NumberReturner(double number)
diff --git a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/CallbackExtensions.cs b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/CallbackExtensions.cs
index 8fe9067aae..fef3002435 100644
--- a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/CallbackExtensions.cs
+++ b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/CallbackExtensions.cs
@@ -4,7 +4,9 @@
using Amazon.JSII.Runtime.Deputy;
using Amazon.JSII.Runtime.Services;
using Amazon.JSII.Runtime.Services.Converters;
+using Newtonsoft.Json.Linq;
using System;
+using System.Linq;
using System.Reflection;
namespace Amazon.JSII.Runtime
@@ -71,11 +73,11 @@ static CallbackResult InvokeMethod(InvokeRequest request, IReferenceMap referenc
if (methodInfo == null)
{
- throw new InvalidOperationException($"Received callback for {deputy.GetType().Name}.{request.Method} getter, but this method does not exist");
+ throw new InvalidOperationException($"Received callback for {deputy.GetType().Name}.{request.Method} method, but this method does not exist");
}
JsiiMethodAttribute attribute = methodInfo.GetCustomAttribute();
- return new CallbackResult(attribute?.Returns, methodInfo.Invoke(deputy, request.Arguments));
+ return new CallbackResult(attribute?.Returns, methodInfo.Invoke(deputy, request.Arguments.Select(arg => FromKernel(arg, referenceMap)).ToArray()));
}
static CallbackResult InvokeGetter(GetRequest request, IReferenceMap referenceMap)
@@ -117,7 +119,24 @@ static void InvokeSetter(SetRequest request, IReferenceMap referenceMap)
throw new InvalidOperationException($"Received callback for {deputy.GetType().Name}.{request.Property} setter, but this property does not have a setter");
}
- methodInfo.Invoke(deputy, new object[] { request.Value });
+ methodInfo.Invoke(deputy, new object[] { FromKernel(request.Value, referenceMap) });
+ }
+
+ /*
+ * This is a temporary workaround / hack to solve an immediate problem, but does not completely solve the
+ * problem to it's full extent. See https://github.com/awslabs/jsii/issues/404 for more information.
+ */
+ private static object FromKernel(object obj, IReferenceMap referenceMap)
+ {
+ if (obj is JObject)
+ {
+ var prop = ((JObject)obj).Property("$jsii.byref");
+ if (prop != null)
+ {
+ return referenceMap.GetOrCreateNativeReference(new ByRefValue(prop.Value.Value()));
+ }
+ }
+ return obj;
}
}
diff --git a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Deputy/DeputyBase.cs b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Deputy/DeputyBase.cs
index c315018125..bf04054421 100644
--- a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Deputy/DeputyBase.cs
+++ b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Deputy/DeputyBase.cs
@@ -54,7 +54,7 @@ protected DeputyBase(DeputyProps props = null)
Reference = new ByRefValue(response["$jsii.byref"]);
IReferenceMap referenceMap = serviceProvider.GetRequiredService();
- referenceMap.AddNativeReference(Reference, this);
+ referenceMap.AddNativeReference(Reference, this, true);
Override[] GetOverrides()
{
diff --git a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/IReferenceMap.cs b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/IReferenceMap.cs
index 3294f2de5f..48de5ea9db 100644
--- a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/IReferenceMap.cs
+++ b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/IReferenceMap.cs
@@ -5,7 +5,7 @@ namespace Amazon.JSII.Runtime.Services
{
public interface IReferenceMap
{
- void AddNativeReference(ByRefValue reference, DeputyBase nativeReference);
+ void AddNativeReference(ByRefValue reference, DeputyBase nativeReference, bool force = false);
DeputyBase GetOrCreateNativeReference(ObjectReference reference);
diff --git a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/ReferenceMap.cs b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/ReferenceMap.cs
index 609ab784fb..4ffaef5cc6 100644
--- a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/ReferenceMap.cs
+++ b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/ReferenceMap.cs
@@ -16,9 +16,9 @@ public ReferenceMap(ITypeCache types)
_types = types ?? throw new ArgumentNullException(nameof(types));
}
- public void AddNativeReference(ByRefValue reference, DeputyBase nativeReference)
+ public void AddNativeReference(ByRefValue reference, DeputyBase nativeReference, bool force)
{
- if (_references.ContainsKey(reference.Value))
+ if (_references.ContainsKey(reference.Value) && !force)
{
throw new ArgumentException(
$"Cannot add reference for {reference.Value}: A reference with this name already exists",
diff --git a/packages/jsii-java-runtime-test/project/src/test/java/software/amazon/jsii/testing/ComplianceTest.java b/packages/jsii-java-runtime-test/project/src/test/java/software/amazon/jsii/testing/ComplianceTest.java
index b15956ab4c..9791cc269a 100644
--- a/packages/jsii-java-runtime-test/project/src/test/java/software/amazon/jsii/testing/ComplianceTest.java
+++ b/packages/jsii-java-runtime-test/project/src/test/java/software/amazon/jsii/testing/ComplianceTest.java
@@ -8,6 +8,7 @@
import software.amazon.jsii.tests.calculator.AbstractClassReturner;
import software.amazon.jsii.tests.calculator.Add;
import software.amazon.jsii.tests.calculator.AllTypes;
+import software.amazon.jsii.tests.calculator.AllTypesEnum;
import software.amazon.jsii.tests.calculator.AsyncVirtualMethods;
import software.amazon.jsii.tests.calculator.Calculator;
import software.amazon.jsii.tests.calculator.CalculatorProps;
@@ -38,6 +39,7 @@
import software.amazon.jsii.tests.calculator.NullShouldBeTreatedAsUndefined;
import software.amazon.jsii.tests.calculator.NullShouldBeTreatedAsUndefinedData;
import software.amazon.jsii.tests.calculator.NumberGenerator;
+import software.amazon.jsii.tests.calculator.PartiallyInitializedThisConsumer;
import software.amazon.jsii.tests.calculator.Polymorphism;
import software.amazon.jsii.tests.calculator.Power;
import software.amazon.jsii.tests.calculator.PublicClass;
@@ -55,6 +57,7 @@
import software.amazon.jsii.tests.calculator.lib.Number;
import software.amazon.jsii.tests.calculator.lib.StructWithOnlyOptionals;
import software.amazon.jsii.tests.calculator.lib.Value;
+import software.amazon.jsii.tests.calculator.ConstructorPassesThisOut;
import java.io.IOException;
import java.time.Instant;
@@ -1010,6 +1013,27 @@ public void eraseUnsetDataValues() {
assertEquals("{prop1=value1}", EraseUndefinedHashValues.prop2IsUndefined().toString());
}
+ @Test
+ public void objectIdDoesNotGetReallocatedWhenTheConstructorPassesThisOut() {
+ final PartiallyInitializedThisConsumer reflector = new PartiallyInitializedThisConsumerImpl();
+ final ConstructorPassesThisOut object = new ConstructorPassesThisOut(reflector);
+
+ assertTrue(object != null);
+ }
+
+ static class PartiallyInitializedThisConsumerImpl extends PartiallyInitializedThisConsumer {
+ @Override
+ public String consumePartiallyInitializedThis(final ConstructorPassesThisOut obj,
+ final Instant dt,
+ final AllTypesEnum en) {
+ assertNotNull(obj);
+ assertEquals(Instant.EPOCH, dt);
+ assertEquals(AllTypesEnum.ThisIsGreat, en);
+
+ return "OK";
+ }
+ }
+
static class MulTen extends Multiply {
public MulTen(final int value) {
super(new Number(value), new Number(10));
diff --git a/packages/jsii-java-runtime-test/project/src/test/java/software/amazon/jsii/testing/JsiiClientTest.java b/packages/jsii-java-runtime-test/project/src/test/java/software/amazon/jsii/testing/JsiiClientTest.java
index 3c9da690e4..480383adbb 100644
--- a/packages/jsii-java-runtime-test/project/src/test/java/software/amazon/jsii/testing/JsiiClientTest.java
+++ b/packages/jsii-java-runtime-test/project/src/test/java/software/amazon/jsii/testing/JsiiClientTest.java
@@ -116,7 +116,7 @@ public void asyncMethodOverrides() {
assertEquals("overrideMe", first.getInvoke().getMethod());
assertEquals("myCookie", first.getCookie());
assertEquals(1, first.getInvoke().getArgs().size());
- assertEquals(10, first.getInvoke().getArgs().get(0));
+ assertEquals(JsiiObjectMapper.valueToTree(10), first.getInvoke().getArgs().get(0));
assertEquals(obj.getObjId(), JsiiObjectRef.parse(first.getInvoke().getObjref()).getObjId());
// now complete the callback with some override value
@@ -146,7 +146,7 @@ public void asyncMethodOverridesThrow() {
assertEquals("overrideMe", first.getInvoke().getMethod());
assertEquals("myCookie", first.getCookie());
assertEquals(1, first.getInvoke().getArgs().size());
- assertEquals(10, first.getInvoke().getArgs().get(0));
+ assertEquals(JsiiObjectMapper.valueToTree(10), first.getInvoke().getArgs().get(0));
assertEquals(obj.getObjId(), JsiiObjectRef.parse(first.getInvoke().getObjref()).getObjId());
// now complete the callback with an error
@@ -171,7 +171,7 @@ public void syncVirtualMethods() {
jsiiRuntime.setCallbackHandler(callback -> {
assertEquals(obj.getObjId(), JsiiObjectRef.parse(callback.getInvoke().getObjref()).getObjId());
assertEquals("virtualMethod", callback.getInvoke().getMethod());
- assertEquals(10, callback.getInvoke().getArgs().get(0));
+ assertEquals(JsiiObjectMapper.valueToTree(10), callback.getInvoke().getArgs().get(0));
assertEquals("myCookie", callback.getCookie());
// interact with jsii from inside the callback
@@ -226,8 +226,7 @@ public void staticMethods() {
*/
@Test
public void serializeViaJsiiToJsonIfExists() {
- JsiiObjectMapper om = JsiiObjectMapper.instance;
- JsonNode result = om.valueToTree(new JsiiSerializable() {
+ JsonNode result = JsiiObjectMapper.INSTANCE.valueToTree(new JsiiSerializable() {
public JsonNode $jsii$toJson() {
ObjectNode node = JSON.objectNode();
node.set("foo", OM.valueToTree("bar"));
diff --git a/packages/jsii-java-runtime/package.json b/packages/jsii-java-runtime/package.json
index 5f6ddfcfbb..6c5509e8dd 100644
--- a/packages/jsii-java-runtime/package.json
+++ b/packages/jsii-java-runtime/package.json
@@ -8,7 +8,7 @@
"scripts": {
"gen": "/bin/bash ./generate.sh",
"build": "tsc && npm run gen && cd project && mvn deploy -D altDeploymentRepository=local::default::file://${PWD}/../maven-repo",
- "test": "echo 'Tests are performed by jsii-java-runtime-test'",
+ "test": "echo 'Tests are run as part of the build target'",
"package": "package-java"
},
"devDependencies": {
diff --git a/packages/jsii-java-runtime/pom.xml.t.js b/packages/jsii-java-runtime/pom.xml.t.js
index 12eb2aed90..d77310c4d0 100644
--- a/packages/jsii-java-runtime/pom.xml.t.js
+++ b/packages/jsii-java-runtime/pom.xml.t.js
@@ -83,6 +83,30 @@ process.stdout.write(`
[1.3.2,)
provided
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.4.1
+ test
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.4.1
+ test
+
+
+
+
+ org.mockito
+ mockito-core
+ 2.25.1
+ test
+
@@ -128,6 +152,16 @@ process.stdout.write(`
protected
+
+
+ maven-surefire-plugin
+ 2.22.0
+
+
+
+ maven-failsafe-plugin
+ 2.22.0
+
diff --git a/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiClient.java b/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiClient.java
index 7f5b84d3db..bdd86f20cb 100644
--- a/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiClient.java
+++ b/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiClient.java
@@ -5,7 +5,6 @@
import software.amazon.jsii.api.JsiiOverride;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -27,16 +26,6 @@ public final class JsiiClient {
*/
private static final JsonNodeFactory JSON = JsonNodeFactory.instance;
- /**
- * JSON object mapper.
- */
- private static final ObjectMapper STD_OM = new ObjectMapper();
-
- /**
- * Jsii custom object mapper.
- */
- private static final JsiiObjectMapper JSII_OM = JsiiObjectMapper.instance;
-
/**
* TCP port to connect to (always "localhost").
*/
@@ -95,7 +84,7 @@ public JsiiObjectRef createObject(final String fqn,
request.setArgs(initializerArgs);
request.setOverrides(overrides);
- ObjectNode req = JSII_OM.valueToTree(request);
+ ObjectNode req = JsiiObjectMapper.valueToTree(request);
req.put("api", "create");
JsonNode resp = this.runtime.requestResponse(req);
@@ -245,11 +234,7 @@ public List pendingCallbacks() {
List result = new ArrayList<>();
callbacksArray.forEach(node -> {
- try {
- result.add(STD_OM.treeToValue(node, Callback.class));
- } catch (JsonProcessingException e) {
- throw new JsiiException(e);
- }
+ result.add(JsiiObjectMapper.treeToValue(node, Callback.class));
});
return result;
diff --git a/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiEngine.java b/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiEngine.java
index 975d3a29f7..c957574a31 100644
--- a/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiEngine.java
+++ b/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiEngine.java
@@ -5,7 +5,10 @@
import software.amazon.jsii.api.InvokeRequest;
import software.amazon.jsii.api.JsiiOverride;
import software.amazon.jsii.api.SetRequest;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.base.Throwables;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -45,11 +48,6 @@ public final class JsiiEngine implements JsiiCallbackHandler {
*/
private final JsiiRuntime runtime = new JsiiRuntime();
- /**
- * JSON object mapper.
- */
- private static final JsiiObjectMapper OM = JsiiObjectMapper.instance;
-
/**
* The set of modules we already loaded into the VM.
*/
@@ -253,7 +251,7 @@ private JsiiObject createNative(final String fqn) {
+ e.getMessage(), e);
}
} catch (ClassNotFoundException e) {
- System.err.println("WARNING: Cannot find the class: " + fqn + ". Defaulting to JsiiObject");
+ this.log("WARNING: Cannot find the class: %s. Defaulting to JsiiObject", fqn);
return new JsiiObject(JsiiObject.InitializationMode.Jsii);
}
}
@@ -323,7 +321,7 @@ private JsonNode invokeCallbackGet(final GetRequest req) {
String methodName = javaScriptPropertyToJavaPropertyName("get", req.getProperty());
try {
Method getter = obj.getClass().getMethod(methodName);
- return OM.valueToTree(invokeMethod(obj, getter));
+ return JsiiObjectMapper.valueToTree(invokeMethod(obj, getter));
} catch (NoSuchMethodException e) {
throw new JsiiException(e);
}
@@ -350,7 +348,8 @@ private JsonNode invokeCallbackSet(final SetRequest req) {
throw new JsiiException("Unable to find property setter " + setterMethodName);
}
- return OM.valueToTree(invokeMethod(obj, setter, req.getValue()));
+ final Object arg = JsiiObjectMapper.treeToValue(req.getValue(), setter.getParameterTypes()[0]);
+ return JsiiObjectMapper.valueToTree(invokeMethod(obj, setter, arg));
}
/**
@@ -362,7 +361,14 @@ private JsonNode invokeCallbackSet(final SetRequest req) {
private JsonNode invokeCallbackMethod(final InvokeRequest req, final String cookie) {
Object obj = this.getObject(req.getObjref());
Method method = this.findCallbackMethod(obj.getClass(), cookie);
- return OM.valueToTree(invokeMethod(obj, method, req.getArgs().toArray()));
+
+ final Class>[] argTypes = method.getParameterTypes();
+ final Object[] args = new Object[argTypes.length];
+ for (int i = 0; i < argTypes.length; i++) {
+ args[i] = JsiiObjectMapper.treeToValue(req.getArgs().get(i), argTypes[i]);
+ }
+
+ return JsiiObjectMapper.valueToTree(invokeMethod(obj, method, args));
}
/**
@@ -379,7 +385,12 @@ private Object invokeMethod(final Object obj, final Method method, final Object.
method.setAccessible(true);
try {
- return method.invoke(obj, args);
+ try {
+ return method.invoke(obj, args);
+ } catch (Exception e) {
+ this.log("Error while invoking %s with %s: %s", method, Arrays.toString(args), Throwables.getStackTraceAsString(e));
+ throw e;
+ }
} catch (InvocationTargetException e) {
throw new JsiiException(e.getTargetException());
} catch (IllegalAccessException e) {
@@ -508,6 +519,10 @@ private static Collection discoverOverrides(final Class> classTo
return overrides.values();
}
+ private void log(final String format, final Object... args) {
+ System.err.println(String.format(format, args));
+ }
+
/**
* Attempts to find the @Jsii annotation from a type.
* @param type The type.
diff --git a/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiObject.java b/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiObject.java
index d3f1afc2b8..d6cc888cf6 100644
--- a/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiObject.java
+++ b/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiObject.java
@@ -11,11 +11,6 @@
*/
public class JsiiObject implements JsiiSerializable {
- /**
- * JSON object mapper.
- */
- private static final JsiiObjectMapper OM = JsiiObjectMapper.instance;
-
/**
* The jsii engine used by this object.
*/
@@ -58,16 +53,11 @@ public enum InitializationMode {
*/
@Nullable
protected final T jsiiCall(final String method, final Class returnType, @Nullable final Object... args) {
- try {
- return OM.treeToValue(JsiiObject.engine.getClient().callMethod(
- this.objRef,
- method,
- OM.valueToTree(args)),
- returnType);
-
- } catch (JsonProcessingException e) {
- throw new JsiiException(e);
- }
+ return JsiiObjectMapper.treeToValue(JsiiObject.engine.getClient()
+ .callMethod(this.objRef,
+ method,
+ JsiiObjectMapper.valueToTree(args)),
+ returnType);
}
/**
@@ -82,16 +72,9 @@ protected final T jsiiCall(final String method, final Class returnType, @
@Nullable
protected static T jsiiStaticCall(final Class> nativeClass, final String method, final Class returnType, @Nullable final Object... args) {
String fqn = engine.loadModuleForClass(nativeClass);
- try {
- return OM.treeToValue(engine.getClient().callStaticMethod(
- fqn,
- method,
- OM.valueToTree(args)),
- returnType);
-
- } catch (JsonProcessingException e) {
- throw new JsiiException(e);
- }
+ return JsiiObjectMapper.treeToValue(engine.getClient()
+ .callStaticMethod(fqn, method, JsiiObjectMapper.valueToTree(args)),
+ returnType);
}
/**
@@ -104,17 +87,12 @@ protected static T jsiiStaticCall(final Class> nativeClass, final String m
*/
@Nullable
protected final T jsiiAsyncCall(final String method, final Class returnType, @Nullable final Object... args) {
- try {
- JsiiClient client = engine.getClient();
- JsiiPromise promise = client.beginAsyncMethod(this.objRef, method, OM.valueToTree(args));
+ JsiiClient client = engine.getClient();
+ JsiiPromise promise = client.beginAsyncMethod(this.objRef, method, JsiiObjectMapper.valueToTree(args));
- engine.processAllPendingCallbacks();
+ engine.processAllPendingCallbacks();
- JsonNode ret = client.endAsyncMethod(promise);
- return OM.treeToValue(ret, returnType);
- } catch (JsonProcessingException e) {
- throw new JsiiException(e);
- }
+ return JsiiObjectMapper.treeToValue(client.endAsyncMethod(promise), returnType);
}
/**
@@ -126,11 +104,7 @@ protected final T jsiiAsyncCall(final String method, final Class returnTy
*/
@Nullable
protected final T jsiiGet(final String property, final Class type) {
- try {
- return OM.treeToValue(engine.getClient().getPropertyValue(this.objRef, property), type);
- } catch (JsonProcessingException e) {
- throw new JsiiException(e);
- }
+ return JsiiObjectMapper.treeToValue(engine.getClient().getPropertyValue(this.objRef, property), type);
}
/**
@@ -143,12 +117,8 @@ protected final T jsiiGet(final String property, final Class type) {
*/
@Nullable
protected static T jsiiStaticGet(final Class> nativeClass, final String property, final Class type) {
- try {
- String fqn = engine.loadModuleForClass(nativeClass);
- return OM.treeToValue(engine.getClient().getStaticPropertyValue(fqn, property), type);
- } catch (JsonProcessingException e) {
- throw new JsiiException(e);
- }
+ String fqn = engine.loadModuleForClass(nativeClass);
+ return JsiiObjectMapper.treeToValue(engine.getClient().getStaticPropertyValue(fqn, property), type);
}
/**
@@ -157,7 +127,7 @@ protected static T jsiiStaticGet(final Class> nativeClass, final String pr
* @param value The property value.
*/
protected final void jsiiSet(final String property, @Nullable final Object value) {
- engine.getClient().setPropertyValue(this.objRef, property, OM.valueToTree(value));
+ engine.getClient().setPropertyValue(this.objRef, property, JsiiObjectMapper.valueToTree(value));
}
/**
@@ -168,7 +138,7 @@ protected final void jsiiSet(final String property, @Nullable final Object value
*/
protected static void jsiiStaticSet(final Class> nativeClass, final String property, @Nullable final Object value) {
String fqn = engine.loadModuleForClass(nativeClass);
- engine.getClient().setStaticPropertyValue(fqn, property, OM.valueToTree(value));
+ engine.getClient().setStaticPropertyValue(fqn, property, JsiiObjectMapper.valueToTree(value));
}
/**
diff --git a/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiObjectMapper.java b/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiObjectMapper.java
index 15bfcf4dfc..f562de697d 100644
--- a/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiObjectMapper.java
+++ b/packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiObjectMapper.java
@@ -1,186 +1,250 @@
package software.amazon.jsii;
-import com.fasterxml.jackson.annotation.JsonInclude;
+import java.io.IOException;
+import java.time.Instant;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.DeserializationConfig;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
+import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
+import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-
-import java.io.IOException;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-
-import static software.amazon.jsii.JsiiEngine.tryGetJsiiAnnotation;
+import com.fasterxml.jackson.databind.type.MapLikeType;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.google.common.annotations.VisibleForTesting;
/**
- * Implements serialization/deserialization of jsii data.
+ * Provides a correctly configured JSON processor for handling JSII requests and responses.
*/
public final class JsiiObjectMapper {
- /**
- * Singleton instance of the object mapper.
- */
- public static JsiiObjectMapper instance = new JsiiObjectMapper();
+ public static final long serialVersionUID = 1L;
+
+ /**
+ * An ObjectMapper that can be used to serialize and deserialize JSII requests and responses.
+ */
+ public static final ObjectMapper INSTANCE = new JsiiObjectMapper().getObjectMapper();
+
+ /**
+ * Similar to calling JsiiObjectMapper.INSTANCE.treeToValue, but handles a null JsonNode argument
+ * well, and throws JsiiException instead of JsonProcessingException.
+ */
+ public static T treeToValue(final JsonNode tree, final Class valueType) {
+ if (tree == null) {
+ return null;
+ }
+ try {
+ return INSTANCE.treeToValue(tree, valueType);
+ } catch (final JsonProcessingException jpe) {
+ throw new JsiiException(jpe);
+ }
+ }
+
+ /**
+ * Similar to calling JsiiObjectMapper.INSTANCE.valueToTree, but handles a null argument well by
+ * returning null.
+ */
+ public static T valueToTree(final Object value) {
+ if (value == null) {
+ return null;
+ }
+ return INSTANCE.valueToTree(value);
+ }
- /**
- * JSON token that represents an object reference.
- */
- private static final String TOKEN_REF = JsiiObjectRef.TOKEN_REF;
+ private static final String TOKEN_REF = JsiiObjectRef.TOKEN_REF;
- /**
- * JSON token to represent a date.
- */
- private static final String TOKEN_DATE = "$jsii.date";
+ private static final String TOKEN_DATE = "$jsii.date";
- /**
- * JSON token to represent an enum.
- */
- private static final String TOKEN_ENUM = "$jsii.enum";
+ private static final String TOKEN_ENUM = "$jsii.enum";
- /**
- * The standard JSON mapper.
- */
- private static ObjectMapper standardMapper = new ObjectMapper();
+ private final ObjectMapper objectMapper;
- /**
- * Object mapper configured for jsii serialization.
- */
- private ObjectMapper serializer;
+ private final JsiiEngine jsiiEngine;
- /**
- * Creates an object mapper.
- */
- private JsiiObjectMapper() {
- this.serializer = new ObjectMapper();
- this.serializer.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ private JsiiObjectMapper() {
+ this(JsiiEngine.getInstance());
+ }
- SimpleModule module = new SimpleModule();
- module.addSerializer(JsiiSerializable.class, new JsiiSerializer());
- module.addSerializer(Instant.class, new DateSerializer());
- module.addSerializer(Enum.class, new EnumSerializer());
+ @VisibleForTesting
+ JsiiObjectMapper(final JsiiEngine jsiiEngine) {
+ this.jsiiEngine = jsiiEngine;
+ this.objectMapper = new ObjectMapper();
+ this.objectMapper.setSerializationInclusion(Include.NON_NULL);
- this.serializer.registerModule(module);
- }
+ final SimpleModule module = new SimpleModule("JSII", Version.unknownVersion());
+ module.setDeserializerModifier(new JsiiDeserializerModifier());
+ module.addSerializer(Enum.class, new EnumSerializer());
+ module.addSerializer(Instant.class, new Instanterializer());
+ module.addSerializer(JsiiSerializable.class, new JsiiSerializer());
- /**
- * Converts a local value to a jsii JSON representation.
- *
- * @param value The local java value.
- * @param The return type.
- * @return A JSON tree.
- */
- public T valueToTree(final Object value) {
- if (value == null) {
- return null;
- }
+ this.objectMapper.findAndRegisterModules();
+ this.objectMapper.registerModule(module);
+ }
- return this.serializer.valueToTree(value);
- }
+ @VisibleForTesting
+ ObjectMapper getObjectMapper() {
+ return this.objectMapper;
+ }
+
+ /**
+ * A JsonDeserializer designed to correctly handle JSII "magic objects" that are used to remodel "pass-by-reference"
+ * values, dates, and enum constants.
+ */
+ private final class JsiiDeserializer extends StdDeserializer