As a Scala developer I prefer to use Scala libraries rather than Java ones for my code. However, it is not always possible. Sometimes I might not find a required library, or I just have to use some legacy code I already have. Even though Scala runs on a JVM and is fully compatible with Java, the languages have different ideologies, different code styles and sometimes different API’s. Thus, in Scala it is preferable to use immutable case classes for the data modeling. However, in Java the common building blocks are JavaBeans, which are mutable. Another problem is that a lot of Scala libraries expect case classes. Even if they work with JavaBeans, usually you have to write some boilerplate code.
So, quite often it is easier to have a separate model in your Scala code, and converters between Java model classes and Scala ones.
BeanPurée is a library that helps you to automate this process. And it helps you to do even more, because it’s a bridge from JavaBeans to shapeless.
Let’s take this Java class as an example:
public class Dog {
private String name;
private int age;
private boolean chaseCats;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public boolean isChaseCats() { return chaseCats; }
public void setChaseCats(boolean chaseCats) { this.chaseCats = chaseCats; }
public String toString() {
return "Dog " + name + ", " + age +
(chaseCats ? " is looking for cats" : " is sleeping");
}
}
BeanPurée provides BeanGeneric
class, which has the same function with
shapeless’s Generic
:
> import me.limansky.beanpuree._
scalaimport me.limansky.beanpuree._
> val gen = BeanGeneric[Dog]
scala: me.limansky.beanpuree.BeanGeneric[Dog]{type Repr = shapeless.::[String,shapeless.::[Int,shapeless.::[Boolean,shapeless.HNil]]]} = $anon$1@56f5a8b7 gen
We just get a BeanGeneric
instance with representation type String :: Int :: Boolean :: HNil
. Internally, BeanGeneric
uses the getters order to build the
Repr type. Let’s try to use it:
> val fima = gen.from("Fima" :: 12 :: false :: HNil)
scala: Dog = Dog Fima, 12 is sleeping
fima
> fima.setChaseCats(true)
scala
> gen.to(fima)
scala: gen.Repr = Fima :: 12 :: true :: HNi res3
So far, so good, let’s combine it with shapeless:
> case class ScalaDog(name: String, age: Int, chaseCats: Boolean)
scalaclass ScalaDog
defined
> val sgen = Generic[ScalaDog]
scala: shapeless.Generic[ScalaDog]{type Repr = shapeless.::[String,shapeless.::[Int,shapeless.::[Boolean,shapeless.HNil]]]} = anon$macro$8$1@45e06950
sgen
> sgen.from(gen.to(fima))
scala: ScalaDog = ScalaDog(Fima,12,true) res4
Since the shape of Dog
and ScalaDog
is the same we can convert from one
class to another using a combination of Generic
and BeanGeneric
.
Just as shapeless provides LabelledGeneric
with field names information in the
Repr
type, BeanPurée provides LabelledBeanGeneric
, which adds property
names to the generic representation.
> val lgen = LabelledBeanGeneric[Dog]
scala: me.limansky.beanpuree.LabelledBeanGeneric[Dog]{type Repr = shapeless.::[String with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("name")],String],shapeless.::[Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("age")],Int],shapeless.::[Boolean with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("chaseCats")],Boolean],shapeless.HNil]]]} = me.limansky.beanpuree.LabelledBeanGeneric$$anon$1@412d56b4
lgen
> lgen.from('name ->> "Rex" :: 'age ->> 5 :: 'chaseCats ->> true :: HNil)
scala: Dog = Dog Rex, 5 is looking for cats res7
Having this stuff allows me to implement a more intelligent (but not too much)
converter between case classes and beans. This class is called BeanConverter
.
It doesn’t rely on a definition order of properties . Instead, it uses names.
> case class ScalaDog(name: String, chaseCats: Boolean, age: Int)
scalaclass ScalaDog
defined
> val conv = BeanConverter[Dog, ScalaDog]
scala: me.limansky.beanpuree.BeanConverter[Dog,ScalaDog] = me.limansky.beanpuree.BeanConverter$$anon$1@5dd7b913
conv
> conv.productToBean(ScalaDog("Lassie", false, 5))
scala: Dog = Dog Lassie, 5 is sleeping res0
That’s all the features of the release 0.1, which is available in the Sonatype Maven repository. I have several ideas for the next releases, e.g automatic Java types to Scala types conversion, and partial converters.