Bug 553224 - [ViewModel] DMR resolution error messages

Add as much context about DMR resolution problems as is available to
the reference converter.  Enhance existing JUnit tests to verify
details of exceptions; complete JUnit coverage of failure conditions.

Change-Id: I5fd2deb2422dd940d28a492e6037e13ac091378b
Signed-off-by: Christian W. Damus <give.a.damus@gmail.com>
diff --git a/bundles/org.eclipse.emfforms.core.services.databinding.featurepath/src/org/eclipse/emfforms/core/services/databinding/featurepath/FeaturePathDomainModelReferenceConverter.java b/bundles/org.eclipse.emfforms.core.services.databinding.featurepath/src/org/eclipse/emfforms/core/services/databinding/featurepath/FeaturePathDomainModelReferenceConverter.java
index 78d2d5d..39cb35f 100644
--- a/bundles/org.eclipse.emfforms.core.services.databinding.featurepath/src/org/eclipse/emfforms/core/services/databinding/featurepath/FeaturePathDomainModelReferenceConverter.java
+++ b/bundles/org.eclipse.emfforms.core.services.databinding.featurepath/src/org/eclipse/emfforms/core/services/databinding/featurepath/FeaturePathDomainModelReferenceConverter.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2011-2016 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2011-2019 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
@@ -10,20 +10,26 @@
  *
  * Contributors:
  * Lucas Koehler - initial API and implementation
+ * Christian W. Damus - bug 553224
  ******************************************************************************/
 package org.eclipse.emfforms.core.services.databinding.featurepath;
 
 import java.util.List;
 
+import org.eclipse.emf.common.util.URI;
 import org.eclipse.emf.databinding.IEMFListProperty;
 import org.eclipse.emf.databinding.IEMFValueProperty;
 import org.eclipse.emf.databinding.edit.EMFEditProperties;
 import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.ENamedElement;
 import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EPackage;
 import org.eclipse.emf.ecore.EReference;
 import org.eclipse.emf.ecore.EStructuralFeature;
 import org.eclipse.emf.ecore.EStructuralFeature.Setting;
 import org.eclipse.emf.ecore.InternalEObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.util.EcoreUtil;
 import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference;
 import org.eclipse.emf.ecp.view.spi.model.VFeaturePathDomainModelReference;
 import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
@@ -58,7 +64,8 @@
 		}
 		if (!VFeaturePathDomainModelReference.class.isInstance(domainModelReference)) {
 			throw new IllegalArgumentException(
-				"DomainModelReference must be an instance of VFeaturePathDomainModelReference."); //$NON-NLS-1$
+				String.format("DomainModelReference %1$s is not an instance of VFeaturePathDomainModelReference.", //$NON-NLS-1$
+					identify(domainModelReference)));
 		}
 
 		final VFeaturePathDomainModelReference featurePathReference = (VFeaturePathDomainModelReference) domainModelReference;
@@ -68,7 +75,15 @@
 		}
 		if (featurePathReference.getDomainModelEFeature().eIsProxy()) {
 			throw new DatabindingFailedException(
-				"The field domainModelEFeature of the given VFeaturePathDomainModelReference must not be a proxy."); //$NON-NLS-1$
+				String.format("The domainModelEFeature %1$s of the given DMR %2$s is a proxy.", //$NON-NLS-1$
+					identify(featurePathReference.getDomainModelEFeature()), identify(featurePathReference)));
+		}
+		for (final EReference path : featurePathReference.getDomainModelEReferencePath()) {
+			if (path.eIsProxy()) {
+				throw new DatabindingFailedException(
+					String.format("The path reference %1$s of the given DMR %2$s is a proxy.", //$NON-NLS-1$
+						identify(path), identify(featurePathReference)));
+			}
 		}
 		return featurePathReference;
 	}
@@ -93,16 +108,16 @@
 
 		if (referencePath.get(0).isMany()) {
 			throw new DatabindingFailedException(String.format(
-				"The path is not fully resolved. The reference being resolved is not a single reference [%1$s]. The DMR is %2$s.", //$NON-NLS-1$
-				referencePath.get(0), domainModelReference));
+				"The path is not fully resolved. The reference being resolved is not a single reference: %1$s. The DMR is %2$s.", //$NON-NLS-1$
+				identify(referencePath.get(0)), identify(domainModelReference)));
 		}
 		IEMFValueProperty emfValueProperty = EMFEditProperties.value(editingDomain, referencePath.get(0));
 		for (int i = 1; i < referencePath.size(); i++) {
 			final EReference eReference = referencePath.get(i);
 			if (eReference.isMany()) {
 				throw new DatabindingFailedException(String.format(
-					"The path is not fully resolved. The reference being resolved is not a single reference [%1$s]. The DMR is %2$s.", //$NON-NLS-1$
-					eReference, domainModelReference));
+					"The path is not fully resolved. The reference being resolved is not a single reference: %1$s. The DMR is %2$s.", //$NON-NLS-1$
+					identify(eReference), identify(domainModelReference)));
 			}
 			emfValueProperty = emfValueProperty.value(eReference);
 		}
