https://kotlinlang.org logo
Title
b

bodiam

05/11/2019, 4:25 AM
Hi all, what am I missing here?
I've converter the above Java code to the following Kotlin code:
j

jacob

05/11/2019, 4:26 AM
what's the problem you're seeing?
b

bodiam

05/11/2019, 4:26 AM
class ProductRandomizerProvider : RandomizerProvider { override fun getRandomizerByField(field: Field, context: RandomizerContext): Randomizer<*>? { if (field.name == "name" && context.currentRandomizationDepth == 0) { return { "product" } as Randomizer<*> } if (field.name == "name" && context.currentField == "owner") { return { "owner" } as Randomizer<*> } return null } }
But when I use this code using Java 11, I get the following error:
Caused by: java.lang.ClassCastException: class random.RandomizerProviderTestsKotlin$testCustomRandomizerProvider$parameters$1$getRandomizerByField$1 cannot be cast to class org.jeasy.random.api.Randomizer (random.RandomizerProviderTestsKotlin$testCustomRandomizerProvider$parameters$1$getRandomizerByField$1 and org.jeasy.random.api.Randomizer are in unnamed module of loader 'app') at random.RandomizerProviderTestsKotlin$testCustomRandomizerProvider$parameters$1.getRandomizerByField(Test.kt:58)
When I run the Java code using Java 11, all is fine.
The thing the JVM is complaining about is this line:
return { "foo" } as Randomizer<*>
@jacob (sorry, wasn't done typing yet! 😉 )
j

jacob

05/11/2019, 4:28 AM
haha, I asked too quickly 😛
I don't know that you can cast to
Randomizer<*>
nah, that's not it
b

bodiam

05/11/2019, 4:30 AM
I didn't know either, but that's what IntelliJ made of it.
I'm not sure if you can cast to it really, but I also don't know an alternative.
j

jacob

05/11/2019, 4:31 AM
class Field {
   fun getName() = ""
}
class RandomizerContext {
   fun getCurrentRandomizationDepth() = 0
   fun getCurrentField() = ""
}

class Randomizer<T>

fun getRandomizerByField(field: Field, context: RandomizerContext): Randomizer<*>? {
   // return custom randomizer based on the context
   if (field.getName().equals("name") && context.getCurrentRandomizationDepth() === 0) {
      return { "foo" }
   }
   if (field.getName().equals("name") && context.getCurrentField().equals("bestFriend")) {
      return { "bar" }
   }
   return null
}
with that code, I get
b

bodiam

05/11/2019, 4:31 AM
yep
Field btw is import java.lang.reflect.Field
j

jacob

05/11/2019, 4:32 AM
oh, cool, one tick
so I wonder if Java 11 prevents you casting a function to a class? or an inline class or whatever bytecode KT takes lambdas to
I don't know. I first thought it was an Java 11 issue, but the code runs fine in the original context.
j

jacob

05/11/2019, 4:33 AM
the other thing I can see if the module error. so maybe something around java 9 modules and kt?
b

bodiam

05/11/2019, 4:33 AM
but when I use Java 11, nope.
j

jacob

05/11/2019, 4:35 AM
which suggests something like https://github.com/jmockit/jmockit1/issues/553 as a possibility?
b

bodiam

05/11/2019, 4:35 AM
I have no idea. I tried doing some --add-module parameters on jeasy, but that didn't do anything.
but that's private fields. That doesn't seem to be related I think?
b

bodiam

05/11/2019, 4:37 AM
I looked at this one though: https://github.com/mockk/mockk/issues/150
right. So basically that code says: can't case to Randomizer<*> ??
that might make sense. But how to fix?
j

jacob

05/11/2019, 4:42 AM
possible that https://github.com/j-easy/easy-random/blob/master/easy-random-core/src/main/java/org/jeasy/random/api/Randomizer.java#L32 is the issue? I'm not sure what that annotation does, but likely it marks the java class as being a "function" in same sense
so the java function returns () -> "" which is the same as whatever that "functionalInterface" annotation generates
but the kt lambda doesn't generate a java lambda maybe?
b

bodiam

05/11/2019, 4:43 AM
@functionalinterface is just a marker interface
it means it's an interface with 1 abstract method
j

jacob

05/11/2019, 4:47 AM
https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html suggests that lambda's can inhabit a functionalinterface. my best guess is that if https://marcin-chwedczuk.github.io/lambda-expressions-in-kotlin is accurate, KT compiles a lambda to a class that is not castable since it isn't a java8lambda
you can see that this is exactly how things should work
j

jacob

05/11/2019, 5:00 AM
does
fun getRandomizerByField(field: Field, context: RandomizerContext): Randomizer<*>? {
   // return custom randomizer based on the context
   if (field.getName().equals("name") && context.getCurrentRandomizationDepth() === 0) {
      return object: Randomizer<Any> {
         override val randomValue = "foo"
      }
   }
   if (field.getName().equals("name") && context.getCurrentField().equals("bestFriend")) {
      return object: Randomizer<Any> {
         override val randomValue = "bar"
      }
   }
   return null
}
work?
b

bodiam

05/11/2019, 5:02 AM
let me check
It works a little bit.
The code you provided doesn't compile, but with your pointer, I've created this:
return Randomizer<Any> { "product" }
j

jacob

05/11/2019, 5:07 AM
haha, a little bit?
b

bodiam

05/11/2019, 5:07 AM
I know, I know 😉
j

jacob

05/11/2019, 5:07 AM
oh yeah, that makes more sense
b

bodiam

05/11/2019, 5:07 AM
and, the original error is now gone. I just have a new error now 🙂
"Caused by: java.lang.IllegalArgumentException: Primitive types can't be instantiated in Java"
thing is: I don't see primitives types in my code 🙂
j

jacob

05/11/2019, 5:08 AM
man that's strange
sorry, that's about as far as I can think short of going through the bytecode of the kotlin program and the java program
b

bodiam

05/11/2019, 5:09 AM
I've found the primitive type now
it's the byte array inside the string...
j

jacob

05/11/2019, 5:09 AM
oh wow
b

bodiam

05/11/2019, 5:09 AM
that's not correct
I don't think this code is correct
j

jacob

05/11/2019, 5:11 AM
the lambda casting?
b

bodiam

05/11/2019, 5:13 AM
the return Randomizer { "xxx" }
but it might have been a different issue!
I think the code works now
class ProductRandomizerProvider : RandomizerProvider { override fun getRandomizerByField(field: Field, context: RandomizerContext): Randomizer<*>? { if (field.name == "name" && context.currentRandomizationDepth == 0) { return Randomizer { "product" } } if (field.name == "name" && context.currentField == "bla") { return Randomizer { "xxx" } } return null } }
I need to dive a bit more into it to see if it's 100% correct, or if the code just passes because I have another mistake
but thanks for your help Jacob!
do you mind if I shout out your name on my blogpost for helping me out??
j

jacob

05/11/2019, 5:20 AM
hey, that's wonderful!
no, don't mind at all.
really cool result, I actually got to learn a bunch about how all this stuff works now
b

bodiam

05/11/2019, 5:21 AM
cool! I'll officially publish the post on Monday (on Reddit and that stuff), so I'll update the post later with an updated example
thanks again for helping me out, appreciate it!!
j

jacob

05/11/2019, 5:22 AM
no worries 🙂
b

bodiam

05/11/2019, 5:31 AM
updated!
k

karelpeeters

05/11/2019, 7:33 AM
I'm curious why casting a lambda to a random interface should work at all, behind the scenes a lambda is just another class with an invoke function, it doesn't magically implement the interface.
d

Dico

05/11/2019, 11:38 AM
It looks like you're importing the wrong
Randomizer
in one of your source files