I have many types of maps in my method. I've tried to make a mapToString() method to handle them, but some of my maps have interfaces as their key value.
public <T> String interfaceToString(Class<T> clazz, Object instance) {
log.info(clazz + "");
StringBuilder toString = new StringBuilder("[ " + clazz.getSimpleName() + " - ");
List<Method> methods = Arrays.asList(clazz.getDeclaredMethods());
methods.forEach(method -> {
try {
toString.append(method.getName() + ": " + method.invoke(instance) + ", ");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
toString.replace(toString.length() - 2, toString.length() - 1, "");
toString.append("]");
return toString.toString();
}
public <T> String mapToString(Map<T, ?> map) {
StringBuilder toString = new StringBuilder("[ ");
for (T key : map.keySet()) {
Object value = map.get(key);
if (value.getClass().isInterface()) {
toString.append(interfaceToString(value.getClass(), value));
} else {
toString.append("[" + key + ", " + value + "] ");
}
}
toString.append(" ]");
return toString.toString();
}
It works for regular maps, but it just prints the object representation for map's whose key values are interfaces. I'm working with many interfaces, because I am using Spring JPA Projections to pull back partial parts of my database tables.
When I was debugging, when I logged value.getClass() in the mapToString, it's class was a Proxy. Which is why the isInterface() check failed when appending to the StringBuilder.
Is there a better way to do this?
EDIT
Here is my interface. It's a Spring JPA repository projection.
public interface OrderGetOpenReceievablesAndHistoryView {
Integer getNo();
String getBillchCode();
String getBilltlCode();
}
Here is my map:
Map<Integer, OrderGetOpenReceievablesAndHistoryView> carrierOrders = new HashMap<>();
I am assigning the map's key as an Integer and it's value as OrderGetOpenReceievablesAndHistoryView.
carrierOrders = orderRepository.findOpenRecievablesHistoryViewByNoIn(carrierOrderNos).stream().collect(Collectors.toMap(OrderGetOpenReceievablesAndHistoryView::getNo, Function.identity()));
I want to print the OrderGetOpenReceievablesAndHistoryView values as a normal String from the interfaceToString() method.
EDIT2
So, what I really want is a method to print out a map whose values are an interface. If I do this, it prints it out fine:
for (Integer key : carrierOrders.keySet()) {
OrderGetOpenReceievablesAndHistoryView value = carrierOrders.get(key);
log.info("{} : {}, {}, {}", key, value.getBillchCode(), value.getBilltlCode(), value.getNo());
}
Since I'm working with potentially hundreds of maps whose values could be anything, I don't want to have to write this loop everytime I want to print them.
Here is an output from the regular for each loop:
104432581 : TAXZ443237, HJMU499371, 104432581
Here is an output from the mapToString method():
[104406075, org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap@750bef00]
Every object is an instance of a concrete class, so value.getClass().isInterface()
will never evaluate to true
. When you want a special treatment for an interface type known at compile time, i.e. the map’s value type as declared in the generic type, you have to pass it as an argument to the method.
E.g.
public static <V> String mapToString(Map<?,V> map, Class<? super V> valueType) {
StringBuilder toString = new StringBuilder("[ ");
for(Map.Entry<?,V> entry: map.entrySet()) {
toString.append(entry.getKey()).append(": ");
if(!valueType.isInterface()) toString.append(entry.getValue());
else appendInterface(toString, valueType, entry.getValue());
toString.append(", ");
}
if(toString.length() > 2) toString.setLength(toString.length() - 2);
return toString.append(" ]").toString();
}
private static <V> void appendInterface(
StringBuilder toString, Class<V> valueType, V value) {
toString.append("[ ").append(valueType.getSimpleName()).append(" - ");
for(Method m: valueType.getDeclaredMethods()) {
if(!Modifier.isStatic(m.getModifiers()) && m.getParameterCount() == 0) {
Object o;
try {
o = m.invoke(value);
} catch(ReflectiveOperationException e) {
e.printStackTrace();
continue;
}
toString.append(m.getName()).append(": ").append(o).append(", ");
}
}
toString.replace(toString.length() - 2, toString.length(), "]");
}
Which you can invoke like
Map<Integer, OrderGetOpenReceievablesAndHistoryView> carrierOrders = new HashMap<>();
String s = mapToString(carrierOrders, OrderGetOpenReceievablesAndHistoryView.class);