Cardboard: Serializable ItemStack with enchantments

Discussion in 'Resources' started by NuclearW, May 14, 2012.

Thread Status:
Not open for further replies.
  1. Offline

    NuclearW

    Cardboard is a simple wrapper to allow for serialization to file of ItemStacks, and preserves enchantments.

    Cardboard consists of two classes, the CardboardBox and the CardboardEnchant which you will use.
    CardboardBox.java (open)
    Code:
    package com.nuclearw.cardboard;
     
    import java.io.Serializable;
    import java.util.HashMap;
    import java.util.Map;
     
    import org.bukkit.enchantments.Enchantment;
    import org.bukkit.inventory.ItemStack;
     
    /**
    * A serializable ItemStack
    */
    public class CardboardBox implements Serializable {
        private static final long serialVersionUID = 729890133797629668L;
     
        private final int type, amount;
        private final short damage;
        private final byte data;
     
        private final HashMap<CardboardEnchantment, Integer> enchants;
     
        public CardboardBox(ItemStack item) {
            this.type = item.getTypeId();
            this.amount = item.getAmount();
            this.damage = item.getDurability();
            this.data = item.getData().getData();
     
            HashMap<CardboardEnchantment, Integer> map = new HashMap<CardboardEnchantment, Integer>();
     
            Map<Enchantment, Integer> enchantments = item.getEnchantments();
     
            for(Enchantment enchantment : enchantments.keySet()) {
                map.put(new CardboardEnchantment(enchantment), enchantments.get(enchantment));
            }
     
            this.enchants = map;
        }
     
        public ItemStack unbox() {
            ItemStack item = new ItemStack(type, amount, damage, data);
     
            HashMap<Enchantment, Integer> map = new HashMap<Enchantment, Integer>();
     
            for(CardboardEnchantment cEnchantment : enchants.keySet()) {
                map.put(cEnchantment.unbox(), enchants.get(cEnchantment));
            }
     
            item.addUnsafeEnchantments(map);
     
            return item;
        }
    }
    
    CardboardEnchant.java (open)
    Code:
    package com.nuclearw.cardboard;
     
    import java.io.Serializable;
     
    import org.bukkit.enchantments.Enchantment;
     
    /**
    * A serializable Enchantment
    */
    public class CardboardEnchantment implements Serializable {
        private static final long serialVersionUID = 8973856768102665381L;
     
        private final int id;
     
        public CardboardEnchantment(Enchantment enchantment) {
            this.id = enchantment.getId();
        }
     
        public Enchantment unbox() {
            return Enchantment.getById(this.id);
        }
    }
    


    To create a CardboardBox for serialization, simply pass it an ItemStack in the constructor:
    Code:
    CardboardBox cardboardBox = new CardboardBox(item);
    To get your item back, after you've deserialized the cardboardbox, simply use .unbox():
    Code:
    ItemStack item = cardboardBox.unbox();
    And that's all there is to it, comment if you have any suggestions, complaints, or inquiries.
     
    Lactem, ZephireNZ, rtainc and 7 others like this.
  2. Offline

    DrAgonmoray

    <3
     
    NuclearW likes this.
  3. Offline

    LucasEmanuel

    Thank you! This is now integrated into the main plugin of our community and working perfectly :)
     
  4. Offline

    NuclearW

    Glad to hear it was useful. I wouldn't mind looking at how you're using it too if you don't mind.
     
  5. Offline

    LucasEmanuel

    No problem, go right ahead. Im experiencing some problems now but that might also be because i have never worked with objectstreams :)

    Edit: Fixed the issue, had just forgot to initialize an object :)

    Direct link to where i use it:
    https://github.com/Chilinot/LetsPla...tsplay/managers/LetsPlayInventoryManager.java
     
    NuclearW likes this.
  6. Hmmm I may look at this as I play Survival on a server that I administrate so this would be useful.
     
  7. Offline

    resba

    I love you.
     
    NuclearW likes this.
  8. Offline

    oaschi

    Thank you so much :D nice work!
     
  9. Offline

    DormantOden

    Thanks for these classes!
    Posting a Serializeable Inventory class that uses them in case someone finds it useful, add to it as needed:

    Code:
    public class CardboardInventory implements Serializable
    {
        private static final long serialVersionUID = 4679317485302321489L;
     
        private final HashMap<Integer, CardboardBox> s_armour, s_items;
     
        public CardboardInventory(PlayerInventory inventory)
        {
         
            ItemStack[] armour, items;
            armour = inventory.getArmorContents();
            items = inventory.getContents();
         
            s_armour = new HashMap<Integer, CardboardBox>();
            s_items = new HashMap<Integer, CardboardBox>();
         
            //armour
            for(int i=0; i<armour.length; i++)
            {
                CardboardBox value = null;
                if(armour[i] != null){
                    value = new CardboardBox(armour[i]);
                }
                s_armour.put(i, value);
            }
            //items
            for(int i=0; i<items.length; i++)
            {
                CardboardBox value = null;
                if(items[i] != null){
                    value = new CardboardBox(items[i]);
                }
                s_items.put(i, value);
            }
        }
     
        /**
        * Gets the armour item stacks.
        * @return
        */
        public ItemStack[] unboxArmour()
        {
            ItemStack[] armour = new ItemStack[s_armour.size()];
            for(int i=0; i<s_armour.size(); i++)
            {
                if(s_armour.get(i) == null){
                    armour[i] = null;
                }else{
                    armour[i] = s_armour.get(i).unbox();
                }
            }
            return armour;
        }
     
        /**
        * Gets the Items (Contents) of the inventory.
        * @return
        */
        public ItemStack[] unboxContents()
        {
            ItemStack[] items = new ItemStack[s_items.size()];
            for(int i=0; i<s_items.size(); i++)
            {
                if(s_items.get(i) == null){
                    items[i] = null;
                }else{
                    items[i] = s_items.get(i).unbox();
                }
            }
            return items;
        }
     
    }
     
    ZephireNZ and hammale like this.
  10. Offline

    randomizer1234

    CardboardBox fails to save splash potions, here's a fix (also with code enhancements):
    PHP:
    package com.nuclearw.cardboard;
     
    import java.io.Serializable;
    import java.util.HashMap;
    import java.util.Map;
     
    import org.bukkit.enchantments.Enchantment;
    import org.bukkit.inventory.ItemStack;
     
    /**
    * A serializable ItemStack
    */
    public class CardboardBox implements Serializable {
        private static final 
    long serialVersionUID 729890133797629669L;
     
        private final 
    int typeamount;
        private final 
    short damage;
     
        private final 
    Map<CardboardEnchantmentIntegerenchants;
     
        public 
    CardboardBox(ItemStack item) {
            
    this.type item.getTypeId();
            
    this.amount item.getAmount();
            
    this.damage item.getDurability();
     
            
    this.enchants = new HashMap<CardboardEnchantmentInteger>();
     
            
    Map<EnchantmentIntegerenchantments item.getEnchantments();
     
            for(
    Enchantment enchantment enchantments.keySet()) {
                
    this.enchants.put(new CardboardEnchantment(enchantment), enchantments.get(enchantment));
            }
        }
     
        public 
    ItemStack unbox() {
            
    ItemStack item = new ItemStack(typeamountdamage);
     
            
    HashMap<EnchantmentIntegermap = new HashMap<EnchantmentInteger>();
     
            for(
    CardboardEnchantment cEnchantment enchants.keySet()) {
                
    map.put(cEnchantment.unbox(), this.enchants.get(cEnchantment));
            }
     
            
    item.addUnsafeEnchantments(map);
     
            return 
    item;
        }
    }
    Explaination: if you look at ItemStack's sourcecode (this is the Bukkit sourcecode, but I've checked and CraftBukkit does the same thing), you will see that this constructor
    PHP:
        public ItemStack(final int type, final int amount, final short damage, final Byte data) {
            
    this.type type;
            
    this.amount amount;
            
    this.durability damage;
            if (
    data != null) {
                
    createData(data);
                
    this.durability data;
            }
        }
    which is the one that you use when unboxing, overrides the damage/durability of the item with the data.
    Now, damage is a short, data is a Byte, so it's here that you are losing the "splash part" of the potion.

    With this fix you will also save 8 bits :D
     
    Icyene likes this.
  11. Offline

    agd555

    Hey, how about serializing for MySQL database? I need to "export" inventory object to database, but don't have idea how to save.. "type of type" of item (e.g. color of wool, type of slab).
     
  12. Quick question: why do you store damage and data ? Aren't they the same value for all items ? I want to know if what I knew about data values was wrong xD

    EDIT: And you should save the contents of written books as well :p
     
  13. Offline

    hammale

    Some way to easily store inventories would be great :D (MovingVan as I believe you called it)

    EDIT: o i guess DormantOden already made one :)
     
  14. Offline

    xXSniperzzXx_SD

    This still working?
     
  15. Offline

    Splated

    Could this be used to save a player inventory to the config.yml?

    if so how would i save and load it?
     
  16. Offline

    turt2live

    I've personally updated this, however it requires the Gson library (compiled in CraftBukkit).

    Now it's a single class:

    Code:
    package com.nuclearw.cardboard;
     
    import java.io.Serializable;
    import java.util.HashMap;
    import java.util.Map;
     
    import org.bukkit.configuration.serialization.ConfigurationSerializable;
    import org.bukkit.configuration.serialization.ConfigurationSerialization;
    import org.bukkit.craftbukkit.libs.com.google.gson.Gson;
    import org.bukkit.craftbukkit.libs.com.google.gson.reflect.TypeToken;
    import org.bukkit.inventory.ItemStack;
    import org.bukkit.inventory.meta.ItemMeta;
     
    /**
    * A serializable ItemStack
    */
    public class CardboardBox implements Serializable {
        private static final long serialVersionUID = 729890133797629668L;
        private static final String CLASS_KEY = "CARDBOARD-BOX-SERIAL-DESERIAL-CLASS";
     
        public static final ItemStack NO_ITEM = null;
     
        // JSON String of item
        private final String rawValue;
     
        public CardboardBox(ItemStack item) {
            // Serialize item into a map
            Map<String, Object> serial = item.serialize();
     
            // Check for item meta
            if(serial.get("meta") != null) {
     
                // We have item meta! Let's do out thing...
     
                ItemMeta itemmeta = item.getItemMeta(); // Fetch item meta object
     
                // Serialize item meta
                Map<String, Object> meta = itemmeta.serialize();
     
                // Copy item meta map so we can edit it (Map<String, Object> meta is IMMUTABLE)
                Map<String, Object> meta2 = new HashMap<String, Object>();
                for(String key : meta.keySet()) {
                    meta2.put(key, meta.get(key));
                }
     
                // We loop the keys in the item meta, looking for things we can serialize
                for(String key : meta2.keySet()) {
                    Object o = meta2.get(key); // The object
     
                    if(o instanceof ConfigurationSerializable) { // Can we serialize?
                        // Yes! Let's do it.
                        ConfigurationSerializable serial1 = (ConfigurationSerializable) o; // Cast for ease of use
     
                        // Recursively serialize the object
                        Map<String, Object> serialed = recursizeSerialization(serial1);
     
                        meta2.put(key, serialed); // Re-insert the item, serialized
                    }
                }
                serial.put("meta", meta2); // Re-insert the item meta
            }
     
            // Convert the item to JSON
            Gson gson = new Gson();
            this.rawValue = gson.toJson(serial);
        }
     
        @SuppressWarnings ("unchecked")
        public ItemStack unbox() {
            // Decode the item from JSON
            Gson gson = new Gson();
            Map<String, Object> keys = gson.fromJson(this.rawValue, new TypeToken<Map<String, Object>>() {}.getType());
     
            // Repair Gson thinking int == double
            if(keys.get("amount") != null) {
                Double d = (Double) keys.get("amount");
                Integer i = d.intValue();
                keys.put("amount", i);
            }
     
            // Create item
            ItemStack item;
            try {
                item = ItemStack.deserialize(keys);
            } catch(Exception e) {
                return NO_ITEM; // NO_ITEM == null, shouldn't happen
            }
            if(item == null) {
                return NO_ITEM; // NO_ITEM == null, shouldn't happen
            }
     
            // Handle item meta
            if(keys.containsKey("meta")) {
                Map<String, Object> itemmeta = (Map<String, Object>) keys.get("meta");
                // Convert doubles -> ints
                itemmeta = recursiveDoubleToInteger(itemmeta);
     
                // Deserilize everything
                itemmeta = recursiveDeserialization(itemmeta);
     
                // Create the Item Meta
                ItemMeta meta = (ItemMeta) ConfigurationSerialization.deserializeObject(itemmeta, ConfigurationSerialization.getClassByAlias("ItemMeta"));
     
                // Assign the item meta
                item.setItemMeta(meta);
            }
     
            return item;
        }
     
        // Recursive Methods
     
        private Map<String, Object> recursizeSerialization(ConfigurationSerializable o) {
            // Serialize the object to start off with
            Map<String, Object> map = o.serialize();
     
            // Copy map, as it's immutable
            Map<String, Object> map2 = new HashMap<String, Object>();
            for(String key : map.keySet()) {
                Object o2 = map.get(key);
                if(o2 instanceof ConfigurationSerializable) {
                    // We have a serializable object!
                    ConfigurationSerializable serialObj = (ConfigurationSerializable) o2;
     
                    // Recursively serialize that too
                    Map<String, Object> newMap = recursizeSerialization(serialObj);
     
                    // Insert deserialization class key
                    newMap.put(CLASS_KEY, ConfigurationSerialization.getAlias(serialObj.getClass()));
     
                    // Insert the map in place of 'o2'
                    map2.put(key, newMap);
                } else {
                    map2.put(key, o2); // Else: We can't serialize it, insert into the 'new' map
                }
            }
            // Used in deserialization, this is the class off the passed 'o' object
            map2.put(CLASS_KEY, ConfigurationSerialization.getAlias(o.getClass()));
     
            return map2; // return the 'new' map (fully serialized)
        }
     
        private Map<String, Object> recursiveDoubleToInteger(Map<String, Object> map) {
            // We copy the map
            Map<String, Object> map2 = new HashMap<String, Object>();
            for(String key : map.keySet()) {
                Object o = map.get(key);
                if(o instanceof Double) {
                    // Convert Double -> Int
                    Double d = (Double) o;
                    Integer i = d.intValue();
                    map2.put(key, i);
                } else if(o instanceof Map) { // We have a map, we assume it's a serialized object
                    @SuppressWarnings ("unchecked")
                    Map<String, Object> map3 = (Map<String, Object>) o;
                    map2.put(key, recursiveDoubleToInteger(map3)); // Doubles -> Ints
                } else {
                    map2.put(key, o); // Else: Insert the object as-is
                }
            }
            return map2;
        }
     
        private Map<String, Object> recursiveDeserialization(Map<String, Object> map) {
            // Copy map, immutable should be passed into this method
            Map<String, Object> map2 = new HashMap<String, Object>();
            for(String key : map.keySet()) {
                Object o = map.get(key);
                if(o instanceof Map) {
                    // Attempt to deserialize
                    @SuppressWarnings ("unchecked")
                    Map<String, Object> map3 = (Map<String, Object>) o;
                    if(map3.containsKey(CLASS_KEY)) {
                        String alias = (String) map3.get(CLASS_KEY);
                        Object deserialed = ConfigurationSerialization.deserializeObject(map3, ConfigurationSerialization.getClassByAlias(alias));
                        map2.put(key, deserialed);
                    }// else: ignore, nothing we can do
                } else {
                    map2.put(key, o); // Else: Insert the object as-is
                }
            }
            return map2;
        }
     
    }
    
     
    YoFuzzy3 and NuclearW like this.
  17. Offline

    YoFuzzy3

    turt2live
    Thanks for the update, I've put it to nice use in a plugin I made. Although as I was testing it I found out that it doesn't yet support enchanted books. Maybe you can fix that? :)
     
  18. Offline

    turt2live

    It should... it doesn't do anything weird to the item :confused:
     
  19. Offline

    YoFuzzy3

    Here's 2 screenshots of before and after. http://imgur.com/fy4LZ,dlLrK
     
  20. Offline

    turt2live

  21. Offline

    YoFuzzy3

    The enchanted books lose their data when saving/reading to/from the HashMap. The way I save the HashMaps to a file doesn't affect it. Pastebin made the code look a little messy, but here it is. http://pastebin.com/V1hzYnhx
     
  22. Offline

    turt2live

    Hmm.

    I'll have to look into that. Maybe it would be better to have something like CarboardBox.unpack(String json)?
     
  23. Offline

    YoFuzzy3

    I don't even know what json is.. so what's the benefit?
     
  24. Offline

    turt2live

    JavaScript Object Notation
    It allows for a much simpler save format.

    What it would do for you is basically do something like CarboardBox.pack() to get a String, then use that string elsewhere.
     
  25. Offline

    YoFuzzy3

    But I don't really see anything wrong with the method of saving I'm using at the moment. Isn't the enchanted books bug a problem with CardboardBox? :/
     
  26. Offline

    turt2live

    Not that I can tell.
     
  27. Offline

    Geekola

    I couldn't get this to go... at least not with enchantments so I rewrote a new class that so far seems to tick all the boxes I have tried it against. Putting it here in hopes it helps

    Code:java
    1.  
    2. package com.github.mineGeek.ZoneReset.Data;
    3.  
    4. import java.io.Serializable;
    5. import java.util.HashMap;
    6. import java.util.Map;
    7.  
    8. import org.bukkit.configuration.serialization.ConfigurationSerializable;
    9. import org.bukkit.configuration.serialization.ConfigurationSerialization;
    10. import org.bukkit.inventory.ItemStack;
    11. import org.bukkit.inventory.meta.ItemMeta;
    12. import org.bukkit.material.MaterialData;
    13.  
    14. public class ItemSerializable implements Serializable {
    15.  
    16.  
    17. private static final long serialVersionUID = 9218747208794761041L;
    18. private int materialId;
    19. private byte data;
    20. private short durability;
    21. private int amount;
    22. private Map<String, Object> meta;
    23.  
    24. public ItemSerializable( ItemStack i ) {
    25.  
    26. this.materialId = i.getTypeId();
    27. this.data = i.getData().getData();
    28. this.durability = i.getDurability();
    29. this.amount = i.getAmount();
    30.  
    31. if ( i.getItemMeta() instanceof ConfigurationSerializable ) {
    32. this.meta = this.getNewMap(((ConfigurationSerializable) i.getItemMeta()).serialize());
    33. }
    34.  
    35. }
    36.  
    37. public ItemStack getItemStack() {
    38.  
    39. ItemStack i = new ItemStack( this.materialId );
    40. i.setAmount( this.amount );
    41. i.setDurability( this.durability );
    42. i.setData( new MaterialData( this.data ) );
    43. i.setDurability( this.durability );
    44.  
    45. if ( this.meta != null && !this.meta.isEmpty() ) {
    46. i.setItemMeta( (ItemMeta) ConfigurationSerialization.deserializeObject(this.meta, ConfigurationSerialization.getClassByAlias("ItemMeta")) );
    47. }
    48.  
    49. return i;
    50.  
    51. }
    52.  
    53. @SuppressWarnings("unchecked")
    54. private Map<String, Object> getNewMap( Map<String, Object> map ) {
    55.  
    56. Map<String, Object> newMap = new HashMap<String, Object>();
    57.  
    58. if ( !map.isEmpty() ) {
    59.  
    60. for ( String x : map.keySet() ) {
    61.  
    62. Object value = map.get(x);
    63.  
    64. if ( value instanceof Map ) {
    65. value = getNewMap( (Map<String, Object>) value );
    66. }
    67.  
    68. newMap.put( new String(x), value);
    69. }
    70.  
    71. }
    72.  
    73. return newMap;
    74. }
    75.  
    76. }
    77.  
     
  28. Offline

    turt2live

    How was it not working?
     
  29. Offline

    Geekola

    Cant fully recall now, but I think it was with:
    Code:java
    1.  
    2. // Copy item meta map so we can edit it (Map<String, Object> meta is IMMUTABLE)
    3. Map<String, Object> meta2 = new HashMap<String, Object>();
    4. for(String key : meta.keySet()) {
    5. meta2.put(key, meta.get(key));
    6. }
    7.  

    Was not creating a copy of map meta (e.g. enchantments), but rather a reference to it ( which was imutable). This caused it to not copy enchantments ( though IIRC it copied books and such).

    I haven't had a problem with the code I posted though.
     
  30. Offline

    turt2live

    I'll play around with my code and fix it up then
     
Thread Status:
Not open for further replies.

Share This Page