Skip to content

Commit 0b45f9c

Browse files
committed
Fix #4068 Make itemstack data in the ingredient manager immutable
1 parent ce7c048 commit 0b45f9c

File tree

8 files changed

+269
-82
lines changed

8 files changed

+269
-82
lines changed

CommonApi/src/main/java/mezz/jei/api/runtime/IIngredientFilter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public interface IIngredientFilter {
2727
*
2828
* @see #getFilteredIngredients(IIngredientType) to get a different type of ingredient, not just ItemStack.
2929
*
30-
* @see IIngredientManager#getAllIngredients(IIngredientType)
30+
* @see IIngredientManager#getAllTypedIngredients(IIngredientType)
3131
* to get all the ingredients known to JEI, not just ones currently shown by the filter.
3232
*
3333
* @since 11.1.1
@@ -41,7 +41,7 @@ default List<ItemStack> getFilteredItemStacks() {
4141
*
4242
* @see #getFilteredItemStacks() to just get ItemStacks, not all types of ingredients.
4343
*
44-
* @see IIngredientManager#getAllIngredients(IIngredientType)
44+
* @see IIngredientManager#getAllTypedIngredients(IIngredientType)
4545
* to get all the ingredients known to JEI, not just ones currently shown by the filter
4646
*/
4747
<T> List<T> getFilteredIngredients(IIngredientType<T> ingredientType);

CommonApi/src/main/java/mezz/jei/api/runtime/IIngredientManager.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ default Collection<ItemStack> getAllItemStacks() {
4646
@Unmodifiable
4747
<V> Collection<V> getAllIngredients(IIngredientType<V> ingredientType);
4848

49+
/**
50+
* Returns an unmodifiable collection of all the ingredients known to JEI, of the specified type.
51+
*
52+
* @since 19.24.0
53+
*/
54+
@Unmodifiable
55+
<V> Collection<ITypedIngredient<V>> getAllTypedIngredients(IIngredientType<V> ingredientType);
56+
4957
/**
5058
* Returns the appropriate ingredient helper for this ingredient.
5159
*/

Gui/src/main/java/mezz/jei/gui/ingredients/IngredientListElementFactory.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static List<IListElementInfo<?>> createBaseList(IIngredientManager ingred
3131

3232
public static <V> List<IListElementInfo<V>> createTestList(IIngredientManager ingredientManager, IIngredientType<V> ingredientType, Collection<V> ingredients, IModIdHelper modIdHelper) {
3333
return ingredients.stream()
34-
.map(i -> ingredientManager.createTypedIngredient(ingredientType, i))
34+
.map(i -> ingredientManager.createTypedIngredient(ingredientType, i, false))
3535
.flatMap(Optional::stream)
3636
.map(i -> ListElementInfo.create(i, ingredientManager, modIdHelper))
3737
.filter(Objects::nonNull)
@@ -52,15 +52,12 @@ public static List<IListElementInfo<?>> rebuildList(IIngredientManager ingredien
5252
}
5353

5454
private static <V> void addToBaseList(List<IListElementInfo<?>> baseList, IIngredientManager ingredientManager, IIngredientType<V> ingredientType, IModIdHelper modIdHelper) {
55-
Collection<V> ingredients = ingredientManager.getAllIngredients(ingredientType);
55+
Collection<ITypedIngredient<V>> typedIngredients = ingredientManager.getAllTypedIngredients(ingredientType);
5656
LOGGER.debug("Registering ingredients: {}", ingredientType.getIngredientClass().getSimpleName());
57-
for (V ingredient : ingredients) {
58-
Optional<ITypedIngredient<V>> typedIngredient = ingredientManager.createTypedIngredient(ingredientType, ingredient);
59-
if (typedIngredient.isPresent()) {
60-
IListElementInfo<V> orderedElement = ListElementInfo.create(typedIngredient.get(), ingredientManager, modIdHelper);
61-
if (orderedElement != null) {
62-
baseList.add(orderedElement);
63-
}
57+
for (ITypedIngredient<V> typedIngredient : typedIngredients) {
58+
IListElementInfo<V> orderedElement = ListElementInfo.create(typedIngredient, ingredientManager, modIdHelper);
59+
if (orderedElement != null) {
60+
baseList.add(orderedElement);
6461
}
6562
}
6663
}

Library/src/main/java/mezz/jei/library/ingredients/IngredientInfo.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package mezz.jei.library.ingredients;
22

3+
import com.google.common.collect.Collections2;
34
import com.mojang.serialization.Codec;
45
import mezz.jei.api.ingredients.IIngredientHelper;
56
import mezz.jei.api.ingredients.IIngredientRenderer;
@@ -20,12 +21,12 @@ public class IngredientInfo<T> {
2021
private final IIngredientHelper<T> ingredientHelper;
2122
private final IIngredientRenderer<T> ingredientRenderer;
2223
private final Codec<T> ingredientCodec;
23-
private final IngredientSet<T> ingredientSet;
24+
private final TypedIngredientSet<T> ingredientSet;
2425
private final ListMultiMap<Object, String> aliases;
2526

2627
public IngredientInfo(
2728
IIngredientType<T> ingredientType,
28-
Collection<T> ingredients,
29+
Collection<ITypedIngredient<T>> ingredients,
2930
IIngredientHelper<T> ingredientHelper,
3031
IIngredientRenderer<T> ingredientRenderer,
3132
@Nullable Codec<T> ingredientCodec
@@ -40,7 +41,7 @@ public IngredientInfo(
4041
this.ingredientRenderer = ingredientRenderer;
4142
this.ingredientCodec = ingredientCodec;
4243

43-
this.ingredientSet = new IngredientSet<>(ingredientHelper, UidContext.Ingredient);
44+
this.ingredientSet = new TypedIngredientSet<>(ingredientHelper, UidContext.Ingredient);
4445
this.ingredientSet.addAll(ingredients);
4546

4647
this.aliases = new ListMultiMap<>();
@@ -63,22 +64,29 @@ public Codec<T> getIngredientCodec() {
6364
}
6465

6566
@Unmodifiable
66-
public Collection<T> getAllIngredients() {
67+
public Collection<ITypedIngredient<T>> getAllTypedIngredients() {
6768
return Collections.unmodifiableCollection(ingredientSet);
6869
}
6970

70-
public void addIngredients(Collection<T> ingredients) {
71+
@Unmodifiable
72+
public Collection<T> getAllIngredients() {
73+
Collection<T> transform = Collections2.transform(ingredientSet, ITypedIngredient::getIngredient);
74+
return Collections.unmodifiableCollection(transform);
75+
}
76+
77+
public void addIngredients(Collection<ITypedIngredient<T>> ingredients) {
7178
this.ingredientSet.addAll(ingredients);
7279
}
7380

74-
public void removeIngredients(Collection<T> ingredients) {
81+
public void removeIngredients(Collection<ITypedIngredient<T>> ingredients) {
7582
this.ingredientSet.removeAll(ingredients);
7683
}
7784

7885
@SuppressWarnings({"removal"})
7986
@Deprecated(forRemoval = true)
8087
public Optional<T> getIngredientByLegacyUid(String uid) {
81-
return ingredientSet.getByLegacyUid(uid);
88+
return ingredientSet.getByLegacyUid(uid)
89+
.map(ITypedIngredient::getIngredient);
8290
}
8391

8492
@Unmodifiable

Library/src/main/java/mezz/jei/library/ingredients/IngredientManager.java

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.jetbrains.annotations.Nullable;
2323
import org.jetbrains.annotations.Unmodifiable;
2424

25+
import java.util.ArrayList;
2526
import java.util.Collection;
2627
import java.util.List;
2728
import java.util.Optional;
@@ -47,6 +48,16 @@ public <V> Collection<V> getAllIngredients(IIngredientType<V> ingredientType) {
4748
.getAllIngredients();
4849
}
4950

51+
@Override
52+
@Unmodifiable
53+
public <V> Collection<ITypedIngredient<V>> getAllTypedIngredients(IIngredientType<V> ingredientType) {
54+
ErrorUtil.checkNotNull(ingredientType, "ingredientType");
55+
56+
return this.registeredIngredients
57+
.getIngredientInfo(ingredientType)
58+
.getAllTypedIngredients();
59+
}
60+
5061
@Override
5162
public <V> IIngredientHelper<V> getIngredientHelper(V ingredient) {
5263
return getIngredientTypeChecked(ingredient)
@@ -112,30 +123,26 @@ public <V> void addIngredientsAtRuntime(IIngredientType<V> ingredientType, Colle
112123
LOGGER.debug("Ingredients added at runtime: {}", ingredientStrings);
113124
}
114125

115-
Collection<V> validIngredients = ingredients.stream()
116-
.filter(i -> {
117-
if (!ingredientHelper.isValidIngredient(i)) {
118-
String errorInfo = ingredientHelper.getErrorInfo(i);
119-
LOGGER.error("Attempted to add an invalid Ingredient: {}", errorInfo);
120-
return false;
121-
}
122-
if (!ingredientHelper.isIngredientOnServer(i)) {
123-
String errorInfo = ingredientHelper.getErrorInfo(i);
124-
LOGGER.error("Attempted to add an Ingredient that is not on the server: {}", errorInfo);
125-
return false;
126-
}
127-
return true;
128-
})
129-
.toList();
126+
List<ITypedIngredient<V>> validTypedIngredients = new ArrayList<>(ingredients.size());
127+
for (V ingredient : ingredients) {
128+
if (!ingredientHelper.isIngredientOnServer(ingredient)) {
129+
String errorInfo = ingredientHelper.getErrorInfo(ingredient);
130+
LOGGER.warn("Attempted to add an Ingredient that is not on the server: {}", errorInfo);
131+
continue;
132+
}
133+
ITypedIngredient<V> typedIngredient = TypedIngredient.createAndFilterInvalid(ingredientHelper, ingredientType, ingredient, false);
134+
if (typedIngredient == null) {
135+
LOGGER.warn("Attempted to add an invalid ingredient at runtime: {}", ingredientHelper.getErrorInfo(ingredient));
136+
continue;
137+
}
138+
139+
validTypedIngredients.add(typedIngredient);
140+
}
130141

131-
ingredientInfo.addIngredients(validIngredients);
142+
ingredientInfo.addIngredients(validTypedIngredients);
132143

133144
if (!this.listeners.isEmpty()) {
134-
List<ITypedIngredient<V>> typedIngredients = validIngredients.stream()
135-
.map(i -> TypedIngredient.createUnvalidated(ingredientType, i))
136-
.toList();
137-
138-
this.listeners.forEach(listener -> listener.onIngredientsAdded(ingredientHelper, typedIngredients));
145+
this.listeners.forEach(listener -> listener.onIngredientsAdded(ingredientHelper, validTypedIngredients));
139146
}
140147
}
141148

@@ -183,10 +190,11 @@ public <V> void removeIngredientsAtRuntime(IIngredientType<V> ingredientType, Co
183190
LOGGER.debug("Ingredients removed at runtime: {}", ingredientStrings);
184191
}
185192

186-
ingredientInfo.removeIngredients(ingredients);
193+
List<ITypedIngredient<V>> typedIngredients = TypedIngredient.createAndFilterInvalidNonnullList(this, ingredientType, ingredients, false);
194+
195+
ingredientInfo.removeIngredients(typedIngredients);
187196

188197
if (!this.listeners.isEmpty()) {
189-
List<ITypedIngredient<V>> typedIngredients = TypedIngredient.createAndFilterInvalidNonnullList(this, ingredientType, ingredients, false);
190198
this.listeners.forEach(listener -> listener.onIngredientsRemoved(ingredientHelper, typedIngredients));
191199
}
192200
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package mezz.jei.library.ingredients;
2+
3+
import mezz.jei.api.ingredients.IIngredientHelper;
4+
import mezz.jei.api.ingredients.IIngredientType;
5+
import mezz.jei.api.ingredients.ITypedIngredient;
6+
import mezz.jei.api.ingredients.subtypes.UidContext;
7+
import org.apache.logging.log4j.LogManager;
8+
import org.apache.logging.log4j.Logger;
9+
import org.jetbrains.annotations.Nullable;
10+
11+
import java.util.AbstractSet;
12+
import java.util.Collection;
13+
import java.util.Iterator;
14+
import java.util.LinkedHashMap;
15+
import java.util.Map;
16+
import java.util.Objects;
17+
import java.util.Optional;
18+
19+
public class TypedIngredientSet<T> extends AbstractSet<ITypedIngredient<T>> {
20+
private static final Logger LOGGER = LogManager.getLogger();
21+
22+
private final IIngredientHelper<T> ingredientHelper;
23+
private final UidContext context;
24+
private final Map<Object, ITypedIngredient<T>> ingredients;
25+
26+
public TypedIngredientSet(IIngredientHelper<T> ingredientHelper, UidContext context) {
27+
this.ingredientHelper = ingredientHelper;
28+
this.context = context;
29+
this.ingredients = new LinkedHashMap<>();
30+
}
31+
32+
@Nullable
33+
private Object getUid(ITypedIngredient<T> typedIngredient) {
34+
try {
35+
return ingredientHelper.getUid(typedIngredient, context);
36+
} catch (RuntimeException e) {
37+
try {
38+
String ingredientInfo = ingredientHelper.getErrorInfo(typedIngredient.getIngredient());
39+
LOGGER.warn("Found a broken ingredient {}", ingredientInfo, e);
40+
} catch (RuntimeException e2) {
41+
LOGGER.warn("Found a broken ingredient.", e2);
42+
}
43+
return null;
44+
}
45+
}
46+
47+
@Override
48+
public boolean add(ITypedIngredient<T> value) {
49+
Object uid = getUid(value);
50+
return uid != null && ingredients.put(uid, value) == null;
51+
}
52+
53+
@Override
54+
public boolean remove(Object value) {
55+
if (value instanceof ITypedIngredient<?> typedIngredient) {
56+
IIngredientType<?> type = typedIngredient.getType();
57+
if (this.ingredientHelper.getIngredientType().equals(type)) {
58+
@SuppressWarnings("unchecked")
59+
ITypedIngredient<T> cast = (ITypedIngredient<T>) typedIngredient;
60+
Object uid = getUid(cast);
61+
return uid != null && ingredients.remove(uid) != null;
62+
}
63+
}
64+
return false;
65+
}
66+
67+
@Override
68+
public boolean removeAll(Collection<?> c) {
69+
Objects.requireNonNull(c);
70+
boolean modified = false;
71+
for (Object value : c) {
72+
modified |= remove(value);
73+
}
74+
return modified;
75+
}
76+
77+
@Override
78+
public boolean addAll(Collection<? extends ITypedIngredient<T>> c) {
79+
Objects.requireNonNull(c);
80+
boolean modified = false;
81+
for (ITypedIngredient<T> value : c) {
82+
modified |= add(value);
83+
}
84+
return modified;
85+
}
86+
87+
@Override
88+
public boolean contains(Object value) {
89+
if (value instanceof ITypedIngredient<?> typedIngredient) {
90+
IIngredientType<?> type = typedIngredient.getType();
91+
if (this.ingredientHelper.getIngredientType().equals(type)) {
92+
@SuppressWarnings("unchecked")
93+
ITypedIngredient<T> cast = (ITypedIngredient<T>) typedIngredient;
94+
Object uid = getUid(cast);
95+
return uid != null && ingredients.containsKey(uid);
96+
}
97+
}
98+
return false;
99+
}
100+
101+
@SuppressWarnings("removal")
102+
@Deprecated(forRemoval = true)
103+
public Optional<ITypedIngredient<T>> getByLegacyUid(String uid) {
104+
ITypedIngredient<T> v = ingredients.get(uid);
105+
if (v != null) {
106+
return Optional.of(v);
107+
}
108+
109+
for (ITypedIngredient<T> typedIngredient : ingredients.values()) {
110+
String legacyUid = ingredientHelper.getUniqueId(typedIngredient.getIngredient(), context);
111+
if (uid.equals(legacyUid)) {
112+
return Optional.of(typedIngredient);
113+
}
114+
}
115+
return Optional.empty();
116+
}
117+
118+
@Override
119+
public void clear() {
120+
ingredients.clear();
121+
}
122+
123+
@Override
124+
public Iterator<ITypedIngredient<T>> iterator() {
125+
return ingredients.values().iterator();
126+
}
127+
128+
@Override
129+
public int size() {
130+
return ingredients.size();
131+
}
132+
}

0 commit comments

Comments
 (0)