1
+ package com .wasteofplastic .invswitcher .mocks ;
2
+
3
+ import static org .mockito .ArgumentMatchers .notNull ;
4
+ import static org .mockito .Mockito .doAnswer ;
5
+ import static org .mockito .Mockito .doReturn ;
6
+ import static org .mockito .Mockito .mock ;
7
+ import static org .mockito .Mockito .when ;
8
+
9
+ import java .lang .reflect .Field ;
10
+ import java .util .HashMap ;
11
+ import java .util .Locale ;
12
+ import java .util .Map ;
13
+ import java .util .Set ;
14
+ import java .util .logging .Logger ;
15
+
16
+ import org .bukkit .Bukkit ;
17
+ import org .bukkit .Keyed ;
18
+ import org .bukkit .NamespacedKey ;
19
+ import org .bukkit .Registry ;
20
+ import org .bukkit .Server ;
21
+ import org .bukkit .Tag ;
22
+ import org .bukkit .UnsafeValues ;
23
+ import org .eclipse .jdt .annotation .NonNull ;
24
+
25
+ public final class ServerMocks {
26
+
27
+ public static @ NonNull Server newServer () {
28
+ Server mock = mock (Server .class );
29
+
30
+ Logger noOp = mock (Logger .class );
31
+ when (mock .getLogger ()).thenReturn (noOp );
32
+ when (mock .isPrimaryThread ()).thenReturn (true );
33
+
34
+ // Unsafe
35
+ UnsafeValues unsafe = mock (UnsafeValues .class );
36
+ when (mock .getUnsafe ()).thenReturn (unsafe );
37
+
38
+ // Server must be available before tags can be mocked.
39
+ Bukkit .setServer (mock );
40
+
41
+ // Bukkit has a lot of static constants referencing registry values. To initialize those, the
42
+ // registries must be able to be fetched before the classes are touched.
43
+ Map <Class <? extends Keyed >, Object > registers = new HashMap <>();
44
+
45
+ doAnswer (invocationGetRegistry -> registers .computeIfAbsent (invocationGetRegistry .getArgument (0 ), clazz -> {
46
+ Registry <?> registry = mock (Registry .class );
47
+ Map <NamespacedKey , Keyed > cache = new HashMap <>();
48
+ doAnswer (invocationGetEntry -> {
49
+ NamespacedKey key = invocationGetEntry .getArgument (0 );
50
+ // Some classes (like BlockType and ItemType) have extra generics that will be
51
+ // erased during runtime calls. To ensure accurate typing, grab the constant's field.
52
+ // This approach also allows us to return null for unsupported keys.
53
+ Class <? extends Keyed > constantClazz ;
54
+ try {
55
+ //noinspection unchecked
56
+ constantClazz = (Class <? extends Keyed >) clazz
57
+ .getField (key .getKey ().toUpperCase (Locale .ROOT ).replace ('.' , '_' )).getType ();
58
+ } catch (ClassCastException e ) {
59
+ throw new RuntimeException (e );
60
+ } catch (NoSuchFieldException e ) {
61
+ return null ;
62
+ }
63
+
64
+ return cache .computeIfAbsent (key , key1 -> {
65
+ Keyed keyed = mock (constantClazz );
66
+ doReturn (key ).when (keyed ).getKey ();
67
+ return keyed ;
68
+ });
69
+ }).when (registry ).get (notNull ());
70
+ return registry ;
71
+ })).when (mock ).getRegistry (notNull ());
72
+
73
+ // Tags are dependent on registries, but use a different method.
74
+ // This will set up blank tags for each constant; all that needs to be done to render them
75
+ // functional is to re-mock Tag#getValues.
76
+ doAnswer (invocationGetTag -> {
77
+ Tag <?> tag = mock (Tag .class );
78
+ doReturn (invocationGetTag .getArgument (1 )).when (tag ).getKey ();
79
+ doReturn (Set .of ()).when (tag ).getValues ();
80
+ doAnswer (invocationIsTagged -> {
81
+ Keyed keyed = invocationIsTagged .getArgument (0 );
82
+ Class <?> type = invocationGetTag .getArgument (2 );
83
+ if (!type .isAssignableFrom (keyed .getClass ())) {
84
+ return null ;
85
+ }
86
+ // Since these are mocks, the exact instance might not be equal. Consider equal keys equal.
87
+ return tag .getValues ().contains (keyed )
88
+ || tag .getValues ().stream ().anyMatch (value -> value .getKey ().equals (keyed .getKey ()));
89
+ }).when (tag ).isTagged (notNull ());
90
+ return tag ;
91
+ }).when (mock ).getTag (notNull (), notNull (), notNull ());
92
+
93
+ // Once the server is all set up, touch BlockType and ItemType to initialize.
94
+ // This prevents issues when trying to access dependent methods from a Material constant.
95
+ try {
96
+ Class .forName ("org.bukkit.inventory.ItemType" );
97
+ Class .forName ("org.bukkit.block.BlockType" );
98
+ } catch (ClassNotFoundException e ) {
99
+ throw new RuntimeException (e );
100
+ }
101
+
102
+ return mock ;
103
+ }
104
+
105
+ public static void unsetBukkitServer () {
106
+ try {
107
+ Field server = Bukkit .class .getDeclaredField ("server" );
108
+ server .setAccessible (true );
109
+ server .set (null , null );
110
+ } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e ) {
111
+ throw new RuntimeException (e );
112
+ }
113
+ }
114
+
115
+ private ServerMocks () {
116
+ }
117
+
118
+ }
0 commit comments