Skip to content

Commit 6d69df4

Browse files
committed
Fix #4068 Make itemstack data in the ingredient manager immutable
1 parent 0e140ec commit 6d69df4

File tree

8 files changed

+217
-42
lines changed

8 files changed

+217
-42
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
@@ -44,6 +44,14 @@ default Collection<ItemStack> getAllItemStacks() {
4444
@Unmodifiable
4545
<V> Collection<V> getAllIngredients(IIngredientType<V> ingredientType);
4646

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

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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, false);
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: 13 additions & 6 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;
@@ -17,12 +18,12 @@ public class IngredientInfo<T> {
1718
private final IIngredientHelper<T> ingredientHelper;
1819
private final IIngredientRenderer<T> ingredientRenderer;
1920
private final Codec<T> ingredientCodec;
20-
private final IngredientSet<T> ingredientSet;
21+
private final TypedIngredientSet<T> ingredientSet;
2122
private final ListMultiMap<Object, String> aliases;
2223

2324
public IngredientInfo(
2425
IIngredientType<T> ingredientType,
25-
Collection<T> ingredients,
26+
Collection<ITypedIngredient<T>> ingredients,
2627
IIngredientHelper<T> ingredientHelper,
2728
IIngredientRenderer<T> ingredientRenderer,
2829
Codec<T> ingredientCodec
@@ -32,7 +33,7 @@ public IngredientInfo(
3233
this.ingredientRenderer = ingredientRenderer;
3334
this.ingredientCodec = ingredientCodec;
3435

35-
this.ingredientSet = new IngredientSet<>(ingredientHelper, UidContext.Ingredient);
36+
this.ingredientSet = new TypedIngredientSet<>(ingredientHelper, UidContext.Ingredient);
3637
this.ingredientSet.addAll(ingredients);
3738

3839
this.aliases = new ListMultiMap<>();
@@ -55,15 +56,21 @@ public Codec<T> getIngredientCodec() {
5556
}
5657

5758
@Unmodifiable
58-
public Collection<T> getAllIngredients() {
59+
public Collection<ITypedIngredient<T>> getAllTypedIngredients() {
5960
return Collections.unmodifiableCollection(ingredientSet);
6061
}
6162

62-
public void addIngredients(Collection<T> ingredients) {
63+
@Unmodifiable
64+
public Collection<T> getAllIngredients() {
65+
Collection<T> transform = Collections2.transform(ingredientSet, ITypedIngredient::getIngredient);
66+
return Collections.unmodifiableCollection(transform);
67+
}
68+
69+
public void addIngredients(Collection<ITypedIngredient<T>> ingredients) {
6370
this.ingredientSet.addAll(ingredients);
6471
}
6572

66-
public void removeIngredients(Collection<T> ingredients) {
73+
public void removeIngredients(Collection<ITypedIngredient<T>> ingredients) {
6774
this.ingredientSet.removeAll(ingredients);
6875
}
6976

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: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
18+
public class TypedIngredientSet<T> extends AbstractSet<ITypedIngredient<T>> {
19+
private static final Logger LOGGER = LogManager.getLogger();
20+
21+
private final IIngredientHelper<T> ingredientHelper;
22+
private final UidContext context;
23+
private final Map<Object, ITypedIngredient<T>> ingredients;
24+
25+
public TypedIngredientSet(IIngredientHelper<T> ingredientHelper, UidContext context) {
26+
this.ingredientHelper = ingredientHelper;
27+
this.context = context;
28+
this.ingredients = new LinkedHashMap<>();
29+
}
30+
31+
@Nullable
32+
private Object getUid(ITypedIngredient<T> typedIngredient) {
33+
try {
34+
return ingredientHelper.getUid(typedIngredient, context);
35+
} catch (RuntimeException e) {
36+
try {
37+
String ingredientInfo = ingredientHelper.getErrorInfo(typedIngredient.getIngredient());
38+
LOGGER.warn("Found a broken ingredient {}", ingredientInfo, e);
39+
} catch (RuntimeException e2) {
40+
LOGGER.warn("Found a broken ingredient.", e2);
41+
}
42+
return null;
43+
}
44+
}
45+
46+
@Override
47+
public boolean add(ITypedIngredient<T> value) {
48+
Object uid = getUid(value);
49+
return uid != null && ingredients.put(uid, value) == null;
50+
}
51+
52+
@Override
53+
public boolean remove(Object value) {
54+
if (value instanceof ITypedIngredient<?> typedIngredient) {
55+
IIngredientType<?> type = typedIngredient.getType();
56+
if (this.ingredientHelper.getIngredientType().equals(type)) {
57+
@SuppressWarnings("unchecked")
58+
ITypedIngredient<T> cast = (ITypedIngredient<T>) typedIngredient;
59+
Object uid = getUid(cast);
60+
return uid != null && ingredients.remove(uid) != null;
61+
}
62+
}
63+
return false;
64+
}
65+
66+
@Override
67+
public boolean removeAll(Collection<?> c) {
68+
Objects.requireNonNull(c);
69+
boolean modified = false;
70+
for (Object value : c) {
71+
modified |= remove(value);
72+
}
73+
return modified;
74+
}
75+
76+
@Override
77+
public boolean addAll(Collection<? extends ITypedIngredient<T>> c) {
78+
Objects.requireNonNull(c);
79+
boolean modified = false;
80+
for (ITypedIngredient<T> value : c) {
81+
modified |= add(value);
82+
}
83+
return modified;
84+
}
85+
86+
@Override
87+
public boolean contains(Object value) {
88+
if (value instanceof ITypedIngredient<?> typedIngredient) {
89+
IIngredientType<?> type = typedIngredient.getType();
90+
if (this.ingredientHelper.getIngredientType().equals(type)) {
91+
@SuppressWarnings("unchecked")
92+
ITypedIngredient<T> cast = (ITypedIngredient<T>) typedIngredient;
93+
Object uid = getUid(cast);
94+
return uid != null && ingredients.containsKey(uid);
95+
}
96+
}
97+
return false;
98+
}
99+
100+
@Override
101+
public void clear() {
102+
ingredients.clear();
103+
}
104+
105+
@Override
106+
public Iterator<ITypedIngredient<T>> iterator() {
107+
return ingredients.values().iterator();
108+
}
109+
110+
@Override
111+
public int size() {
112+
return ingredients.size();
113+
}
114+
}

Library/src/main/java/mezz/jei/library/load/registration/IngredientManagerBuilder.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,19 @@
1616
import mezz.jei.library.ingredients.IngredientInfo;
1717
import mezz.jei.library.ingredients.IngredientManager;
1818
import mezz.jei.library.ingredients.RegisteredIngredients;
19+
import mezz.jei.library.ingredients.TypedIngredient;
20+
import org.apache.logging.log4j.LogManager;
21+
import org.apache.logging.log4j.Logger;
1922

23+
import java.util.ArrayList;
2024
import java.util.Collection;
2125
import java.util.LinkedHashMap;
26+
import java.util.List;
2227
import java.util.SequencedMap;
2328

2429
public class IngredientManagerBuilder implements IModIngredientRegistration, IIngredientAliasRegistration, IExtraIngredientRegistration {
30+
private static final Logger LOGGER = LogManager.getLogger();
31+
2532
private final SequencedMap<IIngredientType<?>, IngredientInfo<?>> ingredientInfos = new LinkedHashMap<>();
2633
private final ISubtypeManager subtypeManager;
2734
private final IColorHelper colorHelper;
@@ -57,7 +64,23 @@ public <V> void register(
5764
throw new IllegalArgumentException("Ingredient type has already been registered: " + ingredientType.getIngredientClass());
5865
}
5966

60-
ingredientInfos.put(ingredientType, new IngredientInfo<>(ingredientType, allIngredients, ingredientHelper, ingredientRenderer, ingredientCodec));
67+
List<ITypedIngredient<V>> allTypedIngredients = new ArrayList<>(allIngredients.size());
68+
for (V ingredient : allIngredients) {
69+
if (!ingredientHelper.isIngredientOnServer(ingredient)) {
70+
String errorInfo = ingredientHelper.getErrorInfo(ingredient);
71+
LOGGER.warn("Attempted to add an Ingredient that is not on the server: {}", errorInfo);
72+
continue;
73+
}
74+
ITypedIngredient<V> typedIngredient = TypedIngredient.createAndFilterInvalid(ingredientHelper, ingredientType, ingredient, false);
75+
if (typedIngredient == null) {
76+
LOGGER.warn("Detected an invalid ingredient during ingredient registration: {}", ingredientHelper.getErrorInfo(ingredient));
77+
continue;
78+
}
79+
80+
allTypedIngredients.add(typedIngredient);
81+
}
82+
83+
ingredientInfos.put(ingredientType, new IngredientInfo<>(ingredientType, allTypedIngredients, ingredientHelper, ingredientRenderer, ingredientCodec));
6184
}
6285

6386
@Override
@@ -66,7 +89,25 @@ public <V> void addExtraIngredients(IIngredientType<V> ingredientType, Collectio
6689
ErrorUtil.checkNotNull(extraIngredients, "extraIngredients");
6790

6891
IngredientInfo<V> castIngredientInfo = getIngredientInfo(ingredientType);
69-
castIngredientInfo.addIngredients(extraIngredients);
92+
IIngredientHelper<V> ingredientHelper = castIngredientInfo.getIngredientHelper();
93+
94+
List<ITypedIngredient<V>> extraTypedIngredients = new ArrayList<>(extraIngredients.size());
95+
for (V ingredient : extraIngredients) {
96+
if (!ingredientHelper.isIngredientOnServer(ingredient)) {
97+
String errorInfo = ingredientHelper.getErrorInfo(ingredient);
98+
LOGGER.warn("Attempted to add an extra Ingredient that is not on the server: {}", errorInfo);
99+
continue;
100+
}
101+
102+
ITypedIngredient<V> typedIngredient = TypedIngredient.createAndFilterInvalid(ingredientHelper, ingredientType, ingredient, false);
103+
if (typedIngredient == null) {
104+
LOGGER.warn("Detected an invalid ingredient when adding extra ingredients: {}", ingredientHelper.getErrorInfo(ingredient));
105+
continue;
106+
}
107+
108+
extraTypedIngredients.add(typedIngredient);
109+
}
110+
castIngredientInfo.addIngredients(extraTypedIngredients);
70111
}
71112

72113
@Override

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,4 @@ modrinthId=u6dRKJwZ
5353
jUnitVersion=5.10.2
5454

5555
# Version
56-
specificationVersion=24.0.0
56+
specificationVersion=24.1.0

0 commit comments

Comments
 (0)