/***************************************************
*
* cismet GmbH, Saarbruecken, Germany
*
* ... and it just works.
*
****************************************************/
package Sirius.navigator.types.treenode;
import Sirius.navigator.connection.SessionManager;
import Sirius.navigator.ui.tree.MetaCatalogueTree;
import Sirius.server.middleware.types.Node;
import Sirius.server.newuser.permission.Permission;
import Sirius.util.NodeComparator;
import org.apache.log4j.Logger;
import java.util.Enumeration;
import java.util.Iterator;
import javax.swing.ImageIcon;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
/**
* DOCUMENT ME!
*
* @version $Revision$, $Date$
*/
public abstract class DefaultMetaTreeNode extends DefaultMutableTreeNode // implements Comparable
{
//~ Static fields/initializers ---------------------------------------------
private static final transient Logger LOG = Logger.getLogger(DefaultMetaTreeNode.class);
//~ Instance fields --------------------------------------------------------
protected boolean explored = false;
protected boolean selected = false;
protected boolean enabled = true;
/** Holds value of property changed. */
private boolean changed;
/** Holds value of property new_node. */
private boolean new_node;
private final transient NodeComparator nodeComparator;
//~ Constructors -----------------------------------------------------------
/**
* Dieser Konstruktor erzeugt eine RootNode ohne Children. Er ist zu Verwendung im SearchTree gedacht, wenn noch
* keine Suche durgefuehrt wurde und noch keine Knoten angezeigt werden koennen. Er sollte nicht angezeigt werden.
* (JTree.setRootVisible(false);)
*
* @param node DOCUMENT ME!
*/
public DefaultMetaTreeNode(final Node node) {
super(node);
nodeComparator = new NodeComparator();
}
//~ Methods ----------------------------------------------------------------
/**
* DOCUMENT ME!
*
* @param selected DOCUMENT ME!
*/
public void setSelected(final boolean selected) {
this.selected = enabled & selected;
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public boolean isSelected() {
return this.selected;
}
/**
* DOCUMENT ME!
*
* @param selected DOCUMENT ME!
*/
public void selectSubtree(final boolean selected) {
final Enumeration enu = this.breadthFirstEnumeration();
while (enu.hasMoreElements()) {
((DefaultMetaTreeNode)enu.nextElement()).setSelected(selected);
}
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public Node getNode() {
if (this.userObject != null) {
return (Node)this.userObject;
}
return null;
}
/**
* DOCUMENT ME!
*
* @param node DOCUMENT ME!
*/
public void setNode(final Node node) {
this.setUserObject(node);
}
/**
* DOCUMENT ME!
*
* @param leaf DOCUMENT ME!
*/
public void setLeaf(final boolean leaf) {
this.getNode().setLeaf(leaf);
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws Exception DOCUMENT ME!
*/
public Node[] getChildren() throws Exception {
final Node node = this.getNode();
final Node[] c = SessionManager.getProxy().getChildren(node, SessionManager.getSession().getUser());
if ((node.getDynamicChildrenStatement() != null) && node.isSqlSort()) {
return c;
}
return Sirius.navigator.tools.NodeSorter.sortNodes(c);
}
/**
* Performs a hard refresh by removing all children, fetching them again from the database and then adding them.
* Make sure to notify the tree model about the node change.<br/>
* <br/>
* <b>NOTE:</b>This operation is preferably used if the node's children are created dynamically and the SQL sort
* property is true. If these conditions are not met consider to do a soft refresh.
*/
public void refreshChildren() {
try {
final Node[] dbChildren = getChildren();
removeAllChildren();
for (final Node node : dbChildren) {
add(MetaCatalogueTree.createTreeNode(node));
}
} catch (final Exception e) {
LOG.warn("cannot refresh node: " + this, e); // NOI18N
}
}
/**
* DOCUMENT ME!
*
* @param toRemove DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public int removeNode(final DefaultMetaTreeNode toRemove) {
final int index = getIndex(toRemove);
if (index >= 0) {
remove(index);
}
return index;
}
/**
* DOCUMENT ME!
*
* @param toAdd DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws IllegalStateException DOCUMENT ME!
*/
public int insertNode(final DefaultMetaTreeNode toAdd) {
final Node newNode = toAdd.getNode();
assert newNode != null : "received DefaultMetaTreeNode without backing Node: " + toAdd; // NOI18N
for (int i = 0; (children != null) && (i < children.size()); ++i) {
final TreeNode tn = getChildAt(i);
if (tn instanceof DefaultMetaTreeNode) {
final DefaultMetaTreeNode dmtn = (DefaultMetaTreeNode)tn;
final Node child = dmtn.getNode();
assert child != null : "found DefaultMetaTreeNode without backing Node: " + dmtn; // NOI18N
if (nodeComparator.compare(child, newNode) > 0) {
insert(toAdd, i);
return i;
}
} else {
throw new IllegalStateException("Illegal child: " + tn); // NOI18N
}
}
// the node was not inserted, so we insert it at the end
add(toAdd);
return getChildCount() - 1;
}
/**
* DOCUMENT ME!
*
* @param enabled DOCUMENT ME!
*/
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public boolean isEnabled() {
return this.enabled;
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public abstract TreeNodeLoader getTreeNodeLoader();
/**
* Gibt an, ober dieser Knoten bereits expandiert wurde, bzw. ob seine Children schon vom Server geladen wurden.
*
* @return DOCUMENT ME!
*/
public boolean isExplored() {
return explored;
}
/**
* Ueberschreibt die Funktion isLeaf() in MutableTreeNode.
*
* @return true/false
*/
@Override
public boolean isLeaf() {
return (this.getUserObject() != null) ? this.getNode().isLeaf() : true;
}
@Override
public boolean getAllowsChildren() {
return !this.isLeaf();
}
/**
* Liefert eine String Repraesentation dieser TreeNode.
*
* @return Der Name des userObjects.
*/
@Override
public abstract String toString();
/**
* Gibt an, ob diese TreeNode eine RootNode ist.<br>
* Eine RootNode ist ein spezieller Typ von DefaultMetaTreeNode. Unter eine RootNode werden alle anderen Knoten
* angehaengt. Pro MetaTree gibt es nur eine RootNode.
*
* @return true/false
*/
public abstract boolean isRootNode();
/**
* Gibt an, ob diese TreeNode eine WaitNode ist.<br>
* Eine WaitNode ist ein spezieller Typ von DefaultMetaTreeNode. Die WaitNode wird dann angezeigt, wenn ein Knoten
* innerhalb eines Threads expandiert wurde. Die WaitNode wird zwar direkt nach dem Aufruf der Methode getChildren
* wieder entfernt, da aber der Thread den View des MetaTree erst wieder aktualisert wenn alle Children geladen
* wurden (asynchrones Update), wird diese WaitNode solange angezeigt, wie der Thread l\u00E4uft. Wird der Knoten
* nicht innerhalb eines Threads angezeigt, wird auch die WaitNode nicht angezeigt.
*
* @return true/false
*/
public abstract boolean isWaitNode();
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public abstract boolean isPureNode();
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public abstract boolean isClassNode();
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public abstract boolean isObjectNode();
/**
* Expandiert diesen Knoten und uerberprueft, ob dessen Kinder bereits vom Server geladen wurden. Hat der Knoten
* noch keine Kinder und ist er kein Blatt (!isLeaf()), werden die Children vom Server geladen.
*
* @throws Exception DOCUMENT ME!
*/
public abstract void explore() throws Exception;
/**
* DOCUMENT ME!
*
* @throws Exception DOCUMENT ME!
*/
public void exploreAll() throws Exception {
if (!this.isLeaf()) {
if (LOG.isDebugEnabled()) {
LOG.warn("exploring all children of node '" + this + "'"); // NOI18N
}
if (!this.isExplored()) {
this.explore();
}
final Enumeration e = this.children();
while (e.hasMoreElements()) {
((DefaultMetaTreeNode)e.nextElement()).exploreAll();
}
}
}
/**
* DOCUMENT ME!
*
* @param childrenIterator DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws Exception DOCUMENT ME!
*/
public TreePath explore(final Iterator<DefaultMetaTreeNode> childrenIterator) throws Exception {
if (childrenIterator.hasNext()) {
if (!this.isLeaf()) {
if (!this.isExplored()) {
this.explore();
}
final Enumeration<DefaultMetaTreeNode> childrenEnumeration = children();
final DefaultMetaTreeNode childNode = childrenIterator.next();
while (childrenEnumeration.hasMoreElements()) {
final DefaultMetaTreeNode thisChildNode = childrenEnumeration.nextElement();
if (thisChildNode.getID() > -1) {
if (thisChildNode.getID() == childNode.getID()) {
return thisChildNode.explore(childrenIterator);
}
} else {
final String thisChildNodeString = thisChildNode.toString();
if (thisChildNodeString != null) {
if (thisChildNodeString.equals(childNode.toString())) {
return thisChildNode.explore(childrenIterator);
}
} else {
LOG.warn("Fixme: thisChildNodeString is null!");
}
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("explore(): child node '" + childNode + "' not found"); // NOI18N
}
final TreePath fallback = handleNotMatchingNodeFound();
if (fallback != null) {
return fallback;
}
}
}
return new TreePath(getPath());
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private TreePath handleNotMatchingNodeFound() {
final Enumeration<DefaultMetaTreeNode> childrenEnum = children();
if (childrenEnum.hasMoreElements()) {
final DefaultMetaTreeNode fallbackCandidate = childrenEnum.nextElement();
return new TreePath(fallbackCandidate.getPath());
}
return null;
}
/**
* Entfernt alle Children dieser Node und setzt ihren status zurueck;
*/
public void removeChildren() {
if (LOG.isDebugEnabled()) {
LOG.debug("removing children"); // NOI18N
}
this.removeAllChildren();
this.explored = false;
}
/**
* Liefert die Beschreibung (bzw. den URL zur Beschreibung) der selektierten TreeNode.
*
* @return URL String der Beschreibung oder "wird geladen ...", wenn WaitNode.
*/
public abstract String getDescription();
/**
* Vergleicht die DefaultMetaTreeNode mit einer Sirius Node, und liefert true, falls diese die gleichen Daten
* enthalten.
*
* @param node DOCUMENT ME!
*
* @return true oder false.
*
* @deprecated use <code>equals(Node node)</code>
*/
public abstract boolean equalsNode(Node node);
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public abstract ImageIcon getOpenIcon();
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public abstract ImageIcon getClosedIcon();
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public abstract ImageIcon getLeafIcon();
/**
* DOCUMENT ME!
*
* @param node DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public abstract boolean equals(DefaultMetaTreeNode node);
/**
* DOCUMENT ME!
*
* @param node DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public boolean deepEquals(final DefaultMetaTreeNode node) {
if (node == null) {
return false;
}
if (this == node) {
return true;
}
if (!node.getClass().equals(this.getClass())) {
return false;
}
if (!((userObject instanceof Node) && (node.userObject instanceof Node))) {
return false;
}
final Node n1 = (Node)userObject;
final Node n2 = (Node)node.userObject;
return n1.deepEquals(n2);
}
/**
* Returns the class ob object id.
*
* @return DOCUMENT ME!
*/
public abstract int getID();
/**
* Returns the class ob object id.
*
* @return DOCUMENT ME!
*/
public abstract int getClassID();
/**
* return the class or object domain (was: localserver)
*
* @return DOCUMENT ME!
*/
public abstract String getDomain();
/**
* Returns the unique key of the node's user object (class or object).
*
* @return DOCUMENT ME!
*
* @throws Exception DOCUMENT ME!
*/
public abstract String getKey() throws Exception;
/**
* Getter for property changed.
*
* @return Value of property changed.
*/
public boolean isChanged() {
return this.changed;
}
/**
* Setter for property changed.
*
* @param changed New value of property changed.
*/
public void setChanged(final boolean changed) {
this.changed = changed;
}
/**
* Getter for property new_node.
*
* @return Value of property new_node.
*/
public boolean isNew() {
return this.new_node;
}
/**
* Setter for property new_node.
*
* @param new_node New value of property new_node.
*/
public void setNew(final boolean new_node) {
this.new_node = new_node;
}
/**
* DOCUMENT ME!
*
* @param key DOCUMENT ME!
* @param p DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws Exception DOCUMENT ME!
*/
public boolean isEditable(final Object key, final Permission p) throws Exception {
return getNode().getPermissions().hasPermission(key, p);
}
/**
* Setter for property explored.
*
* @param explored New value of property explored.
*/
public void setExplored(final boolean explored) {
this.explored = explored;
}
}