Skip to content

Commit 4f9ce0b

Browse files
committed
cleaned up the public API,
prepared base API for the custom ClassModel builder API
1 parent 5779050 commit 4f9ce0b

4 files changed

Lines changed: 210 additions & 64 deletions

File tree

foundry-core/src/main/java/org/machinemc/foundry/model/AttributeAccess.java

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,112 @@ public java.lang.reflect.Method reflect(Class<?> source) throws NoSuchMethodExce
7070

7171
}
7272

73+
/**
74+
* Represents a custom, possibly more complex getter.
75+
*
76+
* @param <T> type of the object this getter invokes at
77+
*/
78+
public sealed interface CustomGetter<T> extends Get {
79+
80+
@FunctionalInterface
81+
non-sealed interface Bool<T> extends CustomGetter<T> {
82+
boolean get(T obj);
83+
}
84+
85+
@FunctionalInterface
86+
non-sealed interface Char<T> extends CustomGetter<T> {
87+
char get(T obj);
88+
}
89+
90+
@FunctionalInterface
91+
non-sealed interface Byte<T> extends CustomGetter<T> {
92+
byte get(T obj);
93+
}
94+
95+
@FunctionalInterface
96+
non-sealed interface Short<T> extends CustomGetter<T> {
97+
short get(T obj);
98+
}
99+
100+
@FunctionalInterface
101+
non-sealed interface Int<T> extends CustomGetter<T> {
102+
int get(T obj);
103+
}
104+
105+
@FunctionalInterface
106+
non-sealed interface Long<T> extends CustomGetter<T> {
107+
long get(T obj);
108+
}
109+
110+
@FunctionalInterface
111+
non-sealed interface Float<T> extends CustomGetter<T> {
112+
float get(T obj);
113+
}
114+
115+
@FunctionalInterface
116+
non-sealed interface Double<T> extends CustomGetter<T> {
117+
double get(T obj);
118+
}
119+
120+
@FunctionalInterface
121+
non-sealed interface Object<T, V> extends CustomGetter<T> {
122+
V get(T obj);
123+
}
124+
125+
}
126+
127+
/**
128+
* Represents a custom, possibly more complex setter.
129+
*
130+
* @param <T> type of the object this setter invokes at
131+
*/
132+
public sealed interface CustomSetter<T> extends Set {
133+
134+
@FunctionalInterface
135+
non-sealed interface Bool<T> extends CustomSetter<T> {
136+
void set(T obj, boolean value);
137+
}
138+
139+
@FunctionalInterface
140+
non-sealed interface Char<T> extends CustomSetter<T> {
141+
void set(T obj, char value);
142+
}
143+
144+
@FunctionalInterface
145+
non-sealed interface Byte<T> extends CustomSetter<T> {
146+
void set(T obj, byte value);
147+
}
148+
149+
@FunctionalInterface
150+
non-sealed interface Short<T> extends CustomSetter<T> {
151+
void set(T obj, short value);
152+
}
153+
154+
@FunctionalInterface
155+
non-sealed interface Int<T> extends CustomSetter<T> {
156+
void set(T obj, int value);
157+
}
158+
159+
@FunctionalInterface
160+
non-sealed interface Long<T> extends CustomSetter<T> {
161+
void set(T obj, long value);
162+
}
163+
164+
@FunctionalInterface
165+
non-sealed interface Float<T> extends CustomSetter<T> {
166+
void set(T obj, float value);
167+
}
168+
169+
@FunctionalInterface
170+
non-sealed interface Double<T> extends CustomSetter<T> {
171+
void set(T obj, double value);
172+
}
173+
174+
@FunctionalInterface
175+
non-sealed interface Object<T, V> extends CustomSetter<T> {
176+
void set(T obj, V value);
177+
}
178+
179+
}
180+
73181
}

foundry-core/src/main/java/org/machinemc/foundry/model/ClassModel.java

