From ca7ce33f3497784b4bfadff544a67600d2a6b1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Salom=C3=A3o?= Date: Wed, 31 Mar 2021 18:14:16 -0300 Subject: [PATCH 1/2] testes --- src/api/java/appeng/api/config/FuzzyMode.java | 39 +- src/main/java/appeng/util/UUIDMatcher.java | 51 -- .../appeng/util/item/AESharedItemStack.java | 254 ++-------- .../util/item/FuzzyItemVariantList.java | 179 +++++++ src/main/java/appeng/util/item/ItemList.java | 239 +++++---- .../java/appeng/util/item/ItemModList.java | 87 ---- .../appeng/util/item/ItemVariantList.java | 118 +++++ .../util/item/MeaningfulItemIterator.java | 74 ++- .../util/item/NormalItemVariantList.java | 51 ++ src/test/java/appeng/util/EnumCyclerTest.java | 91 ++++ .../java/appeng/util/UUIDMatcherTest.java | 61 --- .../util/WideReadableNumberConverterTest.java | 2 +- .../appeng/util/helpers/P2PHelperTest.java | 2 +- .../util/item/AESharedItemStackTest.java | 78 +++ .../util/item/FuzzyItemVariantListTest.java | 157 ++++++ .../java/appeng/util/item/ItemListTest.java | 452 ++++++++++++++++++ .../appeng/util/item/TestItemWithCaps.java | 60 +++ 17 files changed, 1402 insertions(+), 593 deletions(-) delete mode 100644 src/main/java/appeng/util/UUIDMatcher.java create mode 100644 src/main/java/appeng/util/item/FuzzyItemVariantList.java delete mode 100644 src/main/java/appeng/util/item/ItemModList.java create mode 100644 src/main/java/appeng/util/item/ItemVariantList.java create mode 100644 src/main/java/appeng/util/item/NormalItemVariantList.java create mode 100644 src/test/java/appeng/util/EnumCyclerTest.java delete mode 100644 src/test/java/appeng/util/UUIDMatcherTest.java create mode 100644 src/test/java/appeng/util/item/AESharedItemStackTest.java create mode 100644 src/test/java/appeng/util/item/FuzzyItemVariantListTest.java create mode 100644 src/test/java/appeng/util/item/ItemListTest.java create mode 100644 src/test/java/appeng/util/item/TestItemWithCaps.java diff --git a/src/api/java/appeng/api/config/FuzzyMode.java b/src/api/java/appeng/api/config/FuzzyMode.java index 84c36c78f..d8353fac5 100644 --- a/src/api/java/appeng/api/config/FuzzyMode.java +++ b/src/api/java/appeng/api/config/FuzzyMode.java @@ -23,15 +23,38 @@ package appeng.api.config; +public enum FuzzyMode { + /** + * Matches any item from undamaged to including 0% durability, but not negative durability (where item damage + * exceeds maxdamage). + */ + IGNORE_ALL(-1), + /** + * Matches items that have less than 100% durability (that is, at least 1 damage point) if a damaged item is used as + * the filter, or undamaged items otherwise. + */ + PERCENT_99(0), + /** + * If an item with less than 75% durability is used as the filter, items with less than 75% durability are matched. + * Otherwise items with 75% durability or more are matched. + */ + PERCENT_75(25), + /** + * If an item with less than 50% durability is used as the filter, items with less than 50% durability are matched. + * Otherwise items with 50% durability or more are matched. + */ + PERCENT_50(50), + /** + * If an item with less than 25% durability is used as the filter, items with less than 50% durability are matched. + * Otherwise items with 25% durability or more are matched. + */ + PERCENT_25(75); -public enum FuzzyMode -{ - // Note that percentage damaged, is the inverse of percentage durability. - IGNORE_ALL( -1 ), - PERCENT_99( 0 ), - PERCENT_75( 25 ), - PERCENT_50( 50 ), - PERCENT_25( 75 ); + public final float breakPoint; + /** + * Note this is percentage "damaged". It's the inverse of percentage durability. + */ + public final float percentage; public final float breakPoint; public final float percentage; diff --git a/src/main/java/appeng/util/UUIDMatcher.java b/src/main/java/appeng/util/UUIDMatcher.java deleted file mode 100644 index 2a6c92353..000000000 --- a/src/main/java/appeng/util/UUIDMatcher.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. - * - * Applied Energistics 2 is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Applied Energistics 2 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Applied Energistics 2. If not, see . - */ - -package appeng.util; - - -import java.util.regex.Pattern; - - -/** - * Regex wrapper for {@link java.util.UUID}s to not rely on try catch - */ -public final class UUIDMatcher -{ - /** - * String which is the regular expression for {@link java.util.UUID}s - */ - private static final String UUID_REGEX = "[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}"; - - /** - * Pattern which pre-compiles the {@link appeng.util.UUIDMatcher#UUID_REGEX} - */ - private static final Pattern PATTERN = Pattern.compile( UUID_REGEX ); - - /** - * Checks if a potential {@link java.util.UUID} is an {@link java.util.UUID} by applying a regular expression on it. - * - * @param potential to be checked potential {@link java.util.UUID} - * - * @return true, if the potential {@link java.util.UUID} is indeed an {@link java.util.UUID} - */ - public boolean isUUID( final CharSequence potential ) - { - return PATTERN.matcher( potential ).matches(); - } -} diff --git a/src/main/java/appeng/util/item/AESharedItemStack.java b/src/main/java/appeng/util/item/AESharedItemStack.java index 5ce9e784d..700e5b6b1 100644 --- a/src/main/java/appeng/util/item/AESharedItemStack.java +++ b/src/main/java/appeng/util/item/AESharedItemStack.java @@ -25,15 +25,13 @@ import com.google.common.base.Preconditions; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NBTTagCompound; -import appeng.api.config.FuzzyMode; +final class AESharedItemStack { - -final class AESharedItemStack implements Comparable -{ - private static final NBTTagCompound LOW_TAG = new NBTTagCompound(); - private static final NBTTagCompound HIGH_TAG = new NBTTagCompound(); + private final ItemStack itemStack; + private final int itemId; + private final int itemDamage; + private final int hashCode; private final ItemStack itemStack; private final int itemId; @@ -53,228 +51,44 @@ final class AESharedItemStack implements Comparable return new Bounds( this.itemStack, fuzzy, ignoreMeta ); } - ItemStack getDefinition() - { - return this.itemStack; - } - - int getItemDamage() - { - return this.itemDamage; - } + ItemStack getDefinition() { + return this.itemStack; + } int getItemID() { return this.itemId; } - @Override - public int hashCode() - { - return this.hashCode; - } + @Override + public int hashCode() { + return this.hashCode; + } - @Override - public boolean equals( final Object obj ) - { - if( obj instanceof AESharedItemStack ) - { - final AESharedItemStack other = (AESharedItemStack) obj; + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AESharedItemStack)) { + return false; + } - Preconditions.checkState( this.itemStack.getCount() == 1, "ItemStack#getCount() has to be 1" ); - Preconditions.checkArgument( other.getDefinition().getCount() == 1, "ItemStack#getCount() has to be 1" ); + final AESharedItemStack other = (AESharedItemStack) obj; + Preconditions.checkState(this.itemStack.getCount() == 1, "ItemStack#getCount() has to be 1"); + Preconditions.checkArgument(other.getDefinition().getCount() == 1, "ItemStack#getCount() has to be 1"); - if( this.itemStack == other.itemStack ) - { - return true; - } - return ItemStack.areItemStacksEqual( this.itemStack, other.itemStack ); - } - return false; - } + if (this.itemStack == other.itemStack) { + return true; + } + return ItemStack.areItemStacksEqual(this.itemStack, other.itemStack); + } - @Override - public int compareTo( final AESharedItemStack b ) - { - Preconditions.checkState( this.itemStack.getCount() == 1, "ItemStack#getCount() has to be 1" ); - Preconditions.checkArgument( b.getDefinition().getCount() == 1, "ItemStack#getCount() has to be 1" ); + private int makeHashCode() { + return Objects.hash( + this.itemId, + this.itemDamage, + this.itemStack.hasTag() ? this.itemStack.getTag() : 0); + } - if( this.itemStack == b.getDefinition() ) - { - return 0; - } - - final int id = this.itemId - b.itemId; - if( id != 0 ) - { - return id; - } - - final int damageValue = this.itemDamage - b.itemDamage; - if( damageValue != 0 ) - { - return damageValue; - } - - final int nbt = this.compareNBT( b.getDefinition() ); - if( nbt != 0 ) - { - return nbt; - } - - if( !this.itemStack.areCapsCompatible( b.getDefinition() ) ) - { - return System.identityHashCode( this.itemStack ) - System.identityHashCode( b.getDefinition() ); - } - return 0; - } - - private int compareNBT( final ItemStack b ) - { - if( this.itemStack.getTagCompound() == b.getTagCompound() ) - { - return 0; - } - if( this.itemStack.getTagCompound() == LOW_TAG || b.getTagCompound() == HIGH_TAG ) - { - return -1; - } - if( this.itemStack.getTagCompound() == HIGH_TAG || b.getTagCompound() == LOW_TAG ) - { - return 1; - } - return System.identityHashCode( this.itemStack.getTagCompound() ) - System.identityHashCode( b.getTagCompound() ); - } - - private int makeHashCode() - { - return Objects.hash( this.itemId, this.itemDamage, this.itemStack.hasTagCompound() ? this.itemStack.getTagCompound() : 0 ); - } - - /** - * Creates the lower and upper bounds for a specific shared itemstack. - */ - public static final class Bounds - { - /** - * Bounds enforced by {@link ItemStack#isEmpty()} - */ - private static final int MIN_DAMAGE_VALUE = 0; - private static final int MAX_DAMAGE_VALUE = 65535; - - private final AESharedItemStack lower; - private final AESharedItemStack upper; - - public Bounds( final ItemStack stack, final FuzzyMode fuzzy, final boolean ignoreMeta ) - { - Preconditions.checkState( !stack.isEmpty(), "ItemStack#isEmpty() has to be false" ); - Preconditions.checkState( stack.getCount() == 1, "ItemStack#getCount() has to be 1" ); - - final NBTTagCompound tag = stack.hasTagCompound() ? stack.getTagCompound() : null; - - this.lower = this.makeLowerBound( stack, tag, fuzzy, ignoreMeta ); - this.upper = this.makeUpperBound( stack, tag, fuzzy, ignoreMeta ); - } - - public AESharedItemStack lower() - { - return this.lower; - } - - public AESharedItemStack upper() - { - return this.upper; - } - - private AESharedItemStack makeLowerBound( final ItemStack itemStack, final NBTTagCompound tag, final FuzzyMode fuzzy, final boolean ignoreMeta ) - { - final ItemStack newDef = itemStack.copy(); - - if( ignoreMeta ) - { - newDef.setItemDamage( MIN_DAMAGE_VALUE ); - newDef.setTagCompound( tag ); - } - else - { - if( newDef.getItem().isDamageable() ) - { - if (itemStack.getMaxDamage() == 0) - { - newDef.setItemDamage(itemStack.getItemDamage() ); - } - else if ( fuzzy == FuzzyMode.IGNORE_ALL ) - { - newDef.setItemDamage( MIN_DAMAGE_VALUE ); - } - else if( fuzzy == FuzzyMode.PERCENT_99 ) - { - if( itemStack.getItemDamage() == MIN_DAMAGE_VALUE ) - { - newDef.setItemDamage( MIN_DAMAGE_VALUE ); - } - else - { - newDef.setItemDamage( MIN_DAMAGE_VALUE + 1 ); - } - } - else - { - final int breakpoint = fuzzy.calculateBreakPoint( itemStack.getMaxDamage() ); - final int damage = breakpoint <= itemStack.getItemDamage() ? breakpoint : 0; - newDef.setItemDamage( damage ); - } - } - newDef.setTagCompound( LOW_TAG ); - } - - return new AESharedItemStack( newDef ); - } - - private AESharedItemStack makeUpperBound( final ItemStack itemStack, final NBTTagCompound tag, final FuzzyMode fuzzy, final boolean ignoreMeta ) - { - final ItemStack newDef = itemStack.copy(); - - if( ignoreMeta ) - { - newDef.setItemDamage( MAX_DAMAGE_VALUE ); - newDef.setTagCompound( tag ); - } - else - { - if( newDef.getItem().isDamageable() ) - { - if (itemStack.getMaxDamage() == 0) - { - newDef.setItemDamage(itemStack.getItemDamage() ); - } - else if ( fuzzy == FuzzyMode.IGNORE_ALL ) - { - newDef.setItemDamage( itemStack.getMaxDamage() + 1 ); - } - else if( fuzzy == FuzzyMode.PERCENT_99 ) - { - if( itemStack.getItemDamage() == MIN_DAMAGE_VALUE ) - { - newDef.setItemDamage( MIN_DAMAGE_VALUE ); - } - else - { - newDef.setItemDamage( itemStack.getMaxDamage() + 1 ); - } - } - else - { - final int breakpoint = fuzzy.calculateBreakPoint( itemStack.getMaxDamage() ); - final int damage = itemStack.getItemDamage() < breakpoint ? breakpoint - 1 : itemStack.getMaxDamage() + 1; - newDef.setItemDamage( damage ); - } - } - newDef.setTagCompound( HIGH_TAG ); - } - - return new AESharedItemStack( newDef ); - } - - } } diff --git a/src/main/java/appeng/util/item/FuzzyItemVariantList.java b/src/main/java/appeng/util/item/FuzzyItemVariantList.java new file mode 100644 index 000000000..270a3df07 --- /dev/null +++ b/src/main/java/appeng/util/item/FuzzyItemVariantList.java @@ -0,0 +1,179 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2021, TeamAppliedEnergistics, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.util.item; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; + +import com.google.common.base.Preconditions; + +import net.minecraft.item.ItemStack; + +import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectSortedMap; + +import appeng.api.config.FuzzyMode; +import appeng.api.storage.data.IAEItemStack; + +/** + * This variant list is optimized for damageable items, and supports selecting durability ranges with + * {@link #findFuzzy(IAEItemStack, FuzzyMode)}. + */ +class FuzzyItemVariantList extends ItemVariantList { + + static final SharedStackComparator COMPARATOR = new SharedStackComparator(); + + // NOTE: We only use Object as they key here so we can pass our special DamageBounds to the subMap method. + // We NEVER put any keys in this map that are not AESharedItemStacks. + private final Object2ObjectSortedMap records = new Object2ObjectAVLTreeMap<>(COMPARATOR); + + @Override + public Collection findFuzzy(final IAEItemStack filter, final FuzzyMode fuzzy) { + ItemStack itemStack = filter.getDefinition(); + + ItemDamageBound lowerBound = makeLowerBound(itemStack, fuzzy); + ItemDamageBound upperBound = makeUpperBound(itemStack, fuzzy); + Preconditions.checkState(lowerBound.itemDamage > upperBound.itemDamage); + + return this.records.subMap(lowerBound, upperBound).values(); + } + + @SuppressWarnings("unchecked") + @Override + Map getRecords() { + // We ensure on our end that we NEVER use anything but AESharedItemStack as the key in this map + return (Map) (Object) this.records; + } + + static class ItemDamageBound { + final int itemDamage; + + public ItemDamageBound(int itemDamage) { + this.itemDamage = itemDamage; + } + } + + /** + * This comparator creates a strict and total ordering over all {@link AESharedItemStack} of the same item. To + * support selecting ranges of durability, it is defined for type {@link Object} and also accepts + * {@link ItemDamageBound} as an argument to compare against. + */ + static class SharedStackComparator implements Comparator { + @Override + public int compare(Object a, Object b) { + // Either argument can either be a damage bound or a shared item stack + // Since we never put damage bounds into the map as keys, only one + // of the two arguments can possibly be a bound + ItemDamageBound boundA = null; + AESharedItemStack stackA = null; + int itemDamageA; + if (a instanceof ItemDamageBound) { + boundA = (ItemDamageBound) a; + itemDamageA = boundA.itemDamage; + } else { + stackA = (AESharedItemStack) a; + itemDamageA = stackA.getItemDamage(); + } + ItemDamageBound boundB = null; + AESharedItemStack stackB = null; + int itemDamageB; + if (b instanceof ItemDamageBound) { + boundB = (ItemDamageBound) b; + itemDamageB = boundB.itemDamage; + } else { + stackB = (AESharedItemStack) b; + itemDamageB = stackB.getItemDamage(); + } + + // When either argument is a damage bound, we just compare the damage values because it is used + // only to get a certain damage range out of the map. + if (boundA != null || boundB != null) { + return Integer.compare(itemDamageB, itemDamageA); + } + + ItemStack itemStackA = stackA.getDefinition(); + ItemStack itemStackB = stackB.getDefinition(); + Preconditions.checkState(itemStackA.getCount() == 1, "ItemStack#getCount() has to be 1"); + Preconditions.checkArgument(itemStackB.getCount() == 1, "ItemStack#getCount() has to be 1"); + + if (itemStackA == itemStackB) { + return 0; + } + + // Damaged items are sorted before undamaged items + final int damageValue = Integer.compare(itemDamageB, itemDamageA); + if (damageValue != 0) { + return damageValue; + } + + // As a final tie breaker, order by the object identity of the item stack + // While this will order seemingly at random, we only need the order of + // damage values to be predictable, while still having to satisfy the + // complete order requirements of the sorted map + return Long.compare(System.identityHashCode(itemStackA), System.identityHashCode(itemStackB)); + } + } + + /** + * Minecraft reverses the damage values. So anything with a damage of 0 is undamaged and increases the more damaged + * the item is. + *

+ * Further the used subMap follows [MAX_DAMAGE, MIN_DAMAGE), so to include undamaged items, we have to start with a + * lower damage value than 0, while it is fine to use {@link ItemStack#getMaxDamage()} for the upper bound. + */ + private static final int MIN_DAMAGE_VALUE = -1; + + /* + * Keep in mind that the stack order is from most damaged to least damaged, so this lower bound will actually be a + * higher number than the upper bound. + */ + static ItemDamageBound makeLowerBound(final ItemStack stack, final FuzzyMode fuzzy) { + Preconditions.checkState(stack.isDamageable(), "ItemStack#isDamageable() has to be true"); + + int damage; + if (fuzzy == FuzzyMode.IGNORE_ALL) { + damage = stack.getMaxDamage(); + } else { + final int breakpoint = fuzzy.calculateBreakPoint(stack.getMaxDamage()); + damage = stack.getDamage() <= breakpoint ? breakpoint : stack.getMaxDamage(); + } + + return new ItemDamageBound(damage); + } + + /* + * Keep in mind that the stack order is from most damaged to least damaged, so this upper bound will actually be a + * lower number than the lower bound. It also is exclusive. + */ + static ItemDamageBound makeUpperBound(final ItemStack stack, final FuzzyMode fuzzy) { + Preconditions.checkState(stack.isDamageable(), "ItemStack#isDamageable() has to be true"); + + int damage; + if (fuzzy == FuzzyMode.IGNORE_ALL) { + damage = MIN_DAMAGE_VALUE; + } else { + final int breakpoint = fuzzy.calculateBreakPoint(stack.getMaxDamage()); + damage = stack.getDamage() <= breakpoint ? MIN_DAMAGE_VALUE : breakpoint; + } + + return new ItemDamageBound(damage); + } + +} diff --git a/src/main/java/appeng/util/item/ItemList.java b/src/main/java/appeng/util/item/ItemList.java index c8e22b796..e50cd73b7 100644 --- a/src/main/java/appeng/util/item/ItemList.java +++ b/src/main/java/appeng/util/item/ItemList.java @@ -22,9 +22,10 @@ package appeng.util.item; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.ConcurrentModificationException; import java.util.Iterator; -import java.util.NavigableMap; -import java.util.concurrent.ConcurrentSkipListMap; +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicInteger; import net.minecraftforge.oredict.OreDictionary; @@ -34,20 +35,23 @@ import appeng.api.storage.data.IItemList; import appeng.util.item.AESharedItemStack.Bounds; -public final class ItemList implements IItemList -{ + private final Reference2ObjectMap records = new Reference2ObjectOpenHashMap<>(); + /** + * We increment this version field everytime an attempt to mutate this item list (or potentially one of its + * sub-lists) is made. Iterators will copy the version when they are created and compare it against the current + * version whenever they advance to trigger a {@link ConcurrentModificationException}. + */ + private final AtomicInteger version = new AtomicInteger(0); - private final NavigableMap records = new ConcurrentSkipListMap<>(); + @Override + public IAEItemStack findPrecise(final IAEItemStack itemStack) { + if (itemStack == null) { + return null; + } - @Override - public void add( final IAEItemStack option ) - { - if( option == null ) - { - return; - } - - final IAEItemStack st = this.records.get( ( (AEItemStack) option ).getSharedStack() ); + ItemVariantList record = this.records.get(itemStack.getItem()); + return record != null ? record.findPrecise(itemStack) : null; + } if( st != null ) { @@ -55,37 +59,41 @@ public final class ItemList implements IItemList return; } - final IAEItemStack opt = option.copy(); + ItemVariantList record = this.records.get(filter.getItem()); + return record != null ? record.findFuzzy(filter, fuzzy) : Collections.emptyList(); + } this.putItemRecord( opt ); } - @Override - public IAEItemStack findPrecise( final IAEItemStack itemStack ) - { - if( itemStack == null ) - { - return null; - } + @Override + public void add(final IAEItemStack itemStack) { + version.incrementAndGet(); + + if (itemStack == null) { + return; + } return this.records.get( ( (AEItemStack) itemStack ).getSharedStack() ); } - @Override - public Collection findFuzzy( final IAEItemStack filter, final FuzzyMode fuzzy ) - { - if( filter == null ) - { - return Collections.emptyList(); - } + @Override + public void addStorage(final IAEItemStack itemStack) { + version.incrementAndGet(); + + if (itemStack == null) { + return; + } final AEItemStack ais = (AEItemStack) filter; - return ais.getOre().map( or -> - { - if( or.getAEEquivalents().size() == 1 ) - { - final IAEItemStack is = or.getAEEquivalents().get( 0 ); + @Override + public void addCrafting(final IAEItemStack itemStack) { + version.incrementAndGet(); + + if (itemStack == null) { + return; + } return this.findFuzzyDamage( is, fuzzy, is.getItemDamage() == OreDictionary.WILDCARD_VALUE ); } @@ -93,10 +101,13 @@ public final class ItemList implements IItemList { final Collection output = new ArrayList<>(); - for( final IAEItemStack is : or.getAEEquivalents() ) - { - output.addAll( this.findFuzzyDamage( is, fuzzy, is.getItemDamage() == OreDictionary.WILDCARD_VALUE ) ); - } + @Override + public void addRequestable(final IAEItemStack itemStack) { + version.incrementAndGet(); + + if (itemStack == null) { + return; + } return output; } @@ -117,7 +128,12 @@ public final class ItemList implements IItemList return; } - final IAEItemStack st = this.records.get( ( (AEItemStack) option ).getSharedStack() ); + @Override + public int size() { + int size = 0; + for (ItemVariantList entry : records.values()) { + size += entry.size(); + } if( st != null ) { @@ -125,105 +141,78 @@ public final class ItemList implements IItemList return; } - final IAEItemStack opt = option.copy(); + @Override + public Iterator iterator() { + return new ChainedIterator(this.records.values().iterator(), version); + } this.putItemRecord( opt ); } - /* - * public void clean() { Iterator i = iterator(); while (i.hasNext()) { StackType AEI = - * i.next(); if ( !AEI.isMeaningful() ) i.remove(); } } - */ + private ItemVariantList getOrCreateRecord(Item item) { + return this.records.computeIfAbsent(item, this::makeRecordMap); + } - @Override - public void addCrafting( final IAEItemStack option ) - { - if( option == null ) - { - return; - } + private ItemVariantList makeRecordMap(Item item) { + if (item.isDamageable()) { + return new FuzzyItemVariantList(); + } else { + return new NormalItemVariantList(); + } + } - final IAEItemStack st = this.records.get( ( (AEItemStack) option ).getSharedStack() ); + /** + * Iterates over multiple item lists as if they were one list. + */ + private static class ChainedIterator implements Iterator { - if( st != null ) - { - st.setCraftable( true ); - return; - } + private final AtomicInteger parentVersion; + private final int version; + private final Iterator parent; + private Iterator next; - final IAEItemStack opt = option.copy(); - opt.setStackSize( 0 ); - opt.setCraftable( true ); + public ChainedIterator(Iterator iterator, AtomicInteger parentVersion) { + this.parent = iterator; + this.parentVersion = parentVersion; + this.version = parentVersion.get(); + this.ensureItems(); + } - this.putItemRecord( opt ); - } + @Override + public boolean hasNext() { + return next != null && next.hasNext(); + } - @Override - public void addRequestable( final IAEItemStack option ) - { - if( option == null ) - { - return; - } + @Override + public IAEItemStack next() { + if (this.next == null) { + throw new NoSuchElementException(); + } + if (this.version != this.parentVersion.get()) { + throw new ConcurrentModificationException(); + } - final IAEItemStack st = this.records.get( ( (AEItemStack) option ).getSharedStack() ); + IAEItemStack result = this.next.next(); + this.ensureItems(); + return result; + } - if( st != null ) - { - st.setCountRequestable( st.getCountRequestable() + option.getCountRequestable() ); - return; - } + private void ensureItems() { + if (hasNext()) { + return; // Still items left in the current one + } - final IAEItemStack opt = option.copy(); - opt.setStackSize( 0 ); - opt.setCraftable( false ); - opt.setCountRequestable( option.getCountRequestable() ); + // Find the next iterator willing to return some items... + while (this.parent.hasNext()) { + this.next = this.parent.next().iterator(); - this.putItemRecord( opt ); - } + if (this.next.hasNext()) { + return; // Found one! + } + } - @Override - public IAEItemStack getFirstItem() - { - for( final IAEItemStack stackType : this ) - { - return stackType; - } - - return null; - } - - @Override - public int size() - { - return this.records.size(); - } - - @Override - public Iterator iterator() - { - return new MeaningfulItemIterator<>( this.records.values().iterator() ); - } - - @Override - public void resetStatus() - { - for( final IAEItemStack i : this ) - { - i.reset(); - } - } - - private IAEItemStack putItemRecord( final IAEItemStack itemStack ) - { - return this.records.put( ( (AEItemStack) itemStack ).getSharedStack(), itemStack ); - } - - private Collection findFuzzyDamage( final IAEItemStack filter, final FuzzyMode fuzzy, final boolean ignoreMeta ) - { - final AEItemStack itemStack = (AEItemStack) filter; - final Bounds bounds = itemStack.getSharedStack().getBounds( fuzzy, ignoreMeta ); - - return this.records.subMap( bounds.lower(), true, bounds.upper(), true ).descendingMap().values(); - } + // No more items + this.next = null; + } + } } diff --git a/src/main/java/appeng/util/item/ItemModList.java b/src/main/java/appeng/util/item/ItemModList.java deleted file mode 100644 index 60268f679..000000000 --- a/src/main/java/appeng/util/item/ItemModList.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. - * - * Applied Energistics 2 is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Applied Energistics 2 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Applied Energistics 2. If not, see . - */ - -package appeng.util.item; - - -import java.util.Collection; - -import appeng.api.AEApi; -import appeng.api.config.FuzzyMode; -import appeng.api.storage.channels.IItemStorageChannel; -import appeng.api.storage.data.IAEItemStack; -import appeng.api.storage.data.IItemContainer; - - -public class ItemModList implements IItemContainer -{ - - private final IItemContainer backingStore; - private final IItemContainer overrides = AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ).createList(); - - public ItemModList( final IItemContainer backend ) - { - this.backingStore = backend; - } - - @Override - public void add( final IAEItemStack option ) - { - IAEItemStack over = this.overrides.findPrecise( option ); - if( over == null ) - { - over = this.backingStore.findPrecise( option ); - if( over == null ) - { - this.overrides.add( option ); - } - else - { - option.add( over ); - this.overrides.add( option ); - } - } - else - { - this.overrides.add( option ); - } - } - - @Override - public IAEItemStack findPrecise( final IAEItemStack i ) - { - final IAEItemStack over = this.overrides.findPrecise( i ); - if( over == null ) - { - return this.backingStore.findPrecise( i ); - } - return over; - } - - @Override - public Collection findFuzzy( final IAEItemStack input, final FuzzyMode fuzzy ) - { - return this.overrides.findFuzzy( input, fuzzy ); - } - - @Override - public boolean isEmpty() - { - return this.overrides.isEmpty() && this.backingStore.isEmpty(); - } -} diff --git a/src/main/java/appeng/util/item/ItemVariantList.java b/src/main/java/appeng/util/item/ItemVariantList.java new file mode 100644 index 000000000..01af28937 --- /dev/null +++ b/src/main/java/appeng/util/item/ItemVariantList.java @@ -0,0 +1,118 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2021, TeamAppliedEnergistics, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.util.item; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import appeng.api.config.FuzzyMode; +import appeng.api.storage.data.IAEItemStack; + +/** + * Stores variants of a single type of {@link net.minecraft.item.Item}, i.e. versions with different durability, or + * different NBT or capabilities. + */ +abstract class ItemVariantList { + + public void add(final IAEItemStack option) { + final IAEItemStack st = this.getRecords().get(((AEItemStack) option).getSharedStack()); + + if (st != null) { + st.add(option); + return; + } + + final IAEItemStack opt = option.copy(); + + this.putItemRecord(opt); + } + + public IAEItemStack findPrecise(final IAEItemStack itemStack) { + return this.getRecords().get(((AEItemStack) itemStack).getSharedStack()); + } + + public void addStorage(final IAEItemStack option) { + final IAEItemStack st = this.getRecords().get(((AEItemStack) option).getSharedStack()); + + if (st != null) { + st.incStackSize(option.getStackSize()); + return; + } + + final IAEItemStack opt = option.copy(); + + this.putItemRecord(opt); + } + + public void addCrafting(final IAEItemStack option) { + final IAEItemStack st = this.getRecords().get(((AEItemStack) option).getSharedStack()); + + if (st != null) { + st.setCraftable(true); + return; + } + + final IAEItemStack opt = option.copy(); + opt.setStackSize(0); + opt.setCraftable(true); + + this.putItemRecord(opt); + } + + public void addRequestable(final IAEItemStack option) { + final IAEItemStack st = this.getRecords().get(((AEItemStack) option).getSharedStack()); + + if (st != null) { + st.setCountRequestable(st.getCountRequestable() + option.getCountRequestable()); + return; + } + + final IAEItemStack opt = option.copy(); + opt.setStackSize(0); + opt.setCraftable(false); + opt.setCountRequestable(option.getCountRequestable()); + + this.putItemRecord(opt); + } + + public int size() { + int size = 0; + for (IAEItemStack entry : getRecords().values()) { + if (entry.isMeaningful()) { + size++; + } + } + + return size; + } + + public Iterator iterator() { + return new MeaningfulItemIterator<>(this.getRecords().values()); + } + + private void putItemRecord(final IAEItemStack itemStack) { + this.getRecords().put(((AEItemStack) itemStack).getSharedStack(), itemStack); + } + + abstract Map getRecords(); + + public abstract Collection findFuzzy(final IAEItemStack filter, final FuzzyMode fuzzy); + +} diff --git a/src/main/java/appeng/util/item/MeaningfulItemIterator.java b/src/main/java/appeng/util/item/MeaningfulItemIterator.java index 7d7c8b016..310d23fb3 100644 --- a/src/main/java/appeng/util/item/MeaningfulItemIterator.java +++ b/src/main/java/appeng/util/item/MeaningfulItemIterator.java @@ -1,6 +1,6 @@ /* * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2015, AlgorithmX2, All rights reserved. + * Copyright (c) 2021, TeamAppliedEnergistics, All rights reserved. * * Applied Energistics 2 is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -18,44 +18,30 @@ package appeng.util.item; - +import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import appeng.api.storage.data.IAEItemStack; +/** + * This iterator will only return items from a collection that are meaningful (w.r.t. + * {@link IAEItemStack#isMeaningful()}. Items that are not meaningful are automatically removed from the collection as + * it is being iterated. + */ +public class MeaningfulItemIterator implements Iterator { + private final Iterator parent; + private T next; -public class MeaningfulItemIterator implements Iterator -{ + public MeaningfulItemIterator(final Collection collection) { + this.parent = collection.iterator(); + this.next = seekNext(); + } - private final Iterator parent; - private T next; - - public MeaningfulItemIterator( final Iterator iterator ) - { - this.parent = iterator; - } - - @Override - public boolean hasNext() - { - while( this.parent.hasNext() ) - { - this.next = this.parent.next(); - - if( this.next.isMeaningful() ) - { - return true; - } - else - { - this.parent.remove(); // self cleaning :3 - } - } - - this.next = null; - return false; - } + @Override + public boolean hasNext() { + return this.next != null; + } @Override public T next() @@ -65,12 +51,22 @@ public class MeaningfulItemIterator implements Iterator< throw new NoSuchElementException(); } - return this.next; - } + T result = this.next; + this.next = this.seekNext(); + return result; + } - @Override - public void remove() - { - this.parent.remove(); - } + private T seekNext() { + while (this.parent.hasNext()) { + T item = this.parent.next(); + + if (item.isMeaningful()) { + return item; + } else { + this.parent.remove(); + } + } + + return null; + } } diff --git a/src/main/java/appeng/util/item/NormalItemVariantList.java b/src/main/java/appeng/util/item/NormalItemVariantList.java new file mode 100644 index 000000000..89a92a155 --- /dev/null +++ b/src/main/java/appeng/util/item/NormalItemVariantList.java @@ -0,0 +1,51 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2021, TeamAppliedEnergistics, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.util.item; + +import java.util.Collection; +import java.util.Map; + +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; + +import appeng.api.config.FuzzyMode; +import appeng.api.storage.data.IAEItemStack; + +/** + * This variant list is optimized for items that cannot be damaged and thus do not support querying durability ranges + * via {@link #findFuzzy(IAEItemStack, FuzzyMode)}. + */ +class NormalItemVariantList extends ItemVariantList { + + private final Reference2ObjectMap records = new Reference2ObjectOpenHashMap<>(); + + @Override + Map getRecords() { + return this.records; + } + + /** + * For items that do not support durability, we just return all variants to a fuzzy search. + */ + @Override + public Collection findFuzzy(IAEItemStack filter, FuzzyMode fuzzy) { + return this.getRecords().values(); + } + +} diff --git a/src/test/java/appeng/util/EnumCyclerTest.java b/src/test/java/appeng/util/EnumCyclerTest.java new file mode 100644 index 000000000..b00998551 --- /dev/null +++ b/src/test/java/appeng/util/EnumCyclerTest.java @@ -0,0 +1,91 @@ +package appeng.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.EnumSet; + +import org.junit.jupiter.api.Test; + +class EnumCyclerTest { + + @Test + void testRotateEnumForwardWithOnlySomeValidOptions() { + EnumSet validOptions = EnumSet.of(RotateTestEnum.A, RotateTestEnum.C, RotateTestEnum.E); + assertThat(EnumCycler.rotateEnum(RotateTestEnum.A, false, validOptions)).isEqualTo(RotateTestEnum.C); + assertThat(EnumCycler.rotateEnum(RotateTestEnum.C, false, validOptions)).isEqualTo(RotateTestEnum.E); + assertThat(EnumCycler.rotateEnum(RotateTestEnum.E, false, validOptions)).isEqualTo(RotateTestEnum.A); + } + + @Test + void testRotateEnumBackwardsWithOnlySomeValidOptions() { + EnumSet validOptions = EnumSet.of(RotateTestEnum.A, RotateTestEnum.C, RotateTestEnum.E); + assertThat(EnumCycler.rotateEnum(RotateTestEnum.A, true, validOptions)).isEqualTo(RotateTestEnum.E); + assertThat(EnumCycler.rotateEnum(RotateTestEnum.C, true, validOptions)).isEqualTo(RotateTestEnum.A); + assertThat(EnumCycler.rotateEnum(RotateTestEnum.E, true, validOptions)).isEqualTo(RotateTestEnum.C); + } + + /** + * When there are no valid options, the function should reject the arguments. + */ + @Test + void testRotateEnumNoValidOptions() { + assertThrows(IllegalArgumentException.class, () -> { + EnumCycler.rotateEnum(TestEnum.A, false, EnumSet.noneOf(TestEnum.class)); + }); + } + + /** + * When the current enum literal is not part of the valid options, it should just skip to the next valid option + * instead. + */ + @Test + void testRotateEnumCurrentIsNotAValidOption() { + assertThat(EnumCycler.rotateEnum(TestEnum.B, false, EnumSet.of(TestEnum.A))).isEqualTo(TestEnum.A); + assertThat(EnumCycler.rotateEnum(TestEnum.B, true, EnumSet.of(TestEnum.A))).isEqualTo(TestEnum.A); + } + + /** + * When there's only one valid option, it should just rotate back to it. + */ + @Test + void testRotateEnumOnlyOneValidOption() { + assertThat(EnumCycler.rotateEnum(TestEnum.A, false, EnumSet.of(TestEnum.A))).isEqualTo(TestEnum.A); + assertThat(EnumCycler.rotateEnum(TestEnum.A, true, EnumSet.of(TestEnum.A))).isEqualTo(TestEnum.A); + } + + @Test + void testNext() { + assertThat(EnumCycler.next(TestEnum.A)).isEqualTo(TestEnum.B); + assertThat(EnumCycler.next(TestEnum.B)).isEqualTo(TestEnum.C); + assertThat(EnumCycler.next(TestEnum.C)).isEqualTo(TestEnum.A); + assertThat(EnumCycler.next(SingleLiteralEnum.A)).isEqualTo(SingleLiteralEnum.A); + } + + @Test + void testPrev() { + assertThat(EnumCycler.prev(TestEnum.A)).isEqualTo(TestEnum.C); + assertThat(EnumCycler.prev(TestEnum.B)).isEqualTo(TestEnum.A); + assertThat(EnumCycler.prev(TestEnum.C)).isEqualTo(TestEnum.B); + assertThat(EnumCycler.prev(SingleLiteralEnum.A)).isEqualTo(SingleLiteralEnum.A); + } + + enum TestEnum { + A, + B, + C + } + + enum RotateTestEnum { + A, + B, + C, + D, + E + } + + enum SingleLiteralEnum { + A + } + +} \ No newline at end of file diff --git a/src/test/java/appeng/util/UUIDMatcherTest.java b/src/test/java/appeng/util/UUIDMatcherTest.java deleted file mode 100644 index 69e3c6d13..000000000 --- a/src/test/java/appeng/util/UUIDMatcherTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. - * - * Applied Energistics 2 is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Applied Energistics 2 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Applied Energistics 2. If not, see . - */ - -package appeng.util; - - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - - -/** - * Tests for {@link UUIDMatcher} - */ -public final class UUIDMatcherTest -{ - private static final String IS_UUID = "03ba29a1-d6bd-32ba-90b2-375e4d65abc9"; - private static final String NO_UUID = "no"; - private static final String INVALID_UUID = "g3ba29a1-d6bd-32ba-90b2-375e4d65abc9"; - - private final UUIDMatcher matcher; - - public UUIDMatcherTest() - { - this.matcher = new UUIDMatcher(); - } - - @Test - public void testUUID_shouldPass() - { - assertTrue( this.matcher.isUUID( IS_UUID ) ); - } - - @Test - public void testNoUUD_shouldPass() - { - assertFalse( this.matcher.isUUID( NO_UUID ) ); - } - - @Test - public void testInvalidUUID_shouldPass() - { - assertFalse( this.matcher.isUUID( INVALID_UUID ) ); - } -} diff --git a/src/test/java/appeng/util/WideReadableNumberConverterTest.java b/src/test/java/appeng/util/WideReadableNumberConverterTest.java index eb57ea6eb..f48d4929d 100644 --- a/src/test/java/appeng/util/WideReadableNumberConverterTest.java +++ b/src/test/java/appeng/util/WideReadableNumberConverterTest.java @@ -21,7 +21,7 @@ package appeng.util; import static org.junit.Assert.assertEquals; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** diff --git a/src/test/java/appeng/util/helpers/P2PHelperTest.java b/src/test/java/appeng/util/helpers/P2PHelperTest.java index e18c35828..9cb6e1bb3 100644 --- a/src/test/java/appeng/util/helpers/P2PHelperTest.java +++ b/src/test/java/appeng/util/helpers/P2PHelperTest.java @@ -22,7 +22,7 @@ package appeng.util.helpers; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import org.junit.Test; +import org.junit.jupiter.api.Test; import appeng.api.util.AEColor; diff --git a/src/test/java/appeng/util/item/AESharedItemStackTest.java b/src/test/java/appeng/util/item/AESharedItemStackTest.java new file mode 100644 index 000000000..262dacaa9 --- /dev/null +++ b/src/test/java/appeng/util/item/AESharedItemStackTest.java @@ -0,0 +1,78 @@ +package appeng.util.item; + +import java.util.IdentityHashMap; +import java.util.Map; + +import com.google.common.testing.EqualsTester; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.registry.Bootstrap; +import net.minecraft.util.text.StringTextComponent; + +class AESharedItemStackTest { + + @BeforeAll + static void bootstrap() { + Bootstrap.register(); + } + + // Test stack -> Name for debugging the tests + final Map stacks = new IdentityHashMap<>(); + + AESharedItemStackTest() { + TestItemWithCaps TEST_ITEM = new TestItemWithCaps(); + + ItemStack nameTag1 = new ItemStack(TEST_ITEM); + stacks.put(new AESharedItemStack(nameTag1), "no-nbt"); + + // NBT + ItemStack nameTag2 = new ItemStack(TEST_ITEM); + nameTag2.setDisplayName(new StringTextComponent("Hello World")); + stacks.put(new AESharedItemStack(nameTag2), "nbt1"); + + // Different NBT + ItemStack nameTag3 = new ItemStack(TEST_ITEM); + nameTag3.setDisplayName(new StringTextComponent("ABCDEFGH")); + stacks.put(new AESharedItemStack(nameTag3), "nbt2"); + + // NBT + Cap + CompoundNBT capNbt = new CompoundNBT(); + capNbt.putInt("Parent", 1); + ItemStack nameTag4 = new ItemStack(TEST_ITEM, 1, capNbt); + nameTag4.setDisplayName(new StringTextComponent("Hello World")); + stacks.put(new AESharedItemStack(nameTag4), "nbt1+cap1"); + + // NBT + Different Cap + CompoundNBT capNbt2 = new CompoundNBT(); + capNbt2.putInt("Parent", 123); + ItemStack nameTag5 = new ItemStack(TEST_ITEM, 1, capNbt2); + nameTag5.setDisplayName(new StringTextComponent("Hello World")); + stacks.put(new AESharedItemStack(nameTag5), "nbt1+cap2"); + } + + /** + * Tests equality between shared item stacks. + */ + @Test + void testEquals() { + EqualsTester tester = new EqualsTester(); + for (AESharedItemStack stack : stacks.keySet()) { + // Add the stack, and a pristine copy of the stack + tester.addEqualityGroup(stack, new AESharedItemStack(stack.getDefinition().copy())); + } + + // Test that using the same item stack instance makes two separate shared stacks equal + ItemStack itemStack = new ItemStack(Items.CRAFTING_TABLE); + tester.addEqualityGroup( + new AESharedItemStack(itemStack), + new AESharedItemStack(itemStack)); + + tester.testEquals(); + } + +} \ No newline at end of file diff --git a/src/test/java/appeng/util/item/FuzzyItemVariantListTest.java b/src/test/java/appeng/util/item/FuzzyItemVariantListTest.java new file mode 100644 index 000000000..cf0885992 --- /dev/null +++ b/src/test/java/appeng/util/item/FuzzyItemVariantListTest.java @@ -0,0 +1,157 @@ +package appeng.util.item; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; + +import appeng.api.config.FuzzyMode; + +public class FuzzyItemVariantListTest { + + @Test + void testOrderForDamagedItems() { + // Diamond Sword @ 100% durability + ItemStack undamagedSword = new ItemStack(Items.DIAMOND_SWORD); + AESharedItemStack undamagedStack = new AESharedItemStack(undamagedSword); + + // Unenchanted Diamond Sword @ 0% durability + ItemStack damagedSword = new ItemStack(Items.DIAMOND_SWORD); + damagedSword.setDamage(damagedSword.getMaxDamage()); + AESharedItemStack damagedStack = new AESharedItemStack(damagedSword); + + // Create a list of stacks and sort by their natural order + AESharedItemStack[] stacks = new AESharedItemStack[] { + damagedStack, undamagedStack + }; + Arrays.sort(stacks, FuzzyItemVariantList.COMPARATOR); + assertThat(stacks).containsExactly(damagedStack, undamagedStack); + } + + @Nested + class Bounds { + final ItemStack stack = new ItemStack(Items.DIAMOND_SWORD); + final ItemStack damagedStack; + + { + damagedStack = stack.copy(); + damagedStack.setDamage(damagedStack.getMaxDamage()); + } + + @Test + void testIgnoreAll() { + DamageBounds bounds = new DamageBounds(stack, FuzzyMode.IGNORE_ALL); + assertEquals(stack.getMaxDamage(), bounds.lower.itemDamage); + assertEquals(-1, bounds.upper.itemDamage); + } + + /** + * PERCENT_99 with an undamaged item should select only undamaged items, which translates to a damage range of + * [0, -1). + */ + @Test + void test99PercentDurabilityWithUndamagedItem() { + DamageBounds bounds = new DamageBounds(stack, FuzzyMode.PERCENT_99); + assertEquals(0, bounds.lower.itemDamage); + assertEquals(-1, bounds.upper.itemDamage); + } + + /** + * PERCENT_99 with a damaged item should select only damaged items, which translates to a damage range of + * [maxDmg, 0). + */ + @Test + void test99PercentDurabilityWithDamagedItem() { + DamageBounds bounds = new DamageBounds(damagedStack, FuzzyMode.PERCENT_99); + assertEquals(stack.getMaxDamage(), bounds.lower.itemDamage); + assertEquals(0, bounds.upper.itemDamage); + } + + /** + * PERCENT_75 with an undamaged item should select items that have 75% or more durability, which should + * translate to a damage range of [0.25*maxDmg, -1). + */ + @Test + void test75PercentWithUndamagedItem() { + DamageBounds bounds = new DamageBounds(stack, FuzzyMode.PERCENT_75); + assertEquals((int) (0.25 * stack.getMaxDamage()), bounds.lower.itemDamage); + assertEquals(-1, bounds.upper.itemDamage); + } + + /** + * PERCENT_75 with a damaged item should select items that have less than 75% durability, which should translate + * to a damage range of [maxDmg, 0.25*maxDmg). + */ + @Test + void test75PercentWithDamagedItem() { + DamageBounds bounds = new DamageBounds(damagedStack, FuzzyMode.PERCENT_75); + assertEquals(stack.getMaxDamage(), bounds.lower.itemDamage); + assertEquals((int) (0.25 * stack.getMaxDamage()), bounds.upper.itemDamage); + } + + /** + * PERCENT_50 with an undamaged item should select items that have 50% or more durability, which should + * translate to a damage range of [0.50*maxDmg, -1). + */ + @Test + void test50PercentWithUndamagedItem() { + DamageBounds bounds = new DamageBounds(stack, FuzzyMode.PERCENT_50); + assertEquals((int) (0.50 * stack.getMaxDamage()), bounds.lower.itemDamage); + assertEquals(-1, bounds.upper.itemDamage); + } + + /** + * PERCENT_50 with a damaged item should select items that have less than 50% durability, which should translate + * to a damage range of [maxDmg, 0.50*maxDmg). + */ + @Test + void test50PercentWithDamagedItem() { + DamageBounds bounds = new DamageBounds(damagedStack, FuzzyMode.PERCENT_50); + assertEquals(stack.getMaxDamage(), bounds.lower.itemDamage); + assertEquals((int) (0.50 * stack.getMaxDamage()), bounds.upper.itemDamage); + } + + /** + * PERCENT_25 with an undamaged item should select items that have 25% or more durability, which should + * translate to a damage range of [0.75*maxDmg, -1). + */ + @Test + void test25PercentWithUndamagedItem() { + DamageBounds bounds = new DamageBounds(stack, FuzzyMode.PERCENT_25); + assertEquals((int) (0.75 * stack.getMaxDamage()), bounds.lower.itemDamage); + assertEquals(-1, bounds.upper.itemDamage); + } + + /** + * PERCENT_25 with a damaged item should select items that have less than 25% durability, which should translate + * to a damage range of [maxDmg, 0.75*maxDmg). + */ + @Test + void test25PercentWithDamagedItem() { + DamageBounds bounds = new DamageBounds(damagedStack, FuzzyMode.PERCENT_25); + assertEquals(stack.getMaxDamage(), bounds.lower.itemDamage); + assertEquals((int) (0.75 * stack.getMaxDamage()), bounds.upper.itemDamage); + } + + } + + private static class DamageBounds { + final FuzzyItemVariantList.ItemDamageBound lower; + final FuzzyItemVariantList.ItemDamageBound upper; + + public DamageBounds(ItemStack stack, FuzzyMode mode) { + lower = FuzzyItemVariantList.makeLowerBound(stack, mode); + upper = FuzzyItemVariantList.makeUpperBound(stack, mode); + + // This may be counter intuitive, but the map is sorted in descending order of item damage + assertThat(lower.itemDamage).isGreaterThan(upper.itemDamage); + } + } + +} diff --git a/src/test/java/appeng/util/item/ItemListTest.java b/src/test/java/appeng/util/item/ItemListTest.java new file mode 100644 index 000000000..2d3dcfa0b --- /dev/null +++ b/src/test/java/appeng/util/item/ItemListTest.java @@ -0,0 +1,452 @@ +package appeng.util.item; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.text.StringTextComponent; + +import appeng.api.config.FuzzyMode; +import appeng.api.storage.data.IAEItemStack; + +public class ItemListTest { + + ItemList itemList = new ItemList(); + + /** + * add should merge item stacks by adding stored/requestable counts, and setting craftable if it wasn't set before. + */ + @Test + public void testAddMergesAllStackProperties() { + itemList.add(diamondSword(100, 1, 0, false)); + assertPreciseStackProperties(diamondSwordFilter(100), 1, 0, false); + + itemList.add(diamondSword(100, 0, 1, false)); + assertPreciseStackProperties(diamondSwordFilter(100), 1, 1, false); + + itemList.add(diamondSword(100, 0, 0, true)); + assertPreciseStackProperties(diamondSwordFilter(100), 1, 1, true); + } + + /** + * addStorage only considers {@link IAEItemStack#getStackSize()} and ignores other properties when merging stacks, + * but inherits all properties when it's adding a new item. + */ + @Test + public void testAddStorageForNewItem() { + // TODO: This might actually be incorrect, given how addrequestable et al behave + itemList.addStorage(diamondSword(100, 1, 1, true)); + assertPreciseStackProperties(diamondSwordFilter(100), 1, 1, true); + } + + @Test + public void testAddStorageForExistingItem() { + itemList.addStorage(diamondSword(100, 1, 0, false)); + itemList.addStorage(diamondSword(100, 1, 2, true)); + assertPreciseStackProperties(diamondSwordFilter(100), 2, 0, false); + } + + /** + * addRequestable only considers {@link IAEItemStack#getCountRequestable()} and sets the stored amount to 0 and + * craftable to false when adding an item. + */ + @Test + public void testAddRequestableForNewItem() { + itemList.addRequestable(diamondSword(100, 1, 2, true)); + assertPreciseStackProperties(diamondSwordFilter(100), 0, 2, false); + } + + /** + * addRequestable only considers {@link IAEItemStack#getCountRequestable()} when merging into an existing item. + */ + @Test + public void testAddRequestableForExistingItem() { + itemList.addRequestable(diamondSword(100, 0, 1, false)); + itemList.addRequestable(diamondSword(100, 2, 1, true)); + assertPreciseStackProperties(diamondSwordFilter(100), 0, 2, false); + } + + /** + * addCraftable only considers {@link IAEItemStack#isCraftable()} and sets the stored and requestable amounts to 0 + * when adding an item. + */ + @Test + public void testAddCraftingForNewItem() { + itemList.addCrafting(diamondSword(100, 1, 2, true)); + // TODO: I think it is unintended that the requestable amount is used + assertPreciseStackProperties(diamondSwordFilter(100), 0, 2, true); + } + + /** + * addRequestable only considers {@link IAEItemStack#getCountRequestable()} when merging into an existing item. + */ + @Test + public void testAddCraftingForExistingItem() { + itemList.addCrafting(diamondSword(100, 0, 0, false)); + itemList.addCrafting(diamondSword(100, 1, 2, true)); + assertPreciseStackProperties(diamondSwordFilter(100), 0, 0, true); + } + + /** + * an empty craftable stack still creates an entry in the list + */ + @Test + public void testAddEmptyStackThatIsCraftable() { + itemList.add(diamondSword(100, 0, 0, true)); + assertPreciseStackProperties(diamondSwordFilter(100), 0, 0, true); + } + + /** + * check that craftable isn't accidentally reset to false when merging stacks + */ + @Test + public void testAddDoesNotResetCraftableBackToFalse() { + itemList.add(diamondSword(100, 0, 0, true)); + itemList.add(diamondSword(100, 1, 0, false)); + assertPreciseStackProperties(diamondSwordFilter(100), 1, 0, true); + } + + /** + * stacks for the same item, but different damage values should not be merged + */ + @Test + public void testAddDoesNotMergeAcrossDamageValues() { + itemList.add(diamondSword(100, 1, 0, false)); + itemList.add(diamondSword(99, 1, 0, false)); + assertPreciseStackProperties(diamondSwordFilter(100), 1, 0, false); + assertPreciseStackProperties(diamondSwordFilter(99), 1, 0, false); + } + + private void assertPreciseStackProperties(IAEItemStack stack, long stored, long requestable, boolean craftable) { + IAEItemStack storedStack = itemList.findPrecise(stack); + assertEquals(stored, storedStack.getStackSize(), "stored amount"); + assertEquals(requestable, storedStack.getCountRequestable(), "requestable amount"); + assertEquals(craftable, storedStack.isCraftable(), "craftable"); + } + + /** + * Even if the stack has no stored or requestable amounts, it should be returned by the item list if it is + * craftable. + */ + @Test + public void testSizeAndIterateForEmptyButCraftableStack() { + itemList.add(diamondSword(100, 0, 0, true)); + assertListContent(diamondSword(100, 0, 0, true)); + } + + /** + * If the stack is not craftable, an empty stack is actually ignored. + */ + @Test + public void testEmptyStackIsIgnored() { + itemList.add(diamondSword(100, 0, 0, false)); + assertListContent(); + } + + @Test + public void testResetStatus() { + itemList.add(diamondSword(100, 1, 0, false)); + itemList.add(nameTag(1, 0, false)); + assertEquals(2, itemList.size()); + + itemList.resetStatus(); + + assertListContent(); // The list should now be empty + } + + /** + * Tests that iteration across multiple items and variations of those items works. + */ + @Test + public void testIterateAcrossMultipleItems() { + // Add damaged variants of the same item, including NBT variants + AEItemStack sword1 = diamondSword(100, 1, 0, false); + itemList.add(sword1); + AEItemStack sword2 = diamondSword(50, 1, 0, false); + itemList.add(sword2); + AEItemStack sword3 = diamondSword(25, 1, 0, false); + itemList.add(sword3); + AEItemStack sword4 = diamondSword(100, "master sword", 1, 0, false); + itemList.add(sword4); + // And a non-damagable item with different NBT + AEItemStack nameTag1 = nameTag(1, 0, false); + itemList.add(nameTag1); + AEItemStack nameTag2 = nameTag("bob", 1, 0, false); + itemList.add(nameTag2); + + assertListContent(sword1, sword2, sword3, sword4, nameTag1, nameTag2); + } + + @Test + void testConcurrentModificationByAddingItemType() { + AEItemStack sword = diamondSword(100, 1, 0, false); + itemList.add(sword); + AEItemStack nameTag = nameTag(1, 0, false); + itemList.add(nameTag); + AEItemStack craftingTable = AEItemStack.fromItemStack(new ItemStack(Items.CRAFTING_TABLE)); + + assertThrows(ConcurrentModificationException.class, () -> { + Iterator it = itemList.iterator(); + itemList.add(craftingTable); + assertThat(Iterators.toArray(it, IAEItemStack.class)).containsOnly(sword, nameTag); + }); + } + + /** + * Regression test for broken iterators that mutated state in {@see Iterator#hasNext}. This was the case for both + * the top-level and sub-iterator. + */ + @Test + public void testIteratorHasNextDoesNotSkipItems() { + itemList.add(diamondSword(100, 1, 0, false)); + itemList.add(diamondSword(50, 1, 0, false)); + + Iterator it = itemList.iterator(); + assertTrue(it.hasNext()); + assertTrue(it.hasNext()); + assertTrue(it.hasNext()); + } + + @Test + public void testGetFirstItemForEmptyList() { + assertNull(itemList.getFirstItem()); + } + + @Test + public void testGetFirstItem() { + AEItemStack itemStack = diamondSword(100, 1, 0, false); + itemList.add(itemStack); + // The order is no longer well defined w.r.t. the hashmap + assertEquals(itemStack, itemList.getFirstItem()); + } + + @Nested + class FindFuzzyDamageableItems { + + // Swords to cover all durability values + AEItemStack swordAbove100 = diamondSword(101, 1, 0, false); + AEItemStack[] swords = new AEItemStack[101]; + // Filters for inverting the filter as needed + AEItemStack undamagedFilter = diamondSwordFilter(100); + AEItemStack damagedFilter = diamondSwordFilter(0); + + @BeforeEach + void addItems() { + itemList.add(swordAbove100); + for (int i = 0; i <= 100; i++) { + swords[i] = diamondSword(i, 1, 0, false); + assertEquals(i, getDurabilityPercent(swords[i])); + itemList.add(swords[i]); + } + } + + @Test + public void testIgnoreAllWithUndamagedFilter() { + assertReturnedDurabilities(undamagedFilter, FuzzyMode.IGNORE_ALL, 0, 100, false); + } + + @Test + public void testIgnoreAllWithDamagedFilter() { + assertReturnedDurabilities(damagedFilter, FuzzyMode.IGNORE_ALL, 0, 100, false); + } + + @Test + public void testPercent99WithUndamagedFilter() { + assertReturnedDurabilities(undamagedFilter, FuzzyMode.PERCENT_99, 100, 100, false); + } + + @Test + public void testPercent99WithDamagedFilter() { + assertReturnedDurabilities(damagedFilter, FuzzyMode.PERCENT_99, 0, 99, false); + } + + @Test + public void testPercent75WithUndamagedFilter() { + assertReturnedDurabilities(undamagedFilter, FuzzyMode.PERCENT_75, 75, 100, false); + } + + @Test + public void testPercent75WithDamagedFilter() { + assertReturnedDurabilities(damagedFilter, FuzzyMode.PERCENT_75, 0, 74, false); + } + + @Test + public void testPercent50WithUndamagedFilter() { + assertReturnedDurabilities(undamagedFilter, FuzzyMode.PERCENT_50, 50, 100, false); + } + + @Test + public void testPercent50WithDamagedFilter() { + assertReturnedDurabilities(damagedFilter, FuzzyMode.PERCENT_50, 0, 49, false); + } + + @Test + public void testPercent25WithUndamagedFilter() { + assertReturnedDurabilities(undamagedFilter, FuzzyMode.PERCENT_25, 25, 100, false); + } + + @Test + public void testPercent25WithDamagedFilter() { + assertReturnedDurabilities(damagedFilter, FuzzyMode.PERCENT_25, 0, 24, false); + } + + private void assertReturnedDurabilities(IAEItemStack filter, FuzzyMode fuzzyMode, int minDurabilityInclusive, + int maxDurabilityInclusive, boolean above100) { + Collection items = itemList.findFuzzy(filter, fuzzyMode); + + // Build a list of the durabilities that got returned + List durabilities = items.stream().map(this::getDurabilityPercent) + .sorted() + .collect(Collectors.toList()); + + // Build a sorted list of the durabilities we expect + List expectedDurabilities = new ArrayList<>(); + for (int i = minDurabilityInclusive; i <= maxDurabilityInclusive; i++) { + expectedDurabilities.add(getDurabilityPercent(swords[i])); + } + if (above100) { + expectedDurabilities.add(getDurabilityPercent(swordAbove100)); + } + expectedDurabilities.sort(Integer::compare); + + assertEquals(expectedDurabilities, durabilities); + } + + private int getDurabilityPercent(IAEItemStack stack) { + if (stack == swordAbove100) { + return 101; + } + return (int) ((1.0f - (stack.getItemDamage() / (float) stack.getDefinition().getMaxDamage())) * 100); + } + } + + @Test + void testFindFuzzyForNormalItems() { + AEItemStack item1 = nameTag(null, 1, 0, false); + itemList.add(item1); + AEItemStack item2 = nameTag("name1", 1, 0, false); + itemList.add(item2); + AEItemStack item3 = nameTag("name2", 1, 0, false); + itemList.add(item3); + // Add another item to ensure this is not returned + itemList.add(AEItemStack.fromItemStack(new ItemStack(Items.CRAFTING_TABLE))); + + for (FuzzyMode fuzzyMode : FuzzyMode.values()) { + Collection result = itemList.findFuzzy(nameTag(null, 0, 0, false), fuzzyMode); + assertThat(result).containsOnly(item1, item2, item3); + } + } + + /** + * Tests how ItemList behaves w.r.t. null arguments, given that {@link AEItemStack#fromItemStack(ItemStack)} can + * return null for an empty stack, this sometimes leaks into method parameters. As such, methods should behave as if + * an empty item stack was passed. + */ + @Nested + class NullArguments { + @BeforeEach + void addItem() { + itemList.add(diamondSword(100, 1, 0, false)); + } + + @Test + void testFindFuzzy() { + assertThat(itemList.findFuzzy(null, FuzzyMode.PERCENT_99)).isEmpty(); + } + + @Test + void testFindPrecise() { + assertThat(itemList.findPrecise(null)).isNull(); + } + + @Test + void testAdd() { + assertThat(itemList.size()).isEqualTo(1); + itemList.add(null); + assertThat(itemList.size()).isEqualTo(1); + } + + @Test + void testAddStorage() { + assertThat(itemList.size()).isEqualTo(1); + itemList.addStorage(null); + assertThat(itemList.size()).isEqualTo(1); + } + + @Test + void testAddRequestable() { + assertThat(itemList.size()).isEqualTo(1); + itemList.addRequestable(null); + assertThat(itemList.size()).isEqualTo(1); + } + + @Test + void testAddCrafting() { + assertThat(itemList.size()).isEqualTo(1); + itemList.addCrafting(null); + assertThat(itemList.size()).isEqualTo(1); + } + } + + private void assertListContent(AEItemStack... stacks) { + assertEquals(stacks.length == 0, itemList.isEmpty(), "isEmpty"); + assertEquals(stacks.length, itemList.size()); + assertEquals(ImmutableSet.copyOf(stacks), ImmutableSet.copyOf(itemList)); + } + + private AEItemStack diamondSwordFilter(int durabilityPercent) { + return diamondSword(durabilityPercent, 0, 0, false); + } + + private AEItemStack diamondSword(int durabilityPercent, long stored, long requestable, boolean craftable) { + return diamondSword(durabilityPercent, null, stored, requestable, craftable); + } + + private AEItemStack diamondSword(int durabilityPercent, String customName, long stored, long requestable, + boolean craftable) { + ItemStack is = new ItemStack(Items.DIAMOND_SWORD); + if (customName != null) { + is.setDisplayName(new StringTextComponent(customName)); + } + int damage = (int) ((100 - durabilityPercent) / 100.0f * is.getMaxDamage()); + is.setDamage(damage); + AEItemStack ais = AEItemStack.fromItemStack(is); + ais.setStackSize(stored); + ais.setCountRequestable(requestable); + ais.setCraftable(craftable); + return ais; + } + + // customName can be used to create items that differ in NBT + private AEItemStack nameTag(long stored, long requestable, boolean craftable) { + return nameTag(null, stored, requestable, craftable); + } + + private AEItemStack nameTag(String customName, long stored, long requestable, boolean craftable) { + ItemStack is = new ItemStack(Items.NAME_TAG); + if (customName != null) { + is.setDisplayName(new StringTextComponent(customName)); + } + AEItemStack ais = AEItemStack.fromItemStack(is); + ais.setStackSize(stored); + ais.setCountRequestable(requestable); + ais.setCraftable(craftable); + return ais; + } + +} \ No newline at end of file diff --git a/src/test/java/appeng/util/item/TestItemWithCaps.java b/src/test/java/appeng/util/item/TestItemWithCaps.java new file mode 100644 index 000000000..9ece399ae --- /dev/null +++ b/src/test/java/appeng/util/item/TestItemWithCaps.java @@ -0,0 +1,60 @@ +package appeng.util.item; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.IntNBT; +import net.minecraft.util.Direction; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.common.util.INBTSerializable; +import net.minecraftforge.common.util.LazyOptional; + +public class TestItemWithCaps extends Item { + public TestItemWithCaps() { + super(new Properties()); + setRegistryName("appliedenergistics2:test_item"); + } + + @Nullable + @Override + public ICapabilityProvider initCapabilities(ItemStack stack, @Nullable CompoundNBT nbt) { + if (nbt == null) { + return null; + } else { + return new CapabilityProvider(); + } + } + + /** + * Simple capability provider that just has a single counter value to produce different NBT. + */ + public static class CapabilityProvider implements ICapabilityProvider, INBTSerializable { + private int counter; + + @Nonnull + @Override + public LazyOptional getCapability(@Nonnull Capability cap, @Nullable Direction side) { + return LazyOptional.empty(); + } + + @Nonnull + @Override + public LazyOptional getCapability(@Nonnull Capability cap) { + return LazyOptional.empty(); + } + + @Override + public IntNBT serializeNBT() { + return IntNBT.valueOf(counter); + } + + @Override + public void deserializeNBT(IntNBT nbt) { + counter = nbt.getInt(); + } + } +} From 011b3a4b947e26b710acadf8ababaec7dad1678d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Salom=C3=A3o?= Date: Wed, 31 Mar 2021 20:53:26 -0300 Subject: [PATCH 2/2] backport 0bdc408e950fe787277d1e48be61ebf928127429 --- src/api/java/appeng/api/config/FuzzyMode.java | 3 - .../appeng/api/storage/data/IAEItemStack.java | 9 + .../appeng/api/storage/data/IAEStack.java | 52 +- .../java/appeng/fluids/util/AEFluidStack.java | 23 +- src/main/java/appeng/util/UUIDMatcher.java | 51 ++ .../java/appeng/util/item/AEItemStack.java | 506 ++++++++---------- .../appeng/util/item/AEItemStackRegistry.java | 69 +-- .../appeng/util/item/AESharedItemStack.java | 41 +- src/main/java/appeng/util/item/AEStack.java | 269 +++++----- .../util/item/FuzzyItemVariantList.java | 8 +- src/main/java/appeng/util/item/ItemList.java | 81 ++- .../util/item/MeaningfulItemIterator.java | 12 +- 12 files changed, 524 insertions(+), 600 deletions(-) create mode 100644 src/main/java/appeng/util/UUIDMatcher.java diff --git a/src/api/java/appeng/api/config/FuzzyMode.java b/src/api/java/appeng/api/config/FuzzyMode.java index d8353fac5..9e9ae6935 100644 --- a/src/api/java/appeng/api/config/FuzzyMode.java +++ b/src/api/java/appeng/api/config/FuzzyMode.java @@ -56,9 +56,6 @@ public enum FuzzyMode { */ public final float percentage; - public final float breakPoint; - public final float percentage; - FuzzyMode( final float p ) { this.percentage = p; diff --git a/src/api/java/appeng/api/storage/data/IAEItemStack.java b/src/api/java/appeng/api/storage/data/IAEItemStack.java index f0e983c97..7e6a8a7f5 100644 --- a/src/api/java/appeng/api/storage/data/IAEItemStack.java +++ b/src/api/java/appeng/api/storage/data/IAEItemStack.java @@ -112,4 +112,13 @@ public interface IAEItemStack extends IAEStack * @return definition stack */ ItemStack getDefinition(); + + /** + * Compare this AE item stack to another item stack, but ignores the amount. It checks the item type, NBT and damage + * values. + * + * @param is An item stack + */ + boolean equals(ItemStack is); + } \ No newline at end of file diff --git a/src/api/java/appeng/api/storage/data/IAEStack.java b/src/api/java/appeng/api/storage/data/IAEStack.java index 6a3d86ed3..94bc6fa96 100644 --- a/src/api/java/appeng/api/storage/data/IAEStack.java +++ b/src/api/java/appeng/api/storage/data/IAEStack.java @@ -23,27 +23,24 @@ package appeng.api.storage.data; - import java.io.IOException; import io.netty.buffer.ByteBuf; - import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.PacketBuffer; import appeng.api.config.FuzzyMode; import appeng.api.storage.IStorageChannel; - -public interface IAEStack> -{ +public interface IAEStack> { /** * add two stacks together * * @param is added item */ - void add( T is ); + void add(T is); /** * number of items in the stack. @@ -57,7 +54,7 @@ public interface IAEStack> * * @param stackSize , ItemStack.stackSize = N */ - T setStackSize( long stackSize ); + T setStackSize(long stackSize); /** * Same as getStackSize, but for requestable items. ( LP ) @@ -71,7 +68,7 @@ public interface IAEStack> * * @return basically itemStack.stackSize = N but for setStackSize items. */ - T setCountRequestable( long countRequestable ); + T setCountRequestable(long countRequestable); /** * true, if the item can be crafted. @@ -85,7 +82,7 @@ public interface IAEStack> * * @param isCraftable can item be crafted */ - T setCraftable( boolean isCraftable ); + T setCraftable(boolean isCraftable); /** * clears, requestable, craftable, and stack sizes. @@ -104,33 +101,33 @@ public interface IAEStack> * * @param i additional stack size */ - void incStackSize( long i ); + void incStackSize(long i); /** * removes some from the stack size. */ - void decStackSize( long i ); + void decStackSize(long i); /** * adds items to the requestable * * @param i increased amount of requested items */ - void incCountRequestable( long i ); + void incCountRequestable(long i); /** * removes items from the requestable * * @param i decreased amount of requested items */ - void decCountRequestable( long i ); + void decCountRequestable(long i); /** - * write to a NBTTagCompound. + * write to a CompoundNBT. * * @param i to be written data */ - void writeToNBT( NBTTagCompound i ); + void writeToNBT( NBTTagCompound i); /** * Compare stacks using precise logic. @@ -146,17 +143,18 @@ public interface IAEStack> * @return true if they are the same. */ @Override - boolean equals( Object obj ); + boolean equals(Object obj); /** - * Compare the same subtype of {@link IAEStack} with another using a fuzzy comparison. + * Compare the same subtype of {@link IAEStack} with another using a fuzzy + * comparison. * * @param other The stack to compare. - * @param mode Which {@link FuzzyMode} should be used. + * @param mode Which {@link FuzzyMode} should be used. * * @return true if two stacks are equal based on AE Fuzzy Comparison. */ - boolean fuzzyComparison( T other, FuzzyMode mode ); + boolean fuzzyComparison(T other, FuzzyMode mode); /** * Slower for disk saving, but smaller/more efficient for packets. @@ -165,7 +163,7 @@ public interface IAEStack> * * @throws IOException */ - void writeToPacket( ByteBuf data ) throws IOException; + void writeToPacket( ByteBuf data) throws IOException; /** * Clone the Item / Fluid Stack @@ -182,17 +180,7 @@ public interface IAEStack> T empty(); /** - * @return true if the stack is a {@link IAEItemStack} - */ - boolean isItem(); - - /** - * @return true if the stack is a {@link IAEFluidStack} - */ - boolean isFluid(); - - /** - * @return ITEM or FLUID + * @return The {@link IStorageChannel} backing this stack. */ IStorageChannel getChannel(); @@ -202,4 +190,4 @@ public interface IAEStack> * @return itemstack */ ItemStack asItemStackRepresentation(); -} +} \ No newline at end of file diff --git a/src/main/java/appeng/fluids/util/AEFluidStack.java b/src/main/java/appeng/fluids/util/AEFluidStack.java index d04652bcb..2acb30aba 100644 --- a/src/main/java/appeng/fluids/util/AEFluidStack.java +++ b/src/main/java/appeng/fluids/util/AEFluidStack.java @@ -32,6 +32,7 @@ import io.netty.buffer.ByteBuf; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.PacketBuffer; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidStack; @@ -216,18 +217,6 @@ public final class AEFluidStack extends AEStack implements IAEFlu return dup; } - @Override - public boolean isItem() - { - return false; - } - - @Override - public boolean isFluid() - { - return true; - } - @Override public IStorageChannel getChannel() { @@ -318,17 +307,17 @@ public final class AEFluidStack extends AEStack implements IAEFlu } @Override - public void writeToPacket( final ByteBuf buffer ) throws IOException + public void writeToPacket( final ByteBuf i ) throws IOException { final byte mask = (byte) ( ( this.getType( this.getStackSize() ) << 2 ) | ( this .getType( this.getCountRequestable() ) << 4 ) | ( (byte) ( this.isCraftable() ? 1 : 0 ) << 6 ) | ( this.hasTagCompound() ? 1 : 0 ) << 7 ); - buffer.writeByte( mask ); + i.writeByte( mask ); - this.writeToStream( buffer ); + this.writeToStream( i ); - this.putPacketValue( buffer, this.getStackSize() ); - this.putPacketValue( buffer, this.getCountRequestable() ); + this.putPacketValue( i, this.getStackSize() ); + this.putPacketValue( i, this.getCountRequestable() ); } private void writeToStream( final ByteBuf buffer ) throws IOException diff --git a/src/main/java/appeng/util/UUIDMatcher.java b/src/main/java/appeng/util/UUIDMatcher.java new file mode 100644 index 000000000..bd8dab7ac --- /dev/null +++ b/src/main/java/appeng/util/UUIDMatcher.java @@ -0,0 +1,51 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.util; + + +import java.util.regex.Pattern; + + +/** + * Regex wrapper for {@link java.util.UUID}s to not rely on try catch + */ +public final class UUIDMatcher +{ + /** + * String which is the regular expression for {@link java.util.UUID}s + */ + private static final String UUID_REGEX = "[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}"; + + /** + * Pattern which pre-compiles the {@link appeng.util.UUIDMatcher#UUID_REGEX} + */ + private static final Pattern PATTERN = Pattern.compile( UUID_REGEX ); + + /** + * Checks if a potential {@link java.util.UUID} is an {@link java.util.UUID} by applying a regular expression on it. + * + * @param potential to be checked potential {@link java.util.UUID} + * + * @return true, if the potential {@link java.util.UUID} is indeed an {@link java.util.UUID} + */ + public boolean isUUID( final CharSequence potential ) + { + return PATTERN.matcher( potential ).matches(); + } +} \ No newline at end of file diff --git a/src/main/java/appeng/util/item/AEItemStack.java b/src/main/java/appeng/util/item/AEItemStack.java index b2047f4bc..ecf171857 100644 --- a/src/main/java/appeng/util/item/AEItemStack.java +++ b/src/main/java/appeng/util/item/AEItemStack.java @@ -1,6 +1,6 @@ /* * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. + * Copyright (c) 2013 - 2020, AlgorithmX2, All rights reserved. * * Applied Energistics 2 is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -18,7 +18,6 @@ package appeng.util.item; - import java.util.List; import java.util.Objects; import java.util.Optional; @@ -26,348 +25,287 @@ import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import appeng.api.AEApi; import io.netty.buffer.ByteBuf; - import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.ResourceLocation; import net.minecraftforge.fml.common.network.ByteBufUtils; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.items.ItemHandlerHelper; -import appeng.api.AEApi; import appeng.api.config.FuzzyMode; import appeng.api.storage.IStorageChannel; import appeng.api.storage.channels.IItemStorageChannel; import appeng.api.storage.data.IAEItemStack; import appeng.util.Platform; +public final class AEItemStack extends AEStack implements IAEItemStack { -public final class AEItemStack extends AEStack implements IAEItemStack -{ - private AESharedItemStack sharedStack; - private Optional oreReference; + private final AESharedItemStack sharedStack; + private Optional oreReference; - @SideOnly( Side.CLIENT ) - private String displayName; - @SideOnly( Side.CLIENT ) - private List tooltip; - @SideOnly( Side.CLIENT ) - private ResourceLocation uniqueID; + @SideOnly( Side.CLIENT ) + private String displayName; + @SideOnly( Side.CLIENT ) + private List tooltip; - private AEItemStack( final AEItemStack is ) - { - this.setStackSize( is.getStackSize() ); - this.setCraftable( is.isCraftable() ); - this.setCountRequestable( is.getCountRequestable() ); - this.sharedStack = is.sharedStack; - this.oreReference = is.oreReference; - } + private AEItemStack(final AEItemStack is) { + this.setStackSize(is.getStackSize()); + this.setCraftable(is.isCraftable()); + this.setCountRequestable(is.getCountRequestable()); + this.sharedStack = is.sharedStack; + this.oreReference = is.oreReference; + } - private AEItemStack( final AESharedItemStack is, long size ) - { - this.sharedStack = is; - this.setStackSize( size ); - this.setCraftable( false ); - this.setCountRequestable( 0 ); - this.oreReference = OreHelper.INSTANCE.getOre( is.getDefinition() ); - } + private AEItemStack(final AESharedItemStack is, long size) { + this.sharedStack = is; + this.setStackSize(size); + this.setCraftable(false); + this.setCountRequestable(0); + this.oreReference = OreHelper.INSTANCE.getOre( is.getDefinition() ); + } - @Nullable - public static AEItemStack fromItemStack( @Nonnull final ItemStack stack ) - { - if( stack.isEmpty() ) - { - return null; - } + @Nullable + public static AEItemStack fromItemStack(@Nonnull final ItemStack stack) { + if (stack.isEmpty()) { + return null; + } - return new AEItemStack( AEItemStackRegistry.getRegisteredStack( stack ), stack.getCount() ); - } + return new AEItemStack(AEItemStackRegistry.getRegisteredStack(stack), stack.getCount()); + } - public static IAEItemStack fromNBT( final NBTTagCompound i ) - { - if( i == null ) - { - return null; - } + public static IAEItemStack fromNBT( final NBTTagCompound i ) + { + if( i == null ) + { + return null; + } - final ItemStack itemstack = new ItemStack( i ); - if( itemstack.isEmpty() ) - { - return null; - } + final ItemStack itemstack = new ItemStack( i ); + if( itemstack.isEmpty() ) + { + return null; + } - final AEItemStack item = AEItemStack.fromItemStack( itemstack ); - item.setStackSize( i.getLong( "Cnt" ) ); - item.setCountRequestable( i.getLong( "Req" ) ); - item.setCraftable( i.getBoolean( "Craft" ) ); - return item; - } + final AEItemStack item = AEItemStack.fromItemStack( itemstack ); + item.setStackSize( i.getLong( "Cnt" ) ); + item.setCountRequestable( i.getLong( "Req" ) ); + item.setCraftable( i.getBoolean( "Craft" ) ); + return item; + } - @Override - public void writeToNBT( final NBTTagCompound i ) - { - this.getDefinition().writeToNBT( i ); - i.setLong( "Cnt", this.getStackSize() ); - i.setLong( "Req", this.getCountRequestable() ); - i.setBoolean( "Craft", this.isCraftable() ); - } + @Override + public void writeToNBT( final NBTTagCompound i ) + { + this.getDefinition().writeToNBT( i ); + i.setLong( "Cnt", this.getStackSize() ); + i.setLong( "Req", this.getCountRequestable() ); + i.setBoolean( "Craft", this.isCraftable() ); + } - public static AEItemStack fromPacket( final ByteBuf data ) - { - final byte mask = data.readByte(); - final byte stackType = (byte) ( ( mask & 0x0C ) >> 2 ); - final byte countReqType = (byte) ( ( mask & 0x30 ) >> 4 ); - final boolean isCraftable = ( mask & 0x40 ) > 0; + public static AEItemStack fromPacket( final ByteBuf data ) + { + final byte mask = data.readByte(); + final byte stackType = (byte) ( ( mask & 0x0C ) >> 2 ); + final byte countReqType = (byte) ( ( mask & 0x30 ) >> 4 ); + final boolean isCraftable = ( mask & 0x40 ) > 0; - final ItemStack itemstack = new ItemStack( ByteBufUtils.readTag( data ) ); - final long stackSize = getPacketValue( stackType, data ); - final long countRequestable = getPacketValue( countReqType, data ); + final ItemStack itemstack = new ItemStack( ByteBufUtils.readTag( data ) ); + final long stackSize = getPacketValue( stackType, data ); + final long countRequestable = getPacketValue( countReqType, data ); - if( itemstack.isEmpty() ) - { - return null; - } + if( itemstack.isEmpty() ) + { + return null; + } - final AEItemStack item = new AEItemStack( AEItemStackRegistry.getRegisteredStack( itemstack ), stackSize ); - item.setCountRequestable( countRequestable ); - item.setCraftable( isCraftable ); - return item; - } + final AEItemStack item = new AEItemStack( AEItemStackRegistry.getRegisteredStack( itemstack ), stackSize ); + item.setCountRequestable( countRequestable ); + item.setCraftable( isCraftable ); + return item; + } - @Override - public void writeToPacket( final ByteBuf i ) - { - final byte mask = (byte) ( ( this.getType( this.getStackSize() ) << 2 ) | ( this - .getType( this.getCountRequestable() ) << 4 ) | ( (byte) ( this.isCraftable() ? 1 : 0 ) << 6 ) | ( this.hasTagCompound() ? 1 : 0 ) << 7 ); + @Override + public void writeToPacket( final ByteBuf i ) + { + final byte mask = (byte) ( ( this.getType( this.getStackSize() ) << 2 ) | ( this + .getType( this.getCountRequestable() ) << 4 ) | ( (byte) ( this.isCraftable() ? 1 : 0 ) << 6 ) | ( this.hasTagCompound() ? 1 : 0 ) << 7 ); - i.writeByte( mask ); - ByteBufUtils.writeTag( i, this.getDefinition().serializeNBT() ); - this.putPacketValue( i, this.getStackSize() ); - this.putPacketValue( i, this.getCountRequestable() ); - } + i.writeByte( mask ); + ByteBufUtils.writeTag( i, this.getDefinition().serializeNBT() ); + this.putPacketValue( i, this.getStackSize() ); + this.putPacketValue( i, this.getCountRequestable() ); + } - @Override - public void add( final IAEItemStack option ) - { - if( option == null ) - { - return; - } + /** + * We're assuming that using capNBT here is safe, because {@link #getDefinition()} should have been created by + * {@link ItemStack#copy()}, and then never mutated. Copying an item stack will automatically serialize the + * capabilities of the source stack and initialize the target stacks capNBT field using that tag, which we are then + * reusing here. + */ - this.incStackSize( option.getStackSize() ); - this.setCountRequestable( this.getCountRequestable() + option.getCountRequestable() ); - this.setCraftable( this.isCraftable() || option.isCraftable() ); - } + @Override + public void add(final IAEItemStack option) { + if (option == null) { + return; + } - @Override - public boolean fuzzyComparison( final IAEItemStack other, final FuzzyMode mode ) - { - if( mode == FuzzyMode.IGNORE_ALL && OreHelper.INSTANCE.sameOre( this, other ) ) - { - return true; - } + this.incStackSize(option.getStackSize()); + this.setCountRequestable(this.getCountRequestable() + option.getCountRequestable()); + this.setCraftable(this.isCraftable() || option.isCraftable()); + } - final ItemStack itemStack = this.getDefinition(); - final ItemStack otherStack = other.getDefinition(); + @Override + public boolean fuzzyComparison(final IAEItemStack other, final FuzzyMode mode) { + final ItemStack itemStack = this.getDefinition(); + final ItemStack otherStack = other.getDefinition(); - return this.fuzzyItemStackComparison( itemStack, otherStack, mode ); - } + return this.fuzzyItemStackComparison(itemStack, otherStack, mode); + } - @Override - public IAEItemStack copy() - { - return new AEItemStack( this ); - } + @Override + public IAEItemStack copy() { + return new AEItemStack(this); + } - @Override - public boolean isItem() - { - return true; - } + @Override + public IStorageChannel getChannel() { + return AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class); + } - @Override - public boolean isFluid() - { - return false; - } + @Override + public ItemStack createItemStack() { + return ItemHandlerHelper.copyStackWithSize(this.getDefinition(), + (int) Math.min(Integer.MAX_VALUE, this.getStackSize())); + } - @Override - public IStorageChannel getChannel() - { - return AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ); - } + @Override + public Item getItem() { + return this.getDefinition().getItem(); + } - @Override - public ItemStack createItemStack() - { - return ItemHandlerHelper.copyStackWithSize( this.getDefinition(), (int) Math.min( Integer.MAX_VALUE, this.getStackSize() ) ); - } + @Override + public int getItemDamage() { + return this.sharedStack.getItemDamage(); + } - @Override - public Item getItem() - { - return this.getDefinition().getItem(); - } + public Optional getOre() + { + return this.oreReference; + } - @Override - public int getItemDamage() - { - return this.sharedStack.getItemDamage(); - } + @Override + public boolean sameOre( final IAEItemStack is ) + { + return OreHelper.INSTANCE.sameOre( this, is ); + } - @Override - public boolean sameOre( final IAEItemStack is ) - { - return OreHelper.INSTANCE.sameOre( this, is ); - } + @Override + public boolean isSameType(final IAEItemStack otherStack) { + if (otherStack == null) { + return false; + } - @Override - public boolean isSameType( final IAEItemStack otherStack ) - { - if( otherStack == null ) - { - return false; - } + return Objects.equals(this.sharedStack, ((AEItemStack) otherStack).sharedStack); + } - return Objects.equals( this.sharedStack, ( (AEItemStack) otherStack ).sharedStack ); - } + @Override + public boolean isSameType(final ItemStack otherStack) { + if (otherStack.isEmpty()) { + return false; + } + int oldSize = otherStack.getCount(); - @Override - public boolean isSameType( final ItemStack otherStack ) - { - if( otherStack.isEmpty() ) - { - return false; - } - int oldSize = otherStack.getCount(); + otherStack.setCount(1); + boolean ret = ItemStack.areItemStacksEqual(this.getDefinition(), otherStack); + otherStack.setCount(oldSize); - otherStack.setCount( 1 ); - boolean ret = ItemStack.areItemStacksEqual( this.getDefinition(), otherStack ); - otherStack.setCount( oldSize ); + return ret; + } - return ret; - } + @Override + public int hashCode() { + return this.sharedStack.hashCode(); + } - @Override - public int hashCode() - { - return this.sharedStack.hashCode(); - } + @Override + public boolean equals(final Object ia) { + if (ia instanceof AEItemStack) { + return this.isSameType((AEItemStack) ia); + } else if (ia instanceof ItemStack) { + // this actually breaks the equals contract (being equals to unrelated classes) + return equals((ItemStack) ia); + } + return false; + } - @Override - public boolean equals( final Object ia ) - { - if( ia instanceof AEItemStack ) - { - return this.isSameType( (AEItemStack) ia ); - } - else if( ia instanceof ItemStack ) - { - return this.isSameType( (ItemStack) ia ); - } - return false; - } + @Override + public String toString() { + return this.getStackSize() + "x" + this.getDefinition().getItem().getRegistryName(); + } - @Override - public String toString() - { - return this.getStackSize() + "x" + this.getDefinition().getItem().getUnlocalizedName() + "@" + this.getDefinition().getItemDamage(); - } + @SideOnly( Side.CLIENT ) + public List getToolTip() { + if (this.tooltip == null) { + this.tooltip = Platform.getTooltip(this.asItemStackRepresentation()); + } + return this.tooltip; + } - @SideOnly( Side.CLIENT ) - public List getToolTip() - { - if( this.tooltip == null ) - { - this.tooltip = Platform.getTooltip( this.asItemStackRepresentation() ); - } - return this.tooltip; - } + @SideOnly( Side.CLIENT ) + public String getDisplayName() { + if (this.displayName == null) { + this.displayName = Platform.getItemDisplayName(this.asItemStackRepresentation()); + } + return this.displayName; + } - @SideOnly( Side.CLIENT ) - public String getDisplayName() - { - if( this.displayName == null ) - { - this.displayName = Platform.getItemDisplayName( this.asItemStackRepresentation() ); - } - return this.displayName; - } + @SideOnly( Side.CLIENT ) + public String getModID() { + return this.getDefinition().getItem().getRegistryName().getResourceDomain(); + } - @SideOnly( Side.CLIENT ) - public String getModID() - { - if( this.uniqueID == null ) - { - this.uniqueID = Item.REGISTRY.getNameForObject( this.getDefinition().getItem() ); - } + @Override + public boolean hasTagCompound() { + return this.getDefinition().hasTagCompound(); + } - if( this.uniqueID == null ) - { - return "** Null"; - } + @Override + public ItemStack asItemStackRepresentation() { + return this.getDefinition().copy(); + } - return this.uniqueID.getResourceDomain() == null ? "** Null" : this.uniqueID.getResourceDomain(); - } + @Override + public ItemStack getDefinition() { + return this.sharedStack.getDefinition(); + } - public Optional getOre() - { - return this.oreReference; - } + public boolean equals(final ItemStack is) { + return this.isSameType(is); + } - @Override - public boolean hasTagCompound() - { - return this.getDefinition().hasTagCompound(); - } + AESharedItemStack getSharedStack() { + return this.sharedStack; + } - @Override - public ItemStack asItemStackRepresentation() - { - return this.getDefinition().copy(); - } + private boolean fuzzyItemStackComparison(ItemStack a, ItemStack b, FuzzyMode mode) { + if (a.getItem() == b.getItem()) { + if (a.getItem().isDamageable()) { + if (mode == FuzzyMode.IGNORE_ALL) { + return true; + } else if (mode == FuzzyMode.PERCENT_99) { + return (a.getItemDamage() > 1) == (b.getItemDamage() > 1); + } else { + final float percentDamageOfA = (float) a.getItemDamage() / a.getMaxDamage(); + final float percentDamageOfB = (float) b.getItemDamage() / b.getMaxDamage(); - @Override - public ItemStack getDefinition() - { - return this.sharedStack.getDefinition(); - } - - AESharedItemStack getSharedStack() - { - return this.sharedStack; - } - - private boolean fuzzyItemStackComparison( ItemStack a, ItemStack b, FuzzyMode mode ) - { - if( a.getItem() == b.getItem() ) - { - if( a.getItem().isDamageable() ) - { - if( mode == FuzzyMode.IGNORE_ALL ) - { - return true; - } - else if( mode == FuzzyMode.PERCENT_99 ) - { - return ( a.getItemDamage() > 1 ) == ( b.getItemDamage() > 1 ); - } - else - { - final float percentDamageOfA = (float) a.getItemDamage() / (float) a.getMaxDamage(); - final float percentDamageOfB = (float) b.getItemDamage() / (float) b.getMaxDamage(); - - return ( percentDamageOfA > mode.breakPoint ) == ( percentDamageOfB > mode.breakPoint ); - } - } - - return a.getMetadata() == b.getMetadata(); - } - - return false; - } + return (percentDamageOfA > mode.breakPoint) == (percentDamageOfB > mode.breakPoint); + } + } + } + return false; + } } diff --git a/src/main/java/appeng/util/item/AEItemStackRegistry.java b/src/main/java/appeng/util/item/AEItemStackRegistry.java index 2bea02b88..0fce27e36 100644 --- a/src/main/java/appeng/util/item/AEItemStackRegistry.java +++ b/src/main/java/appeng/util/item/AEItemStackRegistry.java @@ -23,7 +23,6 @@ package appeng.util.item; - import java.lang.ref.WeakReference; import java.util.WeakHashMap; @@ -31,56 +30,34 @@ import javax.annotation.Nonnull; import net.minecraft.item.ItemStack; -import appeng.util.Platform; +public final class AEItemStackRegistry { + private static final WeakHashMap> REGISTRY = new WeakHashMap<>(); + private AEItemStackRegistry() { + } -public final class AEItemStackRegistry -{ - private static final WeakHashMap> SERVER_REGISTRY = new WeakHashMap<>(); - private static final WeakHashMap> CLIENT_REGISTRY = new WeakHashMap<>(); + static synchronized AESharedItemStack getRegisteredStack(final @Nonnull ItemStack itemStack) { + if (itemStack.isEmpty()) { + throw new IllegalArgumentException("stack cannot be empty"); + } - private AEItemStackRegistry() - { - } + int oldStackSize = itemStack.getCount(); + itemStack.setCount(1); - private static WeakHashMap> registry() - { - if( Platform.isClient() ) - { - return CLIENT_REGISTRY; - } - else - { - return SERVER_REGISTRY; - } - } + AESharedItemStack search = new AESharedItemStack(itemStack); + WeakReference weak = REGISTRY.get(search); + AESharedItemStack ret = null; - static synchronized AESharedItemStack getRegisteredStack( final @Nonnull ItemStack itemStack ) - { - if( itemStack.isEmpty() ) - { - throw new IllegalArgumentException( "stack cannot be empty" ); - } + if (weak != null) { + ret = weak.get(); + } - int oldStackSize = itemStack.getCount(); - itemStack.setCount( 1 ); + if (ret == null) { + ret = new AESharedItemStack(itemStack.copy()); + REGISTRY.put(ret, new WeakReference<>(ret)); + } + itemStack.setCount(oldStackSize); - AESharedItemStack search = new AESharedItemStack( itemStack ); - WeakReference weak = registry().get( search ); - AESharedItemStack ret = null; - - if( weak != null ) - { - ret = weak.get(); - } - - if( ret == null ) - { - ret = new AESharedItemStack( itemStack.copy() ); - registry().put( ret, new WeakReference<>( ret ) ); - } - itemStack.setCount( oldStackSize ); - - return ret; - } + return ret; + } } diff --git a/src/main/java/appeng/util/item/AESharedItemStack.java b/src/main/java/appeng/util/item/AESharedItemStack.java index 700e5b6b1..c5ab8038f 100644 --- a/src/main/java/appeng/util/item/AESharedItemStack.java +++ b/src/main/java/appeng/util/item/AESharedItemStack.java @@ -18,7 +18,6 @@ package appeng.util.item; - import java.util.Objects; import com.google.common.base.Preconditions; @@ -33,32 +32,32 @@ final class AESharedItemStack { private final int itemDamage; private final int hashCode; - private final ItemStack itemStack; - private final int itemId; - private final int itemDamage; - private final int hashCode; + public AESharedItemStack(final ItemStack itemStack) { + this(itemStack, itemStack.getItemDamage()); + } - public AESharedItemStack( final ItemStack itemStack ) - { - this.itemStack = itemStack; - this.itemId = Item.getIdFromItem( itemStack.getItem() ); - this.itemDamage = itemStack.getItemDamage(); - this.hashCode = this.makeHashCode(); - } + /** + * A constructor to explicitly set the damage value and not fetch it from the {@link ItemStack} + * + * @param itemStack The {@link ItemStack} to filter + * @param damage The damage of the item + */ + private AESharedItemStack(ItemStack itemStack, int damage) { + this.itemStack = itemStack; + this.itemId = Item.getIdFromItem(itemStack.getItem()); + this.itemDamage = damage; - Bounds getBounds( final FuzzyMode fuzzy, final boolean ignoreMeta ) - { - return new Bounds( this.itemStack, fuzzy, ignoreMeta ); - } + // Ensure this is always called last. + this.hashCode = this.makeHashCode(); + } ItemStack getDefinition() { return this.itemStack; } - int getItemID() - { - return this.itemId; - } + int getItemDamage() { + return this.itemDamage; + } @Override public int hashCode() { @@ -88,7 +87,7 @@ final class AESharedItemStack { return Objects.hash( this.itemId, this.itemDamage, - this.itemStack.hasTag() ? this.itemStack.getTag() : 0); + this.itemStack.hasTagCompound() ? this.itemStack.getTagCompound() : 0); } } diff --git a/src/main/java/appeng/util/item/AEStack.java b/src/main/java/appeng/util/item/AEStack.java index f2b42351d..9782663ab 100644 --- a/src/main/java/appeng/util/item/AEStack.java +++ b/src/main/java/appeng/util/item/AEStack.java @@ -18,169 +18,152 @@ package appeng.util.item; - +import appeng.api.storage.data.IAEStack; import io.netty.buffer.ByteBuf; -import appeng.api.storage.data.IAEStack; +public abstract class AEStack> implements IAEStack { -public abstract class AEStack> implements IAEStack -{ + private boolean isCraftable; + private long stackSize; + private long countRequestable; - private boolean isCraftable; - private long stackSize; - private long countRequestable; + @Override + public long getStackSize() { + return this.stackSize; + } - protected static long getPacketValue( final byte type, final ByteBuf tag ) - { - if( type == 0 ) - { - long l = tag.readByte(); - l -= Byte.MIN_VALUE; - return l; - } - else if( type == 1 ) - { - long l = tag.readShort(); - l -= Short.MIN_VALUE; - return l; - } - else if( type == 2 ) - { - long l = tag.readInt(); - l -= Integer.MIN_VALUE; - return l; - } + @Override + public T setStackSize(final long ss) { + this.stackSize = ss; + return (T) this; + } - return tag.readLong(); - } + @Override + public long getCountRequestable() { + return this.countRequestable; + } - @Override - public long getStackSize() - { - return this.stackSize; - } + @Override + public T setCountRequestable(final long countRequestable) { + this.countRequestable = countRequestable; + return (T) this; + } - @Override - public StackType setStackSize( final long ss ) - { - this.stackSize = ss; - return (StackType) this; - } + @Override + public boolean isCraftable() { + return this.isCraftable; + } - @Override - public long getCountRequestable() - { - return this.countRequestable; - } + @Override + public T setCraftable(final boolean isCraftable) { + this.isCraftable = isCraftable; + return (T) this; + } - @Override - public StackType setCountRequestable( final long countRequestable ) - { - this.countRequestable = countRequestable; - return (StackType) this; - } + @Override + public T reset() { + this.stackSize = 0; + this.setCountRequestable(0); + this.setCraftable(false); + return (T) this; + } - @Override - public boolean isCraftable() - { - return this.isCraftable; - } + @Override + public T empty() { + final T dup = this.copy(); + dup.reset(); + return dup; + } - @Override - public StackType setCraftable( final boolean isCraftable ) - { - this.isCraftable = isCraftable; - return (StackType) this; - } + @Override + public boolean isMeaningful() { + return this.stackSize != 0 || this.countRequestable > 0 || this.isCraftable; + } - @Override - public StackType reset() - { - this.stackSize = 0; - // priority = Integer.MIN_VALUE; - this.setCountRequestable( 0 ); - this.setCraftable( false ); - return (StackType) this; - } + @Override + public void incStackSize(final long i) { + this.stackSize += i; + } - @Override - public StackType empty() - { - final StackType dup = this.copy(); - dup.reset(); - return dup; - } + @Override + public void decStackSize(final long i) { + this.stackSize -= i; + } - @Override - public boolean isMeaningful() - { - return this.stackSize != 0 || this.countRequestable > 0 || this.isCraftable; - } + @Override + public void incCountRequestable(final long i) { + this.countRequestable += i; + } - @Override - public void incStackSize( final long i ) - { - this.stackSize += i; - } + @Override + public void decCountRequestable(final long i) { + this.countRequestable -= i; + } - @Override - public void decStackSize( final long i ) - { - this.stackSize -= i; - } + protected abstract boolean hasTagCompound(); - @Override - public void incCountRequestable( final long i ) - { - this.countRequestable += i; - } + protected static long getPacketValue( final byte type, final ByteBuf tag ) + { + if( type == 0 ) + { + long l = tag.readByte(); + l -= Byte.MIN_VALUE; + return l; + } + else if( type == 1 ) + { + long l = tag.readShort(); + l -= Short.MIN_VALUE; + return l; + } + else if( type == 2 ) + { + long l = tag.readInt(); + l -= Integer.MIN_VALUE; + return l; + } - @Override - public void decCountRequestable( final long i ) - { - this.countRequestable -= i; - } + return tag.readLong(); + } - protected byte getType( final long num ) - { - if( num <= 255 ) - { - return 0; - } - else if( num <= 65535 ) - { - return 1; - } - else if( num <= 4294967295L ) - { - return 2; - } - else - { - return 3; - } - } + protected void putPacketValue( final ByteBuf tag, final long num ) + { + if( num <= 255 ) + { + tag.writeByte( (byte) ( num + Byte.MIN_VALUE ) ); + } + else if( num <= 65535 ) + { + tag.writeShort( (short) ( num + Short.MIN_VALUE ) ); + } + else if( num <= 4294967295L ) + { + tag.writeInt( (int) ( num + Integer.MIN_VALUE ) ); + } + else + { + tag.writeLong( num ); + } + } - protected abstract boolean hasTagCompound(); - - protected void putPacketValue( final ByteBuf tag, final long num ) - { - if( num <= 255 ) - { - tag.writeByte( (byte) ( num + Byte.MIN_VALUE ) ); - } - else if( num <= 65535 ) - { - tag.writeShort( (short) ( num + Short.MIN_VALUE ) ); - } - else if( num <= 4294967295L ) - { - tag.writeInt( (int) ( num + Integer.MIN_VALUE ) ); - } - else - { - tag.writeLong( num ); - } - } + protected byte getType( final long num ) + { + if( num <= 255 ) + { + return 0; + } + else if( num <= 65535 ) + { + return 1; + } + else if( num <= 4294967295L ) + { + return 2; + } + else + { + return 3; + } + } } diff --git a/src/main/java/appeng/util/item/FuzzyItemVariantList.java b/src/main/java/appeng/util/item/FuzzyItemVariantList.java index 270a3df07..7ccc16f0c 100644 --- a/src/main/java/appeng/util/item/FuzzyItemVariantList.java +++ b/src/main/java/appeng/util/item/FuzzyItemVariantList.java @@ -145,14 +145,14 @@ class FuzzyItemVariantList extends ItemVariantList { * higher number than the upper bound. */ static ItemDamageBound makeLowerBound(final ItemStack stack, final FuzzyMode fuzzy) { - Preconditions.checkState(stack.isDamageable(), "ItemStack#isDamageable() has to be true"); + Preconditions.checkState(((stack.getMaxDamage() == 0 && stack.getItemDamage() > 0 ) || stack.isItemStackDamageable()), "ItemStack#isDamageable() has to be true"); int damage; if (fuzzy == FuzzyMode.IGNORE_ALL) { damage = stack.getMaxDamage(); } else { final int breakpoint = fuzzy.calculateBreakPoint(stack.getMaxDamage()); - damage = stack.getDamage() <= breakpoint ? breakpoint : stack.getMaxDamage(); + damage = stack.getItemDamage() <= breakpoint ? breakpoint : stack.getMaxDamage(); } return new ItemDamageBound(damage); @@ -163,14 +163,14 @@ class FuzzyItemVariantList extends ItemVariantList { * lower number than the lower bound. It also is exclusive. */ static ItemDamageBound makeUpperBound(final ItemStack stack, final FuzzyMode fuzzy) { - Preconditions.checkState(stack.isDamageable(), "ItemStack#isDamageable() has to be true"); + Preconditions.checkState(((stack.getMaxDamage() == 0 && stack.getItemDamage() > 0 ) || stack.isItemStackDamageable()), "ItemStack#isDamageable() has to be true"); int damage; if (fuzzy == FuzzyMode.IGNORE_ALL) { damage = MIN_DAMAGE_VALUE; } else { final int breakpoint = fuzzy.calculateBreakPoint(stack.getMaxDamage()); - damage = stack.getDamage() <= breakpoint ? MIN_DAMAGE_VALUE : breakpoint; + damage = stack.getItemDamage() <= breakpoint ? MIN_DAMAGE_VALUE : breakpoint; } return new ItemDamageBound(damage); diff --git a/src/main/java/appeng/util/item/ItemList.java b/src/main/java/appeng/util/item/ItemList.java index e50cd73b7..e7a4672a2 100644 --- a/src/main/java/appeng/util/item/ItemList.java +++ b/src/main/java/appeng/util/item/ItemList.java @@ -1,6 +1,6 @@ /* * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2015, AlgorithmX2, All rights reserved. + * Copyright (c) 2013 - 2020, AlgorithmX2, All rights reserved. * * Applied Energistics 2 is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -18,8 +18,6 @@ package appeng.util.item; - -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; @@ -27,13 +25,16 @@ import java.util.Iterator; import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicInteger; -import net.minecraftforge.oredict.OreDictionary; +import net.minecraft.item.Item; + +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; import appeng.api.config.FuzzyMode; import appeng.api.storage.data.IAEItemStack; import appeng.api.storage.data.IItemList; -import appeng.util.item.AESharedItemStack.Bounds; +public final class ItemList implements IItemList { private final Reference2ObjectMap records = new Reference2ObjectOpenHashMap<>(); /** @@ -53,18 +54,20 @@ import appeng.util.item.AESharedItemStack.Bounds; return record != null ? record.findPrecise(itemStack) : null; } - if( st != null ) - { - st.add( option ); - return; - } + @Override + public Collection findFuzzy(final IAEItemStack filter, final FuzzyMode fuzzy) { + if (filter == null) { + return Collections.emptyList(); + } ItemVariantList record = this.records.get(filter.getItem()); return record != null ? record.findFuzzy(filter, fuzzy) : Collections.emptyList(); } - this.putItemRecord( opt ); - } + @Override + public boolean isEmpty() { + return !this.iterator().hasNext(); + } @Override public void add(final IAEItemStack itemStack) { @@ -74,8 +77,8 @@ import appeng.util.item.AESharedItemStack.Bounds; return; } - return this.records.get( ( (AEItemStack) itemStack ).getSharedStack() ); - } + this.getOrCreateRecord(itemStack.getItem()).add(itemStack); + } @Override public void addStorage(final IAEItemStack itemStack) { @@ -85,7 +88,8 @@ import appeng.util.item.AESharedItemStack.Bounds; return; } - final AEItemStack ais = (AEItemStack) filter; + this.getOrCreateRecord(itemStack.getItem()).addStorage(itemStack); + } @Override public void addCrafting(final IAEItemStack itemStack) { @@ -95,11 +99,8 @@ import appeng.util.item.AESharedItemStack.Bounds; return; } - return this.findFuzzyDamage( is, fuzzy, is.getItemDamage() == OreDictionary.WILDCARD_VALUE ); - } - else - { - final Collection output = new ArrayList<>(); + this.getOrCreateRecord(itemStack.getItem()).addCrafting(itemStack); + } @Override public void addRequestable(final IAEItemStack itemStack) { @@ -109,24 +110,17 @@ import appeng.util.item.AESharedItemStack.Bounds; return; } - return output; - } - } ).orElse( this.findFuzzyDamage( ais, fuzzy, false ) ); - } + this.getOrCreateRecord(itemStack.getItem()).addRequestable(itemStack); + } - @Override - public boolean isEmpty() - { - return !this.iterator().hasNext(); - } + @Override + public IAEItemStack getFirstItem() { + for (final IAEItemStack stackType : this) { + return stackType; + } - @Override - public void addStorage( final IAEItemStack option ) - { - if( option == null ) - { - return; - } + return null; + } @Override public int size() { @@ -135,19 +129,20 @@ import appeng.util.item.AESharedItemStack.Bounds; size += entry.size(); } - if( st != null ) - { - st.incStackSize( option.getStackSize() ); - return; - } + return size; + } @Override public Iterator iterator() { return new ChainedIterator(this.records.values().iterator(), version); } - this.putItemRecord( opt ); - } + @Override + public void resetStatus() { + for (final IAEItemStack i : this) { + i.reset(); + } + } private ItemVariantList getOrCreateRecord(Item item) { return this.records.computeIfAbsent(item, this::makeRecordMap); diff --git a/src/main/java/appeng/util/item/MeaningfulItemIterator.java b/src/main/java/appeng/util/item/MeaningfulItemIterator.java index 310d23fb3..2d84f7933 100644 --- a/src/main/java/appeng/util/item/MeaningfulItemIterator.java +++ b/src/main/java/appeng/util/item/MeaningfulItemIterator.java @@ -43,13 +43,11 @@ public class MeaningfulItemIterator implements Iterator< return this.next != null; } - @Override - public T next() - { - if( this.next == null ) - { - throw new NoSuchElementException(); - } + @Override + public T next() { + if (this.next == null) { + throw new NoSuchElementException(); + } T result = this.next; this.next = this.seekNext();