package com.sap.furcas.runtime.parser.incremental;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import com.sap.furcas.metamodel.FURCAS.textblocks.AbstractToken;
import com.sap.furcas.metamodel.FURCAS.textblocks.LexedToken;
import com.sap.furcas.metamodel.FURCAS.textblocks.OmittedToken;
import com.sap.furcas.metamodel.FURCAS.textblocks.TextBlock;
import com.sap.furcas.parsergenerator.TCSSyntaxContainerBean;
import com.sap.furcas.runtime.parser.incremental.testbase.GeneratedParserAndFactoryBasedTest;
import com.sap.furcas.runtime.parser.incremental.testbase.GeneratedParserAndFactoryTestConfiguration;
import com.sap.furcas.runtime.parser.testbase.MockPartitionAssignmentHandler;
import com.sap.furcas.runtime.textblocks.TbNavigationUtil;
import com.sap.furcas.runtime.textblocks.TbUtil;
import com.sap.furcas.runtime.textblocks.model.TextBlocksModel;
import com.sap.furcas.runtime.textblocks.modifcation.TbChangeUtil;
import com.sap.furcas.runtime.textblocks.testutils.EMFTextBlocksModelElementFactory;
import com.sap.furcas.runtime.textblocks.testutils.TestSourceTextBlockCreator;
import com.sap.furcas.runtime.textblocks.testutils.TextBlocksModelElementFactory;
import com.sap.furcas.test.fixture.ScenarioFixtureData;
import com.sap.furcas.test.testutils.ResourceTestHelper;
import com.sap.ide.cts.parser.errorhandling.SemanticParserException;
import com.sap.ide.cts.parser.incremental.IncrementalParserFacade;
public class TestIncrementalParser extends GeneratedParserAndFactoryBasedTest {
private static final String LANGUAGE = "Bibtex";
private static final File TCS = new File("fixtures/Bibtex.tcs");
private static final File[] METAMODELS = { ScenarioFixtureData.BIBTEXT_METAMODEL, ScenarioFixtureData.BIBTEXT1_METAMODEL };
private static IncrementalParserFacade incrementalParserFacade;
private TextBlocksModelElementFactory modelFactory;
private static Resource transientParsingResource;
private static ResourceSet resourceSet;
@BeforeClass
public static void setupParser() throws Exception {
GeneratedParserAndFactoryTestConfiguration testConfig = new GeneratedParserAndFactoryTestConfiguration(LANGUAGE, TCS, METAMODELS);
resourceSet = testConfig.getSourceConfiguration().getResourceSet();
TCSSyntaxContainerBean syntaxBean = parseSyntax(testConfig);
transientParsingResource = ResourceTestHelper.createTransientResource(resourceSet);
incrementalParserFacade = generateParserAndParserFactoryForLanguage(syntaxBean, testConfig,
resourceSet, new MockPartitionAssignmentHandler(transientParsingResource),
new ClassLookupImpl());
ECrossReferenceAdapter crossRefAdapter = new ECrossReferenceAdapter();
resourceSet.eAdapters().add(crossRefAdapter);
crossRefAdapter.setTarget(resourceSet);
}
@Before
public void setup() {
modelFactory = new EMFTextBlocksModelElementFactory();
}
@After
public void cleanup() throws Exception {
transientParsingResource.getContents().clear();
}
@Test
public void testIncrementalParserSetup() throws SemanticParserException {
TextBlock currentVersionTb = createJohnDoe();
EObject syntaxObject = IncrementalParserFacade.getParsingResult(currentVersionTb);
// assert no exception
assertNotNull(syntaxObject);
List<?> entries = getEntries(syntaxObject);
assertEquals(2, entries.size());
}
private TextBlock createJohnDoe() throws SemanticParserException {
AbstractToken content = modelFactory.createToken("");
TextBlock root = TestSourceTextBlockCreator.initialiseTextBlocksWithContentToken(modelFactory, content);
transientParsingResource.getContents().add(root);
TextBlocksModel tbModel = new TextBlocksModel(root);
tbModel.replace(0, 0,
"article{ Testing, \"John Doe\", year = \"2002\" } author = \"John Doe\".");
TextBlock currentVersionTb = incrementalParserFacade.parseIncrementally(root);
return currentVersionTb;
}
/**
* Tests whether an simple addition to a textblock is correctly mapped to an
* insertion in the model without re-creating the parent element.
*
* @throws Exception
*/
@Test
public void testParseBibTextAddNewSubBlock() throws Exception {
TextBlock currentVersionTb = createJohnDoe();
EObject syntaxObject = IncrementalParserFacade.getParsingResult(currentVersionTb);
// assert no exception
assertNotNull(syntaxObject);
EList<?> entries = getEntries(syntaxObject);
assertEquals(2, entries.size());
EObject article = (EObject) entries.get(0);
EList<?> attributes = getArticles(article);
assertEquals(1, attributes.size());
TbChangeUtil.cleanUp(currentVersionTb);
// add a new year to article
TextBlocksModel tbModel = new TextBlocksModel(currentVersionTb);
tbModel.replace(31, 0, "year = \"2010\",");
TextBlock currentVersionTbNew = incrementalParserFacade.parseIncrementally(currentVersionTb);
// textBlock shouldn't have changed
assertEquals(currentVersionTb, currentVersionTb);
EObject syntaxObject2 = IncrementalParserFacade.getParsingResult(currentVersionTbNew);
// bibtexfile element shouldn't have changed
assertEquals(syntaxObject, syntaxObject2);
// article element shouldn't have changed
entries = (EList<?>) (syntaxObject2).eGet((syntaxObject).eClass().getEStructuralFeature("entries"));
EObject newArticle = (EObject) entries.get(0);
assertEquals(article, newArticle);
attributes = getArticles(article);
assertEquals(2, attributes.size());
EObject newYear = (EObject) attributes.get(0);
assertEquals("2010", newYear.eGet(newYear.eClass().getEStructuralFeature("value")));
}
/**
* Make sure {@link LexedToken#getReferencedElements()} are set as expected.
*
* They should just be set for the reference from Article to Author, but no
* where else.
*/
@Test
public void testReferencedElements() throws Exception {
TextBlock currentVersionTb = createJohnDoe();
EObject syntaxObject = IncrementalParserFacade.getParsingResult(currentVersionTb);
EList<?> entries = getEntries(syntaxObject);
assertEquals(2, entries.size());
EObject article = (EObject) entries.get(0);
EObject author = (EObject) entries.get(1);
Collection<LexedToken> tokensWithSetReference = new ArrayList<LexedToken>();
AbstractToken token = TbNavigationUtil.firstToken(currentVersionTb);
while (token != null) {
if (token instanceof LexedToken && ((LexedToken) token).getReferencedElements().size() > 0) {
tokensWithSetReference.add((LexedToken) token);
}
token = TbNavigationUtil.nextToken(token);
}
assertEquals(1, tokensWithSetReference.size());
LexedToken lexedToken = tokensWithSetReference.iterator().next();
assertTrue("Should be the token belonging to the article",
lexedToken.getParent().getCorrespondingModelElements().contains(article));
assertTrue("Token should be pointing to the author",
lexedToken.getReferencedElements().contains(author));
assertEquals(1, lexedToken.getReferencedElements().size());
}
/**
* Different tests asserting how omitted tokens (i.e. whitespaces and comments) are handled
* by the incremental parser.
*/
@Test
public void testOmittedTokens() throws Exception {
TextBlock textBlock = createJohnDoe();
TbChangeUtil.cleanUp(textBlock);
Collection<OmittedToken> omittedTokens = extractTokens(textBlock, OmittedToken.class);
// 1) Assert default omitted tokens as initially created by the lexer
for (OmittedToken token : omittedTokens) {
assertNull("Should have no sequence element", token.getSequenceElement());
assertEquals("Each space has an individual token", " ", token.getValue());
}
// 2) Morph a " " into a comment. Check its state after parsing.
OmittedToken omittedToken = omittedTokens.iterator().next();
TextBlocksModel model = new TextBlocksModel(textBlock);
model.replace(omittedToken.getOffset(), omittedToken.getLength(), "/** A comment */");
textBlock = incrementalParserFacade.parseIncrementally(model.getRoot());
TbChangeUtil.cleanUp(textBlock);
OmittedToken comment = extractTokens(textBlock, OmittedToken.class).iterator().next();
assertEquals("/** A comment */", comment.getValue());
assertNull("Should have no sequence element either", comment.getSequenceElement());
assertTrue("Token types should be different", comment.getType() != omittedToken.getType());
// 3) Change all tokens to be whitespaces (this will still be syntactically valid).
// Check their state after parsing
model = new TextBlocksModel(textBlock);
List<LexedToken> lexedTokens = extractTokens(textBlock, LexedToken.class);
Collections.reverse(lexedTokens); // replace from right to left so that offset information stays valid
for (LexedToken token : lexedTokens) {
assertNotNull("Must have a squence element", token.getSequenceElement());
model.replace(TbUtil.getAbsoluteOffset(token), token.getLength(), " ");
}
textBlock = incrementalParserFacade.parseIncrementally(model.getRoot());
TbChangeUtil.cleanUp(textBlock);
lexedTokens = extractTokens(textBlock, LexedToken.class);
assertEquals("All lexed tokens should be gone", 0, lexedTokens.size());
omittedTokens = extractTokens(textBlock, OmittedToken.class);
for (OmittedToken token : omittedTokens) {
assertNull("Should no longer have a sequence element", token.getSequenceElement());
}
}
private <T> List<T> extractTokens(TextBlock currentVersionTb, Class<T> clazz) {
List<T> result = new ArrayList<T>();
AbstractToken token = TbNavigationUtil.firstToken(currentVersionTb);
while (token != null) {
if (clazz.isInstance(token)) {
result.add(clazz.cast(token));
}
token = TbNavigationUtil.nextToken(token);
}
return result;
}
private EList<?> getArticles(EObject article) {
return (EList<?>) article.eGet(article.eClass().getEStructuralFeature("attributes"));
}
private EList<?> getEntries(EObject syntaxObject) {
return (EList<?>) (syntaxObject).eGet((syntaxObject).eClass().getEStructuralFeature("entries"));
}
}