@@ -140,32 +155,105 @@
 		for (final EReference eReference : featurePathReference.getDomainModelEReferencePath()) {
 			if (eReference.isMany()) {
 				throw new DatabindingFailedException(String.format(
-					"The path is not fully resolved. The reference being resolved is not a single reference [%1$s]. The DMR is %2$s. Last resolved EObject is %3$s.", //$NON-NLS-1$
-					eReference, domainModelReference, currentObject));
+					"The path is not fully resolved. The reference being resolved is not a single reference: %1$s. The DMR is %2$s. Last resolved EObject is %3$s.", //$NON-NLS-1$
+					identify(eReference), identify(domainModelReference), identify(currentObject)));
 			}
 			if (currentObject.eClass().getFeatureID(eReference) == -1) {
 				throw new DatabindingFailedException(String.format(
-					"Given EClass has no such feature. The DMR is %1$s. Last resolved EObject is %2$s. Reference being resolved is %3$s.", //$NON-NLS-1$
-					domainModelReference, currentObject, eReference));
+					"Given EClass %1$s has no such feature %2$s. The DMR is %3$s. Last resolved EObject is %4$s.", //$NON-NLS-1$
+					identify(currentObject.eClass()), identify(eReference),
+					identify(domainModelReference), identify(currentObject)));
 			}
 			final EObject nextObject = (EObject) currentObject.eGet(eReference);
 			if (nextObject == null) {
 				throw new DatabindingFailedException(String.format(
 					"The path is not fully resolved. The DMR is %1$s. Last resolved EObject is %2$s. Reference being resolved is %3$s.", //$NON-NLS-1$
-					domainModelReference, currentObject, eReference));
+					identify(domainModelReference), identify(currentObject), identify(eReference)));
 			}
 			currentObject = nextObject;
 		}
 		final EStructuralFeature structuralFeature = featurePathReference.getDomainModelEFeature();
 		if (structuralFeature.getEType() == null) {
 			throw new DatabindingFailedException(
-				String.format("The eType of the feature %1$s is null.", structuralFeature.getName())); //$NON-NLS-1$
+				String.format("The eType of the feature %1$s is null in DMR %2$s.", identify(structuralFeature), //$NON-NLS-1$
+					identify(domainModelReference)));
 		}
 		if (currentObject.eClass().getEAllStructuralFeatures().contains(structuralFeature)) {
 			return InternalEObject.class.cast(currentObject).eSetting(structuralFeature);
 		}
-		throw new DatabindingFailedException(String.format("The resolved Object %1$s doesn't contain the feature %2$s.", //$NON-NLS-1$
-			currentObject.eClass().getName(), structuralFeature.getName()));
+		throw new DatabindingFailedException(String.format("The resolved EObject %1$s doesn't have the feature %2$s.", //$NON-NLS-1$
+			identify(currentObject), identify(structuralFeature)));
+	}
+
+	private static String identify(EObject eObject) {
+		if (eObject == null) {
+			return "<null>"; //$NON-NLS-1$
+		}
+
+		final StringBuilder result = new StringBuilder();
+
+		// First, a trimmed variant of the to-string representation (omit Java package)
+		result.append(eObject.toString());
+		final int hashCodePos = result.indexOf("@"); //$NON-NLS-1$
+		if (hashCodePos > 0) {
+			final int lastDotPos = result.lastIndexOf(".", hashCodePos); //$NON-NLS-1$
+			if (lastDotPos > 0) {
+				result.delete(0, lastDotPos + 1);
+			}
+		}
+
+		final Resource resource = eObject.eResource();
+		if (resource != null) {
+			result.append('<');
+			result.append(EcoreUtil.getURI(eObject));
+			result.append('>');
+		}
+
+		return result.toString();
+	}
+
+	private static String identify(ENamedElement element) {
+		if (element == null) {
+			return "<null>"; //$NON-NLS-1$
+		}
+
+		final StringBuilder result = new StringBuilder();
+
+		if (element.eIsProxy()) {
+			// Infer as much as possible
+			final URI proxyURI = EcoreUtil.getURI(element);
+
+			// Get the qualified ENamedElement name
+			String fragment = proxyURI.fragment();
+			while (!fragment.isEmpty() && fragment.charAt(0) == '/') {
+				fragment = fragment.substring(1);
+			}
+			result.append(fragment.replace('/', '.'));
+
+			// Get the package namespace (or Ecore path)
+			result.append(" ("); //$NON-NLS-1$
+			result.append(proxyURI.trimFragment());
+			result.append(')');
+		} else {
+			result.append(element.getName());
+
+			for (EObject container = element.eContainer(); container instanceof ENamedElement; container = container
+				.eContainer()) {
+
+				final ENamedElement parent = (ENamedElement) container;
+
+				if (parent instanceof EPackage) {
+					result.append(" ("); //$NON-NLS-1$
+					result.append(((EPackage) container).getNsURI());
+					result.append(')');
+				} else {
+					result.insert(0, '.');
+					result.insert(0, parent.getName());
+				}
+			}
+		}
+
+		return result.toString();
 	}
 
 }
diff --git a/tests/org.eclipse.emfforms.core.services.databinding.featurepath.tests/All Integration Tests for core.services.databinding.featurepath.launch b/tests/org.eclipse.emfforms.core.services.databinding.featurepath.tests/All Integration Tests for core.services.databinding.featurepath.launch
index 3cc1a39..a1d816c 100644
--- a/tests/org.eclipse.emfforms.core.services.databinding.featurepath.tests/All Integration Tests for core.services.databinding.featurepath.launch
+++ b/tests/org.eclipse.emfforms.core.services.databinding.featurepath.tests/All Integration Tests for core.services.databinding.featurepath.launch
@@ -1,43 +1,44 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>

