Skip to content

Commit 88e1410

Browse files
committed
feat: Enable annotation detection for inherited structures.
1 parent 7ce1f9a commit 88e1410

4 files changed

Lines changed: 240 additions & 13 deletions

File tree

src/main/java/net/deanly/structlayout/codec/decode/StructDecoder.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,20 @@ public static <T> StructDecodeResult<T> decode(Class<T> type, byte[] data, int s
1515
throw new InvalidDataOffsetException(startOffset, data.length);
1616
}
1717

18-
// 1. 인스턴스 생성
18+
// 1. 디코딩할 객체의 인스턴스 생성
1919
T instance = ClassFactory.createNoArgumentsInstance(type);
2020

21-
// 2. 필드 정렬
22-
Field[] fields = type.getDeclaredFields();
23-
List<Field> orderedFields = FieldHelper.getOrderedFields(fields);
21+
// 2. 상속 계층의 모든 필드 수집 및 정렬
22+
List<Field> allFields = FieldHelper.getAllDeclaredFieldsIncludingSuperclasses(type);
23+
List<Field> orderedFields = FieldHelper.getOrderedFields(allFields);
2424

25-
// 3. 디코딩 처리
25+
// 3. 디코딩: 필드 순서대로 Byte 데이터를 객체 필드에 매핑
2626
int offset = startOffset;
2727
for (Field field : orderedFields) {
2828
offset += FieldProcessor.processField(instance, field, data, offset);
2929
}
3030

31+
// 4. 디코딩 결과 반환
3132
return StructDecodeResult.of(instance, offset - startOffset);
3233
}
3334
}

src/main/java/net/deanly/structlayout/codec/encode/StructEncoder.java

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,38 +15,65 @@ public static <T> byte[] encode(T instance) {
1515
return new byte[0]; // Null 객체는 빈 배열 반환
1616
}
1717

18-
// 1. 필드 정렬
19-
Field[] fields = instance.getClass().getDeclaredFields();
20-
List<Field> orderedFields = FieldHelper.getOrderedFields(fields);
18+
// 1. 상속 계층 필드 가져오기
19+
List<Field> allFields = getAllDeclaredFields(instance.getClass());
2120

22-
// 2. 필드 별 byte[] 처리
21+
// 2. 필드 정렬
22+
List<Field> orderedFields = FieldHelper.getOrderedFields(allFields);
23+
24+
// 3. 필드 처리 및 병합
2325
List<byte[]> fieldChunks = new ArrayList<>();
2426
for (Field field : orderedFields) {
2527
field.setAccessible(true);
2628
byte[] chunk = FieldProcessor.processField(instance, field);
2729
fieldChunks.add(chunk);
2830
}
2931

30-
// 3. 생성된 byte[] 병합 및 반환
32+
// 4. 병합된 결과 반환
3133
return ByteArrayHelper.mergeChunks(fieldChunks);
3234
}
3335

36+
/**
37+
* 상속 계층의 모든 필드를 가져오는 메서드
38+
*/
39+
private static List<Field> getAllDeclaredFields(Class<?> clazz) {
40+
List<Field> fields = new ArrayList<>();
41+
while (clazz != null && clazz != Object.class) {
42+
Field[] declaredFields = clazz.getDeclaredFields();
43+
for (Field field : declaredFields) {
44+
if (FieldHelper.isStructField(field)) { // `@Struct*` 관련 필드만 필터링
45+
fields.add(field);
46+
}
47+
}
48+
clazz = clazz.getSuperclass(); // 부모 클래스로 이동
49+
}
50+
return fields;
51+
}
52+
3453

