Ambiguous method call with JvmSynthetic setter - java

I have a class with #JvmSynthetic setters in order to provide only fluent builder-like setters for Java clients:
class PersonBuilder {
#set:JvmSynthetic // hide a void setter from Java
var age: Int? = null
#set:JvmSynthetic
var name: String? = null
fun setAge(age: Int?) = apply { this.age = age }
fun setName(name: String?) = apply { this.name = name }
fun build() = Person(age!!, name!!)
}
And call it like this on the Java side:
new PersonBuilder()
.setAge(22) // <- "error"
.setName("Peter")
.build();
It does compile & run, but Android Studio shows the error "Ambiguous method call. Both setAge (Integer) in PersonBuilder and setAge (Integer) in PersonBuilder match" and doesn't provide any auto-suggestions and code formating after this "error".

I believe there is no need to use the #set:JvmSynthetic notation, just changing the visibility of variables should do the trick. Try this:
class PersonBuilder {
private var age: Int? = null
private var name: String? = null
fun setAge(age: Int) = apply { this.age = age }
fun setName(name: String) = apply { this.name = name }
fun build() = Person(age!!, name!!) //be careful!
}
Anyway, be careful with the build function. There are no guarantees that the functions setAge and setName will be called before it, so you can get a NullPointerException
Update
Another way to solve this problem by keeping #JvmSynthetic is to add the #JvmField notation as well. See this article for more information.

Related

AspectJ - Get method parameter value by path?

I have an authorizaton Aspect that checks specific conditions based on method annotation.
This specific example shows annotation to mark a method that is only accessible by customer service. But unfortunately this isn't the only condition.
We have to confirm the customerServiceId that is also passed as one of method parameters. The parameter containing customerServiceId is pretty nested, so I was wondering if it's possible to get parameter value by some kind of a "path".
So let's say we have this method:
fun updateRemoteConfig(val remoteConfig: RemoteConfig) { doSomething() }
RemoteConfig class is pretty nested, so the path to customerServiceId would be something like: remoteConfig.customerService.id
What I would like to achieve is mark the method with annotation:
#CustomerServiceAccess(customerServiceIdPath = "remoteConfig.customerService.id")
And the value would then be fetched inside Aspect method. But I have no idea how to get to the specified value by path. Is it even possible?
The unknown is where arrows are in the code. Here's rest of the aspect:
#Aspect
class AuthorizationAspect {
#Pointcut("#annotation(com.my.project.annotations.CustomerServiceAccess)")
fun customerServiceAccess() = Unit
#Before("customerServiceAccess()")
fun checkAccess(joinPoint: JoinPoint) {
val methodSignature = joinPoint.signature as MethodSignature
val method = methodSignature.method
val canAccess = mutableListOf<() -> Boolean>()
.apply {
addAll(method.getAnnotationsByType(CustomerServiceAccess::class.java).map { it.canAccess(method) })
}
.any { it() }
if (!canAccess) {
throw UnauthorizedException(message = "User cannot perform this action")
}
}
private fun CustomerServiceAccess.canAccess(val method: Method): () -> Boolean = {
->> val customerServiceIdParam = method.getParameterByPath(getCustomerServiceIdPath())
SecurityContext.isCustomerService && SecurityContext.customerServiceId == customerServiceIdParam
}
private fun CustomerServiceAccess.getCustomerServiceIdPath(): String = this.customerServiceIdPath
}
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FUNCTION)
annotation class CustomerServiceAccess(val customerServiceIdPath: String)

how to resolve - Parameter specified as non-null is null

