Solved Difference between MemorySection and ConfigurableSection ?

Discussion in 'Plugin Development' started by Minecraft Zealot, Jun 16, 2024.

Thread Status:
Not open for further replies.
  1. I'm accessing a custom config file. Every time the server restarts, I get an error when dealing with a MemorySection.
    This error does not exist when a MemorySection is not used.
    Everything works fine when accessing my config file as a ConfigurableSection.

    What's the difference between a MemorySection and a ConfigurableSection ? How can I avoid this error?


    This is my error message:
    java.lang.ClassCastException: class org.bukkit.configuration.MemorySection cannot be cast to class java.util.HashMap (org.bukkit.configuration.MemorySection is in unnamed module of loader java.net.URLClassLoader @4e515669; java.util.HashMap is in module java.base of loader 'bootstrap')

    This is the line that causes it:
    HashMap<Integer, Map<String, Object>> items = (HashMap<Integer, Map<String, Object>>)BigPocketsStorageData.get().get(bigPocket.serialize().toString());

    BigPocketsStorageData.get() returns my FileConfiguration reference.
    bigPocket is an ItemStack


    The following 'fix' of using .getObject() instead of .get() yields no error. However, it's still problematic because it returns null when dealing with a MemorySection upon server restart.

    HashMap<Integer, Map<String, Object>> items = (HashMap<Integer, Map<String, Object>>)BigPocketsStorageData.get().getObject(bigPocket.serialize().toString(),HashMap.class);


    This problem only exists with the MemorySection that is used after a server restart.



    Update:::

    Might've found the problem.
    This line here:
    BigPocketsStorageData.get().set(pants.serialize().toString(), items);
    items is a HashMap.
    The problem may be that I am not deserializing or anything.

    I am retrieving this value with
    HashMap<Integer, Map<String, Object>> items = (HashMap<Integer, Map<String, Object>>)BigPocketsStorageData.get().get(bigPocket.serialize().toString());

    somehow this line flawlessly recognizes that what I inputed was a HashMap. The cast does not fail.

    However, once the server restarts, I think the code is unable to recognize the fact that it is a HashMap. Therefore, I am attempting to cast a String "MemorySection" into a HashMap. This might be why it fails.

    I have no clue what MemorySection is, but it looks like storing it into memory after a server shutdown makes the code unable to recognize the HashMap in the yml file.

    Serialization should fix this... I hope... working on it now...

    Further explanation is appreciated.
     
    Last edited: Jun 17, 2024
  2. Offline

    KarimAKL

    @Minecraft Zealot ConfigurationSection is an interface implemented by the MemorySection class. Your issue comes from the cast to HashMap. In this case, MemorySection#get(String) returns a MemorySection, not a HashMap.
    What kind of data are you trying to get?

    How and where are you saving and loading the file?
     
  3. The data that I'm trying to get is a HashMap<Integer, <String, Object>>
    where <String, Object> is a serialized ItemStack. I'm creating a sorta backpack thingy that stores items.

    I am loading this from a folder in the dataholder folder of my plugin.
    Does MemorySection only exist after the server goes down...?

    Here's how I edit and save the data:

    (the path name is the serialized ItemStack)

    BigPocketsStorageData.get().set(pants.serialize().toString(), items);
    BigPocketsStorageData.save();

    where .get() returns the FileConfiguration reference.

    and .save() is just the .save() method of FileConfiguration.
     
  4. Offline

    KarimAKL

    No, once the server is down, nothing should remain in memory.
    A MemorySection is, using the words from the Javadocs, "A type of ConfigurationSection that is stored in memory."
    A ConfigurationSection represents a section of key and value pairs in a configuration file.
    An example using YAML:
    Code:YAML
    1. section-name:
    2. key1: 'value1'
    3. key2: 100

    In this example, 'section-name' is the name of a configuration section (indicated by the colon at the end, starting a new section). The configuration section has two key-value pairs, one for key1='value1' and another for key2=100.
    A section can also have nested sections. The MemorySection class is the class implementing the ConfigurationSection interface, so that it gains functionality. Classes such as FileConfiguration extend from MemorySection.

    The serialized item is unreliable as a key. It can change between versions, and if I recall correctly, even between server sessions. You should change it to a fixed value.
    Since you're going for a "backpack thingy", an example could look like this:
    Code:YAML
    1. backpack:
    2. 0: <item1>
    3. 1: <item2>
    4. 2: <item3>

    In this case, "backpack" is the fixed key name for a section containing key-value pairs of slot=item.
    If you only care about the contents and not the slots, you can disregard them and use a list instead of a section.

    Yes, you will need to deserialize the string to obtain an ItemStack instance. However, your problem arises from casting the read value to a HashMap, which happens before deserialization can occur.
    Currently, when saving your map, you'll most likely get something like this:
    Code:YAML
    1. <serialized item>:
    2. 0:
    3. material: <material>
    4. amount: <amount>
    5. # ...
    6. 1:
    7. material: <material>
    8. amount: <amount>
    9. # ...

    When deserializing this, you will first need the key for the outer section, which is a serialized item. As mentioned earlier, this is an unreliable key, so you might not always be able to get this. Assuming you have the key, you should be able to get the map as you've done, but you should probably move the cast, so you don't end up with runtime exceptions like this.
    Code:Java
    1. Object value = section.get("path");
    2. if (value instanceof Map) {
    3. // Cast to Map with no issues
    4. } else {
    5. // There's a problem with the configuration
    6. }


    Lastly, I was somewhat recently shown a better way to serialize an ItemStack. If you use Paper, you should consider using that.

    I hope this cleared some things up and helped solve the issue. Let me know if you still have trouble.
     
    Minecraft Zealot likes this.
  5. @KarimAKL
    This is really helpful, thank you.
    I've replaced ItemStack's ConfigurationSerializable .serialize() with .serializeAsBytes() for my keys
    However, I'm still having the same issue. Upon server restart, I can't seem to access any data.
    The plugin still works perfectly unless the server restarts.

    Sorry, I should've clarified. I was thinking about deserializing the HashMap. I thought it was the issue, but now I think that it's probably not.

    I have the following line:
    Object potentialMap = BigPocketsStorageData.get().get(Arrays.toString(bigPocket.serializeAsBytes()));
    then I print it out:
    Bukkit.getLogger().info("Object is: "+potentialMap.toString());

    The first time I "open a 'backpack'", and run this line after a server restart, it prints out the argument of get();
    (prints out Arrays.toString(bigPocket.serializeAsBytes()):

    Object is: MemorySection[path='[31, etc...., 6, 0, 0]', root='YamlConfiguration']

    No data is being accessed.
    However, after the first use after a server restart, it properly prints out the HashMap that has the contents of the 'backpack':

    object is: {2={v=3839, type=GLOWSTONE_DUST, amount=64, meta=UNSPECIFIC_META:{meta-type=UNSPECIFIC, etc.....}]}, PublicBukkitValues={
    "vee:nodropondeath": 1b
    }}}}

    I am so incredibly confused... shouldn't ConfigurationSection#get be returning null if it can't find the value????
    EDIT: nevermind, it is returning null... sometimes...?


    Instead it's returning a MemorySection object that is pretty much just a string of the exact argument that was entered...
     
    Last edited: Jun 18, 2024
  6. Offline

    KarimAKL

    The serializeAsBytes() method should be the value for the item, not the key for the section. The key should be fixed (e.g., the literal string "backpack" or a player's UUID). When using serializeAsBytes(), there's no need for a Map. Additionally, to shorten the serialized value, you can represent the byte array in base64.

    The object returned by the #get(String) method is a MemorySection because you're passing it the path for the section, not a key in that section. The "MemorySection[path='[31, etc...., 6, 0, 0]', root='YamlConfiguration']" is just the string representation (the value returned by #toString()) for the object that represents the section at the specified path (hence the "pretty much just a string of the exact argument"). You can use the methods inherited from ConfigurationSection on this object to interact with the section and its members. It might be null sometimes because the path isn't fixed. Is there any reason for why you're using an item as the key?
    If you use a string (representing the value of serializeAsBytes()) rather than a map (the serialize() method), you can also use #getString(String) rather than #get(String) when getting the values.
     
  7. Why is it that I am able to pass the path and obtain my desired HashMap as the path's key - value pairs as long as the server has not restarted? Everything works fine before a server restart, and I'm struggling to understand what's happening after the server shuts down.

    I've temporarily opted to use itemstack.displayName().toString(). This should not change between server sessions, and it doesn't look like it is... I think...?


    This is what's saved in my yml file:

    ? TextComponentImpl{content="Eccentric Pants", style=StyleImpl{obfuscated=not_set,
    bold=not_set, strikethrough=not_set, underlined=not_set, italic=false, color=NamedTextColor{name="gold",
    value="#ffaa00"}, clickEvent=null, hoverEvent=null, insertion=null, font=null},
    children=[]}
    : '1':
    v: 3839
    type: ENDER_CHEST
    amount: 64
    TranslatableComponentImpl:
    1:
    v: 3839
    type: ENDER_CHEST
    amount: 64

    first time I reopen the "backpack" after server restart:
    printed out object reference from BigPocketsStorageData.get().get(bigPocket.displayName().toString());
    :
    Object is: MemorySection[path='TranslatableComponentImpl', root='YamlConfiguration']

    After opening the backpack and overriding the storage information in the file (putting exact same items inside):

    ? TextComponentImpl{content="Eccentric Pants", style=StyleImpl{obfuscated=not_set,
    bold=not_set, strikethrough=not_set, underlined=not_set, italic=false, color=NamedTextColor{name="gold",
    value="#ffaa00"}, clickEvent=null, hoverEvent=null, insertion=null, font=null},
    children=[]}
    : '1':
    v: 3839
    type: ENDER_CHEST
    amount: 64
    TranslatableComponentImpl:
    1:
    v: 3839
    type: ENDER_CHEST
    amount: 64

    (data is exact same)

    This time it properly returns the HashMap from the path:

    Object is: {1={v=3839, type=ENDER_CHEST, amount=64}}
     
  8. Offline

    KarimAKL

    I'm not sure. We have the #getValues(boolean) for that. Although, we do also have #getConfigurationSection(String) for getting a configuration section. It would probably be better to use one of these, as they're less ambiguous than the simple #get(String) for getting an arbitrary object. It also saves you the hassle of casting.

    That should be fine. The name of the ItemStack is in your control and can therefore be considered a fixed value. However, as seen in your YAML file, you're saving the stringified object rather than just the name. As this could change, you should probably use TextComponent#content() rather than #toString().

    Personally, I would write something like this for saving into the file:
    Code:Java
    1. ItemStack holder = ...; // The outer item (e.g., "Eccentric Pants")
    2. String sectionName = ((TextComponent) holder.displayName()).content();
    3. ConfigurationSection section = config.createSection(sectionName);
    4.  
    5. ItemStack item = ...; // The inner item (e.g., "ENDER_CHEST")
    6. byte[] bytes = item.serializeAsBytes();
    7. String base64 = Base64.getEncoder().encodeToString(bytes);
    8.  
    9. section.set("1", base64); // '1' could come from a loop of the inventory slots

    I made use of the items in your YAML file for the examples.
    This basically creates (and gets if it already exists) a new section in your YAML file with the name of the outer item, then inserts the inner item serialized as a base64 representation of a byte array into the section with the key being the slot index. As mentioned in the example, you could get this from a for-loop of the contents. Remember to save the configuration file after editing the section.

    Let me know if you still have any trouble.
     
    Minecraft Zealot likes this.
  9. I'm getting this error from the cast:
    java.lang.ClassCastException: class net.kyori.adventure.text.TranslatableComponentImpl cannot be cast to class net.kyori.adventure.text.TextComponent (net.kyori.adventure.text.TranslatableComponentImpl and net.kyori.adventure.text.TextComponent are in unnamed module of loader java.net.URLClassLoader @4e515669)

    I just changed it to component.toString() for now.

    IT WORKS!!!!
    very cool. Thank you so much.
    I am able to access data after a server restart
    This solves my initial problem.


    I'm making a sort of custom enchantments plugin. This 'backpack' thing is supposed to be an enchantment that functions like backpack. I wanted this to be item specific and have a unique storage menu associated with each 'backpack' item.

    How can I do this?
    I noticed that itemstack.serialize() has a section where it shows the uuid of an item... but I'm not sure if I can use that...
    I can think of saving a UUID.randomUUID() onto the persistent data container of the ItemMeta, or even just an integer that goes up per unique 'backpack' item use...

    Are there any easier ways to store a persistent unique value associated with an item ?
     
  10. Offline

    KarimAKL

    I assume that's in the case of an ItemStack without a custom display name, as the default name is dependent on the client language and therefore uses a TranslatableComponent. In those cases, you could use the translation key. However, as you were getting to, that might not be what you want (I'll follow up).
    I'm glad to hear you got it working, though.

    I am not sure about ItemStack UUIDs. If they have one, I also wouldn't be sure of if and when they change (e.g., is it a different ItemStack if you have more of them, split the stack, rename the item, etc.). You could experiment, but as you mentioned, saving a random UUID onto the ItemStack should work. However, you should probably check to make sure the item can't be duplicated somehow, which would cause two stacks to have the same UUID and hence the same contents, which would allow duplication of inventories.
    I have not played the game since the addition of persistent data containers, so I'm not familiar with them at all, but I assume they save data across sessions similarly to NBT tags with the addition of an API to manage them.
     
    Minecraft Zealot likes this.
Thread Status:
Not open for further replies.

Share This Page