/*
* This software is Copyright 2005,2006,2007,2008 Langdale Consultants.
* Langdale Consultants can be contacted at: http://www.langdale.com.au
*/
package au.com.langdale.jena;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import au.com.langdale.ui.util.NodeTraits;
import au.com.langdale.kena.OntResource;
import com.hp.hpl.jena.graph.FrontsNode;
public class TreeModelBase {
/**
* Construct an initially empty tree.
*
*/
public TreeModelBase() {
}
public interface NodeListener {
void nodeChanged(Node node);
void nodeStructureChanged(Node node);
}
private NodeListener listener;
public static int create_count;
public void setNodeListener(NodeListener listener) {
this.listener = listener;
}
private Node root;
/**
* All tree nodes provided by this model extend this class.
* It implements the TreeNode interface required by DefaultTreeModel
* and in turn provides cut-out methods written in terms of
* Jena types for concrete tree nodes.
*/
public abstract class Node implements Comparable, NodeTraits {
private Node parent;
private ArrayList members;
protected Node() {
create_count++;
}
public List getChildren() {
materialise();
return members;
}
/** See javax.swing.tree.TreeNode */
public Node getParent() {
return parent;
}
public Node[] getPath(boolean includeRoot) {
ArrayList path = new ArrayList();
Node next = this;
do {
path.add(next);
next = next.getParent();
} while(next != null);
int size = path.size() - (includeRoot? 0: 1);
Node[] result = new Node[size];
for(int ix = 0; ix < size; ix++)
result[size - ix - 1] = (Node) path.get(ix);
return result;
}
/** See javax.swing.tree.TreeNode */
public boolean isLeaf() {
return members != null && members.size() == 0;
}
/**
* This is the central method of all tree nodes.
* It is called on demand to create and sort the
* child nodes.
*/
private void materialise() {
if( members != null)
return;
members = new ArrayList();
populate();
Collections.sort(members);
}
/**
* Add one subordinate node. To be called from populate().
*/
protected void add(Node node) {
if( node != null ) {
node.parent = this;
members.add(node);
}
}
/**
* Refresh this node.
*
*/
public void changed() {
nodeChanged(this);
}
/**
* Refresh the tree below this point.
*
*/
public void structureChanged() {
members = null;
nodeStructureChanged(this);
}
/**
* Iterate the children of this node.
*/
public Iterator iterator() {
materialise();
return members.iterator();
}
/**
* Find the child node with the given subject.
*/
public Node findChild(OntResource subject) {
Iterator jt = iterator();
while(jt.hasNext()) {
Node cand = (Node) jt.next();
if(cand.getSubject().equals(subject))
return cand;
}
return null;
}
/**
* Adopt the children of another node. To be called from populate().
*/
protected void adopt(Node node) {
for (Iterator it = node.iterator(); it.hasNext();) {
add((Node) it.next());
}
node.members = null;
}
/**
* Override in concrete node classes to add() child nodes.
*/
protected abstract void populate();
/**
* Natural sort order is used for Nodes.
*/
public int compareTo(Object other) {
if( other instanceof Node)
return collation().compareTo(((Node)other).collation());
else
return 1;
}
/**
* Override in concrete nodes to control sort order.
* @return a String that will serve as the sort key.
*/
protected String collation() {
return toString();
}
/**
* Access the ontology resource underlying the tree Node.
* @return the onology resource
*/
abstract public OntResource getSubject();
/**
* Determine whether there are semantic errors or conflicts
* associated with the resource underlying the tree Node.
*/
abstract public boolean getErrorIndicator();
/**
* The simple identifier for this node.
* @return
*/
public String getName() {
return label(getSubject());
}
/**
* Access an information model resource associated with this node.
* For most nodes this is the same as getSubject(). For nodes in
* a profile model, this is the resource being profiled rather
* than the profile itself.
*
*/
public OntResource getBase() {
return getSubject();
}
@Override
public String toString() {
return getName();
}
/**
* A class whose name selects the icon for the node.
*/
public Class getIconClass() {
return getClass();
}
public boolean getAllowsChildren() {
return true;
}
/**
* Indicates that the tree should be pruned below this node
* to avoid infinite recursion while ensuring all nodes are represented.
*
* @return
*/
public boolean isPruned() {
return false;
}
}
/**
* Empty node represents no resource but carries a message.
*
*
*/
public class Empty extends Node {
private String message;
public Empty(String message) {
this.message = message;
}
@Override
public String toString() {
return message;
}
@Override
public OntResource getSubject() {
return null;
}
@Override
public boolean getAllowsChildren() {
return false;
}
@Override
public boolean getErrorIndicator() {
return false;
}
@Override
protected void populate() {
// no children
}
}
/**
* Create a placemarker node that displays a message.
*/
public Node createEmpty(String message) {
return new Empty(message); // FIXME: never used?
}
/**
* Utility to format a user label text for a resource.
*/
public static String label(OntResource subject) {
if( subject == null)
return "Unknown";
String result = subject.getLabel();
if( result != null)
return result;
if( subject.isAnon())
return "Unnamed";
return subject.getLocalName();
}
public void nodeStructureChanged(Node node) {
if( listener != null)
listener.nodeStructureChanged(node);
}
public void nodeChanged(Node node) {
if( listener != null)
listener.nodeChanged(node);
}
/**
* Utility to format a user label text specifically for a property.
*/
public static String prop_label(OntResource subject) {
String pname = label(subject);
String tname = label(subject.getRange());
if( pname.equals(tname) || pname.equals(tname + "s"))
return pname;
else
return pname + ": " + tname;
}
public Node getRoot() {
return root;
}
public void setRoot(Node root) {
this.root = root;
nodeStructureChanged(root);
}
/**
* Construct a list of resources representing a path starting
* from the root resource of this tree to the given target.
*/
protected List findResourcePathTo(FrontsNode target) {
return null;
}
/**
* Find the node representing the given resource and
* construct a path from the root of the tree to that node.
*
* This is an "informed" search that takes advantage of
* the defined structure of the tree.
*/
public Node[] findPathTo(FrontsNode target, boolean includeRoot) {
List rpath = findResourcePathTo(target);
if( rpath == null )
return null;
Node[] path;
int ix;
if( includeRoot ) {
path = new Node[rpath.size()];
path[0] = getRoot();
ix = 1;
}
else {
path = new Node[rpath.size()-1];
ix = 0;
}
Node parent = getRoot();
Iterator it = rpath.iterator();
it.next(); // skip root resource which is not displayed
while( it.hasNext()) {
OntResource res = (OntResource) it.next();
Node found = parent.findChild(res);
if( found == null )
return null;
path[ix++] = found;
parent = found;
}
return path;
}
}