This gives Java a way to handle partial representations of data model while remaining type safe. Types are generated at compile time via annotations.
<dependencies>
<dependency>
<groupId>com.github.henneberger</groupId>
<artifactId>typekin-processor</artifactId>
<version>1.0.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
public class Main {
// The optional model definition. If present, all TypeOf
// annotations must match this structure.
//
// You add `implements St{name}Model`
@Model
abstract class Foo implements StFooModel {
public abstract String getA();
public abstract String getB();
public abstract String getC();
}
//A subset of the model (for code reuse)
@TypeOf(model = Foo.class)
public interface FooAFragment {
String getA();
}
@TypeOf(model = Foo.class)
public interface FooABFragment {
String getA();
String getB();
}
@TypeOf(model = Foo.class)
public interface FooCFragment {
String getC();
}
//compile error, D is not a property of the model
//@TypeOf(clazz = Foo.class)
//public interface FooDFragment {
// String getD();
//}
// A container of data that can contain any data.
// Function signatures are used to determine
// equivalence classes with TypeOf classes.
//
// You add `implements St{name}`
@StructuralType(model = Foo.class)
public static class FooAData implements StFooAData {
public String getA() { return "A"; }
}
@StructuralType(model = Foo.class)
public static class FooABData implements StFooABData {
public String getA() { return "A"; }
public String getB() { return "B"; }
}
@StructuralType(model = Foo.class)
public static class FooCData implements StFooCData {
public String getC() { return "C"; }
}
public static void main(String[] args) {
print(new FooAData());
print(new FooABData());
//print(new FooCData()); //compile error
}
public static void print(FooAFragment foo) {
System.out.println(foo.getA());
//System.out.println(foo.getB()); //compile error
//System.out.println(foo.getC()); //compile error
}
}
//This gets generated by the annotations:
interface StFooAData extends FooAFragment {}
interface StFooABData extends FooAFragment, FooABFragment {}
interface StFooCData extends FooCFragment {}
interface StFooModel extends FooAFragment, FooABFragment, FooCFragment {}
interface StFooRef extends StFooAData, StFooABData, StFooCData {}
The @Model
annotation defines the data model. All @TypeOf
model fragments that
refer to a @Model
class will be strongy typed. Only abstract methods will be recognized
for type candidates. For nested objects, a generated Ref
class serves as a type reference.
Parameters:
name
: The name of the class it will generaterefName
: The name of the class it will generate for data model relationshipsconcreteName
: The name of the empty concrete class for jvm type validation
E.g.
@Model(name = "FooModel", refName = "FooRef")
abstract class Foo implements FooModel {
public abstract String getA();
public abstract List<BarRef> getBar();
}
@Model(name = "BarModel", refName = "BarRef")
abstract class Bar implements BarModel {
public abstract String getA();
public abstract FooRef getFoo();
}
The @TypeOf
annotation defines a partial representation of the data model. All @StructuralType
classes that have the same model class are compared structurally to all @TypeOf
interfaces.
Parameters:
model
: The class of the model
E.g:
@TypeOf(model = Foo.class)
public interface FooFragment {
String getA();
List<? extends BarFragment> getBar();
}
@TypeOf(model = Bar.class)
public interface BarFragment {
String getA();
FooFragment getFoo();
}
A concrete type that contains data. This can contain any data but only method signatures
that match the model will be used as candidates for @TypeOf
classes.
Parameters:
model
: The class of the modelname
: The name of the class it will generate
E.g.
@StructuralType(model = Foo.class, name = "FooDataType")
public class FooData extends FooDataType {
public String getA() { return null; }
public List<BarData> getBar() { return null; }
public String extraParam() { return null; } //ok
}
@StructuralType(model = Bar.class, name = "BarDataType")
public class BarData extends BarDataType {
public String getA() { return null; }
public Foo getFoo() { return null; }
}
A full example can be found here: tests/src/main/java/com/github/henneberger/typekin/tests/example/FooExample.java
Since it relies on compile time annotations, other annotations may not work with Typekin.
This work is inspiried by https://github.com/tlamr/stjava