-<launchConfiguration type="org.eclipse.pde.ui.JunitLaunchConfig">

-<booleanAttribute key="append.args" value="true"/>

-<stringAttribute key="application" value="org.eclipse.pde.junit.runtime.coretestapplication"/>

-<booleanAttribute key="askclear" value="false"/>

-<booleanAttribute key="automaticAdd" value="false"/>

-<booleanAttribute key="automaticValidate" value="false"/>

-<stringAttribute key="bootstrap" value=""/>

-<stringAttribute key="checked" value="[NONE]"/>

-<booleanAttribute key="clearConfig" value="true"/>

-<booleanAttribute key="clearws" value="true"/>

-<booleanAttribute key="clearwslog" value="false"/>

-<stringAttribute key="configLocation" value="${workspace_loc}/.metadata/.plugins/org.eclipse.pde.core/pde-junit"/>

-<booleanAttribute key="default" value="false"/>

-<booleanAttribute key="includeOptional" value="false"/>

-<stringAttribute key="location" value="${workspace_loc}/../junit-workspace"/>

-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">

-<listEntry value="/org.eclipse.emfforms.core.services.databinding.featurepath.tests/src/org/eclipse/emfforms/core/services/databinding/featurepath/tests/AllIntegrationTests.java"/>

-</listAttribute>

-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">

-<listEntry value="1"/>

-</listAttribute>

-<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value=""/>

-<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>

-<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>

-<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>

-<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>

-<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.eclipse.emfforms.core.services.databinding.featurepath.tests.AllIntegrationTests"/>

-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog"/>

-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.eclipse.emfforms.core.services.databinding.featurepath.tests"/>

-<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>

-<stringAttribute key="pde.version" value="3.3"/>

-<stringAttribute key="product" value="org.eclipse.emf.cdo.server.product.tcp_h2"/>

-<booleanAttribute key="run_in_ui_thread" value="true"/>

-<stringAttribute key="selected_target_plugins" value="com.ibm.icu@default:default,javax.xml@default:default,org.eclipse.core.contenttype@default:default,org.eclipse.core.databinding.observable@default:default,org.eclipse.core.databinding.property@default:default,org.eclipse.core.databinding@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.runtime@default:true,org.eclipse.emf.common@default:default,org.eclipse.emf.databinding@default:default,org.eclipse.emf.ecore@default:default,org.eclipse.equinox.app@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.ds@1:true,org.eclipse.equinox.preferences@default:default,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.util@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi@-1:true,org.hamcrest.core@default:default,org.junit@default:default,org.mockito.mockito-all@default:default"/>

-<stringAttribute key="selected_workspace_plugins" value="org.eclipse.emf.ecp.view.model@default:default,org.eclipse.emfforms.common@default:default,org.eclipse.emfforms.core.services.databinding.featurepath.tests@default:false,org.eclipse.emfforms.core.services.databinding.featurepath@default:default,org.eclipse.emfforms.core.services.databinding.testmodel@default:default,org.eclipse.emfforms.core.services.databinding@default:default"/>

-<booleanAttribute key="show_selected_only" value="false"/>

-<booleanAttribute key="tracing" value="false"/>

-<booleanAttribute key="useCustomFeatures" value="false"/>

-<booleanAttribute key="useDefaultConfig" value="true"/>

-<booleanAttribute key="useDefaultConfigArea" value="false"/>

-<booleanAttribute key="useProduct" value="false"/>

-</launchConfiguration>

