LuaSerializer.java 7.24 KB
Newer Older
1
2
package emu.grasscutter.scripts.serializer;

Akka's avatar
Akka committed
3
import com.esotericsoftware.reflectasm.ConstructorAccess;
Akka's avatar
Akka committed
4
import com.esotericsoftware.reflectasm.MethodAccess;
5
6
import emu.grasscutter.Grasscutter;
import emu.grasscutter.scripts.ScriptUtils;
Akka's avatar
Akka committed
7
8
9
10
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.FieldDefaults;
11
12
13
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;

Akka's avatar
Akka committed
14
15
16
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

17
public class LuaSerializer implements Serializer {
Akka's avatar
Akka committed
18
19
20
21
22

	private final static Map<Class<?>, MethodAccess> methodAccessCache = new ConcurrentHashMap<>();
	private final static Map<Class<?>, ConstructorAccess<?>> constructorCache = new ConcurrentHashMap<>();
	private final static Map<Class<?>, Map<String, FieldMeta>> fieldMetaCache = new ConcurrentHashMap<>();

23
24
25
26
27
28
29
30
31
	@Override
	public <T> List<T> toList(Class<T> type, Object obj) {
		return serializeList(type, (LuaTable) obj);
	}

	@Override
	public <T> T toObject(Class<T> type, Object obj) {
		return serialize(type, (LuaTable) obj);
	}
akatatsu27's avatar
akatatsu27 committed
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

    @Override
    public <T> Map<String, T> toMap(Class<T> type, Object obj) {
        return serializeMap(type, (LuaTable) obj);
    }

