package edu.stanford.nlp.util.logging;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import edu.stanford.nlp.util.logging.Redwood.RedwoodChannels;
import edu.stanford.nlp.util.Generics;
/**
* Primarily for debugging, PrettyLogger helps you dump various collection
* objects in a reasonably structured way via Redwood logging. It has support
* for many built in collection types (Mapping, Iterable, arrays, Properties) as
* well as anything that implements PrettyLoggable.
*
* @see PrettyLoggable
* @author David McClosky
* @author Gabor Angeli (+ primitive arrays; dictionaries)
*/
// TODO Should have an optional maximum depth, perhaps
public class PrettyLogger {
private static final RedwoodChannels DEFAULT_CHANNELS = new RedwoodChannels(Redwood.FORCE);
/**
* Static class.
*/
private PrettyLogger() {}
/*
* Main entry methods and utilities
*/
/**
* Pretty log an object. It will be logged to the default channel. Its class
* name will be used as a description.
*
* @param obj
* the object to be pretty logged
*/
public static void log(Object obj) {
log(obj.getClass().getSimpleName(), obj);
}
/**
* Pretty log an object along with its description. It will be logged to the
* default channel.
*
* @param description
* denote the object in the logs (via a track name, etc.).
* @param obj
* the object to be pretty logged
*/
public static void log(String description, Object obj) {
log(DEFAULT_CHANNELS, description, obj);
}
/**
* Pretty log an object. Its class name will be used as a description.
*
* @param channels
* the channels to pretty log to
* @param obj
* the object to be pretty logged
*/
public static void log(RedwoodChannels channels, Object obj) {
log(channels, obj.getClass().getSimpleName(), obj);
}
/**
* Pretty log an object.
*
* @param channels
* the channels to pretty log to
* @param description
* denote the object in the logs (via a track name, etc.).
* @param obj
* the object to be pretty logged
*/
// TODO perhaps some reflection magic can simplify this process?
@SuppressWarnings("unchecked")
public static <T> void log(RedwoodChannels channels, String description, Object obj) {
if (obj instanceof Map) {
log(channels, description, (Map)obj);
} else if (obj instanceof PrettyLoggable) {
((PrettyLoggable) obj).prettyLog(channels, description);
} else if (obj instanceof Dictionary) {
log(channels, description, (Dictionary)obj);
} else if (obj instanceof Iterable) {
log(channels, description, (Iterable)obj);
} else if (obj.getClass().isArray()) {
Object[] arrayCopy; // the array to log
if(obj.getClass().getComponentType().isPrimitive()){
//(case: a primitive array)
Class componentClass = obj.getClass().getComponentType();
if(boolean.class.isAssignableFrom(componentClass)){
arrayCopy = new Object[((boolean[]) obj).length];
for(int i=0; i<arrayCopy.length; i++){ arrayCopy[i] = ((boolean[]) obj)[i]; }
} else if(byte.class.isAssignableFrom(componentClass)){
arrayCopy = new Object[((byte[]) obj).length];
for(int i=0; i<arrayCopy.length; i++){ arrayCopy[i] = ((byte[]) obj)[i]; }
} else if(char.class.isAssignableFrom(componentClass)){
arrayCopy = new Object[((char[]) obj).length];
for(int i=0; i<arrayCopy.length; i++){ arrayCopy[i] = ((char[]) obj)[i]; }
} else if(short.class.isAssignableFrom(componentClass)){
arrayCopy = new Object[((short[]) obj).length];
for(int i=0; i<arrayCopy.length; i++){ arrayCopy[i] = ((short[]) obj)[i]; }
} else if(int.class.isAssignableFrom(componentClass)){
arrayCopy = new Object[((int[]) obj).length];
for(int i=0; i<arrayCopy.length; i++){ arrayCopy[i] = ((int[]) obj)[i]; }
} else if(long.class.isAssignableFrom(componentClass)){
arrayCopy = new Object[((long[]) obj).length];
for(int i=0; i<arrayCopy.length; i++){ arrayCopy[i] = ((long[]) obj)[i]; }
} else if(float.class.isAssignableFrom(componentClass)){
arrayCopy = new Object[((float[]) obj).length];
for(int i=0; i<arrayCopy.length; i++){ arrayCopy[i] = ((float[]) obj)[i]; }
} else if(double.class.isAssignableFrom(componentClass)){
arrayCopy = new Object[((double[]) obj).length];
for(int i=0; i<arrayCopy.length; i++){ arrayCopy[i] = ((double[]) obj)[i]; }
} else {
throw new IllegalStateException("I forgot about the primitive class: " + componentClass);
}
} else {
//(case: a regular array)
arrayCopy = (T[]) obj;
}
log(channels, description, arrayCopy);
} else {
if (!description.equals("")) {
description += ": ";
}
channels.log(description + obj);
}
}
/**
* Returns true if an object has special logic for pretty logging (e.g.
* implements PrettyLoggable). If so, we ask it to pretty log itself. If not,
* we can safely use its toString() in logs.
* @param obj The object to test
* @return true if the object is dispatchable
*/
@SuppressWarnings("unchecked")
public static boolean dispatchable(Object obj) {
if (obj == null) {
return false;
}
return obj instanceof PrettyLoggable ||
obj instanceof Map ||
obj instanceof Dictionary ||
obj instanceof Iterable ||
obj.getClass().isArray();
}
/*
* Mappings
*/
private static <K, V> void log(RedwoodChannels channels, String description, Map<K, V> mapping) {
Redwood.startTrack(description);
if (mapping == null) {
channels.log("(mapping is null)");
} else if (mapping.isEmpty()) {
channels.log("(empty)");
} else {
// convert keys to sorted list, if possible
List<K> keys = new LinkedList<>();
for (K key : mapping.keySet()) {
keys.add(key);
}
Collections.sort(keys, (a, b) -> {
if (a != null && Comparable.class.isAssignableFrom(a.getClass())) {
return ((Comparable) a).compareTo(b);
} else {
return 0;
}
});
// log key/value pairs
int entryCounter = 0;
for (K key : keys) {
V value = mapping.get(key);
if (!dispatchable(key) && dispatchable(value)) {
log(channels, key.toString(), value);
} else if (dispatchable(key) || dispatchable(value)) {
Redwood.startTrack("Entry " + entryCounter);
if (dispatchable(key)) {
log(channels, "Key", key);
} else {
channels.logf("Key %s", key);
}
if (dispatchable(value)) {
log(channels, "Value", value);
} else {
channels.logf("Value %s", value);
}
Redwood.endTrack("Entry " + entryCounter);
} else {
channels.logf("%s = %s", key, value);
}
entryCounter++;
}
}
Redwood.endTrack(description);
}
/*
* Dictionaries (notably, Properties) -- convert them to Maps and dispatch
*/
private static <K,V> void log(RedwoodChannels channels, String description, Dictionary<K,V> dict) {
//(a real data structure)
Map<K, V> map = Generics.newHashMap();
//(copy to map)
Enumeration<K> keys = dict.keys();
while(keys.hasMoreElements()){
K key = keys.nextElement();
V value = dict.get(key);
map.put(key,value);
}
//(log like normal)
log(channels, description, map);
}
/*
* Iterables (includes Collection, List, Set, etc.)
*/
private static <T> void log(RedwoodChannels channels, String description, Iterable<T> iterable) {
Redwood.startTrack(description);
if (iterable == null) {
channels.log("(iterable is null)");
} else {
int index = 0;
for (T item : iterable) {
if (dispatchable(item) && item != iterable) {
log(channels, "Index " + index, item);
} else {
channels.logf("Index %d: %s", index, item == iterable ? "...<infinite loop>" : item);
}
index++;
}
if (index == 0) {
channels.log("(empty)");
}
}
Redwood.endTrack(description);
}
/*
* Arrays
*/
private static <T> void log(RedwoodChannels channels, String description, T[] array) {
Redwood.startTrack(description);
if (array == null) {
channels.log("(array is null)");
} else if (array.length == 0) {
channels.log("(empty)");
} else {
int index = 0;
for (T item : array) {
if (dispatchable(item)) {
log(channels, "Index " + index, item);
} else {
channels.logf("Index %d: %s", index, item);
}
index++;
}
}
Redwood.endTrack(description);
}
}