0

I am trying to build a DTO (data-transfer-object) classes in Kotlin to be called from a Java application. Given following code:

BaseDto.kt

package sandbox.KotlinDataObjects

import org.apache.commons.lang3.builder.ToStringBuilder
import java.sql.ResultSet
import java.sql.SQLException

/**
 * Base DTO
 * (JSON/XML serialization code removed for clarity)
 */
abstract class BaseDto(
        var id: Long = 0
) {
    @Throws(SQLException::class)
    open protected fun fromResultSet(rs: ResultSet) {
        this.id = rs.getLong("id")
    }

    /**
     * Override toString and use StringBuilder
     * Since DataObject may be one of many objects based on BaseDto
     *   we want to avoid doing this in every data class
     * Derived class seems to generate a toString and this never gets
     *   called, this is the issue I believe
     */
    override fun toString(): String {
        return ToStringBuilder.reflectionToString(this)
    }
}

DataObject.kt

package sandbox.KotlinDataObjects

import org.apache.commons.lang3.builder.ToStringBuilder
import java.sql.ResultSet

data class DataObject(
        var name: String = ""
) : BaseDto() {
    override fun fromResultSet(rs: ResultSet) {
        super.fromResultSet(rs)
        name = rs.getString("name")
    }
}

Main.java

package sandbox.KotlinDataObjects;

import org.mockito.Mockito;

import java.sql.ResultSet;
import java.sql.SQLException;

public class Main {
    /**
     * Mock ResultSet for testing with columns we are requesting
     * @return ResultSet
     * @throws SQLException
     */
    private static ResultSet getMockedResultSet() throws SQLException {
        ResultSet mockedRs = Mockito.mock(ResultSet.class);
        Mockito.when(mockedRs.getLong("id")).thenReturn(12L);
        Mockito.when(mockedRs.getString("name")).thenReturn("MyName");
        return mockedRs;
    }

    public static void main(String[] args) {
        try (ResultSet mockedRs = getMockedResultSet()) {

            // Read DataObject from ResultSet (mocked to avoid DB connection)
            DataObject dobj = new DataObject();
            dobj.fromResultSet(mockedRs);

            System.out.println("toString: dobj="+dobj);
            System.out.println("getters: dobj.name="+dobj.getName()+"  dobj.id="+dobj.getId());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

This displays:

toString: dobj2=DataObject(name=MyName)
getters: dobj2.name=MyName  dobj2.id=12

I would like to include the id field form the BaseDto in the DataObject.toString call. One option is to override the toString with ToStringBuilder.reflectionToString in DataObject.toSting, but it would have to be added to every object derived from BaseDto. Is there a way to declare a data object hierarchy that automatically includes members from base classes. This is doable in java by overriding toString() in the base and using the ToStringBuilder.reflectionToString, in Kotlin the base toString is not called since data object generates its own version of toString which does not call super.

AlexC
  • 1,395
  • 14
  • 26
  • *Alert* Those are not DTOs. Either separate behavior from them or call them something else, not DTO. – Nikhil Vartak Jul 26 '17 at 17:52
  • @niksofteng Why are they not DTOs? The objects exist to transfer data from one source to another, they may be used to transfer data from one object type to another, who cares. In my context they are DTOs (and I am only showing code essential to my question). Did you understand my question? – AlexC Jul 26 '17 at 18:08

1 Answers1

0

Override toString in BaseDto with a reflection call like the following (credit to @junique for the reflectionToString() code example)...

import kotlin.reflect.*
import kotlin.collections.*
import java.lang.reflect.Modifier

abstract class BaseDto(
        var id: Long = 0L
) {
    open protected fun fromResultSet(rs: ResultSet) {
        this.id = rs.id
    }

    override open public fun toString() = reflectionToString(this)

    fun reflectionToString(obj: Any?): String {
        if(obj == null) {
            return "null"
        }
        val s = mutableListOf<String>()
        var clazz: Class<in Any>? = obj.javaClass
        while (clazz != null) {
            for (prop in clazz.declaredFields.filterNot { Modifier.isStatic(it.modifiers) }) {
                prop.isAccessible = true
                s += "${prop.name}=" + prop.get(obj)?.toString()?.trim()
            }
            clazz = clazz.superclass
        }
        return "${obj.javaClass.simpleName}=[${s.joinToString(", ")}]"
    }
}

EDIT

I tested this by creating a regular class instead of a data class. It doesn't work for a data class. Prior to 1.1., data classes could not even extend other classes (but they could implement interfaces). More and more, I am agreeing with the OP.

Les
  • 10,335
  • 4
  • 40
  • 60
  • Unfortunately this does not work, the toString does not get called (and open + abstract is redundant). What is happening is that Derived class is a 'data class' which generates a toString implicitly and does not call this method. I edited the question with this to see if there is more that can be done to make it work. – AlexC Jul 27 '17 at 13:31
  • I suspect this may be a limitation of the language where deriving data class from base will effectively hide methods that are automatically generated by the data class (not sure but it seems that way). – AlexC Jul 27 '17 at 13:35