Skip to content

Commit 6d09bc1

Browse files
committed
added API that maps the extracted data from an object to its fields
1 parent d6dcb6c commit 6d09bc1

7 files changed

Lines changed: 597 additions & 8 deletions

File tree

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package org.machinemc.foundry.model;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
import org.jetbrains.annotations.Unmodifiable;
5+
6+
import java.lang.reflect.AnnotatedType;
7+
import java.util.Collections;
8+
import java.util.Iterator;
9+
import java.util.List;
10+
import java.util.function.Function;
11+
12+
/**
13+
* Represents an object that has been deconstructed.
14+
* It effectively flattens an object into a sequence of {@link Field}s.
15+
*/
16+
public final class DeconstructedObject implements Iterable<DeconstructedObject.Field> {
17+
18+
/**
19+
* Creates a function that deconstructs an instance of the specified type into a
20+
* {@link DeconstructedObject}.
21+
* <p>
22+
* The returned function is thread-safe and optimized for repeated use.
23+
*
24+
* @param type type the object to deconstruct
25+
* @param <T> type of the object
26+
* @return a function that converts an instance of {@link T} into a deconstructed object
27+
*/
28+
public static <T> Function<T, DeconstructedObject> createDeconstructor(Class<T> type) {
29+
ClassModel classModel = ClassModel.of(type);
30+
ObjectFactory<T> objectFactory = ObjectFactory.create(type, classModel);
31+
FieldsExtractor fieldsExtractor = FieldsExtractor.of(classModel);
32+
return obj -> {
33+
ModelDataContainer container = objectFactory.write(obj);
34+
var fields = fieldsExtractor.read(container);
35+
return new DeconstructedObject(fields);
36+
};
37+
}
38+
39+
/**
40+
* Creates a function that reconstructs an instance of the specified type from a
41+
* {@link DeconstructedObject}.
42+
* <p>
43+
* The returned function expects the input {@link DeconstructedObject} to contain fields
44+
* compatible with the target class schema.
45+
* <p>
46+
* The returned function is thread-safe and optimized for repeated use.
47+
*
48+
* @param type type the object to reconstruct
49+
* @param <T> type of the object
50+
* @return a function that converts deconstructed object into an instance of {@link T}
51+
*/
52+
public static <T> Function<DeconstructedObject, T> createConstructor(Class<T> type) {
53+
ClassModel classModel = ClassModel.of(type);
54+
ObjectFactory<T> objectFactory = ObjectFactory.create(type, classModel);
55+
FieldsInjector fieldsInjector = FieldsInjector.of(classModel);
56+
return deconstructed -> {
57+
ModelDataContainer container = objectFactory.newContainer();
58+
fieldsInjector.write(deconstructed.fields, container);
59+
return objectFactory.read(container);
60+
};
61+
}
62+
63+
private final List<Field> fields;
64+
65+
DeconstructedObject(List<Field> fields) {
66+
this.fields = fields;
67+
}
68+
69+
/**
70+
* Returns an unmodifiable list of the fields in this deconstructed object.
71+
*
72+
* @return the list of fields
73+
*/
74+
public @Unmodifiable List<Field> asList() {
75+
return Collections.unmodifiableList(fields);
76+
}
77+
78+
/**
79+
* @return number of fields in this object
80+
*/
81+
public int size() {
82+
return fields.size();
83+
}
84+
85+
@Override
86+
public @NotNull Iterator<Field> iterator() {
87+
return fields.iterator();
88+
}
89+
90+
/**
91+
* Represents a field of a deconstructed object.
92+
*/
93+
public sealed interface Field {
94+
95+
/**
96+
* @return name of the field
97+
*/
98+
String name();
99+
100+
/**
101+
* @return type of the field
102+
*/
103+
Class<?> type();
104+
105+
/**
106+
* @return annotated type of the field
107+
*/
108+
AnnotatedType annotatedType();
109+
110+
}
111+
112+
public record BoolField(String name, AnnotatedType annotatedType, boolean value) implements Field {
113+
@Override
114+
public Class<?> type() {
115+
return boolean.class;
116+
}
117+
}
118+
119+
public record CharField(String name, AnnotatedType annotatedType, char value) implements Field {
120+
@Override
121+
public Class<?> type() {
122+
return char.class;
123+
}
124+
}
125+
126+
public record ByteField(String name, AnnotatedType annotatedType, byte value) implements Field {
127+
@Override
128+
public Class<?> type() {
129+
return byte.class;
130+
}
131+
}
132+
133+
public record ShortField(String name, AnnotatedType annotatedType, short value) implements Field {
134+
@Override
135+
public Class<?> type() {
136+
return short.class;
137+
}
138+
}
139+
140+
public record IntField(String name, AnnotatedType annotatedType, int value) implements Field {
141+
@Override
142+
public Class<?> type() {
143+
return int.class;
144+
}
145+
}
146+
147+
public record LongField(String name, AnnotatedType annotatedType, long value) implements Field {
148+
@Override
149+
public Class<?> type() {
150+
return long.class;
151+
}
152+
}
153+
154+
public record FloatField(String name, AnnotatedType annotatedType, float value) implements Field {
155+
@Override
156+
public Class<?> type() {
157+
return float.class;
158+
}
159+
}
160+
161+
public record DoubleField(String name, AnnotatedType annotatedType, double value) implements Field {
162+
@Override
163+
public Class<?> type() {
164+
return double.class;
165+
}
166+
}
167+
168+
public record ObjectField(String name, Class<?> type, AnnotatedType annotatedType, Object value) implements Field {
169+
}
170+
171+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package org.machinemc.foundry.model;
2+
3+
import org.jetbrains.annotations.ApiStatus;
4+
5+
import java.lang.reflect.AnnotatedType;
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import java.util.function.Function;
9+
10+
/**
11+
* Optimized reader of {@link ModelDataContainer} to map them to deconstructed objects.
12+
* <p>
13+
* This class is for internal use only.
14+
*
15+
* @param fieldReaders field readers
16+
*/
17+
@ApiStatus.Internal
18+
record FieldsExtractor(Function<ModelDataContainer, DeconstructedObject.Field>[] fieldReaders) {
19+
20+
/**
21+
* Creates new fields extractor instance for given class model.
22+
*
23+
* @param model model
24+
* @return fields extractor for given model
25+
*/
26+
static FieldsExtractor of(ClassModel model) {
27+
ModelAttribute[] attributes = model.getAttributes();
28+
//noinspection unchecked
29+
Function<ModelDataContainer, DeconstructedObject.Field>[] fieldReaders = new Function[attributes.length];
30+
for (int i = 0; i < attributes.length; i++) {
31+
ModelAttribute attribute = attributes[i];
32+
String name = attribute.name();
33+
AnnotatedType annotatedType = attribute.annotatedType();
34+
Function<ModelDataContainer, DeconstructedObject.Field> reader;
35+
if (attribute.type() == boolean.class) {
36+
reader = dataContainer ->
37+
new DeconstructedObject.BoolField(name, annotatedType, dataContainer.readBool());
38+
} else if (attribute.type() == char.class) {
39+
reader = dataContainer ->
40+
new DeconstructedObject.CharField(name, annotatedType, dataContainer.readChar());
41+
} else if (attribute.type() == byte.class) {
42+
reader = dataContainer ->
43+
new DeconstructedObject.ByteField(name, annotatedType, dataContainer.readByte());
44+
} else if (attribute.type() == short.class) {
45+
reader = dataContainer ->
46+
new DeconstructedObject.ShortField(name, annotatedType, dataContainer.readShort());
47+
} else if (attribute.type() == int.class) {
48+
reader = dataContainer ->
49+
new DeconstructedObject.IntField(name, annotatedType, dataContainer.readInt());
50+
} else if (attribute.type() == long.class) {
51+
reader = dataContainer ->
52+
new DeconstructedObject.LongField(name, annotatedType, dataContainer.readLong());
53+
} else if (attribute.type() == float.class) {
54+
reader = dataContainer ->
55+
new DeconstructedObject.FloatField(name, annotatedType, dataContainer.readFloat());
56+
} else if (attribute.type() == double.class) {
57+
reader = dataContainer ->
58+
new DeconstructedObject.DoubleField(name, annotatedType, dataContainer.readDouble());
59+
} else {
60+
reader = dataContainer ->
61+
new DeconstructedObject.ObjectField(name, attribute.type(), annotatedType, dataContainer.readObject());
62+
}
63+
fieldReaders[i] = reader;
64+
}
65+
return new FieldsExtractor(fieldReaders);
66+
}
67+
68+
/**
69+
* Reads fields from a container.
70+
*
71+
* @param dataContainer container to read
72+
* @return list of fields
73+
*/
74+
List<DeconstructedObject.Field> read(ModelDataContainer dataContainer) {
75+
List<DeconstructedObject.Field> fields = new ArrayList<>(fieldReaders.length);
76+
for (var reader : fieldReaders) {
77+
fields.add(reader.apply(dataContainer));
78+
}
79+
return fields;
80+
}
81+
82+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package org.machinemc.foundry.model;
2+
3+
import org.jetbrains.annotations.ApiStatus;
4+
5+
import java.util.List;
6+
import java.util.function.BiConsumer;
7+
8+
/**
9+
* Optimized writer of deconstructed object fields into a {@link ModelDataContainer}.
10+
* <p>
11+
* This class is for internal use only.
12+
*
13+
* @param fieldWriters field writers
14+
*/
15+
@ApiStatus.Internal
16+
record FieldsInjector(BiConsumer<DeconstructedObject.Field, ModelDataContainer>[] fieldWriters) {
17+
18+
/**
19+
* Creates new fields injector instance for given class model.
20+
*
21+
* @param model model
22+
* @return fields injector for given model
23+
*/
24+
static FieldsInjector of(ClassModel model) {
25+
ModelAttribute[] attributes = model.getAttributes();
26+
//noinspection unchecked
27+
BiConsumer<DeconstructedObject.Field, ModelDataContainer>[] fieldWriters = new BiConsumer[attributes.length];
28+
for (int i = 0; i < attributes.length; i++) {
29+
ModelAttribute attribute = attributes[i];
30+
BiConsumer<DeconstructedObject.Field, ModelDataContainer> writer;
31+
if (attribute.type() == boolean.class) {
32+
writer = (field, dataContainer) ->
33+
dataContainer.writeBool(((DeconstructedObject.BoolField) field).value());
34+
} else if (attribute.type() == char.class) {
35+
writer = (field, dataContainer) ->
36+
dataContainer.writeChar(((DeconstructedObject.CharField) field).value());
37+
} else if (attribute.type() == byte.class) {
38+
writer = (field, dataContainer) ->
39+
dataContainer.writeByte(((DeconstructedObject.ByteField) field).value());
40+
} else if (attribute.type() == short.class) {
41+
writer = (field, dataContainer) ->
42+
dataContainer.writeShort(((DeconstructedObject.ShortField) field).value());
43+
} else if (attribute.type() == int.class) {
44+
writer = (field, dataContainer) ->
45+
dataContainer.writeInt(((DeconstructedObject.IntField) field).value());
46+
} else if (attribute.type() == long.class) {
47+
writer = (field, dataContainer) ->
48+
dataContainer.writeLong(((DeconstructedObject.LongField) field).value());
49+
} else if (attribute.type() == float.class) {
50+
writer = (field, dataContainer) ->
51+
dataContainer.writeFloat(((DeconstructedObject.FloatField) field).value());
52+
} else if (attribute.type() == double.class) {
53+
writer = (field, dataContainer) ->
54+
dataContainer.writeDouble(((DeconstructedObject.DoubleField) field).value());
55+
} else {
56+
writer = (field, dataContainer) ->
57+
dataContainer.writeObject(((DeconstructedObject.ObjectField) field).value());
58+
}
59+
fieldWriters[i] = writer;
60+
}
61+
return new FieldsInjector(fieldWriters);
62+
}
63+
64+
/**
65+
* Writes provided fields into a container.
66+
*
67+
* @param fields fields to write
68+
* @param dataContainer data container
69+
*/
70+
void write(List<DeconstructedObject.Field> fields, ModelDataContainer dataContainer) {
71+
for (int i = 0; i < fieldWriters.length; i++) {
72+
fieldWriters[i].accept(fields.get(i), dataContainer);
73+
}
74+
}
75+
76+
}

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package org.machinemc.foundry.model;
22

3-
import org.jetbrains.annotations.Nullable;
4-
53
import java.lang.reflect.AnnotatedType;
64

75
/**
@@ -13,8 +11,8 @@
1311
* @param annotatedType annotated type of this attribute if present
1412
* @param access access for this attribute
1513
*/
16-
public record ModelAttribute(Class<?> source, String name, Class<?> type,
17-
@Nullable AnnotatedType annotatedType, AttributeAccess access) {
14+
public record ModelAttribute(Class<?> source, String name, Class<?> type, AnnotatedType annotatedType,
15+
AttributeAccess access) {
1816

1917
/**
2018
* @return whether this attribute is primitive

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,32 @@ public void writeObject(Object value) {
172172
objects[objectsWrite++] = value;
173173
}
174174

175+
/**
176+
* Resets all the reader indices.
177+
*/
178+
public void resetReader() {
179+
booleansRead = 0;
180+
charsRead = 0;
181+
bytesRead = 0;
182+
shortsRead = 0;
183+
intsRead = 0;
184+
longsRead = 0;
185+
floatsRead = 0;
186+
doublesRead = 0;
187+
}
188+
189+
/**
190+
* Resets all the writer indices.
191+
*/
192+
public void resetWriter() {
193+
booleansWrite = 0;
194+
charsWrite = 0;
195+
bytesWrite = 0;
196+
shortsWrite = 0;
197+
intsWrite = 0;
198+
longsWrite = 0;
199+
floatsWrite = 0;
200+
doublesWrite = 0;
201+
}
202+
175203
}

0 commit comments

Comments
 (0)