package org.bukkit.craftbukkit.inventory;
import static org.bukkit.support.Matchers.sameHash;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bukkit.Material;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemFactory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.support.AbstractTestingBase;
import org.bukkit.util.io.BukkitObjectInputStream;
import org.bukkit.util.io.BukkitObjectOutputStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.koloboke.collect.map.hash.HashObjObjMaps;
@RunWith(Parameterized.class)
public class ItemStackTest extends AbstractTestingBase {
static abstract class StackProvider {
final Material material;
StackProvider(Material material) {
this.material = material;
}
ItemStack bukkit() {
return operate(cleanStack(material, false));
}
ItemStack craft() {
return operate(cleanStack(material, true));
}
abstract ItemStack operate(ItemStack cleanStack);
static ItemStack cleanStack(Material material, boolean craft) {
final ItemStack stack = new ItemStack(material);
return craft ? CraftItemStack.asCraftCopy(stack) : stack;
}
@Override
public String toString() {
return material.toString();
}
/**
* For each item in parameterList, it will apply nameFormat at nameIndex.
* For each item in parameterList for each item in materials, it will create a stack provider at each array index that contains an Operator.
*
* @param parameterList
* @param nameFormat
* @param nameIndex
* @param materials
* @return
*/
static List<Object[]> compound(final List<Object[]> parameterList, final String nameFormat, final int nameIndex, final Material...materials) {
final List<Object[]> out = new ArrayList<Object[]>();
for (Object[] params : parameterList) {
final int len = params.length;
for (final Material material : materials) {
final Object[] paramsOut = params.clone();
for (int i = 0; i < len; i++) {
final Object param = paramsOut[i];
if (param instanceof Operator) {
final Operator operator = (Operator) param;
paramsOut[i] = new StackProvider(material) {
@Override
ItemStack operate(ItemStack cleanStack) {
return operator.operate(cleanStack);
}
};
}
}
paramsOut[nameIndex] = String.format(nameFormat, paramsOut[nameIndex], material);
out.add(paramsOut);
}
}
return out;
}
}
interface Operator {
ItemStack operate(ItemStack cleanStack);
}
static class CompoundOperator implements Operator {
static class RecursiveContainer {
final Joiner joiner;
final Object[] strings;
final int nameParameter;
final List<Object[]> stack;
final List<Object[]> out;
final List<Object[]>[] lists;
RecursiveContainer(Joiner joiner, Object[] strings, int nameParameter, List<Object[]> stack, List<Object[]> out, List<Object[]>[] lists) {
this.joiner = joiner;
this.strings = strings;
this.nameParameter = nameParameter;
this.stack = stack;
this.out = out;
this.lists = lists;
}
}
final Operator[] operators;
CompoundOperator(Operator...operators) {
this.operators = operators;
}
@Override
public ItemStack operate(ItemStack cleanStack) {
for (Operator operator : operators) {
operator.operate(cleanStack);
}
return cleanStack;
}
@Override
public String toString() {
return Arrays.toString(operators);
}
/**
* This combines different tests into one large collection, combining no two tests from the same list.
* @param joiner used to join names
* @param nameParameter index of the name parameter
* @param singletonBitmask a list of bits representing the 'singletons' located in your originalLists. Lowest order bits represent the first items in originalLists.
* Singletons are exponentially linked with each other, such that,
* the output will contain every unique subset of only items from the singletons,
* as well as every unique subset that contains at least one item from each non-singleton.
* @param originalLists
* @return
*/
static List<Object[]> compound(final Joiner joiner, final int nameParameter, final long singletonBitmask, final List<Object[]>...originalLists) {
final List<Object[]> out = new ArrayList<Object[]>();
final List<List<Object[]>> singletons = new ArrayList<List<Object[]>>();
final List<List<Object[]>> notSingletons = new ArrayList<List<Object[]>>();
{ // Separate and prime the 'singletons'
int i = 0;
for (List<Object[]> list : originalLists) {
(((singletonBitmask >>> i++) & 0x1) == 0x1 ? singletons : notSingletons).add(list);
}
}
for (final List<Object[]> primarySingleton : singletons) {
// Iterate over our singletons, to multiply the 'out' each time
for (final Object[] entry : out.toArray(EMPTY_ARRAY)) {
// Iterate over a snapshot of 'out' to prevent CMEs / infinite iteration
final int len = entry.length;
for (final Object[] singleton : primarySingleton) {
// Iterate over each item in our singleton for the current 'out' entry
final Object[] toOut = entry.clone();
for (int i = 0; i < len; i++) {
// Iterate over each parameter
if (i == nameParameter) {
toOut[i] = joiner.join(toOut[i], singleton[i]);
} else if (toOut[i] instanceof Operator) {
final Operator op1 = (Operator) toOut[i];
final Operator op2 = (Operator) singleton[i];
toOut[i] = new Operator() {
@Override
public ItemStack operate(final ItemStack cleanStack) {
return op2.operate(op1.operate(cleanStack));
}
};
}
}
out.add(toOut);
}
}
out.addAll(primarySingleton);
}
@SuppressWarnings("unchecked")
final List<Object[]>[] lists = new List[notSingletons.size() + 1];
notSingletons.toArray(lists);
lists[lists.length - 1] = out;
final RecursiveContainer methodParams = new RecursiveContainer(joiner, new Object[lists.length], nameParameter, new ArrayList<Object[]>(lists.length), new ArrayList<Object[]>(), lists);
recursivelyCompound(methodParams, 0);
methodParams.out.addAll(out);
return methodParams.out;
}
private static void recursivelyCompound(final RecursiveContainer methodParams, final int level) {
final List<Object[]> stack = methodParams.stack;
if (level == methodParams.lists.length) {
final Object[] firstParams = stack.get(0);
final int len = firstParams.length;
final int stackSize = stack.size();
final Object[] params = new Object[len];
for (int i = 0; i < len; i++) {
final Object firstParam = firstParams[i];
if (firstParam instanceof Operator) {
final Operator[] operators = new Operator[stackSize];
for (int j = 0; j < stackSize; j++) {
operators[j] = (Operator) stack.get(j)[i];
}
params[i] = new CompoundOperator(operators);
} else if (i == methodParams.nameParameter) {
final Object[] strings = methodParams.strings;
for (int j = 0; j < stackSize; j++) {
strings[j] = stack.get(j)[i];
}
params[i] = methodParams.joiner.join(strings);
} else {
params[i] = firstParam;
}
}
methodParams.out.add(params);
} else {
final int marker = stack.size();
for (final Object[] params : methodParams.lists[level]) {
stack.add(params);
recursivelyCompound(methodParams, level + 1);
stack.remove(marker);
}
}
}
}
interface StackWrapper {
ItemStack stack();
}
static class CraftWrapper implements StackWrapper {
final StackProvider provider;
CraftWrapper(StackProvider provider) {
this.provider = provider;
}
@Override
public ItemStack stack() {
return provider.craft();
}
@Override
public String toString() {
return "Craft " + provider;
}
}
static class BukkitWrapper implements StackWrapper {
final StackProvider provider;
BukkitWrapper(StackProvider provider) {
this.provider = provider;
}
@Override
public ItemStack stack() {
return provider.bukkit();
}
@Override
public String toString() {
return "Bukkit " + provider;
}
}
static class NoOpProvider extends StackProvider {
NoOpProvider(Material material) {
super(material);
}
@Override
ItemStack operate(ItemStack cleanStack) {
return cleanStack;
}
@Override
public String toString() {
return "NoOp " + super.toString();
}
}
@Parameters(name="[{index}]:{" + NAME_PARAMETER + "}")
public static List<Object[]> data() {
return ImmutableList.of(); // TODO, test basic durability issues
}
static final Object[][] EMPTY_ARRAY = new Object[0][];
/**
* Materials that generate unique item meta types.
*/
static final Material[] COMPOUND_MATERIALS;
static final int NAME_PARAMETER = 2;
static {
final ItemFactory factory = CraftItemFactory.instance();
final Map<Class<? extends ItemMeta>, Material> possibleMaterials = HashObjObjMaps.newMutableMap();
ItemMeta meta;
for (final Material material : Material.values()) {
meta = factory.getItemMeta(material);
if (meta == null || possibleMaterials.containsKey(meta.getClass()))
continue;
possibleMaterials.put(meta.getClass(), material);
}
COMPOUND_MATERIALS = possibleMaterials.values().toArray(new Material[possibleMaterials.size()]);
}
@Parameter(0) public StackProvider provider;
@Parameter(1) public StackProvider unequalProvider;
@Parameter(NAME_PARAMETER) public String name;
@Test
public void testBukkitInequality() {
final StackWrapper bukkitWrapper = new CraftWrapper(provider);
testInequality(bukkitWrapper, new BukkitWrapper(unequalProvider));
testInequality(bukkitWrapper, new BukkitWrapper(new NoOpProvider(provider.material)));
}
@Test
public void testCraftInequality() {
final StackWrapper craftWrapper = new CraftWrapper(provider);
testInequality(craftWrapper, new CraftWrapper(unequalProvider));
testInequality(craftWrapper, new CraftWrapper(new NoOpProvider(provider.material)));
}
@Test
public void testMixedInequality() {
final StackWrapper craftWrapper = new CraftWrapper(provider);
testInequality(craftWrapper, new BukkitWrapper(unequalProvider));
testInequality(craftWrapper, new BukkitWrapper(new NoOpProvider(provider.material)));
final StackWrapper bukkitWrapper = new CraftWrapper(provider);
testInequality(bukkitWrapper, new CraftWrapper(unequalProvider));
testInequality(bukkitWrapper, new CraftWrapper(new NoOpProvider(provider.material)));
}
static void testInequality(StackWrapper provider, StackWrapper unequalProvider) {
final ItemStack stack = provider.stack();
final ItemStack stack2 = provider.stack();
assertThat(stack, allOf(equalTo(stack), sameHash(stack)));
assertThat(stack, is(not(sameInstance(stack2))));
assertThat(stack, allOf(equalTo(stack2), sameHash(stack2)));
final ItemStack unequalStack = unequalProvider.stack();
final ItemStack unequalStack2 = unequalProvider.stack();
assertThat(unequalStack, allOf(equalTo(unequalStack), sameHash(unequalStack)));
assertThat(unequalStack, is(not(sameInstance(unequalStack2))));
assertThat(unequalStack, allOf(equalTo(unequalStack2), sameHash(unequalStack2)));
assertThat(stack, is(not(unequalStack)));
assertThat(unequalStack, is(not(stack)));
final ItemStack newStack = new ItemStack(stack2);
assertThat(newStack, allOf(equalTo(stack), sameHash(stack)));
assertThat(newStack, is(not(unequalStack)));
assertThat(newStack.getItemMeta(), allOf(equalTo(stack.getItemMeta()), sameHash(stack.getItemMeta())));
assertThat(newStack.getItemMeta(), is(not(unequalStack.getItemMeta())));
final ItemStack craftStack = CraftItemStack.asCraftCopy(stack2);
assertThat(craftStack, allOf(equalTo(stack), sameHash(stack)));
assertThat(craftStack, is(not(unequalStack)));
assertThat(craftStack.getItemMeta(), allOf(equalTo(stack.getItemMeta()), sameHash(stack.getItemMeta())));
assertThat(craftStack.getItemMeta(), is(not(unequalStack.getItemMeta())));
final ItemStack newUnequalStack = new ItemStack(unequalStack2);
assertThat(newUnequalStack, allOf(equalTo(unequalStack), sameHash(unequalStack)));
assertThat(newUnequalStack, is(not(stack)));
assertThat(newUnequalStack.getItemMeta(), allOf(equalTo(unequalStack.getItemMeta()), sameHash(unequalStack.getItemMeta())));
assertThat(newUnequalStack.getItemMeta(), is(not(stack.getItemMeta())));
final ItemStack newUnequalCraftStack = CraftItemStack.asCraftCopy(unequalStack2);
assertThat(newUnequalCraftStack, allOf(equalTo(unequalStack), sameHash(unequalStack)));
assertThat(newUnequalCraftStack, is(not(stack)));
assertThat(newUnequalCraftStack.getItemMeta(), allOf(equalTo(unequalStack.getItemMeta()), sameHash(unequalStack.getItemMeta())));
assertThat(newUnequalCraftStack.getItemMeta(), is(not(stack.getItemMeta())));
}
@Test
public void testBukkitYamlDeserialize() throws Throwable {
testYamlDeserialize(new BukkitWrapper(provider), new BukkitWrapper(unequalProvider));
}
@Test
public void testCraftYamlDeserialize() throws Throwable {
testYamlDeserialize(new CraftWrapper(provider), new CraftWrapper(unequalProvider));
}
@Test
public void testBukkitStreamDeserialize() throws Throwable {
testStreamDeserialize(new BukkitWrapper(provider), new BukkitWrapper(unequalProvider));
}
@Test
public void testCraftStreamDeserialize() throws Throwable {
testStreamDeserialize(new CraftWrapper(provider), new CraftWrapper(unequalProvider));
}
static void testStreamDeserialize(StackWrapper provider, StackWrapper unequalProvider) throws Throwable {
final ItemStack stack = provider.stack();
final ItemStack unequalStack = unequalProvider.stack();
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = null;
try {
oos = new BukkitObjectOutputStream(out);
oos.writeObject(stack);
oos.writeObject(unequalStack);
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException ex) {
}
}
}
final String data = new String(Base64Coder.encode(out.toByteArray()));
ObjectInputStream ois = null;
final ItemStack readFirst;
final ItemStack readSecond;
try {
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ois = new BukkitObjectInputStream(in);
readFirst = (ItemStack) ois.readObject();
readSecond = (ItemStack) ois.readObject();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException ex) {
}
}
}
testEqualities(data, readFirst, readSecond, stack, unequalStack);
}
static void testYamlDeserialize(StackWrapper provider, StackWrapper unequalProvider) {
final ItemStack stack = provider.stack();
final ItemStack unequalStack = unequalProvider.stack();
final YamlConfiguration configOut = new YamlConfiguration();
configOut.set("provider", stack);
configOut.set("unequal", unequalStack);
final String out = '\n' + configOut.saveToString();
final YamlConfiguration configIn = new YamlConfiguration();
try {
configIn.loadFromString(out);
} catch (InvalidConfigurationException ex) {
throw new RuntimeException(out, ex);
}
testEqualities(out, configIn.getItemStack("provider"), configIn.getItemStack("unequal"), stack, unequalStack);
}
static void testEqualities(String information, ItemStack primaryRead, ItemStack unequalRead, ItemStack primaryOriginal, ItemStack unequalOriginal) {
assertThat(information, primaryRead, allOf(equalTo(primaryOriginal), sameHash(primaryOriginal)));
assertThat(information, unequalRead, allOf(equalTo(unequalOriginal), sameHash(unequalOriginal)));
assertThat(information, primaryRead, is(not(unequalOriginal)));
assertThat(information, primaryRead, is(not(unequalRead)));
}
}