/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the Software), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.lanternpowered.server.inventory; import static com.google.common.base.Preconditions.checkNotNull; import static org.lanternpowered.server.text.translation.TranslationHelper.tr; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import org.lanternpowered.server.game.Lantern; import org.spongepowered.api.effect.Viewer; import org.spongepowered.api.item.ItemType; import org.spongepowered.api.item.inventory.Inventory; import org.spongepowered.api.item.inventory.InventoryArchetype; import org.spongepowered.api.item.inventory.InventoryProperty; import org.spongepowered.api.item.inventory.ItemStack; import org.spongepowered.api.item.inventory.Slot; import org.spongepowered.api.item.inventory.property.InventoryCapacity; import org.spongepowered.api.item.inventory.property.InventoryTitle; import org.spongepowered.api.item.inventory.transaction.InventoryTransactionResult; import org.spongepowered.api.plugin.PluginContainer; import org.spongepowered.api.text.Text; import org.spongepowered.api.text.translation.Translation; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; public abstract class AbstractMutableInventory extends AbstractInventory { static class NameHolder { static final Translation EMPTY = tr("inventory.empty.title"); static final Translation DEFAULT = tr("inventory.name"); } private final LanternEmptyInventory emptyInventory = new LanternEmptyInventory(this); /** * The plugin container that created this {@link AbstractMutableInventory}. */ @Nullable private PluginContainer pluginContainer; @Nullable private final Inventory parent; @Nullable private Translation name; /** * All the {@link InventoryProperty}s of this inventory mapped by their type. */ private final Multimap<Class<?>, InventoryProperty<?,?>> inventoryPropertiesByClass = HashMultimap.create(); /** * All the {@link InventoryProperty}s of this inventory mapped by their key. */ private final Map<InventoryPropertyKey, InventoryProperty<?,?>> inventoryPropertiesByKey = new HashMap<>(); private final Set<ContainerViewListener> viewerListeners = new HashSet<>(); public AbstractMutableInventory(@Nullable Inventory parent, @Nullable Translation name) { this.parent = parent; this.name = name; } void setName(@Nullable Translation name) { this.name = name; } void setPluginContainer(@Nullable PluginContainer pluginContainer) { this.pluginContainer = pluginContainer; } protected void finalizeContent() { } /** * Registers a {@link InventoryProperty} for this inventory. * * @param inventoryProperty The inventory property */ protected void registerProperty(InventoryProperty<?, ?> inventoryProperty) { checkNotNull(inventoryProperty, "inventoryProperty"); this.inventoryPropertiesByClass.put(inventoryProperty.getClass(), inventoryProperty); final InventoryPropertyKey propertyKey = new InventoryPropertyKey(inventoryProperty.getClass(), inventoryProperty.getKey()); this.inventoryPropertiesByKey.put(propertyKey, inventoryProperty); } @Override protected LanternEmptyInventory empty() { return this.emptyInventory; } @Override public PluginContainer getPlugin() { return this.pluginContainer == null ? Lantern.getMinecraftPlugin() : this.pluginContainer; } @Override public InventoryArchetype getArchetype() { return null; // TODO } @Override public InventoryTransactionResult offer(ItemStack stack) { return this.offerFast(stack).asTransactionResult(); } @Override public AbstractInventory parent() { return this.parent == null ? this : (AbstractInventory) this.parent; } @Override public Translation getName() { return this.name == null ? NameHolder.DEFAULT : this.name; } @Override public boolean hasProperty(Class<? extends InventoryProperty<?, ?>> property) { if (this.inventoryPropertiesByClass.containsKey(checkNotNull(property, "property"))) { return true; } return super.hasProperty(property); } @Override public boolean hasProperty(InventoryProperty<?, ?> property) { checkNotNull(property, "property"); final InventoryPropertyKey propertyKey = new InventoryPropertyKey(property.getClass(), property.getKey()); final InventoryProperty property1 = this.inventoryPropertiesByKey.get(propertyKey); if (property1 != null && property1.equals(property)) { return true; } return super.hasProperty(property); } @Override public boolean hasProperty(Inventory child, InventoryProperty<?,?> property) { checkNotNull(property, "property"); if (child instanceof AbstractMutableInventory) { final InventoryPropertyKey propertyKey = new InventoryPropertyKey(property.getClass(), property.getKey()); final InventoryProperty property1 = ((AbstractMutableInventory) child).inventoryPropertiesByKey.get(propertyKey); if (property1 != null && property1.equals(property)) { return true; } } return super.hasProperty(child, property); } @Override <T extends InventoryProperty<?, ?>> ImmutableList.Builder<T> getPropertiesBuilder(Class<T> property) { final ImmutableList.Builder<T> builder = super.getPropertiesBuilder(property); //noinspection unchecked builder.addAll((Collection<? extends T>) this.inventoryPropertiesByClass.get(property)); return builder; } @Override <T extends InventoryProperty<?, ?>> ImmutableList.Builder<T> getPropertiesBuilder(Inventory child, Class<T> property) { final ImmutableList.Builder<T> builder = super.getPropertiesBuilder(child, property); if (child instanceof AbstractMutableInventory) { //noinspection unchecked builder.addAll((Collection<? extends T>) ((AbstractMutableInventory) child).inventoryPropertiesByClass.get(property)); } return builder; } @Override public <T extends InventoryProperty<?, ?>> Optional<T> getProperty(Class<T> property, @Nullable Object key) { checkNotNull(property, "property"); final InventoryPropertyKey propertyKey = new InventoryPropertyKey(property, key); final InventoryProperty<?, ?> property1 = this.inventoryPropertiesByKey.get(propertyKey); if (property1 != null && property.isInstance(property1)) { return Optional.of(property.cast(property1)); } return super.getProperty(property, key); } @Override public <T extends InventoryProperty<?, ?>> Optional<T> getProperty(Inventory child, Class<T> property, @Nullable Object key) { checkNotNull(child, "child"); checkNotNull(property, "property"); final InventoryPropertyKey propertyKey = new InventoryPropertyKey(property, key); if (child instanceof AbstractMutableInventory) { final InventoryProperty<?, ?> property1 = ((AbstractMutableInventory) child).inventoryPropertiesByKey.get(propertyKey); if (property1 != null && property.isInstance(property1)) { return Optional.of(property.cast(property1)); } } return super.getProperty(child, property, key); } protected <T extends InventoryProperty<?, ?>> Optional<T> tryGetProperty(Class<T> property, @Nullable Object key) { if (property == InventoryTitle.class) { //noinspection unchecked return Optional.of((T) new InventoryTitle(Text.of(this.getName()))); } else if (property == InventoryCapacity.class) { //noinspection unchecked return Optional.of((T) new InventoryCapacity(this.capacity())); } return Optional.empty(); } protected <T extends InventoryProperty<?, ?>> List<T> tryGetProperties(Class<T> property) { final List<T> properties = new ArrayList<>(); if (property == InventoryTitle.class) { //noinspection unchecked properties.add((T) new InventoryTitle(Text.of(this.getName()))); } else if (property == InventoryCapacity.class) { //noinspection unchecked properties.add((T) new InventoryCapacity(this.capacity())); } return properties; } protected <T extends InventoryProperty<?, ?>> Optional<T> tryGetProperty(Inventory child, Class<T> property, @Nullable Object key) { return Optional.empty(); } protected <T extends InventoryProperty<?, ?>> List<T> tryGetProperties(Inventory child, Class<T> property) { return new ArrayList<>(); } @SuppressWarnings("unchecked") @Override public <T extends Inventory> T query(ItemType... types) { checkNotNull(types, "types"); return this.query(inventory -> { // Slots are leaf nodes so only check if they contain // the item if (inventory instanceof Slot) { for (ItemType type : types) { if (inventory.contains(type)) { return true; } } } return false; }, true); } @Override public <T extends Inventory> T query(Class<?>... types) { checkNotNull(types, "types"); return this.query(inventory -> { for (Class<?> type : types) { if (type.isInstance(inventory)) { return true; } } return false; }, false); } @SuppressWarnings("unchecked") @Override public <T extends Inventory> T query(ItemStack... types) { checkNotNull(types, "types"); return this.query(inventory -> { // Slots are leaf nodes so only check if they contain // the item if (inventory instanceof Slot) { for (ItemStack type : types) { if (inventory.contains(type)) { return true; } } } return false; }, true); } @SuppressWarnings("unchecked") @Override public <T extends Inventory> T query(InventoryProperty<?, ?>... props) { checkNotNull(props, "props"); return this.query(inventory -> { for (InventoryProperty<?,?> prop : props) { if (((AbstractInventory) inventory).hasProperty(prop)) { return true; } } return false; }, false); } @SuppressWarnings("unchecked") @Override public <T extends Inventory> T query(Translation... names) { checkNotNull(names, "names"); return this.query(inventory -> { for (Translation name : names) { if (inventory.getName().equals(name)) { return true; } } return false; }, false); } @Override public <T extends Inventory> T query(String... names) { checkNotNull(names, "names"); return this.query(inventory -> { String plainName = inventory.getName().get(); for (String name : names) { if (plainName.equals(name)) { return true; } } return false; }, false); } @SuppressWarnings("unchecked") @Override public <T extends Inventory> T query(Object... args) { checkNotNull(args, "args"); // This madness, trying to // cover all the cases return this.query(inventory -> { for (Object arg : args) { if (arg instanceof Inventory) { if (inventory.equals(arg)) { return true; } } else if (arg instanceof InventoryArchetype) { if (inventory.getArchetype().equals(arg)) { return true; } } else if (arg instanceof ItemStack) { if (inventory.contains((ItemStack) arg)) { return true; } } else if (arg instanceof Translation) { if (inventory.getName().equals(arg)) { return true; } } else if (arg instanceof ItemType) { if (inventory.contains((ItemType) arg)) { return true; } } else if (arg instanceof InventoryProperty<?,?>) { if (((AbstractInventory) inventory).hasProperty((InventoryProperty<?, ?>) arg)) { return true; } } else if (arg instanceof Class<?>) { final Class<?> clazz = (Class<?>) arg; if (InventoryProperty.class.isAssignableFrom(clazz)) { if (((AbstractInventory) inventory).hasProperty((Class<? extends InventoryProperty<?, ?>>) clazz)) { return true; } } else if (Inventory.class.isAssignableFrom(clazz)) { if (clazz.isInstance(inventory)) { return true; } } } } return false; }, false); } @Override public void add(ContainerViewListener listener) { checkNotNull(listener, "listener"); this.viewerListeners.add(listener); } @Override protected void addViewer(Viewer viewer, LanternContainer container) { final Iterator<ContainerViewListener> it = this.viewerListeners.iterator(); while (it.hasNext()) { final ContainerViewListener listener = it.next(); if (listener.onViewerAdded(viewer, container) == ContainerViewListener.Result.REMOVE_LISTENER) { it.remove(); } } } @Override protected void removeViewer(Viewer viewer, LanternContainer container) { final Iterator<ContainerViewListener> it = this.viewerListeners.iterator(); while (it.hasNext()) { final ContainerViewListener listener = it.next(); if (listener.onViewerRemoved(viewer, container) == ContainerViewListener.Result.REMOVE_LISTENER) { it.remove(); } } } }