mardi 20 octobre 2020

How to write Java's default method of an interface in Kotlin idiomatically?

I'm trying to convert the Java code of a design pattern called Chain of Responsibility in Kotlin idiomatically. But I'm not getting any clue on converting the default method appendNext() of Java interface in Kotlin. I tried some already existing questions like this and this but they don't seem to be working for my use case.

I tried converting the default method appendNext() to an extension function in Kotlin. But apparently Kotlin doesn't seem to find the method Logger.message() and throws NoSuchMethodError.

I have given the original Java code and the Kotlin code I tried so far in the following snippets.

I would prefer a Kotlin idiomatic solution of this code without using the @JvmDefault annotation. The code should be as concise as Java if not more. Any help would be much appreciated.

Java code

This is the correctly working Java code for the design pattern:

import java.util.Arrays;
import java.util.EnumSet;
import java.util.function.Consumer;

@FunctionalInterface
public interface Logger {
    public enum LogLevel {
        INFO, DEBUG, WARNING, ERROR, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;

        public static LogLevel[] all() {
            return values();
        }
    }

    abstract void message(String msg, LogLevel severity);

    default Logger appendNext(Logger nextLogger) {
        return (msg, severity) -> {
            message(msg, severity);
            nextLogger.message(msg, severity);
        };
    }

    static Logger writeLogger(LogLevel[] levels, Consumer<String> stringConsumer) {
        EnumSet<LogLevel> set = EnumSet.copyOf(Arrays.asList(levels));
        return (msg, severity) -> {
            if (set.contains(severity)) {
                stringConsumer.accept(msg);
            }
        };
    }

    static Logger consoleLogger(LogLevel... levels) {
        return writeLogger(levels, msg -> System.err.println("Writing to console: " + msg));
    }

    static Logger emailLogger(LogLevel... levels) {
        return writeLogger(levels, msg -> System.err.println("Sending via email: " + msg));
    }

    static Logger fileLogger(LogLevel... levels) {
        return writeLogger(levels, msg -> System.err.println("Writing to Log File: " + msg));
    }

    public static void main(String[] args) {
        // Build an immutable chain of responsibility
        Logger logger = consoleLogger(LogLevel.all())
                .appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))
                .appendNext(fileLogger(LogLevel.WARNING, LogLevel.ERROR));

        // Handled by consoleLogger since the console has a LogLevel of all
        logger.message("Entering function ProcessOrder().", LogLevel.DEBUG);
        logger.message("Order record retrieved.", LogLevel.INFO);

        // Handled by consoleLogger and emailLogger since emailLogger implements Functional_Error & Functional_Message
        logger.message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FUNCTIONAL_ERROR);
        logger.message("Order Dispatched.", LogLevel.FUNCTIONAL_MESSAGE);

        // Handled by consoleLogger and fileLogger since fileLogger implements Warning & Error
        logger.message("Customer Address details missing in Branch DataBase.", LogLevel.WARNING);
        logger.message("Customer Address details missing in Organization DataBase.", LogLevel.ERROR);
    }
}

Kotlin code

This is what I tried so far. I moved the Enum to a separate file and kept everything at top level.

Logger.kt

import java.util.*
import java.util.function.Consumer

interface Logger {
    var currentLogger: Logger
    fun message(message: String, severity: LogLevel)
    fun appendNext(nextLogger: Logger): Logger {
        currentLogger.message()
        currentLogger = nextLogger
        nextLogger.message()
    }
}

fun Logger.appendNext(nextLogger: Logger): Logger {
    return object: Logger {
        override fun message(message: String, severity: LogLevel) {
            message(message, severity)
            nextLogger.message(message, severity)
        }
    }
}

fun writeLogger(
    stringConsumer: Consumer<String>,
    vararg levels: LogLevel
): Logger {
    val set = EnumSet.copyOf(listOf(*levels))
    return object: Logger {
        override fun message(message: String, severity: LogLevel) {
            if (set.contains(severity)) {
                stringConsumer.accept(message)
            }
        }
    }
}

fun consoleLogger(vararg levels: LogLevel): Logger {
    return writeLogger(
        Consumer { msg: String -> System.err.println("Writing to console: $msg") },
        *levels
    )
}

fun emailLogger(vararg levels: LogLevel): Logger {
    return writeLogger(
        Consumer { msg: String -> System.err.println("Sending via email: $msg") },
        *levels
    )
}

fun fileLogger(vararg levels: LogLevel): Logger {
    return writeLogger(
        Consumer { msg: String -> System.err.println("Writing to Log File: $msg") },
        *levels
    )
}

fun main() {
    // Build an immutable chain of responsibility
    val logger = consoleLogger(*LogLevel.all())
        .appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))
        .appendNext(fileLogger(LogLevel.WARNING, LogLevel.ERROR))

    // Handled by consoleLogger since the console has a LogLevel of all
    logger.message("Entering function ProcessOrder().", LogLevel.DEBUG)
    logger.message("Order record retrieved.", LogLevel.INFO)

    // Handled by consoleLogger and emailLogger since emailLogger implements Functional_Error & Functional_Message
    logger.message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FUNCTIONAL_ERROR)
    logger.message("Order Dispatched.", LogLevel.FUNCTIONAL_MESSAGE)

    // Handled by consoleLogger and fileLogger since fileLogger implements Warning & Error
    logger.message("Customer Address details missing in Branch DataBase.", LogLevel.WARNING)
    logger.message("Customer Address details missing in Organization DataBase.", LogLevel.ERROR)
}

LogLevel.kt

enum class LogLevel {
    INFO, DEBUG, WARNING, ERROR, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;

    companion object {
        public fun all(): Array<LogLevel> {
            return values()
        }
    }
}

Aucun commentaire:

Enregistrer un commentaire