You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ToStringBuilder.java 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package com.healthmarketscience.jackcess.util;
  2. import java.lang.reflect.Array;
  3. import java.nio.ByteBuffer;
  4. import java.util.Collection;
  5. import java.util.Map;
  6. import java.util.Optional;
  7. import java.util.WeakHashMap;
  8. import java.util.stream.Collectors;
  9. import com.healthmarketscience.jackcess.impl.ByteUtil;
  10. import com.healthmarketscience.jackcess.impl.PageChannel;
  11. /**
  12. * <p>Builder for {@link Object#toString()} methods.</p>
  13. *
  14. * @author Markus Spann
  15. */
  16. public class ToStringBuilder
  17. {
  18. /** Object registry for avoidance of cycles. */
  19. private static final Map<Object, Object> OBJ_REGISTRY = new WeakHashMap<>();
  20. private final StringBuilder buffer;
  21. private final Object object;
  22. private final String fieldSeparator;
  23. private final boolean fieldSeparatorAtStart;
  24. private final String fieldNameValueSeparator;
  25. private final String contentStart;
  26. private final String contentEnd;
  27. private final String nullText;
  28. private final String implSuffix;
  29. private boolean useIdentityHashCode = true;
  30. private final int maxByteDetailLen = 20;
  31. ToStringBuilder(Object _object, String _fieldSeparator, boolean _fieldSeparatorAtStart, String _fieldNameValueSeparator, String _contentEnd,
  32. boolean _useIdentityHashCode) {
  33. buffer = new StringBuilder(512);
  34. object = _object;
  35. fieldSeparator = Optional.ofNullable(_fieldSeparator).orElse(",");
  36. fieldSeparatorAtStart = _fieldSeparatorAtStart;
  37. fieldNameValueSeparator = Optional.ofNullable(_fieldNameValueSeparator).orElse("=");
  38. contentStart = "[";
  39. contentEnd = Optional.ofNullable(_contentEnd).orElse("]");
  40. nullText = "<null>";
  41. implSuffix = "Impl";
  42. useIdentityHashCode = _useIdentityHashCode;
  43. if (object != null) {
  44. buffer.append(_object instanceof String ? _object : getShortClassName(_object.getClass(), implSuffix));
  45. if (useIdentityHashCode) {
  46. buffer.append('@').append(Integer.toHexString(System.identityHashCode(object)));
  47. }
  48. buffer.append(contentStart);
  49. if (fieldSeparatorAtStart) {
  50. buffer.append(fieldSeparator);
  51. }
  52. }
  53. }
  54. public static ToStringBuilder valueBuilder(Object obj)
  55. {
  56. return new ToStringBuilder(obj, null, false, null, null, false);
  57. }
  58. public static ToStringBuilder builder(Object obj)
  59. {
  60. return new ToStringBuilder(obj, System.lineSeparator() + " ", true, ": ", System.lineSeparator() + "]", true);
  61. }
  62. public ToStringBuilder append(String fieldName, Object value)
  63. {
  64. if (fieldName != null) {
  65. buffer.append(fieldName).append(fieldNameValueSeparator);
  66. }
  67. if (value == null) {
  68. buffer.append(nullText);
  69. } else {
  70. appendInternal(buffer, fieldName, value);
  71. }
  72. buffer.append(fieldSeparator);
  73. return this;
  74. }
  75. public ToStringBuilder appendIgnoreNull(String fieldName, Object value)
  76. {
  77. return append(fieldName, value == null ? "" : value);
  78. }
  79. @Override
  80. public String toString()
  81. {
  82. if (object == null) {
  83. buffer.append(nullText);
  84. } else {
  85. removeLastFieldSeparator(buffer, fieldSeparator);
  86. buffer.append(contentEnd);
  87. }
  88. return buffer.toString();
  89. }
  90. void appendInternal(StringBuilder buffer, String fieldName, Object value)
  91. {
  92. boolean primitiveWrapper = value instanceof Number || value instanceof Boolean || value instanceof Character;
  93. if (OBJ_REGISTRY.containsKey(value) && !primitiveWrapper) {
  94. buffer.append(value.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(value)));
  95. return;
  96. }
  97. OBJ_REGISTRY.put(value, null); // register object
  98. try {
  99. if (value instanceof byte[]) {
  100. ByteBuffer bb = PageChannel.wrap((byte[]) value);
  101. int len = bb.remaining();
  102. buffer.append("(").append(len).append(") ").append(ByteUtil.toHexString(bb, bb.position(), Math.min(len, maxByteDetailLen)));
  103. if (len > maxByteDetailLen) {
  104. buffer.append("...");
  105. }
  106. } else if (value.getClass().isArray()) {
  107. Object arr = value;
  108. buffer.append('{');
  109. for (int i = 0; i < Array.getLength(arr); i++) {
  110. Object item = Array.get(arr, i);
  111. if (i > 0) {
  112. buffer.append(fieldSeparator);
  113. }
  114. if (item == null) {
  115. buffer.append(nullText);
  116. } else {
  117. appendInternal(buffer, fieldName, item); // recursive call
  118. }
  119. }
  120. buffer.append('}');
  121. } else if (value instanceof Collection<?>) {
  122. String str = ((Collection<?>) value).stream().map(v -> v == null ? nullText : v.toString()).collect(Collectors.joining(","));
  123. buffer.append('[').append(str).append(']');
  124. } else if (value instanceof Map<?, ?>) {
  125. String str = ((Map<?, ?>) value).entrySet().stream().map(e -> e.getKey() + "=" + (e.getValue() == null ? nullText : e.getValue()))
  126. .collect(Collectors.joining(","));
  127. buffer.append('{').append(str).append('}');
  128. } else {
  129. buffer.append(value);
  130. }
  131. } finally {
  132. OBJ_REGISTRY.remove(value); // unregister object
  133. }
  134. }
  135. static String getShortClassName(Class<?> clazz, String _implSuffix)
  136. {
  137. String nm = clazz.getSimpleName();
  138. if (nm.endsWith(_implSuffix)) {
  139. nm = nm.substring(0, nm.length() - _implSuffix.length());
  140. }
  141. int idx = nm.lastIndexOf('.');
  142. return idx >= 0 ? nm.substring(idx + 1) : nm;
  143. }
  144. static void removeLastFieldSeparator(StringBuilder _buffer, String _fieldSeparator)
  145. {
  146. int len = _buffer.length();
  147. int sepLen = _fieldSeparator.length();
  148. if (len > 0 && sepLen > 0 && len >= sepLen) {
  149. boolean match = true;
  150. for (int i = 0; i < sepLen; i++) {
  151. if (_buffer.charAt(len - 1 - i) != _fieldSeparator.charAt(sepLen - 1 - i)) {
  152. return;
  153. }
  154. }
  155. if (match) {
  156. _buffer.setLength(len - sepLen);
  157. }
  158. }
  159. }
  160. }