/*******************************************************************************
* Copyright (c) 2012 Michael Vorburger (http://www.vorburger.ch).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
/**
* Copyright 2012 Michael Vorburger (http://www.vorburger.ch)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.vorburger.xtext.databinding.tests;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.databinding.UpdateListStrategy;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.conversion.Converter;
import org.eclipse.core.databinding.conversion.IConverter;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.emf.databinding.EMFProperties;
import org.eclipse.emf.databinding.EMFUpdateListStrategy;
import org.eclipse.emf.databinding.FeaturePath;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xtext.resource.XtextResource;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import ch.vorburger.beans.AbstractPropertyChangeNotifier;
import ch.vorburger.databinding.tests.utils.DataBindingTestUtils;
import ch.vorburger.databinding.tests.utils.DatabindingTestRealm;
import ch.vorburger.xtext.databinding.XtextProperties;
import ch.vorburger.xtext.databinding.XtextDataBindingContext;
import ch.vorburger.xtext.databinding.tests.utils.ECoreHelper;
import ch.vorburger.xtext.databinding.tests.utils.XtextResourceTestAccess;
/**
* Tests for the XtexteDataBindingContext & XtextProperties.
*
* @author Michael Vorburger
*/
public class XtextPropertiesTest {
@SuppressWarnings("serial")
private static class Bean extends AbstractPropertyChangeNotifier {
private String name;
private List<Bean> list = new ArrayList<XtextPropertiesTest.Bean>();
public void setName(String name) {
firePropertyChange("name", this.name, this.name = name);
}
public String getName() {
return name;
}
public List<Bean> getList() {
return list;
}
@SuppressWarnings("unused") // BeanProperties requires this
public void setList(List<Bean> list) {
this.list = list;
}
}
private XtextDataBindingContext db;
private EAttribute titleFeature;
private EReference referenceFeature;
private EReference listFeature;
private Bean bean;
private XtextResourceTestAccess access;
private EObject eObject;
private EObject containedEObject;
private EClass clazz;
private ECoreHelper helper;
@Before
public void setUp() {
// Create an ECore model
helper = new ECoreHelper();
EDataType stringType = EcorePackage.eINSTANCE.getEString();
EPackage pkg = helper.createPackage("tests");
clazz = helper.createClass(pkg, "Test");
titleFeature = helper.addAttribute(clazz, stringType, "title");
referenceFeature = helper.addContainmentReference(clazz, clazz, "childContainmentReferenceToTest");
listFeature = helper.addMultiContainmentReference(clazz, clazz, "list");
// Create EObjects
eObject = helper.createInstance(clazz);
eObject.eSet(titleFeature, "This is the Title");
containedEObject = helper.createInstance(clazz);
eObject.eSet(referenceFeature, containedEObject);
// Create a Bean
bean = new Bean();
Realm realm = new DatabindingTestRealm();
db = new XtextDataBindingContext(realm);
// TODO HIGH Use an indirect WritableValue in observe instead of direct (both for illustration and to test SourceAccessor stuff)
XtextResource resource = new XtextResource();
resource.getContents().add(eObject);
access = new XtextResourceTestAccess(resource);
}
@Test
public void testURIFragment() {
Resource resource = containedEObject.eResource();
String uriFragment = resource.getURIFragment(containedEObject);
EObject gotEObject = resource.getEObject(uriFragment);
assertEquals(containedEObject, gotEObject);
// System.out.println(resource.getURIFragment(eObject));
// System.out.println(uriFragment);
}
@Test
public void testSimpleValueBinding() {
db.bindValue(BeanProperties.value("name").observe(bean),
XtextProperties.value(titleFeature).observe(access));
DataBindingTestUtils.assertContextOK(db);
assertEquals(bean.getName(), eObject.eGet(titleFeature));
// First let's test changing the target and checking the model
bean.setName("reset, reset");
assertEquals("reset, reset", bean.getName()); // This just tests the bean, not the binding
assertEquals("reset, reset", eObject.eGet(titleFeature));
// Now let's test changing the model and checking the target
// Start by changing another feature of the model (which is not data bound, in this test)
eObject.eSet(referenceFeature, null);
// Of course, the other feature (under test) should, obviously, not change yet
assertEquals("reset, reset", bean.getName());
// Now we change the data bound feature in model and make sure the target changed
eObject.eSet(titleFeature, "setitsetit!");
assertEquals("setitsetit!", bean.getName());
DataBindingTestUtils.assertContextOK(db);
}
/**
* Tests that using an EObject in observe(), like the EMFProperties Data Binding API expects fails.
* The XtextProperties Data Binding API needs to be observing an XTextDocument (IReadAccess<XtextResource>, IWriteAccess<XtextResource>).
*/
@Test(expected=IllegalArgumentException.class)
public void testErrorObserveObjectInsteadOfResourceAcess() {
db.bindValue(
BeanProperties.value("name").observe(bean),
XtextProperties.value(titleFeature).observe(eObject));
}
@Test
public void testPathFeatureBinding() {
db.bindValue(BeanProperties.value("name").observe(bean),
XtextProperties.value(FeaturePath.fromList(referenceFeature, titleFeature)).observe(access));
DataBindingTestUtils.assertContextOK(db);
// Referenced Feature has now been set (sync) by binding:
assertEquals(bean.getName(), ((EObject)eObject.eGet(referenceFeature)).eGet(titleFeature));
// Root object feature is as set in setUp():
assertEquals("This is the Title", eObject.eGet(titleFeature));
// Again, first let's test changing the target and checking the model
bean.setName("reset, reset");
// Root object feature should NOT have changed:
assertEquals("This is the Title", eObject.eGet(titleFeature));
// Referenced Feature has now been set (sync) by binding and changed:
assertEquals("reset, reset", ((EObject)eObject.eGet(referenceFeature)).eGet(titleFeature));
// Now let's test changing the model and checking the target
// Start by changing another feature of the model (which is not data bound, in this test)
containedEObject.eSet(referenceFeature, helper.createInstance(clazz));
// Of course, the other feature (under test) should, obviously, not change yet
assertEquals("reset, reset", bean.getName());
// Now we change the data bound feature in model and make sure the target changed
containedEObject.eSet(titleFeature, "setitsetit!");
assertEquals("setitsetit!", bean.getName());
DataBindingTestUtils.assertContextOK(db);
}
// TODO HIGH referenceFeature in path was initially null and must be constructed during sync!
@Test
@Ignore // TODO MEDIUM Figure this out
@SuppressWarnings("unchecked")
public void tryOutAndLearnEMFListBinding() {
EObject eObjectInList = helper.createInstance(clazz);
eObjectInList.eSet(titleFeature, "First Object in List");
List<EObject> list = (List<EObject>) eObject.eGet(listFeature);
list.add(eObjectInList);
UpdateListStrategy modelToTarget = new EMFUpdateListStrategy() {
@Override
protected IConverter createConverter(Object fromType, Object toType) {
// TODO Remove System.out.println - or better move it into a else Log WARN below?
System.out.println(getClass().getName() + " :: createConverter :" + fromType.toString() + " -> " + toType.toString());
if (toType == Object.class) {
throw new IllegalArgumentException("Cannot create Converter without knowning toType! If you're using BeanProperties.list, add elementType Class as second argument");
}
if (fromType instanceof EReference && toType == Bean.class) { // TODO handle subclasses?!
// TODO It should be possible to write this in a generic fashion? Backed by Binding?
return new Converter(fromType, toType) {
@Override
public Object convert(Object fromObject) {
final Bean newBean = new Bean();
EObject eObject = (EObject) fromObject;
newBean.setName((String) eObject.eGet(titleFeature));
return newBean;
}
};
}
return super.createConverter(fromType, toType);
}
};
db.bindList(BeanProperties.list("list", Bean.class).observe(bean),
EMFProperties.list(listFeature).observe(eObject),
null, modelToTarget);
DataBindingTestUtils.assertContextOK(db);
// Check that an object was added to the bound list
List<Bean> beanList = bean.getList();
assertEquals(1, beanList.size());
Bean firstBeanInList = beanList.get(0);
assertEquals("First Object in List", firstBeanInList.getName());
// Check that a modification of the object in the list is sync'd
eObjectInList.eSet(titleFeature, "First Object in List CHANGED");
assertEquals("First Object in List CHANGED", firstBeanInList.getName());
// Check opposite modification of the object in the list is sync'd
firstBeanInList.setName("First BEAN in List");
assertEquals("First BEAN in List", eObjectInList.eGet(titleFeature));
}
@Test
@Ignore // TODO MEDIUM Figure this out
@SuppressWarnings("unchecked")
public void testXtextListBinding() {
EObject eObjectInList = helper.createInstance(clazz);
eObjectInList.eSet(titleFeature, "First Object in List");
List<EObject> list = (List<EObject>) eObject.eGet(listFeature);
list.add(eObjectInList);
UpdateListStrategy modelToTarget = new EMFUpdateListStrategy() {
@Override
protected IConverter createConverter(Object fromType, Object toType) {
// TODO Remove System.out.println - or better move it into a else Log WARN below?
System.out.println(getClass().getName() + " :: createConverter :" + fromType.toString() + " -> " + toType.toString());
if (toType == Object.class) {
throw new IllegalArgumentException("Cannot create Converter without knowning toType! If you're using BeanProperties.list, add elementType Class as second argument");
}
if (fromType instanceof EReference && toType == Bean.class) { // TODO handle subclasses?!
// TODO It should be possible to write this in a generic fashion? Backed by Binding?
return new Converter(fromType, toType) {
@Override
public Object convert(Object fromObject) {
final Bean newBean = new Bean();
EObject eObject = (EObject) fromObject;
newBean.setName((String) eObject.eGet(titleFeature));
return newBean;
}
};
}
return super.createConverter(fromType, toType);
}
};
db.bindList(BeanProperties.list("list", Bean.class).observe(bean),
XtextProperties.list(listFeature).observe(access),
null, modelToTarget);
DataBindingTestUtils.assertContextOK(db);
// Check that an object was added to the bound list
List<Bean> beanList = bean.getList();
assertEquals(1, beanList.size());
Bean firstBeanInList = beanList.get(0);
assertEquals("First Object in List", firstBeanInList.getName());
// Check that a modification of the object in the list is sync'd
eObjectInList.eSet(titleFeature, "First Object in List CHANGED");
assertEquals("First Object in List CHANGED", firstBeanInList.getName());
// Check opposite modification of the object in the list is sync'd
firstBeanInList.setName("First BEAN in List");
assertEquals("First BEAN in List", eObjectInList.eGet(titleFeature));
}
// TODO HIGH List Binding change position
@After
public void tearDown() {
db.dispose();
}
}