/*********************************************************************************** * Copyright (c) 2013. Nickolay Gerilovich. Russia. * Some Rights Reserved. ************************************************************************************/ package com.github.nickvl.xspring.core.log.aop; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Universal log adapter, capable to out parameter values by reflection. */ public class UniversalLogAdapter extends AbstractLogAdapter { private final Set<String> excludeFieldNames; private final int cropThreshold; private final boolean skipNullFields; /** * Constructor. * * @param skipNullFields use {@code true} to exclude fields which value is {@code null} from building the string * @param cropThreshold threshold value of processed elements count to stop building the string, applied only for multi-element structures * @param excludeFieldNames field names to exclude from building the string * @throws IllegalArgumentException if <code>cropThreshold </code> is negative */ public UniversalLogAdapter(boolean skipNullFields, int cropThreshold, Set<String> excludeFieldNames) { this.skipNullFields = skipNullFields; if (cropThreshold < 0) { throw new IllegalArgumentException("cropThreshold is negative: " + cropThreshold); } this.cropThreshold = cropThreshold; this.excludeFieldNames = excludeFieldNames == null ? null : new HashSet<String>(excludeFieldNames); } /** * Constructor. * * @param skipNullFields use {@code true} to exclude fields which value is {@code null} from building the string * @param excludeFieldNames field names to exclude from building the string */ public UniversalLogAdapter(boolean skipNullFields, Set<String> excludeFieldNames) { this.skipNullFields = skipNullFields; this.cropThreshold = -1; this.excludeFieldNames = excludeFieldNames == null ? null : new HashSet<String>(excludeFieldNames); } @Override protected String asString(Object value) { if (value == null) { return ToString.getNull(); } Class<?> clazz = value.getClass(); if (!(value instanceof Collection<?> || value instanceof Map<?, ?>) && ToStringDetector.INSTANCE.hasToString(clazz)) { return value.toString(); } ToString builder = cropThreshold == -1 ? ToString.createDefault() : ToString.createCropInstance(cropThreshold); builder.addStart(value); if (value instanceof Collection<?>) { builder.addCollection((Collection<?>) value); } else if (value instanceof Map<?, ?>) { builder.addMap((Map<?, ?>) value); } else if (clazz.isArray()) { builder.addArray(value); } else { while (clazz != Object.class) { appendFieldsIn(builder, value, clazz.getDeclaredFields()); clazz = clazz.getSuperclass(); } } builder.addEnd(value); return builder.toString(); } private boolean reject(Field field) { return field.getName().indexOf('$') != -1 // Reject field from inner class. || Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers()) || excludeFieldNames != null && excludeFieldNames.contains(field.getName()); } private void appendFieldsIn(ToString builder, Object object, Field[] fields) { if (fields.length == 0) { return; } AccessibleObject.setAccessible(fields, true); for (Field field : fields) { if (!reject(field)) { String fieldName = field.getName(); Object fieldValue; try { // memo: it creates wrapper objects for primitive types. fieldValue = field.get(object); } catch (IllegalAccessException ex) { throw new IllegalStateException("Unexpected IllegalAccessException: " + ex.getMessage()); } if (!skipNullFields || fieldValue != null) { builder.addField(fieldName, fieldValue); } } } } }