001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright (c) 2009-2019 CQSE GmbH                                        |
004|                                                                          |
005+-------------------------------------------------------------------------*/
006package com.teamscale.commons.lang;
007
008import java.lang.reflect.Array;
009import java.lang.reflect.Field;
010import java.lang.reflect.Modifier;
011import java.util.ArrayList;
012import java.util.Comparator;
013import java.util.List;
014
015import com.google.common.base.MoreObjects;
016import com.google.common.base.MoreObjects.ToStringHelper;
017
018/**
019 * Utilities for working with Guava's {@link ToStringHelper}.
020 */
021public class ToStringHelpers {
022
023        /**
024         * A reflective variant of {@link MoreObjects#toStringHelper(Object)}.
025         * <p>
026         * It is significantly slower than its {@code MoreObjects} counterpart, so it
027         * should not be used in performance-critical code.
028         * 
029         * @return a {@link ToStringHelper} with all fields of {@literal self} already
030         *         {@linkplain ToStringHelper#add(String, Object) added}.
031         */
032        public static ToStringHelper toReflectiveStringHelper(Object self) {
033                if (self == null) {
034                        return MoreObjects.toStringHelper("null");
035                }
036
037                ToStringHelper helper = MoreObjects.toStringHelper(self);
038                Class<?> clazz = self.getClass();
039                if (clazz.isArray()) {
040                        return addArrayElements(helper, self);
041                }
042                return addInstanceFields(helper, self);
043        }
044
045        private static ToStringHelper addArrayElements(ToStringHelper helper, Object array) {
046                int length = Array.getLength(array);
047                for (int i = 0; i < length; i++) {
048                        Object element = Array.get(array, i);
049                        helper.addValue(element);
050                }
051                return helper;
052        }
053
054        private static ToStringHelper addInstanceFields(ToStringHelper helper, Object instance) {
055                List<Field> allDeclaredFields = getAllProperInstanceFields(instance);
056                allDeclaredFields.sort(Comparator.comparing(Field::getName));
057                for (Field field : allDeclaredFields) {
058                        try {
059                                field.setAccessible(true);
060                                String name = field.getName();
061                                Object value = field.get(instance);
062                                helper.add(name, value);
063                        } catch (IllegalArgumentException | IllegalAccessException e) {
064                                // Ignore and proceed on a best-effort basis
065                                continue;
066                        }
067                }
068                return helper;
069        }
070
071        /**
072         * @return all proper instance (i.e., not {@code static}) fields of the given
073         *         object. Fields that are only synthesized by the compiler are omitted.
074         */
075        private static List<Field> getAllProperInstanceFields(Object instance) {
076                List<Field> declaredFields = new ArrayList<>();
077
078                Class<? extends Object> clazz = instance.getClass();
079                do {
080                        for (Field field : clazz.getDeclaredFields()) {
081                                if (field.isSynthetic()) {
082                                        continue;
083                                }
084                                if (Modifier.isStatic(field.getModifiers())) {
085                                        continue;
086                                }
087                                declaredFields.add(field);
088                        }
089                        clazz = clazz.getSuperclass();
090                } while (clazz != null);
091
092                return declaredFields;
093        }
094}