/*
* Copyright 2011 JBoss, by Red Hat, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.errai.tools.monitoring;
import org.mvel2.util.ParseTools;
import org.mvel2.util.StringAppender;
import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static javax.swing.SwingUtilities.invokeLater;
import static org.jboss.errai.tools.monitoring.UiHelper.createIconEntry;
import static org.mvel2.util.ParseTools.boxPrimitive;
public class ObjectExplorer extends JTree {
private DefaultMutableTreeNode rootNode;
private Object root;
private Map<Object, Runnable> deferred = new ConcurrentHashMap<Object, Runnable>();
private static Map<Class, ValRenderer> renderers = new HashMap<Class, ValRenderer>();
public ObjectExplorer() {
setCellRenderer(new MonitorTreeCellRenderer());
buildTree();
addTreeExpansionListener(new TreeExpansionListener() {
public void treeExpanded(TreeExpansionEvent event) {
Runnable r = deferred.get(event.getPath().getLastPathComponent());
if (r != null) r.run();
}
public void treeCollapsed(TreeExpansionEvent event) {
}
});
}
public void setRoot(Object root) {
this.root = root;
}
public void buildTree() {
DefaultTreeModel model = (DefaultTreeModel) getModel();
rootNode = null;
if (root != null) {
model.setRoot(nestObject(this, null, "<Root>", root));
}
else {
model.setRoot(createIconEntry("database.png", ""));
}
model.reload();
}
public static DefaultMutableTreeNode nestObject(final ObjectExplorer explorer, DefaultMutableTreeNode node,
String field, Object v) {
if (v == null) return null;
Class cls = v != null ? v.getClass() : Object.class;
DefaultMutableTreeNode nNode = createClassNode(field, v);
if (node == null) {
DefaultMutableTreeNode temp = new DefaultMutableTreeNode();
if (renderFieldByType(explorer, temp, field, cls, v)) {
pullNodesUp(nNode, temp);
}
else {
DefaultMutableTreeNode n = (DefaultMutableTreeNode) temp.getChildAt(0);
((JLabel) n.getUserObject()).setIcon(UiHelper.getSwIcon("class.png"));
return n;
}
}
else {
renderFields(explorer, nNode, v != null ? v.getClass() : Object.class, v);
node.add(nNode);
}
return nNode;
}
public static DefaultMutableTreeNode createClassNode(String field, Object v) {
return createIconEntry("class.png", field + " {" + (v == null ? "null" : v.getClass().getName()) + "} = " + v);
}
private static void pullNodesUp(DefaultMutableTreeNode newNode, DefaultMutableTreeNode oldNode) {
while (oldNode.getChildCount() != 0) {
newNode.add((MutableTreeNode) oldNode.getChildAt(0));
}
}
public static void renderFields(final ObjectExplorer explorer, final DefaultMutableTreeNode node, final Class clazz, final Object v) {
if (clazz == null) return;
if (clazz.isPrimitive()) {
renderFieldByType(explorer, node, "val", PrimitiveMarker.class, v);
return;
}
final DefaultMutableTreeNode placeholder = new DefaultMutableTreeNode("<...>");
node.add(placeholder);
Runnable r = new Runnable() {
public void run() {
if (!node.isNodeChild(placeholder)) {
return;
}
node.remove(placeholder);
if (clazz.getSuperclass() != Object.class) {
renderFields(explorer, node, clazz.getSuperclass(), v);
}
for (Field fld : clazz.getDeclaredFields()) {
if ((fld.getModifiers() & Modifier.STATIC) != 0) continue;
fld.setAccessible(true);
renderField(explorer, node, fld, v);
}
invokeLater(new Runnable() {
public void run() {
((DefaultTreeModel) explorer.getModel()).reload(node);
}
});
explorer.deferred.remove(node);
}
};
if (explorer.rootNode == null) {
explorer.rootNode = node;
r.run();
}
else {
explorer.deferred.put(node, r);
}
}
public static void renderField(ObjectExplorer explorer, DefaultMutableTreeNode node, Field fld, Object v) {
Object val;
try {
val = fld.get(v);
}
catch (Throwable t) {
return;
}
DefaultMutableTreeNode tempNode = new DefaultMutableTreeNode();
if (renderFieldByType(explorer, tempNode, fld.getName(), fld.getType(), val)) {
DefaultMutableTreeNode newNode = createClassNode(fld.getName(), val);
pullNodesUp(newNode, tempNode);
node.add(newNode);
}
else {
pullNodesUp(node, tempNode);
}
}
public static boolean renderFieldByType(ObjectExplorer explorer, DefaultMutableTreeNode node,
String field, Class clazz, Object v) {
if (clazz.isArray()) {
clazz = ArrayMarker.class;
}
if (!renderers.containsKey(clazz)) {
if (!_scanClassHeirarchy(clazz, clazz)) {
clazz = Object.class;
}
}
return renderers.get(clazz).render(explorer, node, field, v);
}
public static boolean _scanClassHeirarchy(Class clazz, Class root) {
if (clazz.isPrimitive()) {
renderers.put(clazz, renderers.get(PrimitiveMarker.class));
return true;
}
do {
if (renderers.containsKey(root)) {
renderers.put(boxPrimitive(clazz), renderers.get(root));
return true;
}
for (Class iface : root.getInterfaces()) {
if (_scanClassHeirarchy(clazz, iface)) return true;
}
} while ((root = root.getSuperclass()) != null);
return false;
}
static {
renderers.put(CharSequence.class, new ValRenderer() {
public boolean render(ObjectExplorer explorer, DefaultMutableTreeNode node, String name, Object val) {
node.add(createIconEntry("field.png", fieldLabel(name, val)));
return false;
}
});
renderers.put(ArrayMarker.class, new ValRenderer() {
public boolean render(ObjectExplorer explorer, DefaultMutableTreeNode node, String name, Object val) {
if (val == null) {
node.add(createIconEntry("field.png", fieldLabel(name, "null")));
return false;
}
int length = Array.getLength(val);
Object o;
for (int i = 0; i < length; i++) {
o = Array.get(val, i);
nestObject(explorer, node, String.valueOf(i), o);
}
return true;
}
});
renderers.put(Collection.class, new ValRenderer() {
public boolean render(ObjectExplorer explorer, DefaultMutableTreeNode node, String name, Object val) {
if (val == null) {
node.add(createIconEntry("field.png", fieldLabel(name, "null")));
return false;
}
int i = 0;
for (Object o : (Collection) val) {
nestObject(explorer, node, String.valueOf(i++), o);
}
return true;
}
});
renderers.put(Map.class, new ValRenderer() {
public boolean render(ObjectExplorer explorer, DefaultMutableTreeNode node, String name, Object val) {
if (val == null) {
node.add(createIconEntry("field.png", fieldLabel(name, "null")));
return false;
}
else {
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) val).entrySet()) {
nestObject(explorer, node, String.valueOf(entry.getKey()), entry);
}
return true;
}
}
});
renderers.put(Object.class, new ValRenderer() {
public boolean render(ObjectExplorer explorer, DefaultMutableTreeNode node, String name, Object val) {
nestObject(explorer, node, name, val);
return false;
}
});
ValRenderer boxedPrimRenderer = new ValRenderer() {
public boolean render(ObjectExplorer explorer, DefaultMutableTreeNode node, String name, Object val) {
node.add(createIconEntry("field.png", fieldLabel(name, val)));
return false;
}
};
renderers.put(Integer.class, boxedPrimRenderer);
renderers.put(Long.class, boxedPrimRenderer);
renderers.put(Character.class, boxedPrimRenderer);
renderers.put(Byte.class, boxedPrimRenderer);
renderers.put(Short.class, boxedPrimRenderer);
renderers.put(Double.class, boxedPrimRenderer);
renderers.put(Boolean.class, boxedPrimRenderer);
renderers.put(Float.class, boxedPrimRenderer);
renderers.put(PrimitiveMarker.class, new ValRenderer() {
public boolean render(ObjectExplorer explorer, DefaultMutableTreeNode node, String name, Object val) {
node.add(createIconEntry("field.png", fieldLabelPrimitive(name, val)));
return false;
}
});
}
static String fieldLabel(String name, Object v) {
if (v == null) {
return name + " = null";
}
return name + " {" + friendlyClassName(v) + "@" + v.hashCode() + "} = " + friendlyValue(v);
}
static String fieldLabelPrimitive(String name, Object v) {
if (v == null) {
return name + " = null";
}
Class c = v.getClass();
return name + " {" + friendlyClassName(ParseTools.unboxPrimitive(c), v) + "} = " + friendlyValue(v);
}
static String friendlyClassName(Object v) {
if (v == null) return "null";
return friendlyClassName(v.getClass(), v);
}
static String friendlyClassName(Class cls, Object v) {
if (cls.isPrimitive()) {
if (cls == char[].class) return "char[" + Array.getLength(v) + "]";
else if (cls == int[].class) return "int[" + Array.getLength(v) + "]";
else if (cls == long[].class) return "long[" + Array.getLength(v) + "]";
else if (cls == double[].class) return "double[" + Array.getLength(v) + "]";
else if (cls == short[].class) return "short[" + Array.getLength(v) + "]";
else if (cls == float[].class) return "float[" + Array.getLength(v) + "]";
else if (cls == boolean[].class) return "boolean[" + Array.getLength(v) + "]";
else if (cls == byte[].class) return "byte[" + Array.getLength(v) + "]";
}
else if (cls.isArray()) {
return cls.getComponentType().getName() + "[" + Array.getLength(v) + "]";
}
else if (Collection.class.isAssignableFrom(cls)) {
return cls.getName() + " [" + ((Collection) v).size() + "]";
}
return cls.getName();
}
static String friendlyValue(Object v) {
if (v == null) return "null";
Class cls = v.getClass();
if (cls.isArray()) {
Class comp = cls.getComponentType();
if (comp == char.class) {
return "\"" + (Array.getLength(v) < 50 ? new String((char[]) v) : new String((char[]) v, 0, 50) + "...") + "\"";
}
else {
StringAppender appender = new StringAppender();
int len = Array.getLength(v);
appender.append("[");
for (int i = 0; i < len && i < 25; ) {
appender.append(String.valueOf(Array.get(v, i)));
if (++i < len && i < 25) appender.append(", ");
}
return appender.append("]").toString();
}
}
else if (cls == Character.class) {
return "'" + String.valueOf(v) + "'";
}
return "\"" + String.valueOf(v) + "\"";
}
static class ArrayMarker {
}
static class PrimitiveMarker {
}
}