I have unit test in which I am trying to check is a use case is called with the right parameters but I get an error
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method com.xx.xxx.clean.orderview.domain.OnStandUseCaseCoroutine$Params.<init>, parameter serviceType
#Test
fun `when notifyOnStand is called then we create a TimestampedAction with the correct user id, vehicle, timestamp and pass that to the usecase`() {
val actionCaptor = argumentCaptor<TimestampedAction>()
val timestamp = DateTime.now()
every(noServiceRequiredBus.get()).thenReturn(Observable.just(REQUESTED))
every(timingsUpdater.timestampCalculator(any(), any())).thenReturn(timestamp)
baseOrderViewPresenter.setView(view)
baseOrderViewPresenter.notifyOnStand()
runBlocking {
verify(onStandUseCaseCoroutine).run(OnStandUseCaseCoroutine.Params(any(), any(), capture(actionCaptor)))
}
}
Use case which will get called when when called baseOrderViewPresenter.notifyOnStand() from tets case
class OnStandUseCaseCoroutine #Inject constructor(
private val orderRepository: OrderRepository,
private val serviceOrderTypeProvider: ServiceOrderTypeProvider
) : UseCaseCoroutine<GenericResponse, OnStandUseCaseCoroutine.Params> (){
override suspend fun run(params: Params) =
orderRepository.notifyOnStandSuspend(serviceOrderTypeProvider.apiPathFor(params.serviceType), params.id, params.action)
data class Params(val serviceType: String, val id: String, val action: TimestampedAction)
}
Presenter layer which has the call to use case
private fun onstandUseCaseCoroutines(serviceType: String, id: String, action: TimestampedAction, callback: (GenericResponse?) -> Unit) {
try {
onStandUseCaseCoroutine(OnStandUseCaseCoroutine.Params(serviceType, id, action)) {
callback.invoke(it)
}
} catch (exception: Exception) {
onStandResponseErrors()
}
}
how can I fix this please
I tried changing to bellow code but that did not fix it, I am not sure what to do the capture(actionCaptor) bit if that is the issue
runBlocking {
verify(onStandUseCaseCoroutine).run(OnStandUseCaseCoroutine.Params(anyString(), anyString(), capture(actionCaptor)))
}
Any suggestions please
Thanks
R

Overriding get method for variable in class kotlin?

