blob: a378d13f6338956e04d092b71081a802d51874e2 [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;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.eclipse.jface.databinding.BindSpec;
import org.eclipse.jface.databinding.BindingException;
import org.eclipse.jface.databinding.IBindSpec;
import org.eclipse.jface.databinding.IBindSupportFactory;
import org.eclipse.jface.databinding.IChangeListener;
import org.eclipse.jface.databinding.IDataBindingContext;
import org.eclipse.jface.databinding.IUpdatable;
import org.eclipse.jface.databinding.IUpdatableCollection;
import org.eclipse.jface.databinding.IUpdatableFactory;
import org.eclipse.jface.databinding.IUpdatableTree;
import org.eclipse.jface.databinding.IUpdatableValue;
import org.eclipse.jface.databinding.NestedProperty;
import org.eclipse.jface.databinding.Property;
import org.eclipse.jface.databinding.TreeModelDescription;
import org.eclipse.jface.databinding.converter.IConverter;
import org.eclipse.jface.databinding.converterfunction.ConversionFunctionRegistry;
import org.eclipse.jface.databinding.converters.FunctionalConverter;
import org.eclipse.jface.databinding.converters.IdentityConverter;
import org.eclipse.jface.databinding.updatables.SettableValue;
import org.eclipse.jface.databinding.validator.IValidator;
import org.eclipse.jface.databinding.validator.ValidatorRegistry;
import org.eclipse.jface.util.Assert;
/**
*
* @since 3.2
*/
public class DataBindingContext implements IDataBindingContext {
private List createdUpdatables = new ArrayList();
private DataBindingContext parent;
private List partialValidationMessages = new ArrayList();
private List validationMessages = new ArrayList();
private SettableValue partialValidationMessage = new SettableValue(
String.class, ""); //$NON-NLS-1$
private SettableValue validationMessage = new SettableValue(String.class,
""); //$NON-NLS-1$
private SettableValue combinedValidationMessage = new SettableValue(
String.class, ""); //$NON-NLS-1$
private List factories = new ArrayList();
private List bindSupportFactories = new ArrayList();
protected int validationTime;
protected int updateTime;
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.databinding.IDataBindingContext#addBindSupportFactory(org.eclipse.jface.databinding.IBindSupportFactory)
*/
public void addBindSupportFactory(IBindSupportFactory factory) {
bindSupportFactories.add(factory);
}
/**
*
*/
public DataBindingContext() {
registerFactories();
registerDefaultBindSupportFactory();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.databinding.IDataBindingContext#dispose()
*/
public void dispose() {
for (Iterator it = createdUpdatables.iterator(); it.hasNext();) {
IUpdatable updatable = (IUpdatable) it.next();
updatable.dispose();
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.databinding.IDataBindingContext#getCombinedValidationMessage()
*/
public IUpdatableValue getCombinedValidationMessage() {
return combinedValidationMessage;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.databinding.IDataBindingContext#getPartialValidationMessage()
*/
public IUpdatableValue getPartialValidationMessage() {
return partialValidationMessage;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.databinding.IDataBindingContext#getValidationMessage()
*/
public IUpdatableValue getValidationMessage() {
return validationMessage;
}
protected void registerDefaultBindSupportFactory() {
// Add the default bind support factory
addBindSupportFactory(new IBindSupportFactory() {
public IValidator createValidator(Class fromType, Class toType,
Object modelDescription) {
if (fromType == null || toType == null) {
// System.err.println("FIXME: Boris, is this a bug? In
// registerDefaultBindSupportFactory.addBindSupportFactory.createValidator:
// fromType is null or toType is null!!!!!"); //$NON-NLS-1$
// try {
// throw new BindingException("Cannot create proper
// IValidator."); //$NON-NLS-1$
// } catch (BindingException e) {
// e.printStackTrace();
// System.err.println();
// }
return new IValidator() {
public String isPartiallyValid(Object value) {
return null;
}
public String isValid(Object value) {
return null;
}
};
}
IValidator dataTypeValidator = ValidatorRegistry.getDefault()
.get(fromType, toType);
if (dataTypeValidator == null) {
throw new BindingException(
"No IValidator is registered for conversions from " + fromType.getName() + " to " + toType.getName()); //$NON-NLS-1$ //$NON-NLS-2$
}
return dataTypeValidator;
}
public IConverter createConverter(Class fromType, Class toType,
Object modelDescription) {
if (toType == null) {
return null;
}
if (fromType == toType) {
return new IdentityConverter(fromType, toType);
}
if (ConversionFunctionRegistry.canConvertPair(fromType, toType)) {
return new FunctionalConverter(fromType, toType);
}
// FIXME: djo -- This doesn't always work in the case of object
// types?
if (toType.isAssignableFrom(fromType)
|| fromType.isAssignableFrom(toType)) {
return new IdentityConverter(fromType, toType);
}
return null;
}
});
}
protected void registerFactories() {
addUpdatableFactory(new IUpdatableFactory() {
public IUpdatable createUpdatable(Map properties,
Object description, IDataBindingContext bindingContext) {
if (description instanceof Property) {
Property propertyDescription = (Property) description;
Object o = propertyDescription.getObject();
if (o instanceof IUpdatableValue) {
IUpdatableValue updatableValue = (IUpdatableValue) o;
Class propertyType = propertyDescription
.getPropertyType();
if (propertyType == null) {
throw new BindingException(
"Missing required property type for binding to a property of an IUpdatableValue."); //$NON-NLS-1$
}
Boolean isCollectionProperty = propertyDescription
.isCollectionProperty();
if (isCollectionProperty == null) {
throw new BindingException(
"Missing required property collection information for binding to a property of an IUpdatableValue."); //$NON-NLS-1$
}
Object propertyID = propertyDescription.getPropertyID();
if (isCollectionProperty.booleanValue()) {
return new NestedUpdatableCollection(
DataBindingContext.this, updatableValue,
propertyID, propertyType);
}
return new NestedUpdatableValue(
DataBindingContext.this, updatableValue,
propertyID, propertyType);
} else if (o instanceof List) {
return new ListUpdatableCollection(
(List) o,
propertyDescription.getPropertyType() == null ? Object.class
: propertyDescription.getPropertyType());
}
} else if (description instanceof TreeModelDescription) {
TreeModelDescription treeModelDescription = (TreeModelDescription) description;
if (treeModelDescription.getRoot() != null) {
if (treeModelDescription.getRoot() instanceof IUpdatable) {
if (treeModelDescription.getRoot() instanceof IUpdatableTree)
return (IUpdatableTree) treeModelDescription
.getRoot();
// Nest the TreeModelDescription's root
return new NestedUpdatableTree(
DataBindingContext.this,
treeModelDescription);
} else if (treeModelDescription.getRoot() instanceof Property) {
// Create an Updatable for the
// TreeModelDescription's root first
TreeModelDescription newDescription = new TreeModelDescription(
DataBindingContext.this
.createUpdatable(treeModelDescription
.getRoot()));
Class[] types = treeModelDescription.getTypes();
for (int i = 0; i < types.length; i++) {
String[] props = treeModelDescription
.getChildrenProperties(types[i]);
for (int j = 0; j < props.length; j++)
newDescription.addChildrenProperty(
types[i], props[j]);
}
return DataBindingContext.this
.createUpdatable(newDescription);
}
}
return null;
}
return null;
}
});
}
private void removeValidationListenerAndMessage(List listOfPairs,
Object first) {
for (int i = listOfPairs.size() - 1; i >= 0; i--) {
Pair pair = (Pair) listOfPairs.get(i);
if (pair.a.equals(first)) {
listOfPairs.remove(i);
return;
}
}
return;
}
/**
* @param listener
* @param partialValidationErrorOrNull
*/
public void updatePartialValidationError(IChangeListener listener,
String partialValidationErrorOrNull) {
removeValidationListenerAndMessage(partialValidationMessages, listener);
if (partialValidationErrorOrNull != null) {
partialValidationMessages.add(new Pair(listener,
partialValidationErrorOrNull));
}
updateValidationMessage(
combinedValidationMessage,
partialValidationMessages.size() > 0 ? partialValidationMessages
: validationMessages);
updateValidationMessage(partialValidationMessage,
partialValidationMessages);
}
/**
* @param listener
* @param validationErrorOrNull
*/
public void updateValidationError(IChangeListener listener,
String validationErrorOrNull) {
removeValidationListenerAndMessage(validationMessages, listener);
if (validationErrorOrNull != null) {
validationMessages.add(new Pair(listener, validationErrorOrNull));
}
updateValidationMessage(
combinedValidationMessage,
partialValidationMessages.size() > 0 ? partialValidationMessages
: validationMessages);
updateValidationMessage(validationMessage, validationMessages);
}
private void updateValidationMessage(
SettableValue validationSettableMessage, List listOfPairs) {
if (listOfPairs.size() == 0) {
validationSettableMessage.setValue(""); //$NON-NLS-1$
} else {
validationSettableMessage.setValue(((Pair) listOfPairs
.get(listOfPairs.size() - 1)).b);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.databinding.IDataBindingContext#bind(org.eclipse.jface.databinding.IUpdatable,
* org.eclipse.jface.databinding.IUpdatable,
* org.eclipse.jface.databinding.IBindSpec)
*/
public void bind(IUpdatable targetUpdatable, IUpdatable modelUpdatable,
IBindSpec bindSpec) {
Binding binding;
if (bindSpec == null) {
bindSpec = new BindSpec(null, null);
}
if (targetUpdatable instanceof IUpdatableValue) {
if (modelUpdatable instanceof IUpdatableValue) {
IUpdatableValue target = (IUpdatableValue) targetUpdatable;
IUpdatableValue model = (IUpdatableValue) modelUpdatable;
fillBindSpecDefaults(bindSpec, target.getValueType(), model
.getValueType(), null);
binding = new ValueBinding(this, target, model, bindSpec);
} else {
throw new BindingException(
"incompatible updatables: target is value, model is " + modelUpdatable.getClass().getName()); //$NON-NLS-1$
}
} else if (targetUpdatable instanceof IUpdatableCollection) {
if (modelUpdatable instanceof IUpdatableCollection) {
IUpdatableCollection target = (IUpdatableCollection) targetUpdatable;
IUpdatableCollection model = (IUpdatableCollection) modelUpdatable;
fillBindSpecDefaults(bindSpec, target.getElementType(), model
.getElementType(), null);
binding = new CollectionBinding(this, target, model, bindSpec);
} else {
throw new BindingException(
"incompatible updatables: target is collection, model is " + modelUpdatable.getClass().getName()); //$NON-NLS-1$
}
} else if (targetUpdatable instanceof IUpdatableTree) {
if (modelUpdatable instanceof IUpdatableTree) {
IUpdatableTree target = (IUpdatableTree) targetUpdatable;
IUpdatableTree model = (IUpdatableTree) modelUpdatable;
// Tree bindings does not provide conversions between level
// elements (no bindSpec)
binding = new TreeBinding(this, target, model);
} else {
throw new BindingException(
"incompatible updatables: target is collection, model is " + modelUpdatable.getClass().getName()); //$NON-NLS-1$
}
} else {
throw new BindingException("not yet implemented"); //$NON-NLS-1$
}
// DJO: Each binder is now responsible for adding its own change
// listeners.
// targetUpdatable.addChangeListener(binding);
// modelUpdatable.addChangeListener(binding);
binding.updateTargetFromModel();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.databinding.IDataBindingContext#bind(java.lang.Object,
* org.eclipse.jface.databinding.IUpdatable,
* org.eclipse.jface.databinding.IBindSpec)
*/
public void bind(Object targetDescription, IUpdatable modelUpdatable,
IBindSpec bindSpec) {
bind(createUpdatable(targetDescription), modelUpdatable, bindSpec);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.databinding.IDataBindingContext#bind(org.eclipse.jface.databinding.IUpdatable,
* java.lang.Object, org.eclipse.jface.databinding.IBindSpec)
*/
public void bind(IUpdatable targetUpdatable, Object modelDescription,
IBindSpec bindSpec) {
if (bindSpec == null) {
bindSpec = new BindSpec(null, null);
}
Class fromType = null;
if (targetUpdatable instanceof IUpdatableValue) {
fromType = ((IUpdatableValue) targetUpdatable).getValueType();
} else if (targetUpdatable instanceof IUpdatableCollection) {
fromType = ((IUpdatableCollection) targetUpdatable)
.getElementType();
}
fillBindSpecDefaults(bindSpec, fromType, null, modelDescription);
bind(targetUpdatable, createUpdatable(modelDescription), bindSpec);
}
protected void fillBindSpecDefaults(IBindSpec bindSpec, Class fromType,
Class toType, Object modelDescriptionOrNull) {
if (bindSpec.getValidator() == null) {
((BindSpec) bindSpec).setValidator(createValidator(fromType,
toType, modelDescriptionOrNull));
}
if (bindSpec.getConverter() == null) {
((BindSpec) bindSpec).setConverter(createConverter(fromType,
toType, modelDescriptionOrNull));
}
}
public IValidator createValidator(Class fromType, Class toType,
Object modelDescription) {
for (int i = bindSupportFactories.size() - 1; i >= 0; i--) {
IBindSupportFactory bindSupportFactory = (IBindSupportFactory) bindSupportFactories
.get(i);
IValidator validator = bindSupportFactory.createValidator(fromType,
toType, modelDescription);
if (validator != null) {
return validator;
}
}
if (parent != null) {
return parent.createValidator(fromType, toType, modelDescription);
}
return null;
}
public IConverter createConverter(Class fromType, Class toType,
Object modelDescription) {
for (int i = bindSupportFactories.size() - 1; i >= 0; i--) {
IBindSupportFactory bindSupportFactory = (IBindSupportFactory) bindSupportFactories
.get(i);
IConverter converter = bindSupportFactory.createConverter(fromType,
toType, modelDescription);
if (converter != null) {
return converter;
}
}
if (parent != null) {
return parent.createConverter(fromType, toType, modelDescription);
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.databinding.IDataBindingContext#bind(java.lang.Object,
* java.lang.Object, org.eclipse.jface.databinding.IBindSpec)
*/
public void bind(Object targetDescription, Object modelDescription,
IBindSpec bindSpec) {
bind(createUpdatable(targetDescription), modelDescription, bindSpec);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.databinding.IDataBindingContext#createUpdatable(java.lang.Object)
*/
public IUpdatable createUpdatable(Object description) {
IUpdatable updatable = doCreateUpdatable(description, this);
if (updatable != null) {
createdUpdatables.add(updatable);
}
return updatable;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.databinding.IDataBindingContext#createNestedUpdatable(org.eclipse.jface.databinding.NestedProperty)
*/
public IUpdatable createNestedUpdatable(NestedProperty nestedProperty) {
IUpdatable lastChildUpdatable = null;
Object targetObject = nestedProperty.getObject();
if (nestedProperty.getPrototypeClass() != null) {
Class targetClazz = nestedProperty.getPrototypeClass();
StringTokenizer tokenizer = new StringTokenizer((String) nestedProperty.getPropertyID(), "."); //$NON-NLS-1$
while (tokenizer.hasMoreElements()) {
String nextDesc = (String) tokenizer.nextElement();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(targetClazz);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
Class discoveredClazz = null;
for (int i = 0; i < propertyDescriptors.length; i++) {
PropertyDescriptor descriptor = propertyDescriptors[i];
if (descriptor.getName().equals(
nextDesc)) {
discoveredClazz = descriptor.getPropertyType();
break;
}
}
if (discoveredClazz != null) {
targetClazz = discoveredClazz;
} else {
throw new BindingException("Error using prototype class to determine binding types."); //$NON-NLS-1$
}
} catch (BindingException be) {
throw be;
} catch (Exception e) {
e.printStackTrace();
throw new BindingException("Exeception using prototype class to determine binding types.", e); //$NON-NLS-1$
}
lastChildUpdatable = createUpdatable(new Property(targetObject,
nextDesc, targetClazz, new Boolean(false)));
targetObject = lastChildUpdatable;
}
} else {
String[] properties = (String[]) nestedProperty.getPropertyID();
for (int i = 0; i < properties.length; i++) {
String nextDesc = properties[i];
Class clazz = nestedProperty.getTypes()[i];
lastChildUpdatable = createUpdatable(new Property(targetObject,
nextDesc, clazz, new Boolean(false)));
targetObject = lastChildUpdatable;
}
}
return lastChildUpdatable;
}
protected IUpdatable doCreateUpdatable(Object description,
DataBindingContext thisDatabindingContext) {
for (int i = factories.size() - 1; i >= 0; i--) {
IUpdatableFactory factory = (IUpdatableFactory) factories.get(i);
IUpdatable result = factory.createUpdatable(null, description,
thisDatabindingContext);
if (result != null) {
return result;
}
}
if (parent != null) {
return parent
.doCreateUpdatable(description, thisDatabindingContext);
}
throw new BindingException("could not find updatable for " //$NON-NLS-1$
+ description);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.databinding.IDataBindingContext#addUpdatableFactory(org.eclipse.jface.databinding.IUpdatableFactory)
*/
public void addUpdatableFactory(IUpdatableFactory updatableFactory) {
// TODO: consider the fact that adding new factories for a given
// description
// may hide default ones (e.g., a new PropertyDescriptor may overide the
// ond for EMF)
factories.add(updatableFactory);
}
public void updateTargets() {
Assert.isTrue(false, "updateTargets is not yet implemented"); //$NON-NLS-1$
}
public void updateModels() {
Assert.isTrue(false, "updateModels is not yet implemented"); //$NON-NLS-1$
}
}