Recursive generic in builder pattern


#1

I asked this question on StackOverflow without any suggestion. Currently I worked around it by using Java file in Kotlin project. Here is the question:

I just want to port the following code from Java which uses Log4j (v2.8.2):
ConsoleAppender appender = ConsoleAppender.newBuilder().
withName(“ConsoleAppender”).build();
The problem is with newBuilder() method which is defined as some kind of recursive generic in log4j:

@PluginBuilderFactory
public static <B extends Builder<B>> B newBuilder() {
    return new Builder<B>().asBuilder();
}

Java code infers the generic parameter automatically which is not the case for Kotlin. Is there any solution to call this method in Kotlin?

The code I have tried in Kotlin:

val appender = 
    ConsoleAppender.newBuilder().withName("ConsoleAppender").build()

It has the following error:

Error:(90, 48) Kotlin: Type inference failed: Not enough information to infer 
parameter B in fun !> newBuilder(): B! Please specify it explicitly.

When the code is converted from Java automatically on paste, it sets some stub newBuilder<B>(), where B is not defined and I have no idea what it should be.

We can also discuss here the pattern used in log4j and idiomatic replacement in Kotlin. Here is the related question which seems to be describing recursive generic pattern approach with builder. One more link.


How can I create generic class, which inherits itself?
#2

Try:

ConsoleAppender.newBuilder<ConsoleAppender.Builder>().withName("ConsoleAppender").build()

Description of newBuilder<B>() syntax: Reified type parameters

Update:
I read log4j source code and the correct implementation of Builder interface to use is ConsoleAppender.Builder


#3

Tried, it fails:

Error:(94, 75) Kotlin: One type argument expected for class Builder<B : ConsoleAppender.Builder<B!>!>

ConsoleAppender.Builder is a generic with a recursive parameter, that is the problem.


#4

OK, I dug deeper into Kotlin generics, this particular version of Builder pattern and log4j code and this is what I ended up with:

fun main(args: Array<String>) {
    val builder = createBuilder(ConsoleAppender::class.java) as ConsoleAppender.Builder<*>?
    val appender = builder?.withName("ConsoleAppender")?.build()
    println(appender?.name)
}

@Throws(InvocationTargetException::class, IllegalAccessException::class)
private fun createBuilder(clazz: Class<*>): Builder<*>? {
    for (method in clazz.declaredMethods) {
        if (method.isAnnotationPresent(PluginBuilderFactory::class.java) &&
                Modifier.isStatic(method.modifiers) &&
                TypeUtil.isAssignable(Builder::class.java, method.returnType)) {
            ReflectionUtil.makeAccessible(method)
            return method.invoke(null) as Builder<*>
        }
    }

Function createMethod is adapted from org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.createBuilder(Class<?>), the @Throws annotation can be skipped.

Update:
Sorry, forgot to do proper null checks :slight_smile:


#5

So, do you think that using reflection is only way to make it working in Kotlin?


#6

Honestly, reflection is how newBuilder() methods are used internally by log4j itself, which suggest this is how the are supposed to be used. I’m more suprised they work without reflection in regular Java.

BTW, the code can be simplified if you only need it to work with one particular type of Builder:

fun main(args: Array<String>) {
    val builder = createBuilder(ConsoleAppender::class.java)
    val appender = builder?.withName("ConsoleAppender")?.build()
    println(appender?.name)
}

@Throws(InvocationTargetException::class, IllegalAccessException::class)
private fun createBuilder(clazz: Class<*>): ConsoleAppender.Builder<*>? {
    for (method in clazz.declaredMethods) {
        if (method.name == "newBuilder") {
            return method.invoke(null) as ConsoleAppender.Builder<*>
        }
    }
    return null
}

or even

fun main(args: Array<String>) {
    val builder = ConsoleAppender::class.java.getMethod("newBuilder").invoke(null) as ConsoleAppender.Builder<*>
    val appender = builder.withName("ConsoleAppender").build()
    println(appender.name)
}

Notice no null checks in the last version, sice we know that the method “newBuilder” exists in this particular class.


#7

Ok, this works. Now the deeper question. This solution with recursive generics, according to this article, solves builder inheritance problem. What is idiomatic equivalent in Kotlin for that pattern?


#8

I think this would be it:
Type-safe Builders


#9

I’ve tried to implement builder pattern from the aforementioned article in Kotlin and this is what I came up with: Builder Pattern POC.

Unfortunately, it seems that builder methods have to be chained in order and I cannot figure out why. Maybe someone here will figure out a solution. I’ll keep working on it in the meantime.

Update:
@vagran
I solved the chaining order issue, but it seem that every open, non abstract class requires two builder classes: one open and generic, that can be extended in a subclass, and one without a parameter that can actually be instantiated and used for building.


#10

I found the way, how to do log4j2 casting and posted sample on the StackOverflow