001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.core.util;
020
021import java.lang.reflect.Constructor;
022import java.lang.reflect.Field;
023import java.lang.reflect.Method;
024
025/**
026 * @author DPOH-VAR
027 * @version 1.0
028 */
029public class ReflectionUtils {
030
031    /**
032     * prefix of bukkit classes
033     */
034    private static String preClassB = "org.bukkit.craftbukkit";
035    /**
036     * prefix of minecraft classes
037     */
038    private static String preClassM = "net.minecraft.server";
039
040    public ReflectionUtils(String version) {
041        if (version != null) {
042            preClassB += '.' + version;
043            preClassM += '.' + version;
044        }
045    }
046
047    public static Class<?> getClass(String name) {
048        try {
049            return Class.forName(name);
050        } catch (ClassNotFoundException ignored) {
051            return null;
052        }
053    }
054
055    public static <T> Class<? extends T> getClass(String name, Class<T> superClass) {
056        try {
057            return Class.forName(name).asSubclass(superClass);
058        } catch (ClassCastException | ClassNotFoundException ignored) {
059            return null;
060        }
061    }
062
063    /**
064     * Get class for name. Replace {nms} to net.minecraft.server.V*. Replace {cb} to org.bukkit.craftbukkit.V*. Replace
065     * {nm} to net.minecraft
066     *
067     * @param className possible class paths
068     * @return RefClass object
069     * @throws ClassNotFoundException if no class found
070     */
071    public static RefClass getRefClass(String className) throws ClassNotFoundException {
072        className = className.replace("{cb}", preClassB).replace("{nms}", preClassM)
073                .replace("{nm}", "net.minecraft");
074        return getRefClass(Class.forName(className));
075    }
076
077    /**
078     * get RefClass object by real class
079     *
080     * @param clazz class
081     * @return RefClass based on passed class
082     */
083    public static RefClass getRefClass(Class<?> clazz) {
084        return new RefClass(clazz);
085    }
086
087    /**
088     * RefClass - utility to simplify work with reflections.
089     */
090    public static class RefClass {
091
092        private final Class<?> clazz;
093
094        private RefClass(Class<?> clazz) {
095            this.clazz = clazz;
096        }
097
098        /**
099         * get passed class
100         *
101         * @return class
102         */
103        public Class<?> getRealClass() {
104            return this.clazz;
105        }
106
107        /**
108         * get existing method by name and types
109         *
110         * @param name  name
111         * @param types method parameters. can be Class or RefClass
112         * @return RefMethod object
113         * @throws NoSuchMethodException if method not found
114         */
115        public RefMethod getMethod(String name, Object... types) throws NoSuchMethodException {
116            Class<?>[] classes = new Class[types.length];
117            int i = 0;
118            for (Object e : types) {
119                if (e instanceof Class) {
120                    classes[i++] = (Class<?>) e;
121                } else if (e instanceof RefClass) {
122                    classes[i++] = ((RefClass) e).getRealClass();
123                } else {
124                    classes[i++] = e.getClass();
125                }
126            }
127            try {
128                return new RefMethod(this.clazz.getMethod(name, classes));
129            } catch (NoSuchMethodException ignored) {
130                return new RefMethod(this.clazz.getDeclaredMethod(name, classes));
131            }
132        }
133
134        /**
135         * get field by name
136         *
137         * @param name field name
138         * @return RefField
139         * @throws NoSuchFieldException if field not found
140         */
141        public RefField getField(String name) throws NoSuchFieldException {
142            try {
143                return new RefField(this.clazz.getField(name));
144            } catch (NoSuchFieldException ignored) {
145                return new RefField(this.clazz.getDeclaredField(name));
146            }
147        }
148
149    }
150
151
152    /**
153     * Method wrapper
154     */
155    public static class RefMethod {
156
157        private final Method method;
158
159        private RefMethod(Method method) {
160            this.method = method;
161            method.setAccessible(true);
162        }
163
164        /**
165         * @return passed method
166         */
167        public Method getRealMethod() {
168            return this.method;
169        }
170
171        /**
172         * apply method to object
173         *
174         * @param e object to which the method is applied
175         * @return RefExecutor with method call(...)
176         */
177        public RefExecutor of(Object e) {
178            return new RefExecutor(e);
179        }
180
181
182        public class RefExecutor {
183
184            final Object e;
185
186            public RefExecutor(Object e) {
187                this.e = e;
188            }
189
190            /**
191             * apply method for selected object
192             *
193             * @param params sent parameters
194             * @return return value
195             * @throws RuntimeException if something went wrong
196             */
197            public Object call(Object... params) {
198                try {
199                    return RefMethod.this.method.invoke(this.e, params);
200                } catch (Exception e) {
201                    throw new RuntimeException(e);
202                }
203            }
204
205        }
206
207    }
208
209
210    /**
211     * Constructor wrapper
212     */
213    public static class RefConstructor {
214
215        private final Constructor<?> constructor;
216
217        private RefConstructor(Constructor<?> constructor) {
218            this.constructor = constructor;
219            constructor.setAccessible(true);
220        }
221
222        /**
223         * create new instance with constructor
224         *
225         * @param params parameters for constructor
226         * @return new object
227         * @throws ReflectiveOperationException reflective operation exception
228         * @throws IllegalArgumentException     illegal argument exception
229         */
230        public Object create(Object... params)
231                throws ReflectiveOperationException, IllegalArgumentException {
232            return this.constructor.newInstance(params);
233        }
234
235    }
236
237
238    public static class RefField {
239
240        private final Field field;
241
242        private RefField(Field field) {
243            this.field = field;
244            field.setAccessible(true);
245        }
246
247        /**
248         * @return passed field
249         */
250        public Field getRealField() {
251            return this.field;
252        }
253
254
255        /**
256         * apply fiend for object
257         *
258         * @param e applied object
259         * @return RefExecutor with getter and setter
260         */
261        public RefExecutor of(Object e) {
262            return new RefExecutor(e);
263        }
264
265        public class RefExecutor {
266
267            final Object e;
268
269            public RefExecutor(Object e) {
270                this.e = e;
271            }
272
273            /**
274             * set field value for applied object
275             *
276             * @param param value
277             */
278            public void set(Object param) {
279                try {
280                    RefField.this.field.set(this.e, param);
281                } catch (Exception e) {
282                    throw new RuntimeException(e);
283                }
284            }
285
286            /**
287             * get field value for applied object
288             *
289             * @return value of field
290             */
291            public Object get() {
292                try {
293                    return RefField.this.field.get(this.e);
294                } catch (Exception e) {
295                    throw new RuntimeException(e);
296                }
297            }
298
299        }
300
301    }
302
303}