Fixes some errors with HeadGetter (#1569)

* Add getKey and getValue for Pair calls for nicer access.

* Fixes ConcurrentModificationException in HeadGetter.
Fixes an issue when elements with the same name were overwritten by HeadGetter.
This commit is contained in:
BONNe 2020-11-08 21:23:24 +02:00 committed by GitHub
parent 1d4fd435a9
commit 3581537537
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 44 additions and 35 deletions

View File

@ -2,17 +2,13 @@ package world.bentobox.bentobox.api.panels;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.OptionalInt;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.api.user.User;
@ -190,18 +186,15 @@ public class Panel implements HeadRequester, InventoryHolder {
@Override
public void setHead(PanelItem item) {
// Update the panel item
// Replace the item in the item list if the name is the same
items = items.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> (item.getName().equals(e.getValue().getName()) ? item : e.getValue())));
// Replace the inventory slot item
for (int i = 0; i < inventory.getSize(); i++) {
ItemStack it = inventory.getItem(i);
if (it != null && it.getType().equals(Material.PLAYER_HEAD)) {
ItemMeta meta = it.getItemMeta();
if (meta != null && ChatColor.stripColor(item.getName()).equals(ChatColor.stripColor(meta.getLocalizedName()))) {
inventory.setItem(i, item.getItem());
}
}
// Find panel item index in items and replace it once more in inventory to update it.
OptionalInt index = this.items.entrySet().stream().
filter(entry -> entry.getValue() == item).
mapToInt(Map.Entry::getKey).findFirst();
if (index.isPresent()) {
// Update item inside inventory to change icon only if item is inside panel.
this.inventory.setItem(index.getAsInt(), item.getItem());
}
}

View File

@ -17,6 +17,25 @@ public class Pair<X, Z> {
this.z = z;
}
/**
* Returns X element as key.
* @return X element
*/
public X getKey() {
return x;
}
/**
* Returns Z element as value.
* @return Z element
*/
public Z getValue() {
return z;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/

View File

@ -9,11 +9,11 @@ import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.util.Pair;
/**
@ -36,7 +37,7 @@ public class HeadGetter {
/**
* Local cache for storing requested names and items which must be updated.
*/
private static final Map<String, PanelItem> names = new HashMap<>();
private static final Queue<Pair<String, PanelItem>> names = new LinkedBlockingQueue<>();
/**
* Requesters of player heads.
@ -89,7 +90,7 @@ public class HeadGetter {
// Get the name
headRequesters.computeIfAbsent(panelItem.getPlayerHeadName(), k -> new HashSet<>()).
add(requester);
names.put(panelItem.getPlayerHeadName(), panelItem);
names.add(new Pair<>(panelItem.getPlayerHeadName(), panelItem));
}
}
@ -117,21 +118,19 @@ public class HeadGetter {
*/
private void runPlayerHeadGetter() {
Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> {
synchronized (names)
synchronized (HeadGetter.names)
{
Iterator<Entry<String, PanelItem>> it = names.entrySet().iterator();
if (it.hasNext())
if (!HeadGetter.names.isEmpty())
{
Entry<String, PanelItem> elementEntry = it.next();
Pair<String, PanelItem> elementEntry = HeadGetter.names.poll();
// TODO: In theory BentoBox could use User instance to find existing user UUID's.
// It would avoid one API call.
final String userName = elementEntry.getKey();
// Use cached userId as userId will not change :)
UUID userId = cachedHeads.containsKey(userName) ?
cachedHeads.get(userName).getUserId() :
UUID userId = HeadGetter.cachedHeads.containsKey(userName) ?
HeadGetter.cachedHeads.get(userName).getUserId() :
HeadGetter.getUserIdFromName(userName);
// Create new cache object.
@ -140,24 +139,22 @@ public class HeadGetter {
HeadGetter.getTextureFromUUID(userId));
// Save in cache
cachedHeads.put(userName, cache);
HeadGetter.cachedHeads.put(userName, cache);
// Tell requesters the head came in
if (headRequesters.containsKey(userName))
if (HeadGetter.headRequesters.containsKey(userName))
{
for (HeadRequester req : headRequesters.get(userName))
for (HeadRequester req : HeadGetter.headRequesters.get(userName))
{
elementEntry.getValue().setHead(cache.getPlayerHead());
Bukkit.getServer().getScheduler().runTaskAsynchronously(plugin,
Bukkit.getServer().getScheduler().runTaskAsynchronously(this.plugin,
() -> req.setHead(elementEntry.getValue()));
}
}
it.remove();
}
}
}, 0L, 20L);
}, 0L, 10L);
}