/*
* This software and supporting documentation were developed by
*
* Siemens Corporate Technology
* Competence Center Knowledge Management and Business Transformation
* D-81730 Munich, Germany
*
* Authors (representing a really great team ;-) )
* Stefan B. Augustin, Thorbj�rn Hansen, Manfred Langen
*
* This software is Open Source under GNU General Public License (GPL).
* Read the text of this license in LICENSE.TXT
* or look at www.opensource.org/licenses/
*
* Once more we emphasize, that:
* THIS SOFTWARE IS MADE AVAILABLE, AS IS, WITHOUT ANY WARRANTY
* REGARDING THE SOFTWARE, ITS PERFORMANCE OR
* FITNESS FOR ANY PARTICULAR USE, FREEDOM FROM ANY COMPUTER DISEASES OR
* ITS CONFORMITY TO ANY SPECIFICATION. THE ENTIRE RISK AS TO QUALITY AND
* PERFORMANCE OF THE SOFTWARE IS WITH THE USER.
*
*/
// KFM_HierarchyManager
// ************ package ******************************************************
package KFM;
// ************ imports ******************************************************
import KFM.Exceptions.*;
// java packages
import java.util.Hashtable;
import java.util.Vector;
/** Cache and navigation utility class for any hierarchical (tree-like) structure.
*
* <H2>Features</H2>
*
* <P> Objects of this class can store all or part of the nodes of a tree-like structure.
* Each node is identified by a unique attribute (of class Object, maybe an Id or a name).
* Each node also has a father node (of the same class).
* Additionally, each node can have further attributes of any type.</P>
*
* <P> Class KFM_HierarchyManager offers methods to fill the hierarchy with nodes
* and to navigate from a node upwards in the hierarchy. It does currently <B>not</B>
* offer methods to navigate downwards a tree.</P>
*
* <H2> Usage </H2>
*
* <P> This class is used in SieMap to cache category pathes (to avoid repeated
* SELECT statements to the Portal DB for categories where the path info is already fetched).
* See Portal_ApplicationPage.constructPath() and PortalDb.getCategoryPath() </P>
*
* <P> For a further usage example, see method main(). </P>
*
* <H2> To do </H2>
*
* <P> The first version of this class only has methods that were needed in the first usage.
* More methods come to mind, but I leave room for this class to grow :-) </P>
*
* <P> This method should be made threadsafe later. </P>
*
* @version 1.0 (2000 09 05)
*/
public class KFM_HierarchyManager
{
// ************************************************************
// Inner classes
// ************************************************************
/** KFM_HierarchyNode holds the information of a node of KFM_HierarchyManager,
* which is the identification of its father node and the list of its further attributes.
*
* @see KFM_HierarchyManager
*/
static public class KFM_HierarchyNode {
public Object mFatherId;
public Hashtable mAttributes;
}
// ************************************************************
// Variables
// ************************************************************
/**
* The container for all the nodes of a KFM_HierarchyManager object.
* Each key is the identification attribute of a node, each value is a KFM_HierarchyNode
*/
protected Hashtable mNodeContainer;
// ************************************************************
// Methods
// ************************************************************
/**
* Create an empty KFM_HierarchyManager.
*/
public KFM_HierarchyManager ()
{
mNodeContainer = new Hashtable();
}
/**
* Either create a new node with a given identification and father
* or (if anId is already present) change the father of the given node.
* If the node has no father, simply provide a null value.
*
* @param anId the identification attribute of the node
* @param aFatherId the identification attribute of the father node (or null)
*/
public void createOrSetNode (Object anId, Object aFatherId)
{
createOrSetNode(anId, aFatherId, new Hashtable());
}
/**
* Either create a new node with a given identification, father and further attributes
* or (if anId is already present) change father and further attributes of the given node.
* If the node has no father or no attributes, simply provide null values.
*
* @param anId the identification attribute of the node
* @param aFatherId the identification attribute of the father node (or null)
* @param anAttributeList the list of named attributes of the node (or null),
* the keys are Strings, the values may be of any type
*/
public void createOrSetNode (Object anId, Object aFatherId, Hashtable anAttributeList)
{
KFM_HierarchyNode tNode = new KFM_HierarchyNode();
tNode.mFatherId = aFatherId;
tNode.mAttributes = anAttributeList;
mNodeContainer.put(anId, tNode);
}
/**
* Either create a new node with a given identification, father and one furher attribute
* or (if anId is already present) change father and the further attribute of the given node.
* If the node has no father, simply provide a null value.
*
* @param anId the identification attribute of the node
* @param aFatherId the identification attribute of the father node (or null)
* @param anAttribute the name of a further attribute of the node (not null)
* @param aValue the value of a further attribute of the node (not null)
*/
public void createOrSetNode (Object anId, Object anFatherId, String anAttribute, Object aValue)
{
Hashtable tAttributes = new Hashtable();
tAttributes.put(anAttribute, aValue);
createOrSetNode(anId, anFatherId, tAttributes);
}
/**
* For a given node, return the identification attribute of the father node.
*
* @param anId the identification attribute of the node
* @return the identification attribute of the father node (or null if the node has no father)
* @exception KFM_NoSuchObjectException if no node is contained for anId
*/
public Object getFatherNode (Object anId) throws KFM_NoSuchObjectException
{
Object tNode = mNodeContainer.get(anId);
if (tNode == null)
throw new KFM_NoSuchObjectException(anId);
return ((KFM_HierarchyNode)tNode).mFatherId;
}
/**
* For a given node, return the attribute list.
*
* @param anId the identification attribute of the node
* @return the attribute list of the node (or null if the node has no attributes)
* @exception KFM_NoSuchObjectException if no node is contained for anId
*/
public Hashtable getAttributes (Object anId) throws KFM_NoSuchObjectException
{
Object tNode = mNodeContainer.get(anId);
if (tNode == null)
throw new KFM_NoSuchObjectException(anId);
return ((KFM_HierarchyNode)tNode).mAttributes;
}
/**
* For a given node, return the value of a specific attribute.
*
* @param anId the identification attribute of the node
* @param aName the name of the specific attribute
* @return the attribute value of the node (or null if the node does not have this attribute)
* @exception KFM_NoSuchObjectException if no node is contained for anId
*/
public Object getAttribute (Object anId, String aName) throws KFM_NoSuchObjectException
{
Hashtable tAttributes = getAttributes(anId);
if (tAttributes == null)
return null;
return tAttributes.get(aName);
}
/**
* For a given node, set the value of a specific attribute.
*
* @param anId the identification attribute of the node
* @param aName the name of the specific attribute
* @param aValue the value of the specific attribute
* @exception KFM_NoSuchObjectException if no node is contained for anId
*/
public void setAttribute (Object anId, String aName, Object aValue) throws KFM_NoSuchObjectException
{
Hashtable tAttributes = getAttributes(anId);
tAttributes.put(aName, aValue);
}
/**
* For a Vector of given nodes, return a Vector with the values of a specific attribute of these nodes.
*
* <P> Note: You can combine this with getIdPath() to get a specific attribute for a node and
* all its direct and indirect father nodes. Use it like this: </P>
* <PRE>
* Vector tIds = mHierarchyMgr.getIdPath(tId, true, -1);
* Vector tTitles = mHierarchyMgr.getAttribute(tIds, "Name");
* </PRE>
*
* @param anIdVector the Vector with the identification attribute of the nodes
* @param aName the name of the specific attribute
* @return the Vector of attribute values of the node (a Vector entry can also be null)
* @exception KFM_NoSuchObjectException if no node is contained for one of the Ids in anIdVector
*/
public Vector getAttribute (Vector anIdVector, String aName) throws KFM_NoSuchObjectException
{
Vector tResult = new Vector();
// handle special case
if (anIdVector == null)
return tResult;
Hashtable tAttributes;
int tSize = anIdVector.size();
for (int i=0; i<tSize; i++) {
// May throw KFM_NoSuchNodeException
tAttributes = getAttributes(anIdVector.elementAt(i));
if (tAttributes == null) {
tResult.addElement(null);
} else {
tResult.addElement(tAttributes.get(aName));
}
}
return tResult;
}
/**
* For a given node, return a Vector with the identification attributes of the node and all the
* direct and indirect father nodes.
* Note: this method does take care of infinite loops by stopping after a given number of steps.
* Use the slower getIdPathLoopSafe() if you want an Exception to happen in case of loops.
*
* @param anId the identification attribute of the node
* @param aNodeFirstFlag if true, the Vector is filled in that the node is the first element,
* if false, the node will be the last element
* @param aSizeLimit the maximum size of the resulting Vector (this can be used to avoid loops) or -1
* @return the Vector with identification attributes of the node and all the father nodes
* @exception KFM_NoSuchObjectException if no node is contained for anId (or any father node Id,
* in which case the tree is corrupted)
*/
public Vector getIdPath (Object anId, boolean aNodeFirstFlag, int aSizeLimit)
throws KFM_NoSuchObjectException
{
if (anId == null)
throw new KFM_NoSuchObjectException();
Vector tAllIds = new Vector();
if (aSizeLimit == 0)
return tAllIds;
tAllIds.addElement(anId);
// May throw KFM_NoSuchNodeException
Object tCurrentFather = getFatherNode(anId);
boolean tApplySizeLimit = (aSizeLimit >= 0);
while ((tCurrentFather != null) && (!tApplySizeLimit || tAllIds.size() < aSizeLimit)) {
if (aNodeFirstFlag) {
// append next father at the end of the Vector
tAllIds.addElement(tCurrentFather);
} else {
// insert next father at the beginning of the Vector
tAllIds.insertElementAt(tCurrentFather, 0);
}
// May throw KFM_NoSuchNodeException
tCurrentFather = getFatherNode(tCurrentFather);
}
return tAllIds;
}
/**
* For a given node, return a Vector with the identification attributes of the node and
* all the direct and indirect father nodes.
* Note: this method does take care of infinite loops by checking the father chain
* (two father nodes are considered to be the same, if they are equal(), not only ==).
* Use the faster getIdPath() if you want to avoid loops by limiting the Vector size.
*
* @param anId the identification attribute of the node
* @param aNodeFirstFlag if true, the Vector is filled in that the node is the first element,
* if false, the node will be the last element
* @return the Vector with identification attributes of the node and all the father nodes
* @exception KFM_NoSuchObjectException if no node is contained for anId (or any father node Id,
* in which case the tree is corrupted)
* @exception KFM_InfiniteLoopException if a loop within the father hierarchy was detected
*/
public Vector getIdPathLoopSafe (Object anId, boolean aNodeFirstFlag)
throws KFM_NoSuchObjectException, KFM_InfiniteLoopException
{
if (anId == null)
throw new KFM_NoSuchObjectException();
Vector tAllIds = new Vector();
tAllIds.addElement(anId);
// May throw KFM_NoSuchNodeException
Object tCurrentFather = getFatherNode(anId);
while (tCurrentFather != null) {
// check for a loop
for (int i = tAllIds.size()-1; i >= 0; i--) {
if (tCurrentFather.equals(tAllIds.elementAt(i))) {
throw new KFM_InfiniteLoopException(anId);
}
}
if (aNodeFirstFlag) {
// append next father at the end of the Vector
tAllIds.addElement(tCurrentFather);
} else {
// insert next father at the beginning of the Vector
tAllIds.insertElementAt(tCurrentFather, 0);
}
// May throw KFM_NoSuchNodeException
tCurrentFather = getFatherNode(tCurrentFather);
}
return tAllIds;
}
/**
* Tests if the specified node is contained in the hierarchy.
*
* @param anId the identification attribute of the node
* @return <code>true</code> if the specified node is contained in the hierarchy;
* <code>false</code> otherwise.
*/
public boolean containsNode(Object anId)
{
if (anId == null)
return false;
return mNodeContainer.containsKey(anId);
}
/**
* Method for testing the functionality of KFM_HierarchyManager.
*/
public static void main(String[] args)
{
System.out.println("Testing KFM_HierarchyManager.");
System.out.println("=============================");
KFM_HierarchyManager tManager = new KFM_HierarchyManager();
// Create node 500; no father; on attribute "Title" with value "Yeah"
tManager.createOrSetNode(new Long(500), null, "Title", "Yeah");
System.out.println("Creating node 500 o.k.");
// Create some further leave nodes, all with a Hashtable full of attributes
// (for simplicity, we use the same Hashtable)
Hashtable tAttributes = new Hashtable();
tAttributes.put("Title", "Yogi");
tAttributes.put("Summary", "A very dangerous bear");
tManager.createOrSetNode(new Long(501), null, tAttributes);
System.out.println("Creating node 501 o.k.");
tManager.createOrSetNode(new Long(502), null, tAttributes);
System.out.println("Creating node 502 o.k.");
tManager.createOrSetNode(new Long(503), null, tAttributes);
System.out.println("Creating node 503 o.k.");
// Create two child nodes, all with a Hashtable full of attributes
tManager.createOrSetNode(new Long(504), new Long(500), tAttributes);
System.out.println("Creating node 504 as a child of node 500 o.k.");
tManager.createOrSetNode(new Long(505), new Long(504), tAttributes);
System.out.println("Creating node 505 as a child of node 504 o.k.");
// Test if node 504 is contained
if (tManager.containsNode(new Long(504))) {
System.out.println("Testing for the presence of node 504; it is there");
} else {
System.out.println("Testing for the presence of node 504; oops, it is not there");
}
// Test if node 506 is contained
if (tManager.containsNode(new Long(506))) {
System.out.println("Testing for the presence of node 506; oops, it is there");
} else {
System.out.println("Testing for the presence of node 506; it is not there");
}
// Get all fathers of node 505 as a Vector, direct father first
try {
Vector tFathers = tManager.getIdPath(new Long(505), true, -1);
System.out.println("All fathers of node 505 are:");
// Print them
for (int i=0; i< tFathers.size(); i++)
System.out.println(" " + (Long) (tFathers.elementAt(i)));
} catch (KFM_NoSuchObjectException ex) {
System.out.println("Oops, caught KFM_NoSuchNodeException");
}
// Get all fathers of node 505 as a Vector, direct father last
try {
Vector tFathers = tManager.getIdPath(new Long(505), false, -1);
System.out.println("All fathers of node 505 (reverse order) are:");
// Print them
for (int i=0; i< tFathers.size(); i++)
System.out.println(" " + (Long) (tFathers.elementAt(i)));
} catch (KFM_NoSuchObjectException ex) {
System.out.println("Oops, caught KFM_NoSuchNodeException");
}
// Introduce a loop into the hierarchy
tManager.createOrSetNode(new Long(500), new Long(505), tAttributes);
System.out.println("Creating node 500 as a child of node 505 (this is a loop) o.k.");
// Get all fathers of node 505 as a Vector, direct father first
// This may result in an endless loop, so limit the result Vector
try {
Vector tFathers = tManager.getIdPath(new Long(505), true, 10);
System.out.println("All fathers of node 505 are:");
// Print them
for (int i=0; i< tFathers.size(); i++)
System.out.println(" " + (Long) (tFathers.elementAt(i)));
} catch (KFM_NoSuchObjectException ex) {
System.out.println("Oops, caught KFM_NoSuchNodeException");
}
// Now use getAllFathersLoopSafe() which must throw an KFM_HierarchyLoopException
try {
Vector tFathers = tManager.getIdPathLoopSafe(new Long(505), true);
System.out.println("All fathers of node 505 are:");
// Print them
for (int i=0; i< tFathers.size(); i++)
System.out.println(" " + (Long) (tFathers.elementAt(i)));
} catch (KFM_NoSuchObjectException ex) {
System.out.println("Oops, caught KFM_NoSuchNodeException");
} catch (KFM_InfiniteLoopException ex) {
System.out.println("Yes, we caught KFM_HierarchyLoopException");
}
// Get all attributes called "Title" from node 505 and its fathers
try {
// Careful, we still have the loop
Vector tFathers = tManager.getIdPath(new Long(505), true, 10);
Vector tTitles = tManager.getAttribute(tFathers, "Title");
System.out.println("All titles of node 505 (and its fathers) are:");
// Print them
for (int i=0; i< tTitles.size(); i++)
System.out.println(" " + (String) (tTitles.elementAt(i)));
} catch (KFM_NoSuchObjectException ex) {
System.out.println("Oops, caught KFM_NoSuchNodeException");
}
}
}