/******************************************************************************* * 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; import static com.sap.furcas.prettyprinter.SyntaxCreationHelper.createKeyword; import static com.sap.furcas.prettyprinter.SyntaxCreationHelper.createProperty; import static com.sap.furcas.prettyprinter.SyntaxCreationHelper.createSequence; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.ENamedElement; import org.eclipse.emf.ecore.EParameter; import org.eclipse.emf.ecore.EcoreFactory; import org.eclipse.emf.ecore.EcorePackage; import org.junit.Test; import com.sap.furcas.emf.stubs.EcoreAnyStub; import com.sap.furcas.metamodel.FURCAS.TCS.Block; import com.sap.furcas.metamodel.FURCAS.TCS.ClassTemplate; import com.sap.furcas.metamodel.FURCAS.TCS.CustomSeparator; import com.sap.furcas.metamodel.FURCAS.TCS.EndNLBArg; import com.sap.furcas.metamodel.FURCAS.TCS.ForcedLowerPArg; import com.sap.furcas.metamodel.FURCAS.TCS.IndentIncrBArg; import com.sap.furcas.metamodel.FURCAS.TCS.Literal; import com.sap.furcas.metamodel.FURCAS.TCS.LiteralRef; import com.sap.furcas.metamodel.FURCAS.TCS.NbNLBArg; import com.sap.furcas.metamodel.FURCAS.TCS.PrimitiveTemplate; import com.sap.furcas.metamodel.FURCAS.TCS.Property; import com.sap.furcas.metamodel.FURCAS.TCS.SeparatorPArg; import com.sap.furcas.metamodel.FURCAS.TCS.Sequence; import com.sap.furcas.metamodel.FURCAS.TCS.SpaceKind; import com.sap.furcas.metamodel.FURCAS.TCS.StartNLBArg; import com.sap.furcas.metamodel.FURCAS.TCS.Symbol; import com.sap.furcas.metamodel.FURCAS.TCS.TCSFactory; import com.sap.furcas.metamodel.FURCAS.textblocks.AbstractToken; import com.sap.furcas.metamodel.FURCAS.textblocks.DocumentNode; import com.sap.furcas.metamodel.FURCAS.textblocks.LexedToken; import com.sap.furcas.metamodel.FURCAS.textblocks.TextBlock; import com.sap.furcas.parser.tcs.TCSParserFactory; import com.sap.furcas.prettyprinter.context.InitialPrintContext; import com.sap.furcas.prettyprinter.context.PrintResult; import com.sap.furcas.prettyprinter.exceptions.ForcedBoundMismatchException; import com.sap.furcas.prettyprinter.exceptions.SyntaxMismatchException; import com.sap.furcas.prettyprinter.policy.DefaultPrintPolicy; import com.sap.furcas.prettyprinter.stubs.MockContextTemplateFinder; import com.sap.furcas.prettyprinter.stubs.MockContextTemplateHandler; /** * @author Stephan Erb * */ public class TestSequenceHandler { private static TCSFactory tcsFactory = TCSFactory.eINSTANCE; private static TextBlocksFactory tbfactory = new TextBlocksFactory(new TCSParserFactory()); private static Formatter formatter = new Formatter(tbfactory); /** * Print two literals and assert the correspondingly created lexed tokens. */ @Test public void testSerializeLiteralRefSymbol() throws SyntaxMismatchException { LiteralRef litRef1 = tcsFactory.createLiteralRef(); Symbol lit1 = tcsFactory.createSymbol(); lit1.getSpaces().add(SpaceKind.RIGHT_NONE); litRef1.setReferredLiteral(lit1); lit1.setValue("first"); LiteralRef litRef2 = tcsFactory.createLiteralRef(); Symbol lit2 = tcsFactory.createSymbol(); lit1.getSpaces().add(SpaceKind.LEFT_NONE); litRef2.setReferredLiteral(lit2); lit2.setValue("second"); SequenceHandler handler = createSequenceHandlerForLiteralRef(); PrintResult result = handler.serializeSequence(new EcoreAnyStub(), createSequence(litRef1, litRef2), new InitialPrintContext(), new DefaultPrintPolicy()); assertEquals(2, result.getNodes().size()); LexedToken first = (LexedToken) result.getNodes().get(0); LexedToken second = (LexedToken) result.getNodes().get(1); assertEquals("first", first.getValue()); assertEquals("first".length(), first.getLength()); assertEquals("Should start at the beginning", 0, first.getOffset()); assertSame(litRef1, first.getSequenceElement()); assertEquals("second", second.getValue()); assertEquals("second".length(), second.getLength()); assertEquals("Should follow the first token", first.getOffset()+first.getLength(), second.getOffset()); assertSame(litRef2, second.getSequenceElement()); } /** * Same as {@link #testSerializeLiteralRefSymbol()} but this time the symbols have been configured * to add a space in between them */ @Test public void testSerializeLiteralRefSymbolWithSimbolConfiguredSpace() throws SyntaxMismatchException { LiteralRef litRef1 = tcsFactory.createLiteralRef(); Symbol lit1 = tcsFactory.createSymbol(); lit1.getSpaces().add(SpaceKind.RIGHT_SPACE); litRef1.setReferredLiteral(lit1); lit1.setValue("first"); LiteralRef litRef2 = tcsFactory.createLiteralRef(); Symbol lit2 = tcsFactory.createSymbol(); lit2.getSpaces().add(SpaceKind.LEFT_SPACE); litRef2.setReferredLiteral(lit2); lit2.setValue("second"); Sequence sequence = createSequence(litRef1, litRef2); SequenceHandler handler = createSequenceHandlerForLiteralRef(); PrintResult result = handler.serializeSequence(new EcoreAnyStub(), sequence, new InitialPrintContext(), new DefaultPrintPolicy()); // Check for correct overall text StringBuilder content = new StringBuilder(); for (DocumentNode node : result.getNodes()) { content.append(((AbstractToken)node).getValue()); } assertEquals("first second", content.toString()); // Check for correct offset handling assertEquals(2+1, result.getNodes().size()); for (DocumentNode node : result.getNodes()) { AbstractToken token = (AbstractToken) node; assertEquals(token.getValue(), content.substring(token.getOffset(), token.getOffset()+ token.getLength())); } } /** * Same as {@link #testSerializeLiteralRefSymbol()} but this time force that a space, a tab and a newline * are added between those two literals. */ @Test public void testSerializeLiteralRefSymbolWithCustomSpace() throws SyntaxMismatchException { LiteralRef litRef1 = tcsFactory.createLiteralRef(); Symbol lit1 = tcsFactory.createSymbol(); lit1.getSpaces().add(SpaceKind.RIGHT_NONE); litRef1.setReferredLiteral(lit1); lit1.setValue("first"); LiteralRef litRef2 = tcsFactory.createLiteralRef(); Symbol lit2 = tcsFactory.createSymbol(); lit2.getSpaces().add(SpaceKind.LEFT_NONE); litRef2.setReferredLiteral(lit2); lit2.setValue("second"); // the custom spacing between the two literals CustomSeparator spaceSep = tcsFactory.createCustomSeparator(); spaceSep.setName("space"); CustomSeparator tabSep = tcsFactory.createCustomSeparator(); tabSep.setName("tab"); CustomSeparator newlineSep = tcsFactory.createCustomSeparator(); newlineSep.setName("newline"); Sequence sequence = createSequence(litRef1, spaceSep, tabSep, newlineSep, litRef2); SequenceHandler handler = createSequenceHandlerForLiteralRef(); PrintResult result = handler.serializeSequence(new EcoreAnyStub(), sequence, new InitialPrintContext(), new DefaultPrintPolicy()); // Check for correct overall text StringBuilder content = new StringBuilder(); for (DocumentNode node : result.getNodes()) { content.append(((AbstractToken)node).getValue()); } assertEquals("first \t\nsecond", content.toString()); // Check for correct offset handling assertEquals(2+3, result.getNodes().size()); for (DocumentNode node : result.getNodes()) { AbstractToken token = (AbstractToken) node; assertEquals(token.getValue(), content.substring(token.getOffset(), token.getOffset()+ token.getLength())); } } /** * Similar to {@link #testSerializeLiteralRefSymbolWithCustomSpace()} but using keywords, which require auto-added * spacing for disambiguation. */ @Test public void testSerializeLiteralRefKeywordWithAutoSpace() throws SyntaxMismatchException { LiteralRef litRef1 = createKeyword("first"); LiteralRef litRef2 = createKeyword("second"); Sequence sequence = createSequence(litRef1, litRef2); SequenceHandler handler = createSequenceHandlerForLiteralRef(); PrintResult result = handler.serializeSequence(new EcoreAnyStub(), sequence, new InitialPrintContext(), new DefaultPrintPolicy()); // Check for correct overall text StringBuilder content = new StringBuilder(); for (DocumentNode node : result.getNodes()) { content.append(((AbstractToken)node).getValue()); } assertEquals("first second", content.toString()); // Check for correct offset handling assertEquals(3, result.getNodes().size()); for (DocumentNode node : result.getNodes()) { AbstractToken token = (AbstractToken) node; assertEquals(token.getValue(), content.substring(token.getOffset(), token.getOffset()+ token.getLength())); } } /** * Print a property with a primitive type, which should result in a lexed token. */ @Test public void testSerializePropertyPrimitive() throws SyntaxMismatchException { // Hardwire a template finder to always return a specific template. // Then later, expect the string property to be serialized according to this template. final PrimitiveTemplate template = tcsFactory.createPrimitiveTemplate(); template.setTemplateName("MyStringTemplate"); template.setSerializer("\"%value%\""); TemplateFinder templateFinder = new TemplateFinder(/*syntax lookup*/ null, /*metamodel lookup*/ null) { @Override public PrimitiveTemplate findPrimitiveTemplate(Property seqElem) { return template; }; }; SequenceHandler handler = createSequenceHandlerForProperty(templateFinder, new TemplateHandler(tbfactory, formatter, /*syntax lookuk*/ null)); // The model elment to be printed ENamedElement modelElement = EcoreFactory.eINSTANCE.createEClass(); modelElement.setName("MyModelElementName"); // The property pointing to the name feature configured above Property property = createProperty(EcorePackage.eINSTANCE.getENamedElement_Name()); PrintResult result = handler.serializeSequence(modelElement, createSequence(property), new InitialPrintContext(), new DefaultPrintPolicy()); assertEquals(1, result.getNodes().size()); LexedToken token = (LexedToken) result.getNodes().get(0); assertEquals("Should be name with quotes", "\"MyModelElementName\"", token.getValue()); assertEquals("\"MyModelElementName\"".length(), token.getLength()); assertEquals("Should start at the beginning", 0, token.getOffset()); assertSame(property, token.getSequenceElement()); } /** * Print a property with an EObject type, which should result in a call to * the template handler. */ @Test public void testSerializePropertyEObject() throws SyntaxMismatchException { ClassTemplate templateForTypeOfParameter = tcsFactory.createClassTemplate(); templateForTypeOfParameter.setMetaReference(EcorePackage.Literals.ECLASSIFIER); // Hardwire a template finder to always return a specific template. TemplateFinder templateFinder = new MockContextTemplateFinder(templateForTypeOfParameter); // Hardwire the template handler to behave as if it has just serialized the given template TemplateHandler templatehandler = new MockContextTemplateHandler(tbfactory, templateForTypeOfParameter); // The model elment to be printed: A EParameter, it has a EType EParameter modelElement = EcoreFactory.eINSTANCE.createEParameter(); modelElement.setName("MyParameterWithAType"); modelElement.setEType(EcoreFactory.eINSTANCE.createEClass()); // The property pointing to the eType feature of the eParameter created above. Property property = createProperty(EcorePackage.eINSTANCE.getETypedElement_EType()); SequenceHandler handler = createSequenceHandlerForProperty(templateFinder, templatehandler); PrintResult result = handler.serializeSequence(modelElement, createSequence(property), new InitialPrintContext(), new DefaultPrintPolicy()); assertEquals(1, result.getNodes().size()); TextBlock textBlock = (TextBlock) result.getNodes().get(0); assertNotNull(textBlock); } /** * Print a property with a Collection<EAttribute> type, which should result in a call to * the template handler for each element in this collection. */ @Test public void testSerializePropertyCollection() throws SyntaxMismatchException { ClassTemplate templateForEAttribute = tcsFactory.createClassTemplate(); templateForEAttribute.setMetaReference(EcorePackage.Literals.EATTRIBUTE); // Hardwire a template finder to always return a specific template. TemplateFinder templateFinder = new MockContextTemplateFinder(templateForEAttribute); // Hardwire the template handler to behave as if it has just serialized the given template TemplateHandler templatehandler = new MockContextTemplateHandler(tbfactory, templateForEAttribute); // The model elment to be printed: A EClass, it holds EAttributes. EClass modelElement = EcoreFactory.eINSTANCE.createEClass(); modelElement.setName("MyClassWithAttributes"); for (int i=0; i<10; i++) { modelElement.getEAttributes().add(EcoreFactory.eINSTANCE.createEAttribute()); } // The property pointing to the attribute feature of the eClass above. Property property = createProperty(EcorePackage.eINSTANCE.getEClass_EAttributes()); SequenceHandler handler = createSequenceHandlerForProperty(templateFinder, templatehandler); PrintResult result = handler.serializeSequence(modelElement, createSequence(property), new InitialPrintContext(), new DefaultPrintPolicy()); assertEquals("One for each attribute", 10, result.getNodes().size()); } /** * Same as {@link #testSerializePropertyCollection()} but with a separator between * individual collection items */ @Test public void testSerializePropertyCollectionWithSeparator() throws SyntaxMismatchException { ClassTemplate templateForEAttribute = tcsFactory.createClassTemplate(); templateForEAttribute.setMetaReference(EcorePackage.Literals.EATTRIBUTE); // Hardwire a template finder to always return a specific template. TemplateFinder templateFinder = new MockContextTemplateFinder(templateForEAttribute); // Hardwire the template handler to behave as if it has just serialized the given template TemplateHandler templatehandler = new MockContextTemplateHandler(tbfactory, templateForEAttribute); // The model elment to be printed: A EClass, it holds EAttributes. EClass modelElement = EcoreFactory.eINSTANCE.createEClass(); modelElement.setName("MyClassWithAttributes"); for (int i=0; i<10; i++) { modelElement.getEAttributes().add(EcoreFactory.eINSTANCE.createEAttribute()); } // The property pointing to the attribute feature of the eClass above. Property property = createProperty(EcorePackage.eINSTANCE.getEClass_EAttributes()); // Create the separator and add it to the property created above LiteralRef litRef = tcsFactory.createLiteralRef(); Literal separator = tcsFactory.createSymbol(); litRef.setReferredLiteral(separator); separator.setValue(";"); SeparatorPArg pArg = tcsFactory.createSeparatorPArg(); pArg.setSeparatorSequence(createSequence(litRef)); property.getPropertyArgs().add(pArg); SequenceHandler handler = createSequenceHandlerForProperty(templateFinder, templatehandler); PrintResult result = handler.serializeSequence(modelElement, createSequence(property), new InitialPrintContext(), new DefaultPrintPolicy()); assertEquals("One for each attribute and the seaprators between them, which is size-1", 2*modelElement.getEAttributes().size()-1, result.getNodes().size()); } /** * Same as {@link #testSerializePropertyCollection()} but with a forcedLowerPArg missmatch, * forcing the serialization to be aborted */ @Test(expected=ForcedBoundMismatchException.class) public void testSerializePropertyCollectionWithBoundMismatch() throws SyntaxMismatchException { ClassTemplate templateForEAttribute = tcsFactory.createClassTemplate(); templateForEAttribute.setMetaReference(EcorePackage.Literals.EATTRIBUTE); // Hardwire a template finder to always return a specific template. TemplateFinder templateFinder = new MockContextTemplateFinder(templateForEAttribute); // Hardwire the template handler to behave as if it has just serialized the given template TemplateHandler templatehandler = new MockContextTemplateHandler(tbfactory, templateForEAttribute); // The model elment to be printed: A EClass, it holds EAttributes. EClass modelElement = EcoreFactory.eINSTANCE.createEClass(); modelElement.setName("MyClassWithAttributes"); for (int i=0; i<2; i++) { modelElement.getEAttributes().add(EcoreFactory.eINSTANCE.createEAttribute()); } // The property pointing to the attribute feature of the eClass above. Property property = createProperty(EcorePackage.eINSTANCE.getEClass_EAttributes()); ForcedLowerPArg pArg = tcsFactory.createForcedLowerPArg(); pArg.setValue(3); property.getPropertyArgs().add(pArg); SequenceHandler handler = createSequenceHandlerForProperty(templateFinder, templatehandler); handler.serializeSequence(modelElement, createSequence(property), new InitialPrintContext(), new DefaultPrintPolicy()); // Expected are 3, but the collection has only 2 elements. fail("Should never come this far"); } /** * Same as {@link #testSerializePropertyCollectionWithBoundMismatch()} but this time the * property is partial and does not lead to subsequent errors. */ @Test public void testSerializePropertyCollectionWithIgnoredBoundMismatch() throws SyntaxMismatchException { ClassTemplate templateForEAttribute = tcsFactory.createClassTemplate(); templateForEAttribute.setMetaReference(EcorePackage.Literals.EATTRIBUTE); // Hardwire a template finder to always return a specific template. TemplateFinder templateFinder = new MockContextTemplateFinder(templateForEAttribute); // Hardwire the template handler to behave as if it has just serialized the given template TemplateHandler templatehandler = new MockContextTemplateHandler(tbfactory, templateForEAttribute); // The model elment to be printed: A EClass, it holds EAttributes. EClass modelElement = EcoreFactory.eINSTANCE.createEClass(); modelElement.setName("MyClassWithAttributes"); for (int i=0; i<2; i++) { modelElement.getEAttributes().add(EcoreFactory.eINSTANCE.createEAttribute()); } // The property pointing to the attribute feature of the eClass above. Property property = createProperty(EcorePackage.eINSTANCE.getEClass_EAttributes()); ForcedLowerPArg pArg = tcsFactory.createForcedLowerPArg(); pArg.setValue(3); property.getPropertyArgs().add(pArg); property.getPropertyArgs().add(tcsFactory.createPartialPArg()); SequenceHandler handler = createSequenceHandlerForProperty(templateFinder, templatehandler); PrintResult result = handler.serializeSequence(modelElement, createSequence(property), new InitialPrintContext(), new DefaultPrintPolicy()); assertEquals("No excption, but still nothing should have been created", 0, result.getNodes().size()); } /** * Serialize keywords within a block, thus each should be on its own line. */ @Test public void testSerializeBlock() throws SyntaxMismatchException { LiteralRef litRefBefore = createKeyword("beforeBlock"); LiteralRef litRef1 = createKeyword("firstInBlock"); LiteralRef litRef2 = createKeyword("secondInBlock"); LiteralRef litRefAfter = createKeyword("afterBlock"); Block block = tcsFactory.createBlock(); block.setBlockSequence(createSequence(litRef1, litRef2)); Sequence sequence = createSequence(litRefBefore, block, litRefAfter); SequenceHandler handler = createSequenceHandlerForLiteralRef(); PrintResult result = handler.serializeSequence(new EcoreAnyStub(), sequence, new InitialPrintContext(), new DefaultPrintPolicy()); // Check for correct overall text StringBuilder content = new StringBuilder(); for (DocumentNode node : result.getNodes()) { content.append(((AbstractToken)node).getValue()); } assertEquals( "beforeBlock\n" + " firstInBlock\n" + " secondInBlock\n" + "afterBlock", content.toString()); } /** * Like {@link #testSerializeBlock()} but use some special block formatting features */ @Test public void testSerializeBlockWithFormatting() throws SyntaxMismatchException { LiteralRef litRefBefore = createKeyword("beforeBlock"); LiteralRef litRef1 = createKeyword("firstInBlock"); LiteralRef litRef2 = createKeyword("secondInBlock"); LiteralRef litRefAfter = createKeyword("afterBlock"); Block block = tcsFactory.createBlock(); block.setBlockSequence(createSequence(litRef1, litRef2)); Sequence sequence = createSequence(litRefBefore, block, litRefAfter); // indent inner blocks twice IndentIncrBArg indentInc = tcsFactory.createIndentIncrBArg(); indentInc.setValue(2); block.getBlockArgs().add(indentInc); // don't start the block with a newline StartNLBArg startWithNewLine = tcsFactory.createStartNLBArg(); startWithNewLine.setValue(false); block.getBlockArgs().add(startWithNewLine); // don't end the block with a newline EndNLBArg endWithNewLine = tcsFactory.createEndNLBArg(); endWithNewLine.setValue(false); block.getBlockArgs().add(endWithNewLine); // between inner block elements, always add two newlines NbNLBArg numOfNewlinesWithinTheBlock = tcsFactory.createNbNLBArg(); numOfNewlinesWithinTheBlock.setValue(2); block.getBlockArgs().add(numOfNewlinesWithinTheBlock); SequenceHandler handler = createSequenceHandlerForLiteralRef(); PrintResult result = handler.serializeSequence(new EcoreAnyStub(), sequence, new InitialPrintContext(), new DefaultPrintPolicy()); // Check for correct overall text StringBuilder content = new StringBuilder(); for (DocumentNode node : result.getNodes()) { content.append(((AbstractToken)node).getValue()); } assertEquals( "beforeBlock firstInBlock\n" + " \n" + " secondInBlock afterBlock", content.toString()); } private static SequenceHandler createSequenceHandlerForLiteralRef() { SequenceHandler handler = new SequenceHandler(tbfactory, /* tempate finder*/ null, /*template handler*/ null, /* ocl evaluator*/ null, new SequenceElementValidator(/*ocl evaluator*/ null), formatter); return handler; } private static SequenceHandler createSequenceHandlerForProperty(TemplateFinder templateFinder, TemplateHandler templatehandler) { SequenceHandler handler = new SequenceHandler(tbfactory, templateFinder, templatehandler, /* ocl evaluator*/ null, new SequenceElementValidator(/*ocl evaluator*/ null), formatter); return handler; } }