package org.nightlabs.jfire.personrelation.trade.ui.tucked; import java.util.Deque; import java.util.LinkedList; import java.util.List; import org.apache.log4j.Logger; import org.nightlabs.jdo.ObjectID; import org.nightlabs.jfire.base.ui.jdo.tree.lazy.JDOObjectLazyTreeNode; import org.nightlabs.jfire.personrelation.ui.tree.PersonRelationTreeController; import org.nightlabs.jfire.personrelation.ui.tree.PersonRelationTreeNode; import org.nightlabs.jfire.personrelation.ui.tree.PersonRelationTreeUtil; import org.nightlabs.jfire.prop.id.PropertySetID; /** * The {@link TuckedPersonRelationTreeNode} is initialised upon calling the super class's method setJdoObjectID(), where * we shall assume that up till that stage, any other references needed by the node are readily available and can be queried directly * from the related controller; i.e. in our case, the {@link TuckedPersonRelationTreeController}. * * @author khaireel (at) nightlabs (dot) de */ public class TuckedPersonRelationTreeNode extends PersonRelationTreeNode { private final static Logger logger = Logger.getLogger(TuckedPersonRelationTreeNode.class); // ------------------------------------------------------------------------------------- ++ ------------------------------->> // [Section I] Handling the TUCKED and UNTUCKED statuses, with respect to the node's childCount. // ------------------------------------------------------------------------------------- ++ ------------------------------->> // Keeps track of the actual child-count for each of the ObjectID in the tuckedPath represented by this node. private long actualChildCount = -1L; // Keeps track of the tucked-child-count for each of the ObjectID in the tuckedPath represented by this node. private long tuckedChildCount = -1L; // Keeps track of the tuckStatus for each of the ObjectID in the tuckedPath. private TuckedNodeStatus tuckedStatus = TuckedNodeStatus.NORMAL; // An indication whether or not this TuckedNode is part of the tuckedPath. The value is set only once, and upon initialisation. private boolean isPartOfTuckedPath = false; @Override public void setJdoObjectID(ObjectID jdoObjectID) { super.setJdoObjectID(jdoObjectID); TuckedPersonRelationTreeController tprtController = (TuckedPersonRelationTreeController) getActiveJDOObjectLazyTreeController(); Deque<ObjectID> subPath = tprtController.getSubPathUpUntilCurrentID(this); initTuckedNode(subPath); } /** * Initialises this TuckedNode, given a tuckedPath of {@link ObjectID}s. */ protected void initTuckedNode(Deque<ObjectID> tuckedPath) { actualChildCount = -1L; tuckedChildCount = -1L; tuckedStatus = TuckedNodeStatus.NORMAL; // Used in operational transition. See notes, and see Section III. statusToChangeTo = TuckedNodeStatus.UNSET; // Tucked-node-operational variables. ObjectID objectID = getJdoObjectID(); if (tuckedPath != null) isPartOfTuckedPath = tuckedPath.contains(objectID); if (logger.isDebugEnabled()) { logger.debug("---->> objectID: " + PersonRelationTreeUtil.showObjectID(objectID)); logger.debug(PersonRelationTreeUtil.showObjectIDs("---->> Deque.tuckedPath", tuckedPath, 10)); } // On initialisation, a tuckedNode is 'expanded' (i.e. '!isCollapsed') if it is part of the tuckedPath BUT NOT the last item on the tuckedPath. // In other words, the node isCollapsed if it is NOT part of the tuckedPath OR if it is the last item on the tuckedPath. isExpanded = objectID instanceof PropertySetID || isPartOfTuckedPath && !tuckedPath.getFirst().equals(objectID); // The Deque is reversed: The root is the last element in the Deque. } /** * Sets the actual child-count for this TuckedNode. */ public void setActualChildCount(long actualChildCount) { this.actualChildCount = actualChildCount; } /** * Sets the tucked-child-count for this TuckedNode. */ public void setTuckedChildCount(long tuckedChildCount) { this.tuckedChildCount = tuckedChildCount; } @Override public long getChildNodeCount() { if (!isNodeSet()) return super.getChildNodeCount(); return tuckedStatus.equals(TuckedNodeStatus.TUCKED) ? tuckedChildCount : actualChildCount; } /** * Sets the {@link TuckedNodeStatus} for this TuckedNode. */ public void setTuckedStatus(TuckedNodeStatus tuckedStatus) { this.tuckedStatus = tuckedStatus; } /** * @return the {@link TuckedNodeStatus} of this node. */ public TuckedNodeStatus getTuckedStatus() { return tuckedStatus; } /** * @return true if this TuckedNode is part of the tuckedPath (kept in the related controller). The value is set only once, and upon initialisation. */ public boolean isNodePartOfTuckedPath() { return isPartOfTuckedPath; } /** * @return true if this TuckedNode has gotten its childCounts set. */ public boolean isNodeSet() { return actualChildCount != -1L && tuckedChildCount != -1L; } // ------------------------------------------------------------------------------------- ++ ------------------------------->> // [Section II] Handling the node's cached-children; for use in frequent tuck-untuck situations. // ------------------------------------------------------------------------------------- ++ ------------------------------->> // When we UNTUCK a node, we load the rest of its children. Then if we decide to TUCK it back, we dont want to have // to delete them away, since we may UNTUCK them at a later time. So, in that case, we keep them here in loadedTuckedChildren. // Like an internal-nodal-cache. These are affected/consulted at everytime the node stores new children. private Deque<TuckedPersonRelationTreeNode> loadedTuckedChildren = null; /** * @return the previously loaded node representing given {@link ObjectID}. * Returns null if no node containing such objectID is found. */ protected TuckedPersonRelationTreeNode getLoadedTuckedChildByObjectID(ObjectID objectID) { if (loadedTuckedChildren != null && !loadedTuckedChildren.isEmpty()) { for (TuckedPersonRelationTreeNode loadedChild : loadedTuckedChildren) if (loadedChild.getJdoObjectID().equals(objectID)) return loadedChild; } return null; } /** * @return true if we have successfully kept the loaded-tucked-child. */ protected boolean storeLoadedTuckedChild(TuckedPersonRelationTreeNode loadedChildNode) { if (loadedTuckedChildren == null) loadedTuckedChildren = new LinkedList<TuckedPersonRelationTreeNode>(); if (loadedTuckedChildren.contains(loadedChildNode)) return false; return loadedTuckedChildren.add(loadedChildNode); } /** * @return true if we have successfully removed the loaded-tucked-child. */ protected boolean removeLoadedTuckedChild(TuckedPersonRelationTreeNode loadedChildNode) { if (loadedTuckedChildren != null && !loadedTuckedChildren.isEmpty()) return loadedTuckedChildren.remove(loadedChildNode); return false; } @Override public synchronized boolean addChildNode(JDOObjectLazyTreeNode<ObjectID, Object, PersonRelationTreeController<? extends PersonRelationTreeNode>> childNode) { boolean isAddSucceeded = super.addChildNode(childNode); storeLoadedTuckedChild((TuckedPersonRelationTreeNode) childNode); return isAddSucceeded; } @Override public synchronized void setChildNodes(List<JDOObjectLazyTreeNode<ObjectID, Object, PersonRelationTreeController<? extends PersonRelationTreeNode>>> childNodes) { super.setChildNodes(childNodes); for (JDOObjectLazyTreeNode<ObjectID, Object, PersonRelationTreeController<? extends PersonRelationTreeNode>> childNode : childNodes) storeLoadedTuckedChild((TuckedPersonRelationTreeNode) childNode); } @Override public synchronized boolean removeChildNode(JDOObjectLazyTreeNode<ObjectID, Object, PersonRelationTreeController<? extends PersonRelationTreeNode>> childNode) { boolean isRemoveSucceeded = super.removeChildNode(childNode); removeLoadedTuckedChild((TuckedPersonRelationTreeNode) childNode); return isRemoveSucceeded; } // ------------------------------------------------------------------------------------- ++ ------------------------------->> // [Section III] Handling transitions from UNTUCK to TUCKED, and vice-versa. // ------------------------------------------------------------------------------------- ++ ------------------------------->> // We refer to this variable during node-status transitions, in order to coordinate the operational changes // in the nodes through the system of listeners that already handled by the controller. private TuckedNodeStatus statusToChangeTo = TuckedNodeStatus.UNSET; /** * Sets the impending new status that this TuckedNode will take after. For example, we set this value from the client's context-menu * to indicate the new {@link TuckedNodeStatus} this node should have. */ protected void setStatusToChangeTo(TuckedNodeStatus statusToChangeTo) { this.statusToChangeTo = statusToChangeTo; } /** * @return the impending new status that this TuckedNode will take after. When the value read {@link TuckedNodeStatus}.UNSET, then * nothing should be done to change this node's status. */ protected TuckedNodeStatus getStatusToChangeTo() { return statusToChangeTo; } // ------------------------------------------------------------------------------------- ++ ------------------------------->> // [Section IV] Maintaining operational collapse states. // ------------------------------------------------------------------------------------- ++ ------------------------------->> private boolean isExpanded = false; public boolean isNodeExpanded() { return isExpanded; } public void toggleNodeExpandedState() { isExpanded = !isExpanded; } // ------------------------------------------------------------------------------------- ++ ------------------------------->> // [Section ??] Miscellaneous and debuggings. // ------------------------------------------------------------------------------------- ++ ------------------------------->> /** * @return the "tucked" information status of this node, used for for appending the display with an appropriate label provider. * TODO Move this to the appropriate Label Provider! */ public String getTuckedInfoStatus(ObjectID objectID) { // This TuckedNode should behave like a normal PersonRelationTreeNode if it has been "NORMAL"-ised. return tuckedStatus.equals(TuckedNodeStatus.TUCKED) && isNodeSet() ? String.format("... (+ %s tucked element(s))", actualChildCount-tuckedChildCount) : ""; } /** * Shows the childCounts and statuses and other shits, pertaining to this {@link TuckedPersonRelationTreeNode}. */ public String toDebugString() { String str = this.getClass().getSimpleName() + "@" + PersonRelationTreeUtil.showObjectID(getJdoObjectID()); if (!isNodeSet()) return str + " --------->> [UN-set]"; str += "\n " + PersonRelationTreeUtil.showObjectID(getPropertySetID()) + ": [# tucked: " + tuckedChildCount + "]"; str += ", [# actual: " + actualChildCount + "], [status: \"" + tuckedStatus + "\"]"; str += ", [getChildNodeCount(): " + getChildNodeCount() + "]"; str += ", [loadedTuckedChildren.size(): " + (loadedTuckedChildren == null ? "null" : loadedTuckedChildren.size()) + "]"; str += ", [isExpanded: " + (isExpanded ? "True" : "False") + "]"; return str; } }