Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.fiolino.common.var.ObjectToStringConverter Maven / Gradle / Ivy
Go to download
General structure to easily create dynamic logic via MethodHandles and others.
package org.fiolino.common.var;
import org.fiolino.common.util.Strings;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.DateFormat;
import java.util.*;
final class ObjectToStringConverter {
private static final int INVISIBLE_MODIFIERS = Modifier.STATIC | Modifier.TRANSIENT;
private static final Map, MethodHandle> converterMethods;
static {
MethodHandles.Lookup lookup = MethodHandles.lookup();
converterMethods = new HashMap<>();
for (Method m : lookup.lookupClass().getDeclaredMethods()) {
if (m.isAnnotationPresent(Appender.class)) {
Class>[] argTypes = m.getParameterTypes();
if (argTypes.length < 2) {
throw new AssertionError("Method " + m + " should have at least a parameter for the string builder and the conversion object");
}
MethodHandle handle;
try {
handle = lookup.unreflect(m);
} catch (IllegalAccessException ex) {
throw new AssertionError("Method " + m + " cannot be accessed.", ex);
}
if (argTypes.length == 2) {
handle = MethodHandles.dropArguments(handle, 3, int.class);
}
converterMethods.put(argTypes[1], handle);
}
}
}
private final Set alreadyVisited;
private final int depth;
private final int maxLength;
ObjectToStringConverter(int depth, int maxLength) {
this.depth = depth;
this.maxLength = maxLength;
alreadyVisited = new HashSet<>();
}
StringBuilder append(StringBuilder stringBuilder, Object toAppend) {
return append(stringBuilder, toAppend, 0);
}
private StringBuilder append(StringBuilder stringBuilder, Object toAppend, int indent) {
if (toAppend == null) {
return stringBuilder.append("");
}
try {
MethodHandle handle = fetchConverterMethodFor(toAppend);
if (handle != null) {
handle.invoke(this, stringBuilder, toAppend, indent);
return stringBuilder;
}
Class> c = toAppend.getClass();
if (depth > 0) {
if (c.isArray()) {
appendArray(stringBuilder, toAppend, indent);
return stringBuilder;
}
if (c.getPackage().getName().startsWith("java")) {
return stringBuilder.append('(').append(c.getSimpleName()).append(')')
.append(toAppend.toString());
}
if (!alreadyVisited.add(toAppend)) {
return stringBuilder.append(" ');
}
stringBuilder.append(c.getName()).append(": ");
appendFields(stringBuilder, toAppend, c, indent);
return indent(stringBuilder, indent);
} else {
return stringBuilder.append('(').append(c.getSimpleName()).append(')')
.append(toAppend.toString());
}
} catch (Throwable ex) {
return stringBuilder.append("<<").append(ex.getMessage()).append(">>");
}
}
private void appendFields(StringBuilder stringBuilder, Object toAppend, Class> clazz, int indent) {
Class> currentClass = clazz;
do {
for (Field f : currentClass.getDeclaredFields()) {
appendField(stringBuilder, toAppend, f, indent);
}
currentClass = currentClass.getSuperclass();
} while (currentClass != null);
}
private void appendField(StringBuilder stringBuilder, Object toAppend, Field f, int indent) {
Debugged debugState = f.getAnnotation(Debugged.class);
int m = f.getModifiers();
if (debugState == null) {
if ((m & INVISIBLE_MODIFIERS) != 0) {
return;
}
} else if (!debugState.value()) {
return;
}
try {
f.setAccessible(true);
Object x2 = f.get(toAppend);
if (x2 != null || debugState != null && debugState.printNull()) {
indent(stringBuilder, indent + 1);
appendModifier(stringBuilder, m);
stringBuilder.append(f.getName()).append('=');
if (f.getType().isPrimitive()) {
stringBuilder.append(x2).append(';');
} else {
append(stringBuilder, x2, indent + 1).append(';');
}
}
} catch (IllegalAccessException ex) {
indent(stringBuilder, indent + 1);
stringBuilder.append('<').append(f).append(" not accessible>");
}
}
private void appendModifier(StringBuilder stringBuilder, int modifierFlags) {
if ((modifierFlags & Modifier.TRANSIENT) != 0) {
stringBuilder.append('~');
}
if ((modifierFlags & Modifier.VOLATILE) != 0) {
stringBuilder.append('^');
}
if ((modifierFlags & Modifier.FINAL) != 0) {
stringBuilder.append('!');
}
}
private MethodHandle fetchConverterMethodFor(Object object) {
MethodHandle handle = converterMethods.get(object.getClass());
if (handle != null) return handle;
for (Map.Entry, MethodHandle> e : converterMethods.entrySet()) {
if (e.getKey().isInstance(object)) {
return e.getValue();
}
}
return null;
}
private StringBuilder indent(StringBuilder sb, int tabs) {
sb.append('\n');
for (int i = tabs; i > 0; i--) {
sb.append(" ");
}
return sb;
}
@SuppressWarnings("unused")
@Appender
private void appendDate(StringBuilder sb, Date toConvert) {
sb.append(DateFormat.getDateTimeInstance().format(toConvert));
}
@SuppressWarnings("unused")
@Appender
private void appendEnum(StringBuilder sb, Enum> toAppend) {
sb.append(toAppend.getDeclaringClass().getSimpleName()).append('.')
.append(toAppend.toString());
}
@SuppressWarnings("unused")
@Appender
private void appendCollection(StringBuilder sb, Collection> toConvert, int indent) {
int n = toConvert.size();
sb.append('<').append(n).append(">[");
int count = 0;
boolean needsComma = false;
for (Object x2 : toConvert) {
count++;
if (count > maxLength) {
sb.append("... (").append(n - maxLength).append(" more)");
break;
}
if (needsComma) {
sb.append(',');
} else {
needsComma = true;
}
append(sb, x2, indent + 1);
}
sb.append(']');
}
@SuppressWarnings("unused")
@Appender
private void appendMap(StringBuilder sb, Map toConvert, int indent) {
int n = toConvert.size();
sb.append('<').append(n).append(">{");
boolean needsComma = false;
int count = 0;
for (Map.Entry, ?> e : ((Map, ?>) toConvert).entrySet()) {
count++;
if (count > maxLength) {
sb.append("... (").append(n - maxLength).append(" more)");
break;
}
if (needsComma) {
sb.append(',');
} else {
needsComma = true;
}
append(
sb.append(e.getKey()).append('='),
e.getValue(),
indent + 1);
}
sb.append('}');
}
private void appendArray(StringBuilder sb, Object toConvert, int indent) {
int n = Array.getLength(toConvert);
sb.append('<').append(n).append(">[");
boolean needsComma = false;
for (int i = 0; i < n; i++) {
if (i > maxLength) {
sb.append("... (").append(n - maxLength).append(" more)");
break;
}
if (needsComma) {
sb.append(',');
} else {
needsComma = true;
}
if (toConvert instanceof Object[]) {
append(
sb,
Array.get(toConvert, i),
indent + 1);
} else {
sb.append(Array.get(toConvert, i));
}
}
sb.append(']');
}
@SuppressWarnings("unused")
@Appender
private void appendString(StringBuilder sb, String toConvert) {
Strings.appendQuotedString(sb, toConvert);
}
@SuppressWarnings("unused")
@Appender
private void appendClass(StringBuilder sb, Class> toConvert, int indent) {
if (toConvert.isArray()) {
append(
sb,
toConvert.getComponentType(),
indent).append("[]");
} else {
sb.append(toConvert.getName());
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
private @interface Appender {
}
}