+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.pde.ui.JunitLaunchConfig">
+<booleanAttribute key="append.args" value="true"/>
+<stringAttribute key="application" value="org.eclipse.pde.junit.runtime.coretestapplication"/>
+<booleanAttribute key="askclear" value="false"/>
+<booleanAttribute key="automaticAdd" value="false"/>
+<booleanAttribute key="automaticValidate" value="false"/>
+<stringAttribute key="bootstrap" value=""/>
+<stringAttribute key="checked" value="[NONE]"/>
+<booleanAttribute key="clearConfig" value="true"/>
+<booleanAttribute key="clearws" value="true"/>
+<booleanAttribute key="clearwslog" value="false"/>
+<stringAttribute key="configLocation" value="${workspace_loc}/.metadata/.plugins/org.eclipse.pde.core/pde-junit"/>
+<booleanAttribute key="default" value="false"/>
+<booleanAttribute key="includeOptional" value="false"/>
+<stringAttribute key="location" value="${workspace_loc}/../junit-workspace"/>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/org.eclipse.emfforms.core.services.databinding.featurepath.tests/src/org/eclipse/emfforms/core/services/databinding/featurepath/tests/AllIntegrationTests.java"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="1"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value=""/>
+<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
+<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
+<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
+<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
+<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.eclipse.emfforms.core.services.databinding.featurepath.tests.AllIntegrationTests"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.eclipse.emfforms.core.services.databinding.featurepath.tests"/>
+<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
+<stringAttribute key="pde.version" value="3.3"/>
+<stringAttribute key="product" value="org.eclipse.emf.cdo.server.product.tcp_h2"/>
+<booleanAttribute key="run_in_ui_thread" value="true"/>
+<stringAttribute key="selected_target_plugins" value="com.ibm.icu@default:default,javax.inject@default:default,org.apache.felix.scr@1:true,org.eclipse.core.contenttype@default:default,org.eclipse.core.databinding.observable@default:default,org.eclipse.core.databinding.property@default:default,org.eclipse.core.databinding@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.runtime@default:true,org.eclipse.e4.core.contexts@default:default,org.eclipse.e4.core.di.annotations@default:default,org.eclipse.e4.core.di.extensions.supplier@default:default,org.eclipse.e4.core.di.extensions@default:default,org.eclipse.e4.core.di@default:default,org.eclipse.emf.common@default:default,org.eclipse.emf.databinding.edit@default:default,org.eclipse.emf.databinding@default:default,org.eclipse.emf.ecore.xmi@default:default,org.eclipse.emf.ecore@default:default,org.eclipse.emf.edit@default:default,org.eclipse.equinox.app@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.ds@1:true,org.eclipse.equinox.preferences@default:default,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.util@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi.util@default:default,org.eclipse.osgi@-1:true,org.hamcrest.core@default:default,org.junit@default:default,org.mockito.mockito-core-hamcrest-modified@default:default,org.objenesis@default:default"/>
+<stringAttribute key="selected_workspace_plugins" value="org.eclipse.emf.ecp.common@default:default,org.eclipse.emf.ecp.view.context@default:default,org.eclipse.emf.ecp.view.model.common@default:default,org.eclipse.emf.ecp.view.model@default:default,org.eclipse.emf.ecp.view.template.model@default:default,org.eclipse.emfforms.common@default:default,org.eclipse.emfforms.core.bazaar@default:default,org.eclipse.emfforms.core.services.databinding.featurepath.tests@default:false,org.eclipse.emfforms.core.services.databinding.featurepath@default:default,org.eclipse.emfforms.core.services.databinding.testmodel@default:default,org.eclipse.emfforms.core.services.emf@default:default,org.eclipse.emfforms.core.services.emfspecificservice@default:default,org.eclipse.emfforms.core.services@default:default,org.eclipse.emfforms.localization@default:default"/>
+<booleanAttribute key="show_selected_only" value="false"/>
+<booleanAttribute key="tracing" value="false"/>
+<booleanAttribute key="useCustomFeatures" value="false"/>
+<booleanAttribute key="useDefaultConfig" value="true"/>
+<booleanAttribute key="useDefaultConfigArea" value="false"/>
+<booleanAttribute key="useProduct" value="false"/>
+</launchConfiguration>
diff --git a/tests/org.eclipse.emfforms.core.services.databinding.featurepath.tests/src/org/eclipse/emfforms/internal/core/services/databinding/featurepath/FeaturePathDomainModelReferenceConverter_Test.java b/tests/org.eclipse.emfforms.core.services.databinding.featurepath.tests/src/org/eclipse/emfforms/internal/core/services/databinding/featurepath/FeaturePathDomainModelReferenceConverter_Test.java
index c824223..68c1fe4 100644
--- a/tests/org.eclipse.emfforms.core.services.databinding.featurepath.tests/src/org/eclipse/emfforms/internal/core/services/databinding/featurepath/FeaturePathDomainModelReferenceConverter_Test.java
+++ b/tests/org.eclipse.emfforms.core.services.databinding.featurepath.tests/src/org/eclipse/emfforms/internal/core/services/databinding/featurepath/FeaturePathDomainModelReferenceConverter_Test.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2011-2018 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2011-2019 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
@@ -10,6 +10,7 @@
  *
  * Contributors:
  * Lucas Koehler - initial API and implementation
+ * Christian W. Damus - bug 553224
  ******************************************************************************/
 package org.eclipse.emfforms.internal.core.services.databinding.featurepath;
 
@@ -18,6 +19,7 @@
 import static org.mockito.Mockito.mock;
 
 import java.util.LinkedList;
+import java.util.regex.Pattern;
 
 import org.eclipse.core.databinding.property.list.IListProperty;
 import org.eclipse.core.databinding.property.value.IValueProperty;
@@ -27,6 +29,7 @@
 import org.eclipse.emf.databinding.IEMFValueProperty;
 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;
 import org.eclipse.emf.ecore.EStructuralFeature.Setting;
@@ -35,8 +38,13 @@
 import org.eclipse.emf.ecore.resource.ResourceSet;
 import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
 import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.emf.ecore.xmi.XMLResource;
+import org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl;
+import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
+import org.eclipse.emf.ecp.view.spi.model.VControl;
 import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference;
 import org.eclipse.emf.ecp.view.spi.model.VFeaturePathDomainModelReference;
+import org.eclipse.emf.ecp.view.spi.model.VView;
 import org.eclipse.emf.ecp.view.spi.model.VViewFactory;
 import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
 import org.eclipse.emf.edit.domain.EditingDomain;
@@ -50,9 +58,13 @@
 import org.eclipse.emfforms.core.services.databinding.testmodel.test.model.TestPackage;
 import org.eclipse.emfforms.spi.core.services.databinding.DatabindingFailedException;
 import org.eclipse.emfforms.spi.core.services.databinding.DomainModelReferenceConverter;
