/*
* Rapid Beans Framework: DocumentTreeModel.java
*
* Copyright (C) 2009 Martin Bluemel
*
* Creation Date: 02/11/2006
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation;
* either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
* You should have received a copies of the GNU Lesser General Public License and the
* GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
package org.rapidbeans.presentation.swing;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.swing.JTree;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import org.rapidbeans.core.basic.Link;
import org.rapidbeans.core.basic.Property;
import org.rapidbeans.core.basic.PropertyCollection;
import org.rapidbeans.core.basic.RapidBean;
import org.rapidbeans.core.common.ReadonlyListCollection;
import org.rapidbeans.core.exception.RapidBeansRuntimeException;
import org.rapidbeans.core.type.TypePropertyCollection;
import org.rapidbeans.datasource.Document;
import org.rapidbeans.datasource.Filter;
/**
* model part of the Swing implementation for a tree view for a bean document.
*
* @author Martin Bluemel
*/
public final class DocumentTreeModel implements TreeModel {
/**
* the document to show.
*/
private Document document = null;
/**
* if bean links shall be shown or not.
*/
private boolean showBeanLinks = true;
/**
* @return if bean links are presented to the tree view.
*/
protected boolean getShowBeanLinks() {
return showBeanLinks;
}
/**
* setter.
*
* @param show
* determines if bean links are presented to the tree view.
*/
protected void setShowBeanLinks(final boolean show) {
this.showBeanLinks = show;
}
/**
* if properties shall be shown or not.
*/
private boolean showProperties = true;
/**
* @return if properties are presented to the tree view.
*/
protected boolean getShowProperties() {
return showProperties;
}
/**
* setter.
*
* @param show
* determines if properties are presented to the tree view.
*/
protected void setShowProperties(final boolean show) {
this.showProperties = show;
}
private Filter beanFilter = null;
/**
* constructor.
*
* @param doc
* the document to show
* @param filter
* the filter
*/
public DocumentTreeModel(final Document doc, final Filter filter) {
this.document = doc;
this.beanFilter = filter;
}
/**
* Returns the root of the tree. Returns <code>null</code> only if the tree
* has no nodes.
*
* @return the root of the tree
*/
public Object getRoot() {
return this.document.getRoot();
}
/**
* Returns the child of <code>parent</code> at index <code>index</code> in
* the parent's child array. <code>parent</code> must be a node previously
* obtained from this data source. This should not return <code>null</code> if <code>index</code> is a valid index for <code>parent</code> (that is <code>index >= 0 && index < getChildCount(parent</code>)). The parent can
* be of the following classes.<br/>
* <b>bean: </b>the childs are all Collection Properties with role type
* "composition"<br/>
* <b>PropertyCollection: </b>the child are the beans of this collection<br/>
*
* @param parent
* a node in the tree, obtained from this data source
*
* @param index
* the index of the child tree node
*
* @return the child of <code>parent</code> at index <code>index</code>
*/
@SuppressWarnings("unchecked")
public Object getChild(final Object parent, final int index) {
Object foundObj = null;
if (parent instanceof RapidBean) {
if (this.showProperties) {
if (this.showBeanLinks) {
final List<PropertyCollection> colList = ((RapidBean) parent).getColProperties();
PropertyCollection colProp = null;
if (this.beanFilter == null) {
colProp = colList.get(index);
} else {
int i = 0;
for (final PropertyCollection colProp1 : colList) {
if (this.beanFilter.applies(((TypePropertyCollection) colProp1.getType()).getTargetType())) {
if (i < index) {
i++;
} else {
colProp = colProp1;
break;
}
}
}
}
if (((TypePropertyCollection) colProp.getType()).isComposition()) {
foundObj = new DocumentTreeNodePropColComp(colProp);
} else {
foundObj = new DocumentTreeNodePropColLink(colProp);
}
} else { // showBeanLinks == false
final List<PropertyCollection> compColList = ((RapidBean) parent).getColPropertiesComposition();
if (this.beanFilter == null) {
foundObj = new DocumentTreeNodePropColComp(compColList.get(index));
} else {
int i = 0;
for (final PropertyCollection colProp : compColList) {
if (this.beanFilter.applies(((TypePropertyCollection) colProp.getType()).getTargetType())) {
if (i < index) {
i++;
} else {
foundObj = colProp;
break;
}
}
}
}
}
} else { // showProperties == false
List<PropertyCollection> colList;
if (this.showBeanLinks) {
colList = ((RapidBean) parent).getColProperties();
} else {
colList = ((RapidBean) parent).getColPropertiesComposition();
}
int beansCount = 0;
boolean isComposition;
for (PropertyCollection colProp : colList) {
if (this.showBeanLinks) {
isComposition = ((TypePropertyCollection) colProp.getType()).isComposition();
} else {
isComposition = true;
}
for (RapidBean bean : (List<RapidBean>) colProp.getValue()) {
if (beansCount == index) {
if (isComposition) {
foundObj = bean;
break;
} else {
foundObj = new DocumentTreeNodeBeanLink(bean);
break;
}
}
if (this.beanFilter == null || this.beanFilter.applies(bean)) {
beansCount++;
}
}
if (foundObj != null) {
break;
}
}
}
} else if (parent instanceof DocumentTreeNodePropCol) {
// a composition collection returns directly beans
final PropertyCollection parentColProp = ((DocumentTreeNodePropCol) parent).getColProp();
if (this.beanFilter == null) {
final RapidBean bean = (RapidBean) ((ReadonlyListCollection<Link>) parentColProp.getValue()).get(index);
if (parent instanceof DocumentTreeNodePropColLink) {
foundObj = new DocumentTreeNodeBeanLink(bean);
} else if (parent instanceof DocumentTreeNodePropColComp) {
foundObj = bean;
} else {
throw new RapidBeansRuntimeException("Unexpected parent class \"" + parent.getClass().getName()
+ "\" for bean tree model");
}
} else {
int i = 0;
for (final Object obj : (Collection<Link>) parentColProp.getValue()) {
final RapidBean bean = (RapidBean) obj;
if (this.beanFilter.applies(bean)) {
if (i < index) {
i++;
} else {
foundObj = bean;
break;
}
}
}
}
} else {
// bean link nodes or other objects never should be asked for childs
throw new RapidBeansRuntimeException("Unexpected parent class \"" + parent.getClass().getName()
+ "\" for bean tree model");
}
if (foundObj == null) {
throw new RapidBeansRuntimeException("No child object found for parent " + parent + " at index " + index);
}
return foundObj;
}
/**
* returns the number of children of parent.
*
* @param parent
* the bean or PropertyCollection tree node. Must be a node
* previously obtained from this data source.
*
* @return 0 if the node is a leaf or if it has no children.<br/>
* for beans: the number of Collection Properties with role type
* "composition"<br/>
* for Collection Properties: the number of collected beans<br/>
*/
@SuppressWarnings("unchecked")
public int getChildCount(final Object parent) {
int count = -1;
if (parent instanceof RapidBean) {
List<PropertyCollection> colPropList;
if (this.showBeanLinks) {
colPropList = ((RapidBean) parent).getColProperties();
} else {
colPropList = ((RapidBean) parent).getColPropertiesComposition();
}
if (this.showProperties) {
if (this.beanFilter == null) {
count = colPropList.size();
} else {
count = 0;
for (final PropertyCollection colProp : colPropList) {
if (beanFilter.applies(((TypePropertyCollection) colProp.getType()).getTargetType())) {
count++;
}
}
}
} else { // show properties == false
count = 0;
for (PropertyCollection colProp : colPropList) {
if (colProp.getValue() != null) {
count += ((List<Link>) colProp.getValue()).size();
}
}
}
} else if (parent instanceof DocumentTreeNodeBeanLink) {
count = 0;
} else if (parent instanceof DocumentTreeNodePropCol) {
final Collection<RapidBean> col = (Collection<RapidBean>) ((DocumentTreeNodePropCol) parent).getColProp()
.getValue();
if (col == null) {
count = 0;
} else {
if (this.beanFilter == null) {
count = col.size();
} else {
count = 0;
for (final RapidBean linkedBean : col) {
if (this.beanFilter.applies(linkedBean)) {
count++;
}
}
}
}
} else {
throw new RapidBeansRuntimeException("Unexpected child class \"" + parent.getClass().getName()
+ "\"for bean tree model");
}
return count;
}
/**
* @param node
* the tree node
*
* @return if the node is a leaf node
*/
public boolean isLeaf(final Object node) {
return (this.getChildCount(node) == 0);
}
/**
* Returns the index of child in parent. If either <code>parent</code> or <code>child</code> is <code>null</code>, returns -1. If either <code>parent</code> or <code>child</code> don't belong to this tree
* model, returns -1.
*
* @param parent
* a node in the tree, obtained from this data source
*
* @param child
* the node we are interested in
*
* @return the index of the child in the parent, or -1 if either <code>child</code> or <code>parent</code> are <code>null</code> or don't belong to this tree model
*/
@SuppressWarnings("unchecked")
public int getIndexOfChild(final Object parent, final Object child) {
if (parent == null || child == null) {
throw new IllegalArgumentException("Unexpected null parent or child");
}
int index = 0;
if (parent instanceof RapidBean) {
final RapidBean parentBean = (RapidBean) parent;
List<PropertyCollection> colPropList;
if (this.showBeanLinks) {
colPropList = parentBean.getColProperties();
} else {
colPropList = parentBean.getColPropertiesComposition();
}
if (this.showProperties) {
final DocumentTreeNodePropCol childPropTreeNode = (DocumentTreeNodePropCol) child;
int i = 0;
for (PropertyCollection colProp : colPropList) {
if (childPropTreeNode.getColProp() == colProp) {
index = i;
break;
}
i++;
}
} else { // show properties == false
RapidBean childBean;
if (child instanceof RapidBean) {
childBean = (RapidBean) child;
} else if (child instanceof DocumentTreeNodeBeanLink) {
childBean = ((DocumentTreeNodeBeanLink) child).getLinkedBean();
} else {
throw new RapidBeansRuntimeException("Unexpected child class \"" + child.getClass().getName()
+ "\" for bean tree model");
}
boolean found = false;
for (PropertyCollection colProp : colPropList) {
final Collection<RapidBean> colBeans = (Collection<RapidBean>) colProp.getValue();
for (RapidBean bean : colBeans) {
if (bean == childBean) {
found = true;
break;
}
}
if (found) {
break;
}
}
}
} else if (parent instanceof DocumentTreeNodePropCol) {
final DocumentTreeNodePropCol parentCollection = (DocumentTreeNodePropCol) parent;
RapidBean childBean;
if (child instanceof RapidBean) {
childBean = (RapidBean) child;
} else if (child instanceof DocumentTreeNodeBeanLink) {
childBean = ((DocumentTreeNodeBeanLink) child).getLinkedBean();
} else {
throw new RapidBeansRuntimeException("Unexpected child class \"" + child.getClass().getName()
+ "\" for bean tree model");
}
index = ((ReadonlyListCollection<Link>) parentCollection.getColProp().getValue()).indexOf(childBean);
} else {
throw new RapidBeansRuntimeException("Unexpected parent class \"" + parent.getClass().getName()
+ "\" for bean tree model");
}
return index;
}
/**
* the TreeModelListeners.
*/
private Collection<TreeModelListener> listeners = new ArrayList<TreeModelListener>();
/**
* add a TreeModelListener.
*
* @param l
* the TreeModelListener to add
*/
public void addTreeModelListener(final TreeModelListener l) {
this.listeners.add(l);
}
/**
* remove a TreeModelListener.
*
* @param l
* the TreeModelListener to remove
*/
public void removeTreeModelListener(final TreeModelListener l) {
this.listeners.remove(l);
}
/**
* @param path
* the tree path
* @param newValue
* the new value
*/
public void valueForPathChanged(final TreePath path, final Object newValue) {
TreeModelEvent e = new TreeModelEvent(this, path, new int[] { this.getIndexOfChild(path.getParentPath()
.getLastPathComponent(), newValue) }, new Object[] { newValue });
for (TreeModelListener listener : this.listeners) {
listener.treeNodesChanged(e);
}
}
public DocumentTreeNodePropCol findColPropNode(final Property property) {
final RapidBean bean = property.getBean();
final int len = this.getChildCount(bean);
for (int i = 0; i < len; i++) {
final DocumentTreeNodePropCol currentColPropNode = (DocumentTreeNodePropCol) this.getChild(bean, i);
if (currentColPropNode.getColProp() == property) {
return currentColPropNode;
}
}
return null;
}
/**
* Example Array for toArray.
*/
static final Object[] OA = new Object[0];
/**
* retrieve an Object Array containing the path of all parent beans and
* Properties.
*
* @param argObject
* the object: ether a bean or a BBPropCollctionTreeNode
* @param includeObjectSelf
* if the object given should also be included in the path
*
* @return the Object array which is empty if there isn't any parent
*/
protected Object[] getParentObjects(final Object argObject, final boolean includeObjectSelf) {
ArrayList<Object> al = new ArrayList<Object>();
if (includeObjectSelf) {
al.add(argObject);
}
Object o = argObject;
while (o != null) {
if (o instanceof RapidBean) {
if (this.showProperties) {
PropertyCollection parentProp = ((RapidBean) o).getParentProperty();
if (parentProp == null) {
o = null;
} else {
o = new DocumentTreeNodePropColComp(parentProp);
}
} else { // showProperties == false
o = ((RapidBean) o).getParentBean();
}
} else if (o instanceof DocumentTreeNodeBeanLink) {
if (o instanceof RapidBean) {
PropertyCollection parentProp = ((DocumentTreeNodeBeanLink) o).getLinkedBean().getParentProperty();
if (parentProp == null) {
o = null;
} else {
o = new DocumentTreeNodePropColLink(parentProp);
}
} else { // showProperties == false
o = ((DocumentTreeNodeBeanLink) o).getLinkedBean().getParentBean();
}
} else if (o instanceof DocumentTreeNodePropCol) {
if (((DocumentTreeNodePropCol) o).getColProp() != null) {
o = ((DocumentTreeNodePropCol) o).getColProp().getBean();
} else {
o = null;
}
} else {
throw new RapidBeansRuntimeException("Unexpected parent class \"" + o.getClass().getName()
+ "\" for bean tree model");
}
if (o != null) {
al.add(o);
}
}
// stick the objects found into an array in reverse order.
final int alSize = al.size();
Object[] oa = new Object[alSize];
int j = 0;
for (int i = alSize - 1; i >= 0; i--) {
oa[j++] = al.get(i);
}
return oa;
}
/**
* Fires a change event for a certain node (bean or Property) that indicates
* that the tree structure has changed below this root.
*
* @param root
* the root bean or property with changes below
* @param tree
* the tree widget
*/
protected void fireTreeStructureChanged(final Object root, final JTree tree) {
if (!(root instanceof RapidBean || root instanceof DocumentTreeNodePropCol)) {
throw new RapidBeansRuntimeException("unexpected class of given root object: " + root.getClass().getName());
}
final MyTreeNode treeNode = findPresentedTreeNodes(tree, this.getRoot());
TreeModelEvent e = new TreeModelEvent(this, new Object[] { root });
for (TreeModelListener listener : this.listeners) {
listener.treeStructureChanged(e);
}
if (treeNode != null) {
expand(tree, treeNode);
}
}
@SuppressWarnings("unchecked")
private MyTreeNode findPresentedTreeNodes(final JTree tree, final Object presentedObject) {
MyTreeNode node = null;
final TreePath path = this.findTreePath(tree, presentedObject);
if (path != null) {
node = new MyTreeNode(path);
if (presentedObject instanceof RapidBean) {
for (final Property prop : (((RapidBean) presentedObject)).getPropertyList()) {
if (prop instanceof PropertyCollection) {
final MyTreeNode subnode = findPresentedTreeNodes(tree, new DocumentTreeNodePropColComp(
((PropertyCollection) prop)));
if (subnode != null) {
node.addPresentedChildNode(subnode);
}
}
}
} else if (presentedObject instanceof DocumentTreeNodePropCol) {
PropertyCollection prop = ((DocumentTreeNodePropCol) presentedObject).getColProp();
if (prop.getValue() != null) {
if (((TypePropertyCollection) prop.getType()).isComposition()) {
for (final Link link : (Collection<Link>) prop.getValue()) {
if (link instanceof RapidBean) {
final MyTreeNode subnode = findPresentedTreeNodes(tree, link);
if (subnode != null) {
node.addPresentedChildNode(subnode);
}
}
}
} else {
for (final Link link : (Collection<Link>) prop.getValue()) {
if (link instanceof RapidBean) {
final TreePath path1 = this.findTreePath(tree, presentedObject);
if (path1 != null) {
node.addPresentedChildNode(new MyTreeNode(path1));
}
}
}
}
}
} else {
throw new RuntimeException("unexpected instance");
}
}
return node;
}
private void expand(final JTree tree, final MyTreeNode node) {
if (node.isExpanded()) {
tree.expandPath(node.getTreePath());
for (final MyTreeNode subnode : node.getPresentedChildNode()) {
expand(tree, subnode);
}
}
}
private TreePath findTreePath(final JTree tree, final Object presentedObject) {
TreePath treePath = null;
int row = 0;
while (true) {
treePath = tree.getPathForRow(row++);
if (treePath == null) {
break;
} else if (presentedObject instanceof RapidBean) {
if (treePath.getLastPathComponent() == presentedObject) {
break;
}
} else if (presentedObject instanceof DocumentTreeNodePropCol) {
if (treePath.getLastPathComponent() instanceof DocumentTreeNodePropCol
&& ((DocumentTreeNodePropCol) presentedObject).getColProp() == ((DocumentTreeNodePropCol) treePath
.getLastPathComponent()).getColProp()) {
break;
}
} else {
throw new RuntimeException("unexpected instance");
}
}
return treePath;
}
private class MyTreeNode {
private ArrayList<MyTreeNode> presentedChildNodes = new ArrayList<MyTreeNode>();
private TreePath treePath = null;
public MyTreeNode(TreePath path) {
this.treePath = path;
}
public void addPresentedChildNode(final MyTreeNode node) {
this.presentedChildNodes.add(node);
}
public ArrayList<MyTreeNode> getPresentedChildNode() {
return this.presentedChildNodes;
}
public TreePath getTreePath() {
return treePath;
}
public boolean isExpanded() {
return (this.presentedChildNodes.size() > 0);
}
}
/**
* Fires a change event for created beans.
*
* @param path
* the parent path for these changes
* @param childIndices
* the child indices
* @param beans
* the child objects
*/
protected void fireBeansInserted(final TreePath path, final int[] childIndices, final RapidBean[] beans) {
TreeModelEvent e = new TreeModelEvent(this, path, childIndices, beans);
for (TreeModelListener listener : this.listeners) {
listener.treeNodesInserted(e);
}
}
/**
* Fires a change event for deleted beans.
*
* @param path
* the parent path for these changes
* @param childIndices
* the child indices
* @param beans
* the child objects
*/
protected void fireBeansDeleted(final TreePath path, final int[] childIndices, final RapidBean[] beans) {
TreeModelEvent e = new TreeModelEvent(this, path, childIndices, beans);
for (TreeModelListener listener : this.listeners) {
listener.treeNodesRemoved(e);
}
}
/**
* Fires a change event for modified beans.
*
* @param path
* the parent path for these changes
*/
protected void fireBeanChanged(final TreePath path) {
TreeModelEvent e = new TreeModelEvent(this, path);
for (TreeModelListener listener : this.listeners) {
listener.treeNodesChanged(e);
}
}
}