├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ └── maven.yml ├── renovate.json ├── src ├── main │ └── java │ │ └── org │ │ └── inventivetalent │ │ └── reflection │ │ ├── resolver │ │ ├── wrapper │ │ │ ├── WrapperAbstract.java │ │ │ ├── ClassWrapper.java │ │ │ ├── ConstructorWrapper.java │ │ │ ├── FieldWrapper.java │ │ │ └── MethodWrapper.java │ │ ├── minecraft │ │ │ ├── OBCClassResolver.java │ │ │ └── NMSClassResolver.java │ │ ├── ClassResolver.java │ │ ├── MemberResolver.java │ │ ├── ResolverAbstract.java │ │ ├── ResolverQuery.java │ │ ├── ConstructorResolver.java │ │ ├── MethodResolver.java │ │ └── FieldResolver.java │ │ ├── annotation │ │ ├── Class.java │ │ ├── Field.java │ │ ├── Method.java │ │ └── ReflectionAnnotations.java │ │ ├── accessor │ │ └── FieldAccessor.java │ │ ├── minecraft │ │ ├── MinecraftVersion.java │ │ ├── Minecraft.java │ │ └── DataWatcher.java │ │ └── util │ │ └── AccessUtil.java └── test │ └── java │ └── org │ └── inventivetalent │ └── reflectionhelper │ └── test │ ├── ResolverTest.java │ └── Test.java ├── README.md ├── settings.xml ├── LICENSE ├── .travis.yml └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | target -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: InventivetalentDev 2 | patreon: inventivetalent 3 | custom: ["https://www.paypal.me/inventivetalent", "https://donation.inventivetalent.org"] 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "maven": { 6 | "enabled": true 7 | }, 8 | "ignoreUnstable": false, 9 | "hostRules": [{ 10 | "hostType": "maven", 11 | "endpoint": "https://repo.inventivetalent.org/content/groups/public/" 12 | }] 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up JDK 1.9 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.9 16 | - name: Build with Maven 17 | run: mvn package --file pom.xml 18 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/wrapper/WrapperAbstract.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver.wrapper; 2 | 3 | public abstract class WrapperAbstract { 4 | 5 | /** 6 | * Check whether the wrapped object exists (i.e. is not null) 7 | * 8 | * @return true if the wrapped object exists 9 | */ 10 | public abstract boolean exists(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReflectionHelper 2 | 3 | [![Release](https://jitpack.io/v/org.inventivetalent/reflectionhelper.svg)](https://jitpack.io/#org.inventivetalent/reflectionhelper) 4 | [![Build Status](https://travis-ci.org/InventivetalentDev/ReflectionHelper.svg?branch=master)](https://travis-ci.org/InventivetalentDev/ReflectionHelper) 5 | 6 | API for accessing various classes and their members using reflection. 7 | 8 | See the [SpigotMC page](https://r.spiget.org/19241) for usage details 9 | -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sonatype-nexus-releases 5 | ${env.CI_DEPLOY_USERNAME} 6 | ${env.CI_DEPLOY_PASSWORD} 7 | 8 | 9 | sonatype-nexus-snapshots 10 | ${env.CI_DEPLOY_USERNAME} 11 | ${env.CI_DEPLOY_PASSWORD} 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/minecraft/OBCClassResolver.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver.minecraft; 2 | 3 | import org.inventivetalent.reflection.minecraft.Minecraft; 4 | import org.inventivetalent.reflection.resolver.ClassResolver; 5 | 6 | /** 7 | * {@link ClassResolver} for org.bukkit.craftbukkit.* classes 8 | */ 9 | public class OBCClassResolver extends ClassResolver { 10 | 11 | @Override 12 | public Class resolve(String... names) throws ClassNotFoundException { 13 | for (int i = 0; i < names.length; i++) { 14 | if (!names[i].startsWith("org.bukkit")) { 15 | names[i] = Minecraft.getOBCPackage() + "." + names[i]; 16 | } 17 | } 18 | return super.resolve(names); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/minecraft/NMSClassResolver.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver.minecraft; 2 | 3 | import org.inventivetalent.reflection.minecraft.Minecraft; 4 | import org.inventivetalent.reflection.minecraft.MinecraftVersion; 5 | import org.inventivetalent.reflection.resolver.ClassResolver; 6 | 7 | /** 8 | * {@link ClassResolver} for net.minecraft.server.* classes 9 | */ 10 | public class NMSClassResolver extends ClassResolver { 11 | 12 | @Override 13 | public Class resolve(String... names) throws ClassNotFoundException { 14 | for (int i = 0; i < names.length; i++) { 15 | if (names[i].startsWith("net.minecraft")) 16 | continue; 17 | 18 | if (names[i].contains(".") && MinecraftVersion.VERSION.hasNMSVersionPrefix()) { 19 | /* use class name only */ 20 | String[] path = names[i].split("\\."); 21 | names[i] = Minecraft.getNMSPackage() + "." + path[path.length - 1]; 22 | continue; 23 | } 24 | 25 | /* use the whole name */ 26 | names[i] = Minecraft.getNMSPackage() + "." + names[i]; 27 | } 28 | return super.resolve(names); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Haylee Schäfer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/annotation/Class.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.annotation; 2 | 3 | import org.inventivetalent.reflection.minecraft.Minecraft; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Resolves the annotated {@link org.inventivetalent.reflection.resolver.wrapper.ClassWrapper} or {@link java.lang.Class} field to the first matching class name. 12 | */ 13 | @Target(ElementType.FIELD) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface Class { 16 | 17 | /** 18 | * Name of the class. Use {nms}.MyClass for NMS classes, or {obc}.MyClass for OBC classes. Use > or < as a name prefix in combination with {@link #versions()} to specify versions newer- or older-than. 19 | * 20 | * @return the class name 21 | */ 22 | String[] value(); 23 | 24 | /** 25 | * Specific versions for the names. 26 | * 27 | * @return Array of versions for the class names 28 | */ 29 | Minecraft.Version[] versions() default {}; 30 | 31 | /** 32 | * Whether to ignore any reflection exceptions thrown. Defaults to true 33 | * 34 | * @return whether to ignore exceptions 35 | */ 36 | boolean ignoreExceptions() default true; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/ClassResolver.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver; 2 | 3 | import org.inventivetalent.reflection.resolver.wrapper.ClassWrapper; 4 | import org.inventivetalent.reflection.util.AccessUtil; 5 | 6 | /** 7 | * Default {@link ClassResolver} 8 | */ 9 | public class ClassResolver extends ResolverAbstract { 10 | 11 | public ClassWrapper resolveWrapper(String... names) { 12 | return new ClassWrapper<>(resolveSilent(names)); 13 | } 14 | 15 | public Class resolveSilent(String... names) { 16 | try { 17 | return resolve(names); 18 | } catch (Exception e) { 19 | if (AccessUtil.VERBOSE) { e.printStackTrace(); } 20 | } 21 | return null; 22 | } 23 | 24 | public Class resolve(String... names) throws ClassNotFoundException { 25 | ResolverQuery.Builder builder = ResolverQuery.builder(); 26 | for (String name : names) 27 | builder.with(name); 28 | try { 29 | return super.resolve(builder.build()); 30 | } catch (ReflectiveOperationException e) { 31 | throw (ClassNotFoundException) e; 32 | } 33 | } 34 | 35 | @Override 36 | protected Class resolveObject(ResolverQuery query) throws ReflectiveOperationException { 37 | return Class.forName(query.getName()); 38 | } 39 | 40 | @Override 41 | protected ClassNotFoundException notFoundException(String joinedNames) { 42 | return new ClassNotFoundException("Could not resolve class for " + joinedNames); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/annotation/Field.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.annotation; 2 | 3 | import org.inventivetalent.reflection.minecraft.Minecraft; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Resolves the annotated {@link org.inventivetalent.reflection.resolver.wrapper.FieldWrapper} or {@link java.lang.reflect.Field} field to the first matching field name. 12 | */ 13 | @Target(ElementType.FIELD) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface Field { 16 | 17 | /** 18 | * Name of the class to load this field from 19 | * 20 | * @return name of the class 21 | */ 22 | String className(); 23 | 24 | /** 25 | * Possible names of the field. Use > or < as a name prefix in combination with {@link #versions()} to specify versions newer- or older-than. 26 | * 27 | * @return names of the field 28 | */ 29 | String[] value(); 30 | 31 | /** 32 | * Specific versions for the names. 33 | * 34 | * @return Array of versions for the class names 35 | */ 36 | Minecraft.Version[] versions() default {}; 37 | 38 | /** 39 | * Whether to ignore any reflection exceptions thrown. Defaults to true 40 | * 41 | * @return whether to ignore exceptions 42 | */ 43 | boolean ignoreExceptions() default true; 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/annotation/Method.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.annotation; 2 | 3 | import org.inventivetalent.reflection.minecraft.Minecraft; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Resolves the annotated {@link org.inventivetalent.reflection.resolver.wrapper.MethodWrapper} or {@link java.lang.reflect.Method} field to the first matching method name. 12 | */ 13 | @Target(ElementType.FIELD) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface Method { 16 | 17 | /** 18 | * Name of the class to load this method from 19 | * 20 | * @return name of the class 21 | */ 22 | String className(); 23 | 24 | /** 25 | * Possible names of the method. Use > or < as a name prefix in combination with {@link #versions()} to specify versions newer- or older-than. 26 | * 27 | * @return method names 28 | */ 29 | String[] value(); 30 | 31 | /** 32 | * Specific versions for the names. 33 | * 34 | * @return Array of versions for the class names 35 | */ 36 | Minecraft.Version[] versions() default {}; 37 | 38 | /** 39 | * Whether to ignore any reflection exceptions thrown. Defaults to true 40 | * 41 | * @return whether to ignore exceptions 42 | */ 43 | boolean ignoreExceptions() default true; 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/wrapper/ClassWrapper.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver.wrapper; 2 | 3 | import org.inventivetalent.reflection.util.AccessUtil; 4 | 5 | public class ClassWrapper extends WrapperAbstract { 6 | 7 | private final Class clazz; 8 | 9 | public ClassWrapper(Class clazz) { 10 | this.clazz = clazz; 11 | } 12 | 13 | @Override 14 | public boolean exists() { 15 | return this.clazz != null; 16 | } 17 | 18 | public Class getClazz() { 19 | return clazz; 20 | } 21 | 22 | public String getName() { 23 | return this.clazz.getName(); 24 | } 25 | 26 | public R newInstance() { 27 | try { 28 | return this.clazz.newInstance(); 29 | } catch (Exception e) { 30 | throw new RuntimeException(e); 31 | } 32 | } 33 | 34 | public R newInstanceSilent() { 35 | try { 36 | return this.clazz.newInstance(); 37 | } catch (Exception e) { 38 | if (AccessUtil.VERBOSE) { e.printStackTrace(); } 39 | } 40 | return null; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object object) { 45 | if (this == object) { return true; } 46 | if (object == null || getClass() != object.getClass()) { return false; } 47 | 48 | ClassWrapper that = (ClassWrapper) object; 49 | 50 | return clazz != null ? clazz.equals(that.clazz) : that.clazz == null; 51 | 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return clazz != null ? clazz.hashCode() : 0; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/wrapper/ConstructorWrapper.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver.wrapper; 2 | 3 | import org.inventivetalent.reflection.util.AccessUtil; 4 | 5 | import java.lang.reflect.Constructor; 6 | 7 | public class ConstructorWrapper extends WrapperAbstract { 8 | 9 | private final Constructor constructor; 10 | 11 | public ConstructorWrapper(Constructor constructor) { 12 | this.constructor = constructor; 13 | } 14 | 15 | @Override 16 | public boolean exists() { 17 | return this.constructor != null; 18 | } 19 | 20 | public R newInstance(Object... args) { 21 | try { 22 | return this.constructor.newInstance(args); 23 | } catch (Exception e) { 24 | throw new RuntimeException(e); 25 | } 26 | } 27 | 28 | public R newInstanceSilent(Object... args) { 29 | try { 30 | return this.constructor.newInstance(args); 31 | } catch (Exception e) { 32 | if (AccessUtil.VERBOSE) { e.printStackTrace(); } 33 | } 34 | return null; 35 | } 36 | 37 | public Class[] getParameterTypes() { 38 | return this.constructor.getParameterTypes(); 39 | } 40 | 41 | public Constructor getConstructor() { 42 | return constructor; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object object) { 47 | if (this == object) { return true; } 48 | if (object == null || getClass() != object.getClass()) { return false; } 49 | 50 | ConstructorWrapper that = (ConstructorWrapper) object; 51 | 52 | return constructor != null ? constructor.equals(that.constructor) : that.constructor == null; 53 | 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | return constructor != null ? constructor.hashCode() : 0; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/MemberResolver.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver; 2 | 3 | import org.inventivetalent.reflection.resolver.wrapper.WrapperAbstract; 4 | 5 | import java.lang.reflect.Member; 6 | 7 | /** 8 | * abstract class to resolve members 9 | * 10 | * @param member type 11 | * @see ConstructorResolver 12 | * @see FieldResolver 13 | * @see MethodResolver 14 | */ 15 | public abstract class MemberResolver extends ResolverAbstract { 16 | 17 | protected Class clazz; 18 | 19 | public MemberResolver(Class clazz) { 20 | if (clazz == null) { throw new IllegalArgumentException("class cannot be null"); } 21 | this.clazz = clazz; 22 | } 23 | 24 | public MemberResolver(String className) throws ClassNotFoundException { 25 | this(new ClassResolver().resolve(className)); 26 | } 27 | 28 | /** 29 | * Resolve a member by its index 30 | * 31 | * @param index index 32 | * @return the member 33 | * @throws IndexOutOfBoundsException if the specified index is out of the available member bounds 34 | * @throws ReflectiveOperationException if the object could not be set accessible 35 | */ 36 | public abstract T resolveIndex(int index) throws IndexOutOfBoundsException, ReflectiveOperationException; 37 | 38 | /** 39 | * Resolve member by its index (without exceptions) 40 | * 41 | * @param index index 42 | * @return the member or null 43 | */ 44 | public abstract T resolveIndexSilent(int index); 45 | 46 | /** 47 | * Resolce member wrapper by its index 48 | * 49 | * @param index index 50 | * @return the wrapped member 51 | */ 52 | public abstract WrapperAbstract resolveIndexWrapper(int index); 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/wrapper/FieldWrapper.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver.wrapper; 2 | 3 | import org.inventivetalent.reflection.util.AccessUtil; 4 | 5 | import java.lang.reflect.Field; 6 | 7 | public class FieldWrapper extends WrapperAbstract { 8 | 9 | private final Field field; 10 | 11 | public FieldWrapper(Field field) { 12 | this.field = field; 13 | } 14 | 15 | @Override 16 | public boolean exists() { 17 | return this.field != null; 18 | } 19 | 20 | public String getName() { 21 | return this.field.getName(); 22 | } 23 | 24 | public R get(Object object) { 25 | try { 26 | return (R) this.field.get(object); 27 | } catch (Exception e) { 28 | throw new RuntimeException(e); 29 | } 30 | } 31 | 32 | public R getSilent(Object object) { 33 | try { 34 | return (R) this.field.get(object); 35 | } catch (Exception e) { 36 | if (AccessUtil.VERBOSE) { e.printStackTrace(); } 37 | } 38 | return null; 39 | } 40 | 41 | public void set(Object object, R value) { 42 | try { 43 | this.field.set(object, value); 44 | } catch (Exception e) { 45 | throw new RuntimeException(e); 46 | } 47 | } 48 | 49 | public void setSilent(Object object, R value) { 50 | try { 51 | this.field.set(object, value); 52 | } catch (Exception e) { 53 | if (AccessUtil.VERBOSE) { e.printStackTrace(); } 54 | } 55 | } 56 | 57 | public Field getField() { 58 | return field; 59 | } 60 | 61 | @Override 62 | public boolean equals(Object object) { 63 | if (this == object) { return true; } 64 | if (object == null || getClass() != object.getClass()) { return false; } 65 | 66 | FieldWrapper that = (FieldWrapper) object; 67 | 68 | if (field != null ? !field.equals(that.field) : that.field != null) { return false; } 69 | 70 | return true; 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | return field != null ? field.hashCode() : 0; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | 5 | script: "mvn deploy --settings settings.xml" 6 | env: 7 | global: 8 | - secure: "hKUxyzr5yj9u7JTOpN8r09P/8X9AJMiVvNvgERq+KIG7ngm3hsxODg/CQiB64ch3Rx3molO6g7qUBUTFa4/OiFqmCePxEF+ZpjGER+9tjpWA4Ot/EKWP+ERAuMg/2M7sPhjzfbV1UJfhEvRGNhThTdiThgXeTAG9DIpUAuaYTCfevFRJuFVaunqGN/f0nKNLEr95DkkYycgni2wye1sRRvcOM+shv/VM3YxwCUlUGEEuMlxaN0vvw13n/keu4Jr2qvfyw7750mqu2IqqTLeVpEZgZwb1bM8U2DcVKv6527fOCWQf1+c/3aPHXQnJly1TeS69k4WadefrVZbsA4y5NwX+vE8Ju+0zBSACWQTGGewZAzZIxIrh7m3VXMouvAdQckt8o4vea7Gq61coOXbNuY6rEdHGofEmrWZccjV0Hn8dpVDxTk7jAWxnO+TxOekdLI3mviqyi/Bfn/obqrGvreUOYoFhKyP9ZR2WWO2To0hyhDDyZxHdq1OPjMqeeFYqG40lDq7LvIhFKeT1zt+vR/X+oV04YuZ7/AyhFP0mwjSFeITf8rh7RmPaJrQxE/g3/aXTAEqGtwOUhsIfnAOAI0kiw1qQlNou+iGWwfXVjBX91dsARWXwD6AzMh9FQaQjh0wwj0Ue6H5+K0d9lK0UmWGVZU0O+sKLxulO4D3r2DM=" 9 | - secure: "GH3FtcvragmFD2wbXc9nOCHT4qp8P7aQ8KmSpRbAJ1LR+OkEjvOk1RZwkGjEzQ4AJGWhFHFX3WmFR/9si0ystdWEBI1pubL/L7o79q5l8H60Az90lrz8hmruGeZaVs5klFQBE8IWQ9BvR3n8OGCOIZCqDIshJIB1+3QX7X7vIK95M91ZPps4N5gRpwwU69pB1N16MKJ1BhzZQIWTvA+rB+x5lAlomjWJEwCGuT2d1ZkBjTex/sGEab816LbfOp4/6DvoOdO45FiqCebaSwO6j7Ejg7WV19Q2XLFN6GaFyESHW0hrpoAg96jWyJrKjEP9N0+l7MgwvweZ/cME8BVyA7mstLeqpJgLMlNTKoknKwOv0NNeUn4a64NwCzjkVziokncbCBiJLdw5r0usJqW7rbo9fmQI710asItJN56rqH+elsa1vDpzqSpEqBB3T5AW4VBvsMu5BFhEmC+9W5iMiUhqP/CMFW2+IJeGh4Shb2BNeUm2uelHTiQTogv4/7fK1UUM6opYz2OtFakZO288isg5r+jTrZYFAVVhq4tczyDDhF5fTw9lcpZmreVh/z0u0TemDXM44Iw3wsg8Hmyh+d3C9gDjknpprX3UAN2b2R82+cA43dW3x4rs5m/aHQ4rHuK4oMH0T57gSDtgxp01YjMPQ57Fus2Z8FKVnfhZ8KI=" 10 | deploy: 11 | provider: releases 12 | api_key: 13 | secure: "jA5sN5LpJJUUeTvSdsbAJqrvQvqo05dUeRQDEBgCYA8qDa4aEV0YOOEloscw6nNHSt1cU942oI50Xxp8Ykvfxk8FtRFj4COR3bjtLu2COF4jo/tVa7xcyQhh9dPe2srZkmb59CY5KdjwhMcnlk8B8F0p5ir3NWyoFtwEHeLbfRSCMhHqkwosmSM+V11k3+xG8RcZnTlfTqt/I1GnSoL7vUfLUt16IU5CSioomlW3SMJ0wCpb62iTyTQO7WWfRWa3Yl8kYp+nNXS+2zT1821eLAVH7LugZOs22y0OMu+Mka2uwWa5ylbEttqsJwe2ZTJP5fSDN20uyOcJFNLf67bD87BOEz73kl5E/dwJwvJe8XQtveSZ+LP+Iw1viOia8gYUnnI+l9JRGKNECurbY9QC2/5UjCzULw8NRyNjfLU1C+5htT+BNM+O+Oa6PWqvTONDJgR8dGl476vVJL8JLwfBtjrvMXAC3lqEAi4Y0j9eGOYWihSekfyWnPPiL+oyRUMqqhHLrvC+iKrEWpK+9p0hN9nQ6ugbHyi8o4+F+6iqlmoS/mH9P0hekEUJ5WybqcvncGFvKfMFmXVY3vjg2QoDwqNG4Z/K8phbDHGhcTyoFjpMTEnfJTuAEQTxPSslHvkdw7VjKSjyJ8yn0+Yt2SpsDMsINUtyrAsnui2tWbuS54w=" 14 | file_glob: true 15 | file: 16 | - "target/ReflectionHelper_v*.jar" 17 | skip_cleanup: true 18 | on: 19 | tags: true 20 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/ResolverAbstract.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver; 2 | 3 | import org.inventivetalent.reflection.util.AccessUtil; 4 | 5 | import java.util.Arrays; 6 | import java.util.Map; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | /** 10 | * Abstract resolver class 11 | * 12 | * @param resolved type 13 | * @see ClassResolver 14 | * @see ConstructorResolver 15 | * @see FieldResolver 16 | * @see MethodResolver 17 | */ 18 | public abstract class ResolverAbstract { 19 | 20 | protected final Map resolvedObjects = new ConcurrentHashMap(); 21 | 22 | /** 23 | * Same as {@link #resolve(ResolverQuery...)} but throws no exceptions 24 | * 25 | * @param queries Array of possible queries 26 | * @return the resolved object if it was found, null otherwise 27 | */ 28 | protected T resolveSilent(ResolverQuery... queries) { 29 | try { 30 | return resolve(queries); 31 | } catch (Exception e) { 32 | if (AccessUtil.VERBOSE) { e.printStackTrace(); } 33 | } 34 | return null; 35 | } 36 | 37 | /** 38 | * Attempts to resolve an array of possible queries to an object 39 | * 40 | * @param queries Array of possible queries 41 | * @return the resolved object (if it was found) 42 | * @throws ReflectiveOperationException if none of the possibilities could be resolved 43 | * @throws IllegalArgumentException if the given possibilities are empty 44 | */ 45 | protected T resolve(ResolverQuery... queries) throws ReflectiveOperationException { 46 | if (queries == null || queries.length <= 0) { throw new IllegalArgumentException("Given possibilities are empty"); } 47 | for (ResolverQuery query : queries) { 48 | //Object is already resolved, return it directly 49 | if (resolvedObjects.containsKey(query)) { return resolvedObjects.get(query); } 50 | 51 | //Object is not yet resolved, try to find it 52 | try { 53 | T resolved = resolveObject(query); 54 | //Store if it was found 55 | resolvedObjects.put(query, resolved); 56 | return resolved; 57 | } catch (ReflectiveOperationException e) { 58 | //Not found, ignore the exception 59 | if (AccessUtil.VERBOSE) 60 | e.printStackTrace(); 61 | } 62 | } 63 | 64 | //Couldn't find any of the possibilities 65 | throw notFoundException(Arrays.asList(queries).toString()); 66 | } 67 | 68 | protected abstract T resolveObject(ResolverQuery query) throws ReflectiveOperationException; 69 | 70 | protected ReflectiveOperationException notFoundException(String joinedNames) { 71 | return new ReflectiveOperationException("Objects could not be resolved: " + joinedNames); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/ResolverQuery.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | /** 8 | * Container class for resolver-queries Used by {@link MethodResolver} 9 | * 10 | * @see org.inventivetalent.reflection.resolver.ResolverQuery.Builder 11 | */ 12 | public class ResolverQuery { 13 | 14 | private String name; 15 | private Class[] types; 16 | 17 | public ResolverQuery(String name, Class... types) { 18 | this.name = name; 19 | this.types = types; 20 | } 21 | 22 | public ResolverQuery(String name) { 23 | this.name = name; 24 | this.types = new Class[0]; 25 | } 26 | 27 | public ResolverQuery(Class... types) { 28 | this.types = types; 29 | } 30 | 31 | public String getName() { 32 | return name; 33 | } 34 | 35 | public Class[] getTypes() { 36 | return types; 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) { 41 | if (this == o) { return true; } 42 | if (o == null || getClass() != o.getClass()) { return false; } 43 | 44 | ResolverQuery that = (ResolverQuery) o; 45 | 46 | if (name != null ? !name.equals(that.name) : that.name != null) { return false; } 47 | // Probably incorrect - comparing Object[] arrays with Arrays.equals 48 | return Arrays.equals(types, that.types); 49 | 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | int result = name != null ? name.hashCode() : 0; 55 | result = 31 * result + (types != null ? Arrays.hashCode(types) : 0); 56 | return result; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "ResolverQuery{" + 62 | "name='" + name + '\'' + 63 | ", types=" + Arrays.toString(types) + 64 | '}'; 65 | } 66 | 67 | public static Builder builder() { 68 | return new Builder(); 69 | } 70 | 71 | /** 72 | * Builder class for {@link ResolverQuery} Access using {@link ResolverQuery#builder()} 73 | */ 74 | public static class Builder { 75 | 76 | private List queryList = new ArrayList(); 77 | 78 | private Builder() { 79 | } 80 | 81 | public Builder with(String name, Class[] types) { 82 | queryList.add(new ResolverQuery(name, types)); 83 | return this; 84 | } 85 | 86 | public Builder with(String name) { 87 | queryList.add(new ResolverQuery(name)); 88 | return this; 89 | } 90 | 91 | public Builder with(Class[] types) { 92 | queryList.add(new ResolverQuery(types)); 93 | return this; 94 | } 95 | 96 | public ResolverQuery[] build() { 97 | return queryList.toArray(new ResolverQuery[queryList.size()]); 98 | } 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/test/java/org/inventivetalent/reflectionhelper/test/ResolverTest.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflectionhelper.test; 2 | 3 | import org.inventivetalent.reflection.resolver.ClassResolver; 4 | import org.inventivetalent.reflection.resolver.ConstructorResolver; 5 | import org.inventivetalent.reflection.resolver.FieldResolver; 6 | import org.inventivetalent.reflection.resolver.MethodResolver; 7 | import org.inventivetalent.reflection.resolver.wrapper.ClassWrapper; 8 | import org.junit.Test; 9 | 10 | import java.lang.reflect.Constructor; 11 | import java.lang.reflect.Field; 12 | import java.lang.reflect.InvocationTargetException; 13 | import java.lang.reflect.Method; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | import static org.junit.Assert.assertNotNull; 17 | 18 | public class ResolverTest { 19 | 20 | @Test 21 | public void basic() throws ReflectiveOperationException { 22 | ClassResolver classResolver = new ClassResolver(); 23 | Class clazz = classResolver.resolve("org.inventivetalent.reflection.resolver.wrapper.ClassWrapper"); 24 | assertNotNull(clazz); 25 | assertEquals(ClassWrapper.class, clazz); 26 | 27 | FieldResolver fieldResolver = new FieldResolver(clazz); 28 | Field field = fieldResolver.resolve("clazz"); 29 | assertNotNull(field); 30 | 31 | ConstructorResolver constructorResolver = new ConstructorResolver(clazz); 32 | Constructor constructor = constructorResolver.resolveFirstConstructor(); 33 | assertNotNull(constructor); 34 | 35 | MethodResolver methodResolver = new MethodResolver(clazz); 36 | Method method = methodResolver.resolve("newInstance"); 37 | assertNotNull(method); 38 | } 39 | 40 | @Test 41 | public void shouldResolveSuperField() throws NoSuchFieldException, NoSuchMethodException { 42 | FieldResolver fieldResolver = new FieldResolver(SubClass.class); 43 | Field field = fieldResolver.resolve("a"); 44 | assertNotNull(field); 45 | 46 | MethodResolver methodResolver = new MethodResolver(SubClass.class); 47 | Method method = methodResolver.resolve("a"); 48 | assertNotNull(method); 49 | } 50 | 51 | @Test 52 | public void shouldIgnoreSuperclassIfFoundInSubclass() throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { 53 | BaseClass baseClass = new BaseClass(); 54 | SubClass subClass = new SubClass(); 55 | 56 | FieldResolver fieldResolver = new FieldResolver(SubClass.class); 57 | Field field = fieldResolver.resolve("b"); 58 | 59 | MethodResolver methodResolver = new MethodResolver(SubClass.class); 60 | Method method = methodResolver.resolve("a"); 61 | 62 | assertEquals("sub-b", field.get(subClass)); 63 | assertEquals(2, method.invoke(subClass)); 64 | } 65 | 66 | class BaseClass { 67 | private String a = "base"; 68 | private String b = "base-b"; 69 | 70 | private int a() { 71 | return 1; 72 | } 73 | } 74 | 75 | class SubClass extends BaseClass { 76 | private String b = "sub-b"; 77 | 78 | private int a() { 79 | return 2; 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/ConstructorResolver.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver; 2 | 3 | import org.inventivetalent.reflection.resolver.wrapper.ConstructorWrapper; 4 | import org.inventivetalent.reflection.util.AccessUtil; 5 | 6 | import java.lang.reflect.Constructor; 7 | 8 | /** 9 | * Resolver for constructors 10 | */ 11 | public class ConstructorResolver extends MemberResolver { 12 | 13 | public ConstructorResolver(Class clazz) { 14 | super(clazz); 15 | } 16 | 17 | public ConstructorResolver(String className) throws ClassNotFoundException { 18 | super(className); 19 | } 20 | 21 | @Override 22 | public Constructor resolveIndex(int index) throws IndexOutOfBoundsException, ReflectiveOperationException { 23 | return AccessUtil.setAccessible(this.clazz.getDeclaredConstructors()[index]); 24 | } 25 | 26 | @Override 27 | public Constructor resolveIndexSilent(int index) { 28 | try { 29 | return resolveIndex(index); 30 | } catch (IndexOutOfBoundsException | ReflectiveOperationException ignored) { 31 | } 32 | return null; 33 | } 34 | 35 | @Override 36 | public ConstructorWrapper resolveIndexWrapper(int index) { 37 | return new ConstructorWrapper<>(resolveIndexSilent(index)); 38 | } 39 | 40 | public ConstructorWrapper resolveWrapper(Class[]... types) { 41 | return new ConstructorWrapper<>(resolveSilent(types)); 42 | } 43 | 44 | public Constructor resolveSilent(Class[]... types) { 45 | try { 46 | return resolve(types); 47 | } catch (Exception e) { 48 | if (AccessUtil.VERBOSE) { e.printStackTrace(); } 49 | 50 | } 51 | return null; 52 | } 53 | 54 | public Constructor resolve(Class[]... types) throws NoSuchMethodException { 55 | ResolverQuery.Builder builder = ResolverQuery.builder(); 56 | for (Class[] type : types) 57 | builder.with(type); 58 | try { 59 | return super.resolve(builder.build()); 60 | } catch (ReflectiveOperationException e) { 61 | throw (NoSuchMethodException) e; 62 | } 63 | } 64 | 65 | @Override 66 | protected Constructor resolveObject(ResolverQuery query) throws ReflectiveOperationException { 67 | return AccessUtil.setAccessible(this.clazz.getDeclaredConstructor(query.getTypes())); 68 | } 69 | 70 | public Constructor resolveFirstConstructor() throws ReflectiveOperationException { 71 | for (Constructor constructor : this.clazz.getDeclaredConstructors()) { 72 | return AccessUtil.setAccessible(constructor); 73 | } 74 | return null; 75 | } 76 | 77 | public Constructor resolveFirstConstructorSilent() { 78 | try { 79 | return resolveFirstConstructor(); 80 | } catch (Exception e) { 81 | } 82 | return null; 83 | } 84 | 85 | public Constructor resolveLastConstructor() throws ReflectiveOperationException { 86 | Constructor constructor = null; 87 | for (Constructor constructor1 : this.clazz.getDeclaredConstructors()) { 88 | constructor = constructor1; 89 | } 90 | if (constructor != null) { return AccessUtil.setAccessible(constructor); } 91 | return null; 92 | } 93 | 94 | public Constructor resolveLastConstructorSilent() { 95 | try { 96 | return resolveLastConstructor(); 97 | } catch (Exception e) { 98 | } 99 | return null; 100 | } 101 | 102 | @Override 103 | protected NoSuchMethodException notFoundException(String joinedNames) { 104 | return new NoSuchMethodException("Could not resolve constructor for " + joinedNames + " in class " + this.clazz); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 4.0.0 7 | 8 | org.inventivetalent 9 | reflectionhelper 10 | 1.18.13-SNAPSHOT 11 | 12 | 13 | ReflectionHelper_v${project.version} 14 | src/main/java 15 | 16 | 17 | src/main/java 18 | 19 | **/*.java 20 | 21 | 22 | 23 | src/main/resources 24 | true 25 | 26 | plugin.yml 27 | config.yml 28 | 29 | 30 | 31 | 32 | 33 | org.apache.maven.plugins 34 | maven-compiler-plugin 35 | 3.8.1 36 | 37 | 11 38 | 11 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-shade-plugin 44 | 2.4.3 45 | 46 | 47 | package 48 | 49 | shade 50 | 51 | 52 | 53 | 54 | org.inventivetalent:reflectionhelper** 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | inventive-repo 66 | https://repo.inventivetalent.org/repository/public/ 67 | 68 | 69 | jitpack.io 70 | https://jitpack.io 71 | 72 | 73 | md_5-repo 74 | http://repo.md-5.net/content/repositories/public/ 75 | 76 | 77 | spigot-repo 78 | https://hub.spigotmc.org/nexus/content/groups/public/ 79 | 80 | 81 | 82 | 83 | org.spigotmc 84 | spigot-api 85 | 1.9-R0.1-SNAPSHOT 86 | provided 87 | 88 | 89 | 90 | junit 91 | junit 92 | 4.12 93 | 94 | 95 | 96 | 97 | 98 | sonatype-nexus-releases 99 | https://repo.inventivetalent.org/repository/maven-releases/ 100 | 101 | 102 | sonatype-nexus-snapshots 103 | https://repo.inventivetalent.org/repository/maven-snapshots/ 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/MethodResolver.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver; 2 | 3 | import org.inventivetalent.reflection.resolver.wrapper.MethodWrapper; 4 | import org.inventivetalent.reflection.util.AccessUtil; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | /** 9 | * Resolver for methods 10 | */ 11 | public class MethodResolver extends MemberResolver { 12 | 13 | public MethodResolver(Class clazz) { 14 | super(clazz); 15 | } 16 | 17 | public MethodResolver(String className) throws ClassNotFoundException { 18 | super(className); 19 | } 20 | 21 | public Method resolveSignature(String... signatures)throws ReflectiveOperationException { 22 | for (Method method : clazz.getDeclaredMethods()) { 23 | String methodSignature = MethodWrapper.getMethodSignature(method); 24 | for (String s : signatures) { 25 | if (s.equals(methodSignature)) { 26 | return AccessUtil.setAccessible(method); 27 | } 28 | } 29 | } 30 | return null; 31 | } 32 | 33 | public Method resolveSignatureSilent(String... signatures) { 34 | try { 35 | return resolveSignature(signatures); 36 | } catch (ReflectiveOperationException ignored) { 37 | } 38 | return null; 39 | } 40 | 41 | public MethodWrapper resolveSignatureWrapper(String... signatures) { 42 | return new MethodWrapper(resolveSignatureSilent(signatures)); 43 | } 44 | 45 | @Override 46 | public Method resolveIndex(int index) throws IndexOutOfBoundsException, ReflectiveOperationException { 47 | return AccessUtil.setAccessible(this.clazz.getDeclaredMethods()[index]); 48 | } 49 | 50 | @Override 51 | public Method resolveIndexSilent(int index) { 52 | try { 53 | return resolveIndex(index); 54 | } catch (IndexOutOfBoundsException | ReflectiveOperationException ignored) { 55 | } 56 | return null; 57 | } 58 | 59 | @Override 60 | public MethodWrapper resolveIndexWrapper(int index) { 61 | return new MethodWrapper<>(resolveIndexSilent(index)); 62 | } 63 | 64 | public MethodWrapper resolveWrapper(String... names) { 65 | return new MethodWrapper<>(resolveSilent(names)); 66 | } 67 | 68 | public MethodWrapper resolveWrapper(ResolverQuery... queries) { 69 | return new MethodWrapper<>(resolveSilent(queries)); 70 | } 71 | 72 | public Method resolveSilent(String... names) { 73 | try { 74 | return resolve(names); 75 | } catch (Exception e) { 76 | if (AccessUtil.VERBOSE) { e.printStackTrace(); } 77 | } 78 | return null; 79 | } 80 | 81 | @Override 82 | public Method resolveSilent(ResolverQuery... queries) { 83 | return super.resolveSilent(queries); 84 | } 85 | 86 | public Method resolve(String... names) throws NoSuchMethodException { 87 | ResolverQuery.Builder builder = ResolverQuery.builder(); 88 | for (String name : names) { 89 | builder.with(name); 90 | } 91 | return resolve(builder.build()); 92 | } 93 | 94 | @Override 95 | public Method resolve(ResolverQuery... queries) throws NoSuchMethodException { 96 | try { 97 | return super.resolve(queries); 98 | } catch (ReflectiveOperationException e) { 99 | throw (NoSuchMethodException) e; 100 | } 101 | } 102 | 103 | @Override 104 | protected Method resolveObject(ResolverQuery query) throws ReflectiveOperationException { 105 | Class currentClass = this.clazz; 106 | while (currentClass != null) { 107 | for (Method method : currentClass.getDeclaredMethods()) { 108 | if (method.getName().equals(query.getName()) && (query.getTypes().length == 0 || ClassListEqual(query.getTypes(), method.getParameterTypes()))) { 109 | return AccessUtil.setAccessible(method); 110 | } 111 | } 112 | currentClass = currentClass.getSuperclass(); 113 | } 114 | throw new NoSuchMethodException(); 115 | } 116 | 117 | @Override 118 | protected NoSuchMethodException notFoundException(String joinedNames) { 119 | return new NoSuchMethodException("Could not resolve method for " + joinedNames + " in class " + this.clazz); 120 | } 121 | 122 | static boolean ClassListEqual(Class[] l1, Class[] l2) { 123 | boolean equal = true; 124 | if (l1.length != l2.length) { return false; } 125 | for (int i = 0; i < l1.length; i++) { 126 | if (l1[i] != l2[i]) { 127 | equal = false; 128 | break; 129 | } 130 | } 131 | return equal; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/test/java/org/inventivetalent/reflectionhelper/test/Test.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflectionhelper.test; 2 | 3 | import org.inventivetalent.reflection.minecraft.Minecraft; 4 | import org.inventivetalent.reflection.resolver.wrapper.MethodWrapper; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | import static org.junit.Assert.*; 9 | 10 | public class Test { 11 | 12 | public double primitiveDummyMethod(String aString, double returnValue) { 13 | return returnValue; 14 | } 15 | 16 | public Method genericDummyMethod(Thread aThread, Exception aException) { 17 | return null; 18 | } 19 | 20 | public void voidDummyMethod(Character aCharacter, Class aClass, String aString) { 21 | } 22 | 23 | public String[] complexDummyMethod(Boolean[][] booleans, Object[] objects) { 24 | return null; 25 | } 26 | 27 | public int wildcardMethod1(String string) { 28 | return 0; 29 | } 30 | 31 | public double wildcardMethod2(String string) { 32 | return 0; 33 | } 34 | 35 | public double wildcardMethod3(boolean b) { 36 | return 0; 37 | } 38 | 39 | public int wildCardMethod4(String string) { 40 | return 0; 41 | } 42 | 43 | @org.junit.Test 44 | public void primitiveSignatureTest() throws ReflectiveOperationException { 45 | String signature = MethodWrapper.getMethodSignature(Test.class.getMethod("primitiveDummyMethod", String.class, double.class)); 46 | assertEquals("double primitiveDummyMethod(String,double)", signature); 47 | } 48 | 49 | @org.junit.Test 50 | public void genericSignatureTest() throws ReflectiveOperationException { 51 | String signature = MethodWrapper.getMethodSignature(Test.class.getMethod("genericDummyMethod", Thread.class, Exception.class)); 52 | assertEquals("Method genericDummyMethod(Thread,Exception)", signature); 53 | } 54 | 55 | @org.junit.Test 56 | public void voidSignatureTest() throws ReflectiveOperationException { 57 | String signature = MethodWrapper.getMethodSignature(Test.class.getMethod("voidDummyMethod", Character.class, Class.class, String.class)); 58 | assertEquals("void voidDummyMethod(Character,Class,String)", signature); 59 | } 60 | 61 | @org.junit.Test 62 | public void fullNameSignatureTest() throws ReflectiveOperationException { 63 | String signature = MethodWrapper.getMethodSignature(Test.class.getMethod("genericDummyMethod", Thread.class, Exception.class), true); 64 | assertEquals("java.lang.reflect.Method genericDummyMethod(java.lang.Thread,java.lang.Exception)", signature); 65 | } 66 | 67 | @org.junit.Test 68 | public void complexSignatureTest() throws ReflectiveOperationException { 69 | String signature = MethodWrapper.getMethodSignature(Test.class.getMethod("complexDummyMethod", Boolean[][].class, Object[].class), true); 70 | assertEquals("[Ljava.lang.String; complexDummyMethod([[Ljava.lang.Boolean;,[Ljava.lang.Object;)", signature); 71 | } 72 | 73 | @org.junit.Test 74 | public void signatureObjectTest() throws ReflectiveOperationException { 75 | MethodWrapper.MethodSignature signature = MethodWrapper.MethodSignature.of(Test.class.getMethod("genericDummyMethod", Thread.class, Exception.class), false); 76 | assertEquals("Method", signature.getReturnType()); 77 | assertEquals("genericDummyMethod", signature.getName()); 78 | assertArrayEquals(new String[] { 79 | "Thread", 80 | "Exception" 81 | }, signature.getParameterTypes()); 82 | } 83 | 84 | @org.junit.Test 85 | public void signatureFromStringTest() { 86 | MethodWrapper.MethodSignature signature = MethodWrapper.MethodSignature.fromString("java.lang.reflect.Method genericDummyMethod(java.lang.Thread,java.lang.Exception)"); 87 | assertEquals("java.lang.reflect.Method", signature.getReturnType()); 88 | assertEquals("genericDummyMethod", signature.getName()); 89 | assertEquals("java.lang.Thread", signature.getParameterType(0)); 90 | assertEquals("java.lang.Exception", signature.getParameterType(1)); 91 | } 92 | 93 | @org.junit.Test 94 | public void wildcardTest() throws ReflectiveOperationException { 95 | MethodWrapper.MethodSignature wildcardSignature = MethodWrapper.MethodSignature.fromString("* wildcardMethod*(String)"); 96 | MethodWrapper.MethodSignature testSignature1 = MethodWrapper.MethodSignature.of(Test.class.getMethod("wildcardMethod1", String.class), false); 97 | MethodWrapper.MethodSignature testSignature2 = MethodWrapper.MethodSignature.of(Test.class.getMethod("wildcardMethod2", String.class), false); 98 | MethodWrapper.MethodSignature testSignature3 = MethodWrapper.MethodSignature.of(Test.class.getMethod("wildcardMethod3", boolean.class), false); 99 | MethodWrapper.MethodSignature testSignature4 = MethodWrapper.MethodSignature.of(Test.class.getMethod("wildCardMethod4", String.class), false); 100 | 101 | assertTrue(wildcardSignature.matches(testSignature1)); 102 | assertTrue(wildcardSignature.matches(testSignature2)); 103 | assertFalse(wildcardSignature.matches(testSignature3)); 104 | assertFalse(wildcardSignature.matches(testSignature4)); 105 | } 106 | 107 | @org.junit.Test 108 | public void versionTest() { 109 | assertEquals("net.minecraft.server.v1_16_R3", Minecraft.Version.v1_16_R3.minecraft().getNmsPackage()); 110 | assertEquals("net.minecraft", Minecraft.Version.v1_17_R1.minecraft().getNmsPackage()); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/accessor/FieldAccessor.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.accessor; 2 | 3 | import org.inventivetalent.reflection.util.AccessUtil; 4 | import sun.misc.Unsafe; 5 | 6 | import java.lang.reflect.Field; 7 | import java.lang.reflect.Modifier; 8 | import java.security.AccessController; 9 | import java.security.PrivilegedAction; 10 | 11 | public class FieldAccessor { 12 | 13 | private final Field field; 14 | 15 | public FieldAccessor(Field field) { 16 | this.field = field; 17 | if (field == null) return; 18 | try { 19 | field.setAccessible(true); 20 | } catch (Exception e) { 21 | if (AccessUtil.VERBOSE) { 22 | e.printStackTrace(); 23 | } 24 | } 25 | } 26 | 27 | public boolean hasField() { 28 | return field != null; 29 | } 30 | 31 | public boolean isStatic() { 32 | return Modifier.isStatic(field.getModifiers()); 33 | } 34 | 35 | public boolean isPublic() { 36 | return Modifier.isPublic(field.getModifiers()); 37 | } 38 | 39 | public boolean isFinal() { 40 | return Modifier.isFinal(field.getModifiers()); 41 | } 42 | 43 | public T get(Object obj) { 44 | try { 45 | //noinspection unchecked 46 | return (T) field.get(obj); 47 | } catch (Exception e) { 48 | throw new RuntimeException(e); 49 | } 50 | } 51 | 52 | public void set(Object obj, T value) { 53 | setField(obj, value, field); 54 | } 55 | 56 | /* 57 | * https://github.com/powermock/powermock/blob/42c72daf9d8b04129178d1d3f1fb4e485d3c13dc/powermock-reflect/src/main/java/org/powermock/reflect/internal/WhiteboxImpl.java#L2298-L2403 58 | */ 59 | 60 | private static void setField(Object object, Object value, Field foundField) { 61 | boolean isStatic = (foundField.getModifiers() & Modifier.STATIC) == Modifier.STATIC; 62 | if (isStatic) { 63 | setStaticFieldUsingUnsafe(foundField, value); 64 | } else { 65 | setFieldUsingUnsafe(foundField, object, value); 66 | } 67 | } 68 | 69 | private static void setStaticFieldUsingUnsafe(final Field field, final Object newValue) { 70 | try { 71 | field.setAccessible(true); 72 | int fieldModifiersMask = field.getModifiers(); 73 | boolean isFinalModifierPresent = (fieldModifiersMask & Modifier.FINAL) == Modifier.FINAL; 74 | if (isFinalModifierPresent) { 75 | AccessController.doPrivileged((PrivilegedAction) () -> { 76 | try { 77 | Unsafe unsafe = getUnsafe(); 78 | long offset = unsafe.staticFieldOffset(field); 79 | Object base = unsafe.staticFieldBase(field); 80 | setFieldUsingUnsafe(base, field.getType(), offset, newValue, unsafe); 81 | return null; 82 | } catch (Throwable t) { 83 | throw new RuntimeException(t); 84 | } 85 | }); 86 | } else { 87 | field.set(null, newValue); 88 | } 89 | } catch (SecurityException | IllegalAccessException | IllegalArgumentException ex) { 90 | throw new RuntimeException(ex); 91 | } 92 | } 93 | 94 | private static void setFieldUsingUnsafe(final Field field, final Object object, final Object newValue) { 95 | try { 96 | field.setAccessible(true); 97 | int fieldModifiersMask = field.getModifiers(); 98 | boolean isFinalModifierPresent = (fieldModifiersMask & Modifier.FINAL) == Modifier.FINAL; 99 | if (isFinalModifierPresent) { 100 | AccessController.doPrivileged((PrivilegedAction) () -> { 101 | try { 102 | Unsafe unsafe = getUnsafe(); 103 | long offset = unsafe.objectFieldOffset(field); 104 | setFieldUsingUnsafe(object, field.getType(), offset, newValue, unsafe); 105 | return null; 106 | } catch (Throwable t) { 107 | throw new RuntimeException(t); 108 | } 109 | }); 110 | } else { 111 | try { 112 | field.set(object, newValue); 113 | } catch (IllegalAccessException ex) { 114 | throw new RuntimeException(ex); 115 | } 116 | } 117 | } catch (SecurityException ex) { 118 | throw new RuntimeException(ex); 119 | } 120 | } 121 | 122 | private static Unsafe getUnsafe() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { 123 | Field field = Unsafe.class.getDeclaredField("theUnsafe"); 124 | field.setAccessible(true); 125 | return (Unsafe) field.get(null); 126 | } 127 | 128 | private static void setFieldUsingUnsafe(Object base, Class type, long offset, Object newValue, Unsafe unsafe) { 129 | if (type == Integer.TYPE) { 130 | unsafe.putInt(base, offset, ((Integer) newValue)); 131 | } else if (type == Short.TYPE) { 132 | unsafe.putShort(base, offset, ((Short) newValue)); 133 | } else if (type == Long.TYPE) { 134 | unsafe.putLong(base, offset, ((Long) newValue)); 135 | } else if (type == Byte.TYPE) { 136 | unsafe.putByte(base, offset, ((Byte) newValue)); 137 | } else if (type == Boolean.TYPE) { 138 | unsafe.putBoolean(base, offset, ((Boolean) newValue)); 139 | } else if (type == Float.TYPE) { 140 | unsafe.putFloat(base, offset, ((Float) newValue)); 141 | } else if (type == Double.TYPE) { 142 | unsafe.putDouble(base, offset, ((Double) newValue)); 143 | } else if (type == Character.TYPE) { 144 | unsafe.putChar(base, offset, ((Character) newValue)); 145 | } else { 146 | unsafe.putObject(base, offset, newValue); 147 | } 148 | } 149 | 150 | /* 151 | * https://github.com/powermock/powermock/blob/42c72daf9d8b04129178d1d3f1fb4e485d3c13dc/powermock-reflect/src/main/java/org/powermock/reflect/internal/WhiteboxImpl.java#L2298-L2403 152 | */ 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/minecraft/MinecraftVersion.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.minecraft; 2 | 3 | import org.bukkit.Bukkit; 4 | 5 | import java.util.regex.Matcher; 6 | 7 | public class MinecraftVersion { 8 | 9 | public static final MinecraftVersion VERSION; 10 | 11 | static { 12 | System.out.println("[ReflectionHelper/MinecraftVersion] I am loaded from package " + Minecraft.class.getPackage().getName()); 13 | try { 14 | VERSION = MinecraftVersion.getVersion(); 15 | } catch (Exception e) { 16 | throw new RuntimeException("Failed to get version", e); 17 | } 18 | System.out.println("[ReflectionHelper/MinecraftVersion] Version is " + VERSION); 19 | } 20 | 21 | private final String packageName; 22 | private final int version; 23 | private final String nmsFormat; 24 | private final String obcFormat; 25 | private final String nmsPackage; 26 | private final String obcPackage; 27 | private final boolean nmsVersionPrefix; 28 | 29 | MinecraftVersion(String packageName, int version, String nmsFormat, String obcFormat, boolean nmsVersionPrefix) { 30 | this.packageName = packageName; 31 | this.version = version; 32 | this.nmsFormat = nmsFormat; 33 | this.obcFormat = obcFormat; 34 | this.nmsPackage = String.format(this.nmsFormat, packageName); 35 | this.obcPackage = String.format(this.obcFormat, packageName); 36 | this.nmsVersionPrefix = nmsVersionPrefix; 37 | } 38 | 39 | MinecraftVersion(String packageName, int version) { 40 | this(packageName, version, "net.minecraft.server.%s", "org.bukkit.craftbukkit.%s", true); 41 | } 42 | 43 | // Used by SantiyCheck 44 | MinecraftVersion(Minecraft.Version version) { 45 | this(version.name(), version.version()); 46 | } 47 | 48 | /** 49 | * @return the version-number 50 | */ 51 | public int version() { 52 | return version; 53 | } 54 | 55 | /** 56 | * @deprecated use {@link #getNmsPackage()} / {@link #getObcPackage()} instead 57 | */ 58 | @Deprecated 59 | public String packageName() { 60 | return packageName; 61 | } 62 | 63 | /** 64 | * @return the full package name for net.minecraft.... 65 | */ 66 | public String getNmsPackage() { 67 | return nmsPackage; 68 | } 69 | 70 | /** 71 | * @return the full package name for org.bukkit.... 72 | */ 73 | public String getObcPackage() { 74 | return obcPackage; 75 | } 76 | 77 | /** 78 | * @return if the nms package name has version prefix 79 | */ 80 | public boolean hasNMSVersionPrefix() { 81 | return nmsVersionPrefix; 82 | } 83 | 84 | /** 85 | * @param version the version to check 86 | * @return true if this version is older than the specified version 87 | */ 88 | public boolean olderThan(Minecraft.Version version) { 89 | return version() < version.version(); 90 | } 91 | 92 | /** 93 | * @param version the version to check 94 | * @return true if this version is equals than the specified version 95 | */ 96 | public boolean equal(Minecraft.Version version) { 97 | return version() == version.version(); 98 | } 99 | 100 | /** 101 | * @param version the version to check 102 | * @return true if this version is newer than the specified version 103 | */ 104 | public boolean newerThan(Minecraft.Version version) { 105 | return version() >= version.version(); 106 | } 107 | 108 | /** 109 | * @param oldVersion The older version to check 110 | * @param newVersion The newer version to check 111 | * @return true if this version is newer than the oldVersion and older that the newVersion 112 | */ 113 | public boolean inRange(Minecraft.Version oldVersion, Minecraft.Version newVersion) { 114 | return newerThan(oldVersion) && olderThan(newVersion); 115 | } 116 | 117 | public boolean matchesPackageName(String packageName) { 118 | return this.packageName.toLowerCase().contains(packageName.toLowerCase()); 119 | } 120 | 121 | @Override 122 | public String toString() { 123 | return packageName + " (" + version() + ")"; 124 | } 125 | 126 | public static MinecraftVersion getVersion() { 127 | Class serverClass; 128 | try { 129 | serverClass = Bukkit.getServer().getClass(); 130 | } catch (Exception e) { 131 | System.err.println("[ReflectionHelper/MinecraftVersion] Failed to get bukkit server class: " + e.getMessage()); 132 | System.err.println("[ReflectionHelper/MinecraftVersion] Assuming we're in a test environment!"); 133 | return null; 134 | } 135 | String name = serverClass.getPackage().getName(); 136 | String versionPackage = name.substring(name.lastIndexOf('.') + 1); 137 | for (Minecraft.Version version : Minecraft.Version.values()) { 138 | MinecraftVersion minecraftVersion = version.minecraft(); 139 | if (minecraftVersion.matchesPackageName(versionPackage)) { 140 | return minecraftVersion; 141 | } 142 | } 143 | System.err.println("[ReflectionHelper/MinecraftVersion] Failed to find version enum for '" + name + "'/'" + versionPackage + "'"); 144 | 145 | System.out.println("[ReflectionHelper/MinecraftVersion] Generating dynamic constant..."); 146 | Matcher matcher = Minecraft.NUMERIC_VERSION_PATTERN.matcher(versionPackage); 147 | while (matcher.find()) { 148 | if (matcher.groupCount() < 3) { 149 | continue; 150 | } 151 | 152 | String majorString = matcher.group(1); 153 | String minorString = matcher.group(2); 154 | if (minorString.length() == 1) { 155 | minorString = "0" + minorString; 156 | } 157 | String patchString = matcher.group(3); 158 | if (patchString.length() == 1) { 159 | patchString = "0" + patchString; 160 | } 161 | 162 | String numVersionString = majorString + minorString + patchString; 163 | int numVersion = Integer.parseInt(numVersionString); 164 | String packageName = "v" + versionPackage.substring(1).toUpperCase(); 165 | 166 | boolean postOneSeventeen = numVersion > 11701; 167 | 168 | //dynamic register version 169 | System.out.println("[ReflectionHelper/MinecraftVersion] Injected dynamic version " + packageName + " (#" + numVersion + ")."); 170 | System.out.println("[ReflectionHelper/MinecraftVersion] Please inform inventivetalent about the outdated version, as this is not guaranteed to work."); 171 | if (postOneSeventeen) { // new nms package format for 1.17+ 172 | return new MinecraftVersion(packageName, numVersion, "net.minecraft", "org.bukkit.craftbukkit.%s", false); 173 | } 174 | return new MinecraftVersion(packageName, numVersion); 175 | } 176 | 177 | System.err.println("[ReflectionHelper/MinecraftVersion] Failed to create dynamic version for " + versionPackage); 178 | 179 | return new MinecraftVersion("UNKNOWN", -1); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/util/AccessUtil.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.util; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | import java.lang.invoke.VarHandle; 5 | import java.lang.reflect.*; 6 | import java.security.AccessController; 7 | import java.security.PrivilegedAction; 8 | 9 | /** 10 | * Helper class to set fields, methods & constructors accessible 11 | */ 12 | public abstract class AccessUtil { 13 | 14 | public static final boolean VERBOSE = System.getProperties().containsKey("org_inventivetalent_reflection_verbose"); 15 | 16 | private static final Object modifiersVarHandle; 17 | private static final Field modifiersField; 18 | 19 | /** 20 | * Sets the field accessible and removes final modifiers 21 | * 22 | * @param field Field to set accessible 23 | * @return the Field 24 | * @throws ReflectiveOperationException (usually never) 25 | */ 26 | 27 | public static Field setAccessible(Field field) throws ReflectiveOperationException { 28 | return setAccessible(field, false); 29 | } 30 | 31 | public static Field setAccessible(Field field, boolean readOnly) throws ReflectiveOperationException { 32 | return setAccessible(field, readOnly, false); 33 | } 34 | 35 | private static Field setAccessible(Field field, boolean readOnly, boolean privileged) throws ReflectiveOperationException { 36 | try { 37 | field.setAccessible(true); 38 | } catch (InaccessibleObjectException e) { 39 | if (VERBOSE) { 40 | System.err.println("field.setAccessible"); 41 | e.printStackTrace(); 42 | } 43 | if (!privileged) { 44 | return AccessController.doPrivileged((PrivilegedAction) () -> { 45 | try { 46 | return setAccessible(field, readOnly, true); 47 | } catch (Exception e1) { 48 | if (VERBOSE) { 49 | System.err.println("privileged setAccessible"); 50 | e1.printStackTrace(); 51 | } 52 | } 53 | return field; 54 | }); 55 | } 56 | } 57 | if (readOnly) { 58 | return field; 59 | } 60 | removeFinal(field, privileged); 61 | return field; 62 | } 63 | 64 | private static void removeFinal(Field field, boolean privileged) throws ReflectiveOperationException { 65 | int modifiers = field.getModifiers(); 66 | if (Modifier.isFinal(modifiers)) { 67 | try { 68 | removeFinalSimple(field); 69 | } catch (Exception e1) { 70 | if (VERBOSE) { 71 | System.err.println("removeFinalSimple"); 72 | e1.printStackTrace(); 73 | } 74 | try { 75 | removeFinalVarHandle(field); 76 | } catch (Exception e2) { 77 | if (VERBOSE) { 78 | System.err.println("removeFinalVarHandle"); 79 | e2.printStackTrace(); 80 | } 81 | try { 82 | removeFinalNativeDeclaredFields(field); 83 | } catch (Exception e3) { 84 | if (VERBOSE) { 85 | System.err.println("removeFinalNativeDeclaredFields"); 86 | e3.printStackTrace(); 87 | } 88 | if (!privileged) { 89 | AccessController.doPrivileged((PrivilegedAction) () -> { 90 | try { 91 | setAccessible(field, false, true); 92 | } catch (Exception e) { 93 | if (VERBOSE) { 94 | System.err.println("privileged setAccessible"); 95 | e1.printStackTrace(); 96 | } 97 | } 98 | return null; 99 | }); 100 | return; 101 | } 102 | } 103 | } 104 | } 105 | if (VERBOSE && Modifier.isFinal(field.getModifiers())) { 106 | System.err.println("[ReflectionHelper] Failed to make " + field + " non-final"); 107 | } 108 | } 109 | } 110 | 111 | private static void removeFinalSimple(Field field) throws ReflectiveOperationException { 112 | int modifiers = field.getModifiers(); 113 | Field modifiersField = Field.class.getDeclaredField("modifiers"); 114 | modifiersField.setAccessible(true); 115 | modifiersField.setInt(field, modifiers & ~Modifier.FINAL); 116 | } 117 | 118 | private static void removeFinalVarHandle(Field field) throws ReflectiveOperationException { 119 | int modifiers = field.getModifiers(); 120 | int newModifiers = modifiers & ~Modifier.FINAL; 121 | if (modifiersVarHandle != null) { 122 | ((VarHandle) modifiersVarHandle).set(field, newModifiers); 123 | } else { 124 | modifiersField.setInt(field, newModifiers); 125 | } 126 | } 127 | 128 | private static void removeFinalNativeDeclaredFields(Field field) throws ReflectiveOperationException { 129 | removeFinalNativeDeclaredFields(field, false); 130 | } 131 | 132 | private static void removeFinalNativeDeclaredFields(Field field, boolean secondTry) throws ReflectiveOperationException { 133 | int modifiers = field.getModifiers(); 134 | // https://github.com/ViaVersion/ViaVersion/blob/e07c994ddc50e00b53b728d08ab044e66c35c30f/bungee/src/main/java/us/myles/ViaVersion/bungee/platform/BungeeViaInjector.java 135 | // Java 12 compatibility *this is fine* 136 | Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); 137 | getDeclaredFields0.setAccessible(true); 138 | Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false); 139 | for (Field classField : fields) { 140 | if ("modifiers".equals(classField.getName())) { 141 | classField.setAccessible(true); 142 | classField.set(field, modifiers & ~Modifier.FINAL); 143 | break; 144 | } 145 | } 146 | } 147 | 148 | /** 149 | * Sets the method accessible 150 | * 151 | * @param method Method to set accessible 152 | * @return the Method 153 | * @throws ReflectiveOperationException (usually never) 154 | */ 155 | public static Method setAccessible(Method method) throws ReflectiveOperationException { 156 | method.setAccessible(true); 157 | return method; 158 | } 159 | 160 | /** 161 | * Sets the constructor accessible 162 | * 163 | * @param constructor Constructor to set accessible 164 | * @return the Constructor 165 | * @throws ReflectiveOperationException (usually never) 166 | */ 167 | public static Constructor setAccessible(Constructor constructor) throws ReflectiveOperationException { 168 | constructor.setAccessible(true); 169 | return constructor; 170 | } 171 | 172 | private static Object initModifiersVarHandle() { 173 | try { 174 | VarHandle.class.getName(); // Makes this method fail-fast on JDK 8 175 | return MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup()) 176 | .findVarHandle(Field.class, "modifiers", int.class); 177 | } catch (IllegalAccessException | NoClassDefFoundError | NoSuchFieldException e) { 178 | if (VERBOSE) { 179 | e.printStackTrace(); 180 | } 181 | } 182 | return null; 183 | } 184 | 185 | private static Field initModifiersField() { 186 | try { 187 | Field modifiersField = Field.class.getDeclaredField("modifiers"); 188 | modifiersField.setAccessible(true); 189 | return modifiersField; 190 | } catch (NoSuchFieldException ignored) {} 191 | return null; 192 | } 193 | 194 | static { 195 | if (VERBOSE) { 196 | System.out.println("[ReflectionHelper] Verbose mode enabled"); 197 | } 198 | 199 | modifiersVarHandle = initModifiersVarHandle(); 200 | modifiersField = initModifiersField(); 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/annotation/ReflectionAnnotations.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.annotation; 2 | 3 | import org.inventivetalent.reflection.minecraft.Minecraft; 4 | import org.inventivetalent.reflection.minecraft.MinecraftVersion; 5 | import org.inventivetalent.reflection.resolver.ClassResolver; 6 | import org.inventivetalent.reflection.resolver.FieldResolver; 7 | import org.inventivetalent.reflection.resolver.MethodResolver; 8 | import org.inventivetalent.reflection.resolver.wrapper.ClassWrapper; 9 | import org.inventivetalent.reflection.resolver.wrapper.FieldWrapper; 10 | import org.inventivetalent.reflection.resolver.wrapper.MethodWrapper; 11 | 12 | import java.lang.annotation.Annotation; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.regex.Matcher; 16 | import java.util.regex.Pattern; 17 | 18 | public class ReflectionAnnotations { 19 | 20 | public static final ReflectionAnnotations INSTANCE = new ReflectionAnnotations(); 21 | 22 | static final Pattern classRefPattern = Pattern.compile("@Class\\((.*)\\)"); 23 | 24 | private ReflectionAnnotations() { 25 | } 26 | 27 | public void load(Object toLoad) { 28 | if (toLoad == null) { throw new IllegalArgumentException("toLoad cannot be null"); } 29 | 30 | ClassResolver classResolver = new ClassResolver(); 31 | 32 | for (java.lang.reflect.Field field : toLoad.getClass().getDeclaredFields()) { 33 | Class classAnnotation = field.getAnnotation(Class.class); 34 | Field fieldAnnotation = field.getAnnotation(Field.class); 35 | Method methodAnnotation = field.getAnnotation(Method.class); 36 | 37 | if (classAnnotation == null && fieldAnnotation == null && methodAnnotation == null) { 38 | continue; 39 | } else { 40 | field.setAccessible(true); 41 | } 42 | 43 | if (classAnnotation != null) { 44 | List nameList = parseAnnotationVersions(Class.class, classAnnotation); 45 | if (nameList.isEmpty()) { throw new IllegalArgumentException("@Class names cannot be empty"); } 46 | String[] names = nameList.toArray(new String[nameList.size()]); 47 | for (int i = 0; i < names.length; i++) {// Replace NMS & OBC 48 | names[i] = names[i] 49 | .replace("{nms}", "net.minecraft.server." + MinecraftVersion.VERSION.packageName()) 50 | .replace("{obc}", "org.bukkit.craftbukkit." + MinecraftVersion.VERSION.packageName()); 51 | } 52 | try { 53 | if (ClassWrapper.class.isAssignableFrom(field.getType())) { 54 | field.set(toLoad, classResolver.resolveWrapper(names)); 55 | } else if (java.lang.Class.class.isAssignableFrom(field.getType())) { 56 | field.set(toLoad, classResolver.resolve(names)); 57 | } else { 58 | throwInvalidFieldType(field, toLoad, "Class or ClassWrapper"); 59 | return; 60 | } 61 | } catch (ReflectiveOperationException e) { 62 | if (!classAnnotation.ignoreExceptions()) { 63 | throwReflectionException("@Class", field, toLoad, e); 64 | return; 65 | } 66 | } 67 | } else if (fieldAnnotation != null) { 68 | List nameList = parseAnnotationVersions(Field.class, fieldAnnotation); 69 | if (nameList.isEmpty()) { throw new IllegalArgumentException("@Field names cannot be empty"); } 70 | String[] names = nameList.toArray(new String[nameList.size()]); 71 | try { 72 | FieldResolver fieldResolver = new FieldResolver(parseClass(Field.class, fieldAnnotation, toLoad)); 73 | if (FieldWrapper.class.isAssignableFrom(field.getType())) { 74 | field.set(toLoad, fieldResolver.resolveWrapper(names)); 75 | } else if (java.lang.reflect.Field.class.isAssignableFrom(field.getType())) { 76 | field.set(toLoad, fieldResolver.resolve(names)); 77 | } else { 78 | throwInvalidFieldType(field, toLoad, "Field or FieldWrapper"); 79 | return; 80 | } 81 | } catch (ReflectiveOperationException e) { 82 | if (!fieldAnnotation.ignoreExceptions()) { 83 | throwReflectionException("@Field", field, toLoad, e); 84 | return; 85 | } 86 | } 87 | } else if (methodAnnotation != null) { 88 | List nameList = parseAnnotationVersions(Method.class, methodAnnotation); 89 | if (nameList.isEmpty()) { throw new IllegalArgumentException("@Method names cannot be empty"); } 90 | String[] names = nameList.toArray(new String[nameList.size()]); 91 | 92 | boolean isSignature = names[0].contains(" ");// Only signatures can contain spaces (e.g. "void aMethod()") 93 | for (String s : names) { 94 | if (s.contains(" ") != isSignature) { 95 | throw new IllegalArgumentException("Inconsistent method names: Cannot have mixed signatures/names"); 96 | } 97 | } 98 | 99 | try { 100 | MethodResolver methodResolver = new MethodResolver(parseClass(Method.class, methodAnnotation, toLoad)); 101 | if (MethodWrapper.class.isAssignableFrom(field.getType())) { 102 | if (isSignature) { 103 | field.set(toLoad, methodResolver.resolveSignatureWrapper(names)); 104 | } else { 105 | field.set(toLoad, methodResolver.resolveWrapper(names)); 106 | } 107 | } else if (java.lang.reflect.Method.class.isAssignableFrom(field.getType())) { 108 | if (isSignature) { 109 | field.set(toLoad, methodResolver.resolveSignature(names)); 110 | } else { 111 | field.set(toLoad, methodResolver.resolve(names)); 112 | } 113 | } else { 114 | throwInvalidFieldType(field, toLoad, "Method or MethodWrapper"); 115 | return; 116 | } 117 | } catch (ReflectiveOperationException e) { 118 | if (!methodAnnotation.ignoreExceptions()) { 119 | throwReflectionException("@Method", field, toLoad, e); 120 | return; 121 | } 122 | } 123 | } 124 | } 125 | } 126 | 127 | /** 128 | * Parses an annotation to the current server version. Removes all entries that don't match the version, but keeps the original order for matching names. 129 | * 130 | * @param clazz Class of the annotation 131 | * @param annotation annotation 132 | * @param annotation type 133 | * @return a list of matching names 134 | */ 135 | List parseAnnotationVersions(java.lang.Class clazz, A annotation) { 136 | List list = new ArrayList<>(); 137 | 138 | try { 139 | String[] names = (String[]) clazz.getMethod("value").invoke(annotation); 140 | Minecraft.Version[] versions = (Minecraft.Version[]) clazz.getMethod("versions").invoke(annotation); 141 | 142 | if (versions.length == 0) {// No versions specified -> directly use the names 143 | for (String name : names) { 144 | list.add(name); 145 | } 146 | } else { 147 | if (versions.length > names.length) { 148 | throw new RuntimeException("versions array cannot have more elements than the names (" + clazz + ")"); 149 | } 150 | for (int i = 0; i < versions.length; i++) { 151 | if (MinecraftVersion.VERSION.equal(versions[i])) {// Wohoo, perfect match! 152 | list.add(names[i]); 153 | } else { 154 | if (names[i].startsWith(">") && Minecraft.VERSION.newerThan(versions[i])) {// Match if the current version is newer 155 | list.add(names[i].substring(1)); 156 | } else if (names[i].startsWith("<") && Minecraft.VERSION.olderThan(versions[i])) {// Match if the current version is older 157 | list.add(names[i].substring(1)); 158 | } 159 | } 160 | } 161 | } 162 | } catch (ReflectiveOperationException e) { 163 | throw new RuntimeException(e); 164 | } 165 | 166 | return list; 167 | } 168 | 169 | String parseClass(java.lang.Class clazz, A annotation, Object toLoad) { 170 | try { 171 | String className = (String) clazz.getMethod("className").invoke(annotation); 172 | Matcher matcher = classRefPattern.matcher(className); 173 | while (matcher.find()) { 174 | if (matcher.groupCount() != 1) { continue; } 175 | String fieldName = matcher.group(1);// It's a reference to a previously loaded class 176 | java.lang.reflect.Field field = toLoad.getClass().getField(fieldName); 177 | if (ClassWrapper.class.isAssignableFrom(field.getType())) { 178 | return ((ClassWrapper) field.get(toLoad)).getName(); 179 | } else if (java.lang.Class.class.isAssignableFrom(field.getType())) { 180 | return ((java.lang.Class) field.get(toLoad)).getName(); 181 | } 182 | } 183 | return className; 184 | } catch (ReflectiveOperationException e) { 185 | throw new RuntimeException(e); 186 | } 187 | } 188 | 189 | void throwInvalidFieldType(java.lang.reflect.Field field, Object toLoad, String expected) { 190 | throw new IllegalArgumentException("Field " + field.getName() + " in " + toLoad.getClass() + " is not of type " + expected + ", it's " + field.getType()); 191 | } 192 | 193 | void throwReflectionException(String annotation, java.lang.reflect.Field field, Object toLoad, ReflectiveOperationException exception) { 194 | throw new RuntimeException("Failed to set " + annotation + " field " + field.getName() + " in " + toLoad.getClass(), exception); 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/wrapper/MethodWrapper.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver.wrapper; 2 | 3 | import org.inventivetalent.reflection.util.AccessUtil; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.Arrays; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | public class MethodWrapper extends WrapperAbstract { 11 | 12 | private final Method method; 13 | 14 | public MethodWrapper(Method method) { 15 | this.method = method; 16 | } 17 | 18 | @Override 19 | public boolean exists() { 20 | return this.method != null; 21 | } 22 | 23 | public String getName() { 24 | return this.method.getName(); 25 | } 26 | 27 | public R invoke(Object object, Object... args) { 28 | try { 29 | return (R) this.method.invoke(object, args); 30 | } catch (Exception e) { 31 | throw new RuntimeException(e); 32 | } 33 | } 34 | 35 | public R invokeSilent(Object object, Object... args) { 36 | try { 37 | return (R) this.method.invoke(object, args); 38 | } catch (Exception e) { 39 | if (AccessUtil.VERBOSE) { e.printStackTrace(); } 40 | } 41 | return null; 42 | } 43 | 44 | public Method getMethod() { 45 | return method; 46 | } 47 | 48 | @Override 49 | public boolean equals(Object object) { 50 | if (this == object) { return true; } 51 | if (object == null || getClass() != object.getClass()) { return false; } 52 | 53 | MethodWrapper that = (MethodWrapper) object; 54 | 55 | return method != null ? method.equals(that.method) : that.method == null; 56 | 57 | } 58 | 59 | @Override 60 | public int hashCode() { 61 | return method != null ? method.hashCode() : 0; 62 | } 63 | 64 | /** 65 | * Generates a method's signature. 66 | * 67 | * @param method the method to get the signature for 68 | * @param fullClassNames whether to use the full class name 69 | * @return the method's signature 70 | */ 71 | public static String getMethodSignature(Method method, boolean fullClassNames) { 72 | // StringBuilder stringBuilder = new StringBuilder(); 73 | // 74 | // Class returnType = method.getReturnType(); 75 | // if (returnType.isPrimitive()) { 76 | // stringBuilder.append(returnType); 77 | // } else { 78 | // stringBuilder.append(fullClassNames ? returnType.getName() : returnType.getSimpleName()); 79 | // } 80 | // stringBuilder.append(" "); 81 | // stringBuilder.append(method.getName()); 82 | // 83 | // stringBuilder.append("("); 84 | // 85 | // boolean first = true; 86 | // for (Class clazz : method.getParameterTypes()) { 87 | // if (!first) { stringBuilder.append(","); } 88 | // stringBuilder.append(fullClassNames ? clazz.getName() : clazz.getSimpleName()); 89 | // first = false; 90 | // } 91 | // return stringBuilder.append(")").toString(); 92 | 93 | return MethodSignature.of(method, fullClassNames).getSignature(); 94 | } 95 | 96 | /** 97 | * @param method Method to get the signature for 98 | * @return the signature 99 | * @see #getMethodSignature(Method, boolean) 100 | */ 101 | public static String getMethodSignature(Method method) { 102 | return getMethodSignature(method, false); 103 | } 104 | 105 | public static class MethodSignature { 106 | static final Pattern SIGNATURE_STRING_PATTERN = Pattern.compile("(.+) (.*)\\((.*)\\)"); 107 | 108 | private final String returnType; 109 | private final Pattern returnTypePattern; 110 | private final String name; 111 | private final Pattern namePattern; 112 | private final String[] parameterTypes; 113 | private final String signature; 114 | 115 | public MethodSignature(String returnType, String name, String[] parameterTypes) { 116 | this.returnType = returnType; 117 | this.returnTypePattern = Pattern.compile(returnType 118 | .replace("?", "\\w") 119 | .replace("*", "\\w*") 120 | .replace("[", "\\[") 121 | .replace("]", "\\]")); 122 | this.name = name; 123 | this.namePattern = Pattern.compile(name.replace("?", "\\w").replace("*", "\\w*")); 124 | this.parameterTypes = parameterTypes; 125 | 126 | StringBuilder builder = new StringBuilder(); 127 | builder.append(returnType).append(" ").append(name).append("("); 128 | boolean first = true; 129 | for (String parameterType : parameterTypes) { 130 | if (!first) { 131 | builder.append(","); 132 | } 133 | builder.append(parameterType); 134 | first = false; 135 | } 136 | this.signature = builder.append(")").toString(); 137 | } 138 | 139 | public static MethodSignature of(Method method, boolean fullClassNames) { 140 | Class returnType = method.getReturnType(); 141 | Class[] parameterTypes = method.getParameterTypes(); 142 | 143 | String returnTypeString; 144 | if (returnType.isPrimitive()) { 145 | returnTypeString = returnType.toString(); 146 | } else { 147 | returnTypeString = fullClassNames ? returnType.getName() : returnType.getSimpleName(); 148 | } 149 | String methodName = method.getName(); 150 | String[] parameterTypeStrings = new String[parameterTypes.length]; 151 | for (int i = 0; i < parameterTypeStrings.length; i++) { 152 | if (parameterTypes[i].isPrimitive()) { 153 | parameterTypeStrings[i] = parameterTypes[i].toString(); 154 | } else { 155 | parameterTypeStrings[i] = fullClassNames ? parameterTypes[i].getName() : parameterTypes[i].getSimpleName(); 156 | } 157 | } 158 | 159 | return new MethodSignature(returnTypeString, methodName, parameterTypeStrings); 160 | } 161 | 162 | public static MethodSignature fromString(String signatureString) { 163 | if (signatureString == null) { return null; } 164 | Matcher matcher = SIGNATURE_STRING_PATTERN.matcher(signatureString); 165 | if (matcher.find()) { 166 | if (matcher.groupCount() != 3) { 167 | throw new IllegalArgumentException("invalid signature"); 168 | } 169 | return new MethodSignature(matcher.group(1), matcher.group(2), matcher.group(3).split(",")); 170 | } else { 171 | throw new IllegalArgumentException("invalid signature"); 172 | } 173 | } 174 | 175 | public String getReturnType() { 176 | return returnType; 177 | } 178 | 179 | public boolean isReturnTypeWildcard() { 180 | return "?".equals(returnType) || "*".equals(returnType); 181 | } 182 | 183 | public String getName() { 184 | return name; 185 | } 186 | 187 | public boolean isNameWildcard() { 188 | return "?".equals(name) || "*".equals(name); 189 | } 190 | 191 | public String[] getParameterTypes() { 192 | return parameterTypes; 193 | } 194 | 195 | public String getParameterType(int index) throws IndexOutOfBoundsException { 196 | return parameterTypes[index]; 197 | } 198 | 199 | public boolean isParameterWildcard(int index) throws IndexOutOfBoundsException { 200 | return "?".equals(getParameterType(index)) || "*".equals(getParameterType(index)); 201 | } 202 | 203 | public String getSignature() { 204 | return signature; 205 | } 206 | 207 | /** 208 | * Checks whether this signature matches another signature. Wildcards are checked in this signature, but not the other signature. 209 | * 210 | * @param other signature to check 211 | * @return whether the signatures match 212 | */ 213 | public boolean matches(MethodSignature other) { 214 | if (other == null) { return false; } 215 | 216 | // if (!returnType.equals(other.returnType)) { 217 | // if (!isReturnTypeWildcard()) { return false; } 218 | // } 219 | // if (!name.equals(other.name)) { 220 | // if (!isNameWildcard()) { return false; } 221 | // } 222 | // if (parameterTypes.length != other.parameterTypes.length) { return false; } 223 | // for (int i = 0; i < parameterTypes.length; i++) { 224 | // if (!getParameterType(i).equals(other.getParameterType(i))) { 225 | // if (!isParameterWildcard(i)) { return false; } 226 | // } 227 | // } 228 | 229 | if (!returnTypePattern.matcher(other.returnType).matches()) { 230 | return false; 231 | } 232 | if (!namePattern.matcher(other.name).matches()) { 233 | return false; 234 | } 235 | if (parameterTypes.length != other.parameterTypes.length) { return false; } 236 | for (int i = 0; i < parameterTypes.length; i++) { 237 | if (!Pattern.compile(getParameterType(i).replace("?", "\\w").replace("*", "\\w*")).matcher(other.getParameterType(i)).matches()) { 238 | return false; 239 | } 240 | } 241 | 242 | return true; 243 | } 244 | 245 | @Override 246 | public boolean equals(Object o) { 247 | if (this == o) { return true; } 248 | if (o == null || getClass() != o.getClass()) { return false; } 249 | 250 | MethodSignature signature1 = (MethodSignature) o; 251 | 252 | if (!returnType.equals(signature1.returnType)) { return false; } 253 | if (!name.equals(signature1.name)) { return false; } 254 | // Probably incorrect - comparing Object[] arrays with Arrays.equals 255 | if (!Arrays.equals(parameterTypes, signature1.parameterTypes)) { return false; } 256 | return signature.equals(signature1.signature); 257 | 258 | } 259 | 260 | @Override 261 | public int hashCode() { 262 | int result = returnType.hashCode(); 263 | result = 31 * result + name.hashCode(); 264 | result = 31 * result + Arrays.hashCode(parameterTypes); 265 | result = 31 * result + signature.hashCode(); 266 | return result; 267 | } 268 | 269 | @Override 270 | public String toString() { 271 | return getSignature(); 272 | } 273 | } 274 | 275 | } 276 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/resolver/FieldResolver.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.resolver; 2 | 3 | import org.inventivetalent.reflection.accessor.FieldAccessor; 4 | import org.inventivetalent.reflection.resolver.wrapper.FieldWrapper; 5 | import org.inventivetalent.reflection.util.AccessUtil; 6 | 7 | import java.lang.reflect.Field; 8 | 9 | /** 10 | * Resolver for fields 11 | */ 12 | public class FieldResolver extends MemberResolver { 13 | 14 | public FieldResolver(Class clazz) { 15 | super(clazz); 16 | } 17 | 18 | public FieldResolver(String className) throws ClassNotFoundException { 19 | super(className); 20 | } 21 | 22 | @Override 23 | public Field resolveIndex(int index) throws IndexOutOfBoundsException, ReflectiveOperationException { 24 | return AccessUtil.setAccessible(this.clazz.getDeclaredFields()[index]); 25 | } 26 | 27 | @Override 28 | public Field resolveIndexSilent(int index) { 29 | try { 30 | return resolveIndex(index); 31 | } catch (IndexOutOfBoundsException | ReflectiveOperationException ignored) { 32 | } 33 | return null; 34 | } 35 | 36 | @Deprecated 37 | @Override 38 | public FieldWrapper resolveIndexWrapper(int index) { 39 | return new FieldWrapper<>(resolveIndexSilent(index)); 40 | } 41 | 42 | @Deprecated 43 | public FieldWrapper resolveWrapper(String... names) { 44 | return new FieldWrapper<>(resolveSilent(names)); 45 | } 46 | 47 | public FieldAccessor resolveIndexAccessor(int index) { 48 | return new FieldAccessor(resolveIndexSilent(index)); 49 | } 50 | 51 | public FieldAccessor resolveAccessor(String... names) { 52 | return new FieldAccessor(resolveSilent(names)); 53 | } 54 | 55 | public Field resolveSilent(String... names) { 56 | try { 57 | return resolve(names); 58 | } catch (Exception e) { 59 | if (AccessUtil.VERBOSE) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | return null; 64 | } 65 | 66 | public Field resolve(String... names) throws NoSuchFieldException { 67 | ResolverQuery.Builder builder = ResolverQuery.builder(); 68 | for (String name : names) 69 | builder.with(name); 70 | try { 71 | return super.resolve(builder.build()); 72 | } catch (ReflectiveOperationException e) { 73 | throw ( NoSuchFieldException ) e; 74 | } 75 | } 76 | 77 | public Field resolveSilent(ResolverQuery... queries) { 78 | try { 79 | return resolve(queries); 80 | } catch (Exception e) { 81 | if (AccessUtil.VERBOSE) { 82 | e.printStackTrace(); 83 | } 84 | } 85 | return null; 86 | } 87 | 88 | public Field resolve(ResolverQuery... queries) throws NoSuchFieldException { 89 | try { 90 | return super.resolve(queries); 91 | } catch (ReflectiveOperationException e) { 92 | throw ( NoSuchFieldException ) e; 93 | } 94 | } 95 | 96 | @Override 97 | protected Field resolveObject(ResolverQuery query) throws ReflectiveOperationException { 98 | return resolveObject(query, this.clazz); 99 | } 100 | 101 | private Field resolveObject(ResolverQuery query, Class clazz) { 102 | Field field = null; 103 | if (query.getTypes() == null || query.getTypes().length == 0) { 104 | try { 105 | field = AccessUtil.setAccessible(clazz.getDeclaredField(query.getName())); 106 | } catch (ReflectiveOperationException e) { 107 | // The field may not exist in current class; continue to check in superclass 108 | } 109 | } else { 110 | for (Field f : clazz.getDeclaredFields()) { 111 | if (f.getName().equals(query.getName())) { 112 | for (Class type : query.getTypes()) { 113 | if (f.getType().equals(type)) { 114 | try { 115 | field = AccessUtil.setAccessible(f); 116 | } catch (ReflectiveOperationException e) { 117 | // The field may not exist in current class; continue to check in superclass 118 | } 119 | } 120 | } 121 | } 122 | } 123 | } 124 | if (field == null) { 125 | // If field not found in current class and its types, then check in superclass 126 | Class superclass = clazz.getSuperclass(); 127 | if (superclass != null) { 128 | return resolveObject(query, superclass); 129 | } 130 | } 131 | return field; 132 | } 133 | 134 | /** 135 | * Attempts to find the first field of the specified type 136 | * 137 | * @param type Type to find 138 | * @return the Field 139 | * @throws ReflectiveOperationException (usually never) 140 | * @see #resolveByLastType(Class) 141 | */ 142 | public Field resolveByFirstType(Class type) throws ReflectiveOperationException { 143 | for (Field field : this.clazz.getDeclaredFields()) { 144 | if (field.getType().equals(type)) { 145 | return AccessUtil.setAccessible(field); 146 | } 147 | } 148 | throw new NoSuchFieldException("Could not resolve field of type '" + type.toString() + "' in class " + this.clazz); 149 | } 150 | 151 | public FieldAccessor resolveByFirstTypeAccessor(Class type) { 152 | return new FieldAccessor(resolveByFirstTypeSilent(type)); 153 | } 154 | 155 | /** 156 | * Attempts to find the first field of the specified type 157 | * 158 | * @param type Type to find 159 | * @return the Field 160 | * @see #resolveByLastTypeSilent(Class) 161 | */ 162 | public Field resolveByFirstTypeSilent(Class type) { 163 | try { 164 | return resolveByFirstType(type); 165 | } catch (Exception e) { 166 | if (AccessUtil.VERBOSE) { 167 | e.printStackTrace(); 168 | } 169 | } 170 | return null; 171 | } 172 | 173 | /** 174 | * Attempts to find the first field which extends/implements the specified type 175 | * 176 | * @param type Type to find 177 | * @return the Field 178 | * @throws ReflectiveOperationException (usually never) 179 | * @see #resolveByLastType(Class) 180 | */ 181 | public Field resolveByFirstExtendingType(Class type) throws ReflectiveOperationException { 182 | for (Field field : this.clazz.getDeclaredFields()) { 183 | if (type.isAssignableFrom(field.getType())) { 184 | return AccessUtil.setAccessible(field); 185 | } 186 | } 187 | throw new NoSuchFieldException("Could not resolve field of type '" + type.toString() + "' in class " + this.clazz); 188 | } 189 | 190 | 191 | /** 192 | * Attempts to find the first field which extends/implements the specified type 193 | * 194 | * @param type Type to find 195 | * @return the Field 196 | * @see #resolveByLastTypeSilent(Class) 197 | */ 198 | public Field resolveByFirstExtendingTypeSilent(Class type) { 199 | try { 200 | return resolveByFirstExtendingType(type); 201 | } catch (Exception e) { 202 | if (AccessUtil.VERBOSE) { 203 | e.printStackTrace(); 204 | } 205 | } 206 | return null; 207 | } 208 | 209 | public FieldAccessor resolveByFirstExtendingTypeAccessor(Class type) { 210 | return new FieldAccessor(resolveByFirstExtendingTypeSilent(type)); 211 | } 212 | 213 | 214 | /** 215 | * Attempts to find the last field of the specified type 216 | * 217 | * @param type Type to find 218 | * @return the Field 219 | * @throws ReflectiveOperationException (usually never) 220 | * @see #resolveByFirstType(Class) 221 | */ 222 | public Field resolveByLastType(Class type) throws ReflectiveOperationException { 223 | Field field = null; 224 | for (Field field1 : this.clazz.getDeclaredFields()) { 225 | if (field1.getType().equals(type)) { 226 | field = field1; 227 | } 228 | } 229 | if (field == null) { 230 | throw new NoSuchFieldException("Could not resolve field of type '" + type.toString() + "' in class " + this.clazz); 231 | } 232 | return AccessUtil.setAccessible(field); 233 | } 234 | 235 | public Field resolveByLastTypeSilent(Class type) { 236 | try { 237 | return resolveByLastType(type); 238 | } catch (Exception e) { 239 | if (AccessUtil.VERBOSE) { 240 | e.printStackTrace(); 241 | } 242 | } 243 | return null; 244 | } 245 | 246 | public FieldAccessor resolveByLastTypeAccessor(Class type) { 247 | return new FieldAccessor(resolveByLastTypeSilent(type)); 248 | } 249 | 250 | /** 251 | * Attempts to find the last field which extends/implements the specified type 252 | * 253 | * @param type Type to find 254 | * @return the Field 255 | * @throws ReflectiveOperationException (usually never) 256 | * @see #resolveByFirstType(Class) 257 | */ 258 | public Field resolveByLastExtendingType(Class type) throws ReflectiveOperationException { 259 | Field field = null; 260 | for (Field field1 : this.clazz.getDeclaredFields()) { 261 | if (type.isAssignableFrom(field1.getType())) { 262 | field = field1; 263 | } 264 | } 265 | if (field == null) { 266 | throw new NoSuchFieldException("Could not resolve field of type '" + type.toString() + "' in class " + this.clazz); 267 | } 268 | return AccessUtil.setAccessible(field); 269 | } 270 | 271 | public Field resolveByLastExtendingTypeSilent(Class type) { 272 | try { 273 | return resolveByLastExtendingType(type); 274 | } catch (Exception e) { 275 | if (AccessUtil.VERBOSE) { 276 | e.printStackTrace(); 277 | } 278 | } 279 | return null; 280 | } 281 | 282 | public FieldAccessor resolveByLastExtendingTypeAccessor(Class type) { 283 | return new FieldAccessor(resolveByLastExtendingTypeSilent(type)); 284 | } 285 | 286 | @Override 287 | protected NoSuchFieldException notFoundException(String joinedNames) { 288 | return new NoSuchFieldException("Could not resolve field for " + joinedNames + " in class " + this.clazz); 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/minecraft/Minecraft.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.minecraft; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.entity.Entity; 5 | import org.inventivetalent.reflection.resolver.ConstructorResolver; 6 | import org.inventivetalent.reflection.resolver.FieldResolver; 7 | import org.inventivetalent.reflection.resolver.MethodResolver; 8 | import org.inventivetalent.reflection.resolver.minecraft.NMSClassResolver; 9 | import org.inventivetalent.reflection.resolver.minecraft.OBCClassResolver; 10 | import org.inventivetalent.reflection.util.AccessUtil; 11 | 12 | import java.lang.reflect.Constructor; 13 | import java.lang.reflect.Field; 14 | import java.lang.reflect.Method; 15 | import java.util.regex.Matcher; 16 | import java.util.regex.Pattern; 17 | 18 | /** 19 | * Helper class to access minecraft/bukkit specific objects 20 | */ 21 | public class Minecraft { 22 | 23 | public static final Pattern NUMERIC_VERSION_PATTERN = Pattern.compile("v([0-9])_([0-9]*)_R([0-9])"); 24 | 25 | /** 26 | * @deprecated use {@link MinecraftVersion#VERSION} instead 27 | */ 28 | @Deprecated 29 | public static final Version VERSION; 30 | public static final MinecraftVersion MINECRAFT_VERSION = MinecraftVersion.VERSION; 31 | 32 | private static NMSClassResolver nmsClassResolver = new NMSClassResolver(); 33 | private static OBCClassResolver obcClassResolver = new OBCClassResolver(); 34 | private static Class NmsEntity; 35 | private static Class CraftEntity; 36 | 37 | static { 38 | try { 39 | MinecraftVersion.getVersion(); 40 | } catch (Exception e) { 41 | } 42 | 43 | Version tempVersion = Version.UNKNOWN; 44 | try { 45 | tempVersion = Version.getVersion(); 46 | } catch (Exception e) { 47 | System.out.println("[ReflectionHelper] Failed to get legacy version"); 48 | } 49 | VERSION = tempVersion; 50 | 51 | try { 52 | Version.runSanityCheck(); 53 | } catch (Exception e) { 54 | throw new RuntimeException("Sanity check which should always succeed just failed! Am I crazy?!", e); 55 | } 56 | 57 | try { 58 | NmsEntity = nmsClassResolver.resolve("Entity", "world.entity.Entity"); 59 | CraftEntity = obcClassResolver.resolve("entity.CraftEntity"); 60 | } catch (ReflectiveOperationException e) { 61 | throw new RuntimeException(e); 62 | } 63 | } 64 | 65 | /** 66 | * @return the current NMS/OBC version (format <version>. 67 | */ 68 | @Deprecated 69 | public static String getVersion() { 70 | return MINECRAFT_VERSION.packageName() + "."; 71 | } 72 | 73 | /** 74 | * @return the current NMS version package 75 | */ 76 | public static String getNMSPackage() { 77 | return MINECRAFT_VERSION.getNmsPackage(); 78 | } 79 | 80 | /** 81 | * @return the current OBC package 82 | */ 83 | public static String getOBCPackage() { 84 | return MINECRAFT_VERSION.getObcPackage(); 85 | } 86 | 87 | public static Object getHandle(Object object) throws ReflectiveOperationException { 88 | Method method; 89 | try { 90 | method = AccessUtil.setAccessible(object.getClass().getDeclaredMethod("getHandle")); 91 | } catch (ReflectiveOperationException e) { 92 | method = AccessUtil.setAccessible(CraftEntity.getDeclaredMethod("getHandle")); 93 | } 94 | return method.invoke(object); 95 | } 96 | 97 | public static Entity getBukkitEntity(Object object) throws ReflectiveOperationException { 98 | Method method; 99 | try { 100 | method = AccessUtil.setAccessible(NmsEntity.getDeclaredMethod("getBukkitEntity")); 101 | } catch (ReflectiveOperationException e) { 102 | method = AccessUtil.setAccessible(CraftEntity.getDeclaredMethod("getHandle")); 103 | } 104 | return (Entity) method.invoke(object); 105 | } 106 | 107 | public static Object getHandleSilent(Object object) { 108 | try { 109 | return getHandle(object); 110 | } catch (Exception e) { 111 | } 112 | return null; 113 | } 114 | 115 | public enum Version { 116 | UNKNOWN(-1) { 117 | @Override 118 | public boolean matchesPackageName(String packageName) { 119 | return false; 120 | } 121 | }, 122 | 123 | v1_7_R1(10701), 124 | v1_7_R2(10702), 125 | v1_7_R3(10703), 126 | v1_7_R4(10704), 127 | 128 | v1_8_R1(10801), 129 | v1_8_R2(10802), 130 | v1_8_R3(10803), 131 | //Does this even exists? 132 | v1_8_R4(10804), 133 | 134 | v1_9_R1(10901), 135 | v1_9_R2(10902), 136 | 137 | v1_10_R1(11001), 138 | 139 | v1_11_R1(11101), 140 | 141 | v1_12_R1(11201), 142 | 143 | v1_13_R1(11301), 144 | v1_13_R2(11302), 145 | 146 | v1_14_R1(11401), 147 | 148 | v1_15_R1(11501), 149 | 150 | v1_16_R1(11601), 151 | v1_16_R2(11602), 152 | v1_16_R3(11603), 153 | 154 | v1_17_R1(11701), 155 | 156 | v1_18_R1(11801), 157 | v1_18_R2(11802), 158 | 159 | v1_19_R1(11901), 160 | v1_19_R2(11902), 161 | v1_19_R3(11904), 162 | 163 | /// (Potentially) Upcoming versions 164 | v1_20_R1(12001), 165 | 166 | v1_21_R1(12101), 167 | ; 168 | 169 | private final MinecraftVersion version; 170 | 171 | Version(int version, String nmsFormat, String obcFormat, boolean nmsVersionPrefix) { 172 | this.version = new MinecraftVersion(name(), version, nmsFormat, obcFormat, nmsVersionPrefix); 173 | } 174 | 175 | Version(int version) { 176 | if (version >= 11701) { // 1.17+ new class package name format 177 | this.version = new MinecraftVersion(name(), version, "net.minecraft", "org.bukkit.craftbukkit.%s", false); 178 | } else { 179 | this.version = new MinecraftVersion(name(), version); 180 | } 181 | } 182 | 183 | /** 184 | * @return the version-number 185 | */ 186 | public int version() { 187 | return version.version(); 188 | } 189 | 190 | /** 191 | * @param version the version to check 192 | * @return true if this version is older than the specified version 193 | */ 194 | @Deprecated 195 | public boolean olderThan(Version version) { 196 | return version() < version.version(); 197 | } 198 | 199 | /** 200 | * @param version the version to check 201 | * @return true if this version is newer than the specified version 202 | */ 203 | @Deprecated 204 | public boolean newerThan(Version version) { 205 | return version() >= version.version(); 206 | } 207 | 208 | /** 209 | * @param oldVersion The older version to check 210 | * @param newVersion The newer version to check 211 | * @return true if this version is newer than the oldVersion and older that the newVersion 212 | */ 213 | @Deprecated 214 | public boolean inRange(Version oldVersion, Version newVersion) { 215 | return newerThan(oldVersion) && olderThan(newVersion); 216 | } 217 | 218 | public boolean matchesPackageName(String packageName) { 219 | return packageName.toLowerCase().contains(name().toLowerCase()); 220 | } 221 | 222 | /** 223 | * @return the minecraft version 224 | */ 225 | public MinecraftVersion minecraft() { 226 | return version; 227 | } 228 | 229 | static void runSanityCheck() { 230 | assert v1_14_R1.newerThan(v1_13_R2); 231 | assert v1_13_R2.olderThan(v1_14_R1); 232 | 233 | assert v1_13_R2.newerThan(v1_8_R1); 234 | 235 | assert v1_13_R2.newerThan(v1_8_R1) && v1_13_R2.olderThan(v1_14_R1); 236 | } 237 | 238 | @Deprecated 239 | public static Version getVersion() { 240 | String name = Bukkit.getServer().getClass().getPackage().getName(); 241 | String versionPackage = name.substring(name.lastIndexOf('.') + 1); 242 | for (Version version : values()) { 243 | if (version.matchesPackageName(versionPackage)) {return version;} 244 | } 245 | System.err.println("[ReflectionHelper] Failed to find version enum for '" + name + "'/'" + versionPackage + "'"); 246 | 247 | System.out.println("[ReflectionHelper] Generating dynamic constant..."); 248 | Matcher matcher = NUMERIC_VERSION_PATTERN.matcher(versionPackage); 249 | while (matcher.find()) { 250 | if (matcher.groupCount() < 3) {continue;} 251 | 252 | String majorString = matcher.group(1); 253 | String minorString = matcher.group(2); 254 | if (minorString.length() == 1) {minorString = "0" + minorString;} 255 | String patchString = matcher.group(3); 256 | if (patchString.length() == 1) {patchString = "0" + patchString;} 257 | 258 | String numVersionString = majorString + minorString + patchString; 259 | int numVersion = Integer.parseInt(numVersionString); 260 | String packge = versionPackage; 261 | 262 | try { 263 | // Add enum value 264 | Field valuesField = new FieldResolver(Version.class).resolve("$VALUES"); 265 | Version[] oldValues = (Version[]) valuesField.get(null); 266 | Version[] newValues = new Version[oldValues.length + 1]; 267 | System.arraycopy(oldValues, 0, newValues, 0, oldValues.length); 268 | Version dynamicVersion = (Version) newEnumInstance(Version.class, new Class[]{ 269 | String.class, 270 | int.class, 271 | int.class 272 | }, new Object[]{ 273 | packge, 274 | newValues.length - 1, 275 | numVersion 276 | }); 277 | newValues[newValues.length - 1] = dynamicVersion; 278 | valuesField.set(null, newValues); 279 | 280 | System.out.println("[ReflectionHelper] Injected dynamic version " + packge + " (#" + numVersion + ")."); 281 | System.out.println("[ReflectionHelper] Please inform inventivetalent about the outdated version, as this is not guaranteed to work."); 282 | return dynamicVersion; 283 | } catch (ReflectiveOperationException e) { 284 | System.out.println("[ReflectionHelper] Failed to create deprecated dynamic version"); 285 | System.out.println("[ReflectionHelper] If the above (ReflectionHelper/MinecraftVersion) succeeded, you can ignore this."); 286 | e.printStackTrace(); 287 | } 288 | } 289 | 290 | return UNKNOWN; 291 | } 292 | 293 | @Override 294 | public String toString() { 295 | return name() + " (" + version() + ")"; 296 | } 297 | } 298 | 299 | @Deprecated 300 | public static Object newEnumInstance(Class clazz, Class[] types, Object[] values) throws ReflectiveOperationException { 301 | Constructor constructor = new ConstructorResolver(clazz).resolve(types); 302 | Field accessorField = new FieldResolver(Constructor.class).resolve("constructorAccessor"); 303 | Object constructorAccessor = accessorField.get(constructor); 304 | if (constructorAccessor == null) { 305 | new MethodResolver(Constructor.class).resolve("acquireConstructorAccessor").invoke(constructor); 306 | constructorAccessor = accessorField.get(constructor); 307 | } 308 | return new MethodResolver(constructorAccessor.getClass()).resolve("newInstance").invoke(constructorAccessor, (Object) values); 309 | } 310 | 311 | } 312 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/reflection/minecraft/DataWatcher.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.reflection.minecraft; 2 | 3 | import org.inventivetalent.reflection.resolver.ConstructorResolver; 4 | import org.inventivetalent.reflection.resolver.FieldResolver; 5 | import org.inventivetalent.reflection.resolver.MethodResolver; 6 | import org.inventivetalent.reflection.resolver.ResolverQuery; 7 | import org.inventivetalent.reflection.resolver.minecraft.NMSClassResolver; 8 | 9 | import java.lang.reflect.Field; 10 | import java.lang.reflect.ParameterizedType; 11 | import java.lang.reflect.Type; 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | public class DataWatcher { 18 | 19 | static NMSClassResolver nmsClassResolver = new NMSClassResolver(); 20 | 21 | static Class ItemStack = nmsClassResolver.resolveSilent("ItemStack", "world.item.ItemStack"); 22 | static Class ChunkCoordinates = nmsClassResolver.resolveSilent("ChunkCoordinates", "world.level.ChunkCoordinates"); 23 | static Class BlockPosition = nmsClassResolver.resolveSilent("BlockPosition", "core.BlockPosition"); 24 | static Class Vector3f = nmsClassResolver.resolveSilent("Vector3f", "core.Vector3f"); 25 | static Class DataWatcher = nmsClassResolver.resolveSilent("DataWatcher", "network.syncher.DataWatcher"); 26 | static Class Entity = nmsClassResolver.resolveSilent("Entity", "world.entity.Entity"); 27 | 28 | static ConstructorResolver DataWacherConstructorResolver = new ConstructorResolver(DataWatcher); 29 | 30 | static FieldResolver DataWatcherFieldResolver = new FieldResolver(DataWatcher); 31 | 32 | static MethodResolver DataWatcherMethodResolver = new MethodResolver(DataWatcher); 33 | 34 | public static Object newDataWatcher(Object entity) throws ReflectiveOperationException { 35 | return DataWacherConstructorResolver.resolve(new Class[]{Entity}).newInstance(entity); 36 | } 37 | 38 | public static Object setValue(Object dataWatcher, int index, Object dataWatcherObject/*1.9*/, Object value) throws ReflectiveOperationException { 39 | if (Minecraft.VERSION.olderThan(Minecraft.Version.v1_9_R1)) { 40 | return V1_8.setValue(dataWatcher, index, value); 41 | } else { 42 | return V1_9.setValue(dataWatcher, dataWatcherObject, value); 43 | } 44 | } 45 | 46 | public static Object setValue(Object dataWatcher, int index, V1_9.ValueType type, Object value) throws ReflectiveOperationException { 47 | return setValue(dataWatcher, index, type.getType(), value); 48 | } 49 | 50 | public static Object setValue(Object dataWatcher, int index, Object value, FieldResolver dataWatcherObjectFieldResolver/*1.9*/, String... dataWatcherObjectFieldNames/*1.9*/) throws ReflectiveOperationException { 51 | if (Minecraft.VERSION.olderThan(Minecraft.Version.v1_9_R1)) { 52 | return V1_8.setValue(dataWatcher, index, value); 53 | } else { 54 | Object dataWatcherObject = dataWatcherObjectFieldResolver.resolve(dataWatcherObjectFieldNames).get(null/*Should be a static field*/); 55 | return V1_9.setValue(dataWatcher, dataWatcherObject, value); 56 | } 57 | } 58 | 59 | @Deprecated 60 | public static Object getValue(DataWatcher dataWatcher, int index) throws ReflectiveOperationException { 61 | if (Minecraft.VERSION.olderThan(Minecraft.Version.v1_9_R1)) { 62 | return V1_8.getValue(dataWatcher, index); 63 | } else { 64 | return V1_9.getValue(dataWatcher, index); 65 | } 66 | } 67 | 68 | public static Object getValue(Object dataWatcher, int index, V1_9.ValueType type) throws ReflectiveOperationException { 69 | return getValue(dataWatcher, index, type.getType()); 70 | } 71 | 72 | public static Object getValue(Object dataWatcher, int index, Object dataWatcherObject/*1.9*/) throws ReflectiveOperationException { 73 | if (Minecraft.VERSION.olderThan(Minecraft.Version.v1_9_R1)) { 74 | return V1_8.getWatchableObjectValue(V1_8.getValue(dataWatcher, index)); 75 | } else { 76 | return V1_9.getValue(dataWatcher, dataWatcherObject); 77 | } 78 | } 79 | 80 | //TODO: update type-ids to 1.9 81 | public static int getValueType(Object value) { 82 | int type = 0; 83 | if (value instanceof Number) { 84 | if (value instanceof Byte) { 85 | type = 0; 86 | } else if (value instanceof Short) { 87 | type = 1; 88 | } else if (value instanceof Integer) { 89 | type = 2; 90 | } else if (value instanceof Float) { 91 | type = 3; 92 | } 93 | } else if (value instanceof String) { 94 | type = 4; 95 | } else if (value != null && value.getClass().equals(ItemStack)) { 96 | type = 5; 97 | } else if (value != null && (value.getClass().equals(ChunkCoordinates) || value.getClass().equals(BlockPosition))) { 98 | type = 6; 99 | } else if (value != null && value.getClass().equals(Vector3f)) { 100 | type = 7; 101 | } 102 | 103 | return type; 104 | } 105 | 106 | /** 107 | * Helper class for versions newer than 1.9 108 | */ 109 | public static class V1_9 { 110 | 111 | static Class DataWatcherItem = nmsClassResolver.resolveSilent("DataWatcher$Item", "network.syncher.DataWatcher$Item");//>= 1.9 only 112 | static Class DataWatcherObject = nmsClassResolver.resolveSilent("DataWatcherObject", "network.syncher.DataWatcherObject");//>= 1.9 only 113 | 114 | static ConstructorResolver DataWatcherItemConstructorResolver;//>=1.9 only 115 | 116 | static FieldResolver DataWatcherItemFieldResolver;//>=1.9 only 117 | static FieldResolver DataWatcherObjectFieldResolver;//>=1.9 only 118 | 119 | public static Object newDataWatcherItem(Object dataWatcherObject, Object value) throws ReflectiveOperationException { 120 | if (DataWatcherItemConstructorResolver == null) { 121 | DataWatcherItemConstructorResolver = new ConstructorResolver(DataWatcherItem); 122 | } 123 | return DataWatcherItemConstructorResolver.resolveFirstConstructor().newInstance(dataWatcherObject, value); 124 | } 125 | 126 | public static Object setItem(Object dataWatcher, int index, Object dataWatcherObject, Object value) throws ReflectiveOperationException { 127 | return setItem(dataWatcher, index, newDataWatcherItem(dataWatcherObject, value)); 128 | } 129 | 130 | public static Object setItem(Object dataWatcher, int index, Object dataWatcherItem) throws ReflectiveOperationException { 131 | Map map = (Map) DataWatcherFieldResolver.resolveByLastTypeSilent(Map.class).get(dataWatcher); 132 | map.put(index, dataWatcherItem); 133 | return dataWatcher; 134 | } 135 | 136 | public static Object setValue(Object dataWatcher, Object dataWatcherObject, Object value) throws ReflectiveOperationException { 137 | DataWatcherMethodResolver.resolve("set").invoke(dataWatcher, dataWatcherObject, value); 138 | return dataWatcher; 139 | } 140 | 141 | // public static Object getValue(Object dataWatcher, int index) throws ReflectiveOperationException { 142 | // Map map = (Map) DataWatcherFieldResolver.resolve("c").get(dataWatcher); 143 | // return map.get(index); 144 | // } 145 | 146 | public static Object getItem(Object dataWatcher, Object dataWatcherObject) throws ReflectiveOperationException { 147 | return DataWatcherMethodResolver.resolve(new ResolverQuery("c", DataWatcherObject)).invoke(dataWatcher, dataWatcherObject); 148 | } 149 | 150 | public static Object getValue(Object dataWatcher, Object dataWatcherObject) throws ReflectiveOperationException { 151 | return DataWatcherMethodResolver.resolve("get").invoke(dataWatcher, dataWatcherObject); 152 | } 153 | 154 | public static Object getValue(Object dataWatcher, ValueType type) throws ReflectiveOperationException { 155 | return getValue(dataWatcher, type.getType()); 156 | } 157 | 158 | public static Object getItemObject(Object item) throws ReflectiveOperationException { 159 | if (DataWatcherItemFieldResolver == null) { DataWatcherItemFieldResolver = new FieldResolver(DataWatcherItem); } 160 | return DataWatcherItemFieldResolver.resolve("a").get(item); 161 | } 162 | 163 | public static int getItemIndex(Object dataWatcher, Object item) throws ReflectiveOperationException { 164 | int index = -1;//Return -1 if the item is not in the DataWatcher 165 | Map map = (Map) DataWatcherFieldResolver.resolveByLastTypeSilent(Map.class).get(dataWatcher); 166 | for (Map.Entry entry : map.entrySet()) { 167 | if (entry.getValue().equals(item)) { 168 | index = entry.getKey(); 169 | break; 170 | } 171 | } 172 | return index; 173 | } 174 | 175 | public static Type getItemType(Object item) throws ReflectiveOperationException { 176 | if (DataWatcherObjectFieldResolver == null) { DataWatcherObjectFieldResolver = new FieldResolver(DataWatcherObject); } 177 | Object object = getItemObject(item); 178 | Object serializer = DataWatcherObjectFieldResolver.resolve("b").get(object); 179 | Type[] genericInterfaces = serializer.getClass().getGenericInterfaces(); 180 | if (genericInterfaces.length > 0) { 181 | Type type = genericInterfaces[0]; 182 | if (type instanceof ParameterizedType) { 183 | Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments(); 184 | if (actualTypes.length > 0) { 185 | return actualTypes[0]; 186 | } 187 | } 188 | } 189 | return null; 190 | } 191 | 192 | public static Object getItemValue(Object item) throws ReflectiveOperationException { 193 | if (DataWatcherItemFieldResolver == null) { DataWatcherItemFieldResolver = new FieldResolver(DataWatcherItem); } 194 | return DataWatcherItemFieldResolver.resolve("b").get(item); 195 | } 196 | 197 | public static void setItemValue(Object item, Object value) throws ReflectiveOperationException { 198 | DataWatcherItemFieldResolver.resolve("b").set(item, value); 199 | } 200 | 201 | public enum ValueType { 202 | 203 | /** 204 | * Byte 205 | */ 206 | @Deprecated 207 | ENTITY_FLAG("world.entity.Entity", 57, 0 /*"ax", "ay"*/), 208 | ENTITY_SHARED_FLAGS("world.entity.Entity", 57, 0), 209 | /** 210 | * Integer 211 | */ 212 | ENTITY_AIR_TICKS("world.entity.Entity", 58, 1), 213 | /** 214 | * String 215 | */ 216 | ENTITY_NAME("world.entity.Entity", 59, 2/*"az", "aA"*/), 217 | /** 218 | * Byte < 1.9 Boolean > 1.9 219 | */ 220 | ENTITY_NAME_VISIBLE("world.entity.Entity", 60, 3/*"aA", "aB"*/), 221 | /** 222 | * Boolean 223 | */ 224 | ENTITY_SILENT("world.entity.Entity", 61, 4/*"aB", "aC"*/), 225 | 226 | ENTITY_NO_GRAVITY("world.entity.Entity", 0, 5), 227 | 228 | ENTITY_POSE("world.entity.Entity", 0, 6), 229 | 230 | ENTITY_TICKS_FROZEN("world.entity.Entity", 0, 7), 231 | 232 | ////////// 233 | 234 | ENTITY_LIVING_FLAGS("world.entity.EntityLiving", 0, 0), 235 | 236 | /** 237 | * Float 238 | */ 239 | ENTITY_LIVING_HEALTH("world.entity.EntityLiving", 0, 1), 240 | 241 | @Deprecated 242 | ENTITY_LIVING_f("world.entity.EntityLiving", 4, 2/*"f"*/), 243 | ENTITY_LIVING_COLOR("world.entity.EntityLiving", 4, 2), 244 | 245 | @Deprecated 246 | ENTITY_LIVING_g("world.entity.EntityLiving", 5, 3/*"g"*/), 247 | ENTITY_LIVING_AMBIENCE("world.entity.EntityLiving", 5, 3), 248 | 249 | @Deprecated 250 | ENTITY_LIVING_h("world.entity.EntityLiving", 6, 4/*"h"*/), 251 | ENTITY_LIVING_ARROW_COUNT("world.entity.EntityLiving", 6, 4), 252 | 253 | ENTITY_LIVING_STINGER_COUNT("world.entity.EntityLiving", 6, 5), 254 | 255 | ////////// 256 | 257 | /** 258 | * Byte 259 | */ 260 | ENTITY_INSENTIENT_FLAG("world.entity.EntityInsentient", 0, 0/* "a"*/), 261 | 262 | /////////// 263 | 264 | /** 265 | * Integer 266 | */ 267 | ENTITY_SLIME_SIZE("world.entity.monster.EntitySlime", 0, 0/* "bt", "bu"*/), 268 | 269 | ///////////// 270 | 271 | @Deprecated 272 | ENTITY_WITHER_a("world.entity.boss.wither.EntityWither", 0, 0/*"a"*/), 273 | ENTITY_WITHER_TARGET_A("world.entity.boss.wither.EntityWither", 0, 0), 274 | 275 | @Deprecated 276 | ENTITY_WIHER_b("world.entity.boss.wither.EntityWither", 1, 1/*"b"*/), 277 | ENTITY_WITHER_TARGET_B("world.entity.boss.wither.EntityWither", 1, 1), 278 | 279 | @Deprecated 280 | ENTITY_WITHER_c("world.entity.boss.wither.EntityWither", 2, 2/*"c"*/), 281 | ENTITY_WITHER_TARGET_C("world.entity.boss.wither.EntityWither", 2, 2), 282 | 283 | @Deprecated 284 | ENTITY_WITHER_bw("world.entity.boss.wither.EntityWither", 3, 3/*"bw", "bx"*/), 285 | ENTITY_WITHER_ID("world.entity.boss.wither.EntityWither", 3, 3), 286 | 287 | ////////// 288 | 289 | ENTITY_AGEABLE_CHILD("world.entity.EntityAgeable", 0, 0), 290 | 291 | ////////// 292 | 293 | ENTITY_HORSE_CHESTED_ID("world.entity.animal.horse.EntityHorseChestedAbstract", 0, 0), 294 | 295 | /////////// 296 | 297 | ENTITY_HORSE_ABSTRACT_FLAGS("world.entity.animal.horse.EntityHorseAbstract", 0, 0), 298 | ENTITY_HORSE_ABSTRACT_OWNER_UUID("world.entity.animal.horse.EntityHorseAbstract", 0, 1), 299 | 300 | ENTITY_HORSE_TYPE_VARIANT("world.entity.animal.horse.EntityHorse", 0, 0), 301 | 302 | ///////// 303 | 304 | 305 | /** 306 | * Float 307 | */ 308 | ENTITY_HUMAN_ABSORPTION_HEARTS("world.entity.player.EntityHuman", 0, 0 /*"a"*/), 309 | 310 | /** 311 | * Integer 312 | */ 313 | ENTITY_HUMAN_SCORE("world.entity.player.EntityHuman", 1, 1 /*"b"*/), 314 | 315 | /** 316 | * Byte 317 | */ 318 | ENTITY_HUMAN_SKIN_LAYERS("world.entity.player.EntityHuman", 2, 2 /*"bp", "bq"*/), 319 | 320 | /** 321 | * Byte (0 = left, 1 = right) 322 | */ 323 | ENTITY_HUMAN_MAIN_HAND("world.entity.player.EntityHuman", 3, 3/*"bq", "br"*/), 324 | 325 | ENTITY_HUMAN_SHOULDER_LEFT("world.entity.player.EntityHuman", 0, 4), 326 | ENTITY_HUMAN_SHOULDER_RIGHT("world.entity.player.EntityHuman", 0, 5), 327 | ; 328 | 329 | private Object type; 330 | 331 | ValueType(String className, String... fieldNames) { 332 | try { 333 | this.type = new FieldResolver(nmsClassResolver.resolve(className)).resolve(fieldNames).get(null); 334 | } catch (Exception e) { 335 | System.err.println("[ReflectionHelper] Failed to find DataWatcherObject for " + className + " " + Arrays.toString(fieldNames)); 336 | } 337 | } 338 | 339 | ValueType(String className, int index) { 340 | try { 341 | this.type = new FieldResolver(nmsClassResolver.resolve(className)).resolveIndex(index).get(null); 342 | } catch (Exception e) { 343 | System.err.println("[ReflectionHelper] Failed to find DataWatcherObject for " + className + " #" + index); 344 | } 345 | } 346 | 347 | ValueType(String className, int ignored, int offset) { 348 | List dataWatcherFields = new ArrayList<>(); 349 | try { 350 | Class clazz = nmsClassResolver.resolve(className); 351 | for (Field field : clazz.getDeclaredFields()) { 352 | if ("DataWatcherObject".equals(field.getType().getSimpleName())) { 353 | dataWatcherFields.add(field.getName()); 354 | } 355 | if (dataWatcherFields.size() > offset + 1) break; 356 | } 357 | this.type = new FieldResolver(clazz).resolveAccessor(dataWatcherFields.get(offset)).get(null); 358 | } catch (Exception e) { 359 | System.err.println("[ReflectionHelper] Failed to find DataWatcherObject for " + className + " #" + ignored + " (offset: " + offset + ", fields: " + dataWatcherFields + ")"); 360 | } 361 | } 362 | 363 | public boolean hasType() { 364 | return getType() != null; 365 | } 366 | 367 | public Object getType() { 368 | return type; 369 | } 370 | } 371 | 372 | } 373 | 374 | /** 375 | * Helper class for versions older than 1.8 376 | */ 377 | public static class V1_8 { 378 | 379 | static Class WatchableObject = nmsClassResolver.resolveSilent("WatchableObject", "DataWatcher$WatchableObject");//<=1.8 only 380 | 381 | static ConstructorResolver WatchableObjectConstructorResolver;//<=1.8 only 382 | 383 | static FieldResolver WatchableObjectFieldResolver;//<=1.8 only 384 | 385 | public static Object newWatchableObject(int index, Object value) throws ReflectiveOperationException { 386 | return newWatchableObject(getValueType(value), index, value); 387 | } 388 | 389 | public static Object newWatchableObject(int type, int index, Object value) throws ReflectiveOperationException { 390 | if (WatchableObjectConstructorResolver == null) { 391 | WatchableObjectConstructorResolver = new ConstructorResolver(WatchableObject); 392 | } 393 | return WatchableObjectConstructorResolver.resolve(new Class[]{ 394 | int.class, 395 | int.class, 396 | Object.class}).newInstance(type, index, value); 397 | } 398 | 399 | public static Object setValue(Object dataWatcher, int index, Object value) throws ReflectiveOperationException { 400 | int type = getValueType(value); 401 | 402 | Map map = (Map) DataWatcherFieldResolver.resolveByLastType(Map.class).get(dataWatcher); 403 | map.put(index, newWatchableObject(type, index, value)); 404 | 405 | return dataWatcher; 406 | } 407 | 408 | public static Object getValue(Object dataWatcher, int index) throws ReflectiveOperationException { 409 | Map map = (Map) DataWatcherFieldResolver.resolveByLastType(Map.class).get(dataWatcher); 410 | 411 | return map.get(index); 412 | } 413 | 414 | public static int getWatchableObjectIndex(Object object) throws ReflectiveOperationException { 415 | if (WatchableObjectFieldResolver == null) { WatchableObjectFieldResolver = new FieldResolver(WatchableObject); } 416 | return WatchableObjectFieldResolver.resolve("b").getInt(object); 417 | } 418 | 419 | public static int getWatchableObjectType(Object object) throws ReflectiveOperationException { 420 | if (WatchableObjectFieldResolver == null) { WatchableObjectFieldResolver = new FieldResolver(WatchableObject); } 421 | return WatchableObjectFieldResolver.resolve("a").getInt(object); 422 | } 423 | 424 | public static Object getWatchableObjectValue(Object object) throws ReflectiveOperationException { 425 | if (WatchableObjectFieldResolver == null) { WatchableObjectFieldResolver = new FieldResolver(WatchableObject); } 426 | return WatchableObjectFieldResolver.resolve("c").get(object); 427 | } 428 | 429 | } 430 | 431 | private DataWatcher() { 432 | } 433 | 434 | } 435 | --------------------------------------------------------------------------------