com.diffplug.common.debug.FieldsAndGetters Maven / Gradle / Ivy
/*
* Copyright 2016 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.common.debug;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import com.diffplug.common.base.Errors;
import com.diffplug.common.base.Predicates;
import com.diffplug.common.base.StringPrinter;
import com.diffplug.common.base.Throwing;
/**
* Utilities for obtaining the fields and getter methods of an object using reflection.
* Useful for first-pass debugging of runtime objects.
*/
public class FieldsAndGetters {
/**
* Returns a {@code Stream} of all public fields and their values for the given object.
*
* @see #fields(Object, Predicate)
*/
public static Stream> fields(@Nullable Object obj) {
return fields(obj, Predicates.alwaysTrue());
}
private static Class> getClassNullable(@Nullable Object obj) {
return obj == null ? ObjectIsNull.class : obj.getClass();
}
/**
* Returns a {@code Stream} of all public fields which match {@code predicate} and their values for the given object.
*
* This method uses reflection to find all of the public instance fields of the given object,
* and if they pass the given predicate, it includes them in a stream of {@code Map.Entry}
* where the entry's value is the value of the field for this object.
*/
public static Stream> fields(@Nullable Object obj, Predicate predicate) {
Objects.requireNonNull(predicate);
return Arrays.asList(getClassNullable(obj).getFields()).stream()
// gotta be public
.filter(field -> Modifier.isPublic(field.getModifiers()))
// gotta be an instance field
.filter(field -> !Modifier.isStatic(field.getModifiers()))
// gotta pass the predicate
.filter(predicate)
// get its value
.map(field -> ImmutableEntry.create(field, tryCall(field.getName(), () -> field.get(obj))));
}
/**
* Returns a {@code Stream} of all public getter methods and their return values for the given object.
*
* @see #getters(Object, Predicate)
*/
public static Stream> getters(@Nullable Object obj) {
return getters(obj, Predicates.alwaysTrue());
}
/**
* Returns a {@code Stream} of all public getter methods which match {@code predicate} and their return values for the given object.
*
* This method uses reflection to find all of the public instance methods which don't take any arguments
* and return a value. If they pass the given predicate, then they are called, and the return value is
* included in a stream of {@code Map.Entry}.
*
* Note that there are some methods which have the signature of a getter, but actually mutate the object
* being inspected, e.g. {@link java.io.InputStream#read()}. These will be called unless you manually
* exclude them using the predicate.
*/
public static Stream> getters(@Nullable Object obj, Predicate predicate) {
Objects.requireNonNull(predicate);
return Arrays.asList(getClassNullable(obj).getMethods()).stream()
// we only want methods that don't take parameters
.filter(method -> method.getParameterTypes().length == 0)
// we only want public methods
.filter(method -> Modifier.isPublic(method.getModifiers()))
// we only want instance methods
.filter(method -> !Modifier.isStatic(method.getModifiers()))
// we only want methods that don't return void
.filter(method -> !method.getReturnType().equals(Void.TYPE))
// we'll convert them to the class they were declared by
.map(method -> Errors.rethrow().get(() -> {
if (Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
return method;
} else {
for (Class> clazz : method.getDeclaringClass().getInterfaces()) {
if (Modifier.isPublic(clazz.getModifiers())) {
try {
return clazz.getMethod(method.getName());
} catch (NoSuchMethodException e) {
// take no action
}
}
}
}
return method;
}))
// we only want methods that pass our predicate
.filter(predicate)
// turn it into Map
.map(method -> ImmutableEntry.create(method, tryCall(method.getName(), () -> method.invoke(obj))));
}
/** Sentinel class for null objects. */
public static class ObjectIsNull {}
/** Executes the given function, return any exceptions it might throw as wrapped values. */
private static Object tryCall(String methodName, Throwing.Supplier