I'm having an issue with some Scala code and am trying to rule out a bad class design (well, constructor design) before I begin to treat it as a networking issue.
So I have a model Scala class called Hail
:
class Hail(val handle : String, val message : String) extends BaseMessage {
def this() {
this("default_user", "default_message")
}
}
abstract class BaseMessage extends AbstractMessage(true) {
}
// This is a 3rd party open source class written in Java
public abstract class AbstractMessage implements Message
{
private transient boolean reliable = true;
protected AbstractMessage()
{
}
protected AbstractMessage( boolean reliable )
{
this.reliable = reliable;
}
public Message setReliable(boolean f)
{
this.reliable = f;
return this;
}
public boolean isReliable()
{
return reliable;
}
}
// This is also a 3rd party open source class written in Java
public interface Message
{
public Message setReliable(boolean f);
public boolean isReliable();
}
At runtime, instances of this class get serialized (to binary), sent over the wire to a server, where they are deserialized (back into Hail
instances) and processed.
So I have client code that looks like this:
val h1 : Hail = new Hail("user1", "Hello!")
val h2 : Hail = new Hail("user2", "Aloha!")
val h3 : Hail = new Hail("user3", "Bien venu mes amigos")
client.send(h1)
client.send(h2)
client.send(h3)
When the server receives these messages, it prints out their handle/message combos to STDOUT. The messages I receive are as follows:
Server received a Hail: default_user, default_message
Server received a Hail: default_user, default_message
Server received a Hail: default_user, default_message
Instead of what I would be expecting:
Server received a Hail: user1, Hello!
Server received a Hail: user1, Aloha!
Server received a Hail: user3, Bien venu mes amigos
Again, this could be a networking/serialization/server-side issue. But before I go down that route I want to make sure my Hail
class has been written correctly.
The serialization framework I'm using requires all messages (such as Hail
) to have no-arg constructors (hence the one I provided above). So it seems to me that something is wrong with my other constructor, and perhaps the server defaults to calling the no-arg constructor because it can't use anything else.
I decompiled my Hail
class and see the following:
@ScalaSignature(bytes="<lots of bytes here omitted for brevity")
public class Hail
extends BaseMessage
{
private final String handle;
public String handle()
{
return this.handle;
}
public String message()
{
return this.message;
}
public Hail()
{
this("default_user", "default_message");
}
public Hail(String handle, String message) {}
}
Right away several things are curious/suspicious to me:
private final String handle
(which is desired!), I don't see a reciprocal private final String message
field...public Hail(String handle, String message)
) is empty/undefined. This is probably the root of my issues.So I ask, how can I refactor Hail
's source so that the following end result is bytecode that would decompile to:
@ScalaSignature(bytes="<lots of bytes here omitted for brevity")
public class Hail
extends BaseMessage
{
private final String handle;
public String handle()
{
return this.handle;
}
public String message()
{
return this.message;
}
public Hail()
{
this("default_user", "default_message");
}
public Hail(String handle, String message)
{
this.handle = handle;
this.message = message;
}
}
Any ideas?
The problem is that the serializer server-side uses the no-arg constructor, and then change the values of mutable parameters in the class.
However, scala val
s are not mutable, so they are compiled as final
parameters of your class, and as such cannot be mutated. So the objects are instantiated with the default values, and then keep those values, since they cannot be mutated.
I would recommend using a scala-compatible serializer, but a simpler solution is to allow the properties to be mutated by declaring them as var
s instead of val
s.