I have a model class in Java which I converted to data class in kotlin
public class VideoAssets implements Serializable {
#SerializedName("type")
#Expose
String type;
#SerializedName("mpeg")
#Expose
List<Mpeg> mpeg = null;
#SerializedName("hls")
#Expose
String hls;
#SerializedName("widevine")
#Expose
WideVine wideVine;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public List<Mpeg> getMpeg() {
return mpeg;
}
public void setMpeg(List<Mpeg> mpeg) {
this.mpeg = mpeg;
}
public String getHls() {
hls = Macros.INSTANCE.replaceURl(hls);
return hls;
}
public void setHls(String hls) {
this.hls = hls;
}
public WideVine getWideVine() {
return wideVine;
}
public void setWideVine(WideVine wideVine) {
this.wideVine = wideVine;
}
}
As you see I want to change the value of variable hls when I retrieve it.
I created the data class as below
data class VideoAssets(#SerializedName("mpeg") #Expose
var mpeg: List<Mpeg> = emptyList(),
#SerializedName("hls")
#Expose
var hls: String,
#SerializedName("widevine")
#Expose
val wideVine: WideVine? = null) : Serializable
I am struggling here as how should I update the get method for data class.
After searching and taking reference from Override getter for Kotlin data class
I even created a non data class which doesn't seem to work
class VideoAssets(#SerializedName("mpeg") #Expose
var mpeg: List<Mpeg> = emptyList(),
#SerializedName("hls")
#Expose
val hlsUrl: String? = null,
#SerializedName("widevine")
#Expose
val wideVine: WideVine? = null) : Serializable {
val hls: String? = hlsUrl
get() = field?.let { Macros.replaceURl(it) }
}
Whenerver I try to retrieve videoAssets.getHls() it returns null while it should return the new value. The object videoAssets.gethlsUrl() has the value but not `videoAssets.getHls()' is always null.
Can someone point me what I am missing?
Here's your code:
val hls: String? = hlsUrl
get() = field?.let { Macros.replaceURl(it) }
So what this is doing, is creating a property called hls and giving it a backing field (a variable) called field. It initially sets that to whatever value for hlsUrl was passed into the constructor (might be null).
The getter code takes that value for field, and if it isn't null it calls that replaceURl function and returns the result, otherwise it returns null.
So if you set hlsUrl to null, field will always be null and the hls getter will always return null. Even if you update hlsUrl later (whicb I'm assuming you're doing, the code runs fine for me if I pass in a value to the constructor) the value of field is fixed at initialisation.
Also your Java code runs differently - when that gets the new value of hls, it stores that and uses it in the function call of the next get. You're never changing the value of field so your Kotlin code uses the initial value every time.
Technically you don't need the backing field since you're always effectively calling hlsUrl?.let { Macros.replaceURl(it) }. In that case you could make hlsUrl var and update that, or you can add a setter to your hls property and set the backing field when you get the new value
Here's the Kotlin page on properties, in case you haven't seen it!

How to get name from Java enum in Kotlin

I have a project that is written with both Java and Kotlin languages and recently I faced next issue.
Let's say we have MyMonth enum:
public enum MyMonth {
JAN("January"),
FEB("February");
private final String name;
MyMonth(final String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Then in Kotlin when we print the name of the month:
fun main() {
val month = MyMonth.JAN
println(month.name)
}
we get:
JAN
which is what is described in the documentation https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-enum/name.html, however actually the name should be January.
Is there a way to get a name in Kotlin that is specified in Java enum?
UPD: My Kotlin version is 1.3.30-release-170
UPD2: IDEA even shows me that name is coming from the getName() method defined in Java:
UPD3: When we explicitly use .getName() it works, however it looks kind of weird
You can call the getter directly instead of using the property syntax:
fun main() {
val name = MyMonth.JAN.getName()
println(name)
}
Your Java API has name as a private field. Nothing can access it, not in Java nor Kotlin.
If you want to access it, add e.g. the following to the Java API:
public String getMonthName() { return name; }
...and then access it from Kotlin as
val month = MyMonth.JAN.monthName
enum is now low usage. You can use IntDef instead of enums.
#IntDef(MyMonth.JAN, MyMonth.FEB)
#Retention(AnnotationRetention.SOURCE)
annotation class MyMonth {
companion object {
const val JAN = 0
const val FEB = 1
fun getDisplayString(toDoFilterTypes: Int): String {
return when (toDoFilterTypes) {
JAN -> "All"
FEB -> "Job"
else -> "N/A"
}
}
}
}

Kotlin - nonnull getter for a nullable field

I'm new with Kotlin and I try to rework a small Java project to this new language. I use mongodb in my project and I have a class, for example:
class PlayerEntity {
constructor() {} //for mongodb to create an instance
constructor(id: ObjectId, name: String) { //used in code
this.id = id
this.name = name
}
#org.mongodb.morphia.annotations.Id
var id: ObjectId? = null
var name: String? = null
}
I have to mark id field as nullable (var id: ObjectId?) because of empty constructor. When I try to access this field from another class I have to use non-null check: thePlayer.id!!. But the logic of my application is that id field is never null (mongo creates an instance of Player and immediately sets id field). And I don't want to make a non-null check everywhere.
I tried to make a non-null getter, but it does not compile:
var id: ObjectId? = null
get(): ObjectId = id!!
I can also make some stub for id and use it in constructor, but this looks like a dirty hack:
val DUMMY_ID = new ObjectId("000000000000000000000000");
So is there a workaround to solve the issue?
I personally use a private var prefixed by _ + public val in similiar situations.
class Example<out T> {
private var _id: T? = null
val id: T
get() = _id!!
}
For your situation, it would look like this:
#org.mongodb.morphia.annotations.Id
private var _id: ObjectId? = null
val id: ObjectId
get() = _id!!
Alternatively, declare your variable as lateinit like this (but note that this exposes the setter publicly):
#org.mongodb.morphia.annotations.Id
lateinit var id: ObjectId

Categories