+import org.hamcrest.CustomTypeSafeMatcher;
+import org.hamcrest.Matcher;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 /**
  * JUnit test for {@link FeaturePathDomainModelReferenceConverter}.
@@ -65,6 +77,9 @@
 	private FeaturePathDomainModelReferenceConverter converter;
 	private static EObject validEObject;
 
+	@Rule
+	public final ExpectedException thrown = ExpectedException.none();
+
 	@BeforeClass
 	public static void setupClass() {
 		validEObject = createValidEObject();
@@ -81,11 +96,12 @@
 
 	private static Resource createVirtualResource() {
 		final ResourceSet rs = new ResourceSetImpl();
+		rs.getResourceFactoryRegistry().getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl()); //$NON-NLS-1$
 		final AdapterFactoryEditingDomain domain = new AdapterFactoryEditingDomain(
 			new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE),
 			new BasicCommandStack(), rs);
 		rs.eAdapters().add(new AdapterFactoryEditingDomain.EditingDomainProvider(domain));
-		final Resource resource = rs.createResource(URI.createURI("VIRTAUAL_URI")); //$NON-NLS-1$
+		final Resource resource = rs.createResource(URI.createURI("VIRTUAL_URI.xmi")); //$NON-NLS-1$
 		return resource;
 	}
 
@@ -102,6 +118,28 @@
 		return AdapterFactoryEditingDomain.getEditingDomainFor(object);
 	}
 
+	private static VFeaturePathDomainModelReference createValidDMR() {
+		final Resource resource = createVirtualResource();
+		final VView view = VViewFactory.eINSTANCE.createView();
+		final VControl control = VViewFactory.eINSTANCE.createControl();
+		view.getChildren().add(control);
+		resource.getContents().add(view);
+		final VFeaturePathDomainModelReference result = VViewFactory.eINSTANCE.createFeaturePathDomainModelReference();
+		control.setDomainModelReference(result);
+		((XMLResource) resource).setID(result, "theDMR"); //$NON-NLS-1$
+		return result;
+	}
+
+	static Matcher<String> find(String regex) {
+		return new CustomTypeSafeMatcher<String>("matches '" + regex + "'") { //$NON-NLS-1$//$NON-NLS-2$
+			@Override
+			protected boolean matchesSafely(String item) {
+				final java.util.regex.Matcher m = Pattern.compile(regex).matcher(item);
+				return m.find();
+			}
+		};
+	}
+
 	/**
 	 * Set up that is executed before every test.
 	 */
