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}