| /******************************************************************************* |
| * Copyright (c) 2012 itemis AG (http://www.itemis.eu) and others. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| *******************************************************************************/ |
| package org.eclipse.xtext.xbase.lib.util; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| |
| import com.google.common.annotations.Beta; |
| import com.google.common.base.Preconditions; |
| |
| /** |
| * Extension methods to simplify reflective invocation of methods and fields. |
| * |
| * @author Sven Efftinge - Initial contribution and API |
| * @since 2.3 |
| */ |
| @Beta |
| public class ReflectExtensions { |
| |
| /** |
| * Sets the given value on an the receivers's accessible field with the given name. |
| * |
| * @param receiver the receiver, never <code>null</code> |
| * @param fieldName the field's name, never <code>null</code> |
| * @param value the value to set |
| * |
| * @throws NoSuchFieldException see {@link Class#getField(String)} |
| * @throws SecurityException see {@link Class#getField(String)} |
| * @throws IllegalAccessException see {@link Field#set(Object, Object)} |
| * @throws IllegalArgumentException see {@link Field#set(Object, Object)} |
| */ |
| public void set(Object receiver, String fieldName, /* @Nullable */ Object value) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { |
| Preconditions.checkNotNull(receiver,"receiver"); |
| Preconditions.checkNotNull(fieldName,"fieldName"); |
| Class<? extends Object> clazz = receiver.getClass(); |
| Field f = getDeclaredField(clazz, fieldName); |
| if (!f.isAccessible()) |
| f.setAccessible(true); |
| f.set(receiver, value); |
| } |
| |
| /** |
| * Retrieves the value of the given accessible field of the given receiver. |
| * |
| * @param receiver the container of the field, not <code>null</code> |
| * @param fieldName the field's name, not <code>null</code> |
| * @return the value of the field |
| * |
| * @throws NoSuchFieldException see {@link Class#getField(String)} |
| * @throws SecurityException see {@link Class#getField(String)} |
| * @throws IllegalAccessException see {@link Field#get(Object)} |
| * @throws IllegalArgumentException see {@link Field#get(Object)} |
| */ |
| @SuppressWarnings("unchecked") |
| /* @Nullable */ |
| public <T> T get(Object receiver, String fieldName) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { |
| Preconditions.checkNotNull(receiver,"receiver"); |
| Preconditions.checkNotNull(fieldName,"fieldName"); |
| |
| Class<? extends Object> clazz = receiver.getClass(); |
| Field f = getDeclaredField(clazz, fieldName); |
| if (!f.isAccessible()) |
| f.setAccessible(true); |
| return (T) f.get(receiver); |
| } |
| |
| private Field getDeclaredField(Class<?> clazz, String name) throws NoSuchFieldException { |
| NoSuchFieldException initialException = null; |
| do { |
| try { |
| Field f = clazz.getDeclaredField(name); |
| return f; |
| } catch(NoSuchFieldException noSuchField) { |
| if (initialException == null) { |
| initialException = noSuchField; |
| } |
| } |
| } while((clazz = clazz.getSuperclass()) != null); |
| throw initialException; |
| } |
| |
| /** |
| * Invokes the first accessible method defined on the receiver'c class with the given name and |
| * a parameter list compatible to the given arguments. |
| * |
| * @param receiver the method call receiver, not <code>null</code> |
| * @param methodName the method name, not <code>null</code> |
| * @param args the arguments for the method invocation |
| * @return the result of the method invocation. <code>null</code> if the method was of type void. |
| * |
| * @throws SecurityException see {@link Class#getMethod(String, Class...)} |
| * @throws NoSuchMethodException see {@link Class#getMethod(String, Class...)} |
| * @throws IllegalAccessException see {@link Method#invoke(Object, Object...)} |
| * @throws IllegalArgumentException see {@link Method#invoke(Object, Object...)} |
| * @throws InvocationTargetException see {@link Method#invoke(Object, Object...)} |
| */ |
| /* @Nullable */ |
| public Object invoke(Object receiver, String methodName, /* @Nullable */ Object...args) throws SecurityException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { |
| Preconditions.checkNotNull(receiver,"receiver"); |
| Preconditions.checkNotNull(methodName,"methodName"); |
| final Object[] arguments = args==null ? new Object[]{null}:args; |
| |
| Class<? extends Object> clazz = receiver.getClass(); |
| Method compatible = null; |
| do { |
| for (Method candidate : clazz.getDeclaredMethods()) { |
| if (candidate != null && !candidate.isBridge() && isCompatible(candidate, methodName, arguments)) { |
| if (compatible != null) |
| throw new IllegalStateException("Ambiguous methods to invoke. Both "+compatible+" and "+candidate+" would be compatible choices."); |
| compatible = candidate; |
| } |
| } |
| } while(compatible == null && (clazz = clazz.getSuperclass()) != null); |
| if (compatible != null) { |
| if (!compatible.isAccessible()) |
| compatible.setAccessible(true); |
| return compatible.invoke(receiver, arguments); |
| } |
| // not found provoke method not found exception |
| Class<?>[] paramTypes = new Class<?>[arguments.length]; |
| for (int i = 0; i< arguments.length ; i++) { |
| paramTypes[i] = arguments[i] == null ? Object.class : arguments[i].getClass(); |
| } |
| Method method = receiver.getClass().getMethod(methodName, paramTypes); |
| return method.invoke(receiver, arguments); |
| } |
| |
| private boolean isCompatible(Method candidate, String featureName, Object... args) { |
| if (!candidate.getName().equals(featureName)) |
| return false; |
| if (candidate.getParameterTypes().length != args.length) |
| return false; |
| for (int i = 0; i< candidate.getParameterTypes().length; i++) { |
| Object param = args[i]; |
| Class<?> class1 = candidate.getParameterTypes()[i]; |
| if (class1.isPrimitive()) |
| class1 = wrapperTypeFor(class1); |
| if (param != null && !class1.isInstance(param)) |
| return false; |
| } |
| return true; |
| } |
| |
| private Class<?> wrapperTypeFor(Class<?> primitive) { |
| Preconditions.checkNotNull(primitive); |
| if (primitive == Boolean.TYPE) return Boolean.class; |
| if (primitive == Byte.TYPE) return Byte.class; |
| if (primitive == Character.TYPE) return Character.class; |
| if (primitive == Short.TYPE) return Short.class; |
| if (primitive == Integer.TYPE) return Integer.class; |
| if (primitive == Long.TYPE) return Long.class; |
| if (primitive == Float.TYPE) return Float.class; |
| if (primitive == Double.TYPE) return Double.class; |
| if (primitive == Void.TYPE) return Void.class; |
| throw new IllegalArgumentException(primitive+ " is not a primitive"); |
| } |
| |
| } |