Do not cache registry (#651)

This commit is contained in:
TheMode 2022-02-04 22:28:05 +01:00 committed by GitHub
parent 6716b21a4a
commit 0743759eed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 318 additions and 257 deletions

View File

@ -44,7 +44,7 @@ import java.util.function.BiFunction;
record EntityTypeImpl(Registry.EntityEntry registry) implements EntityType {
private static final Registry.Container<EntityType> CONTAINER = Registry.createContainer(Registry.Resource.ENTITIES,
(namespace, object) -> new EntityTypeImpl(Registry.entity(namespace, object, null)));
(namespace, properties) -> new EntityTypeImpl(Registry.entity(namespace, properties)));
static final Map<String, BiFunction<Entity, Metadata, EntityMeta>> ENTITY_META_SUPPLIER = createMetaMap();
static EntityType get(@NotNull String namespace) {

View File

@ -9,6 +9,7 @@ import net.minestom.server.tag.Tag;
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.ObjectArray;
import net.minestom.server.utils.block.BlockUtils;
import net.minestom.server.utils.collection.MergedMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
@ -20,7 +21,7 @@ import java.util.*;
import java.util.function.Function;
record BlockImpl(@NotNull Registry.BlockEntry registry,
@NotNull int[] propertiesArray,
int @NotNull [] propertiesArray,
@Nullable NBTCompound nbt,
@Nullable BlockHandler handler) implements Block {
// Block state -> block object
@ -32,20 +33,21 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
// Block id -> Map<PropertiesValues, Block>
private static final ObjectArray<Map<PropertiesHolder, BlockImpl>> POSSIBLE_STATES = new ObjectArray<>();
private static final Registry.Container<Block> CONTAINER = Registry.createContainer(Registry.Resource.BLOCKS,
(namespace, object) -> {
final int blockId = ((Number) object.get("id")).intValue();
final var stateObject = (Map<String, Object>) object.get("states");
(namespace, properties) -> {
final int blockId = properties.getInt("id");
final Registry.Properties stateObject = properties.section("states");
// Retrieve properties
String[] keys = new String[0];
String[][] values = new String[0][];
{
var properties = (Map<String, Object>) object.get("properties");
if (properties != null) {
keys = new String[properties.size()];
values = new String[properties.size()][];
Registry.Properties stateProperties = properties.section("properties");
if (stateProperties != null) {
final int stateCount = stateProperties.size();
keys = new String[stateCount];
values = new String[stateCount][];
int i = 0;
for (var entry : properties.entrySet()) {
for (var entry : stateProperties) {
final int entryIndex = i++;
keys[entryIndex] = entry.getKey();
@ -59,12 +61,11 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
// Retrieve block states
{
final var stateEntries = stateObject.entrySet();
final int propertiesCount = stateEntries.size();
final int propertiesCount = stateObject.size();
PropertiesHolder[] propertiesKeys = new PropertiesHolder[propertiesCount];
BlockImpl[] blocksValues = new BlockImpl[propertiesCount];
int propertiesOffset = 0;
for (var stateEntry : stateEntries) {
for (var stateEntry : stateObject) {
final String query = stateEntry.getKey();
final var stateOverride = (Map<String, Object>) stateEntry.getValue();
final var propertyMap = BlockUtils.parseProperties(query);
@ -82,7 +83,8 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
propertiesArray[keyIndex] = valueIndex;
}
final BlockImpl block = new BlockImpl(Registry.block(namespace, object, stateOverride),
var mainProperties = Registry.Properties.fromMap(new MergedMap<>(properties.asMap(), stateOverride));
final BlockImpl block = new BlockImpl(Registry.block(namespace, mainProperties),
propertiesArray, null, null);
BLOCK_STATE_MAP.set(block.stateId(), block);
propertiesKeys[propertiesOffset] = new PropertiesHolder(propertiesArray);
@ -91,7 +93,7 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
POSSIBLE_STATES.set(blockId, ArrayUtils.toMap(propertiesKeys, blocksValues, propertiesOffset));
}
// Register default state
final int defaultState = ((Number) object.get("defaultStateId")).intValue();
final int defaultState = properties.getInt("defaultStateId");
return getState(defaultState);
});
private static final Cache<NBTCompound, NBTCompound> NBT_CACHE = Caffeine.newBuilder()

View File

@ -7,7 +7,7 @@ import java.util.Collection;
record EnchantmentImpl(Registry.EnchantmentEntry registry) implements Enchantment {
private static final Registry.Container<Enchantment> CONTAINER = Registry.createContainer(Registry.Resource.ENCHANTMENTS,
(namespace, object) -> new EnchantmentImpl(Registry.enchantment(namespace, object, null)));
(namespace, properties) -> new EnchantmentImpl(Registry.enchantment(namespace, properties)));
static Enchantment get(@NotNull String namespace) {
return CONTAINER.get(namespace);

View File

@ -7,7 +7,7 @@ import java.util.Collection;
record MaterialImpl(Registry.MaterialEntry registry) implements Material {
private static final Registry.Container<Material> CONTAINER = Registry.createContainer(Registry.Resource.ITEMS,
(namespace, object) -> new MaterialImpl(Registry.material(namespace, object, null)));
(namespace, properties) -> new MaterialImpl(Registry.material(namespace, properties)));
static Material get(@NotNull String namespace) {
return CONTAINER.get(namespace);

View File

@ -8,7 +8,7 @@ import java.util.Collection;
record ParticleImpl(NamespaceID namespace, int id) implements Particle {
private static final Registry.Container<Particle> CONTAINER = Registry.createContainer(Registry.Resource.PARTICLES,
(namespace, object) -> new ParticleImpl(NamespaceID.from(namespace), ((Number) object.get("id")).intValue()));
(namespace, properties) -> new ParticleImpl(NamespaceID.from(namespace), properties.getInt("id")));
static Particle get(@NotNull String namespace) {
return CONTAINER.get(namespace);

View File

@ -7,7 +7,7 @@ import java.util.Collection;
record PotionEffectImpl(Registry.PotionEffectEntry registry) implements PotionEffect {
private static final Registry.Container<PotionEffect> CONTAINER = Registry.createContainer(Registry.Resource.POTION_EFFECTS,
(namespace, object) -> new PotionEffectImpl(Registry.potionEffect(namespace, object, null)));
(namespace, properties) -> new PotionEffectImpl(Registry.potionEffect(namespace, properties)));
static PotionEffect get(@NotNull String namespace) {
return CONTAINER.get(namespace);

View File

@ -8,7 +8,7 @@ import java.util.Collection;
record PotionTypeImpl(NamespaceID namespace, int id) implements PotionType {
private static final Registry.Container<PotionType> CONTAINER = Registry.createContainer(Registry.Resource.POTION_TYPES,
(namespace, object) -> new PotionTypeImpl(NamespaceID.from(namespace), ((Number) object.get("id")).intValue()));
(namespace, properties) -> new PotionTypeImpl(NamespaceID.from(namespace), properties.getInt("id")));
static PotionType get(@NotNull String namespace) {
return CONTAINER.get(namespace);

View File

@ -26,28 +26,28 @@ import java.util.function.Supplier;
*/
public final class Registry {
@ApiStatus.Internal
public static BlockEntry block(String namespace, @NotNull Map<String, Object> jsonObject, Map<String, Object> override) {
return new BlockEntry(namespace, jsonObject, override);
public static BlockEntry block(String namespace, @NotNull Properties main) {
return new BlockEntry(namespace, main, null);
}
@ApiStatus.Internal
public static MaterialEntry material(String namespace, @NotNull Map<String, Object> jsonObject, Map<String, Object> override) {
return new MaterialEntry(namespace, jsonObject, override);
public static MaterialEntry material(String namespace, @NotNull Properties main) {
return new MaterialEntry(namespace, main, null);
}
@ApiStatus.Internal
public static EntityEntry entity(String namespace, @NotNull Map<String, Object> jsonObject, Map<String, Object> override) {
return new EntityEntry(namespace, jsonObject, override);
public static EntityEntry entity(String namespace, @NotNull Properties main) {
return new EntityEntry(namespace, main, null);
}
@ApiStatus.Internal
public static EnchantmentEntry enchantment(String namespace, @NotNull Map<String, Object> jsonObject, Map<String, Object> override) {
return new EnchantmentEntry(namespace, jsonObject, override);
public static EnchantmentEntry enchantment(String namespace, @NotNull Properties main) {
return new EnchantmentEntry(namespace, main, null);
}
@ApiStatus.Internal
public static PotionEffectEntry potionEffect(String namespace, @NotNull Map<String, Object> jsonObject, Map<String, Object> override) {
return new PotionEffectEntry(namespace, jsonObject, override);
public static PotionEffectEntry potionEffect(String namespace, @NotNull Properties main) {
return new PotionEffectEntry(namespace, main, null);
}
@ApiStatus.Internal
@ -73,8 +73,8 @@ public final class Registry {
ObjectArray<T> ids = new ObjectArray<>(entries.size());
for (var entry : entries.entrySet()) {
final String namespace = entry.getKey();
final Map<String, Object> object = entry.getValue();
final T value = loader.get(namespace, object);
final Properties properties = Properties.fromMap(entry.getValue());
final T value = loader.get(namespace, properties);
ids.set(value.id(), value);
namespaces.put(value.name(), value);
}
@ -119,7 +119,7 @@ public final class Registry {
}
public interface Loader<T extends ProtocolObject> {
T get(String namespace, Map<String, Object> object);
T get(String namespace, Properties properties);
}
}
@ -148,7 +148,7 @@ public final class Registry {
}
}
public static class BlockEntry extends Entry {
public static final class BlockEntry implements Entry {
private final NamespaceID namespace;
private final int id;
private final int stateId;
@ -164,33 +164,34 @@ public final class Registry {
private final String blockEntity;
private final int blockEntityId;
private final Supplier<Material> materialSupplier;
private final Properties custom;
private BlockEntry(String namespace, Map<String, Object> main, Map<String, Object> override) {
super(main, override);
private BlockEntry(String namespace, Properties main, Properties custom) {
this.custom = custom;
this.namespace = NamespaceID.from(namespace);
this.id = getInt("id");
this.stateId = getInt("stateId");
this.translationKey = getString("translationKey");
this.hardness = getDouble("hardness");
this.explosionResistance = getDouble("explosionResistance");
this.friction = getDouble("friction");
this.speedFactor = getDouble("speedFactor", 1);
this.jumpFactor = getDouble("jumpFactor", 1);
this.air = getBoolean("air", false);
this.solid = getBoolean("solid");
this.liquid = getBoolean("liquid", false);
this.id = main.getInt("id");
this.stateId = main.getInt("stateId");
this.translationKey = main.getString("translationKey");
this.hardness = main.getDouble("hardness");
this.explosionResistance = main.getDouble("explosionResistance");
this.friction = main.getDouble("friction");
this.speedFactor = main.getDouble("speedFactor", 1);
this.jumpFactor = main.getDouble("jumpFactor", 1);
this.air = main.getBoolean("air", false);
this.solid = main.getBoolean("solid");
this.liquid = main.getBoolean("liquid", false);
{
Map<String, Object> blockEntity = element("blockEntity");
Properties blockEntity = main.section("blockEntity");
if (blockEntity != null) {
this.blockEntity = (String) blockEntity.get("namespace");
this.blockEntityId = ((Number) blockEntity.get("id")).intValue();
this.blockEntity = blockEntity.getString("namespace");
this.blockEntityId = blockEntity.getInt("id");
} else {
this.blockEntity = null;
this.blockEntityId = 0;
}
}
{
final String materialNamespace = getString("correspondingItem", null);
final String materialNamespace = main.getString("correspondingItem", null);
this.materialSupplier = materialNamespace != null ? () -> Material.fromNamespaceId(materialNamespace) : () -> null;
}
}
@ -258,9 +259,14 @@ public final class Registry {
public @Nullable Material material() {
return materialSupplier.get();
}
@Override
public Properties custom() {
return custom;
}
}
public static class MaterialEntry extends Entry {
public static final class MaterialEntry implements Entry {
private final NamespaceID namespace;
private final int id;
private final String translationKey;
@ -269,25 +275,25 @@ public final class Registry {
private final boolean isFood;
private final Supplier<Block> blockSupplier;
private final EquipmentSlot equipmentSlot;
private final Properties custom;
private MaterialEntry(String namespace, Map<String, Object> main, Map<String, Object> override) {
super(main, override);
private MaterialEntry(String namespace, Properties main, Properties custom) {
this.custom = custom;
this.namespace = NamespaceID.from(namespace);
this.id = getInt("id");
this.translationKey = getString("translationKey");
this.maxStackSize = getInt("maxStackSize", 64);
this.maxDamage = getInt("maxDamage", 0);
this.isFood = getBoolean("edible", false);
this.id = main.getInt("id");
this.translationKey = main.getString("translationKey");
this.maxStackSize = main.getInt("maxStackSize", 64);
this.maxDamage = main.getInt("maxDamage", 0);
this.isFood = main.getBoolean("edible", false);
{
final String blockNamespace = getString("correspondingBlock", null);
final String blockNamespace = main.getString("correspondingBlock", null);
this.blockSupplier = blockNamespace != null ? () -> Block.fromNamespaceId(blockNamespace) : () -> null;
}
{
final Map<String, Object> armorProperties = element("armorProperties");
final Properties armorProperties = main.section("armorProperties");
if (armorProperties != null) {
final String slot = (String) armorProperties.get("slot");
switch (slot) {
switch (armorProperties.getString("slot")) {
case "feet" -> this.equipmentSlot = EquipmentSlot.BOOTS;
case "legs" -> this.equipmentSlot = EquipmentSlot.LEGGINGS;
case "chest" -> this.equipmentSlot = EquipmentSlot.CHESTPLATE;
@ -335,206 +341,71 @@ public final class Registry {
public @Nullable EquipmentSlot equipmentSlot() {
return equipmentSlot;
}
}
public static class EntityEntry extends Entry {
private final NamespaceID namespace;
private final int id;
private final String translationKey;
private final double width;
private final double height;
private final double drag;
private final double acceleration;
private final EntitySpawnType spawnType;
private EntityEntry(String namespace, Map<String, Object> main, Map<String, Object> override) {
super(main, override);
this.namespace = NamespaceID.from(namespace);
this.id = getInt("id");
this.translationKey = getString("translationKey");
this.width = getDouble("width");
this.height = getDouble("height");
this.drag = getDouble("drag", 0.02);
this.acceleration = getDouble("acceleration", 0.08);
this.spawnType = EntitySpawnType.valueOf(getString("packetType").toUpperCase(Locale.ROOT));
}
public @NotNull NamespaceID namespace() {
return namespace;
}
public int id() {
return id;
}
public String translationKey() {
return translationKey;
}
public double width() {
return width;
}
public double height() {
return height;
}
public double drag() {
return drag;
}
public double acceleration() {
return acceleration;
}
public EntitySpawnType spawnType() {
return spawnType;
@Override
public Properties custom() {
return custom;
}
}
public static class EnchantmentEntry extends Entry {
private final NamespaceID namespace;
private final int id;
private final String translationKey;
private final double maxLevel;
private final boolean isCursed;
private final boolean isDiscoverable;
private final boolean isTradeable;
private final boolean isTreasureOnly;
private EnchantmentEntry(String namespace, Map<String, Object> main, Map<String, Object> override) {
super(main, override);
this.namespace = NamespaceID.from(namespace);
this.id = getInt("id");
this.translationKey = getString("translationKey");
this.maxLevel = getDouble("maxLevel");
this.isCursed = getBoolean("curse", false);
this.isDiscoverable = getBoolean("discoverable", true);
this.isTradeable = getBoolean("tradeable", true);
this.isTreasureOnly = getBoolean("treasureOnly", false);
}
public @NotNull NamespaceID namespace() {
return namespace;
}
public int id() {
return id;
}
public String translationKey() {
return translationKey;
}
public double maxLevel() {
return maxLevel;
}
public boolean isCursed() {
return isCursed;
}
public boolean isDiscoverable() {
return isDiscoverable;
}
public boolean isTradeable() {
return isTradeable;
}
public boolean isTreasureOnly() {
return isTreasureOnly;
public record EntityEntry(NamespaceID namespace, int id,
String translationKey,
double width, double height,
double drag, double acceleration,
EntitySpawnType spawnType,
Properties custom) implements Entry {
public EntityEntry(String namespace, Properties main, Properties custom) {
this(NamespaceID.from(namespace),
main.getInt("id"),
main.getString("translationKey"),
main.getDouble("width"),
main.getDouble("height"),
main.getDouble("drag", 0.02),
main.getDouble("acceleration", 0.08),
EntitySpawnType.valueOf(main.getString("packetType").toUpperCase(Locale.ROOT)),
custom);
}
}
public static class PotionEffectEntry extends Entry {
private final NamespaceID namespace;
private final int id;
private final String translationKey;
private final int color;
private final boolean isInstantaneous;
private PotionEffectEntry(String namespace, Map<String, Object> main, Map<String, Object> override) {
super(main, override);
this.namespace = NamespaceID.from(namespace);
this.id = getInt("id");
this.translationKey = getString("translationKey");
this.color = getInt("color");
this.isInstantaneous = getBoolean("instantaneous");
}
public @NotNull NamespaceID namespace() {
return namespace;
}
public int id() {
return id;
}
public String translationKey() {
return translationKey;
}
public int color() {
return color;
}
public boolean isInstantaneous() {
return isInstantaneous;
public record EnchantmentEntry(NamespaceID namespace, int id,
String translationKey,
double maxLevel,
boolean isCursed,
boolean isDiscoverable,
boolean isTradeable,
boolean isTreasureOnly,
Properties custom) implements Entry {
public EnchantmentEntry(String namespace, Properties main, Properties custom) {
this(NamespaceID.from(namespace),
main.getInt("id"),
main.getString("translationKey"),
main.getDouble("maxLevel"),
main.getBoolean("isCursed", false),
main.getBoolean("isDiscoverable", true),
main.getBoolean("isTradeable", true),
main.getBoolean("isTreasureOnly", false),
custom);
}
}
public static class Entry {
private final Map<String, Object> main, override;
private Entry(Map<String, Object> main, Map<String, Object> override) {
this.main = main;
this.override = override;
public record PotionEffectEntry(NamespaceID namespace, int id,
String translationKey,
int color,
boolean isInstantaneous,
Properties custom) implements Entry {
public PotionEffectEntry(String namespace, Properties main, Properties custom) {
this(NamespaceID.from(namespace),
main.getInt("id"),
main.getString("translationKey"),
main.getInt("color"),
main.getBoolean("instant"),
custom);
}
}
public String getString(String name, String defaultValue) {
var element = element(name);
return element != null ? (String) element : defaultValue;
}
public String getString(String name) {
return element(name);
}
public double getDouble(String name, double defaultValue) {
var element = element(name);
return element != null ? ((Number) element).doubleValue() : defaultValue;
}
public double getDouble(String name) {
return ((Number) element(name)).doubleValue();
}
public int getInt(String name, int defaultValue) {
var element = element(name);
return element != null ? ((Number) element).intValue() : defaultValue;
}
public int getInt(String name) {
return ((Number) element(name)).intValue();
}
public boolean getBoolean(String name, boolean defaultValue) {
var element = element(name);
return element != null ? (boolean) element : defaultValue;
}
public boolean getBoolean(String name) {
return element(name);
}
protected <T> T element(String name) {
Object result;
if (override != null && (result = override.get(name)) != null) {
return (T) result;
}
return (T) main.get(name);
}
public interface Entry {
@ApiStatus.Experimental
Properties custom();
}
private static Object readObject(JsonReader reader) throws IOException {
@ -544,19 +415,117 @@ public final class Registry {
reader.beginArray();
while (reader.hasNext()) list.add(readObject(reader));
reader.endArray();
yield List.copyOf(list);
yield list;
}
case BEGIN_OBJECT -> {
Map<String, Object> map = new HashMap<>();
reader.beginObject();
while (reader.hasNext()) map.put(reader.nextName().intern(), readObject(reader));
while (reader.hasNext()) map.put(reader.nextName(), readObject(reader));
reader.endObject();
yield Map.copyOf(map);
yield map;
}
case STRING -> reader.nextString().intern();
case STRING -> reader.nextString();
case NUMBER -> ToNumberPolicy.LONG_OR_DOUBLE.readNumber(reader);
case BOOLEAN -> reader.nextBoolean();
default -> throw new IllegalStateException("Invalid peek: " + reader.peek());
};
}
record PropertiesMap(Map<String, Object> map) implements Properties {
@Override
public String getString(String name, String defaultValue) {
var element = element(name);
return element != null ? (String) element : defaultValue;
}
@Override
public String getString(String name) {
return element(name);
}
@Override
public double getDouble(String name, double defaultValue) {
var element = element(name);
return element != null ? ((Number) element).doubleValue() : defaultValue;
}
@Override
public double getDouble(String name) {
return ((Number) element(name)).doubleValue();
}
@Override
public int getInt(String name, int defaultValue) {
var element = element(name);
return element != null ? ((Number) element).intValue() : defaultValue;
}
@Override
public int getInt(String name) {
return ((Number) element(name)).intValue();
}
@Override
public boolean getBoolean(String name, boolean defaultValue) {
var element = element(name);
return element != null ? (boolean) element : defaultValue;
}
@Override
public boolean getBoolean(String name) {
return element(name);
}
@Override
public Properties section(String name) {
Map<String, Object> map = element(name);
if (map == null) return null;
return new PropertiesMap(map);
}
@Override
public Map<String, Object> asMap() {
return map;
}
private <T> T element(String name) {
//noinspection unchecked
return (T) map.get(name);
}
}
public interface Properties extends Iterable<Map.Entry<String, Object>> {
static Properties fromMap(Map<String, Object> map) {
return new PropertiesMap(map);
}
String getString(String name, String defaultValue);
String getString(String name);
double getDouble(String name, double defaultValue);
double getDouble(String name);
int getInt(String name, int defaultValue);
int getInt(String name);
boolean getBoolean(String name, boolean defaultValue);
boolean getBoolean(String name);
Properties section(String name);
Map<String, Object> asMap();
@Override
default @NotNull Iterator<Map.Entry<String, Object>> iterator() {
return asMap().entrySet().iterator();
}
default int size() {
return asMap().size();
}
}
}

View File

@ -8,7 +8,7 @@ import java.util.Collection;
record SoundEventImpl(NamespaceID namespace, int id) implements SoundEvent {
private static final Registry.Container<SoundEvent> CONTAINER = Registry.createContainer(Registry.Resource.SOUNDS,
(namespace, object) -> new SoundEventImpl(NamespaceID.from(namespace), ((Number) object.get("id")).intValue()));
(namespace, properties) -> new SoundEventImpl(NamespaceID.from(namespace), properties.getInt("id")));
static SoundEvent get(@NotNull String namespace) {
return CONTAINER.get(namespace);

View File

@ -8,7 +8,7 @@ import java.util.Collection;
record StatisticTypeImpl(NamespaceID namespace, int id) implements StatisticType {
private static final Registry.Container<StatisticType> CONTAINER = Registry.createContainer(Registry.Resource.STATISTICS,
(namespace, object) -> new StatisticTypeImpl(NamespaceID.from(namespace), ((Number) object.get("id")).intValue()));
(namespace, properties) -> new StatisticTypeImpl(NamespaceID.from(namespace), properties.getInt("id")));
static StatisticType get(@NotNull String namespace) {
return CONTAINER.get(namespace);

View File

@ -0,0 +1,90 @@
package net.minestom.server.utils.collection;
import org.jetbrains.annotations.ApiStatus;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
@ApiStatus.Internal
public final class MergedMap<K, V> extends AbstractMap<K, V> {
private final Map<K, V> first, second;
public MergedMap(Map<K, V> first, Map<K, V> second) {
this.first = Objects.requireNonNull(first);
this.second = Objects.requireNonNull(second);
}
// mandatory methods
final Set<Entry<K, V>> entrySet = new AbstractSet<>() {
@Override
public Iterator<Map.Entry<K, V>> iterator() {
return stream().iterator();
}
@Override
public int size() {
return (int) stream().count();
}
@Override
public Stream<Entry<K, V>> stream() {
return Stream.concat(first.entrySet().stream(), secondStream())
.map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey(), e.getValue()));
}
@Override
public Stream<Entry<K, V>> parallelStream() {
return stream().parallel();
}
@Override
public Spliterator<Entry<K, V>> spliterator() {
return stream().spliterator();
}
};
Stream<Entry<K, V>> secondStream() {
return second.entrySet().stream().filter(e -> !first.containsKey(e.getKey()));
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
return entrySet;
}
// optimizations
@Override
public boolean containsKey(Object key) {
return first.containsKey(key) || second.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return first.containsValue(value) ||
secondStream().anyMatch(Predicate.isEqual(value));
}
@Override
public V get(Object key) {
V v = first.get(key);
return v != null ? v : second.get(key);
}
@Override
public V getOrDefault(Object key, V defaultValue) {
V v = first.get(key);
return v != null ? v : second.getOrDefault(key, defaultValue);
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
first.forEach(action);
second.forEach((k, v) -> {
if (!first.containsKey(k)) action.accept(k, v);
});
}
}