Skip to content

Commit

Permalink
add and use copy of ToStringBuilder from xbase.lib
Browse files Browse the repository at this point in the history
Signed-off-by: Christian Dietrich <christian.dietrich@itemis.de>
  • Loading branch information
cdietrich committed Mar 1, 2023
1 parent 404128b commit 086ffce
Show file tree
Hide file tree
Showing 7 changed files with 765 additions and 7 deletions.
2 changes: 1 addition & 1 deletion org.eclipse.lsp4j.debug/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ jar {
}

jar.bnd (
'Import-Package': "com.google.common.*;version=\"$versions.guava\",com.google.gson.*;version=\"$versions.gson\",*",
'Import-Package': "com.google.gson.*;version=\"$versions.gson\",*",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,372 @@
package org.eclipse.lsp4j.debug.util;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;


/**
* Helps with the construction of good {@link Object#toString()} representations.
* <p>You can customize the output using the builder-style methods {@link ToStringBuilder#singleLine()} {@link ToStringBuilder#skipNulls()} and {@link ToStringBuilder#hideFieldNames()}.</p>
* <p>You can either directly list fields to include via {@link ToStringBuilder#add(String, Object)} and {@link ToStringBuilder#add(Object)}
* or you can let the builder do it automatically using reflection, either including the fields declared in this class or including all superclasses.</p>
* <p>The builder will automatically handle cycles in the object tree. It also pretty prints arrays and Iterables.</p>
*
* This class is not thread safe.
* @since 2.7
*/
public final class ToStringBuilder {

public static class ToStringContext {

public final static ToStringContext INSTANCE = new ToStringContext();

private final static ThreadLocal<IdentityHashMap<Object, Boolean>> currentlyProcessed = new ThreadLocal<IdentityHashMap<Object, Boolean>>() {
@Override
public IdentityHashMap<Object, Boolean> initialValue() {
return new IdentityHashMap<Object, Boolean>();
}
};

public boolean startProcessing(final Object obj) {
return ToStringContext.currentlyProcessed.get().put(obj, Boolean.TRUE) == null;
}

public void endProcessing(final Object obj) {
ToStringContext.currentlyProcessed.get().remove(obj);
}
}

private static ToStringContext toStringContext = ToStringContext.INSTANCE;

private final Object instance;

private final String typeName;

private boolean multiLine = true;

private boolean skipNulls = false;

private boolean showFieldNames = true;

private boolean prettyPrint = true;

private final List<Part> parts = new ArrayList<Part>();

/**
* Creates a new ToStringBuilder for the given object. If you don't use reflection, then this instance
* is only used for obtaining its classes' simple name.
*
* @param instance the object to convert to a String
*/
public ToStringBuilder(final Object instance) {
this.instance = instance;
this.typeName = instance.getClass().getSimpleName();
}

/**
* Fields are printed on a single line, separated by commas instead of newlines
* @return this
*/
public ToStringBuilder singleLine() {
this.multiLine = false;
return this;
}

/**
* Fields with null values will be excluded from the output
* @return this
*/
public ToStringBuilder skipNulls() {
this.skipNulls = true;
return this;
}

/**
* Field names will not be included in the output. Useful for small classes.
* @return this
*/
public ToStringBuilder hideFieldNames() {
this.showFieldNames = false;
return this;
}

/**
* By default, Iterables, Arrays and multiline Strings are pretty-printed.
* Switching to their normal representation makes the toString method significantly faster.
* @since 2.9
* @return this
*/
public ToStringBuilder verbatimValues() {
this.prettyPrint = false;
return this;
}

/**
* Adds all fields declared directly in the object's class to the output
* @return this
*/
public ToStringBuilder addDeclaredFields() {
Field[] fields = instance.getClass().getDeclaredFields();
for(Field field : fields) {
addField(field);
}
return this;
}

/**
* Adds all fields declared in the object's class and its superclasses to the output.
* @return this
*/
public ToStringBuilder addAllFields() {
List<Field> fields = getAllDeclaredFields(instance.getClass());
for(Field field : fields) {
addField(field);
}
return this;
}

/**
* @param fieldName the name of the field to add to the output using reflection
* @return this
*/
public ToStringBuilder addField(final String fieldName) {
List<Field> fields = getAllDeclaredFields(instance.getClass());
for(Field field : fields) {
if(fieldName.equals(field.getName())) {
addField(field);
break;
}
}
return this;
}

private ToStringBuilder addField(final Field field) {
if (!Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
try {
add(field.getName(), field.get(instance));
} catch(IllegalAccessException e) {
sneakyThrow(e);
}
}
return this;
}

@SuppressWarnings("unchecked")
private static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
throw (T) t;
}

/**
* @param value the value to add to the output
* @param fieldName the field name to list the value under
* @return this
*/
public ToStringBuilder add(final String fieldName, final Object value) {
return addPart(fieldName, value);
}

/**
* @param value the value to add to the output without a field name
* @return this
*/
public ToStringBuilder add(final Object value) {
return addPart(value);
}

private Part addPart() {
final Part p = new Part();
this.parts.add(p);
return p;
}

private ToStringBuilder addPart(final Object value) {
final Part p = this.addPart();
p.value = value;
return this;
}

private ToStringBuilder addPart(final String fieldName, final Object value) {
final Part p = this.addPart();
p.fieldName = fieldName;
p.value = value;
return this;
}

/**
* @return the String representation of the processed object
*/
@Override
public String toString() {
boolean startProcessing = ToStringBuilder.toStringContext.startProcessing(this.instance);
if (!startProcessing) {
return this.toSimpleReferenceString(this.instance);
}
try {
final IndentationAwareStringBuilder builder = new IndentationAwareStringBuilder();
builder.append(typeName).append(" ");
builder.append("[");
String nextSeparator = "";
if (multiLine) {
builder.increaseIndent();
}
for (Part part : parts) {
if (!skipNulls || part.value != null) {
if (multiLine) {
builder.newLine();
} else {
builder.append(nextSeparator);
nextSeparator = ", ";
}
if (part.fieldName != null && this.showFieldNames) {
builder.append(part.fieldName).append(" = ");
}
this.internalToString(part.value, builder);
}
}
if (multiLine) {
builder.decreaseIndent().newLine();
}
builder.append("]");
return builder.toString();
} finally {
ToStringBuilder.toStringContext.endProcessing(this.instance);
}
}

private void internalToString(final Object object, final IndentationAwareStringBuilder sb) {
if (prettyPrint) {
if (object instanceof Iterable<?>) {
serializeIterable((Iterable<?>)object, sb);
} else if (object instanceof Object[]) {
sb.append(Arrays.toString((Object[])object));
} else if (object instanceof byte[]) {
sb.append(Arrays.toString((byte[])object));
} else if (object instanceof char[]) {
sb.append(Arrays.toString((char[])object));
} else if (object instanceof int[]) {
sb.append(Arrays.toString((int[])object));
} else if (object instanceof boolean[]) {
sb.append(Arrays.toString((boolean[])object));
} else if (object instanceof long[]) {
sb.append(Arrays.toString((long[])object));
} else if (object instanceof float[]) {
sb.append(Arrays.toString((float[])object));
} else if (object instanceof double[]) {
sb.append(Arrays.toString((double[])object));
} else if (object instanceof CharSequence) {
sb.append("\"").append(((CharSequence)object).toString().replace("\n", "\\n").replace("\r", "\\r")).append("\"");
} else if (object instanceof Enum<?>) {
sb.append(((Enum<?>)object).name());
} else {
sb.append(String.valueOf(object));
}
} else {
sb.append(String.valueOf(object));
}
}

private void serializeIterable(final Iterable<?> object, final IndentationAwareStringBuilder sb) {
final Iterator<?> iterator = object.iterator();
sb.append(object.getClass().getSimpleName()).append(" (");
if (multiLine) {
sb.increaseIndent();
}
boolean wasEmpty = true;
while (iterator.hasNext()) {
wasEmpty = false;
if (multiLine) {
sb.newLine();
}
this.internalToString(iterator.next(), sb);
if (iterator.hasNext()) {
sb.append(",");
}
}
if (multiLine) {
sb.decreaseIndent();
}
if (!wasEmpty && this.multiLine) {
sb.newLine();
}
sb.append(")");
}

private String toSimpleReferenceString(final Object obj) {
String simpleName = obj.getClass().getSimpleName();
int identityHashCode = System.identityHashCode(obj);
return simpleName + "@" + Integer.valueOf(identityHashCode);
}

private List<Field> getAllDeclaredFields(final Class<?> clazz) {
final ArrayList<Field> result = new ArrayList<>();

for(Class<?> current = clazz; current != null; current = current.getSuperclass()) {
Field[] declaredFields = current.getDeclaredFields();
result.addAll(Arrays.asList(declaredFields));

}
return result;
}

private static final class Part {
private String fieldName;
private Object value;
}

private static class IndentationAwareStringBuilder {
private final StringBuilder builder = new StringBuilder();

private final String indentationString = " ";

private final String newLineString = "\n";

private int indentation = 0;

public IndentationAwareStringBuilder increaseIndent() {
indentation++;
return this;
}

public IndentationAwareStringBuilder decreaseIndent() {
indentation--;
return this;
}

public IndentationAwareStringBuilder append(final CharSequence string) {
if (indentation > 0) {
String indented = string.toString().replace(
newLineString,
newLineString + repeat(indentationString, indentation)
);
builder.append(indented);
} else {
builder.append(string);
}
return this;
}

public IndentationAwareStringBuilder newLine() {
builder.append(newLineString).
append(repeat(this.indentationString, this.indentation));
return this;
}

@Override
public String toString() {
return this.builder.toString();
}

private String repeat(String string, int count) {
StringBuilder result = new StringBuilder();
for(int i=0; i < count; i++) {
result.append(string);
}
return result.toString();
}
}
}
Loading

0 comments on commit 086ffce

Please sign in to comment.