1

I have code accepts a class as a parameter and prepares data to call either the constructor for that class of a companion object factory method if the factory method is present.

All works fine when calling the constructor, but I get the error

java.lang.IllegalArgumentException: No argument provided for a required parameter: instance of fun nz.salect.objjson.JVMTest.StudentWithFactory.Companion.fromJson(kotlin.String, kotlin.Int): nz.salect.objjson.JVMTest.StudentWithFactory

when calling the factory method. The factory method in question:

data class StudentWithFactory(val name: String, val years: Int=0) {

    companion object {

        fun fromJson(name: String="", age: Int = 0):StudentWithFactory {
            return StudentWithFactory(name, age)
        }
    }
}

has no required parameters, unless there is some hidden parameter. Any ideas?

In fact, I reverted removing the parameters completely from fromJson and directly calling the companion method using ::fromJson.callby(emptyMap()). Same error.

It is clear that companion methods need at least one additional parameter. Perhaps the class? Or the companion object? How can I specify the needed parameter(s)?

The function building up the callBy() is supplied a class (or finds the class from a supplied class) and json names and values.

var funk:KFunction<*>?=null
val companionFuncs=cls.companionObject?.declaredMemberFunctions
if(companionFuncs?.size ?:0 >0){
    companionFuncs?.forEach {
        if(it.name == "fromJson") funk=it
    }

}
val cons:KFunction<T> = if(funk != null)
       funk as KFunction<T>
    else
       cls.primaryConstructor ?: throw IllegalArgumentException("no primary constructor ${cls.simpleName}")
val valuesMap = cons.parameters.filter{it.name in vals}
    .associateBy(
    {it},
    {generateValue(it)}
)
val data = cons.callBy(valuesMap) //as T
return data
innov8
  • 2,093
  • 2
  • 24
  • 31
  • 2
    Please provide your code that invokes the factory method. I assume you use reflection, then the default parameters will not be used. – Rene Feb 22 '19 at 12:16
  • I'm not a kotlin reflection expert, but maybe you'll need `@JvmOverloads` on your companion factory method, so that the no-arg method actualy exists in the bytecode. – Joffrey Feb 22 '19 at 13:52
  • Can you provide the code which you call factory method in? I tryied to call the factory method directly without problems, but maybe I "misunderstood your problem... – Pietro Martinelli Feb 22 '19 at 14:12
  • I think this is a similar question: https://stackoverflow.com/questions/48175768/error-when-use-callby-on-a-function-with-default-parameters-in-kotlin – Pietro Martinelli Feb 22 '19 at 14:29
  • @Rene I added the code that invokes the factory method. Default parameters are used where present in supplied json data. – innov8 Feb 23 '19 at 01:32
  • @Joffrey, using a annotation to make the factory method a true static is an interesting idea and i will explore, but JvmOverloads does not change the need for the companion object as a parameter :( – innov8 Feb 23 '19 at 01:41
  • @PietroMartinelli i posted the relevant source. Hope that helps convey the problem. It can be a challenge to make things clear – innov8 Feb 23 '19 at 01:43

2 Answers2

0

You can use the parameters property to determine how much parameters you have to pass to the function/constructor.

If you call

val paramsConstr = StudentWithFactory::class.primaryConstructor?.parameters

paramsConstr will be of size two as expected, but if you call

val paramsFunc = ::fromJson.parameters

paramsFunc will be of size three. The first element corresponds to the instance of the companion object. So, thats the list of parameters you need to provide.

You can invoke the fromJson like this:

// not using any default parameters
::fromJson.callBy(mapOf(
        paramsFunc[0] to StudentWithFactory::class.companionObjectInstance,
        paramsFunc[1] to "Hello",
        paramsFunc[2] to 30
))

// using only the default parameter for "name"
::fromJson.callBy(mapOf(
        paramsFunc[0] to StudentWithFactory::class.companionObjectInstance,
        paramsFunc[2] to 30
))
Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
0

In addition to my short answer, a more technical explanation:

Yes, there actually is a hidden parameter and you can see it (for example), if you take a look at the decompiled (to Java) bytecode:

public final class StudentWithFactory {

   // ...
   public static final class Companion {
      // ...
      @NotNull
      public static StudentWithFactory fromJson$default(StudentWithFactory.Companion var0, String var1, int var2, int var3, Object var4) {
         // ...

         return var0.fromJson(var1, var2);
      }
      // ...
   }
}

The first parameter (var0) is actually an instance of the companion object. var1 (name) and var2 (age) are the parameters you declared. var3 is a bitmask for determining if explicit values have been passed or if the default ones should be used*. I honestly don't know what var4 is for. It is unused in the Java code. But the imported part is that you only need to worry about var0, var1 and var2 if you want to invoke the function.

So, in the end the non-static version of fromJson* is actually invoked on the instance of the companion object:

var0.fromJson(var1, var2)

*left code out for simplicity

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
  • Thanks @Willi, I posted my question late Friday night for me, and was thinking about an experiment similar to that and using the reflection data in the debugger to verify that the companion object is the hidden parameter. As the data passed to the method making the call is the Class, from which the companion object is found by reflection, then the fromJson() method found from the companion object, the question now is what value to pass, and i will update the question – innov8 Feb 23 '19 at 01:19
  • @innov8 you can find a working example for your original question in my other answer. – Willi Mentzel Feb 23 '19 at 08:27
  • @innov8 such big edits are discouraged because they render already given answers (to the original question) obsolete. My answers fully covered your original question. Now they don't. Pls create a new question for that and rollback the edit. – Willi Mentzel Feb 23 '19 at 08:30
  • The edit added the code, as several people asked for it. The code made a 'big' edit, but as it is only adding code people asked for, it clarifies the question. I did also then add text moving on to a follow on question. That I have removed and will post as a separate question. I would like to highlight your answer as a correct answer for reference, but suggest adding a comment that constructors are unusual in not having hidden parameters – innov8 Feb 24 '19 at 11:18
  • @innov8 ok, i see! "but suggest adding a comment that constructors are unusual in not having hidden parameters" what do you mean by that? – Willi Mentzel Feb 25 '19 at 19:11
  • Part of the puzzle was that the code worked fine for calling a constructor, but the same code would not call the factory method. To me, a key in putting the picture together is what while constructors do NOT have a hidden parameter, companion object methods do. I think mentioning something about this in the answer could play a big part in helping others :) Thank you for your input, it is appreciated that you take the time. – innov8 Feb 26 '19 at 03:43
  • @innov8 I added that in my short answer, since it is more practical. this long answer is, as i said, an addition to that :) – Willi Mentzel Mar 10 '19 at 16:06