package jadex.rules.tools.stateviewer;
import jadex.commons.SGUI;
import jadex.commons.SUtil;
import jadex.rules.state.IOAVState;
import jadex.rules.state.IOAVStateListener;
import jadex.rules.state.OAVAttributeType;
import jadex.rules.state.OAVJavaType;
import jadex.rules.state.OAVObjectType;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.ref.WeakReference;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.swing.JTree;
import javax.swing.Timer;
import javax.swing.UIDefaults;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
/**
* Swing Tree model for an OAV state. Enables displaying an
* oav state in a swing tree.
*/
public class OAVTreeModel implements TreeModel
{
/**
* flag to indicate if java objects should be inspectable in the tree
* TO DO: make configurable via GUI?
*/
protected final static boolean enableObjectInspection = true;
//-------- static part --------
/**
* The image icons.
*/
protected static UIDefaults icons = new UIDefaults(new Object[]
{
// Tab icons.
"object", SGUI.makeIcon(OAVTreeModel.class, "/jadex/rules/tools/stateviewer/images/object.png"),
"attribute", SGUI.makeIcon(OAVTreeModel.class, "/jadex/rules/tools/stateviewer/images/attribute.png"),
"value", SGUI.makeIcon(OAVTreeModel.class, "/jadex/rules/tools/stateviewer/images/value.png"),
"javaobject", SGUI.makeIcon(OAVTreeModel.class, "/jadex/rules/tools/stateviewer/images/bean.png"),
"javaattribute", SGUI.makeIcon(OAVTreeModel.class, "/jadex/rules/tools/stateviewer/images/javaattribute.png"),
"javavalue", SGUI.makeIcon(OAVTreeModel.class, "/jadex/rules/tools/stateviewer/images/value.png")
});
/**
* The list of timers to update the object inspector tree nodes
*/
protected static List timerList;
//-------- attributes --------
/** The root node. */
protected RootNode root;
/** The local copy of the state (synchronized to swing thread). */
protected CopyState copy;
/** The listeners. */
protected Set listeners;
/** The pending notification flag. */
protected boolean notify;
/** Nodes for objects to allow fine-tuned tree redraw (object-id -> {node1, node2, ...}).
* Because the state is a (possibly cyclic) graph, there may be more than one node for a single object! */
protected Map nodes;
/** list for all created Attribute inspector nodes */
protected List inspectors;
// /** Random to generate unique(?) IDs*/
// protected Random rng;
/** UUID counter */
private int uuidcounter;
//-------- constructors --------
/**
* Create new OAV tree model.
* @param id The root object id.
* @param state The OAV state.
* @param showempty Flag, if empty attributes should be shown.
*/
public OAVTreeModel(IOAVState state)
{
// use identity hash for different (java) objects being equal (e.g. empty list).
// todo: mixed identity map like used in state?
this.nodes = new IdentityHashMap();
this.root = new RootNode();
this.inspectors = new ArrayList();
// this.rng = new Random(System.currentTimeMillis());
this.uuidcounter = 0;
Timer refreshTimer = new Timer(5000, new ObjectInspectorRefreshAction(this));
refreshTimer.start();
OAVTreeModel.addRefreshTimer(refreshTimer);
// Todo: create copy state on state thread.
this.copy = new CopyState(state, new SwingSynchronizator());
// could this anonymous inner class result in a cyclic reference that prevents the TreeModel
// to be removed from gc when Introspector plugin is closed?
// model -> copystate -> listener -> model$this0
// |-> state -> listener -> copystate-lister$this0
// ^-<- Agent
// IMHO the OAVTreeModel for the Introspector plugin will be removed only
// if the Agent that was introspected is removed too
copy.addStateListener(new IOAVStateListener()
{
/**
* Notification when an attribute value of an object has been set.
* @param id The object id.
* @param attr The attribute type.
* @param oldvalue The oldvalue.
* @param newvalue The newvalue.
*/
public void objectModified(Object id, OAVObjectType type,
final OAVAttributeType attr, Object oldvalue, Object newvalue)
{
// System.out.println("modified: "+id+"."+attr.getName()+": "+oldvalue+" -> "+newvalue);
Object tmp = nodes.get(id);
if(tmp instanceof ObjectNode)
{
// Find object and attribute node.
ObjectNode node = (ObjectNode)tmp;
List children = node.getChildren();
AttributeNode attrnode = null;
for(int i=0; attrnode==null && i<children.size(); i++)
{
if(((AttributeNode)children.get(i)).attribute==attr)
attrnode = (AttributeNode)children.get(i);
}
// Handle updates
// Add new attribute node.
if(attrnode==null && newvalue!=null)
{
// Rebuild children to create and find index of new node (hack???)
node.children = null;
List newchildren = node.getChildren();
node.children = children; // Keep old children, as there may be expanded subtrees.
for(int i=0; attrnode==null && i<newchildren.size(); i++)
{
if(((AttributeNode)newchildren.get(i)).attribute==attr)
attrnode = (AttributeNode)newchildren.get(i);
}
int index = newchildren.indexOf(attrnode);
children.add(index, attrnode); // insert new node into old children.
if(listeners!=null)
{
TreeModelEvent event = new TreeModelEvent(this, node.getPath(), new int[]{index}, new Object[]{attrnode});
TreeModelListener[] alisteners = (TreeModelListener[])listeners.toArray(new TreeModelListener[listeners.size()]);
for(int i=0; i<alisteners.length; i++)
{
alisteners[i].treeNodesInserted(event);
}
}
}
else if(attrnode!=null && OAVAttributeType.NONE.equals(attr.getMultiplicity()))
{
// Update existing node with new value
if(newvalue!=null)
{
attrnode.drop();
attrnode.children = null;
if(listeners!=null)
{
TreeModelEvent event = new TreeModelEvent(this, attrnode.getPath());
TreeModelListener[] alisteners = (TreeModelListener[])listeners.toArray(new TreeModelListener[listeners.size()]);
for(int i=0; i<alisteners.length; i++)
{
alisteners[i].treeStructureChanged(event);
}
}
}
// Remove existing node
else
{
int index = children.indexOf(attrnode);
children.remove(attrnode);
attrnode.drop();
if(listeners!=null)
{
TreeModelEvent event = new TreeModelEvent(this, node.getPath(), new int[]{index}, new Object[]{attrnode});
TreeModelListener[] alisteners = (TreeModelListener[])listeners.toArray(new TreeModelListener[listeners.size()]);
for(int i=0; i<alisteners.length; i++)
{
alisteners[i].treeNodesRemoved(event);
}
}
}
}
else if(attrnode!=null)
{
if(attrnode.children!=null) // Otherwise node is not shown and doesn't need update.
{
assert oldvalue!=newvalue;
// Add new value (at the end of children)
if(newvalue!=null)
{
Object child = newvalue;
if(!(attr.getType() instanceof OAVJavaType))
child = new ObjectNode(attrnode, child);
else if(isInspectable(newvalue))
// objectInspector Node
child = new ObjectInspectorNode(attrnode, newvalue.getClass(), null, newvalue);
// else use plain value
attrnode.children.add(child);
if(listeners!=null)
{
TreeModelEvent event = new TreeModelEvent(this, attrnode.getPath(), new int[]{attrnode.children.size()-1}, new Object[]{child});
TreeModelListener[] alisteners = (TreeModelListener[])listeners.toArray(new TreeModelListener[listeners.size()]);
for(int i=0; i<alisteners.length; i++)
{
alisteners[i].treeNodesInserted(event);
}
}
}
// Remove child of attribute node.
else if(attrnode.children.size()>1)
{
Object child = oldvalue;
if(!(attr.getType() instanceof OAVJavaType))
child = new ObjectNode(attrnode, child);
else if(isInspectable(child))
// objectInspector Node
child = new ObjectInspectorNode(attrnode, child.getClass(), null, child);
// else use plain value
// int index = attrnode.children.indexOf(child);
int index = getIndexForChild(attrnode.children, child);
// System.out.println("modified removing: "+child+", "+index);
if(attrnode.children.get(index) instanceof ObjectNode)
((ObjectNode)attrnode.children.get(index)).drop();
if(attrnode.children.get(index) instanceof AbstractInspectorNode)
((AbstractInspectorNode)attrnode.children.get(index)).drop();
attrnode.children.remove(index);
if(listeners!=null)
{
TreeModelEvent event = new TreeModelEvent(this, attrnode.getPath(), new int[]{index}, new Object[]{child});
TreeModelListener[] alisteners = (TreeModelListener[])listeners.toArray(new TreeModelListener[listeners.size()]);
for(int i=0; i<alisteners.length; i++)
{
alisteners[i].treeNodesRemoved(event);
}
}
}
// Remove attribute node when last value is removed.
else
{
int index = children.indexOf(attrnode);
children.remove(attrnode);
attrnode.drop();
if(listeners!=null)
{
TreeModelEvent event = new TreeModelEvent(this, node.getPath(), new int[]{index}, new Object[]{attrnode});
TreeModelListener[] alisteners = (TreeModelListener[])listeners.toArray(new TreeModelListener[listeners.size()]);
for(int i=0; i<alisteners.length; i++)
{
alisteners[i].treeNodesRemoved(event);
}
}
}
}
}
}
else if(tmp instanceof ObjectInspectorNode)
{
// todo? Or does it automatically handles changes?
}
}
/**
* Notification when an object has been added to the state.
* @param child The object id.
* @param type The object type.
*/
public void objectAdded(Object id, OAVObjectType type, boolean root)
{
// System.out.println("added: "+id+", "+type+", "+root);
if(root)
{
assert !nodes.containsKey(id): "Node already contained: "+id+", "+nodes.get(id);
List children = OAVTreeModel.this.root.getChildren();
Object child = id;
if(!(copy.getType(child) instanceof OAVJavaType))
{
// child = new ObjectNode(this, child);
child = new ObjectNode(getRoot(), child);
}
else if(isInspectable(child))
{
// objectInspector Node
// child = new ObjectInspectorNode(this, child.getClass(), null, child);
child = new ObjectInspectorNode(getRoot(), child.getClass(), null, child);
}
children.add(child);
if(listeners!=null)
{
TreeModelEvent event = new TreeModelEvent(this, new Object[]{OAVTreeModel.this.root}, new int[]{children.size()-1}, new Object[]{child});
TreeModelListener[] alisteners = (TreeModelListener[])listeners.toArray(new TreeModelListener[listeners.size()]);
for(int i=0; i<alisteners.length; i++)
{
alisteners[i].treeNodesInserted(event);
}
}
}
}
/**
* Notification when an object has been removed from state.
* @param id The object id.
* @param type The object type.
*/
public void objectRemoved(Object id, OAVObjectType type)
{
// System.out.println("removed: "+id+", "+type);
Object node = nodes.get(id);
if(node instanceof ObjectNode)
{
ObjectNode onode = (ObjectNode)node;
int index = getIndexOfChild(onode.parent, onode);
Object[] path = null;
if(onode.parent==OAVTreeModel.this.root)
{
((RootNode)onode.parent).children.remove(index);
path = new Object[]{OAVTreeModel.this.root};
}
else if(onode.parent instanceof AttributeNode)
{
((AttributeNode)onode.parent).children.remove(index);
path = ((AttributeNode)onode.parent).getPath();
}
onode.drop();
if(listeners!=null && path!=null)
{
TreeModelEvent event = new TreeModelEvent(this, path, new int[]{index}, new Object[]{node});
TreeModelListener[] alisteners = (TreeModelListener[])listeners.toArray(new TreeModelListener[listeners.size()]);
for(int i=0; i<alisteners.length; i++)
{
alisteners[i].treeNodesRemoved(event);
}
}
}
else if(node instanceof ObjectInspectorNode)
{
ObjectInspectorNode onode = (ObjectInspectorNode)node;
int index = getIndexOfChild(onode.parent, onode);
Object[] path = null;
if(onode.parent==OAVTreeModel.this.root)
{
((RootNode)onode.parent).children.remove(index);
path = new Object[]{OAVTreeModel.this.root};
}
else if(onode.parent instanceof AttributeNode)
{
// try
// {
((AttributeNode)onode.parent).children.remove(index);
path = ((AttributeNode)onode.parent).getPath();
// }
// catch(Exception e)
// {
// e.printStackTrace();
// }
}
onode.drop();
if(listeners!=null && path!=null)
{
TreeModelEvent event = new TreeModelEvent(this, path, new int[]{index}, new Object[]{node});
TreeModelListener[] alisteners = (TreeModelListener[])listeners.toArray(new TreeModelListener[listeners.size()]);
for(int i=0; i<alisteners.length; i++)
{
alisteners[i].treeNodesRemoved(event);
}
}
}
}
}, false);
}
//-------- TreeModel interface --------
/**
* Get the root node of the tree.
*/
public Object getRoot()
{
return root;
}
/**
* Get the number of children of the given node.
*/
public int getChildCount(Object parent)
{
// System.out.println("getChildCount: "+parent);
int count;
// Node for an attribute.
if(parent instanceof AttributeNode)
{
count = ((AttributeNode)parent).getChildren().size();
}
// Node for an OAV object.
else if(parent instanceof ObjectNode)
{
count = ((ObjectNode)parent).getChildren().size();
}
// Root node.
else if(parent instanceof RootNode)
{
count = ((RootNode)parent).getChildren().size();
}
// Node for an ObjectInspector object.
else if(parent instanceof ObjectInspectorNode)
{
count = ((ObjectInspectorNode)parent).getChildren().size();
}
// Node for an ObjectAttributeInspector object.
else if(parent instanceof ObjectInspectorAttributeNode)
{
count = ((ObjectInspectorAttributeNode)parent).getChildren().size();
}
// Node is value.
else
{
count = 0;
}
return count;
}
/**
* Get the given child of a node.
*/
public Object getChild(Object parent, int index)
{
// System.out.println("getChild: "+parent+", "+index);
Object ret;
// Node for an attribute.
if(parent instanceof AttributeNode)
{
ret = ((AttributeNode)parent).getChildren().get(index);
}
// Node for an object.
else if(parent instanceof ObjectNode)
{
ret = ((ObjectNode)parent).getChildren().get(index);
}
// Root node.
else if(parent instanceof RootNode)
{
ret = ((RootNode)parent).getChildren().get(index);
}
// Node for an ObjectInspector object.
else if(parent instanceof ObjectInspectorNode)
{
ret = ((ObjectInspectorNode)parent).getChildren().get(index);
}
// Node for an ObjectAttributeInspector object.
else if(parent instanceof ObjectInspectorAttributeNode)
{
ret = ((ObjectInspectorAttributeNode)parent).getChildren().get(index);
}
// Value node has no children.
else
{
throw new IllegalArgumentException("Node has no children: "+parent);
}
return ret;
}
/**
* Get the index of a given child.
*/
public int getIndexOfChild(Object parent, Object child)
{
int index = -1;
// Hack!!! Inefficient implementation!?
int count = getChildCount(parent);
for(int i=0; index==-1 && i<count; i++)
{
if(getChild(parent, i).equals(child))
index = i;
}
return index;
}
/**
* Check if a node is a leaf node.
*/
public boolean isLeaf(Object node)
{
// System.out.println("isLeaf: "+getChildCount(node)==0+" "+node);
return getChildCount(node)==0;
}
/**
* Add a listener to the model.
*/
public void addTreeModelListener(TreeModelListener l)
{
if(listeners==null)
listeners = new HashSet();
listeners.add(l);
}
/**
* Remove a listener from the model.
*/
public void removeTreeModelListener(TreeModelListener l)
{
if(listeners!=null)
listeners.remove(l);
}
/**
* Called by user changes (when the tree is editable).
*/
public void valueForPathChanged(TreePath path, Object newValue)
{
// ignored...
}
// --- helper methods to update the tree ---
/**
* Generate a unique id for ObjectNode's
* @return synchronized call to System.currentTimeMillis();
*/
protected synchronized int getNextNodeUUID()
{
//return rng.nextInt();
return uuidcounter += 1;
//return new Long(System.currentTimeMillis());
}
/**
* Regenerate subtree if a node was replaced
* @param treePath The Path to the node that was changed
*/
protected void fireTreeStructureChanged(Object[] treePath)
{
TreeModelEvent event =
new TreeModelEvent(this, treePath);
if (listeners != null)
{
TreeModelListener[] alisteners = (TreeModelListener[])listeners.toArray(new TreeModelListener[listeners.size()]);
for(int i=0; i<alisteners.length; i++)
{
alisteners[i].treeStructureChanged(event);
}
// System.out.println("Structure: " + event);
}
}
/**
* Tests if two lists of Inspector nodes (e.g. children)
* are semantically equals
* @param l1 list to test
* @param l2 list to test
* @return true if the lists are the same or contains the semantically same objects in the same order
*/
protected boolean testInspectorNodesListEquals(List l1, List l2)
{
if (l1 == l2)
return true;
if (!(l1 instanceof List) || !(l2 instanceof List))
return false;
ListIterator e1 = l1.listIterator();
ListIterator e2 = l2.listIterator();
while(e1.hasNext() && e2.hasNext())
{
Object o1 = e1.next();
Object o2 = e2.next();
try
{
if (!(o1==null ? o2==null : ((AbstractInspectorNode)o1).equals(o2, false)))
return false;
}
catch (ClassCastException e)
{
return false;
}
}
return !(e1.hasNext() || e2.hasNext());
}
/**
* Get the index for a child in children list, beginning with index=0
* @see getIndexForChild(List children, Object child, int start)
*/
protected int getIndexForChild(List children, Object child)
{
return getIndexForChild(children, child, 0);
}
/**
* Returns the FIRST occurrence of child in given children list.
* Be sure to remove the child from the list or to save the index
* to find additional occurrences of the child in later searches.
* <p>
* This method is using the "equals(Object o, boolean checkUID)" method
* to find a child with the checkUID parameter set to false and can be used
* to find a semantically equal child.
* <p>
* @param children List to search for the child.
* @param child The child to search for.
* @return the FIRST index for the child in the children list
*/
protected int getIndexForChild(List children, Object child, int start)
{
assert children != null;
int index = -1;
for(int i=start; index==-1 && i<children.size(); i++)
// for(int i=children.size()-1; i>=0 && index == -1; i--)
{
if((children.get(i) instanceof ObjectNode)
&& ((ObjectNode)children.get(i)).equals(child, false))
{
index = i;
}
else if ((children.get(i) instanceof ObjectInspectorNode)
&& ((ObjectInspectorNode)children.get(i)).equals(child, false))
{
index = i;
}
else if ((children.get(i) instanceof ObjectInspectorAttributeNode)
&& ((ObjectInspectorAttributeNode)children.get(i)).equals(child, false))
{
index = i;
}
else if ((children.get(i) instanceof ObjectInspectorValueNode)
&& ((ObjectInspectorValueNode)children.get(i)).equals(child, false))
{
index = i;
}
else if (children.get(i).equals(child))
{
index = i;
}
}
return index;
}
/**
* Refresh all displayed attributes
* @param oldRoot
*/
protected void refreshInspectorNodes()
{
// System.out.println("refresh called");
TreeModelEvent event = null;
// Object[] listener = listenerList.getListenerList();
Object[] inspectorNodes = inspectors.toArray(new Object[inspectors.size()]);
// the array contains all attribute nodes ordered in the path from root to leaf.
// e.g. a parent node will have a lower index than his children
//
// we could loop from leafs to root, to avoid drawing problems when updating a node that was
// already removed due to a parent object change
// OR
// null all (grand) children from a dropped node in the array to avoid later redrawing what will
// cause a drawing error / problem
//
// TO DO: decide which implementation is more efficient
for (int inspectorIndex = inspectorNodes.length-1; inspectorIndex >= 0; inspectorIndex--)
// for (int inspectorIndex = 0; inspectorIndex < inspectorNodes.length; inspectorIndex++)
{
if (inspectorNodes[inspectorIndex] instanceof ObjectInspectorAttributeNode)
{
ObjectInspectorAttributeNode node = (ObjectInspectorAttributeNode) inspectorNodes[inspectorIndex];
if (node.children!=null) // if childrens are not displayed, no update is needed
{
// Regenerate children and fire change event for changed values
// keep and restore old children as there may be expanded subtrees
List oldchildren = node.children;
node.children = null;
List newchildren = node.getChildren();
node.children = oldchildren;
if (node.isArrayNode())
{
// if we have a simple type we don't have to check subtrees,
// simply remove old and add new children
if (!isInspectable(node.type.getComponentType(), true))
{
//if (!oldchildren.equals(newchildren))
if (!testInspectorNodesListEquals(oldchildren, newchildren))
{
node.children = newchildren;
fireTreeStructureChanged(node.getPath());
}
}
// some array fields can be inspected, so assume that there may be a
// expanded subtree.
else
{
// List to save already selected children from newchildren
// Needed to avoid double select of the same value in an array
List prevSelectedChildren = new ArrayList();
// Handle removed children
Map removedChildren = new TreeMap();
for (int i = 0; i < oldchildren.size(); i++)
{
Object oldchild = oldchildren.get(i);
// use only not previous selected children
int index = getIndexForChild(newchildren, oldchild, 0);
while (index != -1 && prevSelectedChildren.contains(newchildren.get(index)))
{
prevSelectedChildren.add(newchildren.get(index));
index = getIndexForChild(newchildren, oldchild, index+1);
}
//if (!newchildren.contains(oldchild))
// value was removed
if (index == -1)
{
// add it to removedChildren
removedChildren.put(new Integer(i), oldchild);
// don't remove child from old children here! This will change
// index of other child's as well
}
}
// clear selected children after use
prevSelectedChildren.clear();
// remove the removed from oldchildren an store
// index and value in change event arrays
Object[] removed = removedChildren.entrySet().toArray();
int[] indexes = new int[removed.length];
Object[] childs = new Object[removed.length];
for (int i = 0; i < removed.length; i++)
{
Map.Entry entry = (Map.Entry) removed[i];
indexes[i] = ((Integer) entry.getKey()).intValue();
childs[i] = entry.getValue();
// drop child if it is an inspector node
if (entry.getValue() instanceof ObjectInspectorNode)
((ObjectInspectorNode) entry.getValue()).drop();
// remove from children list
oldchildren.remove(entry.getValue());
//oldchildren.remove(getIndexForChild(oldchildren, entry.getValue()));
}
// Handle inserted children
// replace the rest of old children at their position in new children
// save the not replaced children as added nodes in change event arrays
Map insertedChildren = new TreeMap();
for (int i = 0; i < newchildren.size(); i++)
{
Object newchild = newchildren.get(i);
// use only not previous selected children
int index = getIndexForChild(oldchildren, newchild, 0);
while (index != -1 && prevSelectedChildren.contains(newchildren.get(index)))
{
prevSelectedChildren.add(oldchildren.get(index));
index = getIndexForChild(oldchildren, newchild, index+1);
}
// replace new with old as there may be expanded sub trees
//if (oldchildren.contains(newchild))
if (index != -1)
{
//Object oldchild = oldchildren.get(oldchildren.indexOf(newchild));
Object oldchild = oldchildren.get(index);
// remove oldchild from oldchildren
//(needed to support same object twice in arrays)
oldchildren.remove(index);
newchildren.remove(i);
newchildren.add(i, oldchild);
// drop newchild, it was replaced by the old one
if (newchild instanceof ObjectInspectorNode)
((ObjectInspectorNode) newchild).drop();
}
// value was added
else
{
// register children for change event
insertedChildren.put(new Integer(i), newchild);
}
}
// clear selected children after use
prevSelectedChildren.clear();
// update the name prefix for children
for (int i = 0; i < newchildren.size(); i++)
{
Object obj = newchildren.get(i);
if (obj instanceof ObjectInspectorNode)
{
((ObjectInspectorNode) obj).namePrefix = "["+i+"] ";
}
else if (obj instanceof ObjectInspectorValueNode)
{
((ObjectInspectorValueNode) obj).namePrefix = "["+i+"] ";
}
}
// create the array for nodes inserted change event
Object[] inserted = insertedChildren.entrySet().toArray();
int[] insertedIndexes = new int[inserted.length];
Object[] insertedChilds = new Object[inserted.length];
for (int insertedIndex = 0; insertedIndex < inserted.length; insertedIndex++)
{
Map.Entry entry = (Map.Entry) inserted[insertedIndex];
insertedIndexes[insertedIndex] = ((Integer) entry.getKey()).intValue();
insertedChilds[insertedIndex] = entry.getValue();
}
// add the new children as node children
node.children = newchildren;
TreeModelListener[] alisteners = (TreeModelListener[])listeners.toArray(new TreeModelListener[listeners.size()]);
// create and fire event for removed children
if (removedChildren.size() > 0)
{
event = new TreeModelEvent(this, node.getPath(), indexes, childs);
for(int i=0; i<alisteners.length; i++)
{
alisteners[i].treeNodesRemoved(event);
}
//System.out.println("Removed: " + event);
}
// create and fire event for inserted children
if (insertedChildren.size() > 0)
{
event = new TreeModelEvent(this, node.getPath(), insertedIndexes, insertedChilds);
for(int i=0; i<alisteners.length; i++)
{
alisteners[i].treeNodesInserted(event);
}
//System.out.println("Inserted: " + event);
}
}
}
// we have no array, so expect only one child for the attribute node
else
{
// Number of children and index of a child can't change at runtime, expect of arrays
assert oldchildren.size() == newchildren.size(): node;
// An attribute field can only have one value
assert newchildren.size() == 1 : node;
//if ( !(oldchildren.get(0).equals(newchildren.get(0))) )
if ( getIndexForChild(oldchildren, newchildren.get(0)) == -1 )
{
// replace old children with new children and drop old
node.children = newchildren;
if (oldchildren.get(0) instanceof ObjectInspectorNode)
{
((ObjectInspectorNode) oldchildren.get(0)).drop();
}
// create and fire event
if (oldchildren.get(0) instanceof ObjectInspectorNode || newchildren.get(0) instanceof ObjectInspectorNode)
{
// regenerate tree if inspector object node has changed
fireTreeStructureChanged((Object[]) SUtil.joinArrays(node.getPath(), new Object[]{oldchildren.get(0)}));
}
else
{
// only redraw node if we have a inspector value
event = new TreeModelEvent(this, node.getPath(), new int[]{0}, new Object[]{oldchildren.get(0)});
// System.out.println("Changed: " + event);
TreeModelListener[] alisteners = (TreeModelListener[])listeners.toArray(new TreeModelListener[listeners.size()]);
for(int i=0; i<alisteners.length; i++)
{
alisteners[i].treeNodesChanged(event);
}
}
}
else if (newchildren.get(0) instanceof ObjectInspectorNode)
{
// no difference between children, drop new children if it is an ObjectInspectorNode
((ObjectInspectorNode) newchildren.get(0)).drop();
}
}
}
}
else
{
if (inspectorNodes[inspectorIndex] != null)
System.err.println("Error in ObjectInspectorTreeModel, unknown inspector node type: " + inspectorNodes[inspectorIndex]);
}
}
}
/**
* Dispose the model and remove all listeners.
*/
public void dispose()
{
this.copy.dispose();
}
//-------- static part --------
/**
* Decide if java object should be inspectable.
*/
protected static boolean isInspectable(Object obj)
{
boolean ret = enableObjectInspection && obj!=null;
if(ret)
{
ret = ret && !(obj instanceof String);
ret = ret && !(obj instanceof Number);
ret = ret && !(obj instanceof Character);
ret = ret && !(obj instanceof Boolean);
ret = ret && obj.getClass()!=Object.class;
}
return ret;
}
/**
* Decide if java object should be inspectable.
* This method test if clazz.isAssignableFrom([String|Number|Character|])
*/
protected static boolean isInspectable(Class clazz, boolean inspectObjectClass)
{
boolean ret = enableObjectInspection && clazz!=null;
if(ret)
{
ret = ret && (clazz.isAssignableFrom(String.class));
ret = ret && (clazz.isAssignableFrom(Number.class));
ret = ret && (clazz.isAssignableFrom(Character.class));
ret = ret && (clazz.isAssignableFrom(Boolean.class));
if (!inspectObjectClass)
ret = ret && clazz != Object.class;
}
return ret;
}
/**
* Sets the refresh delay for the ObjectInspector refresh
* A value equal or lower to 0 disables the refresh and stop the timers
*/
public static void setRefreshDelay(int millis)
{
// System.out.println("Set OAVTreeModel refresh delay to " + millis);
if (timerList != null)
{
if (millis > 0)
for (Iterator timers = timerList.iterator(); timers.hasNext();)
{
Timer timer = (Timer) timers.next();
timer.setDelay(millis);
if (!timer.isRunning())
timer.start();
}
else
for (Iterator timers = timerList.iterator(); timers.hasNext();)
{
Timer timer = (Timer) timers.next();
if (timer.isRunning())
timer.stop();
}
}
}
/**
* Add a timer to the static refresh timer list
* @param t
*/
protected static void addRefreshTimer(Timer t)
{
if (timerList == null)
timerList = new ArrayList();
timerList.add(t);
}
/**
* Remove a timer from the static refresh timer list
* @param t
*/
protected static void removeRefreshTimer(Timer t)
{
if (t != null)
{
t.stop();
if (timerList == null)
timerList.remove(t);
}
}
/** A abstract node for this model */
abstract class AbstractInspectorNode
{
/** The parent node */
protected Object parent;
/** The children of this node (cached)*/
protected List children;
/** The path from the root node to this node. */
protected Object[] path;
/** A unique id for this node */
protected int nodeUUID;
// --- constructor ---
protected AbstractInspectorNode()
{
nodeUUID = getNextNodeUUID();
}
// --- abstract methods ---
public abstract List getChildren();
public abstract Object[] getPath();
protected abstract boolean equals(Object obj, boolean checkUUID);
// --- methods ---
public void drop() { };
public int hashCode()
{
return nodeUUID;
}
}
/**
* A node representing an attribute value.
*/
public class AttributeNode
{
//-------- attributes --------
/** The attribute. */
protected OAVAttributeType attribute;
/** The object node. */
protected ObjectNode parent;
/** The children. */
protected List children;
/** The path from the root node to this node. */
protected Object[] path;
/** A unique id for this node */
protected int nodeUUID;
//-------- constructors --------
/**
* Create a new attribute node.
* @param parent The parent.
* @param attribute The attribute.
*/
public AttributeNode(ObjectNode parent, OAVAttributeType attribute)
{
this.parent = parent;
this.attribute = attribute;
this.nodeUUID = getNextNodeUUID();
}
//-------- methods --------
/**
* Get the children of this node.
*/
public List getChildren()
{
if(children==null)
{
children = new ArrayList();
if(OAVAttributeType.NONE.equals(attribute.getMultiplicity()))
{
Object child = copy.getAttributeValue(parent.object, attribute);
if(!(attribute.getType() instanceof OAVJavaType))
child = new ObjectNode(this, child);
else if(isInspectable(child))
// objectInspector Node
child = new ObjectInspectorNode(this, child.getClass(), null, child);
// else use plain value
children.add(child);
}
else
{
Collection coll = copy.getAttributeValues(parent.object, attribute);
if(coll!=null)
{
for(Iterator it=coll.iterator(); it.hasNext(); )
{
Object child = it.next();
if(!(attribute.getType() instanceof OAVJavaType))
child = new ObjectNode(this, child);
else if(isInspectable(child))
// objectInspector Node
child = new ObjectInspectorNode(this, child.getClass(), null, child);
else
// else use wrapped plain value, as JTree does not allow duplicates.
child = new ObjectInspectorValueNode(this, null, child);
children.add(child);
}
}
}
}
return children;
}
/**
* Get the path of this node (inclusive) starting from the root node.
*/
public Object[] getPath()
{
if(path==null)
{
if(parent!=null)
{
path = (Object[])SUtil.joinArrays(parent.getPath(), new Object[]{this});
}
else
{
path = new Object[]{this};
}
}
return path;
}
/**
* Unregister a node and its subnodes.
*/
public void drop()
{
if(children!=null)
{
for(int i=0; i<children.size(); i++)
{
if(children.get(i) instanceof ObjectNode)
((ObjectNode)children.get(i)).drop();
}
}
}
/**
* Create a string representation of the attribute node.
* @return A string representation of the attribute node.
*/
public String toString()
{
String name = attribute.getName();
int idx = name.indexOf("has_");
if(idx!=-1)
name = name.substring(idx+4);
return name; //+" (attribute)";
}
protected boolean equals(Object obj, boolean checkUUID)
{
boolean ret =
obj instanceof AttributeNode
&& ((AttributeNode)obj).parent==parent
&& ((AttributeNode)obj).attribute==attribute;
if (checkUUID && ret)
ret = ret && ((AttributeNode)obj).nodeUUID==nodeUUID;
return ret;
}
public boolean equals(Object obj)
{
// return obj instanceof AttributeNode
// && ((AttributeNode)obj).parent==parent
// && ((AttributeNode)obj).attribute==attribute;
return equals(obj, true);
}
public int hashCode()
{
// int ret = 31 + parent.hashCode();
// ret = ret*31 + attribute.hashCode();
// return ret;
return nodeUUID;
}
}
/**
* A node representing an object.
*/
public class ObjectNode
{
//-------- attributes --------
/** The object. */
protected Object object;
/** The parent node (attribute or root node). */
protected Object parent;
/** The children. */
protected List children;
/** The path from the root node to this node. */
protected Object[] path;
/** A unique id for this node */
protected int nodeUUID;
//-------- constructors --------
/**
* Create a new object node.
* @param parent The parent node (if not root node).
* @param object The object.
* @param attribute The attribute.
*/
public ObjectNode(Object parent, Object object)
{
this.parent = parent;
this.object = object;
this.nodeUUID = getNextNodeUUID();
nodes.put(object, this);
}
//-------- methods --------
/**
* Get the children of this node.
*/
public List getChildren()
{
if(children==null)
{
children = new ArrayList();
OAVObjectType type = copy.getType(object);
while(type!=null)
{
Iterator it = type.getDeclaredAttributeTypes().iterator();
while(it.hasNext())
{
OAVAttributeType attr = (OAVAttributeType)it.next();
if(OAVAttributeType.NONE.equals(attr.getMultiplicity()))
{
if(copy.getAttributeValue(object, attr)!=null)
children.add(new AttributeNode(this, attr));
}
else
{
if(copy.getAttributeValues(object, attr)!=null)
children.add(new AttributeNode(this, attr));
}
}
type = type.getSupertype();
}
}
return children;
}
/**
* Get the path of this node (inclusive) starting from the root node.
*/
public Object[] getPath()
{
if(path==null)
{
if(parent instanceof AttributeNode)
{
path = (Object[])SUtil.joinArrays(((AttributeNode)parent).getPath(), new Object[]{this});
}
else
{
path = new Object[]{parent, this};
}
}
return path;
}
/**
* Unregister a node and its subnodes.
*/
public void drop()
{
nodes.remove(object);
if(children!=null)
{
for(int i=0; i<children.size(); i++)
((AttributeNode)children.get(i)).drop();
}
}
/**
* Create a string representation of the attribute node.
* @return A string representation of the attribute node.
*/
public String toString()
{
String ret = ""+object;
try
{
// Hack!!! configure name slot?
OAVObjectType type = copy.getType(object);
OAVAttributeType name = null;
try{name = type.getAttributeType("element_has_name");} catch(Exception e){}
if(name!=null)
{
Object val = copy.getAttributeValue(object, name);
if(val!=null)
ret = val.toString() + " (id="+ret+")";
}
}
catch(Exception e)
{
System.err.println("no name for "+object+", "+(object!=null?object.getClass().toString():"null"));
e.printStackTrace();
}
return ret;
}
/**
* This method can be used to do a sematically equals check.
* E.g. check only the fields, not the unique identifier.
* @param obj Object to test for equals
* @param checkUUID flag to check the unique Identifier for the node. <br><code>true</code>=do a compete equals check<br><code>false</code>=do a sematically equals check
*/
protected boolean equals(Object obj, boolean checkUUID)
{
boolean ret = obj instanceof ObjectNode
&& ((ObjectNode)obj).object==object;
if (checkUUID && ret)
ret = ret && ((ObjectNode)obj).nodeUUID==nodeUUID;
return ret;
}
public boolean equals(Object obj)
{
return equals(obj, true);
}
public int hashCode()
{
// int ret = 31 + object.hashCode();
// //ret = ret*31 + nodeUUID;
// //ret = ret*31 + (nodeUUID != null ? nodeUUID.hashCode() : 0);
// return ret;
return nodeUUID;
}
}
/**
* The root node containing the nodes for the root objects of the state.
*/
public class RootNode
{
//-------- attributes --------
/** The children. */
protected List children;
//-------- constructors --------
/**
* Create a new object node.
*/
public RootNode()
{
}
//-------- methods --------
/**
* Get the children of this node.
*/
public List getChildren()
{
if(children==null)
{
children = new ArrayList();
for(Iterator it=copy.getRootObjects(); it.hasNext(); )
{
Object child = it.next();
if(!(copy.getType(child) instanceof OAVJavaType))
child = new ObjectNode(this, child);
else if(isInspectable(child))
// objectInspector Node
child = new ObjectInspectorNode(this, child.getClass(), null, child);
children.add(child);
}
}
return children;
}
}
/**
* TreeModel node for java object inspection
* @author claas
*
*/
public class ObjectInspectorNode extends AbstractInspectorNode
{
/** The Class type for this node */
protected Class type;
/** The object represented by this node */
protected Object nodeObject;
/** The list of fields of the represented object*/
protected List fields;
/** The name for this node e.g. the objects name */
protected String name;
/** A prefix to display with name, e.g. for arrays "[index]" */
protected String namePrefix;
// ---- constructors ----
/**
* Create a ObjectInspectorNode
*
* @param type Class type for this object (e.g. myObject.class)
* @param name for this node
* @param object to inspect
*/
public ObjectInspectorNode(Object parent, Class type, String name, Object object)
{
this(parent, type, null, name, object);
}
/**
* Create a ObjectInspectorNode
*
* @param type Class type for this object (e.g. myObject.class)
* @param name for this node
* @param object to inspect
*/
public ObjectInspectorNode(Object parent, Class type, String namePrefix, String name, Object object)
{
this.parent = parent;
this.type = type;
this.name = name;
this.namePrefix = namePrefix;
this.nodeObject = object;
getFields();
nodes.put(object, this);
}
// --- methods ----
/**
* Generate and return a List of all fields for the
* object represented by this node.
* @return List of accessible node object fields
*/
public List getFields()
{
// create list of fields only once,
//number or type of attributes can't change at runtime
if (fields==null)
{
this.fields = new ArrayList();
// find all fields for a class expect strings and null values
if (!type.isPrimitive() && !type.isArray() && !type.equals(String.class) && nodeObject != null)
{
// iterate over fields from the class and superclasses
for (Class clazz = nodeObject.getClass(); clazz != null; clazz = clazz.getSuperclass())
{
Field[] f = clazz.getDeclaredFields();
AccessibleObject.setAccessible(f, true);
for (int i = 0; i < f.length; i++)
{
// get only nonstatic fields
if ((f[i].getModifiers() & Modifier.STATIC) == 0)
fields.add(f[i]);
// TO-DO: Filter other fields as well?
}
}
}
}
return fields;
}
/**
* Get the children of this node.
*/
public List getChildren()
{
if(children==null)
{
children = new ArrayList();
Iterator it = fields.iterator();
while(it.hasNext())
{
Field f = (Field) it.next();
try
{
children.add(new ObjectInspectorAttributeNode(this, f, null));
}
catch (IllegalAccessException e)
{
// Field not accessible - ignore for children ?
children.add("-ERROR- Exception occurred: " +e);
}
}
}
return children;
}
/**
* Get the path of this node (inclusive) starting from the root node.
*/
public Object[] getPath()
{
if(path==null)
{
if (parent != null)
{
if(parent instanceof AttributeNode)
{
path = (Object[])SUtil.joinArrays(((AttributeNode)parent).getPath(), new Object[]{this});
}
else if(parent instanceof ObjectInspectorAttributeNode)
{
path = (Object[])SUtil.joinArrays(((ObjectInspectorAttributeNode)parent).getPath(), new Object[]{this});
}
else
{
path = new Object[]{parent, this};
}
}
else
{
path = new Object[]{this};
}
}
return path;
}
/**
* Unregister a node and its subnodes.
*/
public void drop()
{
nodes.remove(nodeObject);
if(children!=null)
{
for(int i=0; i<children.size(); i++)
{
((ObjectInspectorAttributeNode)children.get(i)).drop();
}
}
}
/**
* Access the object represented by this node
* @return the nodeObject Attribute
*/
protected Object getNodeObject()
{
// if (isInpsectionRootNode())
return nodeObject;
// else
// {
// try
// {
// Object obj = null;
// if (((ObjectInspectorAttributeNode)parent).isArrayNode())
// {
// // parent is an array attribute, use the attribute value for this node
// obj = ((ObjectInspectorAttributeNode)parent).getArrayValue();
// }
// else
// {
// // parent is an normal attribute, use the attribute value for this node
// obj = ((ObjectInspectorAttributeNode)parent).getFieldValue();
// }
//
// if (obj == nodeObject || (obj != null && obj.equals(nodeObject)))
// {
// // ignore, its the same object
// }
// else
// {
// // replace nodeObject
// nodeObject = obj;
// fields = null;
// }
//
// return nodeObject;
//
// } catch (Exception e)
// {
// return "-ERROR- Exception occurred: " + e;
// //e.printStackTrace();
// }
// }
}
protected boolean isInpsectionRootNode()
{
return (!(parent instanceof ObjectInspectorAttributeNode));
}
/**
* This method can be used to do a sematically equals check.
* E.g. check only the fields, not the unique identifier.
* @param obj Object to test for equals
* @param checkUUID flag to check the unique Identifier for the node. <br><code>true</code>=do a compete equals check<br><code>false</code>=do a sematically equals check
*/
protected boolean equals(Object obj, boolean checkUUID)
{
boolean ret = obj instanceof ObjectInspectorNode
&& ((ObjectInspectorNode)obj).parent==parent
&& ((ObjectInspectorNode)obj).type==type
&& ((ObjectInspectorNode)obj).name==name
&& (fields != null && fields.equals(((ObjectInspectorNode)obj).fields))
&& (nodeObject != null && nodeObject.equals(((ObjectInspectorNode) obj).nodeObject));
if (checkUUID && ret)
ret = ret && ((ObjectInspectorNode)obj).nodeUUID==nodeUUID;
return ret;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj)
{
return equals(obj, true);
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
public int hashCode()
{
// int ret = 31 + (parent != null ? parent.hashCode() : 0);
// ret = ret*31 + (type!=null ? type.hashCode() : 0);
// ret = ret*31 + (name!=null ? name.hashCode() : 0);
// ret = ret*31 + (fields != null ? fields.hashCode() : 0);
// ret = ret*31 + (nodeObject != null ? nodeObject.hashCode() : 0);
// //ret = ret*31 + nodeUUID;
// //ret = ret*31 + (nodeUUID != null ? nodeUUID.hashCode() : 0);
// return ret;
return nodeUUID;
}
/**
* Get a string representation of this node
*/
public String toString()
{
getNodeObject();
return (namePrefix != null ? namePrefix : "")
+ (name!=null ? name : "")
// + (type.isPrimitive() ? " = " : "")
+ (nodeObject!=null ? nodeObject.toString() : "null") ;
}
} // class ObjectInspectorNode
/**
* Node for an Java object attribute
* @author claas
*
*/
class ObjectInspectorAttributeNode extends AbstractInspectorNode
{
/** The field represented by this node */
protected Field field;
/** The Class type for this attribute node */
protected Class type;
/** The name for this node e.g. the objects name */
protected String name;
/**
* The attribute value represented by this node <p>
* This value will be updated every time getChildren() creates a list of children
*/
protected Object attributeValue;
// ---- constructor -----
/**
* Create a ObjectInspectorAttributeNode with a dynamic
* reference to the inspectable Field
* @param objectInspectorNode parent Node
* @param f the field to get from object parameter
* @param namePrefix the object to get the field from
*/
public ObjectInspectorAttributeNode(ObjectInspectorNode parent, Field f, String name)
throws IllegalAccessException
{
this.parent = parent;
this.field = f;
this.type = f.getType();
this.name = (name!=null?name:f.getName());
inspectors.add(this);
}
// ---- methods ----
/**
* Get the children of this node.
*/
public List getChildren()
{
if(children==null)
{
this.children = new ArrayList();
//this.attributeValue = field.get(attributeObject);
// get value for this node
// the parent Array-Field if this is an array, else the value itself
//Object value = null;
//if (type.isArray())
if (isArrayNode())
{
try
{
// attributeValue = getArrayValue();
attributeValue = getFieldValue();
if (attributeValue == null)
{
// children.add("null");
// IGNORE !
// don't add children for null valued arrays
//children.add(new ObjectInspectorValueNode(this, null, attributeValue));
}
else
{
for (int i = 0; i < Array.getLength(attributeValue); i++)
{
Object obj = Array.get(attributeValue, i);
if (OAVTreeModel.isInspectable(obj))
{
children.add(new ObjectInspectorNode(this, obj.getClass(), "["+i+"] ", null, obj));
}
else
{
// children.add("["+i+"] "+obj);
children.add(new ObjectInspectorValueNode(this, "["+i+"] ", obj));
}
}
}
} catch (Exception e)
{
e.printStackTrace();
children.add("-ERROR- Exception occurred: " +e);
}
}
else
{
this.attributeValue = getFieldValue();
// create a new object inspector node for inspectable attribute
if (OAVTreeModel.isInspectable(attributeValue))
{
children.add(new ObjectInspectorNode(this, type, name, attributeValue));
}
// else add a simple value node
else
{
//children.add((attributeValue!=null ? attributeValue : "null"));
children.add(new ObjectInspectorValueNode(this, null, attributeValue));
}
}
}
return children;
}
protected boolean isArrayNode()
{
attributeValue = getFieldValue();
return type.isArray() || (attributeValue != null && attributeValue.getClass().isArray());
}
/**
* returns the object that is represented by this attribute node as is.
* e.g. the field of the parent node object.
* If this object is an array, the array itself is returned. If you need access to the value
* of the index that is represented with this node, use getArrayValue() instead.
*/
protected Object getFieldValue()
{
try
{
return field.get(((ObjectInspectorNode)parent).getNodeObject());
} catch (Exception e)
{
//e.printStackTrace();
return "-ERROR- Exception occurred: " + e;
}
}
/**
* Returns the value for the represented field from the parent object inspector node
* If this field is an array, the value from the array index represented
* by this node is returned. When access to the array is needed, e.g. to
* create the the node children, use getFieldValue() instead.
* @return
*/
protected Object getArrayValue()
{
try
{
if (isArrayNode())
{
return Array.get(field.get(((ObjectInspectorNode)parent).getNodeObject()), ((ObjectInspectorNode)parent).getChildren().indexOf(this)/*-1*/);
}
else
return "-ERROR- getArrayValue called on a non array type";
} catch (Exception e)
{
//e.printStackTrace();
return "-ERROR- Exception occurred: " + e;
}
}
/**
* Unregister a node and its subnodes.
*/
public void drop()
{
inspectors.remove(this);
if(children!=null)
{
for(int i=0; i<children.size(); i++)
{
if(children.get(i) instanceof ObjectInspectorNode)
((ObjectInspectorNode)children.get(i)).drop();
}
}
}
/**
* Get the path of this node (inclusive) starting from the root node.
*/
public Object[] getPath()
{
if(path==null)
{
if(parent!=null)
{
path = (Object[])SUtil.joinArrays(((ObjectInspectorNode)parent).getPath(), new Object[]{this});
}
else
{
path = new Object[]{this};
}
}
return path;
}
/**
* This method can be used to do a sematically equals check.
* E.g. check only the fields, not the unique identifier.
* @param obj Object to test for equals
* @param checkUUID flag to check the unique Identifier for the node. <br><code>true</code>=do a compete equals check<br><code>false</code>=do a sematically equals check
*/
protected boolean equals(Object obj, boolean checkUUID)
{
boolean ret = obj instanceof ObjectInspectorAttributeNode
&& ((ObjectInspectorAttributeNode) obj).parent == parent
&& ((ObjectInspectorAttributeNode) obj).field == field
&& ((ObjectInspectorAttributeNode) obj).name == name
;
if (ret)
{
Object objValue = ((ObjectInspectorAttributeNode) obj).attributeValue;
// test if attribute values are equal if type is primitiv
if (type.isPrimitive())
ret = (attributeValue==null ? objValue==null : attributeValue.equals(objValue));
// else test reference
else
ret = (attributeValue==objValue);
}
if (checkUUID && ret)
ret = ret && ((ObjectInspectorAttributeNode)obj).nodeUUID==nodeUUID;
return ret;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj)
{
return equals(obj, true);
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
public int hashCode()
{
// int ret = 31 + (parent != null ? parent.hashCode() : 0);
// ret = ret*31 + (field!=null ? field.hashCode() : 0);
// ret = ret*31 + (name!=null ? name.hashCode() : 0);
// ret = ret*31 + (attributeValue != null ? attributeValue.hashCode() : 0);
// //ret = ret*31 + nodeUUID;
//
// return ret;
return nodeUUID;
}
/**
* Get a string representation of this node
*/
public String toString()
{
String len = "";
try
{
//if (type.isArray())
if (isArrayNode())
{
Object arrayValue = getFieldValue();
len = (arrayValue !=null ? ""+ Array.getLength(arrayValue) : "null");
}
} catch (Exception e)
{
e.printStackTrace();
len = "-ERROR-";
}
return (name != null ? name : "?? INVALID NODE NAME ??")
//+ (type.isArray() ? "["+len+"]" : "")
+ (isArrayNode() ? "["+len+"]" : "")
//+ (attributeValue!=null? " = "+attributeValue: " (null)")
;
}
} // class ObjectInspectorAttributeNode
/**
* This class represents a simple value for ObjectInspectorAtttributeNode values.
* It is needed to display a prefix [index] for arrays
*/
class ObjectInspectorValueNode extends AbstractInspectorNode
{
// ---- attributes ----
/** A simple value node can have a displayed name prefix e.g. for Arrays */
protected String namePrefix;
/** The simple Object represented by this node */
protected Object value;
// --- constructor ----
/** create a simple value node */
public ObjectInspectorValueNode(Object parent, String namePrefix, Object value)
{
super.parent = parent;
this.namePrefix = namePrefix;
this.value = value;
}
// --- methods ---
public List getChildren()
{
return null;
}
public Object[] getPath()
{
return (Object[]) SUtil.joinArrays(((AbstractInspectorNode)super.parent).getPath(), new Object[]{this});
}
/**
* This method can be used to do a sematically equals check.
* E.g. check only the fields, not the unique identifier.
* @param obj Object to test for equals
* @param checkUUID flag to check the unique Identifier for the node. <br><code>true</code>=do a compete equals check<br><code>false</code>=do a sematically equals check
*/
protected boolean equals(Object obj, boolean checkUUID)
{
boolean ret = (obj instanceof ObjectInspectorValueNode)
&& ((ObjectInspectorValueNode)obj).parent == parent
&& (value == null ? ((ObjectInspectorValueNode)obj).value == null : value.equals(((ObjectInspectorValueNode)obj).value));
if (checkUUID && ret)
ret = ret && ((ObjectInspectorValueNode)obj).nodeUUID==nodeUUID;
return ret;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj)
{
return equals(obj, true);
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
public int hashCode()
{
// int ret = 31 + (parent != null ? parent.hashCode() : 0);
// ret = ret*31 + (value!=null ? value.hashCode() : 0);
// //ret = ret*31 + nodeUUID;
// //ret = ret*31 + (nodeUUID != null ? nodeUUID.hashCode() : 0);
// return ret;
return nodeUUID;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString()
{
String ret = (namePrefix != null ? namePrefix : "") + (value != null ? value : "null");
// ret = "[ValueNode: " + ret + "]";
return ret;
}
} // class ObjectInspectorValueNode
/**
* OAV tree cell renderer displays right icons.
*/
public static class OAVTreeCellRenderer extends DefaultTreeCellRenderer
{
/**
* Get the tree cell renderer component.
*/
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel,
boolean expanded, boolean leaf, int row, boolean hasFocus)
{
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
// System.out.println(value+" "+value.getClass());
if(value instanceof ObjectNode)
setIcon(icons.getIcon("object"));
else if(value instanceof AttributeNode)
setIcon(icons.getIcon("attribute"));
else if (value instanceof ObjectInspectorNode)
setIcon(icons.getIcon("javaobject"));
else if (value instanceof ObjectInspectorAttributeNode)
setIcon(icons.getIcon("javaattribute"));
else if (value instanceof ObjectInspectorValueNode)
setIcon(icons.getIcon("javavalue"));
else
setIcon(icons.getIcon("value"));
return this;
}
/* (non-Javadoc)
* @see javax.swing.tree.DefaultTreeCellRenderer#getPreferredSize()
*/
public Dimension getPreferredSize()
{
// change prefered size to disable the swing
// "..." bug in tree display
Dimension d = new Dimension(super.getPreferredSize());
d.setSize(d.getWidth()+10, d.getHeight());
return d;
}
}
}
/**
* Action class to update the tree model e.g. with a timer
*/
class ObjectInspectorRefreshAction implements ActionListener
{
/**
* A WeakReference to the tree model to allow the JVM to remove the
* model from heap when introspector plugin is closed
*/
WeakReference treeModel;
/**
* Create a ActionListener with a weak reference to the OAVTreeModel to update
*/
public ObjectInspectorRefreshAction(OAVTreeModel treeModel)
{
this.treeModel = new WeakReference(treeModel);
}
/**
* Perform OAVTreeModel refresh if TreeModel reference exist else remove
* Timer from Timer list
*/
public void actionPerformed(ActionEvent e)
{
OAVTreeModel model = (OAVTreeModel) treeModel.get();
if (model != null)
{
model.refreshInspectorNodes();
//System.gc();
}
else
{
Object obj = e.getSource();
if (obj instanceof Timer)
OAVTreeModel.removeRefreshTimer((Timer) obj);
// System.err.println("removed timer! - " + obj);
}
}
}