/********************************************************************************
* *
* (c) Copyright 2010 Verizon Communications USA and The Open University UK *
* *
* This software is freely distributed in accordance with *
* the GNU Lesser General Public (LGPL) license, version 3 or later *
* as published by the Free Software Foundation. *
* For details see LGPL: http://www.fsf.org/licensing/licenses/lgpl.html *
* and GPL: http://www.fsf.org/licensing/licenses/gpl-3.0.html *
* *
* This software is provided by the copyright holders and contributors "as is" *
* and any express or implied warranties, including, but not limited to, the *
* implied warranties of merchantability and fitness for a particular purpose *
* are disclaimed. In no event shall the copyright owner or contributors be *
* liable for any direct, indirect, incidental, special, exemplary, or *
* consequential damages (including, but not limited to, procurement of *
* substitute goods or services; loss of use, data, or profits; or business *
* interruption) however caused and on any theory of liability, whether in *
* contract, strict liability, or tort (including negligence or otherwise) *
* arising in any way out of the use of this software, even if advised of the *
* possibility of such damage. *
* *
********************************************************************************/
package com.compendium.core.datamodel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.util.Vector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.compendium.core.ICoreConstants;
/**
* This object store the nodes and views being used in the current session
*
* @author Michelle Bachler
*/
public class NodeCache implements PropertyChangeListener {
/** logger for NodeCache.class */
final Logger log = LoggerFactory.getLogger(this.getClass());
/** Stores the nodes and views against thier ids*/
private Hashtable htNodeSummary = null;
/** Stores the node/view ids against thier use count*/
private Hashtable htNodeSummaryCount = null;
/**
* Constructor.
*/
public NodeCache() {
htNodeSummary = new Hashtable(51);
htNodeSummaryCount = new Hashtable(51);
}
/**
* Adds a node/view to the cache
* If the node/view is already present it increases its reference by one
*
* @param PCObject object, the NodeSummary or View to add to the cache.
* @return boolean, true if the object passed was successfully added (was a NodeSummary or View Object).
*/
public boolean put(PCObject object) {
if(object instanceof View) {
View view = (View)object;
String sViewID = view.getId();
if(!htNodeSummary.containsKey(sViewID)) {
htNodeSummary.put(sViewID, view);
view.addPropertyChangeListener((PropertyChangeListener)this);
htNodeSummaryCount.put(sViewID, new Integer(1));
return true;
}
else {
int referenceCount = ((Integer)htNodeSummaryCount.get(sViewID)).intValue();
referenceCount++;
htNodeSummaryCount.put(sViewID, new Integer(referenceCount));
return true;
}
}
else if(object instanceof NodeSummary) {
NodeSummary node = (NodeSummary)object;
String sNodeID = node.getId();
if(!htNodeSummary.containsKey(sNodeID)) {
htNodeSummary.put(sNodeID, node);
node.addPropertyChangeListener((PropertyChangeListener)this);
htNodeSummaryCount.put(sNodeID, new Integer(1));
return true;
}
else {
int referenceCount = ((Integer)htNodeSummaryCount.get(sNodeID)).intValue();
referenceCount++;
htNodeSummaryCount.put(sNodeID, new Integer(referenceCount));
return true;
}
}
else {
return false;
}
}
/**
* Removes a node/view from the cache.
* If the node/view is already present it decreases its reference by one.
* If the referencecount goes to zero it removes it from the cache.
*
* @param PCObject object, the NodeSummary or View to remove from to the cache.
* @return boolean, true if the object passed was successfully removed (was a NodeSummary or View Object and was found).
*/
public boolean remove(PCObject object) {
if(object instanceof View) {
View view = (View)object;
String sViewID = view.getId();
if(!htNodeSummary.containsKey(sViewID)) {
return false;
}
else {
int referenceCount = ((Integer)htNodeSummaryCount.get(sViewID)).intValue();
referenceCount--;
if(referenceCount == 0) {
htNodeSummary.remove(sViewID);
view.removePropertyChangeListener((PropertyChangeListener)this);
htNodeSummaryCount.remove(sViewID);
return true;
}
else {
htNodeSummaryCount.put(sViewID, new Integer(referenceCount));
return true;
}
}
}
else if(object instanceof NodeSummary) {
NodeSummary node = (NodeSummary)object;
String sNodeID = node.getId();
if(!htNodeSummary.containsKey(sNodeID)) {
return false;
}
else {
int referenceCount = ((Integer)htNodeSummaryCount.get(sNodeID)).intValue();
referenceCount--;
if(referenceCount == 0) {
htNodeSummary.remove(sNodeID);
node.removePropertyChangeListener((PropertyChangeListener)this);
htNodeSummaryCount.remove(sNodeID);
return true;
}
else {
htNodeSummaryCount.put(sNodeID, new Integer(referenceCount));
return true;
}
}
}
else {
return false;
}
}
/**
* Replace the given oldObject with the given newObject.
* @param PCObject oldObject, the NodeSummary or View to replace in the cache.
* @param PCObject newObject, the NodeSummary or View to replace with in the cache.
* @return boolean, true if the object was replaced (was a NodeSummary or View Object and was found).
*/
public boolean replace(PCObject oldObject, PCObject newObject) {
NodeSummary node = (NodeSummary)oldObject;
String sID = node.getId();
if(!htNodeSummary.containsKey(sID)) {
return false;
}
else {
//node.removePropertyChangeListener((PropertyChangeListener)this); // BREAKS LOOP SENDING CHANGE EVENTS
htNodeSummary.put(sID, newObject);
((NodeSummary)newObject).addPropertyChangeListener((PropertyChangeListener)this);
return true;
}
}
/**
* Returns the number of references to the node/view else if no object found returns -1.
*
* @param PCObject object, the NodeSummary or View to get the reference count for.
* @return int, the count of the use of the node or view else -1.
*/
public int getCount(PCObject object) {
if(object instanceof View) {
View view = (View)object;
String sViewID = view.getId();
if(htNodeSummaryCount.containsKey(sViewID)) {
int referenceCount = ((Integer)htNodeSummaryCount.get(sViewID)).intValue();
return referenceCount;
}
else {
return -1;
}
}
else if(object instanceof NodeSummary) {
NodeSummary node = (NodeSummary)object;
String sNodeID = node.getId();
if(htNodeSummaryCount.containsKey(sNodeID)) {
int referenceCount = ((Integer)htNodeSummaryCount.get(sNodeID)).intValue();
return referenceCount;
}
else {
return -1;
}
}
else {
return -1;
}
}
/**
* Returns a list of View objects in the cache.
*
* @return Enumeration, a list of the IView object help in the cache.
*/
public Enumeration getViews() {
Vector views = new Vector();
for(Enumeration e = htNodeSummary.elements();e.hasMoreElements();) {
NodeSummary node = (NodeSummary)e.nextElement();
if(node instanceof View)
views.addElement(node);
}
return views.elements();
}
/**
* Returns the node/view with the given id.
*
* @return NodeSummary, the node/view with the given id.
*/
public NodeSummary getNode(String sID) {
if(htNodeSummary.containsKey(sID)); {
return (NodeSummary)htNodeSummary.get(sID);
}
}
/**
* This method is to be called by links to get the model's from and to nodeSummary
* to update themselves for property change events for from and to node changes.
* The method takes the id of the input nodeSummary and checks it against its own
* hashtable and then returns the nodeSummary with that id. If it does not exist
* it throws a NoSuchElement exception.
*
* @param NodeSummary oNode, the node for which the return the node with the same node id.
* @return NodeSummary with the same node id as the passed NodeSummary object.
* @exception NoSuchElementException, if a node with the same id as the passed node was node found.
*/
public NodeSummary getNode(NodeSummary oNode) throws NoSuchElementException {
String sNodeID = oNode.getId() ;
if(!htNodeSummary.containsKey(sNodeID)) {
throw new NoSuchElementException("Node " + sNodeID + " not found in Model ");
}
else {
return (NodeSummary)htNodeSummary.get(sNodeID);
}
}
/**
* This method updates the cache for an array of NodeSummary objects.
* For each NodeSummary object it calles <code>addNode</code>.
*
* @param NodeSummary[] oNodes, the array of nodes to add to the cache.
* @return NodeSummary[], an array of nodes added to the cache.
*/
public NodeSummary[] addNodes(NodeSummary[] oNodes) {
NodeSummary[] nodeReferences = new NodeSummary[oNodes.length];
for(int i=0;i< oNodes.length; i++) {
nodeReferences[i] = addNode(oNodes[i]) ;
}
return nodeReferences;
}
/**
* Adds a NodeSummary object to cache or, if already in the cache, increments its use count.
*
* @param NodeSummary oNode, the node to add to the cache.
* @return NodeSummary, the node added to the cache.
*/
public NodeSummary addNode(NodeSummary oNode) {
String sNodeID = oNode.getId();
if(!htNodeSummary.contains(sNodeID)) {
htNodeSummary.put(sNodeID, oNode);
oNode.addPropertyChangeListener((PropertyChangeListener)this);
htNodeSummaryCount.put(sNodeID, new Integer(1));
return oNode;
}
else {
int count = ((Integer)htNodeSummaryCount.get(sNodeID)).intValue();
count++ ;
htNodeSummaryCount.put(sNodeID, new Integer(count));
return oNode;
}
}
/**
* This method returns an View object with the given id, if the view is in the cache
*
* @param String sID, the id of the View to look for in the cache.
* @return View, the IView object with the given id if found, else null.
*/
public View getView(String sID) {
if(htNodeSummary.containsKey(sID) && htNodeSummary.get(sID) instanceof View) {
return (View)htNodeSummary.get(sID);
}
else {
return null;
}
}
/**
* Handle a PropertyChangeEvent.
* @param evt, the associated PropertyChangeEvent to handle.
*/
public void propertyChange(PropertyChangeEvent evt) {
String prop = evt.getPropertyName();
Object source = evt.getSource();
Object oldvalue = evt.getOldValue();
Object newvalue = evt.getNewValue();
if (source instanceof NodeSummary) {
if (prop.equals(NodeSummary.NODE_TYPE_PROPERTY)) {
NodeSummary fireNode = (NodeSummary)source;
String sID = fireNode.getId();
NodeSummary oldnode = (NodeSummary)getNode(sID);
NodeSummary newnode = NodeSummary.getNodeSummary(sID);
int nNewType = ((Integer)newvalue).intValue();
int nOldType = ((Integer)oldvalue).intValue();
// IF THE NODE SHOULD CHANGE CLASS AND HAS NOT YET, CHANGE IT.
// ONLY WANT THE DATABASE READ TO HAPPEN ONCE.
// (DEPENDS ON THREAD SPEED THOUGH)
// AFTER THAT, THE NEW OBJECT CAN BE RETRIEVED FROM CACHE
String oldClassName = oldnode.getClass().getName();
String newClassName = newnode.getClass().getName();
if ( (nOldType > ICoreConstants.PARENT_SHORTCUT_DISPLACEMENT && nNewType <=
ICoreConstants.PARENT_SHORTCUT_DISPLACEMENT)
|| ( View.isViewType(nOldType)
&& !View.isViewType(nNewType))
|| ( !View.isViewType(nOldType)
&& View.isViewType(nNewType) ) ) {
// IF NOT BEEN RECREATED YET, DO IT.
if (oldClassName.equals(newClassName)) {
try {
IModel model = oldnode.getModel();
newnode = model.getNodeService().getNodeSummary(model.getSession(), oldnode.getId());
newnode.initialize(model.getSession(), model);
}
catch(Exception ex) {
log.error("Exception (NodeCache.propertyChange)", ex);
}
}
}
// If the new object is not the same as the old object e.g. switched from/to View.
// Replace the object held in the cache under that node id.
if (!oldnode.equals(newnode) && newnode != null) {
replace((PCObject)oldnode, (PCObject)newnode);
}
}
}
}
/**
* Clear the Cache.
*/
public void clear() {
htNodeSummary.clear();
htNodeSummaryCount.clear();
}
/**
* Null all variables to help with garbage collection.
*/
public void cleanUp() {
htNodeSummary.clear();
htNodeSummary = null;
htNodeSummaryCount.clear();
htNodeSummaryCount = null;
}
}