/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.managed.reflection; import com.google.common.collect.ImmutableMap; import com.jcwhatever.nucleus.Nucleus; import com.jcwhatever.nucleus.utils.PreCon; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Map; import javax.annotation.Nullable; /** * Reflection utilities. */ public final class Reflection { private Reflection() {} /** * Maps primitive types to wrapper types. */ public static final Map<Class<?>, Class<?>> PRIMITIVES_TO_WRAPPERS = new ImmutableMap.Builder<Class<?>, Class<?>>() .put(boolean.class, Boolean.class) .put(byte.class, Byte.class) .put(char.class, Character.class) .put(short.class, Short.class) .put(int.class, Integer.class) .put(long.class, Long.class) .put(float.class, Float.class) .put(double.class, Double.class) .put(void.class, Void.class) .build(); /** * Maps primitive wrapper types to primitives. */ public static final Map<Class<?>, Class<?>> WRAPPERS_TO_PRIMITIVES = new ImmutableMap.Builder<Class<?>, Class<?>>() .put(Boolean.class, boolean.class) .put(Byte.class, byte.class) .put(Character.class, char.class) .put(Short.class, short.class) .put(Integer.class, int.class) .put(Long.class, long.class) .put(Float.class, float.class) .put(Double.class, double.class) .put(Void.class, void.class) .build(); /** * Maps primitive names to primitive type. */ public static final Map<String, Class<?>> NAMES_TO_PRIMITIVES = new ImmutableMap.Builder<String, Class<?>>() .put("boolean", boolean.class) .put("byte", byte.class) .put("char", char.class) .put("short", short.class) .put("int", int.class) .put("long", long.class) .put("float", float.class) .put("double", double.class) .put("void", void.class) .build(); /** * Create a new {@link IReflection} instance for the current * NMS version. */ public static IReflection newContext() { return Nucleus.getReflectionManager().newContext(); } /** * Determine if an object is an array. * * @param instance The object to check. */ public static int getArrayDimensions(Object instance) { PreCon.notNull(instance); String className = instance.getClass().getName(); for (int i=0; i < className.length(); i++) { char ch = className.charAt(i); if (ch != '[') return i; } return 0; } /** * Get the component type of an array instance. If the instance * is not an array then the instance type is returned. * * @param instance The instance of a 1 or more dimensional array. * * @return The component type of the array. */ public static Class<?> getArrayComponentType(Object instance) { if (!instance.getClass().isArray()) return instance.getClass(); Class<?> componentType = instance.getClass().getComponentType(); while (componentType.isArray()) { componentType = componentType.getComponentType(); } return componentType; } /** * Remove the final modifier from a field. * * @param field The field. * * @return True if successful, otherwise false. */ public static boolean removeFinal(Field field) { PreCon.notNull(field); field.setAccessible(true); try { Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); } catch (NoSuchFieldException | IllegalAccessException e) { return false; } return true; } /** * Searches the provided collection of constructor candidates for a constructor * that can be used with the provided arguments. * * @param candidates The candidate constructors to check. * @param args The arguments to check. * * @return Null if a matching constructor was not found. */ @Nullable public static Constructor<?> findConstructorByArgs(Collection<Constructor<?>> candidates, Object... args) { for (Constructor<?> c : candidates) { Class<?>[] ptypes = c.getParameterTypes(); if (ptypes.length != args.length) continue; boolean isMatch = true; for (int i=0; i < ptypes.length; i++) { Class<?> arg = args[i].getClass(); if (ptypes[i].isPrimitive() && !arg.isPrimitive()) { arg = WRAPPERS_TO_PRIMITIVES.get(args[i].getClass()); if (arg == null) continue; } if (!ptypes[i].isAssignableFrom(arg)) { isMatch = false; break; } } if (isMatch) { return c; } } return null; } /** * Searches the provided collection of method candidates for a method * that can be used with the provided arguments. * * @param candidates The candidate methods to check. * @param methodName The name of the method to find. * @param args The arguments to check. * * @return Null if a matching method was not found. */ @Nullable public static Method findMethodByArgs(Collection<Method> candidates, String methodName, Object... args) { for (Method method : candidates) { if (!method.getName().equals(methodName)) continue; Class<?>[] ptypes = method.getParameterTypes(); if (ptypes.length != args.length) continue; boolean isMatch = true; for (int i=0; i < ptypes.length; i++) { Class<?> arg = args[i].getClass(); if (ptypes[i].isPrimitive() && !arg.isPrimitive()) { arg = WRAPPERS_TO_PRIMITIVES.get(args[i].getClass()); if (arg == null) continue; } if (!ptypes[i].isAssignableFrom(arg)) { isMatch = false; break; } } if (isMatch) { return method; } } return null; } }