From 2c66bc41467a5d463f0f049942b1f24a99719f92 Mon Sep 17 00:00:00 2001 From: JFronny Date: Sun, 19 Nov 2023 15:06:02 +0100 Subject: [PATCH] fix: implement OverlayMap to store FallbackI18n without evaluating lower map early --- .../mixin/LanguageManagerMixin.java | 6 +- .../jfronny/respackopts/util/OverlayMap.java | 169 ++++++++++++++++++ 2 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 src/main/java/io/gitlab/jfronny/respackopts/util/OverlayMap.java diff --git a/src/client/java/io/gitlab/jfronny/respackopts/mixin/LanguageManagerMixin.java b/src/client/java/io/gitlab/jfronny/respackopts/mixin/LanguageManagerMixin.java index f2521fc..9871142 100644 --- a/src/client/java/io/gitlab/jfronny/respackopts/mixin/LanguageManagerMixin.java +++ b/src/client/java/io/gitlab/jfronny/respackopts/mixin/LanguageManagerMixin.java @@ -1,7 +1,7 @@ package io.gitlab.jfronny.respackopts.mixin; -import com.google.common.collect.ImmutableMap; import io.gitlab.jfronny.respackopts.util.FallbackI18n; +import io.gitlab.jfronny.respackopts.util.OverlayMap; import net.minecraft.client.resource.language.LanguageManager; import net.minecraft.client.resource.language.TranslationStorage; import net.minecraft.resource.ResourceManager; @@ -18,8 +18,8 @@ public class LanguageManagerMixin { @Inject(method = "reload(Lnet/minecraft/resource/ResourceManager;)V", at = @At("TAIL"), locals = LocalCapture.CAPTURE_FAILEXCEPTION) private void rpo$appendTranslations(ResourceManager manager, CallbackInfo ci, List list, boolean bl, TranslationStorage translationStorage) { TranslationStorageAccessor storage = (TranslationStorageAccessor) translationStorage; - Map map = new HashMap<>(storage.getTranslations()); + Map map = new HashMap<>(); FallbackI18n.insertInto(map); - storage.setTranslations(ImmutableMap.copyOf(map)); + storage.setTranslations(new OverlayMap<>(storage.getTranslations(), map)); } } diff --git a/src/main/java/io/gitlab/jfronny/respackopts/util/OverlayMap.java b/src/main/java/io/gitlab/jfronny/respackopts/util/OverlayMap.java new file mode 100644 index 0000000..e9f363c --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/respackopts/util/OverlayMap.java @@ -0,0 +1,169 @@ +package io.gitlab.jfronny.respackopts.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.stream.Stream; + +public final class OverlayMap extends AbstractMap { + private final Map overlay; + private final Map lower; + private final Set masked = new HashSet<>(); + + public OverlayMap(Map overlay, Map lower) { + this.overlay = overlay; + this.lower = lower; + } + + @Override + public int size() { + return overlay.size() + (int) lower.keySet().stream() + .filter(s -> !overlay.containsKey(s) && !masked.contains(s)) + .count(); + } + + private Stream streamKeys() { + return Stream.concat( + overlay.keySet().stream(), + lower.keySet().stream() + .filter(s -> !overlay.containsKey(s) && !masked.contains(s)) + ); + } + + @Override + public boolean isEmpty() { + return overlay.isEmpty() && (masked.size() >= lower.size()); + } + + @Override + public boolean containsKey(Object key) { + return overlay.containsKey(key) || (lower.containsKey(key) && !masked.contains(key)); + } + + @Override + public boolean containsValue(Object value) { + return overlay.containsValue(value) || lower.entrySet() + .stream() + .filter(s -> Objects.equals(s.getValue(), value)) + .anyMatch(s -> !masked.contains(s.getKey())); + } + + @Override + public V get(Object key) { + V result = overlay.get(key); + if (result == null && !masked.contains(key)) result = lower.get(key); + return result; + } + + @Nullable + @Override + public V put(K key, V value) { + V result = overlay.put(key, value); + V low = lower.get(key); + if (low != null && masked.add(key) && result == null) { + result = low; + } + return result; + } + + @Override + public V remove(Object key) { + V result = overlay.remove(key); + V low = lower.get(key); + if (low != null && masked.add((K) key) && result == null) { + result = low; + } + return result; + } + + @Override + public void clear() { + overlay.clear(); + masked.addAll(lower.keySet()); + } + + @NotNull + @Override + public Set> entrySet() { + return entrySet; + } + + private final Set> entrySet = new AbstractSet<>() { + @Override + public int size() { + return OverlayMap.this.size(); + } + + @Override + public boolean isEmpty() { + return OverlayMap.this.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return o instanceof Map.Entry e && Objects.equals(OverlayMap.this.get(e.getKey()), e.getValue()); + } + + @Override + public boolean add(Entry kvEntry) { + return OverlayMap.this.put(kvEntry.getKey(), kvEntry.getValue()) == null; + } + + @Override + public boolean remove(Object o) { + return o instanceof Map.Entry e && OverlayMap.this.remove(e.getKey(), e.getValue()); + } + + @Override + public void clear() { + OverlayMap.this.clear(); + } + + @NotNull + @Override + public Iterator> iterator() { + return new Iterator<>() { + private final Iterator keys = OverlayMap.this.streamKeys().iterator(); + private K previous = null; + @Override + public boolean hasNext() { + return keys.hasNext(); + } + + @Override + public Entry next() { + previous = keys.next(); + return new Entry<>() { + private final K currentK = previous; + private V currentV = OverlayMap.this.get(currentK); + + @Override + public K getKey() { + return currentK; + } + + @Override + public V getValue() { + return currentV; + } + + @Override + public V setValue(V value) { + V result = OverlayMap.this.put(currentK, value); + currentV = value; + return result; + } + }; + } + + @Override + public void remove() { + if (previous == null) throw new IllegalStateException(); + OverlayMap.this.remove(previous); + previous = null; + } + }; + } + }; +}