at.spardat.xma.mdl.tree.TreeNode Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* s IT Solutions AT Spardat GmbH - initial API and implementation
*******************************************************************************/
// @(#) $Id: TreeNode.java 2089 2007-11-28 13:56:13Z s3460 $
package at.spardat.xma.mdl.tree;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import org.eclipse.swt.widgets.TreeItem;
import at.spardat.enterprise.util.StringUtil;
import at.spardat.xma.serializer.XmaInput;
import at.spardat.xma.serializer.XmaOutput;
/**
* Represents a node in a tree widget model.
*
* @author YSD, 03.05.2003 20:11:50
*/
public class TreeNode {
private static final String DUMMY_NODE = "at.spardat.xma.dummynode";
// an empty array of TreeNodes
private static final TreeNode[] emptyNodeArray = new TreeNode[0];
// the displayed text string of this node. Never null.
String text_;
// the key of this node; never null
String key_;
// the id of an displayed image; is zero if no image is set
short imageId_;
/**
* properties encoded in a string; may be null, if no property is set;
* properties are encoded in the form p1=val1,p2=val2,p2=val3,...
*/
String props_;
// list of TreeNodes which are the sons of this node; may be null;
ArrayList childs_;
// either a TreeNode or the tree itself (if the node is a root); never null;
Object parent_;
// the tree to which this node belongs
TreeWM tree_;
// indicates that this has been removed and cannot be accessed anymore.
boolean dead_;
//indicates that this node has a leazy behauvior node.
//the dummynode is removed before the expand event,
//the lazy_ is set to true before and to false after the expand event
private boolean lazy_;
// reserved property key for foreground color
static final String PROP_KEY_FOREGROUND_COLOR = "f";
// reserved property key for background color
static final String PROP_KEY_BACKGROUND_COLOR = "b";
/**
* Constructs a TreeNode and makes it the child of parent at
* a specified zero based index in the child sequence.
*
* @param parent may be ITreeWM in which case this node becomes a
* root node or a TreeNode in which case this becomes
* a direct child of parent.
* @param index the zero based index into the sequence of childs this node should
* get. 0 inserts in front, getChildCount() inserts at the tail
* of the childs.
* @param key the key of the new node. This key must not already be contained
* in the tree.
* @param text the text that is displayed in the node
* @param imageId an optinal id of an image to be displayed. Specify 0 if
* no image should be displayed.
*
* @param addDummyNode adds a dummy node as child to this node to make this node expandable.
* The dummy node is automatically removed at the client side if an expand event occurs on this node.
*
* @exception RuntimeException on invalid arguments.
*/
public TreeNode (Object parent, int index, String key, String text, int imageId, boolean addDummyNode) {
TreeWM pTree = null;
TreeNode pNode = null;
if (parent instanceof TreeWM) pTree = (TreeWM)parent;
else if (parent instanceof TreeNode) pNode = (TreeNode) parent;
else throw new IllegalArgumentException();
if (index == -1) {
index = (pTree == null ? pNode.getChildCount() : pTree.getRootCount());
}
tree_ = (pNode == null ? pTree : pNode.tree_);
if (tree_.containsKey (key)) throw new IllegalArgumentException ("duplicate key");
String parentKey = null;
if (pNode != null) parentKey = pNode.key_;
// execute AddNodeEvent
if (!tree_.handle(tree_.new AddNodeEvent (this, key, parentKey, index, text, (short)imageId, false)))
throw new IllegalArgumentException ();
if(addDummyNode){
addDummyNode();
}
}
/**
* Constructs a TreeNode and makes it the child of parent at
* a specified zero based index in the child sequence.
*
* @param parent may be ITreeWM in which case this node becomes a
* root node or a TreeNode in which case this becomes
* a direct child of parent.
* @param index the zero based index into the sequence of childs this node should
* get. 0 inserts in front, getChildCount() inserts at the tail
* of the childs.
* @param key the key of the new node. This key must not already be contained
* in the tree.
* @param text the text that is displayed in the node
* @param imageId an optinal id of an image to be displayed. Specify 0 if
* no image should be displayed.
* @exception RuntimeException on invalid arguments.
*/
public TreeNode (Object parent, int index, String key, String text, int imageId) {
this(parent, index, key, text, imageId, false);
}
/**
* Constructs a TreeNode and makes it the last child of parent.
*
* @param parent may be TreeWM in which case this node becomes a
* root node or a TreeNode in which case this becomes
* a direct child of parent.
* @param key the key of the new node. This key must not already be contained
* in the tree.
* @param text the text that is displayed in the node
* @param imageId an optinal id of an image to be displayed. Specify 0 if
* no image should be displayed.
* @param addDummyNode adds a dummy node as child to this node to make this node expandable.
* The dummy node is automatically removed at the client side if an expand event occurs on this node.
*
* @exception RuntimeException on invalid arguments.
*/
public TreeNode (Object parent, String key, String text, int imageId,boolean addDummyNode) {
this (parent, -1, key, text, imageId, addDummyNode);
}
/**
* Constructs a TreeNode and makes it the last child of parent.
*
* @param parent may be TreeWM in which case this node becomes a
* root node or a TreeNode in which case this becomes
* a direct child of parent.
* @param key the key of the new node. This key must not already be contained
* in the tree.
* @param text the text that is displayed in the node
* @param imageId an optinal id of an image to be displayed. Specify 0 if
* no image should be displayed.
* @exception RuntimeException on invalid arguments.
*/
public TreeNode (Object parent, String key, String text, int imageId) {
this (parent, -1, key, text, imageId, false);
}
/**
* Internal use constructor.
*/
protected TreeNode () {
}
/**
* Tests if a dummy node exists to this node.
* (A dummy node created by addDummyNode() or by the TreeNode constructor)
* @return true if a dummy node exists to this node - otherwise false.
* @since version_number
* @author s3460
*/
public boolean hasDummyNode(){
return tree_.getNode(this.getKey()+DUMMY_NODE)!=null?true:false;
}
/**
* Adds a dummy node as child to this node to make this node expandable.
* The dummy node is automatically removed at the client side if an expand event occurs on this node.
* There can only exitst one dummy node for every node. Therefore a repeated call to this method does nothing.
* @return true - if a dummy node was added, false otherwise.
* @since version_number
* @author s3460
*/
public boolean addDummyNode(){
if(!hasDummyNode()){
TreeNode dummyNode = new TreeNode(this,0,getKey()+DUMMY_NODE,"",0);
return true;
}
return false;
}
/**
* Removes a dummy node created by addDummyNode() or by the TreeNode constructor.
* If no dummy node exists thsi method does nothing.
* This method is called by the XMA framework if an expand event occurs on this node.
*
* @return true - if a dummy node was removed, false otherwise.
* @since version_number
* @author s3460
*/
public boolean removeDummyNode(){
TreeNode dummy = tree_.getNode(this.getKey()+DUMMY_NODE);
if(dummy != null){
dummy.remove();
return true;
}
return false;
}
/**
* Tests if a node is a dummy node created by addDummyNode() or by the TreeNode constructor.
* @return true if this node is a dummy node.
* @since version_number
* @author s3460
*/
public boolean isDummyNode(){
return this.getKey().endsWith(DUMMY_NODE);
}
/**
* Returns an array of TreeNode objects that are the childs of
* this TreeNode.
*
* @return the childs of this. The array is empty if this does
* not has childs.
*/
public TreeNode [] getChilds () {
checkDead();
if (childs_ == null) return emptyNodeArray;
TreeNode [] childs = new TreeNode[childs_.size()];
Iterator iter = childs_.iterator();
for (int i=0; iter.hasNext(); i++) {
childs[i] = (TreeNode) iter.next();
}
return childs;
}
/**
* Returns the child at the zero based index i which must
* be greater equal zero and less than getChildCount().
*
* @param i zero based index of the child
* @return the requested child
* @exception RuntimeException if index is invalid
*/
public TreeNode getChild (int i) {
return (TreeNode) childs_.get(i);
}
/**
* Returns the parent of this tree node or null, if this
* node is a root node.
*/
public TreeNode getParent () {
checkDead();
if (parent_ instanceof TreeNode) return (TreeNode) parent_;
return null;
}
/**
* Returns the number of childs this node has.
*/
public int getChildCount () {
checkDead();
if (childs_ == null) return 0;
return childs_.size();
}
/**
* Returns the image id of the image of this node or 0 if none has been set.
*/
public short getImageId() {
checkDead();
return imageId_;
}
/**
* Returns the key of this node.
*
* @return the key which is never null.
*/
public String getKey() {
checkDead();
return key_;
}
/**
* Returns the text of this node.
*
* @return the text wich is never null.
*/
public String getText() {
checkDead();
return text_;
}
/**
* Sets a new image id.
*/
public void setImageId (short s) {
checkDead();
tree_.handle(tree_.new ChangeNodeEvent(key_, text_, s, props_, false));
}
/**
* Sets a new text.
*
* @exception IllegalArgumentException if text is null.
*/
public void setText (String string) {
if (string == null) throw new IllegalArgumentException();
checkDead();
tree_.handle(tree_.new ChangeNodeEvent(key_, string, imageId_, props_, false));
}
/**
* Removes this tree node from the tree. This method also removes
* all nodes which are direct or indirect childs of this node.
* You cannot access this and its direct or indirect childs
* anymore after this method terminates.
*/
public void remove () {
checkDead();
tree_.handle(tree_.new RemoveNodeEvent(key_, false));
}
/**
* Marks this tree node to be selected. Calling this method is the
* same as calling select(key) on the tree widget model.
*/
public void select () {
checkDead();
tree_.select(key_);
}
/**
* Deselects this tree node if it is selected. If it is not selected,
* this method does nothing. The same as calling deselect(key)
* on the tree widget model.
*/
public void deselect () {
checkDead();
tree_.deselect(key_);
}
/**
* Returns true if this tree node is selected, false otherwise.
*/
public boolean isSelected () {
checkDead();
return tree_.isSelected(key_);
}
/**
* Sets the expansion status of this tree node.
*
* This method must not
* be called at the server side, but only on the client side in the
* state where the UI is created. If the UI is not created yet or
* this method is called in server side code, this method does nothing.
*
* @param expanded true, if this tree node should be shown in an expanded
* state.
*/
public void setExpanded (boolean expanded) {
checkDead();
TreeUIDelegateClient uiDel = getUIDelegate();
if (uiDel != null) uiDel.setExpanded(this, expanded, false);
}
/**
* At the client side, this TreeNode has a representation in a UI library.
* In the case of SWT, every TreeNode has a SWT TreeItem assigned.
* If you want to set some visual attributes on the TreeItem, you
* may call this method and cast the returned Object down to a SWT-TreeItem.
*
* You must not change the text or the image of the returned
* SWT TreeItem. If you want to change these attributes, use
* either setText or setImageId in this class.
*
* @return if SWT is your UI-library, returns a SWT-TreeItem if your
* code is running at the client side and you have a constructed UI.
* Otherwise returns null. Note that the SWT UI widget (Tree)
* must be alive in order for this method to work.
*/
public Object getUITreeItem () {
checkDead();
TreeUIDelegateClient uiDel = getUIDelegate();
if (uiDel != null) return uiDel.node2item(this);
return null;
}
/**
* This is the inverse method to getUITreeItem. It returns the TreeNode
* that corresponds to the tree item of the underlying UI library. In the case of SWT,
* the argument must be an instance of class TreeItem.
*
* @param uiTreeItem SWT-TreeItem if SWT the UI-library you are using. Must not be null.
* The SWT-TreeItem must not be disposed.
* @return TreeNode for the given TreeItem
*/
public static TreeNode getTreeNodeFor (Object uiTreeItem) {
if (uiTreeItem == null) throw new IllegalArgumentException ();
return TreeUIDelegateClient.item2node((TreeItem)uiTreeItem);
}
/**
* Returns the UIDelegate of the widget model or null, if we are executing at
* the server or the UI is not constructed yet.
*/
private TreeUIDelegateClient getUIDelegate () {
if (!tree_.getPage().isAtServer()) {
TreeUIDelegateClient uiDel = (TreeUIDelegateClient) ((TreeWMClient)tree_).getUIDelegate();
if (uiDel.isUIAttached()) return uiDel;
}
return null;
}
/**
* Returns true if this node is a root node, i.e., there is no other node
* where one of its childs is this node.
*/
public boolean isRoot () {
checkDead();
return parent_ == tree_;
}
/**
* Returns true if this node does not have child nodes.
*/
public boolean isLeaf () {
checkDead();
return childs_ == null || childs_.size() == 0;
}
/**
* Sets the foreground color of this TreeNode.
*
* @param c object denoting the foreground color or null if the
* color should be cleared.
*/
public void setForegroundColor (NodeColor c) {
checkDead();
String propVal = (c == null ? null : c.encode());
setProperty0 (PROP_KEY_FOREGROUND_COLOR, propVal);
}
/**
* Sets the foreground color of this TreeNode.
*
* @param c object denoting the foreground color or null if the
* color should be cleared.
*/
public void setBackgroundColor (NodeColor c) {
checkDead();
String propVal = (c == null ? null : c.encode());
setProperty0 (PROP_KEY_BACKGROUND_COLOR, propVal);
}
/**
* Returns the foreground color set of null, if no color is set.
*/
public NodeColor getForegroundColor () {
checkDead();
String col = getProperty0 (PROP_KEY_FOREGROUND_COLOR);
if (col == null) return null;
return new NodeColor (col);
}
/**
* Returns the foreground color set of null, if no color is set.
*/
public NodeColor getBackgroundColor () {
checkDead();
String col = getProperty0 (PROP_KEY_BACKGROUND_COLOR);
if (col == null) return null;
return new NodeColor (col);
}
/**
* Sets an auxiliary property.
*
* @param key the key of the property. Must not be a key of length 1 because
* those are reserved. Must not be null.
* @param value the value. If null, the property is cleared.
*/
public void setProperty (String key, String value) {
checkDead();
if (isReservedPropertyKey(key)) throw new IllegalArgumentException();
setProperty0 (key, value);
}
/**
* Returns an auxiliary property for a given key.
*
* @param key the key of the property. Must not be a key of length 1 because
* those are reserved. Must not be null.
* @return value of the property or null, if this property is not set.
*/
public String getProperty (String key) {
checkDead();
if (isReservedPropertyKey(key)) throw new IllegalArgumentException();
return getProperty0(key);
}
/**
* Sets an auxiliary property.
*
* @param key key of the property. Must not be null.
* @param value value. May be null which clears the property.
*/
private void setProperty0 (String key, String value) {
if (key == null) throw new IllegalArgumentException();
HashMap props = extractProps();
String oldVal = (String) props.get(key);
if (eqProperty(oldVal, value)) return;
if (value == null) props.remove(key);
else props.put (key, value);
String newPropString = props2String(props);
tree_.handle (tree_.new ChangeNodeEvent(key_, text_, imageId_, newPropString, false));
}
/**
* Returns a property value for a given key or null, it this
* property is not set.
*/
private String getProperty0 (String key) {
if (key == null) throw new IllegalArgumentException();
if (props_ == null) return null;
HashMap props = extractProps();
return (String) props.get(key);
}
/**
* Returns true if and only if the two strings are equal with the
* extension that two nulls are considered equal.
*/
static boolean eqProperty (String v1, String v2) {
if (v1 == null) {
return v2 == null;
} else {
if (v2 == null) return false;
return v1.equals(v2);
}
}
/**
* Throws an exception if this is dead.
*/
private final void checkDead () {
if (dead_) throw new IllegalStateException();
}
/**
* Returns the size of the hypothetical byte stream if this node
* would be externalized.
*/
protected int streamedSize() {
return 2 + text_.length() // text_
+ 2 + key_.length() // key_
+ 2 // imageId_
+ 1 + (props_ == null ? 0 : props_.length()); // props_
}
/**
* Returns the key of the parent node of this if this node is an inner
* node or null, if this node is a root.
*/
protected String getParentKey () {
if (parent_ instanceof TreeNode) return ((TreeNode)parent_).key_;
else return null;
}
/**
* This method makes a copy of the subtree rooted at this and returns
* it. The subtree is a perfect deep copy. The tree_ instance
* in the copied subtree is the same as in the original one.
*
* @param parent the new parent of the returned TreeNode.
*/
protected TreeNode cloneSubTree (Object parent) {
TreeNode n2 = new TreeNode();
n2.text_ = text_;
n2.key_ = key_;
n2.imageId_ = imageId_;
n2.props_ = props_;
n2.tree_ = tree_;
n2.parent_ = parent;
if (childs_ != null) {
n2.childs_ = new ArrayList();
for (int i=0; itext_, key_ and imageId_
* from the stream.
*/
protected void internalize (XmaInput in) throws IOException, ClassNotFoundException {
text_ = in.readString();
key_ = in.readString();
imageId_ = in.readShort();
boolean propsIsNull = in.readBoolean();
if (propsIsNull) props_ = null;
else props_ = in.readString();
}
/**
* Expects a string of the form p1=v1,p2=v2... in instance variable
* props_ and converts this information in a newly created HashMap
* { (p1,v1), (p2,v2), ... }.
*
* @return newly created Hashmap
*/
private HashMap extractProps () {
return string2Props (props_);
}
/**
* Converts string encoded properties to newly created HashMap
*/
static HashMap string2Props (String s) {
HashMap result = new HashMap();
if (s != null) {
StringTokenizer tokizer = new StringTokenizer (s, ",");
while (tokizer.hasMoreTokens()) {
String kv = tokizer.nextToken();
int indexOfEquals = kv.indexOf('=');
String key = kv.substring(0, indexOfEquals);
String value = kv.substring(indexOfEquals+1);
result.put(StringUtil.decode(key, '%'), StringUtil.decode(value, '%'));
}
}
return result;
}
/**
* Expects String tuples (p1,v1), (p2,v2), ... in props, converts this
* information to a string following the format p1=v1,p2=v2. If props==null or
* props.size()==0, null is returned.
*
* @param props HashMap containing String-pairs.
*/
static String props2String (HashMap props) {
String result = null;
if (props == null || props.size() == 0) {
} else {
StringBuffer buf = new StringBuffer();
Iterator iter = props.entrySet().iterator();
boolean first = true;
while (iter.hasNext()) {
Map.Entry kv = (Map.Entry) iter.next();
if (first) first = false;
else buf.append(',');
buf.append (StringUtil.encode((String)kv.getKey(), '%'));
buf.append ("=");
buf.append (StringUtil.encode((String)kv.getValue(), '%'));
}
result = buf.toString();
}
return result;
}
/**
* Returns true if s denotes a reserved property key.
*/
public static boolean isReservedPropertyKey (String s) {
if (s == null) throw new IllegalArgumentException ();
return s.length()==1;
}
/**
* Shows if this node supports the lazy loading of child nodes.
* The lazy loading is implemented by adding a dummynode to this node and
* so making a childless node expandable.
* The dummynode is removed before the expand event at the client side,
* the isLazy() returns true in the expand event but is set to false after the first expand event occured on this node.
* @return true if a node is lazy, i.e. expandable without real child nodes.
*/
public boolean isLazy() {
return hasDummyNode() || lazy_;
}
/**
* Sets the lazy flag.
* Used by TreeUIDelegateClient.
* @param lazy_ The lazy_ to set.
* @author s3460
*/
void setLazy(boolean lazy) {
this.lazy_ = lazy;
}
}