/******************************************************************************* * Copyright (c) 2006-2013 The RCP Company 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: * The RCP Company - initial API and implementation *******************************************************************************/ package com.rcpcompany.uibindings.internal.decorators; import org.eclipse.core.databinding.Binding; import org.eclipse.core.databinding.observable.list.IListChangeListener; import org.eclipse.core.databinding.observable.list.IObservableList; import org.eclipse.core.databinding.observable.list.ListChangeEvent; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExecutableExtension; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import com.rcpcompany.uibindings.Constants; import com.rcpcompany.uibindings.IBindingDataType; import com.rcpcompany.uibindings.IDisposable; import com.rcpcompany.uibindings.IUIBindingDecorator; import com.rcpcompany.uibindings.IValueBinding; import com.rcpcompany.uibindings.UIBindingsUtils; import com.rcpcompany.uibindings.UIBindingsUtils.IClassIdentiferMapper; import com.rcpcompany.uibindings.decorators.SimpleUIBindingDecorator; import com.rcpcompany.uibindings.observables.EMFListAttributeList; import com.rcpcompany.uibindings.utils.IManagerRunnable; import com.rcpcompany.utils.logging.LogUtils; /** * {@link IUIBindingDecorator} used for {@link EObject} references. * <p> * It uses one of the {@link EStructualFeature structural features} of the reference. * * @author Tonny Madsen, The RCP Company */ public class GenericEObjectDecorator extends SimpleUIBindingDecorator implements IUIBindingDecorator, IExecutableExtension { /** * The valid values in this decorator. */ private IObservableList myValidValues; /** * Listener on {@link #myValidValues} to re-validate when the list changes. */ private IListChangeListener myValidValuesListener = null; /** * The mapper used in this decorator. */ private IClassIdentiferMapper myClassIdentiferMapper = null; /** * The string used for <code>null</code> in the binding. */ private String myNullLabel; /** * Whether the UI name is qualified with the class name. * <p> * * TODO: Implement! */ private boolean myQualified = false; /** * If the mapper has used any specific attribute from the EObject, then this must be added so we * can track the value properly. */ @Override public IObservableValue getDisplayObservableValue(IObservableValue value) { if (myClassIdentiferMapper != null) { try { final IObservableValue ov = myClassIdentiferMapper.getObservableValue(value, getBinding().getContext() .getEditingDomain()); if (ov != null) return ov; } catch (final Exception ex) { LogUtils.error(myClassIdentiferMapper, ex); } } return super.getDisplayObservableValue(value); } @Override public void init(IValueBinding binding) { super.init(binding); /* * Get the label used for the null value */ myNullLabel = getBinding().getArgument(Constants.ARG_NULL_LABEL, String.class, ""); /* * Now analyze the list to find the best mapping */ final EClassifier type = getBinding().getDataType().getEType(); if (!(type instanceof EClass)) { myClassIdentiferMapper = UIBindingsUtils.DEFAULT_MAPPER; } else { myClassIdentiferMapper = UIBindingsUtils.createClassIdentiferMapper(getBinding(), (EClass) type); } deduceValidValues(); } @Override public void dispose() { if (myValidValues != null && myValidValuesListener != null) { myValidValues.removeListChangeListener(myValidValuesListener); IManagerRunnable.Factory.cancelAsyncExec("validValues changed", getBinding()); } if (myValidUIList != null) { myValidUIList.dispose(); } if (myClassIdentiferMapper instanceof IDisposable) { ((IDisposable) myClassIdentiferMapper).dispose(); } super.dispose(); } /** * Calculates the valid values. * <p> * Must be called early, as the result is used to determine whether this decorator is r/o. */ private void deduceValidValues() { /* * If the decoration is read-only, then there are no reason to find the valid values */ if (!super.isChangeable()) return; final IBindingDataType dynDataType = getBinding().getDataType(); if (dynDataType == null) return; final IObservableList argument = getBinding().getArgument(Constants.ARG_VALID_VALUES, IObservableList.class, null); if (argument == null) return; final Object elementType = argument.getElementType(); final Class<?> expectedElementType; if (elementType instanceof EClass) { expectedElementType = ((EClass) elementType).getInstanceClass(); } else if (elementType instanceof EReference) { expectedElementType = ((EReference) elementType).getEReferenceType().getInstanceClass(); } else { LogUtils.error(getBinding(), "List for '" + Constants.ARG_VALID_VALUES + "' not with EClass or EReferences: got " + elementType, getBinding().getCreationPoint()); return; } final Class<?> actualElementType = dynDataType.getDataType(); if (!expectedElementType.isAssignableFrom(actualElementType)) { LogUtils.error(getBinding(), "List of supplied valid type does not match feature of binding: expected " + expectedElementType.getName() + " got " + actualElementType.getName(), getBinding() .getCreationPoint()); return; } myValidValues = argument; if (myValidValuesListener != null) { myValidValues.addListChangeListener(myValidValuesListener); } } /** * If the list of valid values changes, then we need to re-validate the current value on the UI * side as it might ad/remove an outstanding error. * * One problem though: Assume we had the valid list [a, b, c] with the current value "a" and * then change that to [a', b, c], then the model side may change!!! */ @Override public void decorateDBBindings(final Binding decoratedToModelDB) { super.decorateDBBindings(decoratedToModelDB); myValidValuesListener = new IListChangeListener() { @Override public void handleListChange(ListChangeEvent event) { if (event.diff.isEmpty()) return; IManagerRunnable.Factory.asyncExec("validValues changed", getBinding(), new Runnable() { @Override public void run() { decoratedToModelDB.updateTargetToModel(); } }); } }; if (myValidValues != null) { myValidValues.addListChangeListener(myValidValuesListener); } } @Override public boolean isChangeable() { return myValidValues != null && super.isChangeable(); } @Override public IObservableList getValidUIList() { if (!calculatedValidUIList) { calculatedValidUIList = true; if (!isChangeable()) return null; final EMFListAttributeList uil = new EMFListAttributeList(myValidValues, myClassIdentiferMapper, String.class, myNullLabel); if (!getBinding().getDataType().isRequired()) { uil.setNullLabel(myNullLabel); } myValidUIList = uil; } return myValidUIList; } @Override protected Object convertModelToUI(Object fromObject) { if (myClassIdentiferMapper == null) return myNullLabel; /* * Any exception just falls through */ final Object o = myClassIdentiferMapper.map(fromObject); return o != null ? o.toString() : ""; } @Override protected Object convertUIToModel(Object fromObject) { if (fromObject == null) return null; if (fromObject.equals("")) return null; if (!isChangeable()) throw new IllegalArgumentException("Value cannot be changed"); if (myNullLabel.equals(fromObject)) return null; for (final Object to : myValidValues) { if (fromObject.equals(convertModelToUI(to))) return to; } throw new IllegalArgumentException("Unknown value"); } @Override public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException { if ("qualified".equals(data)) { myQualified = true; } } }