Lines changed: 55 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,59 @@ public class ClassModel {
3838
* @throws IllegalStateException if the class does not satisfy the requirements
3939
*/
4040
public static ClassModel of(Class<?> type) {
41+
return of0(type);
42+
}
43+
44+
protected ClassModel(ModelAttribute[] attributes, ConstructionMethod constructionMethod) {
45+
this.attributes = attributes;
46+
this.constructionMethod = constructionMethod;
47+
}
48+
49+
/**
50+
* Returns the attributes of this class model.
51+
* <p>
52+
* The order is guaranteed only if the class represented by this model is a record.
53+
* The attributes are then in returned in the order of record component declaration.
54+
*
55+
* @return attributes of this model
56+
*/
57+
public ModelAttribute[] getAttributes() {
58+
return Arrays.copyOf(attributes, attributes.length);
59+
}
60+
61+
/**
62+
* @return which constructor is used in this model
63+
*/
64+
public ConstructionMethod getConstructionMethod() {
65+
return constructionMethod;
66+
}
67+
68+
/**
69+
* Which construction method is used when creating new instance of the class.
70+
*/
71+
public sealed interface ConstructionMethod {
72+
}
73+
74+
/**
75+
* No args constructor is called during the creation and the fields are
76+
* populated via setters.
77+
* <p>
78+
* This method is used by regular classes.
79+
*/
80+
public record NoArgsConstructor() implements ConstructionMethod {
81+
public static final NoArgsConstructor INSTANCE = new NoArgsConstructor();
82+
}
83+
84+
/**
85+
* The object is created just by the all args constructor.
86+
* <p>
87+
* This method is used by records.
88+
*/
89+
public record AllArgsConstructor() implements ConstructionMethod {
90+
public static final AllArgsConstructor INSTANCE = new AllArgsConstructor();
91+
}
92+
93+
private static ClassModel of0(Class<?> type) {
4194
if (type.isEnum() || type.isInterface() || type.isAnnotation() || Modifier.isAbstract(type.getModifiers()))
4295
throw new UnsupportedOperationException("Can not automatically resolve class model for '"
4396
+ type.getName() + "'");
@@ -61,7 +114,7 @@ public static ClassModel of(Class<?> type) {
61114
Constructor<?> noArgs = noArgsConstructor(type);
62115
Preconditions.checkState(noArgs != null, "Class '" + type.getName() + "' is missing "
63116
+ "no arguments constructor");
64-
return new ClassModel(attributes, ConstructionMethod.NO_ARGS_CONSTRUCTOR);
117+
return new ClassModel(attributes, NoArgsConstructor.INSTANCE);
65118
}
66119

67120
private static ClassModel ofRecord(Class<?> type) {
@@ -70,53 +123,7 @@ private static ClassModel ofRecord(Class<?> type) {
70123
.toArray(ModelAttribute[]::new);
71124
Constructor<?> constructor = allArgsConstructor(type, attributes);
72125
Preconditions.checkNotNull(constructor); // is always present on records
73-
return new ClassModel(attributes, ConstructionMethod.ALL_ARGS_CONSTRUCTOR);
74-
}
75-
76-
protected ClassModel(ModelAttribute[] attributes, ConstructionMethod constructionMethod) {
77-
this.attributes = attributes;
78-
this.constructionMethod = constructionMethod;
79-
}
80-
81-
/**
82-
* Returns the attributes of this class model.
83-
* <p>
84-
* The order is guaranteed only if the class represented by this model is a record.
85-
* The attributes are then in returned in the order of record component declaration.
86-
*
87-
* @return attributes of this model
88-
*/
89-
public ModelAttribute[] getAttributes() {
90-
return Arrays.copyOf(attributes, attributes.length);
91-
}
92-
93-
/**
94-
* @return which constructor is used in this model
95-
*/
96-
public ConstructionMethod getConstructionMethod() {
97-
return constructionMethod;
98-
}
99-
100-
/**
101-
* Which construction method is used when creating new instance of the class.
102-
*/
103-
public enum ConstructionMethod {
104-
105-
/**
106-
* No args constructor is called during the creation and the fields are
107-
* populated via setters.
108-
* <p>
109-
* This method is used by regular classes.
110-
*/
111-
NO_ARGS_CONSTRUCTOR,
112-
113-
/**
114-
* The object is created just by the all args constructor.
115-
* <p>
116-
* This method is used by records.
117-
*/
118-
ALL_ARGS_CONSTRUCTOR
119-
126+
return new ClassModel(attributes, AllArgsConstructor.INSTANCE);
120127
}
121128

122129
private static boolean keepField(Field field) {

foundry-core/src/main/java/org/machinemc/foundry/model/DeconstructedObject.java

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import org.jetbrains.annotations.NotNull;
44
import org.jetbrains.annotations.Unmodifiable;
5+
import org.machinemc.foundry.Codec;
56
import org.machinemc.foundry.DataHandler;
7+
import org.machinemc.foundry.Pipeline;
68

79
import java.lang.reflect.AnnotatedType;
810
import java.util.*;
@@ -13,6 +15,25 @@
1315
*/
1416
public final class DeconstructedObject implements Iterable<DeconstructedObject.Field> {
1517

18+
/**
19+
* Creates a codec that transforms objects of type {@link T} into
20+
* {@link DeconstructedObject} and back.
21+
* <p>
22+
* The returned codec is thread-safe and optimized for repeated use.
23+
*
24+
* @param type type the object to (de)construct
25+
* @param <T> type of the object
26+
* @return a codec that converts an instance of {@link T} into a deconstructed object and back
27+
*/
28+
public static <T> Codec<T, DeconstructedObject> codec(Class<T> type) {
29+
ClassModel classModel = ClassModel.of(type);
30+
ObjectFactory<T> objectFactory = ObjectFactory.create(type, classModel);
31+
return new Codec<>(
32+
Pipeline.builder(createDeconstructor(classModel, objectFactory)).build(),
33+
Pipeline.builder(createConstructor(classModel, objectFactory)).build()
34+
);
35+
}
36+
1637
/**
1738
* Creates a data handler that deconstructs an instance of the specified type into a
1839
* {@link DeconstructedObject}.
@@ -25,13 +46,7 @@ public final class DeconstructedObject implements Iterable<DeconstructedObject.F
2546
*/
2647
public static <T> DataHandler<T, DeconstructedObject> createDeconstructor(Class<T> type) {
2748
ClassModel classModel = ClassModel.of(type);
28-
ObjectFactory<T> objectFactory = ObjectFactory.create(type, classModel);
29-
FieldsExtractor fieldsExtractor = FieldsExtractor.of(classModel);
30-
return obj -> {
31-
ModelDataContainer container = objectFactory.write(obj);
32-
var fields = fieldsExtractor.read(container);
33-
return new DeconstructedObject(fields);
34-
};
49+
return createDeconstructor(classModel, ObjectFactory.create(type, classModel));
3550
}
3651

3752
/**
@@ -49,13 +64,7 @@ public static <T> DataHandler<T, DeconstructedObject> createDeconstructor(Class<
4964
*/
5065
public static <T> DataHandler<DeconstructedObject, T> createConstructor(Class<T> type) {
5166
ClassModel classModel = ClassModel.of(type);
52-
ObjectFactory<T> objectFactory = ObjectFactory.create(type, classModel);
53-
FieldsInjector fieldsInjector = FieldsInjector.of(classModel);
54-
return deconstructed -> {
55-
ModelDataContainer container = objectFactory.newContainer();
56-
fieldsInjector.write(deconstructed.asList(), container);
57-
return objectFactory.read(container);
58-
};
67+
return createConstructor(classModel, ObjectFactory.create(type, classModel));
5968
}
6069

6170
private final @Unmodifiable Map<String, Field> fields;
@@ -213,4 +222,24 @@ public Class<?> type() {
213222
public record ObjectField(String name, Class<?> type, AnnotatedType annotatedType, Object value) implements Field {
214223
}
215224

225+
private static <T> DataHandler<T, DeconstructedObject> createDeconstructor(ClassModel classModel,
226+
ObjectFactory<T> objectFactory) {
227+
FieldsExtractor fieldsExtractor = FieldsExtractor.of(classModel);
228+
return obj -> {
229+
ModelDataContainer container = objectFactory.write(obj);
230+
var fields = fieldsExtractor.read(container);
231+
return new DeconstructedObject(fields);
232+
};
233+
}
234+
235+
private static <T> DataHandler<DeconstructedObject, T> createConstructor(ClassModel classModel,
236+
ObjectFactory<T> objectFactory) {
237+
FieldsInjector fieldsInjector = FieldsInjector.of(classModel);
238+
return deconstructed -> {
239+
ModelDataContainer container = objectFactory.newContainer();
240+
fieldsInjector.write(deconstructed.asList(), container);
241+
return objectFactory.read(container);
242+
};
243+
}
244+
216245
}

foundry-core/src/main/java/org/machinemc/foundry/model/ObjectFactory.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ private static ObjectFactory<?> load(Class<?> type, ClassModel classModel) {
154154
ga.visitMethodInsn(opcode, attributeSourceT.getInternalName(), name,
155155
Type.getMethodDescriptor(Type.getType(returnType), paramsArr), isInterface);
156156
}
157+
default -> throw new IllegalStateException(); // TODO custom getters
157158
}
158159
// write with the correct method
159160
String methodName;
@@ -199,7 +200,7 @@ private static ObjectFactory<?> load(Class<?> type, ClassModel classModel) {
199200

200201
ga.visitCode();
201202

202-
if (classModel.getConstructionMethod() == ClassModel.ConstructionMethod.ALL_ARGS_CONSTRUCTOR) {
203+
if (classModel.getConstructionMethod() instanceof ClassModel.AllArgsConstructor) {
203204
ga.newInstance(sourceT);
204205
ga.dup();
205206
}
@@ -240,7 +241,7 @@ private static ObjectFactory<?> load(Class<?> type, ClassModel classModel) {
240241
}
241242

242243
// call all args constructor
243-
if (classModel.getConstructionMethod() == ClassModel.ConstructionMethod.ALL_ARGS_CONSTRUCTOR) {
244+
if (classModel.getConstructionMethod() instanceof ClassModel.AllArgsConstructor) {
244245
Type[] allArgsParams = Arrays.stream(classModel.getAttributes())
245246
.map(ModelAttribute::type)
246247
.map(Type::getType)
@@ -285,6 +286,7 @@ private static ObjectFactory<?> load(Class<?> type, ClassModel classModel) {
285286
}
286287
}
287288
case null -> throw new IllegalStateException("Expected setter"); // should not happen
289+
default -> throw new IllegalStateException(); // TODO custom setters
288290
}
289291
}
290292
ga.loadLocal(instanceVar); // load the finished instance to return

0 commit comments

Comments
 (0)