/* ========================
* JSynoptic : a free Synoptic editor
* ========================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 2001-2007, by :
* Corporate:
* EADS Astrium
* Individual:
* Claude Cazenave
*
* $Id: AbstractNode.java,v 1.8 2008/12/17 22:37:53 cazenave Exp $
*
* Changes
* -------
* 4 janv. 08 : Initial public release
*
*/
package jsynoptic.plugins.java3d.tree;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.MissingResourceException;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
/**
* A tree node to hold a SceneGraph object
* and provide dedicated feature
*/
public abstract class AbstractNode extends DefaultMutableTreeNode {
/**
* Node resources : icons
*/
final static HashMap<String,Icon> _icons=new HashMap<String,Icon>();
/**
* Node resources : descriptions
*/
final static HashMap<String,String> _descriptions=new HashMap<String, String>();
/**
* Node class according to SceneGraphObject class
*/
final static HashMap<String,Class<? extends AbstractNode>> _nodeClasses=new HashMap<String, Class<? extends AbstractNode>>();
/**
* Add a node class name to be used to display a scene graph object in the tree
* @param className scene graph object class name
* @param nodeClass the class of the node to use
*/
public static void addNodeClass(String className, Class<? extends AbstractNode> nodeClass){
_nodeClasses.put(className, nodeClass);
}
/**
* Add resources to display in the tree a scene graph object
* The loaded resources are the icon and the description
* @param className scene graph object class name
* @param id the key to look for the resources
*/
public static void addResources(String className, String id){
// icons are fixed wrt i18n
// Tree.resources provides file URL only
try{
URL url=Tree.resources.getClass().getResource(id+".png");
if(url!=null){
ImageIcon ic=new ImageIcon(url);
_icons.put(className, ic);
}
}
catch(MissingResourceException e){
}
_descriptions.put(className, Tree.resources.getStringValue(id));
}
/**
* Add resources to display in the tree a scene graph object
* @param className scene graph object class name
* @param icon the icon to display
* @param description the description to display
*/
public static void addResource(String className, Icon icon, String description){
_icons.put(className, icon);
_descriptions.put(className, description);
}
/**
* Get a node description according to the class name
*/
public static String getDescription(Object graphObject){
if(graphObject==null) return null; // root node
String res=_descriptions.get(graphObject.getClass().getName());
if(res==null){
// look for sub types
Class<?> c=graphObject.getClass().getSuperclass();
while(c!=Object.class){
res=_descriptions.get(c.getName());
if(res!=null){
// keep for next call
_descriptions.put(graphObject.getClass().getName(), res);
break;
}
c=c.getSuperclass();
}
}
return res;
}
/**
* Get a node icon according to the graph object class name
*/
public static Icon getIcon(Object graphObject){
if(graphObject==null) return null; // root node
Icon res=_icons.get(graphObject.getClass().getName());
if(res==null){
// look for sub types
Class<?> c=graphObject.getClass().getSuperclass();
while(c!=Object.class){
res=_icons.get(c.getName());
if(res!=null){
// keep for next call
_icons.put(graphObject.getClass().getName(), res);
break;
}
c=c.getSuperclass();
}
}
return res;
}
/**
* The tree owner of this node
*/
private final Tree _tree;
/**
* The related scene graph object or null if root
*/
private Object _graphObject;
/**
* This node icon
*/
Icon _icon;
/**
* This node name
*/
protected String _name;
/**
* Latest rendered name used to detect changes
*/
String _lastName;
/**
* This node description
*/
String _description;
/**<b>(boolean)</b> _knownChildren: determines whether the children of the current object can be displayed or not*/
private boolean _knownChildren = false;
/**
* @param graphObject
* @param getChildren
*/
protected AbstractNode(Tree tree, Object graphObject, boolean getChildren) {
_tree=tree;
_graphObject=graphObject;
_lastName=null;
// by default name is equal to class name
String n=(graphObject==null) ? "" : graphObject.getClass().getName();
int k=n.lastIndexOf('.');
_name=n.substring(k+1);
_icon=getIcon(_graphObject);
_description=getDescription(_graphObject);
if(getChildren){
refresh();
}
}
public String getName(){
return _name;
}
/**
* Get the list of children of this scene graph object
* @param list the list to fill with the scene graph children
*/
protected abstract void getSceneGraphChildren(ArrayList<Object> list);
/**
* According to a child scene graph object, returns the class
* of the Node to hold it in the Tree
* @param sceneGraphObject a child object of this Node scene graph object
* @return the class to use to create the Node
* @see createNode
*/
protected Class<? extends AbstractNode> getChildrenNodeClass(Object sceneGraphObject){
return getChildrenNodeClass(sceneGraphObject.getClass());
}
/**
* According to a child scene graph object class, returns the class
* of the Node to hold it in the Tree
* @param sceneGraphObjectClass a child object class of this Node scene graph object
* @return the class to use to create the Node
* @see createNode
*/
@SuppressWarnings("unchecked")
protected Class<? extends AbstractNode> getChildrenNodeClass(Class<?> sceneGraphObjectClass){
String className=sceneGraphObjectClass.getName();
Class<? extends AbstractNode> res=_nodeClasses.get(className);
if(res==null){
// look for a class with Node in this package
int k=className.lastIndexOf('.');
className="jsynoptic.plugins.java3d.tree."+className.substring(k+1)+"Node";
Class<?> cf=null;
try {
cf=Class.forName(className);
} catch (ClassNotFoundException e) {
}
if(cf!=null && AbstractNode.class.isAssignableFrom(cf)){
res=(Class<? extends AbstractNode>) cf;
_nodeClasses.put(sceneGraphObjectClass.getName(), res); // for next use
}
}
if(res==null){
// look for sub types
Class<?> c=sceneGraphObjectClass.getSuperclass();
if(c!=Object.class){
res=getChildrenNodeClass(c);
}
}
if(res==null){
res=SceneGraphTreeNode.class;
_nodeClasses.put(sceneGraphObjectClass.getName(), res); // for next use
}
return res;
}
public synchronized void refresh(){
_knownChildren=true;
// 1) get the list of children in the scene graph
ArrayList<Object> sceneList=new ArrayList<Object>();
getSceneGraphChildren(sceneList);
// 2) get list of children in the tree
HashMap<Object, AbstractNode> map=new HashMap<Object, AbstractNode>();
ArrayList<Object> treeList=new ArrayList<Object>();
int k=getChildCount();
for(int i=0;i<k;i++){
AbstractNode n=(AbstractNode)getChildAt(i);
treeList.add(n._graphObject);
map.put(n._graphObject, n);
}
// 3) check if some to be removed and do remove
treeList.removeAll(sceneList);
AbstractNode[] removedNodes=new AbstractNode[treeList.size()];
int[] removedNodesIndex=new int[treeList.size()];
int j=0;
for(Object r : treeList){
AbstractNode n=map.get(r);
removedNodes[j]=n;
removedNodesIndex[j]=getIndex(n);
j++;
remove(map.get(r));
}
// 4) check if some to be added and do add
treeList=new ArrayList<Object>();
k=getChildCount();
for(int i=0;i<k;i++){
AbstractNode n=(AbstractNode)getChildAt(i);
treeList.add(n._graphObject);
}
sceneList.removeAll(treeList);
int[] ci=new int[sceneList.size()];
int i=0;
for(Object r : sceneList){
ci[i]=k+i;
i++;
add(createNode(r));
}
// 5) notify tree node changes
// if some added and some removed node structure change
// if only some removed or some added notify parent accordingly
if(ci.length>0){
if(removedNodes.length>0){
_tree._treeModel.nodeStructureChanged(this);
}
else{
if(getChildCount()==1){
// first node : expand it to force display
_tree.expandPath(new TreePath(this));
}
_tree._treeModel.nodesWereInserted(this, ci);
}
}
else if(removedNodes.length>0){
_tree._treeModel.nodesWereRemoved(this, removedNodesIndex, removedNodes);
}
// 6) get node changes i.e. name has changed
if(_lastName!=null && _lastName.equals(getName())){
_tree._treeModel.nodeChanged(this);
}
}
protected AbstractNode createNode(Object graphObject){
Class<?> c=getChildrenNodeClass(graphObject);
if(c==null){
throw new RuntimeException("Invalid child "+graphObject.getClass().getName() + " for Node "+getClass().getName());
}
AbstractNode res=null;
Exception ex=null;
try {
Constructor<?> ctor=c.getConstructor(Tree.class, Object.class, boolean.class);
Object o=ctor.newInstance(_tree, graphObject, false);
if(o instanceof AbstractNode){
res=(AbstractNode)o;
}
} catch (SecurityException e) {
ex=e;
} catch (NoSuchMethodException e) {
ex=e;
} catch (IllegalArgumentException e) {
ex=e;
} catch (InstantiationException e) {
ex=e;
} catch (IllegalAccessException e) {
ex=e;
} catch (InvocationTargetException e) {
ex=e;
}
if(res==null){
if(ex!=null){
throw new RuntimeException("Invalid Node class "+c.getName(),ex);
}else{
throw new RuntimeException("Invalid Node class "+c.getName());
}
}
return res;
}
public boolean getAllowsChildren() {
if (!_knownChildren){
refresh();
}
return super.getAllowsChildren();
}
public int getChildCount(){
if (!_knownChildren){
refresh();
}
return super.getChildCount();
}
/**
* @return Returns the _tree.
*/
public Tree getTree() {
return _tree;
}
/**
* @return Returns the _graphObject.
*/
Object getGraphObject() {
return _graphObject;
}
}