1

I'm stuck on this problem for almost 3 months now and just can't resolve it myself. I hope it's possible. I'm trying to inject this code with my own custom entity class, which is hard to access, because the class is static and the field is final. Somehow i'm not sure if the generic type is a problem on accessing it.

public class EntityTypes<T extends Entity> {
    private final EntityTypes.b<T> aZ;
    [some code here]

    public interface b<T extends Entity> {
        T create(EntityTypes<T> entitytypes, World world);
    }

    public static class a<T extends Entity> {
        private final EntityTypes.b<T> a;
        [more code here]
    }
}

So far i tried to use Reflections, but i keep getting:

java.lang.IllegalArgumentException: Can not set net.server.EntityTypes$b field net.server.EntityTypes$a.a to net.server.EntityTypes

That is my running code:

// works
ReflectionUtils.setFinal(EntityTypes.class, EntityTypes.VILLAGER, "aZ", (EntityTypes.b<CustomVillager>) CustomVillager::new);
// while this does not work!
ReflectionUtils.setFinal(EntityTypes.a.class, EntityTypes.VILLAGER, "a", (EntityTypes.b<CustomVillager>) CustomVillager::new);

public class ReflectionUtils {
    // Does only work on Java 12 and above!!
    public static void setFinal(Class cls, Object obj, String fieldName, Object value) {
        try {
            Field field = cls.getDeclaredField(fieldName);

            FieldHelper.makeNonFinal(field);

            field.setAccessible(true);
            field.set(obj, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // For Java 12 final field injection
    // https://stackoverflow.com/questions/56039341/get-declared-fields-of-java-lang-reflect-fields-in-jdk12/
    public final static class FieldHelper {
        private static final VarHandle MODIFIERS;

        static {
            try {
                var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
                MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
            } catch (IllegalAccessException | NoSuchFieldException ex) {
                throw new RuntimeException(ex);
            }
        }

        public static void makeNonFinal(Field field) {
            int mods = field.getModifiers();
            if (Modifier.isFinal(mods)) {
                MODIFIERS.set(field, mods & ~Modifier.FINAL);
            }
        }
    }
}

public class CustomVillager extends EntityVillager {

    public CustomVillager(EntityTypes<? extends CustomVillager> entityTypes, World world) {
        super(entityTypes, world);
    }
}
  • You will be much more likely to get a rapid response if you post working code that demonstrates this problem. I looked at it a little but for me I think I'd need to code an example--and if I did so it might not even match your problem. – Bill K Sep 20 '19 at 21:37
  • Thank you. I just added the ReflectionUtils class as well. – Kongola Nasif Sep 20 '19 at 21:57
  • Is this minecraft? Maybe bukkit one? Please then provide exact thing yo are changing, would be easier for to to test this. And does it need to work in jdk 12? – GotoFinal Sep 23 '19 at 09:06
  • Apparently, you want to set a `final` field outside of the constructor. Neither, the fact that the class is generic nor its static inner class nature, is relevant at all. If you want to set an instance field of class `a`, you need an instance of `a`, not an instance of `EntityTypes`. – Holger Sep 23 '19 at 16:03
  • By the way, the `FieldHelper.makeNonFinal(field)` works starting with Java 9 (besides using `var` for a declaration, which could be easily changed to an explicit `MethodHandles.Lookup`). But this hack is entirely unnecessary here, as it is only required for `static final` fields. – Holger Sep 23 '19 at 16:18

1 Answers1

0

The exception you are getting means that the Field object represents a field on a class that is different than the class of the the object you are trying to set it on. So in your setFinal() method, you get a Field object representing the field named fieldName on the class cls, and then you try to set that field on the object obj. That means that the object passed in as obj must be an instance of the class cls, or otherwise it won't work.

Looking at the two lines that call setFinal(), the first gets the field aZ in EntityTypes class; this field only exists on an instance of EntityTypes. The second setFinal() call gets the field a in the EntityTypes.a class; this field only exists on an instance of EntityTypes.a. You try to set both of these fields on EntityTypes.VILLAGER. You have not shown the code that declares or initializes EntityTypes.VILLAGER, so we don't know what it is, but these two lines would only work if EntityTypes.VILLAGER were both an instance of EntityTypes and an instance of EntityTypes.a, which is impossible (since they are both classes, neither is a subclass of the other, and Java does not have double inheritance of classes). So one of these two lines must be wrong.

newacct
  • 119,665
  • 29
  • 163
  • 224