| /******************************************************************************* |
| * Copyright (c) 2011-2018 EclipseSource Muenchen GmbH and others. |
| * |
| * 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: |
| * Lucas Koehler - initial API and implementation |
| * Eugen Neufeld - changed interface to EMFFormsDatabindingEMF |
| * Lucas Koehler - Added support for DMR Segments |
| ******************************************************************************/ |
| package org.eclipse.emfforms.internal.core.services.databinding; |
| |
| import java.util.LinkedHashSet; |
| import java.util.Optional; |
| import java.util.Set; |
| |
| 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.core.databinding.property.list.IListProperty; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.databinding.IEMFListProperty; |
| import org.eclipse.emf.databinding.IEMFObservable; |
| import org.eclipse.emf.databinding.IEMFValueProperty; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.EStructuralFeature.Setting; |
| import org.eclipse.emf.ecp.common.spi.asserts.Assert; |
| import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference; |
| import org.eclipse.emf.ecp.view.spi.model.VDomainModelReferenceSegment; |
| import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain; |
| import org.eclipse.emf.edit.domain.EditingDomain; |
| import org.eclipse.emfforms.common.RankingHelper; |
| import org.eclipse.emfforms.spi.core.services.databinding.DatabindingFailedException; |
| import org.eclipse.emfforms.spi.core.services.databinding.DomainModelReferenceConverter; |
| import org.eclipse.emfforms.spi.core.services.databinding.EMFFormsDatabinding; |
| import org.eclipse.emfforms.spi.core.services.databinding.emf.DomainModelReferenceConverterEMF; |
| import org.eclipse.emfforms.spi.core.services.databinding.emf.DomainModelReferenceSegmentConverterEMF; |
| import org.eclipse.emfforms.spi.core.services.databinding.emf.EMFFormsDatabindingEMF; |
| import org.eclipse.emfforms.spi.core.services.databinding.emf.EMFFormsSegmentResolver; |
| import org.eclipse.emfforms.spi.core.services.databinding.emf.SegmentConverterListResultEMF; |
| import org.eclipse.emfforms.spi.core.services.databinding.emf.SegmentConverterValueResultEMF; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.component.annotations.Reference; |
| import org.osgi.service.component.annotations.ReferenceCardinality; |
| import org.osgi.service.component.annotations.ReferencePolicy; |
| |
| /** |
| * EMF implementation of {@link EMFFormsDatabindingEMF}. |
| * |
| * @author Lucas Koehler |
| * |
| */ |
| @Component(name = "databindingService", service = { EMFFormsDatabinding.class, EMFFormsDatabindingEMF.class, |
| EMFFormsSegmentResolver.class }) |
| public class EMFFormsDatabindingImpl implements EMFFormsDatabindingEMF, EMFFormsSegmentResolver { |
| |
| private static final RankingHelper<DomainModelReferenceConverterEMF> DMR_RANKING_HELPER = // |
| new RankingHelper<DomainModelReferenceConverterEMF>( |
| DomainModelReferenceConverter.class, |
| DomainModelReferenceConverter.NOT_APPLICABLE, |
| DomainModelReferenceConverter.NOT_APPLICABLE); |
| |
| private static final RankingHelper<DomainModelReferenceSegmentConverterEMF> SEGMENTS_RANKING_HELPER = // |
| new RankingHelper<>( |
| DomainModelReferenceSegmentConverterEMF.class, |
| DomainModelReferenceSegmentConverterEMF.NOT_APPLICABLE, |
| DomainModelReferenceSegmentConverterEMF.NOT_APPLICABLE); |
| |
| private final Set<DomainModelReferenceConverterEMF> referenceConverters = new LinkedHashSet<>(); |
| private final Set<DomainModelReferenceSegmentConverterEMF> segmentConverters = new LinkedHashSet<>(); |
| |
| /** |
| * Adds the given {@link DomainModelReferenceSegmentConverterEMF} to the Set of segment converters. |
| * |
| * @param converter The {@link DomainModelReferenceSegmentConverterEMF} to add |
| */ |
| @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) |
| protected void addDomainModelReferenceSegmentConverter(DomainModelReferenceSegmentConverterEMF converter) { |
| segmentConverters.add(converter); |
| } |
| |
| /** |
| * Removes the given {@link DomainModelReferenceSegmentConverterEMF} from the Set of segment converters. |
| * |
| * @param converter The {@link DomainModelReferenceSegmentConverterEMF} to remove |
| */ |
| protected void removeDomainModelReferenceSegmentConverter(DomainModelReferenceSegmentConverterEMF converter) { |
| segmentConverters.remove(converter); |
| } |
| |
| @Override |
| public IObservableValue getObservableValue(VDomainModelReference domainModelReference, EObject object) |
| throws DatabindingFailedException { |
| Assert.create(domainModelReference).notNull(); |
| Assert.create(object).notNull(); |
| |
| final IEMFValueProperty valueProperty = getValueProperty(domainModelReference, object); |
| final Realm realm = Realm.getDefault(); |
| if (realm != null) { |
| return valueProperty.observe(object); |
| } |
| final DefaultRealm dr = new DefaultRealm(); |
| final IObservableValue observableValue = valueProperty.observe(object); |
| dr.dispose(); |
| return observableValue; |
| } |
| |
| @Override |
| public IEMFValueProperty getValueProperty(VDomainModelReference domainModelReference, EObject object) |
| throws DatabindingFailedException { |
| Assert.create(domainModelReference).notNull(); |
| |
| final EList<VDomainModelReferenceSegment> segments = domainModelReference.getSegments(); |
| if (segments.isEmpty()) { |
| // No segments => Fall back to legacy dmr resolving |
| final DomainModelReferenceConverterEMF bestConverter = getBestDomainModelReferenceConverter( |
| domainModelReference); |
| return bestConverter.convertToValueProperty(domainModelReference, object); |
| } |
| |
| Assert.create(object).notNull(); |
| final EditingDomain editingDomain = getEditingDomain(object); |
| return internalGetValueProperty(domainModelReference, object.eClass(), editingDomain); |
| } |
| |
| @Override |
| public IEMFValueProperty getValueProperty(VDomainModelReference domainModelReference, EClass rootEClass) |
| throws DatabindingFailedException { |
| return getValueProperty(domainModelReference, rootEClass, null); |
| } |
| |
| @Override |
| public IEMFValueProperty getValueProperty(VDomainModelReference domainModelReference, EClass rootEClass, |
| EditingDomain editingDomain) throws DatabindingFailedException { |
| Assert.create(domainModelReference).notNull(); |
| |
| final EList<VDomainModelReferenceSegment> segments = domainModelReference.getSegments(); |
| if (segments.isEmpty()) { |
| // No segments => Fall back to legacy dmr resolving |
| final DomainModelReferenceConverterEMF bestConverter = getBestDomainModelReferenceConverter( |
| domainModelReference); |
| return bestConverter.convertToValueProperty(domainModelReference, rootEClass, editingDomain); |
| } |
| |
| Assert.create(rootEClass).notNull(); |
| return internalGetValueProperty(domainModelReference, rootEClass, editingDomain); |
| } |
| |
| /** |
| * Actual calculation of a value property using {@link VDomainModelReferenceSegment segments}. |
| * |
| * @param domainModelReference The domain model reference pointing to the desired value |
| * @param rootEClass The root EClass of the rendered form |
| * @param editingDomain The {@link EditingDomain} of the resulting value property, may be null |
| * @return The resulting {@link IEMFValueProperty} |
| * @throws DatabindingFailedException |
| */ |
| private IEMFValueProperty internalGetValueProperty(VDomainModelReference domainModelReference, EClass rootEClass, |
| EditingDomain editingDomain) throws DatabindingFailedException { |
| |
| final EList<VDomainModelReferenceSegment> segments = domainModelReference.getSegments(); |
| |
| // Get value property for the (always present) first segment |
| final DomainModelReferenceSegmentConverterEMF firstConverter = getBestDomainModelReferenceSegmentConverter( |
| segments.get(0)); |
| SegmentConverterValueResultEMF converterResult = firstConverter.convertToValueProperty(segments.get(0), |
| rootEClass, editingDomain); |
| IEMFValueProperty resultProperty = converterResult.getValueProperty(); |
| |
| // Iterate over all remaining segments and get the value properties for their corresponding EClasses. |
| for (int i = 1; i < segments.size(); i++) { |
| final EClass nextEClass = unpackNextEClass(converterResult.getNextEClass(), domainModelReference, |
| segments.get(i)); |
| final VDomainModelReferenceSegment segment = segments.get(i); |
| |
| final DomainModelReferenceSegmentConverterEMF bestConverter = getBestDomainModelReferenceSegmentConverter( |
| segment); |
| converterResult = bestConverter.convertToValueProperty(segment, |
| nextEClass, editingDomain); |
| final IEMFValueProperty nextProperty = converterResult.getValueProperty(); |
| // Chain the properties together |
| resultProperty = resultProperty.value(nextProperty); |
| } |
| |
| return resultProperty; |
| } |
| |
| /** |
| * Adds the given {@link DomainModelReferenceConverterEMF} to the Set of reference converters. |
| * |
| * @param converter The {@link DomainModelReferenceConverterEMF} to add |
| */ |
| @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) |
| protected void addDomainModelReferenceConverter(DomainModelReferenceConverterEMF converter) { |
| referenceConverters.add(converter); |
| } |
| |
| /** |
| * Removes the given {@link DomainModelReferenceConverterEMF} to the Set of reference converters. |
| * |
| * @param converter The {@link DomainModelReferenceConverterEMF} to remove |
| */ |
| protected void removeDomainModelReferenceConverter(DomainModelReferenceConverterEMF converter) { |
| referenceConverters.remove(converter); |
| } |
| |
| @Override |
| public IObservableList getObservableList(VDomainModelReference domainModelReference, EObject object) |
| throws DatabindingFailedException { |
| Assert.create(domainModelReference).notNull(); |
| Assert.create(object).notNull(); |
| |
| final IListProperty listProperty = getListProperty(domainModelReference, object); |
| final Realm realm = Realm.getDefault(); |
| if (realm != null) { |
| return listProperty.observe(object); |
| } |
| final DefaultRealm dr = new DefaultRealm(); |
| final IObservableList observableList = listProperty.observe(object); |
| dr.dispose(); |
| return observableList; |
| } |
| |
| @Override |
| public IEMFListProperty getListProperty(VDomainModelReference domainModelReference, EObject object) |
| throws DatabindingFailedException { |
| Assert.create(domainModelReference).notNull(); |
| |
| final EList<VDomainModelReferenceSegment> segments = domainModelReference.getSegments(); |
| if (segments.isEmpty()) { |
| // No segments => Fall back to legacy dmr resolving |
| final DomainModelReferenceConverterEMF bestConverter = getBestDomainModelReferenceConverter( |
| domainModelReference); |
| return bestConverter.convertToListProperty(domainModelReference, object); |
| } |
| |
| Assert.create(object).notNull(); |
| final EditingDomain editingDomain = getEditingDomain(object); |
| |
| // If there is only one segment, get its list property. Otherwise, get its value property |
| final DomainModelReferenceSegmentConverterEMF firstConverter = getBestDomainModelReferenceSegmentConverter( |
| segments.get(0)); |
| if (segments.size() == 1) { |
| // If there is only one segment, directly return its list property |
| return firstConverter.convertToListProperty(segments.get(0), object.eClass(), editingDomain) |
| .getListProperty(); |
| } |
| |
| final SegmentConverterValueResultEMF converterResult = firstConverter.convertToValueProperty(segments.get(0), |
| object.eClass(), editingDomain); |
| IEMFValueProperty valueProperty = converterResult.getValueProperty(); |
| |
| /* |
| * Iterate over all "middle" segments and get the value properties for their corresponding EClasses. |
| * Get the EClass by getting the target EClass of the EReference from the value property of the previously |
| * resolved segment. |
| */ |
| EClass nextEClass = unpackNextEClass(converterResult.getNextEClass(), domainModelReference, segments.get(0)); |
| for (int i = 1; i < segments.size() - 1; i++) { |
| final VDomainModelReferenceSegment segment = segments.get(i); |
| |
| final DomainModelReferenceSegmentConverterEMF bestConverter = getBestDomainModelReferenceSegmentConverter( |
| segment); |
| final SegmentConverterValueResultEMF nextConverterResult = bestConverter.convertToValueProperty(segment, |
| nextEClass, editingDomain); |
| final IEMFValueProperty nextProperty = nextConverterResult.getValueProperty(); |
| |
| nextEClass = unpackNextEClass(nextConverterResult.getNextEClass(), domainModelReference, segment); |
| // Chain the properties together |
| valueProperty = valueProperty.value(nextProperty); |
| } |
| |
| // Get the list property for the last segment |
| final int lastIndex = segments.size() - 1; |
| final DomainModelReferenceSegmentConverterEMF lastConverter = getBestDomainModelReferenceSegmentConverter( |
| segments.get(lastIndex)); |
| final SegmentConverterListResultEMF converterListResult = lastConverter.convertToListProperty( |
| segments.get(lastIndex), nextEClass, |
| editingDomain); |
| |
| return valueProperty.list(converterListResult.getListProperty()); |
| } |
| |
| /** |
| * Returns the most suitable {@link DomainModelReferenceConverter}, that is registered to this |
| * {@link EMFFormsDatabindingImpl}, for the given {@link VDomainModelReference}. |
| * |
| * @param domainModelReference The {@link VDomainModelReference} for which a {@link DomainModelReferenceConverter} |
| * is needed |
| * @return The most suitable {@link DomainModelReferenceConverter} |
| * @throws DatabindingFailedException If no applicable DMR Converter was found |
| */ |
| private DomainModelReferenceConverterEMF getBestDomainModelReferenceConverter( |
| final VDomainModelReference domainModelReference) throws DatabindingFailedException { |
| if (domainModelReference == null) { |
| throw new IllegalArgumentException("The given VDomainModelReference must not be null."); //$NON-NLS-1$ |
| } |
| |
| final DomainModelReferenceConverterEMF bestConverter = DMR_RANKING_HELPER.getHighestRankingElement( |
| referenceConverters, converter -> converter.isApplicable(domainModelReference)); |
| if (bestConverter == null) { |
| throw new DatabindingFailedException("No applicable DomainModelReferenceConverter could be found."); //$NON-NLS-1$ |
| } |
| return bestConverter; |
| } |
| |
| @Override |
| public EStructuralFeature extractFeature(IObservableValue observableValue) throws DatabindingFailedException { |
| if (IEMFObservable.class.isInstance(observableValue)) { |
| return IEMFObservable.class.cast(observableValue).getStructuralFeature(); |
| } |
| throw new DatabindingFailedException( |
| String.format("The IObservableValue class %1$s is not supported!", observableValue.getClass().getName())); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public EStructuralFeature extractFeature(IObservableList observableList) throws DatabindingFailedException { |
| if (IEMFObservable.class.isInstance(observableList)) { |
| return IEMFObservable.class.cast(observableList).getStructuralFeature(); |
| } |
| throw new DatabindingFailedException( |
| String.format("The IObservableList class %1$s is not supported!", observableList.getClass().getName())); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public EObject extractObserved(IObservableValue observableValue) throws DatabindingFailedException { |
| if (IEMFObservable.class.isInstance(observableValue)) { |
| return (EObject) IEMFObservable.class.cast(observableValue).getObserved(); |
| } |
| throw new DatabindingFailedException( |
| String.format("The IObservableValue class %1$s is not supported!", observableValue.getClass().getName())); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public EObject extractObserved(IObservableList observableList) throws DatabindingFailedException { |
| if (IEMFObservable.class.isInstance(observableList)) { |
| return (EObject) IEMFObservable.class.cast(observableList).getObserved(); |
| } |
| throw new DatabindingFailedException( |
| String.format("The IObservableList class %1$s is not supported!", observableList.getClass().getName())); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public Setting getSetting(VDomainModelReference domainModelReference, EObject object) |
| throws DatabindingFailedException { |
| Assert.create(domainModelReference).notNull(); |
| Assert.create(object).notNull(); |
| |
| final EList<VDomainModelReferenceSegment> segments = domainModelReference.getSegments(); |
| if (segments.isEmpty()) { |
| // No segments => Fall back to legacy dmr resolving |
| final DomainModelReferenceConverterEMF bestConverter = getBestDomainModelReferenceConverter( |
| domainModelReference); |
| final Realm realm = Realm.getDefault(); |
| if (realm != null) { |
| return bestConverter.getSetting(domainModelReference, object); |
| } |
| final DefaultRealm dr = new DefaultRealm(); |
| final Setting setting = bestConverter.getSetting(domainModelReference, object); |
| dr.dispose(); |
| return setting; |
| } |
| |
| Setting setting = resolveSegment(segments.get(0), object); |
| |
| /* |
| * If present, iterate over the remaining segments. For every iteration step, use the resolved EObject of the |
| * previously resolved Setting in order to resolve the next Setting. |
| */ |
| for (int i = 1; i < segments.size(); i++) { |
| final VDomainModelReferenceSegment segment = segments.get(i); |
| final Object nextObject = setting.get(true); |
| |
| if (!EObject.class.isInstance(nextObject)) { |
| throw new DatabindingFailedException( |
| String.format( |
| "The Setting could not be fully resolved because an intermediate Object was no EObject or was null. " //$NON-NLS-1$ |
| + "The DMR was %1$s. The last resolved segment was %2$s. The root EObject was %3$s.", //$NON-NLS-1$ |
| domainModelReference, segments.get(i - 1), object)); |
| } |
| |
| final EObject nextEObject = (EObject) nextObject; |
| setting = resolveSegment(segment, nextEObject); |
| } |
| |
| return setting; |
| } |
| |
| /** |
| * Returns the most suitable {@link DomainModelReferenceSegmentConverterEMF}, that is registered to this |
| * {@link EMFFormsDatabindingImpl}, for the given {@link VDomainModelReferenceSegment}. |
| * |
| * @param segment The {@link VDomainModelReferenceSegment} for which a |
| * {@link DomainModelReferenceSegmentConverterEMF} |
| * is needed |
| * @return The most suitable {@link DomainModelReferenceSegmentConverterEMF}, does not return <code>null</code> |
| * @throws DatabindingFailedException if no suitable segment converter could be found |
| */ |
| private DomainModelReferenceSegmentConverterEMF getBestDomainModelReferenceSegmentConverter( |
| final VDomainModelReferenceSegment segment) throws DatabindingFailedException { |
| |
| final DomainModelReferenceSegmentConverterEMF bestConverter = SEGMENTS_RANKING_HELPER.getHighestRankingElement( |
| segmentConverters, converter -> converter.isApplicable(segment)); |
| |
| if (bestConverter == null) { |
| throw new DatabindingFailedException(String |
| .format("No suitable DomainModelReferenceSegmentConverter could be found for segment %1$s", segment)); //$NON-NLS-1$ |
| } |
| return bestConverter; |
| |
| } |
| |
| /** |
| * Unpacks the given {@link EClass} Optional and throws an exception if it is not present. |
| * |
| * @param nextEClass the {@link EClass} to check |
| * @param domainModelReference only needed for exception description |
| * @param segment only needed for exception description |
| * @return the unpacked {@link EClass} |
| * @throws DatabindingFailedException if the next EClass is <code>null</code> |
| */ |
| private EClass unpackNextEClass(final Optional<EClass> nextEClass, VDomainModelReference domainModelReference, |
| final VDomainModelReferenceSegment segment) throws DatabindingFailedException { |
| return nextEClass.orElseThrow(() -> new DatabindingFailedException(String.format( |
| "The Segment [%1$s] could not be resolved because this segment's root EClass" //$NON-NLS-1$ |
| + " could not be resolved from the preceding segment. The DMR is %2$s.", //$NON-NLS-1$ |
| segment, domainModelReference))); |
| } |
| |
| private EditingDomain getEditingDomain(EObject object) throws DatabindingFailedException { |
| return AdapterFactoryEditingDomain.getEditingDomainFor(object); |
| } |
| |
| @Override |
| public Setting resolveSegment(VDomainModelReferenceSegment segment, EObject domainObject) |
| throws DatabindingFailedException { |
| final DomainModelReferenceSegmentConverterEMF bestConverter = getBestDomainModelReferenceSegmentConverter( |
| segment); |
| return bestConverter.getSetting(segment, domainObject); |
| } |
| } |