I've been battling the whole morning to lock down the serialVersionUID in a Kotlin class. I have a BaseModel which is extended by Project
abstract class BaseModel<T>(
var id: Int? = null,
private val fileName: String,
private val data: MutableList<T>,
private val indices: MutableMap<Int, T>
) : Serializable {
...
protected fun writeToDisk() {
val oos = ObjectOutputStream(BufferedOutputStream(FileOutputStream(fetchFileName())) )
oos.writeObject(fetchData());
oos.close();
}
}
And the project class:
class Project(
var name: String = "",
var repo: String = ""
) : BaseModel<Project>(
data = Data.projects,
indices = Data.projectsIndex,
fileName = "data/projects.dat"
), Serializable {
...
override fun toString(): String {
return "Project: id=${id}, name=${name}, repo=${repo}"
}
}
Every time I write to Disk and then change anything in the class and try to read it back again, I would get:
java.io.InvalidClassException: com.jvaas.bob.model.Project; local
class incompatible: stream classdesc serialVersionUID =
4156405178259085766, local class serialVersionUID =
2024101567466310467
I've tried adding:
private val serialVersionUID: Long = 1
to all classes with no effect.
Some examples on StackOverflow were using serialVersionUid which had no effect either (I believe this is intelliJ lowercasing the last two letters for some reason)
#JvmStatic doesn't work here since it's not an object, I've tried making it non-private with no success.
You can define serialVersionUID as a constant in a companion object:
abstract class BaseModel<T> : Serializable {
companion object {
private const val serialVersionUID: Long = -1
}
}
Constants are compiled to fields, and fields of a companion are stored as static fields of the class that contains companion. Therefore you get what you need – a private static field serialVersionUID in your serializable class.
The solution was actually much simpler than I thought, use a companion object. This now serializes perfectly and if I add more fields, it still serializes to disk and deserializes unless I change the serialVersionUID
Base:
abstract class BaseModel<T>(
var id: Int? = null,
private val fileName: String,
private val data: MutableList<T>,
private val indices: MutableMap<Int, T>
) : Serializable {
companion object {
#JvmStatic private val serialVersionUID: Long = 1
}
...
}
Project:
class Project(
var name: String = "",
var repo: String = ""
) : BaseModel<Project>(
data = Data.projects,
indices = Data.projectsIndex,
fileName = "data/projects.dat"
), Serializable {
companion object {
#JvmStatic private val serialVersionUID: Long = 1
}
override fun toString(): String {
return "Project: id=${id}, name=${name}, repo=${repo}"
}
}
Install this plugin: GenerateSerialVersionUID,use plugin to auto generate default serial version uid,usage: Click here.
Related
I've got a stupid question that stunned me a bit.
I have an enum and a data class like this:
enum class MyEventType(val typeName: String) {
FIRST("firstEventReceived")
}
data class MyEvent(
val id: String,
val event: MyEventType
)
I need to send this as a json string, but common desearilizer makes such a json
{
"id": "identifier",
"event": "FIRST"
}
but i need
{
"id": "identifier",
"event": "firstEventReceived"
}
As far as i understand, kotlin allows to override getter in data class, but i didn't succeed in it... Trying to make
data class MyEvent(
val id: String
) {
val event: MyEventType get() event.typeName
}
but i've missed something, i guess...
The simplest way is probably to annotate the property with #JsonValue:
enum class MyEventType(#JsonValue val typeName: String) {
FIRST("firstEventReceived")
}
data class MyEvent(
val id: String,
val event: MyEventType
)
fun main() {
MyEvent(id = "foo", event = MyEventType.FIRST)
.let { jacksonObjectMapper().writeValueAsString(it) }
.let { println(it) }
}
Prints:
{"id":"foo","event":"firstEventReceived"}
The easiest way is to annotate the typeName with #JsonValue. This will serialise and deserialise the enum field as you want.
enum class MyEventType(#JsonValue val typeName: String) {
FIRST("firstEventReceived");
}
An alternative is to use #JsonFormat (if you are using jackson version < 2.9);
enum class MyEventType(#JsonFormat(shape = JsonFormat.Shape.OBJECT) val typeName: String) {
FIRST("firstEventReceived");
}
Herer's an example;
#JvmStatic
fun main(args: Array<String>) {
val mapper = jacksonObjectMapper()
val json = mapper.writeValueAsString(MyEvent("1", MyEventType.FIRST))
println(json)
val event = mapper.readValue<MyEvent>(json)
println(event)
}
You get the output;
{"id":"1","event":"firstEventReceived"}
MyEvent(id=1, event=FIRST)
I used Jackson version 2.12.0. Here's a good read on enum manipulation with Jackson - https://www.baeldung.com/jackson-serialize-enums
Also you can have enum with 2+ fields which you want to be serialized
enum class MyEventType(
val firstField: String,
val secondField: String,
val thirdField: String
) {
MY_ENUM("firstFieldValue", "secondFieldValue", "thirdFieldValue")
}
You can chose one of the following two options:
Put #JsonValue over a method(lets call it getter) that will return the required value(if you need only part of the fields):
#JsonValue
fun getSerializedObject(): String {
return "{firstField: $firstField, thirdField: $thirdField}"
}
Result will be "{firstField: firstFieldValue, thirdField: thirdFieldValue}"
Put #JsonFormat(shape = JsonFormat.Shape.OBJECT) over your enum class(for serialization class as common class):
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
enum class MyEventType(
val firstField: String,
val secondField: String,
val thirdField: String
) {
MY_ENUM("firstField", "secondField", "thirdField")
}
Result will be "{"firstField": "firstFieldValue", "secondField": "secondFieldValue", "thirdField": "thirdFieldValue"}"
For GSON users, you can use the #SerializedName annotation:
enum class ConnectionStatus {
#SerializedName("open")
OPEN,
#SerializedName("connecting")
CONNECTING,
#SerializedName("closed")
CLOSED
}
I'm trying to develop a CorDapp using the example here. I've added two modules into my project, one for contracts and the other one for flows. I've added test cases for my contract and it works fine, but test cases for flow fail on setup stage. Here's the code of my test class
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SharedInformationFlowTests {
private lateinit var network: MockNetwork
private lateinit var a: StartedMockNode
private val proposal = LedgerUpdateProposal.testProposal()
#BeforeAll
fun setup() {
val params = MockNetworkParameters(cordappsForAllNodes = listOf(
TestCordapp.findCordapp("com.something.contract")
))
network = MockNetwork(params) //fails here
a = network.createPartyNode()
network.runNetwork()
}
#AfterAll
fun tearDown() {
network.stopNodes()
}
And here are the error messages I get:
[WARN] 13:42:52,620 [main] spi.SqlExceptionHelper. - SQL Error: 0, SQLState: null {changeSet=migration/vault-schema.changelog-v9.xml::update-vault-states::R3.Corda, databaseChangeLog=master.changelog.json}
[ERROR] 13:42:52,620 [main] spi.SqlExceptionHelper. - Connection is closed {changeSet=migration/vault-schema.changelog-v9.xml::update-vault-states::R3.Corda, databaseChangeLog=master.changelog.json}
net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException: Could not create the
DataSource: Migration failed for change set migration/vault-schema.changelog-v9.xml::update-vault-states::R3.Corda:
Reason: net.corda.nodeapi.internal.persistence.HibernateConfigException: Could not create Hibernate configuration: Unable to open JDBC Connection for DDL execution
I think there's something wrong with the liquibase. I've tried adding changelog file to my resources/migration directory as advised here, but it doesn't seem to have any effect. Please help.
UPDATE Added schema
/**
* The family of com.sentinel.schemas for SharingInformationState.
*/
object SharingInformationSchema
/**
* An SharingInformationState schema.
*/
object SharingInformationSchemaV1 : MappedSchema(
schemaFamily = SharingInformationSchema.javaClass,
version = 1,
mappedTypes = listOf(PersistentSharingInformation::class.java)) {
override val migrationResource: String? = "sharing-information-schema-v1.changelog-master.xml"
#Entity
#Table(name = "persistent_sharing_information")
class PersistentSharingInformation(
#Column(name = "owner_id")
var dataOwnerId: Long,
#Column(name = "buyer_id")
var dataBuyerId: Long,
#Column(name = "start_date")
val startDate: String,
#Column(name = "end_date")
val endDate: String,
#Column(name = "shared_fields")
val sharedFieldsIds: String,
#Column(name = "agreement_status")
val agreementStatus: String,
#Column(name = "contract_type")
val contractType: String,
#Column(name = "linear_id")
var linearId: UUID
) : PersistentState() {
// Default constructor required by hibernate.
constructor() : this(0L, 0L,
"", "", "[]", "", "", UUID.randomUUID())
}
}
#BelongsToContract(com.package.contract.SharingInformationContract::class)
class SharingInformationState(val ourParty: Party,
val proposal: LedgerUpdateProposal,
override val linearId: UniqueIdentifier = UniqueIdentifier()) : LinearState, QueryableState {
override val participants: List<AbstractParty> = listOf(ourParty)
override fun generateMappedObject(schema: MappedSchema): PersistentState {
return when (schema) {
SharingInformationSchemaV1 -> SharingInformationSchemaV1.PersistentSharingInformation(
proposal.ownerId,
proposal.buyerId,
proposal.startDate,
proposal.endDate,
proposal.sharedFieldsIds.toString(),
proposal.agreementStatus.name,
proposal.contractType.name,
linearId.id
)
else -> throw IllegalArgumentException("Unrecognised schema $schema")
}
}
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(SharingInformationSchemaV1)
}
#CordaSerializable
enum class AgreementStatus { APPROVED, REJECTED }
#CordaSerializable
enum class ContractType { CORPORATE, CONSUMER, MARKETING, BINDING }
In reference to https://google.github.io/dagger/multibindings.html, there's this code
#AutoAnnotation
static MyKey createMyKey(String name, Class<?> implementingClass, int[] thresholds) {
return new AutoAnnotation_MyComponentTest_createMyKey(name, implementingClass, thresholds);
}
When converted to Kotlin, I use below
companion object {
#AutoAnnotation
#JvmStatic
fun createMyKey(name: String, implementingClass: Class<*>, thresholds: IntArray): MyKey {
return AutoAnnotation_MainActivity_createMyKey(name, implementingClass, thresholds)
}
}
It still complaints
error: #AutoAnnotation method must be static
public final com.elyeproj.daggermultibinding.MyKey createMyKey(#org.jetbrains.annotations.NotNull()
^
I tried both with and without #JvmStatic still not working. How to resolve this?
The following works for me. Have the create key method as global function.
class HelloAutoAnnotations {
fun execute() {
println("HelloAutoAnnotations...")
DaggerMyComponent.create().myMap().forEach(::println)
}
}
#MapKey(unwrapValue = false)
private annotation class MyKey(val username: String, val password: String)
#Module
private class MyModule {
#Provides
#IntoMap
#MyKey(username = "user1", password = "T0gether")
fun providesUser(): String = "Rooney"
#Provides
#IntoMap
#MyKey(username = "user2", password = "T0gether")
fun provideUser(): String = "Doo"
}
#Component(modules = arrayOf(MyModule::class))
private interface MyComponent {
fun myMap(): Map<MyKey, String>
}
#AutoAnnotation
private fun createMyKey(username: String, password: String): MyKey {
return MyKeyCreator.createMyKey(username, password)
}
According to the docs:
Maps whose keys are not known at compile time
Map multibindings work only if your map’s keys are known at compile
time and can be expressed in an annotation. If your map’s keys don’t
fit in those constraints, then you cannot create a multibound map, but
you can work around that by using set multibindings to bind a set of
objects that you can then transform into a non-multibound map.
#Module
class MyModule {
#Provides #IntoSet
static Map.Entry<Foo, Bar> entryOne(...) {
Foo key = ...;
Bar value = ...;
return new SimpleImmutableEntry(key, value);
}
#Provides #IntoSet
static Map.Entry<Foo, Bar> entryTwo(...) {
Foo key = ...;
Bar value = ...;
return new SimpleImmutableEntry(key, value);
}
}
#Module
class MyMapModule {
#Provides
static Map<Foo, Bar> fooBarMap(Set<Map.Entry<Foo, Bar>> entries) {
Map<Foo, Bar> fooBarMap = new LinkedHashMap<>(entries.size());
for (Map.Entry<Foo, Bar> entry : entries) {
fooBarMap.put(entry.getKey(), entry.getValue());
}
return fooBarMap;
}
}
So you should try this approach perhaps.
I'm converting a Java class that extends an abstract class as per the code below
public class BadRequestAlertException extends AbstractThrowableProblem {
private static final long serialVersionUID = 1L;
private final String entityName;
private final String errorKey;
public BadRequestAlertException(String defaultMessage, String entityName, String errorKey) {
this(ErrorConstants.DEFAULT_TYPE, defaultMessage, entityName, errorKey);
}
public BadRequestAlertException(URI type, String defaultMessage, String entityName, String errorKey) {
super(type, defaultMessage, Status.BAD_REQUEST, null, null, null, getAlertParameters(entityName, errorKey));
this.entityName = entityName;
this.errorKey = errorKey;
}
public String getEntityName() {
return entityName;
}
public String getErrorKey() {
return errorKey;
}
private static Map<String, Object> getAlertParameters(String entityName, String errorKey) {
Map<String, Object> parameters = new HashMap<>();
parameters.put("message", "error." + errorKey);
parameters.put("params", entityName);
return parameters;
}
}
Into Kotlin as per the implementation below
class BadRequestAlertException(type: URI = ErrorConstants.DEFAULT_TYPE, defaultMessage: String, val entityName: String, val errorKey: String) : AbstractThrowableProblem(type, defaultMessage, Status.BAD_REQUEST, null, null, null, getAlertParameters(entityName, errorKey)) {
companion object {
private const val serialVersionUID = 1L
private fun getAlertParameters(entityName: String, errorKey: String): Map<String, Any> {
val parameters = HashMap<String, Any>()
parameters["message"] = "error.$errorKey"
parameters["params"] = entityName
return parameters
}
}
}
The Kotlin implementation raises the following error Class 'BadRequestAlertException' is not abstract and does not implement abstract base class member public abstract fun getCause(): Exceptional! defined in org.zalando.problem.AbstractThrowableProblem.
Why doesn't the Java version implement the getClause() method, but Kotlin requires the method to be overridden? Is there a problem with the conversion, or just the way Kotlin extends abstract classes?
FWIW it appears as though the legitimate bug that originally caused this issue is fixed in Kotlin 1.5.0. See KT-45853 ("JVM / IR: "Accidental override" caused by inheriting Throwable.getCause from Java interface") for details... the following Kotlin class compiles & runs as far as I can tell:
import com.fasterxml.jackson.annotation.JsonIgnore
import java.net.URI
import org.zalando.problem.AbstractThrowableProblem
import org.zalando.problem.Exceptional
import org.zalando.problem.Status
class BadRequestAlertExceptionKt(
type: URI = URI.create("about:blank"),
defaultMessage: String,
val entityName: String,
val errorKey: String
) : AbstractThrowableProblem(
type,
defaultMessage,
Status.BAD_REQUEST,
null,
null,
null,
getAlertParameters(entityName, errorKey)
) {
companion object {
private const val serialVersionUID = 1L
private fun getAlertParameters(entityName: String, errorKey: String): Map<String, Any> {
val parameters = HashMap<String, Any>()
parameters["message"] = "error.$errorKey"
parameters["params"] = entityName
return parameters
}
}
#JsonIgnore
override fun getCause(): Exceptional? = super.cause
}
It has been already clarified what's the difference between val and const val here.
But my question is, why we should use const keyword? There is no difference from the generated Java code perspective.
This Kotlin code:
class Application
private val testVal = "example"
private const val testConst = "another example"
Generates:
public final class ApplicationKt
{
private static final String testVal = "example";
private static final String testConst = "another example";
}
It's not always the same generated code.
If testVal and testConst were public, the generated code wouldn't be the same. testVal would be private with a public get, whereas testConst would be public, without any getter. So const avoids generating a getter.
In my opinion the main difference is that val means that no setter will be generated for the property (but a getter will be generated) and not that the value is constant, while a const val is a constant (like a Java's private/public static final xxx).
Example:
class Foo {
private val testVal: String
get() = Random().nextInt().toString()
}
As directly mentioned in the documentation, testConst can be used in annotation parameters, but testVal can't.
More generally speaking, const guarantees that you have a constant variable in the Java sense, and
Whether a variable is a constant variable or not may have implications with respect to class initialization (§12.4.1), binary compatibility (§13.1), reachability (§14.21), and definite assignment (§16.1.1).
You don't see the difference between generated code because your variables are private. Otherwise the result would have the getter for testVal:
public final class ApplicationKt {
#NotNull
private static final String testVal = "example";
#NotNull
public static final String testConst = "another example";
#NotNull
public static final String getTestVal() {
return testVal;
}
}
So in your particular case it is the same, except you can use const properties in annotations:
const val testVal: String = "This subsystem is deprecated"
#Deprecated(testVal) fun foo() { ... }
There are also differences in using them.
Example of constants(Kotlin):
class Constants {
companion object {
val EXAMPLE1 = "example1" // need companion and a getter
const val EXAMPLE2 = "example2" // no getter, but companion is generated and useless
#JvmField val EXAMPLE3 = "example3"; // public static final with no getters and companion
}
}
How to use(Java):
public class UseConstants {
public void method(){
String ex1 = Constants.Companion.getEXAMPLE1();
String ex2 = Constants.EXAMPLE2;
String ex3 = Constants.EXAMPLE3;
}
}
"Consts" are compile time Constants whereas "val" is used to define constants at run time.
This means, that "consts" can never be assigned to a function or any class constructor, but only to a String or primitive.
Example:
const val NAME = "M Sakamoto"
val PICon = getPI()
fun getPI(): Double {
return 3.14
}
fun main(args: Array<String>) {
println("Name : $NAME")
println("Value of PI : $PICon")
}
Output:
Name : M Sakamoto
Value of PI : 3.14