Skip to content

Commit e44d1e6

Browse files
authored
Improve auto wrapper handling (#1518)
1 parent baecaf4 commit e44d1e6

File tree

2 files changed

+182
-53
lines changed

2 files changed

+182
-53
lines changed

src/main/java/com/comphenix/protocol/wrappers/AutoWrapper.java

Lines changed: 70 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
*/
1717
package com.comphenix.protocol.wrappers;
1818

19+
import com.comphenix.protocol.reflect.accessors.Accessors;
20+
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
21+
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
22+
import com.comphenix.protocol.reflect.instances.DefaultInstances;
23+
import com.google.common.base.Defaults;
24+
import java.lang.reflect.Constructor;
1925
import java.lang.reflect.Field;
2026
import java.lang.reflect.Modifier;
2127
import java.util.Arrays;
@@ -41,9 +47,18 @@
4147
* @author dmulloy2
4248
*/
4349
public class AutoWrapper<T> implements EquivalentConverter<T> {
50+
private static final Object[] NO_ARGS = new Object[0];
51+
4452
private Map<Integer, Function<Object, Object>> wrappers = new HashMap<>();
4553
private Map<Integer, Function<Object, Object>> unwrappers = new HashMap<>();
4654

55+
// lazy
56+
private FieldAccessor[] nmsAccessors;
57+
private FieldAccessor[] wrapperAccessors;
58+
59+
private Object[] nmsDefaultArgs;
60+
private ConstructorAccessor nmsInstanceCreator;
61+
4762
private Class<T> wrapperClass;
4863
private Class<?> nmsClass;
4964

@@ -79,75 +94,77 @@ public T wrap(Object nmsObject) {
7994
throw new InvalidWrapperException(wrapperClass.getSimpleName() + " is not accessible!", ex);
8095
}
8196

82-
Field[] wrapperFields = wrapperClass.getDeclaredFields();
83-
Field[] nmsFields = Arrays
84-
.stream(nmsClass.getDeclaredFields())
85-
.filter(field -> !Modifier.isStatic(field.getModifiers()))
86-
.toArray(Field[]::new);
97+
// ensures that all accessors are present
98+
computeFieldAccessors();
8799

88-
for (int i = 0; i < wrapperFields.length; i++) {
89-
try {
90-
Field wrapperField = wrapperFields[i];
100+
for (int i = 0; i < wrapperAccessors.length; i++) {
101+
FieldAccessor source = nmsAccessors[i];
102+
FieldAccessor target = wrapperAccessors[i];
91103

92-
Field nmsField = nmsFields[i];
93-
if (!nmsField.isAccessible())
94-
nmsField.setAccessible(true);
104+
Object value = source.get(nmsObject);
105+
if (wrappers.containsKey(i))
106+
value = wrappers.get(i).apply(value);
95107

96-
Object value = nmsField.get(nmsObject);
97-
if (wrappers.containsKey(i))
98-
value = wrappers.get(i).apply(value);
99-
100-
wrapperField.set(instance, value);
101-
} catch (Exception ex) {
102-
throw new InvalidWrapperException("Failed to wrap field at index " + i, ex);
103-
}
108+
target.set(instance, value);
104109
}
105110

106111
return instance;
107112
}
108113

109114
public Object unwrap(Object wrapper) {
110-
Object instance;
115+
// ensures that all accessors are present
116+
computeFieldAccessors();
117+
computeNmsConstructorAccess();
111118

112-
try {
113-
instance = nmsClass.newInstance();
114-
} catch (ReflectiveOperationException ex) {
115-
throw new InvalidWrapperException("Failed to construct new " + nmsClass.getSimpleName(), ex);
116-
}
119+
Object instance = nmsInstanceCreator.invoke(nmsDefaultArgs);
117120

118-
Field[] wrapperFields = wrapperClass.getDeclaredFields();
119-
Field[] nmsFields = Arrays
120-
.stream(nmsClass.getDeclaredFields())
121-
.filter(field -> !Modifier.isStatic(field.getModifiers()))
122-
.toArray(Field[]::new);
123-
124-
for (int i = 0; i < wrapperFields.length; i++) {
125-
try {
126-
Field wrapperField = wrapperFields[i];
127-
128-
Field nmsField = nmsFields[i];
129-
if (!nmsField.isAccessible())
130-
nmsField.setAccessible(true);
131-
if (Modifier.isFinal(nmsField.getModifiers()))
132-
unsetFinal(nmsField);
133-
134-
Object value = wrapperField.get(wrapper);
135-
if (unwrappers.containsKey(i))
136-
value = unwrappers.get(i).apply(value);
137-
138-
nmsField.set(instance, value);
139-
} catch (ReflectiveOperationException ex) {
140-
throw new InvalidWrapperException("Failed to unwrap field", ex);
141-
}
121+
for (int i = 0; i < wrapperAccessors.length; i++) {
122+
FieldAccessor source = wrapperAccessors[i];
123+
FieldAccessor target = nmsAccessors[i];
124+
125+
Object value = source.get(wrapper);
126+
if (unwrappers.containsKey(i))
127+
value = unwrappers.get(i).apply(value);
128+
129+
target.set(instance, value);
142130
}
143131

144132
return instance;
145133
}
146134

147-
private void unsetFinal(Field field) throws ReflectiveOperationException {
148-
Field modifiers = Field.class.getDeclaredField("modifiers");
149-
modifiers.setAccessible(true);
150-
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
135+
private void computeFieldAccessors() {
136+
if (nmsAccessors == null) {
137+
nmsAccessors = Arrays
138+
.stream(nmsClass.getDeclaredFields())
139+
.filter(field -> !Modifier.isStatic(field.getModifiers()))
140+
.map(field -> Accessors.getFieldAccessor(field, true))
141+
.toArray(FieldAccessor[]::new);
142+
}
143+
144+
if (wrapperAccessors == null) {
145+
wrapperAccessors = Arrays
146+
.stream(wrapperClass.getDeclaredFields())
147+
.map(field -> Accessors.getFieldAccessor(field, true))
148+
.toArray(FieldAccessor[]::new);
149+
}
150+
}
151+
152+
private void computeNmsConstructorAccess() {
153+
if (nmsInstanceCreator == null) {
154+
ConstructorAccessor noArgs = Accessors.getConstructorAccessorOrNull(nmsClass);
155+
if (noArgs != null) {
156+
// no args constructor is available - use it
157+
nmsInstanceCreator = noArgs;
158+
nmsDefaultArgs = NO_ARGS;
159+
} else {
160+
// use the first constructor of the class
161+
nmsInstanceCreator = Accessors.getConstructorAccessor(nmsClass.getDeclaredConstructors()[0]);
162+
nmsDefaultArgs = Arrays
163+
.stream(nmsInstanceCreator.getConstructor().getParameterTypes())
164+
.map(type -> type.isPrimitive() ? Defaults.defaultValue(type) : null)
165+
.toArray(Object[]::new);
166+
}
167+
}
151168
}
152169

153170
// ---- Equivalent conversion
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.comphenix.protocol.wrappers;
2+
3+
import static com.comphenix.protocol.utility.MinecraftReflection.getMinecraftClass;
4+
import static org.junit.Assert.assertEquals;
5+
import static org.junit.Assert.assertFalse;
6+
import static org.junit.Assert.assertSame;
7+
import static org.junit.Assert.assertTrue;
8+
9+
import com.comphenix.protocol.BukkitInitialization;
10+
import net.minecraft.advancements.AdvancementDisplay;
11+
import net.minecraft.advancements.AdvancementFrameType;
12+
import net.minecraft.network.chat.ChatComponentText;
13+
import net.minecraft.world.item.Items;
14+
import org.bukkit.Material;
15+
import org.bukkit.inventory.ItemStack;
16+
import org.junit.BeforeClass;
17+
import org.junit.Test;
18+
19+
public class AutoWrapperTest {
20+
21+
@BeforeClass
22+
public static void initializeBukkit() {
23+
BukkitInitialization.initializeAll();
24+
}
25+
26+
@Test
27+
public void testToNms() {
28+
WrappedAdvancementDisplay display = new WrappedAdvancementDisplay();
29+
display.title = WrappedChatComponent.fromText("Test123");
30+
display.description = WrappedChatComponent.fromText("Test567");
31+
display.item = new ItemStack(Material.SAND);
32+
display.background = new MinecraftKey("test");
33+
display.frameType = WrappedFrameType.CHALLENGE;
34+
display.announceChat = false;
35+
display.showToast = true;
36+
display.hidden = true;
37+
display.x = 5f;
38+
display.y = 67f;
39+
40+
AdvancementDisplay nms = (AdvancementDisplay) displayWrapper().unwrap(display);
41+
42+
assertTrue(nms.h());
43+
assertTrue(nms.j());
44+
assertFalse(nms.i());
45+
assertEquals("test", nms.d().a());
46+
assertEquals("Test123", nms.a().a());
47+
assertEquals("Test567", nms.b().a());
48+
assertSame(AdvancementFrameType.b, nms.e());
49+
assertSame(Items.L, nms.c().c());
50+
assertEquals(5f, nms.f(), 0f);
51+
assertEquals(67f, nms.g(), 0f);
52+
}
53+
54+
@Test
55+
public void testFromNms() {
56+
AdvancementDisplay display = new AdvancementDisplay(
57+
new net.minecraft.world.item.ItemStack(Items.L),
58+
new ChatComponentText("Test123"),
59+
new ChatComponentText("Test567"),
60+
new net.minecraft.resources.MinecraftKey("minecraft", "test"),
61+
AdvancementFrameType.b,
62+
true,
63+
false,
64+
true
65+
);
66+
display.a(5f, 67f);
67+
68+
WrappedAdvancementDisplay wrapped = displayWrapper().wrap(display);
69+
70+
assertTrue(wrapped.showToast);
71+
assertTrue(wrapped.hidden);
72+
assertFalse(wrapped.announceChat);
73+
assertEquals("test", wrapped.background.getKey());
74+
assertEquals("{\"text\":\"Test123\"}", wrapped.title.getJson());
75+
assertEquals("{\"text\":\"Test567\"}", wrapped.description.getJson());
76+
assertSame(WrappedFrameType.CHALLENGE, wrapped.frameType);
77+
assertSame(Material.SAND, wrapped.item.getType());
78+
assertEquals(5f, wrapped.x, 0f);
79+
assertEquals(67f, wrapped.y, 0f);
80+
}
81+
82+
private AutoWrapper<WrappedAdvancementDisplay> displayWrapper() {
83+
return AutoWrapper
84+
.wrap(WrappedAdvancementDisplay.class, "advancements.AdvancementDisplay")
85+
.field(0, BukkitConverters.getWrappedChatComponentConverter())
86+
.field(1, BukkitConverters.getWrappedChatComponentConverter())
87+
.field(2, BukkitConverters.getItemStackConverter())
88+
.field(3, MinecraftKey.getConverter())
89+
.field(4, EnumWrappers.getGenericConverter(getMinecraftClass("advancements.AdvancementFrameType"),
90+
WrappedFrameType.class));
91+
}
92+
93+
public enum WrappedFrameType {
94+
TASK,
95+
CHALLENGE,
96+
GOAL
97+
}
98+
99+
public static final class WrappedAdvancementDisplay {
100+
101+
public WrappedChatComponent title;
102+
public WrappedChatComponent description;
103+
public ItemStack item;
104+
public MinecraftKey background;
105+
public WrappedFrameType frameType;
106+
public boolean showToast;
107+
public boolean announceChat;
108+
public boolean hidden;
109+
public float x;
110+
public float y;
111+
}
112+
}

0 commit comments

Comments
 (0)