Skip to content

Content of file EMFFormsDatabindingImpl.java

/*******************************************************************************
 * 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); } }