-
Notifications
You must be signed in to change notification settings - Fork 754
/
ModuleHookMap.java
170 lines (147 loc) · 5.81 KB
/
ModuleHookMap.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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
package slimeknights.tconstruct.library.module;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.LinkedHashMultimap;
import lombok.RequiredArgsConstructor;
import slimeknights.mantle.data.loadable.ErrorFactory;
import slimeknights.tconstruct.library.modifiers.impl.BasicModifier;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/** Logic for handling modifier and tool hooks, automatically fetching the default instance as needed. */
@SuppressWarnings({"ClassCanBeRecord", "unused"}) // no record as we don't want the map to be public
@RequiredArgsConstructor
public class ModuleHookMap {
/** Instance with no modifiers */
public static final ModuleHookMap EMPTY = new ModuleHookMap(Collections.emptyMap());
/** Internal map of modifier hook to object. It's the caller's responsibility to make sure the object is valid for the hook */
private final Map<ModuleHook<?>,Object> modules;
/**
* Creates a modifier hook map from the given module list
* @param modules List of modules
* @return Modifier hook map
*/
public static ModuleHookMap createMap(List<? extends WithHooks<?>> modules, ErrorFactory error) {
if (modules.isEmpty()) {
return EMPTY;
}
Builder builder = builder();
for (WithHooks<?> module : modules) {
for (ModuleHook<?> hook : module.getModuleHooks()) {
builder.addHookChecked(module.module(), hook, error);
}
}
return builder.build();
}
/** Checks if a module is registered for the given hook */
public boolean hasHook(ModuleHook<?> hook) {
return modules.containsKey(hook);
}
/** Gets the module matching the given hook, or null if not defined */
@SuppressWarnings("unchecked")
@Nullable
public <T> T getOrNull(ModuleHook<T> hook) {
return (T)modules.get(hook);
}
/** Gets the module matching the given hook */
public <T> T getOrDefault(ModuleHook<T> hook) {
T object = getOrNull(hook);
if (object != null) {
return object;
}
return hook.getDefaultInstance();
}
/** Gets an unchecked view of all internal modules for the sake of serialization */
public Map<ModuleHook<?>,Object> getAllModules() {
return modules;
}
/** Creates a new builder instance */
public static ModuleHookMap.Builder builder() {
return new ModuleHookMap.Builder();
}
@SuppressWarnings("UnusedReturnValue")
public static class Builder {
private final ErrorFactory ILLEGAL_ARGUMENT = IllegalArgumentException::new;
/** Builder for the final map */
private final LinkedHashMultimap<ModuleHook<?>,Object> modules = LinkedHashMultimap.create();
private Builder() {}
/**
* Adds a module to the builder, validating it at runtime. Used for JSON parsing
* @throws IllegalArgumentException if the hook type is invalid
*/
public Builder addHookChecked(Object object, ModuleHook<?> hook) {
return addHookChecked(object, hook, ILLEGAL_ARGUMENT);
}
/**
* Adds a module to the builder, validating it at runtime. Used for JSON parsing
* @throws RuntimeException if the hook is type in invalid matching the given factory
*/
public Builder addHookChecked(Object object, ModuleHook<?> hook, ErrorFactory error) {
if (hook.isValid(object)) {
modules.put(hook, object);
} else {
throw error.create("Object " + object + " is invalid for hook " + hook);
}
return this;
}
/** Adds a modifier module to the builder, automatically adding all its hooks. Use {@link #addHook(Object, ModuleHook)} to specify hooks. */
public Builder addModule(HookProvider module) {
for (ModuleHook<?> hook : module.getDefaultHooks()) {
addHookChecked(module, hook);
}
return this;
}
/** Adds a module to the builder */
public <H, T extends H> Builder addHook(T object, ModuleHook<H> hook) {
modules.put(hook, object);
return this;
}
/** Adds a module to the builder that implements multiple hooks */
public <T> Builder addHook(T object, ModuleHook<? super T> hook1, ModuleHook<? super T> hook2) {
addHook(object, hook1);
addHook(object, hook2);
return this;
}
/** Adds a module to the builder that implements multiple hooks */
public <T> Builder addHook(T object, ModuleHook<? super T> hook1, ModuleHook<? super T> hook2, ModuleHook<? super T> hook3) {
addHook(object, hook1);
addHook(object, hook2);
addHook(object, hook3);
return this;
}
/** Adds a module to the builder that implements multiple hooks */
@SafeVarargs
public final <T> Builder addHook(T object, ModuleHook<? super T>... hooks) {
for (ModuleHook<? super T> hook : hooks) {
addHook(object, hook);
}
return this;
}
/** Helper to deal with generics */
@SuppressWarnings("unchecked")
private static <T> void insert(ImmutableMap.Builder<ModuleHook<?>,Object> builder, ModuleHook<T> hook, Collection<Object> objects) {
if (objects.size() == 1) {
builder.put(hook, objects.iterator().next());
} else if (!objects.isEmpty()) {
builder.put(hook, hook.merge((Collection<T>)objects));
}
}
/** Builds the final map */
public ModuleHookMap build() {
if (modules.isEmpty()) {
return EMPTY;
}
ImmutableMap.Builder<ModuleHook<?>,Object> builder = ImmutableMap.builder();
for (Entry<ModuleHook<?>,Collection<Object>> entry : modules.asMap().entrySet()) {
insert(builder, entry.getKey(), entry.getValue());
}
return new ModuleHookMap(builder.build());
}
/** Transitions this builder into a basic modifier builder */
public BasicModifier.Builder modifier() {
return BasicModifier.Builder.builder(build());
}
}
}