1+ package world .bentobox .aoneblock .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