diff --git a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java index 4f45f7b6d45..cb3d4128bc7 100644 --- a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java +++ b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java @@ -1,5 +1,8 @@ package gregtech.api.capability.impl; +import gregtech.api.capability.INotifiableHandler; +import gregtech.api.metatileentity.MetaTileEntity; + import net.minecraft.item.ItemStack; import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.IItemHandlerModifiable; @@ -15,28 +18,47 @@ /** * Efficiently delegates calls into multiple item handlers */ -public class ItemHandlerList implements IItemHandlerModifiable { +public class ItemHandlerList extends AbstractList implements IItemHandlerModifiable, INotifiableHandler { - private final Int2ObjectMap handlerBySlotIndex = new Int2ObjectOpenHashMap<>(); - private final Object2IntMap baseIndexOffset = new Object2IntArrayMap<>(); + protected final Int2ObjectMap handlerBySlotIndex = new Int2ObjectOpenHashMap<>(); + protected final Object2IntMap baseIndexOffset = new Object2IntArrayMap<>(); - public ItemHandlerList(List itemHandlerList) { - int currentSlotIndex = 0; - for (IItemHandler itemHandler : itemHandlerList) { - if (baseIndexOffset.containsKey(itemHandler)) { - throw new IllegalArgumentException("Attempted to add item handler " + itemHandler + " twice"); - } - baseIndexOffset.put(itemHandler, currentSlotIndex); - int slotsCount = itemHandler.getSlots(); - for (int slotIndex = 0; slotIndex < slotsCount; slotIndex++) { - handlerBySlotIndex.put(currentSlotIndex + slotIndex, itemHandler); - } - currentSlotIndex += slotsCount; + // this is only used for get() + protected IItemHandler[] handlers = new IItemHandler[0]; + + public ItemHandlerList(Collection itemHandlerList) { + addAll(itemHandlerList); + baseIndexOffset.defaultReturnValue(-1); + } + + public ItemHandlerList() { + this(Collections.emptyList()); + } + + public static ItemHandlerList of(IItemHandler... handlers) { + ItemHandlerList list = new ItemHandlerList(); + if (handlers != null && handlers.length > 0) { + Collections.addAll(list, handlers); } + return list.toImmutable(); } + /** + * @param handler the handler to get the slot offset of + * @return the slot offset, or {@code -1} if the handler does not exist + */ public int getIndexOffset(IItemHandler handler) { - return baseIndexOffset.getOrDefault(handler, -1); + return baseIndexOffset.get(handler); + } + + @NotNull + protected IItemHandler getHandlerBySlot(int slot) { + if (invalidSlot(slot)) throw new IndexOutOfBoundsException(); + return handlerBySlotIndex.get(slot); + } + + protected int getInternalSlot(int slot) { + return slot - getIndexOffset(getHandlerBySlot(slot)); } @Override @@ -47,11 +69,12 @@ public int getSlots() { @Override public void setStackInSlot(int slot, @NotNull ItemStack stack) { if (invalidSlot(slot)) return; - IItemHandler itemHandler = handlerBySlotIndex.get(slot); - int actualSlot = slot - baseIndexOffset.get(itemHandler); + IItemHandler itemHandler = getHandlerBySlot(slot); + int actualSlot = getInternalSlot(slot); if (itemHandler instanceof IItemHandlerModifiable modifiable) { modifiable.setStackInSlot(actualSlot, stack); } else { + // should this no-op instead? itemHandler.extractItem(actualSlot, Integer.MAX_VALUE, false); itemHandler.insertItem(actualSlot, stack, false); } @@ -61,31 +84,27 @@ public void setStackInSlot(int slot, @NotNull ItemStack stack) { @Override public ItemStack getStackInSlot(int slot) { if (invalidSlot(slot)) return ItemStack.EMPTY; - IItemHandler itemHandler = handlerBySlotIndex.get(slot); - return itemHandler.getStackInSlot(slot - baseIndexOffset.get(itemHandler)); + return getHandlerBySlot(slot).getStackInSlot(getInternalSlot(slot)); } @Override public int getSlotLimit(int slot) { if (invalidSlot(slot)) return 0; - IItemHandler itemHandler = handlerBySlotIndex.get(slot); - return itemHandler.getSlotLimit(slot - baseIndexOffset.get(itemHandler)); + return getHandlerBySlot(slot).getSlotLimit(getInternalSlot(slot)); } @NotNull @Override public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) { if (invalidSlot(slot)) return stack; - IItemHandler itemHandler = handlerBySlotIndex.get(slot); - return itemHandler.insertItem(slot - baseIndexOffset.get(itemHandler), stack, simulate); + return getHandlerBySlot(slot).insertItem(getInternalSlot(slot), stack, simulate); } @NotNull @Override public ItemStack extractItem(int slot, int amount, boolean simulate) { if (invalidSlot(slot)) return ItemStack.EMPTY; - IItemHandler itemHandler = handlerBySlotIndex.get(slot); - return itemHandler.extractItem(slot - baseIndexOffset.get(itemHandler), amount, simulate); + return getHandlerBySlot(slot).extractItem(getInternalSlot(slot), amount, simulate); } @NotNull @@ -93,7 +112,161 @@ public Collection getBackingHandlers() { return Collections.unmodifiableCollection(baseIndexOffset.keySet()); } + @Override + public void addNotifiableMetaTileEntity(MetaTileEntity metaTileEntity) { + for (IItemHandler handler : this) { + if (handler instanceof INotifiableHandler notifiableHandler) { + notifiableHandler.addNotifiableMetaTileEntity(metaTileEntity); + } + } + } + + @Override + public void removeNotifiableMetaTileEntity(MetaTileEntity metaTileEntity) { + for (IItemHandler handler : this) { + if (handler instanceof INotifiableHandler notifiableHandler) { + notifiableHandler.removeNotifiableMetaTileEntity(metaTileEntity); + } + } + } + + @Override + public int size() { + return baseIndexOffset.size(); + } + + @Override + public boolean add(IItemHandler handler) { + if (this == handler) { + throw new IllegalArgumentException("Cannot add a handler list to itself!"); + } + int s = size(); + if (handler instanceof ItemHandlerList list) { + addAll(s, list); + } else { + add(s, handler); + } + return s != size(); + } + + @Override + public void add(int unused, @NotNull IItemHandler element) { + Objects.requireNonNull(element); + if (baseIndexOffset.containsKey(element)) { + throw new IllegalArgumentException("Attempted to add item handler " + element + " twice"); + } + + int offset = getSlots(); + baseIndexOffset.put(element, offset); + for (int slotIndex = 0; slotIndex < element.getSlots(); slotIndex++) { + handlerBySlotIndex.put(offset + slotIndex, element); + } + } + + @Override + public @NotNull Iterator iterator() { + return baseIndexOffset.keySet().iterator(); + } + + @Override + public IItemHandler get(int index) { + if (invalidIndex(index)) throw new IndexOutOfBoundsException(); + updateHandlerArray(); + return handlers[index]; + } + + @Override + public IItemHandler remove(int index) { + if (invalidIndex(index)) throw new IndexOutOfBoundsException(); + + IItemHandler removed = get(index); + int removedSlots = removed.getSlots(); + + // remove handler + int lower = baseIndexOffset.removeInt(removed); + + // update slot indices ahead of the removed handler and + // remove slot indices + int upper = lower + removedSlots; + int size = getSlots(); // slot size will be mutated + for (int i = lower; i < size; i++) { + if (i < upper) { + handlerBySlotIndex.put(i, getHandlerBySlot(i + removedSlots)); + } else { + handlerBySlotIndex.remove(i); + } + } + + // update handlers ahead of the removed handler + for (IItemHandler h : this) { + int offset = getIndexOffset(h); + if (offset > lower) { + baseIndexOffset.put(h, offset - removedSlots); + } + } + + return removed; + } + + @Override + public int indexOf(@NotNull Object o) { + for (int i = 0; i < size(); i++) { + if (Objects.equals(o, get(i))) + return i; + } + return -1; + } + + @Override + public int lastIndexOf(@NotNull Object o) { + for (int i = size() - 1; i >= 0; i--) { + if (Objects.equals(o, get(i))) + return i; + } + return -1; + } + private boolean invalidSlot(int slot) { - return slot < 0 && slot >= this.getSlots(); + return slot < 0 || slot >= handlerBySlotIndex.size(); + } + + private boolean invalidIndex(int index) { + return index < 0 || index >= baseIndexOffset.size(); + } + + private void updateHandlerArray() { + if (handlers.length != size()) { + handlers = baseIndexOffset.keySet().toArray(new IItemHandler[size()]); + } + } + + public ItemHandlerList toImmutable() { + return new Immutable(this); + } + + private static class Immutable extends ItemHandlerList { + + private Immutable(ItemHandlerList list) { + this.handlers = list.handlers; + this.baseIndexOffset.putAll(list.baseIndexOffset); + this.handlerBySlotIndex.putAll(list.handlerBySlotIndex); + } + + @Override + public void add(int unused, @NotNull IItemHandler element) { + // no op? + throw new UnsupportedOperationException(); + } + + @Override + public IItemHandler remove(int index) { + // no op? + throw new UnsupportedOperationException(); + } + + @Override + public IItemHandler get(int index) { + return handlers[index]; + } } } diff --git a/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java b/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java new file mode 100644 index 00000000000..8372d2b93ca --- /dev/null +++ b/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java @@ -0,0 +1,68 @@ +package gregtech.api.capability.impl; + +import gregtech.Bootstrap; + +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.ItemStackHandler; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Objects; + +import static org.hamcrest.MatcherAssert.assertThat; + +public class ItemHandlerListTest { + + @BeforeAll + public static void bootstrap() { + Bootstrap.perform(); + } + + @Test + public void testListOperations() { + var list = new ItemHandlerList(); + ItemStackHandler firstHandler = new ItemStackHandler(16); + ItemStackHandler secondHandler = new ItemStackHandler(16); + + // test add + list.add(firstHandler); + assertThat("wrong number of slots!", list.getSlots() == 16); + assertThat("wrong number of handlers!", list.size() == 1); + list.add(secondHandler); + assertThat("wrong number of slots!", list.getSlots() == 32); + assertThat("wrong number of handlers!", list.size() == 2); + + // test removal + IItemHandler removed = list.remove(0); + assertThat("wrong number of slots!", list.getSlots() == 16); + assertThat("wrong number of handlers!", list.size() == 1); + assertThat("removed handler is not the first handler!", Objects.equals(removed, firstHandler)); + int newIndex = list.getIndexOffset(secondHandler); + assertThat("second handler was not updated!", newIndex == 0); + + IItemHandler get = list.get(0); + assertThat("Second handler is not first!", get == secondHandler); + + // test add after removal + list.add(firstHandler); + + get = list.get(1); + assertThat("First handler is not second!", get == firstHandler); + assertThat("wrong number of slots!", list.getSlots() == 32); + assertThat("wrong number of handlers!", list.size() == 2); + + // test immutable + ItemHandlerList immutable = list.toImmutable(); + + try { + immutable.remove(0); + assertThat("list was modified!", false); + } catch (UnsupportedOperationException ignored) {} + + try { + immutable.add(firstHandler); + assertThat("list was modified!", false); + } catch (UnsupportedOperationException ignored) {} + } +}