package tc.oc.commons.core.reflect; import java.lang.reflect.Field; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nullable; import com.google.common.reflect.TypeToken; public final class Fields { private Fields() {} public static String descriptor(Field field) { return Types.descriptor(field.getType()); } public static Field needAssignableField(boolean privateAccess, Class<?> parent, Class<?> type, String name) throws NoSuchFieldException { Field field = null; if(privateAccess) { try { field = parent.getDeclaredField(name); } catch(NoSuchFieldException ignored) {} } if(field == null) { try { field = parent.getField(name); } catch(NoSuchFieldException ignored) {} } if(field == null) { throw new NoSuchFieldException("No field named " + name); } if(!field.getType().isAssignableFrom(type)) { throw new NoSuchFieldException("Field " + parent.getName() + "." + name + " with type " + field.getType().getName() + " is not assignable from " + type.getName()); } return field; } private static Field assertAssignableTo(Field field, TypeToken<?> type) { if(!type.isAssignableFrom(TypeToken.of(field.getGenericType()))) { throw new NoSuchFieldError("Field " + field.getName() + " is not of type " + type); } return field; } public static <T> T read(Class<?> parent, Class<T> type, String name, @Nullable Object obj) { return read(parent, TypeToken.of(type), name, obj); } public static <T> T read(Class<?> parent, TypeToken<T> type, String name, @Nullable Object obj) { return read(named(parent, name), type, obj); } public static <T> T read(Class<?> parent, TypeToken<T> type, @Nullable Object obj) { return (T) readFieldUnchecked(oneOfType(parent, type), obj); } public static <T> T read(Field field, TypeToken<T> type, @Nullable Object obj) { assertAssignableTo(field, type); return (T) readFieldUnchecked(field, obj); } public static Object readFieldUnchecked(Field field, @Nullable Object obj) { final boolean wasAccessible = field.isAccessible(); try { field.setAccessible(true); return field.get(obj); } catch(IllegalAccessException e) { throw new NoSuchFieldError("Cannot access field " + field.getName()); } finally { field.setAccessible(wasAccessible); } } public static void writeField(@Nullable Object obj, Field field, Object value) { final boolean wasAccessible = field.isAccessible(); try { field.setAccessible(true); field.set(obj, value); } catch(IllegalAccessException e) { throw new RuntimeException(e); } finally { field.setAccessible(wasAccessible); } } public static void writeField(@Nullable Object obj, String name, Object value) { final Field field; try { field = obj.getClass().getDeclaredField(name); } catch(NoSuchFieldException e) { throw new RuntimeException(e); } final boolean wasAccessible = field.isAccessible(); try { field.setAccessible(true); field.set(obj, value); } catch(IllegalAccessException e) { throw new RuntimeException(e); } finally { field.setAccessible(wasAccessible); } } public static Field named(Class<?> decl, String name) { try { return decl.getDeclaredField(name); } catch(NoSuchFieldException e) { try { return decl.getField(name); } catch(NoSuchFieldException e1) { throw new NoSuchFieldError("No field named \"" + name + "\" accessible from " + decl.getName()); } } } public static Stream<Field> accessibleFrom(Class<?> decl) { return Stream.concat(Stream.of(decl.getDeclaredFields()), Stream.of(decl.getFields())) .distinct(); } public static Predicate<Field> ofType(TypeToken<?> type) { return field -> TypeToken.of(field.getGenericType()).equals(type); } public static Predicate<Field> assignableFrom(TypeToken<?> type) { return field -> TypeToken.of(field.getGenericType()).isAssignableFrom(type); } public static Predicate<Field> assignableTo(TypeToken<?> type) { return field -> type.isAssignableFrom(field.getGenericType()); } public static Stream<Field> ofType(Class<?> decl, TypeToken<?> type) { return accessibleFrom(decl).filter(ofType(type)); } public static Stream<Field> assignableFrom(Class<?> decl, TypeToken<?> type) { return accessibleFrom(decl).filter(assignableFrom(type)); } public static Stream<Field> assignableTo(Class<?> decl, TypeToken<?> type) { return accessibleFrom(decl).filter(assignableTo(type)); } private static Field one(Stream<Field> fields, Class<?> decl, TypeToken<?> type, String description) { final List<Field> list = fields.collect(Collectors.toList()); switch(list.size()) { case 0: throw new NoSuchFieldError("No field " + description + " " + type + " in " + decl.getName()); case 1: return list.get(0); default: throw new NoSuchFieldError("Multiple fields " + description + " " + type + " in " + decl.getName() + ": " + list.stream().map(Field::getName).collect(Collectors.joining(", "))); } } public static Field oneOfType(Class<?> decl, TypeToken<?> type) { return one(ofType(decl, type), decl, type, "of type"); } public static Field oneAssignableFrom(Class<?> decl, TypeToken<?> type) { return one(assignableFrom(decl, type), decl, type, "assignable from"); } public static Field oneAssignableTo(Class<?> decl, TypeToken<?> type) { return one(assignableTo(decl, type), decl, type, "assignable to"); } public static Stream<Field> declaredInAncestors(Class<?> klass) { return Types.ancestors(klass) .stream() .flatMap(ancestor -> Stream.of(ancestor.getDeclaredFields())) .distinct(); } }