@groovy.transform.TypeChecked
abstract class Entity {
...
double getMass() {
...
}
...
}
@groovy.transform.TypeChecked
abstract class Location {
...
Entity[] getContent() {
...
}
...
}
@groovy.transform.TypeChecked
abstract class Container {...} //inherits, somehow, from both Location and Entity
@groovy.transform.TypeChecked
class Main {
void main() {
double x
Container c = new Chest() //Chest extends Container
Entity e = c
x = e.mass
Location l = c
x = l.content //Programmer error, should throw compile-time error
}
}
Essentially, is there a way to achieve this, without sacrificing any of the three properties outlines in main()
:
I don't think you can do that with classes. Maybe you'd wanted traits (under discussion update: available in Groovy 2.3 and already rocking!) or, for a pure dynamic solution, @Mixin
, which you'd back up with a good test suite.
My guess: @Delegate
is your best friend here, but, as it stands, you can only store a Chest
object in a Container
type variable. So you'd need some interfaces.
Even if the superclass is not under your control, you can use groovy as
operator to make it implement an interface.
First, i rewrote your classes to remove the abstract
and add interfaces:
import groovy.transform.TypeChecked as TC
interface HasMass { double mass }
interface HasContent { Entity[] getContent() }
@TC class Entity implements HasMass { double mass }
@TC class Location {
Entity[] getContent() {
[new Entity(mass: 10.0), new Entity(mass: 20.0)] as Entity[]
}
}
Note i didn't added HasContent
to Location
, to show the usage of as
.
Second, comes the Container
and Chest
. @Delegate
is added and it auto-inherits the interfaces of the delegates:
@TC
abstract class Container {
@Delegate Location location = new Location()
@Delegate Entity entity = new Entity()
}
@TC class Chest extends Container { }
Last, it becomes type-checkable, as long as you stick to interfaces:
@TC class Mult {
static main(args) {
def x // use 'def' for flow-typing
Container c = new Chest() //Chest extends Container
HasMass e = c
x = e.mass
def l = c as HasContent
x = l.content //Programmer error, should throw compile-time error
assert c.content.collect { Entity it -> it.mass } == [10.0, 20.0]
}
}