/*********************************************************************************** * Copyright (c) 2013. Nickolay Gerilovich. Russia. * Some Rights Reserved. ************************************************************************************/ package com.github.nickvl.xspring.core.log.aop; import java.lang.reflect.Array; import java.util.Collection; import java.util.Collections; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * String representation builder of any class instance. */ final class ToString { private static final String NULL_VALUE = "NIL"; private static final String CONTENT_START = "["; private static final String CONTENT_END = "]"; private static final String FIELD_VALUE_SEPARATOR = "="; private static final String FIELD_SEPARATOR = ";"; private static final String ENUMERATION_START = "{"; private static final String ENUMERATION_SEPARATOR = ","; private static final String ENUMERATION_END = "}"; private static final String SIZE_START = "..<size="; private static final String SIZE_END = ">.."; private final int cropThreshold; private final StringBuilder buffer = new StringBuilder(512); private final Set<Object> valuesInProgress = Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>()); private ToString(int cropThreshold) { this.cropThreshold = cropThreshold; } public static ToString createDefault() { return new ToString(-1); } public static ToString createCropInstance(int cropThreshold) { return new ToString(cropThreshold); } /** * Gets the text representation of {@code null} value fields. * * @return the current text to output when null found */ public static String getNull() { return NULL_VALUE; } /** * Adds the start of <code>toString</code> of the given object. * * @param object the <code>Object</code> to build <code>toString</code> */ public void addStart(Object object) { if (object != null) { valuesInProgress.add(object); addClassName(object); addContentStart(); } } /** * Ads the end of <code>toString</code> of the given object. * * @param object the <code>Object</code> to build a <code>toString</code> */ public void addEnd(Object object) { removeLastFieldSeparator(); addContentEnd(); valuesInProgress.remove(object); //not really needed but added for symmetry purposes (with addStart()) } private void removeLastFieldSeparator() { int length = buffer.length(); int sepLen = FIELD_SEPARATOR.length(); if (length > 0 && sepLen > 0 && length >= sepLen) { for (int i = 0; i < sepLen; i++) { if (buffer.charAt(length - sepLen + i) != FIELD_SEPARATOR.charAt(i)) { return; } } buffer.setLength(length - sepLen); } } /** * Adds to the <code>toString</code> a field of the given object. * * @param fieldName the field name * @param value the value of the field */ public void addField(String fieldName, Object value) { addFieldStart(fieldName); if (value == null) { addNullValue(); } else { parse(value); } addFieldEnd(); } /** * Adds array as a given object to build the <code>toString</code>. * * @param array the array to build a <code>toString</code> */ public void addArray(Object array) { buffer.append(ENUMERATION_START); int maxIndex = Array.getLength(array) - 1; if (maxIndex != -1) { for (int i = 0;; i++) { if (i == cropThreshold) { buffer.append(SIZE_START).append(maxIndex + 1).append(SIZE_END); break; } Object item = Array.get(array, i); if (item == null) { addNullValue(); } else { parse(item); } if (i == maxIndex) { break; } buffer.append(ENUMERATION_SEPARATOR); } } buffer.append(ENUMERATION_END); } /** * Adds collection as a given object to build the <code>toString</code>. * * @param coll the collection to build a <code>toString</code> */ public void addCollection(Collection<?> coll) { buffer.append(ENUMERATION_START); if (!coll.isEmpty()) { Iterator<?> iterator = coll.iterator(); for (int i = 0;; i++) { if (i == cropThreshold) { buffer.append(SIZE_START).append(coll.size()).append(SIZE_END); break; } Object item = iterator.next(); if (item == null) { addNullValue(); } else { parse(item); } if (!iterator.hasNext()) { break; } buffer.append(ENUMERATION_SEPARATOR); } } buffer.append(ENUMERATION_END); } /** * Adds map as a given object to build the <code>toString</code>. * * @param map the map to build a <code>toString</code> */ public void addMap(Map<?, ?> map) { buffer.append(ENUMERATION_START); if (!map.isEmpty()) { Iterator<?> iterator = map.entrySet().iterator(); for (int i = 0;; i++) { if (i == cropThreshold) { buffer.append(SIZE_START).append(map.size()).append(SIZE_END); break; } Object item = iterator.next(); // iterator shall never return null parse(item); if (!iterator.hasNext()) { break; } buffer.append(ENUMERATION_SEPARATOR); } } buffer.append(ENUMERATION_END); } private void parse(Object value) { if (valuesInProgress.contains(value)) { addCyclicObject(value); return; } valuesInProgress.add(value); if (value instanceof Collection<?>) { addCollection((Collection<?>) value); } else if (value instanceof Map<?, ?>) { addMap((Map<?, ?>) value); } else if (value instanceof long[]) { add((long[]) value); } else if (value instanceof int[]) { add((int[]) value); } else if (value instanceof short[]) { add((short[]) value); } else if (value instanceof byte[]) { add((byte[]) value); } else if (value instanceof char[]) { add((char[]) value); } else if (value instanceof double[]) { add((double[]) value); } else if (value instanceof float[]) { add((float[]) value); } else if (value instanceof boolean[]) { add((boolean[]) value); } else if (value.getClass().isArray()) { add((Object[]) value); } else { add(value); } valuesInProgress.remove(value); } private void addCyclicObject(Object value) { buffer.append(value.getClass().getSimpleName()) .append('@') .append(Integer.toHexString(System.identityHashCode(value))); } private void add(Object value) { Class<?> clazz = value.getClass(); if (ToStringDetector.INSTANCE.hasToString(clazz)) { buffer.append(value.toString()); } else { buffer.append(clazz.getSimpleName()) .append('@') .append(Integer.toHexString(value.hashCode())); } } private void add(Object[] array) { buffer.append(ENUMERATION_START); int maxIndex = array.length - 1; if (maxIndex != -1) { for (int i = 0;; i++) { if (i == cropThreshold) { buffer.append(SIZE_START).append(array.length).append(SIZE_END); break; } Object item = array[i]; if (item == null) { addNullValue(); } else { parse(item); } if (i == maxIndex) { break; } buffer.append(ENUMERATION_SEPARATOR); } } buffer.append(ENUMERATION_END); } private void add(long[] array) { buffer.append(ENUMERATION_START); int maxIndex = array.length - 1; if (maxIndex != -1) { for (int i = 0;; i++) { if (i == cropThreshold) { buffer.append(SIZE_START).append(array.length).append(SIZE_END); break; } buffer.append(array[i]); if (i == maxIndex) { break; } buffer.append(ENUMERATION_SEPARATOR); } } buffer.append(ENUMERATION_END); } private void add(int[] array) { buffer.append(ENUMERATION_START); int maxIndex = array.length - 1; if (maxIndex != -1) { for (int i = 0;; i++) { if (i == cropThreshold) { buffer.append(SIZE_START).append(array.length).append(SIZE_END); break; } buffer.append(array[i]); if (i == maxIndex) { break; } buffer.append(ENUMERATION_SEPARATOR); } } buffer.append(ENUMERATION_END); } private void add(short[] array) { buffer.append(ENUMERATION_START); int maxIndex = array.length - 1; if (maxIndex != -1) { for (int i = 0;; i++) { if (i == cropThreshold) { buffer.append(SIZE_START).append(array.length).append(SIZE_END); break; } buffer.append(array[i]); if (i == maxIndex) { break; } buffer.append(ENUMERATION_SEPARATOR); } } buffer.append(ENUMERATION_END); } private void add(byte[] array) { buffer.append(ENUMERATION_START); int maxIndex = array.length - 1; if (maxIndex != -1) { for (int i = 0;; i++) { if (i == cropThreshold) { buffer.append(SIZE_START).append(array.length).append(SIZE_END); break; } buffer.append(array[i]); if (i == maxIndex) { break; } buffer.append(ENUMERATION_SEPARATOR); } } buffer.append(ENUMERATION_END); } private void add(char[] array) { buffer.append(ENUMERATION_START); int maxIndex = array.length - 1; if (maxIndex != -1) { for (int i = 0;; i++) { if (i == cropThreshold) { buffer.append(SIZE_START).append(array.length).append(SIZE_END); break; } buffer.append(array[i]); if (i == maxIndex) { break; } buffer.append(ENUMERATION_SEPARATOR); } } buffer.append(ENUMERATION_END); } private void add(double[] array) { buffer.append(ENUMERATION_START); int maxIndex = array.length - 1; if (maxIndex != -1) { for (int i = 0;; i++) { if (i == cropThreshold) { buffer.append(SIZE_START).append(array.length).append(SIZE_END); break; } buffer.append(array[i]); if (i == maxIndex) { break; } buffer.append(ENUMERATION_SEPARATOR); } } buffer.append(ENUMERATION_END); } private void add(float[] array) { buffer.append(ENUMERATION_START); int maxIndex = array.length - 1; if (maxIndex != -1) { for (int i = 0;; i++) { if (i == cropThreshold) { buffer.append(SIZE_START).append(array.length).append(SIZE_END); break; } buffer.append(array[i]); if (i == maxIndex) { break; } buffer.append(ENUMERATION_SEPARATOR); } } buffer.append(ENUMERATION_END); } private void add(boolean[] array) { buffer.append(ENUMERATION_START); int maxIndex = array.length - 1; if (maxIndex != -1) { for (int i = 0;; i++) { if (i == cropThreshold) { buffer.append(SIZE_START).append(array.length).append(SIZE_END); break; } buffer.append(array[i]); if (i == maxIndex) { break; } buffer.append(ENUMERATION_SEPARATOR); } } buffer.append(ENUMERATION_END); } private void addClassName(Object object) { buffer.append(object.getClass().getSimpleName()); } private void addContentStart() { buffer.append(CONTENT_START); } private void addContentEnd() { buffer.append(CONTENT_END); } private void addNullValue() { buffer.append(NULL_VALUE); } private void addFieldStart(String fieldName) { if (fieldName != null) { buffer.append(fieldName); buffer.append(FIELD_VALUE_SEPARATOR); } } private void addFieldEnd() { buffer.append(FIELD_SEPARATOR); } @Override public String toString() { return buffer.toString(); } }