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

Akka's avatar
Akka committed
3
4
 import com.esotericsoftware.reflectasm.ConstructorAccess;
import com.esotericsoftware.reflectasm.MethodAccess;
5
6
7

import emu.grasscutter.Grasscutter;
import emu.grasscutter.scripts.ScriptUtils;
Akka's avatar
Akka committed
8
9
10
11
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.FieldDefaults;
12
13
14
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;

15
16
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
Akka's avatar
Akka committed
17
18
19
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

20
public class LuaSerializer implements Serializer {
Akka's avatar
Akka committed
21
22
23
24
25

	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<>();

26
27
28
29
30
31
32
33
34
35
36
	@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);
	}
	
	public <T> List<T> serializeList(Class<T> type, LuaTable table) {
Akka's avatar
Akka committed
37
		List<T> list = new ArrayList<>();
38
		
39
40
41
42
		if (table == null) {
			return list;
		}
		
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
		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 {
				    	object = (T) keyValue;
				    }
					
					if (object != null) {
						list.add(object);
					}
				} catch (Exception ex) {

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

		return list;
	}
Akka's avatar
Akka committed
76

77
78
79
80
81
82
83
84
85
86
87
88
89
90
	public <T> T serialize(Class<T> type, LuaTable table) {
		T object = null;
		
		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;
			}
		}
		
		try {
91
			if (!methodAccessCache.containsKey(type)) {
Akka's avatar
Akka committed
92
93
94
95
96
97
98
				cacheType(type);
			}
			var methodAccess = methodAccessCache.get(type);
			var fieldMetaMap = fieldMetaCache.get(type);

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

99
			if (table == null) {
100
101
				return object;
			}
102
			
103
104
105
			LuaValue[] keys = table.keys();
			for (LuaValue k : keys) {
				try {
Akka's avatar
Akka committed
106
107
					var keyName = k.checkjstring();
					if(!fieldMetaMap.containsKey(keyName)){
Akka's avatar
Akka committed
108
109
						continue;
					}
Akka's avatar
Akka committed
110
					var fieldMeta = fieldMetaMap.get(keyName);
111
112
113
					LuaValue keyValue = table.get(k);

					if (keyValue.istable()) {
Akka's avatar
Akka committed
114
115
116
117
118
119
120
						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());
121
				    } else {
Akka's avatar
Akka committed
122
						methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring());
123
124
125
126
127
128
129
				    }
				} catch (Exception ex) {
					//ex.printStackTrace();
					continue;
				}
			}
		} catch (Exception e) {
130
			Grasscutter.getLogger().info(ScriptUtils.toMap(table).toString());
131
132
133
134
135
			e.printStackTrace();
		}
		
		return object;
	}
Akka's avatar
Akka committed
136

Akka's avatar
Akka committed
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
	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
174
		}
Akka's avatar
Akka committed
175
176
177
178
179
180
181
182
183
184
185
186
187
188
		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
189
	}
190
}