Problems with appending objects to a ListBuffer in Scala

2.3k views Asked by At

I am having Problems appending Objects to a scala.collection.mutable.ListBuffer. I am familiar with the respective API and know that normally you use the += or ++= method to add an object or Sequence of Objects.

I am implementing a card game with network support and have the simple problem of adding some chosen cards to a List of hand cards. In the following code I am going to get the reference to the List of hand cards(ListBuffer), print the size of the ListBuffer, add the chosen cards to it and print the size again.

// get the references and ensure that it are rally ListBuffers / Lists
val handCards: mutable.ListBuffer[ClientCard] = playerPanel.player.handCards
val chosenCards: List[ClientCard] = _chosenCards

// print the number of elements per list
println("number of hand cards: " + handCards.size)
println("number of chosen cards: " + chosenCards.size)

// append the chosen cards to the hand cards
println("append operation: " + handCards + " ++= " + chosenCards)
handCards ++= chosenCards

// print the number of hand cards again
println("number of hand cards: " + handCards.size)

As the result one would expect, that the size of the handCards would grow by the size of the chosen cards. But the output is (formated):

number of hand cards: 5
number of chosen cards: 2

append operation: ListBuffer(
    rftg.card.Card$$anon$1@1304043, 
    rftg.card.Card$$anon$1@cb07ef, 
    rftg.card.Card$$anon$1@176086d, 
    rftg.card.Card$$anon$1@234265, 
    rftg.card.Card$$anon$1@dc1f04
) ++= List(
    rftg.card.Card$$anon$1@1784427, 
    rftg.card.Card$$anon$1@c272bc
)

number of hand cards: 5

So the elements have not been appended.

A ClientCard is always a representative of a "real card" and only consists of information needed for drawing the card.

trait ClientCard extends AnyRef with ClientObject with CardLike

trait ClientObject extends Serializable {
    def uid: Int
}

trait CardLike {
    val imagePath: String
}

A ClientCard is created in the Card class:

def clientCard = new ClientCard() {
    val uid = Card.this.hashCode()
    val imagePath = CardTemplate.cardFolder + Card.this.imageFilename
}

And there is the ClientPlayer (the representative of a "real player") where the ListBuffer is created:

// definition of ClientPlayer trait
trait ClientPlayer extends ClientObject {
    val victoryPoints: Int
    val handCards: mutable.ListBuffer[ClientCard] 
    val playedCards: mutable.ListBuffer[ClientCard]
}

// piece of code to create a client player
def clientPlayer = new ClientPlayer() {
    val uid = Player.this.hashCode()
    val victoryPoints = Player.this.victoryPoints

    val handCards = new mutable.ListBuffer[ClientCard]
    handCards ++= (Player.this.handCards.map(_.clientCard)) 

    val playedCards = new mutable.ListBuffer[ClientCard]
    playedCards ++= Player.this.playedCards.map(_.clientCard)
}

Does anyone know what is going wrong here? Or to be more generic: what circumstances are there to prevent the successful appending of objects to a ListBuffer?

Edit: There is something that I forgot to mention and what seemed to cause this strange behaviour. After creation of the handCards ListBuffer it is being sent over network and is therefore being serialized and deserialized again.

After Rex Kerr's comment I tried to create a deep-copy method for a ClientPlayer and copyed each ClientPlayer right after recieving it. This solved the problem. Does anyone have an explanation for this behaviour?

1

There are 1 answers

1
Rex Kerr On BEST ANSWER

Deserialization produces an extraordinarily fragile ListBuffer. This is probably a bug, but as a workaround, the only thing you should do with it is immediately add it to some other collection (e.g. by toListing it, or by adding it to an empty ListBuffer).

Here's some code you can use to verify that serialization/deserialization is problematic:

import collection.mutable.ListBuffer
import java.io._
val baos = new ByteArrayOutputStream
val oos = new ObjectOutputStream(baos)
oos.writeObject( ListBuffer(1,2,3) )
val bais = new ByteArrayInputStream( baos.toByteArray )
val ois = new ObjectInputStream(bais)
val lb = ois.readObject.asInstanceOf[ListBuffer[Int]]
val lb2 = ListBuffer[Int]() ++= lb
lb2 ++= List(1)  // All okay
lb ++= List(1)  // Throws an exception for me

I'll submit a bug report, but for the time being you should not rely upon the ListBuffer being in a sensible state when deserialized, and instead rebuild it. (You may wish to serialize and deserialize a List instead.)