/*
* 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Iterables;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.lanternpowered.server.inventory.slot.LanternSlot;
import org.spongepowered.api.effect.Viewer;
import org.spongepowered.api.item.ItemType;
import org.spongepowered.api.item.inventory.EmptyInventory;
import org.spongepowered.api.item.inventory.Inventory;
import org.spongepowered.api.item.inventory.ItemStack;
import org.spongepowered.api.item.inventory.Slot;
import org.spongepowered.api.item.inventory.transaction.InventoryTransactionResult;
import org.spongepowered.api.item.inventory.transaction.SlotTransaction;
import org.spongepowered.api.text.translation.Translation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import javax.annotation.Nullable;
public class AbstractChildrenInventory extends AbstractMutableInventory {
private final Object2IntMap<AbstractInventory> childrenIndexes = new Object2IntOpenHashMap<>();
/**
* All the children that are present in this {@link Inventory}.
*/
private final List<AbstractInventory> children;
public AbstractChildrenInventory(@Nullable Inventory parent, @Nullable Translation name) {
this(parent, name, new ArrayList<>());
}
public AbstractChildrenInventory(@Nullable Inventory parent, @Nullable Translation name, List<AbstractInventory> children) {
super(parent, name);
this.children = checkNotNull(children, "children");
}
@Override
AbstractInventory getChild(int index) {
return index < 0 || index >= this.children.size() ? empty() : this.children.get(index);
}
@Override
int getChildIndex(AbstractInventory inventory) {
return this.childrenIndexes.get(checkNotNull(inventory, "inventory"));
}
@Override
protected void finalizeContent() {
super.finalizeContent();
for (int i = 0; i < this.children.size(); i++) {
this.childrenIndexes.put(this.children.get(i), i);
}
}
/**
* Gets the children of the inventory.
*
* @return The children
*/
public List<AbstractInventory> getChildren() {
return Collections.unmodifiableList(this.children);
}
/**
* Registers a child {@link Inventory} for this inventory. {@link Slot}s
* cannot be added through this method.
*
* @param childInventory The child inventory
*/
protected <T extends Inventory> T registerChild(T childInventory) {
checkNotNull(childInventory, "childInventory");
final AbstractInventory childInventory1 = (AbstractInventory) childInventory;
checkArgument(!this.children.contains(childInventory1), "The child is already registered");
this.children.add(childInventory1);
return childInventory;
}
Iterable<LanternSlot> getSlotInventories() {
return Collections.emptyList();
}
/**
* Prioritizes a child {@link Inventory} to have a higher priority
* for {@link Inventory#poll}, {@link Inventory#offer(ItemStack)},
* ... functions. The child must be registered
*
* @param childInventory The child inventory
* @param <T> The child inventory type
* @return The child inventory
*/
protected <T extends Inventory> T prioritizeChild(T childInventory) {
checkNotNull(childInventory, "inventory");
final AbstractInventory childInventory1 = (AbstractInventory) childInventory;
checkArgument(this.children.contains(childInventory1), "The inventory is not registered");
if (this.children.size() == 1) {
return childInventory;
}
this.children.remove(childInventory1);
this.children.add(0, childInventory1);
return childInventory;
}
private FastOfferResult offerFast(ItemStack stack, List<Inventory> processed, boolean add) {
FastOfferResult offerResult = null;
// Loop through the slots
for (AbstractInventory inventory : this.children) {
if (!add && processed.contains(inventory)) {
continue;
}
offerResult = inventory.offerFast(stack);
if (offerResult.getRest() == null) {
return offerResult;
}
stack = offerResult.getRest();
if (add) {
processed.add(inventory);
}
}
if (offerResult == null) {
return new FastOfferResult(stack, false);
}
return offerResult;
}
@Override
public FastOfferResult offerFast(ItemStack stack) {
checkNotNull(stack, "stack");
final List<Inventory> processed = new ArrayList<>();
final Inventory inventory = query(stack);
if (inventory instanceof AbstractChildrenInventory) {
final FastOfferResult offerResult = ((AbstractChildrenInventory) inventory).offerFast(stack, processed, true);
if (offerResult.getRest() == null) {
return offerResult;
}
stack = offerResult.getRest();
}
return offerFast(stack, processed, false);
}
private PeekOfferTransactionsResult peekOfferFastTransactions(ItemStack stack, List<Inventory> processed, boolean add) {
PeekOfferTransactionsResult peekResult = null;
final List<SlotTransaction> transactions = new ArrayList<>();
// Loop through the slots
for (AbstractInventory inventory : this.children) {
if (!add && processed.contains(inventory)) {
continue;
}
peekResult = inventory.peekOfferFastTransactions(stack);
if (peekResult.getOfferResult().getRest() == null) {
peekResult.getTransactions().addAll(transactions);
return peekResult;
} else {
transactions.addAll(peekResult.getTransactions());
}
stack = peekResult.getOfferResult().getRest();
if (add) {
processed.add(inventory);
}
}
if (peekResult == null) {
return new PeekOfferTransactionsResult(transactions, new FastOfferResult(stack, false));
}
return new PeekOfferTransactionsResult(transactions, peekResult.getOfferResult());
}
@Override
public PeekOfferTransactionsResult peekOfferFastTransactions(ItemStack stack) {
checkNotNull(stack, "stack");
final PeekOfferTransactionsResult peekResult;
final List<Inventory> processed = new ArrayList<>();
final Inventory inventory = query(stack);
if (inventory instanceof AbstractChildrenInventory) {
peekResult = ((AbstractChildrenInventory) inventory).peekOfferFastTransactions(stack, processed, true);
if (peekResult.getOfferResult().getRest() == null) {
return peekResult;
}
stack = peekResult.getOfferResult().getRest();
} else {
peekResult = null;
}
final PeekOfferTransactionsResult peekResult1 = peekOfferFastTransactions(stack, processed, false);
if (peekResult != null) {
peekResult1.getTransactions().addAll(peekResult.getTransactions());
}
return peekResult1;
}
List<Inventory> queryInventories(Predicate<Inventory> matcher, boolean nested) {
int count = 0;
final List<Inventory> matches = new ArrayList<>();
for (Inventory inventory : this.children) {
count++;
if (matcher.test(inventory)) {
matches.add(inventory);
}
if (nested) {
Inventory inventory1 = ((AbstractInventory) inventory).query(matcher, true);
if (!(inventory1 instanceof EmptyInventory)) {
matches.add(inventory1);
}
} else if (inventory instanceof AbstractChildrenInventory) {
matches.addAll(((AbstractChildrenInventory) inventory).queryInventories(matcher, false));
}
}
// All the children were a match, we will return this
if (!nested && matches.size() == count) {
return Collections.singletonList(this);
}
return matches;
}
@SuppressWarnings("unchecked")
@Override
public <T extends Inventory> T query(Predicate<Inventory> matcher, boolean nested) {
checkNotNull(matcher, "matcher");
final List<Inventory> matches = queryInventories(matcher, nested);
if (matches.isEmpty()) {
return (T) empty();
}
return (T) new AbstractChildrenInventory(null, null, Collections.unmodifiableList((List) matches));
}
@SuppressWarnings("unchecked")
@Override
public <T extends Inventory> Iterable<T> slots() {
return (Iterable) Iterables.unmodifiableIterable(this.getSlotInventories());
}
@Override
public InventoryTransactionResult set(ItemStack stack) {
return InventoryTransactionResult.builder().type(InventoryTransactionResult.Type.FAILURE).reject(stack).build();
}
@Override
public void clear() {
// Clear all the sub inventories
this.iterator().forEachRemaining(Inventory::clear);
}
@Override
public int size() {
int size = 0;
for (Inventory inventory : this.children) {
size += inventory.size();
}
return size;
}
@Override
public int totalItems() {
int totalItems = 0;
for (Inventory inventory : this.children) {
totalItems += inventory.totalItems();
}
return totalItems;
}
@Override
public int capacity() {
int capacity = 0;
for (Inventory inventory : this.children) {
capacity += inventory.capacity();
}
return capacity;
}
@Override
public boolean hasChildren() {
return !this.children.isEmpty();
}
@Override
public int getMaxStackSize() {
return 64;
}
@Override
public void setMaxStackSize(int size) {
}
@SuppressWarnings("unchecked")
@Override
public Iterator<Inventory> iterator() {
return (Iterator) this.children.iterator();
}
@Override
public boolean contains(ItemStack stack) {
checkNotNull(stack, "stack");
// Loop through the inventories
for (AbstractInventory inventory : this.children) {
if (inventory.contains(stack)) {
return true;
}
}
return false;
}
@Override
public boolean contains(ItemType type) {
checkNotNull(type, "type");
// Loop through the inventories
for (AbstractInventory inventory : this.children) {
if (inventory.contains(type)) {
return true;
}
}
return false;
}
private static class ItemMatcher implements Predicate<ItemStack> {
private final ItemStack itemStack;
ItemMatcher(ItemStack itemStack) {
this.itemStack = itemStack;
}
@Override
public boolean test(ItemStack itemStack) {
return ((LanternItemStack) this.itemStack).isSimilar(itemStack);
}
}
@Override
public Optional<ItemStack> poll(Predicate<ItemStack> matcher) {
checkNotNull(matcher, "matcher");
// Loop through the children inventories
for (AbstractInventory inventory : this.children) {
final Optional<ItemStack> itemStack = inventory.poll(matcher);
if (itemStack.isPresent()) {
return itemStack;
}
}
return Optional.empty();
}
@Override
public Optional<ItemStack> poll(int limit, Predicate<ItemStack> matcher) {
checkNotNull(matcher, "matcher");
checkArgument(limit >= 0, "Limit may not be negative");
if (limit == 0) {
return Optional.empty();
}
ItemStack stack = null;
// Loop through the children inventories
for (AbstractInventory inventory : this.children) {
// Check whether the slot a item contains
if (stack == null) {
stack = inventory.poll(limit, matcher).orElse(null);
if (stack != null) {
if (stack.getQuantity() >= limit) {
return Optional.of(stack);
} else {
limit -= stack.getQuantity();
if (!(matcher instanceof ItemMatcher)) {
matcher = new ItemMatcher(stack);
}
}
}
} else {
final Optional<ItemStack> optItemStack = inventory.poll(limit, matcher);
if (optItemStack.isPresent()) {
final int stackSize = optItemStack.get().getQuantity();
limit -= stackSize;
stack.setQuantity(stack.getQuantity() + stackSize);
if (limit <= 0) {
return Optional.of(stack);
}
}
}
}
return Optional.ofNullable(stack);
}
@Override
public Optional<ItemStack> peek(Predicate<ItemStack> matcher) {
checkNotNull(matcher, "matcher");
// Loop through the children inventories
for (AbstractInventory inventory : this.children) {
final Optional<ItemStack> itemStack = inventory.peek(matcher);
if (itemStack.isPresent()) {
return itemStack;
}
}
return Optional.empty();
}
@Override
public Optional<PeekPollTransactionsResult> peekPollTransactions(Predicate<ItemStack> matcher) {
checkNotNull(matcher, "matcher");
// Loop through the children inventories
for (AbstractInventory inventory : this.children) {
final Optional<PeekPollTransactionsResult> peekResult = inventory.peekPollTransactions(matcher);
if (peekResult.isPresent()) {
return peekResult;
}
}
return Optional.empty();
}
@Override
public Optional<ItemStack> peek(int limit, Predicate<ItemStack> matcher) {
checkNotNull(matcher, "matcher");
checkArgument(limit >= 0, "Limit may not be negative");
if (limit == 0) {
return Optional.empty();
}
ItemStack stack = null;
// Loop through the children inventories
for (AbstractInventory inventory : this.children) {
// Check whether the slot a item contains
if (stack == null) {
stack = inventory.peek(limit, matcher).orElse(null);
if (stack != null) {
if (stack.getQuantity() >= limit) {
return Optional.of(stack);
} else {
limit -= stack.getQuantity();
if (!(matcher instanceof ItemMatcher)) {
matcher = new ItemMatcher(stack);
}
}
}
} else {
int peekedStackSize = 0;
// Check whether the inventory a slot is to avoid
// boxing/unboxing and cloning the item stack
if (inventory instanceof Slot) {
final ItemStack stack1 = ((LanternSlot) inventory).getRawItemStack();
if (stack1 != null && matcher.test(stack1)) {
peekedStackSize = Math.min(((Slot) inventory).getStackSize(), limit);
}
} else {
final Optional<ItemStack> optItemStack = inventory.peek(limit, matcher);
if (optItemStack.isPresent()) {
peekedStackSize = optItemStack.get().getQuantity();
}
}
if (peekedStackSize > 0) {
limit -= peekedStackSize;
stack.setQuantity(stack.getQuantity() + peekedStackSize);
if (limit <= 0) {
return Optional.of(stack);
}
}
}
}
return Optional.ofNullable(stack);
}
@Override
public Optional<PeekPollTransactionsResult> peekPollTransactions(int limit, Predicate<ItemStack> matcher) {
checkNotNull(matcher, "matcher");
checkArgument(limit >= 0, "Limit may not be negative");
if (limit == 0) {
return Optional.empty();
}
PeekPollTransactionsResult peekResult = null;
// Loop through the children inventories
for (AbstractInventory inventory : this.children) {
// Check whether the slot a item contains
if (peekResult == null) {
peekResult = inventory.peekPollTransactions(limit, matcher).orElse(null);
if (peekResult != null) {
if (peekResult.getPeekedItem().getQuantity() >= limit) {
return Optional.of(peekResult);
} else {
limit -= peekResult.getPeekedItem().getQuantity();
if (!(matcher instanceof ItemMatcher)) {
matcher = new ItemMatcher(peekResult.getPeekedItem());
}
}
}
} else {
final PeekPollTransactionsResult peekResult1 = inventory.peekPollTransactions(limit, matcher).orElse(null);
if (peekResult1 != null) {
final int peekedStackSize = peekResult1.getPeekedItem().getQuantity();
final ItemStack peekedItem = peekResult.getPeekedItem();
limit -= peekedStackSize;
peekedItem.setQuantity(peekedItem.getQuantity() + peekedStackSize);
peekResult.getTransactions().addAll(peekResult1.getTransactions());
if (limit <= 0) {
return Optional.of(peekResult);
}
}
}
}
return Optional.ofNullable(peekResult);
}
@Override
public PeekSetTransactionsResult peekSetTransactions(@Nullable ItemStack stack) {
return new PeekSetTransactionsResult(new ArrayList<>(), InventoryTransactionResult.builder()
.type(InventoryTransactionResult.Type.FAILURE).reject(stack).build());
}
@Override
public boolean isValidItem(ItemStack stack) {
checkNotNull(stack, "stack");
for (AbstractInventory child : this.children) {
if (child.isValidItem(stack)) {
return true;
}
}
return false;
}
@Override
public boolean isChild(Inventory child) {
checkNotNull(child, "child");
for (AbstractInventory child0 : this.children) {
if (child0 == child) {
return true;
}
if (child0.isChild(child)) {
return true;
}
}
return false;
}
@Override
public int slotCount() {
int slotCount = 0;
for (AbstractInventory child : this.children) {
if (child instanceof Slot) {
slotCount++;
} else {
slotCount += child.slotCount();
}
}
return slotCount;
}
@Override
protected void addViewer(Viewer viewer, LanternContainer container) {
super.addViewer(viewer, container);
this.children.forEach(child -> child.addViewer(viewer, container));
}
@Override
protected void removeViewer(Viewer viewer, LanternContainer container) {
super.removeViewer(viewer, container);
this.children.forEach(child -> child.removeViewer(viewer, container));
}
}