Below is a type-safe, fluid, builder pattern in Scala as described at http://www.tikalk.com/java/blog/type-safe-builder-scala-using-type-constraints. It's similar to Builder Library for Scala and Java, but deals specifically with compile-time builder checks. How can this called from Java? Can it be done with a clean API for Scala AND Java given the "scala.Predef$$eq$colon$eq" parameters?
sealed trait TBoolean
sealed trait TTrue extends TBoolean
sealed trait TFalse extends TBoolean
class Builder[HasProperty <: TBoolean] private(i: Int) {
protected def this() = this(-1)
def withProperty(i: Int)(implicit ev: HasProperty =:= TFalse) = new Builder[TTrue](i)
def build(implicit ev: HasProperty =:= TTrue) = println(i)
}
//javap output
public class Builder extends java.lang.Object implements scala.ScalaObject{
public Builder withProperty(int, scala.Predef$$eq$colon$eq); //How is this called from Java?
public void build(scala.Predef$$eq$colon$eq);
public Builder();
}
object Builder {
def apply() = new Builder[TFalse]
}
You should be able to use this API from Java, with some extra noise compared to the Scala version. A few convenience fields will quiet things a bit:
object Builder {
def apply() = new Builder[TFalse]
val unassigned = =:=.tpEquals[TFalse]
val assigned = =:=.tpEquals[TTrue]
}
The Java client code should end up looking like
Builder$.MODULE$.apply()
.withProperty(10, Builder$.MODULE$.unassigned())
.build(Builder$.MODULE$.assigned());
The build
method has to check that every property is assigned, so it gets pretty noisy when you generalize to multiple properties:
Builder$.MODULE$.apply()
.withProp1(10, Builder$.MODULE$.unassigned())
.withProp2(20, Builder$.MODULE$.unassigned())
.withProp3(30, Builder$.MODULE$.unassigned())
// ...
.build(Builder$.MODULE$.assigned(),
Builder$.MODULE$.assigned(),
Builder$.MODULE$.assigned(),
//...
);
With some static delegates in a helper class (and some static imports), you should be able to get this down to something like:
createBuilder()
.withProp1(10, unassigned())
.withProp2(20, unassigned())
.build(assigned(), assigned());
OK, thanks to Aaron and ittayd...here is a builder with a fluid API for Scala and Java:
import annotation.bridge
sealed trait TBoolean
sealed trait TTrue extends TBoolean
sealed trait TFalse extends TBoolean
class Builder[HasProperty <: TBoolean] private(i: Int) {
protected def this() = this(-1)
def withProperty(i: Int)(implicit ev: HasProperty =:= TFalse) = new Builder[TTrue](i)
def build(implicit ev: HasProperty =:= TTrue):Int = i
@bridge def withProperty(i: Int) = new Builder[TTrue](i)
@bridge def build = build(null)
}
object Builder {
def apply() = new Builder[TFalse]
val unassigned = =:=.tpEquals[TFalse]
val assigned = =:=.tpEquals[TTrue]
}
Scala Usage (Type Safe):
val v = Builder().withProperty(2).build
Java Usage I (type safe, but ugly):
int i = Builder$.MODULE$.apply()
.withProperty(5, Builder$.MODULE$.unassigned())
.build(Builder$.MODULE$.assigned());
Java Usage II (not type safe, but much cleaner, using @Bridge calls):
static Builder createBuilder() {
return Builder$.MODULE$.apply();
}
...
int j = createBuilder().withProperty(7).build();