package com.sap.furcas.runtime.parser.incremental;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.util.Collection;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.junit.BeforeClass;
import org.junit.Test;
import com.sap.furcas.metamodel.FURCAS.TCS.Alternative;
import com.sap.furcas.metamodel.FURCAS.TCS.ConditionalElement;
import com.sap.furcas.metamodel.FURCAS.textblocks.Version;
import com.sap.furcas.runtime.parser.incremental.testbase.IncrementalParserBasedTest;
import com.sap.furcas.test.fixture.ScenarioFixtureData;
import com.sap.ide.cts.parser.incremental.IncrementalParserFacade;
/**
* Test actions that the incremental parser might encounter within an editor environment.
*
* In the focus of these tests are optional syntax elements implemented via {@link Alternative}s
* or {@link ConditionalElement}s.
*
* @author Stephan Erb
*/
public class TestAlternativeParsingScenarios extends IncrementalParserBasedTest {
private static final String LANGUAGE = "BibtexWithAlternatives";
private static final File TCS = new File("fixtures/BibtexWithAlternatives.tcs");
private static final File[] METAMODELS = { ScenarioFixtureData.BIBTEXT_METAMODEL, ScenarioFixtureData.BIBTEXT1_METAMODEL };
@BeforeClass
public static void setupParser() throws Exception {
setupParser(LANGUAGE, TCS, /*useModelUpdaters*/ true, METAMODELS);
}
protected ParsingResult triggerParserAndExpectReuse() {
ParsingResult result = super.triggerParser();
// Within this testclass we expect full re-use of all root elements
if (result.oldRoot != null) {
assertEquals(result.oldRoot, result.newRoot);
}
return result;
}
@Test
public void testInitialParse() throws Exception {
model.replace(0, 0, "Library: Foo Entries:{}");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
}
/**
* Change name from Foo to Bar
*/
@Test
public void testChangeOptionalProperty() throws Exception {
model.replace(0, 0, "Library: Foo Entries:{}");
triggerParserAndExpectReuse();
assertEquals("Foo", get(getLibrary(), "name"));
model.replace("Library: ".length(), "Foo".length(), "Bar");
triggerParserAndExpectReuse();
assertEquals("Bar", get(getLibrary(), "name"));
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
}
/**
* Change name from nothing to Foo.
* Expect that the propertyInit on hasName is executed.
*/
@Test
public void testSetOptionalProperty() throws Exception {
model.replace(0, 0, "Library: Entries:{}");
triggerParserAndExpectReuse();
assertFalse("Should not be considered set", isSet(getLibrary(), "name"));
assertEquals(false, get(getLibrary(), "hasName"));
model.replace("Library:".length(), "".length(), "NewName");
triggerParserAndExpectReuse();
assertEquals("NewName", get(getLibrary(), "name"));
assertTrue("Should be considered set", isSet(getLibrary(), "name"));
assertEquals(true, get(getLibrary(), "hasName"));
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
}
/**
* Change name from Foo to nothing.
* Expect that the propertyInit on hasName is executed.
*/
@Test
public void testUnsetOptionalProperty() throws Exception {
model.replace(0, 0, "Library: Foo Entries:{}");
triggerParserAndExpectReuse();
assertEquals("Foo", get(getLibrary(), "name"));
assertTrue("Should be considered set", isSet(getLibrary(), "name"));
assertEquals(true, get(getLibrary(), "hasName"));
model.replace("Library: ".length(), "Foo".length(), "");
triggerParserAndExpectReuse();
assertEquals(false, get(getLibrary(), "hasName"));
assertFalse("Should no longer be considered set", isSet(getLibrary(), "name"));
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
}
/**
* Remove several authors. Author are represented using TextBlocks.
*/
@Test
public void testUnsetMultiValuelProperty() throws Exception {
model.replace(0, 0, "Library: Entries:{ Karl Gustav Peter }");
triggerParserAndExpectReuse();
assertEquals("Expect the authors", 3, getCollection(getLibrary(), "entries").size());
model.replace("Library: Entries:{".length(), " Karl Gustav Peter ".length(), "");
triggerParserAndExpectReuse();
assertEquals("Authors should be gone", 0, getCollection(getLibrary(), "entries").size());
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
}
/**
* Remove several authors. Author are represented using TextBlocks.
*
* Same as above but this time force the deletion by switching to an
* alternative which can only be used if there are no authors at all.
*/
@Test
public void testUnsetOptionalMultiValuelProperty() throws Exception {
model.replace(0, 0, "Library: Entries:{ Karl Gustav Peter }");
triggerParserAndExpectReuse();
assertEquals("Expect the authors", 3, getCollection(getLibrary(), "entries").size());
model.replace("Library: ".length(), "Entries:{ Karl Gustav Peter }".length(), "");
triggerParserAndExpectReuse();
assertEquals("Authors should be gone", 0, getCollection(getLibrary(), "entries").size());
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
}
/**
* There are alternative ways to represent an author.
* Switch between these and always expect that the author object is reused.
*/
@Test
public void testSwitchAlternative() throws Exception {
model.replace(0, 0, "Library: Entries:{ MyAuthor }");
triggerParserAndExpectReuse();
EObject author = getFirstEntry();
assertEquals("MyAuthor", get(author, "name"));
model.replace("Library: Entries:{ ".length(), "MyAuthor".length(), "alternative");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertSame("Author object should have been re-used", author, getFirstEntry());
assertFalse("Name not set in chosen alternative", isSet(author, "name"));
// No switch back to the alternative with a name
model.replace("Library: Entries:{ ".length(), "alternative".length(), "SameAuthorNewName");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertSame("Author object should have been re-used", author, getFirstEntry());
assertEquals("SameAuthorNewName", get(author, "name"));
}
/**
* There are alternative ways to represent an author.
* Switch between these and always expect that the author object is reused.
*
* Same as above but we do now switch between alternatives on different
* nesting levels.
*/
@Test
public void testSwitchNestedAlternative() throws Exception {
model.replace(0, 0, "Library: Entries:{ MyAuthor }");
triggerParserAndExpectReuse();
EObject author = getFirstEntry();
assertEquals("MyAuthor", get(author, "name"));
// Switch to a nested alternative
model.replace("Library: Entries:{ ".length(), "MyAuthor".length(), "nestedAlternativeA");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertSame("Author object should have been re-used", author, getFirstEntry());
assertFalse("Name not set in chosen alternative", isSet(author, "name"));
// Switch to another nested alternative
model.replace("Library: Entries:{ ".length(), "nestedAlternativeA".length(), "nestedAlternativeB");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertSame("Author object should have been re-used", author, getFirstEntry());
assertFalse("Name not set in chosen alternative", isSet(author, "name"));
// No switch back to the outer alternative with a name
model.replace("Library: Entries:{ ".length(), "nestedAlternativeB".length(), "SameAuthorNewName");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertSame("Author object should have been re-used", author, getFirstEntry());
assertEquals("SameAuthorNewName", get(author, "name"));
}
/**
* There are alternative ways to represent an author.
* Switch between these and always expect that the author object is reused.
*
* Same as above but we do now switch between alternatives on different
* nesting levels and of different types (alternatives and isDefined clauses).
*/
@Test
public void testSwitchNestedIsDefined() throws Exception {
model.replace(0, 0, "Library: Entries:{ MyAuthor }");
triggerParserAndExpectReuse();
EObject author = getFirstEntry();
assertEquals("MyAuthor", get(author, "name"));
// Switch to a nested alternative in an isDefined
model.replace("Library: Entries:{ ".length(), "".length(), "nested name ");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertSame("Author object should have been re-used", author, getFirstEntry());
assertEquals("Name should be the same", "MyAuthor", get(author, "name"));
// Switch to another nested alternative within the other isDefined clause
model.replace("Library: Entries:{ nested ".length(), "name MyAuthor".length(), "keywordA");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertSame("Author object should have been re-used", author, getFirstEntry());
assertFalse("Name not set in chosen alternative", isSet(author, "name"));
// Switch to back to the previous isDefined clause
model.replace("Library: Entries:{ nested ".length(), "keywordA".length(), "name MyAuthor");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertSame("Author object should have been re-used", author, getFirstEntry());
assertEquals("Name should be the same", "MyAuthor", get(author, "name"));
// Switch to yet another nested alternative which lies outside of the isDefined.
model.replace("Library: Entries:{ ".length(), "nested name MyAuthor".length(), "nestedAlternativeB");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertSame("Author object should have been re-used", author, getFirstEntry());
assertFalse("Name not set in chosen alternative", isSet(author, "name"));
// No switch back to the outer alternative with a name
model.replace("Library: Entries:{ ".length(), "nestedAlternativeB".length(), "SameAuthorNewName");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertSame("Author object should have been re-used", author, getFirstEntry());
assertEquals("SameAuthorNewName", get(author, "name"));
}
/**
* There are alternative ways to represent an author.
* Switch between these and always expect that the author object is reused.
*
* Same as above but we do now switch between alternatives on different
* nesting levels and of different types (alternatives and isDefined clauses).
*/
@Test
public void testSwitchNestedIsDefinedNoElse() throws Exception {
model.replace(0, 0, "Library: Entries:{ MyAuthor }");
triggerParserAndExpectReuse();
EObject author = getFirstEntry();
assertEquals("MyAuthor", get(author, "name"));
// Don't enable the isDefined
model.replace("Library: Entries:{ ".length(), "MyAuthor".length(), "noElse");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertSame("Author object should have been re-used", author, getFirstEntry());
assertFalse("Name not set in chosen alternative", isSet(author, "name"));
// Switch to the then clause of the isDefined
model.replace("Library: Entries:{ ".length(), "".length(), "name MyAuthor ");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertSame("Author object should have been re-used", author, getFirstEntry());
assertEquals("Name should be the same", "MyAuthor", get(author, "name"));
// Switch to back out of the then block (there is no else block)
model.replace("Library: Entries:{ ".length(), "name MyAuthor".length(), "");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertSame("Author object should have been re-used", author, getFirstEntry());
assertFalse("Name not set in chosen alternative", isSet(author, "name"));
}
/**
* Switch between alternatives but with an intermediate delete.
* No reuse expected (this is just to assure that our reuse check works...)
*/
@Test
public void testSwitchAlternativeWithoutReuse() throws Exception {
model.replace(0, 0, "Library: Entries:{ MyAuthor }");
triggerParserAndExpectReuse();
EObject author = getFirstEntry();
assertEquals("MyAuthor", get(author, "name"));
model.replace("Library: Entries:{ ".length(), "MyAuthor".length(), "");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertNull("Author object should be gone", getFirstEntry());
model.replace("Library: Entries:{ ".length(), "".length(), "alternative");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertFalse("Author should have been recreated", author.equals(getFirstEntry()));
assertFalse("Name not set in chosen alternative", isSet(author, "name"));
}
/**
* Replace an author with an entry.
*/
@Test
public void testReplaceWithModelElementOfDifferentType() throws Exception {
model.replace(0, 0, "Library: Entries:{ Author }");
triggerParserAndExpectReuse();
EObject author = getFirstEntry();
EClass authorEntryType = author.eClass();
model.replace("Library: Entries:{ ".length(), "Author".length(), "article");
triggerParserAndExpectReuse();
assertTrue(model.getRoot().getVersion() == Version.REFERENCE);
assertFalse("Types should be different", authorEntryType.equals(getFirstEntry().eClass()));
assertFalse("No reuse for different types expected", author.equals(getFirstEntry()));
}
private Object get(EObject obj, String name) {
return obj.eGet(obj.eClass().getEStructuralFeature(name));
}
private Collection<?> getCollection(EObject obj, String name) {
return (Collection<?>) obj.eGet(obj.eClass().getEStructuralFeature(name));
}
private boolean isSet(EObject obj, String name) {
return obj.eIsSet(obj.eClass().getEStructuralFeature(name));
}
private EObject getLibrary() {
return IncrementalParserFacade.getParsingResult(model.getRoot());
}
private EObject getFirstEntry() {
Collection<?> col = getCollection(getLibrary(), "entries");
if (col.isEmpty()) {
return null;
}
return (EObject) getCollection(getLibrary(), "entries").iterator().next();
}
}