package org.infinispan.objectfilter.impl.util; import java.beans.IntrospectionException; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Collection; import java.util.Iterator; import java.util.Map; /** * @author anistor@redhat.com * @since 7.0 */ public final class ReflectionHelper { public interface PropertyAccessor { //todo [anistor] use this info to validate the query uses the types correctly Class<?> getPropertyType(); /** * Indicates if this is a repeated property (ie. array or collection). */ boolean isMultiple(); Object getValue(Object instance); /** * Obtains an Iterator over the values of an array, collection or map attribute. * * @param instance the target instance for accessing the attribute * @return the Iterator or null if the attribute is null */ Iterator<Object> getValueIterator(Object instance); /** * Get the accessor of a nested property. * * @param propName the name of the nested property * @return the accessor of the nested property * @throws IntrospectionException if the nested property was not found */ PropertyAccessor getAccessor(String propName) throws IntrospectionException; } private abstract static class BasePropertyAccessor implements PropertyAccessor { @Override public PropertyAccessor getAccessor(String propName) throws IntrospectionException { return ReflectionHelper.getAccessor(getPropertyType(), propName); } } private static class FieldPropertyAccessor extends BasePropertyAccessor { protected final Field field; FieldPropertyAccessor(Field field) { this.field = field; } @Override public Class<?> getPropertyType() { return field.getType(); } public boolean isMultiple() { return false; } public Object getValue(Object instance) { try { return field.get(instance); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } public Iterator<Object> getValueIterator(Object instance) { throw new UnsupportedOperationException("This property cannot be iterated"); } } private static class ArrayFieldPropertyAccessor extends FieldPropertyAccessor { ArrayFieldPropertyAccessor(Field field) { super(field); } public boolean isMultiple() { return true; } public Iterator<Object> getValueIterator(Object instance) { Object value = getValue(instance); return value == null ? null : new ArrayIterator<>(value); } @Override public Class<?> getPropertyType() { return determineElementType(field.getType(), field.getGenericType()); } } private static class CollectionFieldPropertyAccessor extends FieldPropertyAccessor { CollectionFieldPropertyAccessor(Field field) { super(field); } public boolean isMultiple() { return true; } public Iterator<Object> getValueIterator(Object instance) { Object value = getValue(instance); return value == null ? null : ((Collection) value).iterator(); } @Override public Class<?> getPropertyType() { return determineElementType(field.getType(), field.getGenericType()); } } private static class MapFieldPropertyAccessor extends FieldPropertyAccessor { MapFieldPropertyAccessor(Field field) { super(field); } public boolean isMultiple() { return true; } public Iterator<Object> getValueIterator(Object instance) { Object value = getValue(instance); return value == null ? null : ((Map<?, Object>) value).values().iterator(); } @Override public Class<?> getPropertyType() { return determineElementType(field.getType(), field.getGenericType()); } } private static class MethodPropertyAccessor extends BasePropertyAccessor { protected final Method method; MethodPropertyAccessor(Method method) { this.method = method; } @Override public Class<?> getPropertyType() { return method.getReturnType(); } public boolean isMultiple() { return false; } public Object getValue(Object instance) { try { return method.invoke(instance); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } public Iterator<Object> getValueIterator(Object instance) { throw new UnsupportedOperationException("This property cannot be iterated"); } } private static class ArrayMethodPropertyAccessor extends MethodPropertyAccessor { ArrayMethodPropertyAccessor(Method method) { super(method); } public boolean isMultiple() { return true; } public Iterator<Object> getValueIterator(Object instance) { Object value = getValue(instance); return value == null ? null : new ArrayIterator<>(value); } @Override public Class<?> getPropertyType() { return determineElementType(method.getReturnType(), method.getGenericReturnType()); } } private static class CollectionMethodPropertyAccessor extends MethodPropertyAccessor { CollectionMethodPropertyAccessor(Method method) { super(method); } public boolean isMultiple() { return true; } public Iterator<Object> getValueIterator(Object instance) { Object value = getValue(instance); return value == null ? null : ((Collection<Object>) value).iterator(); } @Override public Class<?> getPropertyType() { return determineElementType(method.getReturnType(), method.getGenericReturnType()); } } private static class MapMethodPropertyAccessor extends MethodPropertyAccessor { MapMethodPropertyAccessor(Method method) { super(method); } public boolean isMultiple() { return true; } public Iterator<Object> getValueIterator(Object instance) { Object value = getValue(instance); return value == null ? null : ((Map<?, Object>) value).values().iterator(); } @Override public Class<?> getPropertyType() { return determineElementType(method.getReturnType(), method.getGenericReturnType()); } } private ReflectionHelper() { } public static PropertyAccessor getAccessor(Class<?> clazz, String propertyName) throws IntrospectionException { if (propertyName == null || propertyName.length() == 0) { throw new IllegalArgumentException("Property name cannot be null or empty"); } if (propertyName.contains(".")) { throw new IllegalArgumentException("The argument cannot be a nested property name"); } // try getter method access // we need to find a no-arg public "getXyz" or "isXyz" method which has a suitable return type String propertyNameSuffix = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); try { Method m = clazz.getDeclaredMethod("get" + propertyNameSuffix); if (Modifier.isPublic(m.getModifiers()) && !m.getReturnType().equals(Void.class)) { return getMethodAccessor(m); } } catch (NoSuchMethodException e) { try { Method m = clazz.getDeclaredMethod("is" + propertyNameSuffix); if (Modifier.isPublic(m.getModifiers()) && (boolean.class.equals(m.getReturnType()) || Boolean.class.equals(m.getReturnType()))) { return getMethodAccessor(m); } } catch (NoSuchMethodException e1) { // ignored, continue } } // try field access try { Field f = clazz.getDeclaredField(propertyName); if (f != null && !f.isSynthetic()) { return getFieldAccessor(f); } } catch (NoSuchFieldException e) { // ignored, continue } throw new IntrospectionException("Property not found: " + propertyName); } private static PropertyAccessor getFieldAccessor(Field f) { f.setAccessible(true); Class<?> fieldClass = f.getType(); if (fieldClass.isArray()) { return new ArrayFieldPropertyAccessor(f); } else if (Collection.class.isAssignableFrom(fieldClass)) { return new CollectionFieldPropertyAccessor(f); } else if (Map.class.isAssignableFrom(fieldClass)) { return new MapFieldPropertyAccessor(f); } return new FieldPropertyAccessor(f); } private static PropertyAccessor getMethodAccessor(Method m) { Class<?> fieldClass = m.getReturnType(); if (fieldClass.isArray()) { return new ArrayMethodPropertyAccessor(m); } else if (Collection.class.isAssignableFrom(fieldClass)) { return new CollectionMethodPropertyAccessor(m); } else if (Map.class.isAssignableFrom(fieldClass)) { return new MapMethodPropertyAccessor(m); } return new MethodPropertyAccessor(m); } private static Class determineElementType(Class<?> type, Type genericType) { if (type.isArray()) { if (genericType instanceof Class) { return type.getComponentType(); } GenericArrayType genericArrayType = (GenericArrayType) genericType; Type genericComponentType = genericArrayType.getGenericComponentType(); if (genericComponentType instanceof ParameterizedType) { return (Class) ((ParameterizedType) genericComponentType).getRawType(); } else { return (Class) ((TypeVariable) genericComponentType).getBounds()[0]; } } else if (Collection.class.isAssignableFrom(type)) { return determineCollectionElementType(genericType); } else if (Map.class.isAssignableFrom(type)) { return determineMapValueTypeParam(genericType); } return null; } private static Class determineMapValueTypeParam(Type genericType) { if (genericType instanceof ParameterizedType) { ParameterizedType type = (ParameterizedType) genericType; Type fieldArgType = type.getActualTypeArguments()[1]; if (fieldArgType instanceof TypeVariable) { TypeVariable genericComponentType = (TypeVariable) fieldArgType; return (Class) genericComponentType.getBounds()[0]; } else { return (Class) fieldArgType; } } else if (genericType instanceof Class) { Class c = (Class) genericType; if (c.getGenericSuperclass() != null && Map.class.isAssignableFrom(c.getSuperclass())) { Class x = determineMapValueTypeParam(c.getGenericSuperclass()); if (x != null) { return x; } } for (Type t : c.getGenericInterfaces()) { if (t instanceof Class && Map.class.isAssignableFrom((Class<?>) t) || t instanceof ParameterizedType && Map.class.isAssignableFrom((Class) ((ParameterizedType) t).getRawType())) { Class x = determineMapValueTypeParam(t); if (x != null) { return x; } } } } return null; } private static Class determineCollectionElementType(Type genericType) { if (genericType instanceof ParameterizedType) { ParameterizedType type = (ParameterizedType) genericType; Type fieldArgType = type.getActualTypeArguments()[0]; if (fieldArgType instanceof Class) { return (Class) fieldArgType; } return (Class) ((ParameterizedType) fieldArgType).getRawType(); } else if (genericType instanceof Class) { Class c = (Class) genericType; if (c.getGenericSuperclass() != null && Collection.class.isAssignableFrom(c.getSuperclass())) { Class x = determineCollectionElementType(c.getGenericSuperclass()); if (x != null) { return x; } } for (Type t : c.getGenericInterfaces()) { if (t instanceof Class && Map.class.isAssignableFrom((Class<?>) t) || t instanceof ParameterizedType && Collection.class.isAssignableFrom((Class) ((ParameterizedType) t).getRawType())) { Class x = determineCollectionElementType(t); if (x != null) { return x; } } } } return null; } }