blob: 1b12ba3da8b06fda1a5dd6c254c88116230336cf [file] [log] [blame]
/*******************************************************************************
* 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");
}
}