3554
public static <T> void encodeWithDebug(T instance) {
3655
if (instance == null) {
3756
return;
3857
}
3958

40-
Field[] fields = instance.getClass().getDeclaredFields();
41-
List<Field> orderedFields = FieldHelper.getOrderedFields(fields);
59+
// 1. 상속 계층 필드 가져오기
60+
List<Field> allFields = getAllDeclaredFields(instance.getClass());
4261

43-
List<FieldDebugInfo> debugInfos = new ArrayList<>();
62+
// 2. 필드 정렬
63+
List<Field> orderedFields = FieldHelper.getOrderedFields(allFields);
4464

65+
// 3. Debug 정보를 생성
66+
List<FieldDebugInfo> debugInfos = new ArrayList<>();
4567
for (Field field : orderedFields) {
4668
field.setAccessible(true);
4769
debugInfos.addAll(FieldProcessor.processFieldRecursivelyWithDebug(instance, field, null));
4870
}
4971

72+
// 4. Debug 출력
73+
printDebugInfo(debugInfos);
74+
}
75+
76+
private static void printDebugInfo(List<FieldDebugInfo> debugInfos) {
5077
int offset = 0;
5178
int totalBytes = 0;
5279

src/main/java/net/deanly/structlayout/codec/helpers/FieldHelper.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,33 @@ public static List<Field> getOrderedFields(Field[] fields) {
4242
return orderedFields;
4343
}
4444

45+
/**
46+
* Returns a list of fields from the given array that are annotated with specific annotations
47+
* and orders them according to the order value defined in their annotations.
48+
*
49+
* The supported annotations are:
50+
* - {@code StructSequenceField}
51+
* - {@code StructObjectField}
52+
* - {@code StructField}
53+
* - {@code StructSequenceObjectField}
54+
*
55+
* @param fields the list of fields to be filtered and ordered
56+
* @return a list of fields annotated with the supported annotations, ordered by their `order` value
57+
*/
58+
public static List<Field> getOrderedFields(List<Field> fields) {
59+
List<Field> orderedFields = new ArrayList<>();
60+
for (Field field : fields) {
61+
if (field.isAnnotationPresent(StructSequenceField.class) ||
62+
field.isAnnotationPresent(StructObjectField.class) ||
63+
field.isAnnotationPresent(StructField.class) ||
64+
field.isAnnotationPresent(StructSequenceObjectField.class)) {
65+
orderedFields.add(field);
66+
}
67+
}
68+
orderedFields.sort(Comparator.comparingInt(FieldHelper::getOrderValue));
69+
return orderedFields;
70+
}
71+
4572
/**
4673
* Retrieves the order value of the given field based on its annotation.
4774
* The method checks for specific annotations in the following order:
@@ -69,6 +96,47 @@ public static int getOrderValue(Field field) {
6996
throw new FieldOrderException(field.getName());
7097
}
7198

99+
/**
100+
* Checks if the given {@code Field} is annotated with any of the supported
101+
* struct-related annotations: {@code StructField}, {@code StructSequenceField},
102+
* {@code StructObjectField}, or {@code StructSequenceObjectField}.
103+
*
104+
* @param field the {@code Field} to be checked for struct-related annotations
105+
* @return {@code true} if the field is annotated with one of the supported
106+
* struct-related annotations; {@code false} otherwise
107+
*/
108+
public static boolean isStructField(Field field) {
109+
return field.isAnnotationPresent(StructField.class)
110+
|| field.isAnnotationPresent(StructSequenceField.class)
111+
|| field.isAnnotationPresent(StructObjectField.class)
112+
|| field.isAnnotationPresent(StructSequenceObjectField.class);
113+
}
114+
115+
/**
116+
* Retrieves all declared fields from the specified class and its superclasses,
117+
* filtering only those annotated with struct-related annotations.
118+
*
119+
* The method iterates through the class hierarchy, starting from the given
120+
* class and moving up to its superclasses, excluding {@code Object.class}.
121+
* For each class, it collects declared fields that satisfy the given filter criteria.
122+
*
123+
* @param clazz the class to inspect for declared fields, including fields
124+
* from its superclasses
125+
* @return a list of fields that are declared in the class and its superclasses,
126+
* and are annotated with struct-related annotations
127+
*/
128+
public static List<Field> getAllDeclaredFieldsIncludingSuperclasses(Class<?> clazz) {
129+
List<Field> fields = new ArrayList<>();
130+
while (clazz != null && clazz != Object.class) {
131+
for (Field field : clazz.getDeclaredFields()) {
132+
if (isStructField(field)) { // `@Struct*` 어노테이션 필터링
133+
fields.add(field);
134+
}
135+
}
136+
clazz = clazz.getSuperclass(); // 부모 클래스로 이동
137+
}
138+
return fields;
139+
}
72140

73141
public static boolean isFieldTypeApplicable(Class<?> structFieldType, Class<? extends net.deanly.structlayout.Field<?>> fieldType) {
74142
// Field의 필드 타입 가져오기
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package net.deanly.structlayout.codec;
2+
3+
import net.deanly.structlayout.annotation.StructField;
4+
import net.deanly.structlayout.annotation.StructObjectField;
5+
import net.deanly.structlayout.annotation.StructSequenceField;
6+
import net.deanly.structlayout.codec.decode.StructDecoder;
7+
import net.deanly.structlayout.codec.encode.StructEncoder;
8+
import net.deanly.structlayout.type.basic.*;
9+
import org.junit.jupiter.api.Test;
10+
11+
import static org.junit.jupiter.api.Assertions.assertEquals;
12+
13+
public class StructInheritanceTest {
14+
15+
// 부모 클래스 정의
16+
public static class ParentStruct {
17+
@StructField(order = 0, type = Int32LEField.class)
18+
private int parentField1;
19+
20+
@StructField(order = 1, type = Int16LEField.class)
21+
private short parentField2;
22+
23+
public ParentStruct() {
24+
}
25+
26+
public ParentStruct(int parentField1, short parentField2) {
27+
this.parentField1 = parentField1;
28+
this.parentField2 = parentField2;
29+
}
30+
31+
public int getParentField1() {
32+
return parentField1;
33+
}
34+
35+
public short getParentField2() {
36+
return parentField2;
37+
}
38+
}
39+
40+
// 자식 클래스 정의
41+
public static class ChildStruct extends ParentStruct {
42+
@StructField(order = 2, type = ByteField.class)
43+
private byte childField1;
44+
45+
@StructSequenceField(order = 3, lengthType = UInt8Field.class, elementType = Int32BEField.class)
46+
private int[] childField2;
47+
48+
@StructObjectField(order = 4)
49+
private NestedObject nestedObject;
50+
51+
public ChildStruct() {
52+
}
53+
54+
public ChildStruct(int parentField1, short parentField2, byte childField1, int[] childField2, NestedObject nestedObject) {
55+
super(parentField1, parentField2);
56+
this.childField1 = childField1;
57+
this.childField2 = childField2;
58+
this.nestedObject = nestedObject;
59+
}
60+
61+
public byte getChildField1() {
62+
return childField1;
63+
}
64+
65+
public int[] getChildField2() {
66+
return childField2;
67+
}
68+
69+
public NestedObject getNestedObject() {
70+
return nestedObject;
71+
}
72+
}
73+
74+
// 중첩된 객체 정의
75+
public static class NestedObject {
76+
@StructField(order = 0, type = Int32BEField.class)
77+
private int nestedField1;
78+
79+
@StructField(order = 1, type = Int16BEField.class)
80+
private short nestedField2;
81+
82+
public NestedObject() {
83+
}
84+
85+
public NestedObject(int nestedField1, short nestedField2) {
86+
this.nestedField1 = nestedField1;
87+
this.nestedField2 = nestedField2;
88+
}
89+
90+
public int getNestedField1() {
91+
return nestedField1;
92+
}
93+
94+
public short getNestedField2() {
95+
return nestedField2;
96+
}
97+
}
98+
99+
@Test
100+
public void testEncodeDecodeInheritanceStruct() {
101+
// 1. 테스트 데이터를 설정
102+
int parentField1 = 42;
103+
short parentField2 = 320;
104+
byte childField1 = 7;
105+
int[] childField2 = new int[]{10, 20, 30};
106+
NestedObject nestedObject = new NestedObject(130, (short) 25);
107+
108+
ChildStruct original = new ChildStruct(parentField1, parentField2, childField1, childField2, nestedObject);
109+
110+
// 2. Encode 처리
111+
byte[] encodedData = StructEncoder.encode(original);
112+
113+
// 3. Decode 처리
114+
ChildStruct decoded = StructDecoder.decode(ChildStruct.class, encodedData, 0).getValue();
115+
116+
// 4. 부모 필드 검증
117+
assertEquals(parentField1, decoded.getParentField1(), "ParentField1 did not match");
118+
assertEquals(parentField2, decoded.getParentField2(), "ParentField2 did not match");
119+
120+
// 5. 자식 필드 검증
121+
assertEquals(childField1, decoded.getChildField1(), "ChildField1 did not match");
122+
123+
for (int i = 0; i < childField2.length; i++) {
124+
assertEquals(childField2[i], decoded.getChildField2()[i], "ChildField2[" + i + "] did not match");
125+
}
126+
127+
// 6. 중첩 객체 검증
128+
assertEquals(nestedObject.getNestedField1(), decoded.getNestedObject().getNestedField1(), "NestedField1 did not match");
129+
assertEquals(nestedObject.getNestedField2(), decoded.getNestedObject().getNestedField2(), "NestedField2 did not match");
130+
}
131+
}

0 commit comments

Comments
 (0)