Skip to content

Commit

Permalink
Casl 534 java proxy fix (#347)
Browse files Browse the repository at this point in the history
* CASL-534 choosing more than just primaty constructor

Signed-off-by: Jakub Amanowicz <jakub.amanowicz@here.com>

* CASL-534 non-arg constructor preference

Signed-off-by: Jakub Amanowicz <jakub.amanowicz@here.com>

* CASL-534 access-based test

Signed-off-by: Jakub Amanowicz <jakub.amanowicz@here.com>

* CASL-534 constructors cache

Signed-off-by: Jakub Amanowicz <jakub.amanowicz@here.com>

---------

Signed-off-by: Jakub Amanowicz <jakub.amanowicz@here.com>
  • Loading branch information
Amaneusz authored Sep 26, 2024
1 parent 61edc30 commit 400303b
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 1 deletion.
24 changes: 23 additions & 1 deletion here-naksha-lib-base/src/jvmMain/kotlin/naksha/base/Platform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,15 @@ actual class Platform {
@JvmField
internal val initialized = AtomicBoolean(false)

private val nonArgsConstuctorsCache: AtomicMap<KClass<out Proxy>, KFunction<out Proxy>>

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
Expand Down Expand Up @@ -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 <T: Proxy> resolveConstructorFor(klass: KClass<T>): KFunction<T>{
var constructor = nonArgsConstuctorsCache[klass]
if(constructor == null){
constructor = nonArgConstructorFor(klass)
nonArgsConstuctorsCache[klass] = constructor
}
return constructor as KFunction<T>
}

/**
* Returns non-arg constructor for [klass] or throws [IllegalArgumentException] if none is found.
*/
private fun <T : Proxy> nonArgConstructorFor(klass: KClass<T>): KFunction<T> {
return klass.constructors.firstOrNull { constructor: KFunction<T> ->
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
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {}
}
}
79 changes: 79 additions & 0 deletions here-naksha-lib-model/src/jvmTest/java/NakshaFeatureProxyTest.java
Original file line number Diff line number Diff line change
@@ -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 {}
}

0 comments on commit 400303b

Please sign in to comment.