0

I have the following class.

package net.runelite.client.plugins;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter(AccessLevel.PUBLIC)
@AllArgsConstructor
public enum PluginType
{
    PVM("PvM"),
    PVP("PvP"),
    SKILLING("Skilling"),
    UTILITY("Utilities"),
    MISCELLANEOUS("Miscellaneous"),
    SYSTEM("System"),
    MINIGAME("Minigame"),
    GAMEMODE("Gamemode"),
    UNCATEGORIZED("Uncategorized");

    private final String name;

    @Override
    public String toString()
    {
        return getName();
    }
}

I need to add in an enum in the class above.

And in my use case i cannot modify the class itself so i need a way to inject/add one in there on runetime.

I was thinking of a method below, but did not find a working way to implent this.

try {
    Method valueOf = field.getType().getMethod("valueOf", String.class);
    Object value = valueOf.invoke(null, param);
    field.set(test, value);
} catch ( ReflectiveOperationException e) {

}
thekguy
  • 127
  • 9
  • 2
    You are aware that enums are final? – Thorbjørn Ravn Andersen Feb 13 '21 at 16:53
  • 2
    There is no way to do this. – Louis Wasserman Feb 13 '21 at 16:54
  • @Thorbjørn Ravn Andersen can i delete the full enum and recreate the full thing with an added enum in there then using reflection/injection? – thekguy Feb 13 '21 at 17:00
  • I think that can be useful for you https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection – Dmitrii B Feb 13 '21 at 17:11
  • Enums cannot be changed at runtime. If you do really need this, use a map instead. – Stefan Feb 13 '21 at 17:19
  • Probably not. I would suggest bytecode modification and a custom classloader. Let me just say that this is most likely not a good way to go, even if lombok tempts you. Consider another approach. – Thorbjørn Ravn Andersen Feb 13 '21 at 17:20
  • 2
    If you need an "enum" that can be extended dynamically at runtime, then don't use an `enum`. Instead, use a regular class with `public static final` fields for the constants. – Andreas Feb 13 '21 at 17:44
  • I cannot alter the main class in my case i can only access it on runtime. – thekguy Feb 14 '21 at 14:24
  • I think you are asking for [dynamic enum](https://stackoverflow.com/questions/8467215/generating-enums-dynamically). Another example you can find at https://blog.gotofinal.com/java/diorite/breakingjava/2017/06/24/dynamic-enum.html – Matthias Wegner Feb 14 '21 at 20:51
  • 1
    @MatthiasWegner those hacks tend to fail on newer Java versions. On the other hand, you can achieve the same much simpler. But creating a new instance of an enum type does not imply that you are done. The interaction with the already existing code is likely to cause failures at various place. I provided some examples in [my answer](https://stackoverflow.com/a/66207339/2711488)… See also [this older answer](https://stackoverflow.com/a/34956094/2711488) for an example, how to do this for a foreign class (still no Unsafe nor modifying implementation-specific fields needed). – Holger Feb 15 '21 at 11:39

1 Answers1

4

Since hacks to create new instances exist at various places in the internet, let’s demonstrate a simple one (compared to all those others) and the consequences:

public class EnumHack {
    enum Test {
        A, B, C;
    }
    public static void main(String[] args) throws Throwable {
        Test d = (Test)MethodHandles.lookup()
            .findConstructor(Test.class,
                MethodType.methodType(void.class, String.class, int.class))
            .invokeExact("D", 3);

        System.out.println("created "+d);

        try {
            testSet(d);
        } catch(Throwable t) {
            t.printStackTrace();
        }
        try {
            testSwitch(d);
        } catch(Throwable t) {
            t.printStackTrace();
        }
    }

    static void testSet(Test t) {
        EnumSet<Test> set = EnumSet.allOf(Test.class);
        System.out.println("all: " + set);
        if(set.add(t)) System.out.println("added " + t);
        System.out.println("Set now contains " + set);
    }

    static void testSwitch(Test t) {
        String result = switch(t) {
            case A -> "a";
            case B -> "b";
            case C -> "c";
        };
        System.out.println(result);
    }
}

This will print

created D
all: [A, B, C]
added D
java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
    at java.base/java.util.RegularEnumSet$EnumSetIterator.next(RegularEnumSet.java:106)
    at java.base/java.util.RegularEnumSet$EnumSetIterator.next(RegularEnumSet.java:79)
    at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:456)
    at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453)
    at java.base/java.lang.StringConcatHelper.simpleConcat(StringConcatHelper.java:408)
    at EnumHack.testSet(EnumHack.java:33)
    at EnumHack.main(EnumHack.java:18)
java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
    at EnumHack.testSwitch(EnumHack.java:37)
    at EnumHack.main(EnumHack.java:23)

Even when you manage to alter the internally stored arrays of all constants in the runtime (there are more than one), the assumptions about the actually existing instances are compiled into the code at arbitrary places.

For code outside your control, it is very unlikely to get it to work with a changed range of valid constants for an enum type.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • How would i do the InvokeExact In a strucutre like this? TAG("NAME"); As in my example above. – thekguy Feb 15 '21 at 22:54
  • 1
    The `String` and `int` parameters are the name and ordinal of the constant, which are always present. All other parameters are just appended after them. So, adapt the type to `MethodType.methodType(void.class, String.class, int.class, String.class)` and the invocation to `invokeExact("YOUR_NAME", 9, "YourName")`. But since you are not within the same class as the `enum` type, you’d have to follow the steps of [this answer](https://stackoverflow.com/a/34956094/2711488). – Holger Feb 16 '21 at 07:03