diff --git a/Essentials/src/com/earth2me/essentials/api/IItemDb.java b/Essentials/src/com/earth2me/essentials/api/IItemDb.java index 44f75bd32..7df534ec8 100644 --- a/Essentials/src/com/earth2me/essentials/api/IItemDb.java +++ b/Essentials/src/com/earth2me/essentials/api/IItemDb.java @@ -33,6 +33,9 @@ public interface IItemDb { /** * Create a stack from the given name with the maximum stack size for that material. * + * Note that this will always check against resolver functions from other plugins as well. + * To avoid this behaviour, use net.ess3.api.IItemDb#get(String name, boolean useResolvers). + * * @param name Item name to look up in the database * @return The requested item stack with the maximum stack size * @throws Exception if the item stack cannot be created diff --git a/Essentials/src/com/earth2me/essentials/items/AbstractItemDb.java b/Essentials/src/com/earth2me/essentials/items/AbstractItemDb.java index 867d3ae13..65672215d 100644 --- a/Essentials/src/com/earth2me/essentials/items/AbstractItemDb.java +++ b/Essentials/src/com/earth2me/essentials/items/AbstractItemDb.java @@ -1,34 +1,114 @@ package com.earth2me.essentials.items; -import com.earth2me.essentials.Essentials; import com.earth2me.essentials.IConf; import com.earth2me.essentials.User; import com.earth2me.essentials.utils.MaterialUtil; -import com.earth2me.essentials.utils.StringUtil; import com.earth2me.essentials.utils.VersionUtil; -import org.bukkit.Color; -import org.bukkit.DyeColor; -import org.bukkit.FireworkEffect; -import org.bukkit.Material; +import net.ess3.api.IEssentials; +import net.ess3.api.PluginKey; +import org.bukkit.*; import org.bukkit.block.Banner; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.*; +import org.bukkit.plugin.Plugin; import org.bukkit.potion.Potion; import org.bukkit.potion.PotionEffect; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Set; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; import static com.earth2me.essentials.I18n.tl; public abstract class AbstractItemDb implements IConf, net.ess3.api.IItemDb { + protected final IEssentials ess; protected boolean ready = false; + private Map resolverMap = new HashMap<>(); + + AbstractItemDb(IEssentials ess) { + this.ess = ess; + } + + @Override + public void registerResolver(Plugin plugin, String name, ItemResolver resolver) throws Exception { + PluginKey key = PluginKey.fromKey(plugin, name); + if (resolverMap.containsKey(key)) { + throw new Exception("Tried to add a duplicate resolver with name " + key.toString()); + } + + resolverMap.put(key, resolver); + } + + @Override + public void unregisterResolver(Plugin plugin, String name) throws Exception { + PluginKey key = PluginKey.fromKey(plugin, name); + if (!resolverMap.containsKey(key)) { + throw new Exception("Tried to remove nonexistent resolver with name " + key.toString()); + } + + resolverMap.remove(key); + } + + @Override + public boolean isResolverPresent(Plugin plugin, String name) { + return resolverMap.containsKey(PluginKey.fromKey(plugin, name)); + } + + @Override + public Map getResolvers() { + return new HashMap<>(resolverMap); + } + + @Override + public Map getResolvers(Plugin plugin) { + Map matchingResolvers = new HashMap<>(); + for (PluginKey key : resolverMap.keySet()) { + if (key.getPlugin().equals(plugin)) { + matchingResolvers.put(key, resolverMap.get(key)); + } + } + + return matchingResolvers; + } + + @Override + public ItemResolver getResolver(Plugin plugin, String name) { + return resolverMap.get(PluginKey.fromKey(plugin, name)); + } + + @Override + public ItemStack get(String id) throws Exception { + return get(id, true); + } + + ItemStack tryResolvers(String id) { + for (PluginKey key : resolverMap.keySet()) { + if (ess.getSettings().isDebug()) { + ess.getLogger().info(String.format("Trying resolver '%s' for item '%s'...", key, id)); + } + + Function resolver = resolverMap.get(key); + ItemStack stack = resolver.apply(id); + + if (stack != null) { + return stack; + } + } + + return null; + } + + Collection getResolverNames() { + return resolverMap.values().stream() + .map(ItemResolver::getNames) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + @Override public List getMatching(User user, String[] args) throws Exception { List is = new ArrayList<>(); diff --git a/Essentials/src/com/earth2me/essentials/items/FlatItemDb.java b/Essentials/src/com/earth2me/essentials/items/FlatItemDb.java index b02d6f9a7..2ec81254a 100644 --- a/Essentials/src/com/earth2me/essentials/items/FlatItemDb.java +++ b/Essentials/src/com/earth2me/essentials/items/FlatItemDb.java @@ -25,7 +25,7 @@ import static com.earth2me.essentials.I18n.tl; public class FlatItemDb extends AbstractItemDb { protected static final Logger LOGGER = Logger.getLogger("Essentials"); private static Gson gson = new Gson(); - private final transient IEssentials ess; + // Maps primary name to ItemData private final transient Map items = new HashMap<>(); @@ -38,7 +38,7 @@ public class FlatItemDb extends AbstractItemDb { private transient ManagedFile file = null; public FlatItemDb(final IEssentials ess) { - this.ess = ess; + super(ess); } @Override @@ -99,7 +99,14 @@ public class FlatItemDb extends AbstractItemDb { } @Override - public ItemStack get(String id) throws Exception { + public ItemStack get(String id, boolean useResolvers) throws Exception { + if (useResolvers) { + ItemStack resolved = tryResolvers(id); + if (resolved != null) { + return resolved; + } + } + id = id.toLowerCase(); final String[] split = id.split(":"); @@ -204,7 +211,9 @@ public class FlatItemDb extends AbstractItemDb { @Override public Collection listNames() { - return Collections.unmodifiableSet(allAliases); + Set names = new HashSet<>(allAliases); + names.addAll(getResolverNames()); + return names; } public static class ItemData { diff --git a/Essentials/src/com/earth2me/essentials/items/LegacyItemDb.java b/Essentials/src/com/earth2me/essentials/items/LegacyItemDb.java index 6f84c8104..2d8c82f4a 100644 --- a/Essentials/src/com/earth2me/essentials/items/LegacyItemDb.java +++ b/Essentials/src/com/earth2me/essentials/items/LegacyItemDb.java @@ -20,7 +20,6 @@ import static com.earth2me.essentials.I18n.tl; public class LegacyItemDb extends AbstractItemDb { protected static final Logger LOGGER = Logger.getLogger("Essentials"); - private final transient IEssentials ess; private final transient Map items = new HashMap<>(); private final transient Map> names = new HashMap<>(); private final transient Map primaryName = new HashMap<>(); @@ -32,7 +31,7 @@ public class LegacyItemDb extends AbstractItemDb { private final transient Pattern csvSplitPattern = Pattern.compile("(\"([^\"]*)\"|[^,]*)(,|$)"); public LegacyItemDb(final IEssentials ess) { - this.ess = ess; + super(ess); file = new ManagedFile("items.csv", ess); } @@ -121,7 +120,14 @@ public class LegacyItemDb extends AbstractItemDb { } @Override - public ItemStack get(final String id) throws Exception { + public ItemStack get(final String id, final boolean useResolvers) throws Exception { + if (useResolvers) { + ItemStack resolved = tryResolvers(id); + if (resolved != null) { + return resolved; + } + } + int itemid = 0; String itemname; short metaData = 0; @@ -243,7 +249,9 @@ public class LegacyItemDb extends AbstractItemDb { @Override public Collection listNames() { - return primaryName.values(); + Collection values = primaryName.values(); + values.addAll(getResolverNames()); + return values; } static class ItemData { diff --git a/Essentials/src/net/ess3/api/IItemDb.java b/Essentials/src/net/ess3/api/IItemDb.java index b400d1740..dddd29f59 100644 --- a/Essentials/src/net/ess3/api/IItemDb.java +++ b/Essentials/src/net/ess3/api/IItemDb.java @@ -1,6 +1,84 @@ package net.ess3.api; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; + +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; + public interface IItemDb extends com.earth2me.essentials.api.IItemDb { + /** + * Add an item resolver that is called before looking up the item in the item database. + * + * @param plugin The owning plugin + * @param name The name of the resolver + * @param resolver The resolver accepting a String and returning an ItemStack, or null if + * none was found + * @throws Exception If a resolver with a conflicting name is found + */ + void registerResolver(Plugin plugin, String name, ItemResolver resolver) throws Exception; + + /** + * Remove an item resolver from the given plugin with the given name. + * + * @param plugin The owning plugin + * @param name The name of the resolver + * @throws Exception If no matching resolver was found + */ + void unregisterResolver(Plugin plugin, String name) throws Exception; + + /** + * Check whether a resolver with a given name from a given plugin has been registered. + * + * @param plugin The owning plugin + * @param name The name of the resolver + * @return Whether the resolver could be found + */ + boolean isResolverPresent(Plugin plugin, String name); + + /** + * Get all registered resolvers. + * + * @return A map of all registered resolvers + */ + Map getResolvers(); + + /** + * Get all registered resolvers from the given plugin. + * + * @param plugin The owning plugin + * @return A map of all matching resolvers + */ + Map getResolvers(Plugin plugin); + + /** + * Get the resolver function with the given name from the given plugin. + * + * @param plugin The owning plugin + * @param name The name of the resolver + * @return The resolver function, or null if not found + */ + ItemResolver getResolver(Plugin plugin, String name); + + /** + * Create a stack from the given name with the maximum stack size for that material. + * + * @param name Item name to look up in the database + * @param useResolvers Whether to call other plugins' resolver functions before looking the + * item up in the database + * @return The requested item stack with the maximum stack size + * @throws Exception if the item stack cannot be created + */ + ItemStack get(String name, boolean useResolvers) throws Exception; + + @FunctionalInterface + interface ItemResolver extends Function { + default Collection getNames() { + return null; + } + } + } diff --git a/Essentials/src/net/ess3/api/PluginKey.java b/Essentials/src/net/ess3/api/PluginKey.java new file mode 100644 index 000000000..353485a2a --- /dev/null +++ b/Essentials/src/net/ess3/api/PluginKey.java @@ -0,0 +1,51 @@ +package net.ess3.api; + +import org.bukkit.plugin.Plugin; + +import java.util.Objects; +import java.util.UUID; + +public final class PluginKey { + private final Plugin plugin; + private final String key; + + private PluginKey(Plugin plugin, String key) { + this.plugin = plugin; + this.key = key; + } + + public Plugin getPlugin() { + return plugin; + } + + public String getKey() { + return key; + } + + @Override + public int hashCode() { + return Objects.hash(plugin, key); + } + + @Override + public String toString() { + return plugin.getName().toLowerCase() + ":" + key; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof PluginKey || o.getClass().getName().equals("org.bukkit.NamespacedKey"))) { + return false; + } + return this == o || this.toString().equals(o.toString()); + } + + public static PluginKey random(Plugin plugin) { + return new PluginKey(plugin, UUID.randomUUID().toString()); + } + + public static PluginKey fromKey(Plugin plugin, String key) { + return new PluginKey(plugin, key); + } + +}