diff --git a/here-naksha-lib-base/src/jvmMain/kotlin/naksha/base/Platform.kt b/here-naksha-lib-base/src/jvmMain/kotlin/naksha/base/Platform.kt index 58c8a14a7..179d467ab 100644 --- a/here-naksha-lib-base/src/jvmMain/kotlin/naksha/base/Platform.kt +++ b/here-naksha-lib-base/src/jvmMain/kotlin/naksha/base/Platform.kt @@ -175,12 +175,15 @@ actual class Platform { @JvmField internal val initialized = AtomicBoolean(false) + private val nonArgsConstuctorsCache: AtomicMap, KFunction> + init { val unsafeConstructor = Unsafe::class.java.getDeclaredConstructor() unsafeConstructor.isAccessible = true unsafe = unsafeConstructor.newInstance() val someByteArray = ByteArray(8) baseOffset = unsafe.arrayBaseOffset(someByteArray.javaClass) + nonArgsConstuctorsCache = AtomicMap() } @JvmStatic @@ -512,11 +515,30 @@ actual class Platform { if (klass.isInstance(proxy)) return proxy as T if (doNotOverride) throw IllegalStateException("The symbol $symbol is already bound to incompatible type") } - proxy = klass.primaryConstructor!!.call() + + proxy = resolveConstructorFor(klass).call() proxy.bind(pobject, symbol) return proxy } + private fun resolveConstructorFor(klass: KClass): KFunction{ + var constructor = nonArgsConstuctorsCache[klass] + if(constructor == null){ + constructor = nonArgConstructorFor(klass) + nonArgsConstuctorsCache[klass] = constructor + } + return constructor as KFunction + } + + /** + * Returns non-arg constructor for [klass] or throws [IllegalArgumentException] if none is found. + */ + private fun nonArgConstructorFor(klass: KClass): KFunction { + return klass.constructors.firstOrNull { constructor: KFunction -> + constructor.parameters.isEmpty() + } ?: throw IllegalArgumentException("Unable to find non-arg constructor for class: ${klass.qualifiedName}") + } + /** * The default logger singleton to be used as initial value by the default [loggerThreadLocal]. This is based upon * [SLF4j](https://www.slf4j.org/). If an application has a different logger singleton, it can simply place this variable. If the diff --git a/here-naksha-lib-base/src/jvmTest/java/naksha/base/JavaProxyTest.java b/here-naksha-lib-base/src/jvmTest/java/naksha/base/JavaProxyTest.java new file mode 100644 index 000000000..8735c1bb8 --- /dev/null +++ b/here-naksha-lib-base/src/jvmTest/java/naksha/base/JavaProxyTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017-2024 HERE Europe B.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package naksha.base; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import kotlin.reflect.full.IllegalCallableAccessException; +import org.junit.jupiter.api.Test; + +class JavaProxyTest { + + @Test + void shouldAllowProxyingInJava() { + // Given: + ProxyParent parent = new ProxyParent(); + + // When: + var child = parent.proxy(Platform.klassOf(ProxyChild.class)); + + // Then: + assertNotNull(child); + assertInstanceOf(ProxyChild.class, child); + } + + @Test + void shouldFailForProxyWithoutNonArgConstructor() { + // Given: + ProxyParent parent = new ProxyParent(); + + // Then: + assertThrows(IllegalArgumentException.class, () -> { + parent.proxy(Platform.klassOf(ProxyChildWithoutNonArgConstructor.class)); + }); + } + + @Test + void shouldFailForProxyWithUnsifficientVisibility() { + // Given: + ProxyParent parent = new ProxyParent(); + + // Then: + assertThrows(IllegalCallableAccessException.class, () -> { + parent.proxy(Platform.klassOf(ProxyChildWithoutPublicConstructor.class)); + }); + } + + static class ProxyParent extends AnyObject {} + + public static class ProxyChild extends ProxyParent {} + + public static class ProxyChildWithoutPublicConstructor extends ProxyParent { + ProxyChildWithoutPublicConstructor() { + // hello from package-private + } + } + + static class ProxyChildWithoutNonArgConstructor extends ProxyParent { + ProxyChildWithoutNonArgConstructor(String unusedParam) {} + } +} diff --git a/here-naksha-lib-model/src/jvmTest/java/NakshaFeatureProxyTest.java b/here-naksha-lib-model/src/jvmTest/java/NakshaFeatureProxyTest.java new file mode 100644 index 000000000..1b6c086a6 --- /dev/null +++ b/here-naksha-lib-model/src/jvmTest/java/NakshaFeatureProxyTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017-2024 HERE Europe B.V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import kotlin.reflect.full.IllegalCallableAccessException; +import naksha.base.Platform; +import naksha.geo.PointCoord; +import naksha.geo.SpPoint; +import naksha.model.objects.NakshaFeature; +import org.junit.jupiter.api.Test; + +class NakshaFeatureProxyTest { + + @Test + void shouldAllowProxyingFeature() { + // Given: + NakshaFeature nakshaFeature = new NakshaFeature(); + nakshaFeature.setId("my_id"); + nakshaFeature.setGeometry(new SpPoint(new PointCoord(10, 20))); + + // When: + CustomFeature proxiedFeature = nakshaFeature.proxy(Platform.klassOf(CustomFeature.class)); + + // Then: + assertEquals(nakshaFeature.getId(), proxiedFeature.getId()); + assertEquals(nakshaFeature.getGeometry(), proxiedFeature.getGeometry()); + } + + @Test + void shouldFailForProxyWithoutNonArgConstructor() { + // Given: + NakshaFeature nakshaFeature = new NakshaFeature(); + + // Then: + assertThrows(IllegalArgumentException.class, () -> { + nakshaFeature.proxy(Platform.klassOf(CustomFeatureWithoutNonArgConstructor.class)); + }); + } + + @Test + void shouldFailForNonPublicProxy() { + // Given: + NakshaFeature nakshaFeature = new NakshaFeature(); + + // Then: + assertThrows(IllegalCallableAccessException.class, () -> { + nakshaFeature.proxy(Platform.klassOf(NonPublicCustomFeature.class)); + }); + } + + public static class CustomFeature extends NakshaFeature { + + public CustomFeature() {} + } + + public static class CustomFeatureWithoutNonArgConstructor extends NakshaFeature { + + public CustomFeatureWithoutNonArgConstructor(String unusedParam) {} + } + + static class NonPublicCustomFeature extends NakshaFeature {} +}