Not a solution, just a very preliminary mock-up
+---------------------------------------------------------+
| |
| D I S C L A I M E R |
| |
| This is a mock-up. It is not type-safe. It relies on |
| runtime reflection (even worse: it relies on |
| Java-reflection!). Do not use this in production. |
| |
| If you can come up with a type-safe solution, I will |
| definitely take a look at it and upvote your answer. |
| |
+---------------------------------------------------------+
You have said explicitly that type-safety is a must, so the code below cannot count as a solution. However, before investigating any further, maybe you would like to experiment with a purely runtime-reflection based implementation, to understand the requirements better. Here is a very quick-and-dirty mock-up implementation:
import scala.language.dynamics
class DynamicBuilder[X](underConstruction: X) extends Dynamic {
val clazz = underConstruction.getClass
def applyDynamic(name: String)(arg: Any): DynamicBuilder[X] = {
if (name.startsWith("with")) {
val propertyName = name.drop(4)
val setterName = "set" + propertyName
clazz.getDeclaredMethods().
find(_.getName == setterName).
fold(throw new IllegalArgumentException("No method " + setterName)) {
m =>
m.invoke(underConstruction, arg.asInstanceOf[java.lang.Object])
this
}
} else {
throw new IllegalArgumentException("Expected 'result' or 'withXYZ'")
}
}
def result(): X = underConstruction
}
object DynamicBuilder {
def build[A](a: A) = new DynamicBuilder[A](a)
}
Once the build
-method is imported
import DynamicBuilder.build
and the definitions of the classes that correspond to POJOs are in scope
class SomethingElse(val p5: String) {
var bar: String = _
def setBar(s: String): Unit = { bar = s }
override def toString = s"SomethingElse[p5 = $p5, bar = $bar]"
}
class SomeBigJavaObj(val p1: Float, val p2: Double) {
var foo: Int = 0
var bar: String = _
var sthElse: SomethingElse = _
def setFoo(i: Int): Unit = { foo = i }
def setBar(s: String): Unit = { bar = s }
def setSomethingElse(s: SomethingElse): Unit = { sthElse = s }
override def toString: String =
s"""|SomeBigJavaObj[
| p1 = $p1, p2 = $p2,
| foo = $foo, bar = $bar,
| sthElse = $sthElse
|]""".stripMargin
}
and also all the required variables p1
,...,p6
from your example are defined
val p1 = 3.1415f
val p2 = 12345678d
val p3 = 42
val p4 = "BAR"
val p5 = "P5"
val p6 = "b-a-r"
you can use exactly the syntax from your question:
val obj: SomeBigJavaObj =
build(new SomeBigJavaObj(p1, p2))
.withFoo(p3)
.withBar(p4)
.withSomethingElse(
build(new SomethingElse(p5))
.withBar(p6)
.result()
)
.result()
The result looks as follows:
println(obj)
// Output:
// SomeBigJavaObj[
// p1 = 3.1415, p2 = 1.2345678E7,
// foo = 42, bar = BAR,
// sthElse = SomethingElse[p5 = P5, bar = b-a-r]
// ]
For now, the idea is just to see how badly it fails when you try to use it with a somewhat more realistic example. It might turn out that in reality, everything is a little bit more complicated:
- Maybe some setters are generic
- Maybe some of them use Java wildcards with Java's strange call-site variance
- Maybe instead of setters there are some other methods that take multiple parameters as varargs
- Maybe there are overloaded setters with same name but different types of arguments.
- etc.
I understand that this cannot be a solution, however, I hope that this might be useful as an additional feasibility check, and that it maybe helps to make the requirements a tiny bit more precise, before investing more time and energy in type-safe macro-based solutions.
If this does roughly what you wanted, I might consider to update the answer. If this is not helpful at all, I'll delete the answer.