/*******************************************************************************
* Copyright (c) 2011 SAP AG and others.
* 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
*
* Contributors:
* SAP AG - initial API and implementation
******************************************************************************/
package com.sap.furcas.prettyprinter.incremental;
import static com.sap.furcas.prettyprinter.testutils.PrettyPrintAssertionUtil.assertEqualsByLines;
import java.io.File;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import com.sap.furcas.metamodel.FURCAS.textblocks.DocumentNode;
import com.sap.furcas.metamodel.FURCAS.textblocks.TextBlock;
import com.sap.furcas.prettyprinter.PrettyPrinter;
import com.sap.furcas.prettyprinter.template.ClassLookupImpl;
import com.sap.furcas.runtime.common.util.TCSSpecificOCLEvaluator;
import com.sap.furcas.runtime.parser.impl.ParserScope;
import com.sap.furcas.runtime.parser.incremental.testbase.IncrementalParserBasedTest;
import com.sap.furcas.test.fixture.FeatureFixtureData;
/**
* Test the incremental re-use capabilities of the pretty printer.
* It should be able to extract and re-use information from existing TextBlocks.
*
* @author Stephan Erb
*
*/
public class TestTextBlockBasedPrintPolicy extends IncrementalParserBasedTest {
private static final String LANGUAGE = "QueryByIdentifierFeatureTestSyntax";
private static final File TCS = FeatureFixtureData.PARG_QUERY_BY_IDENTIFIER_TCS;
private static final File[] METAMODELS = { FeatureFixtureData.PARG_REFERENCE_BY_IDENTIFIER_METAMODEL };
private static PrettyPrinter prettyPrinter;
@BeforeClass
public static void setupParser() throws Exception {
setupParser(LANGUAGE, TCS, new ClassLookupImpl(), METAMODELS);
ParserScope scope = incrementalParserFacade.getParserScope();
prettyPrinter = new PrettyPrinter(scope.getSyntax(), scope.getMetamodelLookup(),
new TCSSpecificOCLEvaluator(), incrementalParserFacade.getParserFactory());
}
/**
* Make sure the simple stuff works: Parsing and re-printing.
*/
@Test
public void testSimple() throws Exception {
String content = "Definitions : def a ; Usages : use a ;";
model.replace(0, model.getLength(), content);
triggerParser();
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject());
assertEqualsByLines(reprinted.getCachedString(), content);
}
/**
* Parsing and re-printing, while passing in the old block.
*
* Content is formatted according to the default style
* so this should be exactly the same as {@link #testSimple()}.
*/
@Test
public void testSimpleWithReuse() throws Exception {
String content = "Definitions : def a ; Usages : use a ;";
model.replace(0, model.getLength(), content);
triggerParser();
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject(), getRootBlock());
assertEqualsByLines(reprinted.getCachedString(), content);
}
/**
* Default formatting is slightly modified. As the old block is not passed in,
* we cannot expect any formatting re-use.
*/
@Test(expected=Throwable.class)
public void testSimpleFormattingChanges() throws Exception {
String content = "Definitions: def a; Usages: use a;";
model.replace(0, model.getLength(), content);
triggerParser();
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject());
assertEqualsByLines(reprinted.getCachedString(), content);
}
/**
* Default formatting is slightly modified.
* The pretty printer should re-use the complete formatting information.
*/
@Test
public void testSimpleFormattingChangesWithReuse() throws Exception {
String content = "Definitions: def a; Usages: use a;";
model.replace(0, model.getLength(), content);
triggerParser();
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject(), getRootBlock());
assertEqualsByLines(reprinted.getCachedString(), content);
}
/**
* The pretty printer should reuse leading and trailing formatting/whitespaces
*/
@Test
public void testFormattingChangesWithReuse() throws Exception {
String content = "Definitions : \n" +
" def a;\n" +
" def b ; \n " +
" def c;\n" +
"\n" +
"Usages: \n" +
" use a;\n";
model.replace(0, model.getLength(), content);
triggerParser();
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject(), getRootBlock());
assertEqualsByLines(reprinted.getCachedString(), content);
}
/**
* The pretty printer should re-use the complete formatting information.
*/
@Test
public void testFormattingReuseWithBrokenMapping() throws Exception {
String content = "Definitions: \n" +
" def a;\n" +
" def b ; \n " +
" def c;\n" +
"\n" +
"Usages: \n" +
" use a;\n";
model.replace(0, model.getLength(), content);
triggerParser();
breakMapping(getRootBlock());
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject(), getRootBlock());
assertEqualsByLines(reprinted.getCachedString(), content);
}
/**
* The pretty printer should re-use the complete formatting information.
*/
@Test
public void testFormattingChangesWithReuseAtBlockBoundaries() throws Exception {
String content = " // Leading \n" +
" Definitions: def a; Usages: use a; // Line comment \n" +
" // Trailing ";
model.replace(0, model.getLength(), content);
triggerParser();
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject(), getRootBlock());
assertEqualsByLines(reprinted.getCachedString(), content);
}
@Test
public void testFormattingChangesReuseAtBlockBoundariesWithBrokenMapping() throws Exception {
String content = " // Leading \n" +
" Definitions: def a; Usages: use a; // Line comment \n" +
" // Trailing ";
model.replace(0, model.getLength(), content);
triggerParser();
breakMapping(getRootBlock());
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject(), getRootBlock());
assertEqualsByLines(reprinted.getCachedString(), content);
}
/**
* There is an alternativ way to reference definitions named xxFOOxx.
* Without re-use the pretty printer has to default to the first choice.
*/
@Test
public void testAlternativeChoice() throws Exception {
String content = "Definitions : def xxFOOxx ; Usages : useXX FOO ; use xxFOOxx ;";
String expected = "Definitions : def xxFOOxx ; Usages : use xxFOOxx ; use xxFOOxx ;";
model.replace(0, model.getLength(), content);
triggerParser();
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject());
assertEqualsByLines(reprinted.getCachedString(), expected);
}
/**
* There is an alternativ way to reference definitions named xxFOOxx.
* The pretty printer should re-use the alternative choice that was used
* in the existing textblock.
*/
@Test
public void testAlternativeChoiceWithReuse() throws Exception {
String content = "Definitions : def xxFOOxx ; Usages : useXX FOO ; use xxFOOxx ;";
model.replace(0, model.getLength(), content);
triggerParser();
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject(), getRootBlock());
assertEqualsByLines(reprinted.getCachedString(), content);
}
/**
* Should work as currently alt choice indizes are encoded as integers and not as
* references to model elements.
*/
@Test
public void testAlternativeChoiceReuseWithBrokenMapping() throws Exception {
String content = "Definitions : def xxFOOxx ; Usages : useXX FOO ; use xxFOOxx ;";
model.replace(0, model.getLength(), content);
triggerParser();
breakMapping(getRootBlock());
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject(), getRootBlock());
assertEqualsByLines(reprinted.getCachedString(), content);
}
@Test
public void testAlternativeChoiceWithFormattingAndReuse() throws Exception {
String content = "Definitions:\n " +
" def xxFOOxx;\n" +
"Usages: \n " +
" useXX FOO;\n" +
" use xxFOOxx;";
model.replace(0, model.getLength(), content);
triggerParser();
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject(), getRootBlock());
assertEqualsByLines(reprinted.getCachedString(), content);
}
@Test
public void testReprintAfterDeleteSecond() throws Exception {
String content = "Definitions: \n \n" +
" def a;\n" +
" def b;\n" +
" def c;\n" +
"Usages:";
String expected = "Definitions: \n \n" +
" def a;\n" +
" def c;\n" +
"Usages:";
model.replace(0, model.getLength(), content);
triggerParser();
// Delete the "def b"
EList<EObject> definitions = getDefinitions(getRootObject());
definitions.remove(1);
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject(), getRootBlock());
assertEqualsByLines(reprinted.getCachedString(), expected);
}
/**
* Delete the first definition. This is a bit harder, as we want to keep
* the two line breaks after "Definitions:"
*/
@Test
@Ignore("Proper fix would require a reworking of how we assign/distribute"+
"Whitespaces to adjacent TextBlocks")
public void testReprintAfterDeleteFirst() throws Exception {
String content = "Definitions: \n \n" +
" def a;\n" +
" def b;\n" +
" def c;\n" +
"Usages:";
String expected = "Definitions: \n \n" +
" def b;\n" +
" def c;\n" +
"Usages:";
model.replace(0, model.getLength(), content);
triggerParser();
// Delete the "def a"
EList<EObject> definitions = getDefinitions(getRootObject());
definitions.remove(0);
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject(), getRootBlock());
assertEqualsByLines(reprinted.getCachedString(), expected);
}
@Test
public void testReprintAfterInsert() throws Exception {
String content = "Definitions: \n" +
" def a;\n" +
" def b;\n" +
" def c;\n" +
"Usages:";
String expected ="Definitions: \n" +
" def a; def d ;\n" + // default syntax (which is used for def d) does not define newlines.
" def b;\n" +
" def c;\n" +
"Usages:";
model.replace(0, model.getLength(), content);
triggerParser();
// Insert "def d" after "def a"
EList<EObject> definitions = getDefinitions(getRootObject());
EClass defClass = definitions.get(0).eClass();
EObject def = defClass.getEPackage().getEFactoryInstance().create(defClass);
def.eSet(defClass.getEStructuralFeature("name"), "d");
definitions.add(1, def);
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject(), getRootBlock());
assertEqualsByLines(reprinted.getCachedString(), expected);
}
/**
* Delete a definition. The reference in the usage will
* thus be broken on re-print.
*/
@Test
public void testReprintWithBrokenReference() throws Exception {
String content = "Definitions : def a ; Usages : use a ;";
String expected = "Definitions : Usages : use ;";
model.replace(0, model.getLength(), content);
triggerParser();
// Delete the "def a"
EList<EObject> definitions = getDefinitions(getRootObject());
definitions.remove(0);
EList<EObject> usages = getUsages(getRootObject());
usages.get(0).eUnset(usages.get(0).eClass().getEStructuralFeature("boundDefinition"));
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject());
assertEqualsByLines(reprinted.getCachedString(), expected);
}
/**
* Delete a definition but make sure the pretty pritner keeps
* the id used to reference the definition.
*/
@Test
public void testReprintWithBrokenReferenceAndRecovery() throws Exception {
String content = "Definitions: def a; Usages: use a;";
String expected = "Definitions: Usages: use a;";
model.replace(0, model.getLength(), content);
triggerParser();
// Delete the "def a"
EList<EObject> definitions = getDefinitions(getRootObject());
definitions.remove(0);
EList<EObject> usages = getUsages(getRootObject());
usages.get(0).eUnset(usages.get(0).eClass().getEStructuralFeature("boundDefinition"));
TextBlock reprinted = prettyPrinter.prettyPrint(getRootObject(), getRootBlock());
assertEqualsByLines(reprinted.getCachedString(), expected);
}
@SuppressWarnings("unchecked")
private EList<EObject> getDefinitions(EObject root) {
return (EList<EObject>) root.eGet(root.eClass().getEStructuralFeature("definitions"));
}
@SuppressWarnings("unchecked")
private EList<EObject> getUsages(EObject root) {
return (EList<EObject>) root.eGet(root.eClass().getEStructuralFeature("usages"));
}
private TextBlock getRootBlock() {
return model.getRoot();
}
private EObject getRootObject() {
return model.getRoot().getCorrespondingModelElements().iterator().next();
}
/**
* Break all links from the TextBlocks model to the syntax mapping.
*/
private void breakMapping(TextBlock rootBlock) {
rootBlock.setType(null);
rootBlock.setSequenceElement(null);
for (DocumentNode node : rootBlock.getSubNodes()) {
node.setSequenceElement(null);
if (node instanceof TextBlock) {
breakMapping((TextBlock) node);
}
}
}
}