| /** |
| * Copyright (c) 2011, 2015 - Lunifera GmbH (Gross Enzersdorf, Austria), Loetz GmbH&Co.KG (69115 Heidelberg, Germany) |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Florian Pirchner - Initial implementation |
| */ |
| package org.eclipse.osbp.ecview.core.databinding.emf.model; |
| |
| import java.beans.PropertyChangeListener; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.core.databinding.beans.BeanProperties; |
| import org.eclipse.core.databinding.beans.PojoProperties; |
| import org.eclipse.core.databinding.observable.Realm; |
| import org.eclipse.core.databinding.observable.list.IObservableList; |
| import org.eclipse.core.databinding.observable.value.IObservableValue; |
| import org.eclipse.emf.databinding.EMFProperties; |
| import org.eclipse.emf.databinding.FeaturePath; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EPackage; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| |
| // TODO: Auto-generated Javadoc |
| /** |
| * This class is responsible to bind values inside the ECView UI model. |
| * <p> |
| * For now, 3 different types are known:<br> |
| * In all cases the given bean is an EObject, since the ECView model is based on |
| * Ecore. But there may be 3 different types of values contained in the EObject. |
| * <br> |
| * Example based on YList#selection. And the selection is an instance of |
| * ItemDTO. And we'd like to bind "selection.group.name"<br> |
| * ItemDTO can be of type... |
| * <ul> |
| * <li>EObject - then we need to use EMFBinding to bind the nested attributes.</li> |
| * <li>Bean- then we need to use EMFBinding to create a master binding value for |
| * YList#selection, and this binding is input for a Bean-DetailBinding.</li> |
| * <li>Pojo - then we need to use EMFBinding to create a master binding value |
| * for YList#selection, and this binding is input for a Pojo-DetailBinding.</li> |
| * </ul> |
| */ |
| public class ECViewModelBindable { |
| |
| /** |
| * Returns an observable value tracking the attribute path. |
| * |
| * @param yElement |
| * - The ECView model element |
| * @param attributePath |
| * - The attribute path being tracked |
| * @param elementType |
| * - The type that is contained in the yElement. For instance if |
| * YList#selection is of type Item.class, then this property is |
| * Item.class. |
| * @param emfNsURI |
| * - The namespace URI of the EMF package, if the elementType is |
| * provided by emf. Required to get the EClass for the given |
| * elementType. May be <code>null</code> if the elementType is |
| * not related to emf. |
| * @return the i observable value |
| */ |
| public static IObservableValue observeValue(EObject yElement, |
| String attributePath, Class<?> elementType, String emfNsURI) { |
| return observeValue(Realm.getDefault(), yElement, attributePath, |
| elementType, emfNsURI); |
| } |
| |
| /** |
| * Returns an observable value tracking the attribute path. |
| * |
| * @param realm |
| * - The realm |
| * @param yElement |
| * - The ECView model element |
| * @param attributePath |
| * - The attribute path being tracked |
| * @param elementType |
| * - The type that is contained in the yElement. For instance if |
| * YList#selection is of type Item.class, then this property is |
| * Item.class. |
| * @param emfNsURI |
| * - The namespace URI of the EMF package, if the elementType is |
| * provided by emf. Required to get the EClass for the given |
| * elementType. May be <code>null</code> if the elementType is |
| * not related to emf. |
| * @return the i observable value |
| */ |
| public static IObservableValue observeValue(Realm realm, EObject yElement, |
| String attributePath, Class<?> elementType, String emfNsURI) { |
| |
| if (elementType != null && EObject.class.isAssignableFrom(elementType)) { |
| EPackage ePkg = EPackage.Registry.INSTANCE.getEPackage(emfNsURI); |
| if (ePkg == null) { |
| throw new IllegalArgumentException(emfNsURI |
| + " is not a valid EPackage!"); |
| } |
| |
| EClass eClass = (EClass) ePkg.getEClassifier(elementType |
| .getSimpleName()); |
| if (eClass == null) { |
| throw new IllegalArgumentException(elementType.getSimpleName() |
| + " is not contained in the EPackage for nsURI " |
| + emfNsURI); |
| } |
| |
| FeaturePath path = getFeaturePath(attributePath, yElement.eClass(), |
| eClass); |
| return observeValue(realm, yElement, path); |
| } else { |
| return doObserveValue(yElement, attributePath, elementType); |
| } |
| |
| } |
| |
| /** |
| * See {@link #observeValue(Realm, EObject, String, Class, String)}. |
| * |
| * @param yElement |
| * the y element |
| * @param attributePath |
| * the attribute path |
| * @param elementType |
| * the element type |
| * @return the i observable value |
| */ |
| protected static IObservableValue doObserveValue(EObject yElement, |
| String attributePath, Class<?> elementType) { |
| if (yElement == null) { |
| throw new IllegalArgumentException( |
| "ECView model element must not be null!"); |
| } |
| |
| if (attributePath == null || attributePath.equals("")) { |
| throw new IllegalArgumentException( |
| "Attribute path must not be empty!"); |
| } |
| |
| int separatorIndex = attributePath.indexOf("."); |
| String subPath = attributePath.substring(separatorIndex + 1); |
| |
| EClass eClass = yElement.eClass(); |
| String[] properties = attributePath.split("\\."); |
| EStructuralFeature feature = eClass |
| .getEStructuralFeature(properties[0]); |
| if (feature == null) { |
| throw new IllegalStateException(String.format( |
| "%s is not a valid feature for %s!", properties[0], |
| eClass.getName())); |
| } |
| |
| if (elementType != null && EObject.class.isAssignableFrom(elementType)) { |
| throw new IllegalStateException( |
| "Please use observeValue(EObject, FeaturePath)"); |
| } |
| |
| if (properties.length == 1) { |
| return EMFProperties.value(feature).observe(yElement); |
| } else if (hasPropertyChangeSupport(elementType)) { |
| IObservableValue masterObservable = EMFProperties.value(feature) |
| .observe(yElement); |
| return BeanProperties.value(elementType, subPath).observeDetail( |
| masterObservable); |
| } else { |
| IObservableValue masterObservable = EMFProperties.value(feature) |
| .observe(yElement); |
| return PojoProperties.value(elementType, subPath).observeDetail( |
| masterObservable); |
| } |
| } |
| |
| /** |
| * Returns an observable value tracking the attribute path. The difference |
| * to {@link #observeValue(Realm, EObject, FeaturePath)} is, that the object |
| * located in the given yElement is an EObject. |
| * |
| * @param yElement |
| * - The ECView model element |
| * @param path |
| * - The feature path being tracked |
| * @return the i observable value |
| */ |
| public static IObservableValue observeValue(EObject yElement, |
| FeaturePath path) { |
| return observeValue(Realm.getDefault(), yElement, path); |
| } |
| |
| /** |
| * Returns an observable value tracking the attribute path. The difference |
| * to {@link #observeValue(EObject, String, Class, String)} is, that the object |
| * located in the given yElement is an EObject. |
| * |
| * @param realm |
| * - The realm. |
| * @param yElement |
| * - The ECView model element |
| * @param path |
| * - The feature path being tracked |
| * @return the i observable value |
| */ |
| public static IObservableValue observeValue(Realm realm, EObject yElement, |
| FeaturePath path) { |
| if (yElement == null) { |
| throw new IllegalArgumentException( |
| "ECView model element must not be null!"); |
| } |
| |
| if (path == null || path.getFeaturePath().length == 0) { |
| throw new IllegalArgumentException( |
| "Feature path must not be empty!"); |
| } |
| |
| return EMFProperties.value(path).observe(yElement); |
| } |
| |
| /** |
| * Returns a feature path required for EMF databinding. |
| * |
| * @param features |
| * - The features |
| * @return the feature path |
| */ |
| public static FeaturePath getFeaturePath(List<EStructuralFeature> features) { |
| return FeaturePath.fromList(features |
| .toArray(new EStructuralFeature[features.size()])); |
| } |
| |
| /** |
| * Returns a feature path required for EMF databinding. |
| * |
| * @param path |
| * - A dot notated path |
| * @param yElementClass |
| * - the EClass of the yElement. See |
| * {@link #observeValue(EObject, FeaturePath)} |
| * @param yElementTypeClass |
| * - the EClass of the object contained in the yElement for the |
| * first path-segment. |
| * @return the feature path |
| */ |
| public static FeaturePath getFeaturePath(String path, EClass yElementClass, |
| EClass yElementTypeClass) { |
| return getFeaturePath(path.split("\\."), yElementClass, |
| yElementTypeClass); |
| } |
| |
| /** |
| * Returns a feature path required for EMF databinding. |
| * |
| * @param path |
| * the path |
| * @param yElementClass |
| * - the EClass of the yElement. See |
| * {@link #observeValue(EObject, FeaturePath)} |
| * @param yElementTypeClass |
| * - the EClass of the object contained in the yElement for the |
| * first path-segment. |
| * @return the feature path |
| */ |
| public static FeaturePath getFeaturePath(String[] path, |
| EClass yElementClass, EClass yElementTypeClass) { |
| |
| if (path.length == 0) { |
| throw new IllegalArgumentException("Not a valid path"); |
| } |
| |
| // handle the first feature |
| List<EStructuralFeature> features = new ArrayList<EStructuralFeature>(); |
| EStructuralFeature firstFeature = yElementClass |
| .getEStructuralFeature(path[0]); |
| features.add(firstFeature); |
| |
| // handle all other |
| EClass currentClass = yElementTypeClass; |
| for (int i = 1; i < path.length && currentClass != null; i++) { |
| String string = path[i]; |
| |
| EStructuralFeature feature = currentClass |
| .getEStructuralFeature(string); |
| features.add(feature); |
| |
| if (feature instanceof EReference) { |
| EReference eReference = (EReference) feature; |
| currentClass = eReference.getEReferenceType(); |
| } else { |
| currentClass = null; |
| } |
| } |
| |
| return FeaturePath.fromList(features |
| .toArray(new EStructuralFeature[features.size()])); |
| |
| } |
| |
| /** |
| * Returns an observable list tracking the attribute path. |
| * |
| * @param yElement |
| * - The ECView model element |
| * @param attributePath |
| * - The attribute path being tracked |
| * @param elementType |
| * - The type that is contained in the yElement. For instance if |
| * YList#selection is of type Item.class, then this property is |
| * Item.class. |
| * @return the i observable list |
| */ |
| public static IObservableList observeList(EObject yElement, |
| String attributePath, Class<?> elementType) { |
| return observeList(Realm.getDefault(), yElement, attributePath, |
| elementType); |
| } |
| |
| /** |
| * Returns an observable list tracking the attribute path. |
| * |
| * @param realm |
| * - The realm |
| * @param yElement |
| * - The ECView model element |
| * @param attributePath |
| * - The attribute path being tracked |
| * @param elementType |
| * - The type that is contained in the yElement. For instance if |
| * YList#selection is of type Item.class, then this property is |
| * Item.class. |
| * @return the i observable list |
| */ |
| public static IObservableList observeList(Realm realm, EObject yElement, |
| String attributePath, Class<?> elementType) { |
| if (yElement == null) { |
| throw new IllegalArgumentException( |
| "ECView model element must not be null!"); |
| } |
| |
| if (attributePath == null || attributePath.equals("")) { |
| throw new IllegalArgumentException( |
| "Attribute path must not be empty!"); |
| } |
| |
| int separatorIndex = attributePath.indexOf("."); |
| String subPath = attributePath.substring(separatorIndex + 1); |
| |
| EClass eClass = yElement.eClass(); |
| String[] properties = attributePath.split("\\."); |
| EStructuralFeature feature = eClass |
| .getEStructuralFeature(properties[0]); |
| if (feature == null) { |
| throw new IllegalStateException(String.format( |
| "%s is not a valid feature for %s!", properties[0], |
| eClass.getName())); |
| } |
| |
| if (EObject.class.isAssignableFrom(elementType)) { |
| throw new IllegalStateException("Please use observeEmfValue"); |
| } |
| |
| if (properties.length == 1) { |
| return EMFProperties.list(feature).observe(yElement); |
| } else if (hasPropertyChangeSupport(elementType)) { |
| IObservableValue masterObservable = EMFProperties.value(feature) |
| .observe(yElement); |
| return BeanProperties.list(elementType, subPath).observeDetail( |
| masterObservable); |
| } else { |
| IObservableValue masterObservable = EMFProperties.value(feature) |
| .observe(yElement); |
| return PojoProperties.list(elementType, subPath).observeDetail( |
| masterObservable); |
| } |
| } |
| |
| /** |
| * Returns an observable list tracking the attribute path. The difference to |
| * {@link #observeList(EObject, String, Class)} is, that the object located |
| * in the given yElement is an EObject. |
| * |
| * @param yElement |
| * - The ECView model element |
| * @param path |
| * - The feature path being tracked |
| * @return the i observable list |
| */ |
| public static IObservableList observeList(EObject yElement, FeaturePath path) { |
| return observeList(Realm.getDefault(), yElement, path); |
| } |
| |
| /** |
| * Returns an observable list tracking the attribute path. The difference to |
| * {@link #observeList(EObject, String, Class)} is, that the object located |
| * in the given yElement is an EObject. |
| * |
| * @param realm |
| * - The realm. |
| * @param yElement |
| * - The ECView model element |
| * @param path |
| * - The feature path being tracked |
| * @return the i observable list |
| */ |
| public static IObservableList observeList(Realm realm, EObject yElement, |
| FeaturePath path) { |
| if (yElement == null) { |
| throw new IllegalArgumentException( |
| "ECView model element must not be null!"); |
| } |
| |
| if (path == null || path.getFeaturePath().length == 0) { |
| throw new IllegalArgumentException( |
| "Feature path must not be empty!"); |
| } |
| |
| return EMFProperties.list(path).observe(yElement); |
| } |
| |
| /** |
| * Returns true, if the bean has property change support. |
| * |
| * @param valueType |
| * the value type |
| * @return true, if successful |
| */ |
| public static boolean hasPropertyChangeSupport(Class<?> valueType) { |
| @SuppressWarnings("unused") |
| Method method = null; |
| try { |
| try { |
| method = valueType.getMethod("addPropertyChangeListener", |
| new Class[] { String.class, |
| PropertyChangeListener.class }); |
| return true; |
| } catch (NoSuchMethodException e) { |
| method = valueType.getMethod("addPropertyChangeListener", |
| new Class[] { PropertyChangeListener.class }); |
| return true; |
| } |
| } catch (SecurityException e) { |
| } catch (NoSuchMethodException e) { |
| } |
| return false; |
| } |
| |
| /** |
| * Creates an attribute path that may be used for bindings in that class. |
| * |
| * @param eFeature |
| * the e feature |
| * @param path |
| * the path |
| * @return the attribute path |
| */ |
| public static String getAttributePath(EStructuralFeature eFeature, |
| String path) { |
| if (path == null || path.equals("")) { |
| return eFeature.getName(); |
| } else { |
| return eFeature.getName() + "." + path; |
| } |
| } |
| |
| } |