@@ -133,8 +171,11 @@
 	 * {@link org.eclipse.emfforms.core.services.databinding.featurepath.FeaturePathDomainModelReferenceConverter#isApplicable(org.eclipse.emf.ecp.view.spi.model.VDomainModelReference)}
 	 * .
 	 */
-	@Test(expected = IllegalArgumentException.class)
+	@Test
 	public void testIsApplicableNull() {
+		thrown.expect(IllegalArgumentException.class);
+		thrown.expectMessage("must not be null"); //$NON-NLS-1$
+
 		converter.isApplicable(null);
 	}
 
@@ -212,8 +253,12 @@
 	 *
 	 * @throws DatabindingFailedException if the databinding failed
 	 */
-	@Test(expected = DatabindingFailedException.class)
+	@Test
 	public void testConvertToValuePropertyNoFeature() throws DatabindingFailedException {
+		thrown.expect(DatabindingFailedException.class);
+		thrown.expectMessage("field domainModelEFeature"); //$NON-NLS-1$
+		thrown.expectMessage("must not be null"); //$NON-NLS-1$
+
 		final VFeaturePathDomainModelReference pathReference = VViewFactory.eINSTANCE
 			.createFeaturePathDomainModelReference();
 		converter.convertToValueProperty(pathReference, validEObject);
@@ -226,8 +271,11 @@
 	 *
 	 * @throws DatabindingFailedException if the databinding failed
 	 */
-	@Test(expected = IllegalArgumentException.class)
+	@Test
 	public void testConvertToValuePropertyNull() throws DatabindingFailedException {
+		thrown.expect(IllegalArgumentException.class);
+		thrown.expectMessage("must not be null"); //$NON-NLS-1$
+
 		converter.convertToValueProperty(null, validEObject);
 	}
 
@@ -238,9 +286,55 @@
 	 *
 	 * @throws DatabindingFailedException if the databinding failed
 	 */
-	@Test(expected = IllegalArgumentException.class)
+	@Test
 	public void testConvertToValuePropertyWrongReferenceType() throws DatabindingFailedException {
-		converter.convertToValueProperty(mock(VDomainModelReference.class), validEObject);
+		final VDomainModelReference dmr = mock(VDomainModelReference.class);
+
+		thrown.expect(IllegalArgumentException.class);
+		thrown.expectMessage("DomainModelReference " + dmr.toString()); //$NON-NLS-1$
+		thrown.expectMessage("is not an instance of VFeaturePathDomainModelReference"); //$NON-NLS-1$
+
+		converter.convertToValueProperty(dmr, validEObject);
+	}
+
+	@Test
+	public void testConvertToValuePropertyListReferenceFirstInPath() throws DatabindingFailedException {
+		thrown.expect(DatabindingFailedException.class);
+		thrown.expectMessage("not a single reference: A.bList (test)"); //$NON-NLS-1$
+		thrown.expectMessage("DMR is VFeaturePathDomainModelReferenceImpl@"); //$NON-NLS-1$
+
+		final VFeaturePathDomainModelReference pathReference = VViewFactory.eINSTANCE
+			.createFeaturePathDomainModelReference();
+		// create reference path to the attribute
+		final LinkedList<EReference> referencePath = new LinkedList<EReference>();
+		referencePath.add(TestPackage.Literals.A__BLIST);
+		referencePath.add(TestPackage.Literals.B__C);
+		referencePath.add(TestPackage.Literals.C__D);
+
+		pathReference.getDomainModelEReferencePath().addAll(referencePath);
+		pathReference.setDomainModelEFeature(TestPackage.Literals.D__X);
+
+		converter.convertToValueProperty(pathReference, validEObject);
+	}
+
+	@Test
+	public void testConvertToValuePropertyListReferenceInPath() throws DatabindingFailedException {
+		thrown.expect(DatabindingFailedException.class);
+		thrown.expectMessage("not a single reference: B.cList (test)"); //$NON-NLS-1$
+		thrown.expectMessage("DMR is VFeaturePathDomainModelReferenceImpl@"); //$NON-NLS-1$
+
+		final VFeaturePathDomainModelReference pathReference = VViewFactory.eINSTANCE
+			.createFeaturePathDomainModelReference();
+		// create reference path to the attribute
+		final LinkedList<EReference> referencePath = new LinkedList<EReference>();
+		referencePath.add(TestPackage.Literals.A__B);
+		referencePath.add(TestPackage.Literals.B__CLIST);
+		referencePath.add(TestPackage.Literals.C__D);
+
+		pathReference.getDomainModelEReferencePath().addAll(referencePath);
+		pathReference.setDomainModelEFeature(TestPackage.Literals.D__X);
+
+		converter.convertToValueProperty(pathReference, validEObject);
 	}
 
 	@Test
@@ -344,8 +438,12 @@
 	 *
 	 * @throws DatabindingFailedException if the databinding failed
 	 */
-	@Test(expected = DatabindingFailedException.class)
+	@Test
 	public void testConvertToListPropertyNoFeature() throws DatabindingFailedException {
+		thrown.expect(DatabindingFailedException.class);
+		thrown.expectMessage("field domainModelEFeature"); //$NON-NLS-1$
+		thrown.expectMessage("must not be null"); //$NON-NLS-1$
+
 		final VFeaturePathDomainModelReference pathReference = VViewFactory.eINSTANCE
 			.createFeaturePathDomainModelReference();
 		converter.convertToListProperty(pathReference, validEObject);
@@ -388,8 +486,11 @@
 	 *
 	 * @throws DatabindingFailedException if the databinding failed
 	 */
-	@Test(expected = IllegalArgumentException.class)
+	@Test
 	public void testConvertToListPropertyNull() throws DatabindingFailedException {
+		thrown.expect(IllegalArgumentException.class);
+		thrown.expectMessage("must not be null"); //$NON-NLS-1$
+
 		converter.convertToListProperty(null, validEObject);
 	}
 
@@ -400,8 +501,11 @@
 	 *
 	 * @throws DatabindingFailedException if the databinding failed
 	 */
-	@Test(expected = IllegalArgumentException.class)
+	@Test
 	public void testConvertToListPropertyWrongReferenceType() throws DatabindingFailedException {
+		thrown.expect(IllegalArgumentException.class);
+		thrown.expectMessage("not an instance of VFeaturePathDomainModelReference"); //$NON-NLS-1$
+
 		converter.convertToListProperty(mock(VDomainModelReference.class), validEObject);
 	}
 
@@ -456,30 +560,203 @@
 		assertEquals(expected, setting.get(true));
 	}
 
-	@Test(expected = DatabindingFailedException.class)
+	@Test
 	public void testGetSettingNoFeature() throws DatabindingFailedException {
+		thrown.expect(DatabindingFailedException.class);
+		thrown.expectMessage("field domainModelEFeature"); //$NON-NLS-1$
+		thrown.expectMessage("must not be null"); //$NON-NLS-1$
+
 		final VFeaturePathDomainModelReference pathReference = VViewFactory.eINSTANCE
 			.createFeaturePathDomainModelReference();
 		converter.getSetting(pathReference, validEObject);
 	}
 
-	@Test(expected = IllegalArgumentException.class)
+	@Test
 	public void testGetSettingNull() throws DatabindingFailedException {
+		thrown.expect(IllegalArgumentException.class);
+		thrown.expectMessage("must not be null"); //$NON-NLS-1$
+
 		converter.getSetting(null, validEObject);
 	}
 
-	@Test(expected = IllegalArgumentException.class)
+	@Test
 	public void testGetSettingWrongReferenceType() throws DatabindingFailedException {
+		thrown.expect(IllegalArgumentException.class);
+		thrown.expectMessage("not an instance of VFeaturePathDomainModelReference"); //$NON-NLS-1$
+
 		converter.getSetting(mock(VDomainModelReference.class), validEObject);
 	}
 
-	@Test(expected = DatabindingFailedException.class)
+	@Test
 	public void testGetSettingInvalidFeatureInPath() throws DatabindingFailedException {
-		final VFeaturePathDomainModelReference reference = VViewFactory.eINSTANCE
-			.createFeaturePathDomainModelReference();
-		reference.getDomainModelEReferencePath().add(TestPackage.eINSTANCE.getDExtended_A());
-		reference.setDomainModelEFeature(TestPackage.eINSTANCE.getA_B());
-		final D d = TestFactory.eINSTANCE.createD();
+		thrown.expect(DatabindingFailedException.class);
+		thrown.expectMessage("EClass D (test) has no such feature DExtended.a (test)"); //$NON-NLS-1$
+		thrown.expectMessage(find(
+			"The DMR is VFeaturePathDomainModelReferenceImpl@\\p{XDigit}+ \\(changeListener: null\\)<VIRTUAL_URI\\.xmi#theDMR>")); //$NON-NLS-1$
+		thrown.expectMessage(
+			find("resolved EObject is DImpl@\\p{XDigit}+ \\(x: null, yList: null\\)<VIRTUAL_URI\\.xmi#/>")); //$NON-NLS-1$
+
+		final D d = (D) createValidEObject(TestPackage.Literals.D);
+		final VFeaturePathDomainModelReference reference = createValidDMR();
+		reference.getDomainModelEReferencePath().add(TestPackage.Literals.DEXTENDED__A);
+		reference.setDomainModelEFeature(TestPackage.Literals.A__B);
 		converter.getSetting(reference, d);
 	}
+
+	@Test
+	public void testGetSettingUnresolvedFeature() throws DatabindingFailedException {
+		final EPackage pkg = EcoreFactory.eINSTANCE.createEPackage();
+		pkg.setName("gone"); //$NON-NLS-1$
+		pkg.setNsPrefix("gone"); //$NON-NLS-1$
+		pkg.setNsURI("http://www.eclipse.org/ecp/test/gone"); //$NON-NLS-1$
+		final EClass eClass = EcoreFactory.eINSTANCE.createEClass();
+		eClass.setName("Gone"); //$NON-NLS-1$
+		final EReference ref = EcoreFactory.eINSTANCE.createEReference();
+		ref.setName("ref"); //$NON-NLS-1$
+		ref.setEType(eClass);
+		eClass.getEStructuralFeatures().add(ref);
+		pkg.getEClassifiers().add(eClass);
+		final Resource resource = new EcoreResourceFactoryImpl().createResource(URI.createURI(pkg.getNsURI()));
+		resource.getContents().add(pkg);
+
+		thrown.expect(DatabindingFailedException.class);
+		thrown.expectMessage("domainModelEFeature Gone.ref (http://www.eclipse.org/ecp/test/gone)"); //$NON-NLS-1$
+		thrown.expectMessage(find(
+			"DMR VFeaturePathDomainModelReferenceImpl@\\p{XDigit}+ \\(changeListener: null\\)<VIRTUAL_URI\\.xmi#theDMR>")); //$NON-NLS-1$
+		thrown.expectMessage("is a proxy"); //$NON-NLS-1$
+
+		final D d = (D) createValidEObject(TestPackage.Literals.D);
+		final VFeaturePathDomainModelReference reference = createValidDMR();
+		reference.getDomainModelEReferencePath().add(TestPackage.Literals.DEXTENDED__A);
+		reference.setDomainModelEFeature(ref);
+
+		// Unload the package to proxify everything
+		resource.unload();
+
+		converter.getSetting(reference, d);
+	}
+
+	@Test
+	public void testGetSettingUnresolvedReferenceInPath() throws DatabindingFailedException {
+		final EPackage pkg = EcoreFactory.eINSTANCE.createEPackage();
+		pkg.setName("gone"); //$NON-NLS-1$
+		pkg.setNsPrefix("gone"); //$NON-NLS-1$
+		pkg.setNsURI("http://www.eclipse.org/ecp/test/gone"); //$NON-NLS-1$
+		final EClass eClass = EcoreFactory.eINSTANCE.createEClass();
+		eClass.setName("Gone"); //$NON-NLS-1$
+		final EReference ref = EcoreFactory.eINSTANCE.createEReference();
+		ref.setName("ref"); //$NON-NLS-1$
+		ref.setEType(eClass);
+		eClass.getEStructuralFeatures().add(ref);
+		pkg.getEClassifiers().add(eClass);
+		final Resource resource = new EcoreResourceFactoryImpl().createResource(URI.createURI(pkg.getNsURI()));
+		resource.getContents().add(pkg);
+
+		thrown.expect(DatabindingFailedException.class);
+		thrown.expectMessage("path reference Gone.ref (http://www.eclipse.org/ecp/test/gone)"); //$NON-NLS-1$
+		thrown.expectMessage(find(
+			"DMR VFeaturePathDomainModelReferenceImpl@\\p{XDigit}+ \\(changeListener: null\\)<VIRTUAL_URI\\.xmi#theDMR>")); //$NON-NLS-1$
+		thrown.expectMessage("is a proxy"); //$NON-NLS-1$
+
+		final D d = (D) createValidEObject(TestPackage.Literals.D);
+		final VFeaturePathDomainModelReference reference = createValidDMR();
+		reference.getDomainModelEReferencePath().add(ref);
+		reference.setDomainModelEFeature(TestPackage.Literals.A__B);
+
+		// Unload the package to proxify everything
+		resource.unload();
+
+		converter.getSetting(reference, d);
+	}
+
+	@Test
+	public void testGetSettingListReferenceInPath() throws DatabindingFailedException {
+		thrown.expect(DatabindingFailedException.class);
+		thrown.expectMessage("not a single reference: B.cList (test)"); //$NON-NLS-1$
+		thrown.expectMessage("DMR is VFeaturePathDomainModelReferenceImpl@"); //$NON-NLS-1$
+		thrown.expectMessage(
+			find("resolved EObject is BImpl@\\p{XDigit}+<VIRTUAL_URI\\.xmi#//@b>")); //$NON-NLS-1$
+
+		final VFeaturePathDomainModelReference pathReference = VViewFactory.eINSTANCE
+			.createFeaturePathDomainModelReference();
+		// create reference path to the attribute
+		final LinkedList<EReference> referencePath = new LinkedList<EReference>();
+		referencePath.add(TestPackage.Literals.A__B);
+		referencePath.add(TestPackage.Literals.B__CLIST);
+		referencePath.add(TestPackage.Literals.C__D);
+
+		pathReference.getDomainModelEReferencePath().addAll(referencePath);
+		pathReference.setDomainModelEFeature(TestPackage.Literals.D__X);
+
+		final A a = (A) createValidEObject(TestPackage.Literals.A);
+		final B b = TestFactory.eINSTANCE.createB();
+		a.setB(b);
+		converter.getSetting(pathReference, a);
+	}
+
+	@Test
+	public void testGetSettingMissingObjectInPath() throws DatabindingFailedException {
+		thrown.expect(DatabindingFailedException.class);
+		thrown.expectMessage("DMR is VFeaturePathDomainModelReferenceImpl@"); //$NON-NLS-1$
+		thrown.expectMessage(
+			find("resolved EObject is BImpl@\\p{XDigit}+<VIRTUAL_URI\\.xmi#//@b>")); //$NON-NLS-1$
+		thrown.expectMessage("Reference being resolved is B.c (test)"); //$NON-NLS-1$
+
+		final VFeaturePathDomainModelReference pathReference = VViewFactory.eINSTANCE
+			.createFeaturePathDomainModelReference();
+		// create reference path to the attribute
+		final LinkedList<EReference> referencePath = new LinkedList<EReference>();
+		referencePath.add(TestPackage.Literals.A__B);
+		referencePath.add(TestPackage.Literals.B__C);
+		referencePath.add(TestPackage.Literals.C__D);
+
+		pathReference.getDomainModelEReferencePath().addAll(referencePath);
+		pathReference.setDomainModelEFeature(TestPackage.Literals.D__X);
+
+		final A a = (A) createValidEObject(TestPackage.Literals.A);
+		final B b = TestFactory.eINSTANCE.createB();
+		a.setB(b);
+		converter.getSetting(pathReference, a);
+	}
+
+	@Test
+	public void testGetSettingFeatureHasNoType() throws DatabindingFailedException {
+		final EPackage pkg = EcoreFactory.eINSTANCE.createEPackage();
+		pkg.setName("untyped"); //$NON-NLS-1$
+		pkg.setNsPrefix("ut"); //$NON-NLS-1$
+		pkg.setNsURI("http://www.eclipse.org/ecp/test/untyped"); //$NON-NLS-1$
+		final EClass eClass = EcoreFactory.eINSTANCE.createEClass();
+		eClass.setName("Untyped"); //$NON-NLS-1$
+		final EReference ref = EcoreFactory.eINSTANCE.createEReference();
+		ref.setName("untyped"); //$NON-NLS-1$
+		// Don't set a type (that's the point)
+		eClass.getEStructuralFeatures().add(ref);
+		pkg.getEClassifiers().add(eClass);
+		final Resource resource = new EcoreResourceFactoryImpl().createResource(URI.createURI(pkg.getNsURI()));
+		resource.getContents().add(pkg);
+
+		thrown.expect(DatabindingFailedException.class);
+		thrown.expectMessage("in DMR VFeaturePathDomainModelReferenceImpl@"); //$NON-NLS-1$
+		thrown.expectMessage("eType of the feature Untyped.untyped (http://www.eclipse.org/ecp/test/untyped) is null"); //$NON-NLS-1$
+
+		final VFeaturePathDomainModelReference dmr = createValidDMR();
+		dmr.setDomainModelEFeature(ref);
+
+		final EObject untyped = createValidEObject(eClass);
+		converter.getSetting(dmr, untyped);
+	}
+
+	@Test
+	public void testGetSettingObjectDoesNotHaveFeature() throws DatabindingFailedException {
+		thrown.expect(DatabindingFailedException.class);
+		thrown.expectMessage(find("resolved EObject BImpl@\\p{XDigit}+<VIRTUAL_URI.xmi#/>")); //$NON-NLS-1$
+		thrown.expectMessage("doesn't have the feature C.d (test)"); //$NON-NLS-1$
+
+		final VFeaturePathDomainModelReference dmr = createValidDMR();
+		dmr.setDomainModelEFeature(TestPackage.Literals.C__D);
+
+		final EObject b = createValidEObject(TestPackage.Literals.B);
+		converter.getSetting(dmr, b);
+	}
+
 }