    private <T> Map<String,T> serializeMap(Class<T> type, LuaTable table) {
        Map<String,T> map = new HashMap<>();

        if (table == null) {
            return map;
        }

        try {
            LuaValue[] keys = table.keys();
            for (LuaValue k : keys) {
                try {
                    LuaValue keyValue = table.get(k);

                    T object = null;

                    if (keyValue.istable()) {
                        object = serialize(type, keyValue.checktable());
                    } else if (keyValue.isint()) {
                        object = (T) (Integer) keyValue.toint();
                    } else if (keyValue.isnumber()) {
                        object = (T) (Float) keyValue.tofloat(); // terrible...
                    } else if (keyValue.isstring()) {
                        object = (T) keyValue.tojstring();
                    } else if (keyValue.isboolean()) {
                        object = (T) (Boolean) keyValue.toboolean();
                    } else {
                        object = (T) keyValue;
                    }

                    if (object != null) {
                        map.put(String.valueOf(k),object);
                    }
                } catch (Exception ex) {

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return map;
    }

    public <T> List<T> serializeList(Class<T> type, LuaTable table) {
Akka's avatar
Akka committed
82
		List<T> list = new ArrayList<>();
akatatsu27's avatar
akatatsu27 committed
83

84
85
86
		if (table == null) {
			return list;
		}
akatatsu27's avatar
akatatsu27 committed
87

88
89
90
91
92
		try {
			LuaValue[] keys = table.keys();
			for (LuaValue k : keys) {
				try {
					LuaValue keyValue = table.get(k);
akatatsu27's avatar
akatatsu27 committed
93

94
					T object = null;
akatatsu27's avatar
akatatsu27 committed
95

96
97
98
99
100
101
102
103
					if (keyValue.istable()) {
						object = serialize(type, keyValue.checktable());
				    } else if (keyValue.isint()) {
				    	object = (T) (Integer) keyValue.toint();
				    } else if (keyValue.isnumber()) {
				    	object = (T) (Float) keyValue.tofloat(); // terrible...
				    } else if (keyValue.isstring()) {
				    	object = (T) keyValue.tojstring();
Akka's avatar
Akka committed
104
105
106
                    } else if (keyValue.isboolean()) {
                        object = (T) (Boolean) keyValue.toboolean();
                    } else {
107
108
				    	object = (T) keyValue;
				    }
Akka's avatar
Akka committed
109

110
111
112
113
114
115
116
117
118
119
120
121
122
					if (object != null) {
						list.add(object);
					}
				} catch (Exception ex) {

				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return list;
	}
Akka's avatar
Akka committed
123

124
125
	public <T> T serialize(Class<T> type, LuaTable table) {
		T object = null;
akatatsu27's avatar
akatatsu27 committed
126

127
128
129
130
131
132
133
134
135
		if (type == List.class) {
			try {
				Class<T> listType = (Class<T>) type.getTypeParameters()[0].getClass();
				return (T) serializeList(listType, table);
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}
akatatsu27's avatar
akatatsu27 committed
136

137
		try {
138
			if (!methodAccessCache.containsKey(type)) {
Akka's avatar
Akka committed
139
140
141
142
143
144
145
				cacheType(type);
			}
			var methodAccess = methodAccessCache.get(type);
			var fieldMetaMap = fieldMetaCache.get(type);

			object = (T) constructorCache.get(type).newInstance();

146
			if (table == null) {
147
148
				return object;
			}
akatatsu27's avatar
akatatsu27 committed
149

150
151
152
			LuaValue[] keys = table.keys();
			for (LuaValue k : keys) {
				try {
Akka's avatar
Akka committed
153
154
					var keyName = k.checkjstring();
					if(!fieldMetaMap.containsKey(keyName)){
Akka's avatar
Akka committed
155
156
						continue;
					}
Akka's avatar
Akka committed
157
					var fieldMeta = fieldMetaMap.get(keyName);
158
159
160
					LuaValue keyValue = table.get(k);

					if (keyValue.istable()) {
Akka's avatar
Akka committed
161
162
163
164
165
166
167
						methodAccess.invoke(object, fieldMeta.index, serialize(fieldMeta.getType(), keyValue.checktable()));
				    } else if (fieldMeta.getType().equals(float.class)) {
						methodAccess.invoke(object, fieldMeta.index, keyValue.tofloat());
				    } else if (fieldMeta.getType().equals(int.class)) {
						methodAccess.invoke(object, fieldMeta.index, keyValue.toint());
				    } else if (fieldMeta.getType().equals(String.class)) {
						methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring());
Akka's avatar
Akka committed
168
169
170
                    } else if (fieldMeta.getType().equals(boolean.class)) {
                        methodAccess.invoke(object, fieldMeta.index, keyValue.toboolean());
                    } else {
Akka's avatar
Akka committed
171
						methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring());
172
173
174
175
176
177
178
				    }
				} catch (Exception ex) {
					//ex.printStackTrace();
					continue;
				}
			}
		} catch (Exception e) {
179
			Grasscutter.getLogger().info(ScriptUtils.toMap(table).toString());
180
181
			e.printStackTrace();
		}
akatatsu27's avatar
akatatsu27 committed
182

183
184
		return object;
	}
Akka's avatar
Akka committed
185

Akka's avatar
Akka committed
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
	public <T> Map<String, FieldMeta> cacheType(Class<T> type){
		if(fieldMetaCache.containsKey(type)) {
			return fieldMetaCache.get(type);
		}
		if(!constructorCache.containsKey(type)){
			constructorCache.putIfAbsent(type, ConstructorAccess.get(type));
		}
		var methodAccess = Optional.ofNullable(methodAccessCache.get(type)).orElse(MethodAccess.get(type));
		methodAccessCache.putIfAbsent(type, methodAccess);

		var fieldMetaMap = new HashMap<String, FieldMeta>();
		var methodNameSet = new HashSet<>(Arrays.stream(methodAccess.getMethodNames()).toList());

		Arrays.stream(type.getDeclaredFields())
				.filter(field -> methodNameSet.contains(getSetterName(field.getName())))
				.forEach(field -> {
					var setter = getSetterName(field.getName());
					var index = methodAccess.getIndex(setter);
					fieldMetaMap.put(field.getName(), new FieldMeta(field.getName(), setter, index, field.getType()));
				});

		Arrays.stream(type.getFields())
				.filter(field -> !fieldMetaMap.containsKey(field.getName()))
				.filter(field -> methodNameSet.contains(getSetterName(field.getName())))
				.forEach(field -> {
					var setter = getSetterName(field.getName());
					var index = methodAccess.getIndex(setter);
					fieldMetaMap.put(field.getName(), new FieldMeta(field.getName(), setter, index, field.getType()));
				});

		fieldMetaCache.put(type, fieldMetaMap);
		return fieldMetaMap;
	}

	public String getSetterName(String fieldName){
		if(fieldName == null || fieldName.length() == 0){
			return null;
Akka's avatar
Akka committed
223
		}
Akka's avatar
Akka committed
224
225
226
227
228
229
230
231
232
233
234
235
236
237
		if(fieldName.length() == 1){
			return "set" + fieldName.toUpperCase();
		}
		return "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
	}

	@Data
	@AllArgsConstructor
	@FieldDefaults(level = AccessLevel.PRIVATE)
	static class FieldMeta{
		String name;
		String setter;
		int index;
		Class<?> type;
Akka's avatar
Akka committed
238
	}
239
}