blob: 78009dba74df141c7670cff81bd1310b83472e03 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.internal.databinding.beans;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.jface.databinding.BindingException;
import org.eclipse.jface.databinding.ChangeEvent;
import org.eclipse.jface.databinding.IChangeListener;
import org.eclipse.jface.databinding.ITree;
import org.eclipse.jface.databinding.TreeModelDescription;
/**
* This class is a JavaBean implementation for a ITree. It is a ITree facade that is built
* from a <code>TreeModelDescription</code>
*/
public class JavaBeanTree implements ITree {
/**
* Keep track of element parents, and children
* @since 3.2
*/
private class TreeNode {
private Object parent;
private List children;
/**
* @param parent
*/
public TreeNode(Object parent) {
this.parent=parent;
}
/**
* @return children
*/
public List getChildren() {
return children==null? Collections.EMPTY_LIST: children;
}
/**
* @param children
*/
public void setChildren(List children) {
this.children = children;
}
/**
* @return parent
*/
public Object getParent() {
return parent;
}
}
private final static int READ = 0;
private final static int WRITE = 1;
private TreeModelDescription modelDescription;
private Object[] rootElements;
private HashMap descriptors = new HashMap();
private ITree.ChangeSupport changeSupport = null;
private HashMap nodes = new HashMap();
private boolean updating = false;
private PropertyChangeListener elementListener = new PropertyChangeListener() {
public void propertyChange(java.beans.PropertyChangeEvent event) {
if (!updating && changeSupport!=null) {
String[] properties = modelDescription.getChildrenProperties(event.getSource().getClass());
// Determine if the change is on a children Property, in which case
// that level of the tree has changed.
if (properties!=null && event.getPropertyName()!=null &&
Arrays.asList(properties).contains(event.getPropertyName())) {
// Assume children have changed
changeSupport.fireTreeChange(ChangeEvent.REPLACE, null, getChildren(event.getSource()), event.getSource(), -1);
}
else {
// Assume a property has changed that may change the way this element
// is rendered in a viewer, use our tracking to figure out the index.
TreeNode node = (TreeNode)nodes.get(event.getSource());
if (node!=null) {
int index = -1;
Object[] children = getChildren(node.getParent());
if (children!=null) {
index = Arrays.asList(children).indexOf(event.getSource());
}
if (index<0) index = ChangeEvent.POSITION_UNKNOWN;
changeSupport.fireTreeChange(ChangeEvent.CHANGE, null, event.getSource(), node.getParent(), index);
}
}
}
}
};
HashMap listenerSupport = new HashMap();
/**
* @param modelDescripton
*/
public JavaBeanTree(TreeModelDescription modelDescripton) {
this.modelDescription = modelDescripton;
if (modelDescripton.getRoot()==null)
rootElements = Collections.EMPTY_LIST.toArray();
else
rootElements = modelDescripton.getRoot().getClass().isArray()?
(Object[]) modelDescripton.getRoot():
new Object[] { modelDescripton.getRoot() };
TreeNode root = new TreeNode(null);
nodes.put(this, root);
hookup(null, rootElements);
}
// pick up the leaf most registered class that is assignable
// from o.class.
private Class getRelevantClass(Object o) {
if (o==null)
return null;
Class leafClass = null;
Class current = o.getClass();
Class[] list = modelDescription.getTypes();
for (int i = 0; i < list.length; i++)
if (list[i].isAssignableFrom(current)) {
// found registered class
if (leafClass==null)
leafClass=list[i];
else
if (leafClass.isAssignableFrom(list[i]))
leafClass=list[i];
}
return leafClass;
}
private Method[] getInvocationMethods(Class clazz, int methodType) {
if (clazz==null)
return null;
String[] childrenProperties = modelDescription
.getChildrenProperties(clazz);
if (childrenProperties == null || childrenProperties.length == 0)
return null;
PropertyHelper[] properties = (PropertyHelper[]) descriptors.get(clazz);
if (properties == null) {
List newpProperties = new ArrayList();
// Walk by properties to preserve the tree order
for (int i = 0; i < childrenProperties.length; i++) {
PropertyHelper property = new PropertyHelper(
childrenProperties[i], clazz);
if (property.getGetter() != null || property.getSetter() != null) {
newpProperties.add(property);
}
}
properties = (PropertyHelper[]) newpProperties
.toArray(new PropertyHelper[newpProperties.size()]);
descriptors.put(clazz, properties);
}
List methods = new ArrayList();
for (int i = 0; i < properties.length; i++) {
if (methodType==READ)
methods.add(properties[i].getGetter());
else
methods.add(properties[i].getSetter());
}
return (Method[])methods.toArray(new Method[methods.size()]);
}
private Method[] getReadMethods(Object element) {
Class clazz = getRelevantClass(element);
if (clazz==null) return null;
Method[] methods = getInvocationMethods(clazz, READ);
return methods;
}
private Method[] getWriteMethods (Object element) {
Class clazz = getRelevantClass(element);
if (clazz==null) return null;
Method[] methods = getInvocationMethods(clazz, WRITE);
return methods;
}
private void hookup (Object parent, Object[] children) {
Object key = parent==null?JavaBeanTree.this:parent;
ListenerSupport support = (ListenerSupport) listenerSupport.get(key);
if (support==null) {
support = new ListenerSupport(elementListener);
listenerSupport.put(key, support);
}
support.setHookTargets(children);
// Track a child's index
trackChildren(parent, children);
}
public Object[] getChildren(Object parentElement) {
if (parentElement==null)
return rootElements;
Method[] getters = getReadMethods(parentElement);
if (getters==null)
return Collections.EMPTY_LIST.toArray();
List children = new ArrayList();
try {
for (int i = 0; i < getters.length; i++) {
Object list = getters[i].invoke(parentElement, new Object[0]);
if (list!=null) {
if (getters[i].getReturnType().isArray())
children.addAll(Arrays.asList((Object[])list));
else
children.addAll((Collection) getters[i].invoke(parentElement, new Object[0]));
}
}
} catch (IllegalArgumentException e) {
throw new BindingException(e.getLocalizedMessage());
} catch (IllegalAccessException e) {
throw new BindingException(e.getLocalizedMessage());
} catch (InvocationTargetException e) {
throw new BindingException(e.getLocalizedMessage());
}
Object[] result = children.toArray();
hookup(parentElement, result);
trackChildren(parentElement, result);
return result;
}
private void trackChildren(Object parentElement, Object[] children) {
Object key = parentElement==null?JavaBeanTree.this : parentElement;
TreeNode node = (TreeNode) nodes.get(key);
Set removed = new HashSet(node.getChildren());
List newList = children==null? new ArrayList(): Arrays.asList(children);
if (children!=null)
for (int i=0; i<children.length; i++) {
if (!removed.remove(children[i])) {
// new child
TreeNode child = new TreeNode(parentElement);
nodes.put(children[i], child);
}
}
for (Iterator itr=removed.iterator(); itr.hasNext();)
nodes.remove(itr.next());
node.setChildren(newList);
}
public void setChildren(Object parentElement, Object[] children) {
if (parentElement==null)
throw new BindingException ("Changing root element/s is not supported"); //$NON-NLS-1$
Method[] setters = getWriteMethods(parentElement);
// TODO we can try to figure out (by type) which methods to invoke on which object
if (setters==null || setters.length!=1)
throw new BindingException("Can not determine children set method for: "+parentElement.getClass().getName()); //$NON-NLS-1$
Method setter = setters[0];
Class[] parameters = setter.getParameterTypes();
Object arg;
if (parameters[0].isArray())
arg = children;
else
arg = Arrays.asList(children);
try {
setter.invoke(parentElement, new Object[] { arg } );
} catch (IllegalArgumentException e) {
throw new BindingException(e.getLocalizedMessage());
} catch (IllegalAccessException e) {
throw new BindingException(e.getLocalizedMessage());
} catch (InvocationTargetException e) {
throw new BindingException(e.getLocalizedMessage());
}
hookup(parentElement, children);
if (changeSupport!=null)
changeSupport.fireTreeChange(ChangeEvent.REPLACE, null, children, parentElement, -1);
}
public boolean hasChildren(Object element) {
Object[] children = getChildren(element);
return (children!=null && children.length>0);
}
public Class[] getTypes() {
return modelDescription.getTypes();
}
public void addTreeChangeListener(IChangeListener listener) {
if (listener==null)
return;
if (changeSupport==null)
changeSupport = new ITree.ChangeSupport(this);
changeSupport.addTreeChangeListener(listener);
}
public void removeTreeChangeListener(IChangeListener listener) {
if (listener==null || changeSupport==null)
return;
changeSupport.removeTreeChangeListener(listener);
}
public void dispose() {
if (listenerSupport!=null) {
for (Iterator itr=listenerSupport.values().iterator(); itr.hasNext();)
((ListenerSupport)itr.next()).dispose();
listenerSupport=null;
descriptors=null;
changeSupport=null;
modelDescription=null;
}
}
}