/*******************************************************************************
* 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.TextBlocksFactory.getLengthOf;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.eclipse.emf.ecore.EObject;
import com.sap.furcas.metamodel.FURCAS.TCS.Alternative;
import com.sap.furcas.metamodel.FURCAS.TCS.Block;
import com.sap.furcas.metamodel.FURCAS.TCS.ConditionalElement;
import com.sap.furcas.metamodel.FURCAS.TCS.ContextTemplate;
import com.sap.furcas.metamodel.FURCAS.TCS.CustomSeparator;
import com.sap.furcas.metamodel.FURCAS.TCS.EndNLBArg;
import com.sap.furcas.metamodel.FURCAS.TCS.FunctionCall;
import com.sap.furcas.metamodel.FURCAS.TCS.IndentIncrBArg;
import com.sap.furcas.metamodel.FURCAS.TCS.InjectorAction;
import com.sap.furcas.metamodel.FURCAS.TCS.InjectorActionsBlock;
import com.sap.furcas.metamodel.FURCAS.TCS.Literal;
import com.sap.furcas.metamodel.FURCAS.TCS.LiteralRef;
import com.sap.furcas.metamodel.FURCAS.TCS.LookupPropertyInit;
import com.sap.furcas.metamodel.FURCAS.TCS.NbNLBArg;
import com.sap.furcas.metamodel.FURCAS.TCS.PrimitivePropertyInit;
import com.sap.furcas.metamodel.FURCAS.TCS.PrimitiveTemplate;
import com.sap.furcas.metamodel.FURCAS.TCS.Property;
import com.sap.furcas.metamodel.FURCAS.TCS.RefersToPArg;
import com.sap.furcas.metamodel.FURCAS.TCS.SeparatorPArg;
import com.sap.furcas.metamodel.FURCAS.TCS.Sequence;
import com.sap.furcas.metamodel.FURCAS.TCS.SequenceElement;
import com.sap.furcas.metamodel.FURCAS.TCS.SequenceInAlternative;
import com.sap.furcas.metamodel.FURCAS.TCS.SpaceKind;
import com.sap.furcas.metamodel.FURCAS.TCS.StartNLBArg;
import com.sap.furcas.metamodel.FURCAS.TCS.StartNbNLBArg;
import com.sap.furcas.metamodel.FURCAS.TCS.Symbol;
import com.sap.furcas.metamodel.FURCAS.TCS.TCSPackage;
import com.sap.furcas.metamodel.FURCAS.textblocks.DocumentNode;
import com.sap.furcas.metamodel.FURCAS.textblocks.LexedToken;
import com.sap.furcas.prettyprinter.Formatter.FormatRequest;
import com.sap.furcas.prettyprinter.Formatter.Type;
import com.sap.furcas.prettyprinter.context.PrintContext;
import com.sap.furcas.prettyprinter.context.PrintResult;
import com.sap.furcas.prettyprinter.context.PrintResult.LeafResult;
import com.sap.furcas.prettyprinter.context.PrintResult.NullResult;
import com.sap.furcas.prettyprinter.context.PrintResult.ResultContainer;
import com.sap.furcas.prettyprinter.exceptions.AlternativeChoiceMismatch;
import com.sap.furcas.prettyprinter.exceptions.NoMatchingTemplateException;
import com.sap.furcas.prettyprinter.exceptions.SyntaxMismatchException;
import com.sap.furcas.prettyprinter.policy.PrintPolicy;
import com.sap.furcas.prettyprinter.utils.EMFModelInspector;
import com.sap.furcas.prettyprinter.utils.TCSConditionEvaluator;
import com.sap.furcas.runtime.common.exceptions.ModelAdapterException;
import com.sap.furcas.runtime.common.util.TCSSpecificOCLEvaluator;
import com.sap.furcas.runtime.tcs.BlockArgumentUtil;
import com.sap.furcas.runtime.tcs.PropertyArgumentUtil;
import com.sap.furcas.runtime.tcs.TcsUtil;
import com.sap.furcas.runtime.textblocks.shortprettyprint.PrettyPrinterUtil;
/**
* This is the primary serialization component of the {@link PrettyPrinter}. It pretty prints
* {@link Sequence Sequences} and their contained {@link SequenceElement}s to lists of formatted
* {@link DocumentNode}s. These {@link DocumentNode}s are passed around in form of {@link PrintResult}s.
*
* The class is called by {@link TemplateHandler} in order to print template sequences.
*
* @author Stephan Erb
*
*/
public class SequenceHandler {
private final TextBlocksFactory tbFactory;
private final TemplateHandler templateHandler;
private final TemplateFinder templateFinder;
private final TCSSpecificOCLEvaluator oclEvaluator;
private final SequenceElementValidator seValidator;
private final Formatter formatter;
public SequenceHandler(TextBlocksFactory factory, TemplateFinder templateFinder, TemplateHandler templateHandler,
TCSSpecificOCLEvaluator oclEvaluator, SequenceElementValidator validator, Formatter formatter) {
this.tbFactory = factory;
this.templateFinder = templateFinder;
this.templateHandler = templateHandler;
this.oclEvaluator = oclEvaluator;
this.seValidator = validator;
this.formatter = formatter;
}
/**
* Serailize sequences such as: "keyword" "{" propertyName "}" {{property=1}}
*/
public final ResultContainer serializeSequence(EObject modelElement, Sequence sequence, PrintContext context, PrintPolicy policy) throws SyntaxMismatchException {
ResultContainer result = new ResultContainer(context.getPendingFormattingRequest());
result.configureFormattingBetweenBlockElements(context.getBlockFormattingBetweenElements());
Iterator<SequenceElement> iter = sequence.getElements().iterator();
while (iter.hasNext()) {
SequenceElement seqElem = iter.next();
PrintResult subResult = serializeSequenceElement(modelElement, seqElem, result.asSubContext(context), policy);
if (iter.hasNext() && subResult.hasSyntacticContribution()) {
subResult.appendFormatRequests(context.getBlockFormattingBetweenElements());
}
result.merge(subResult);
}
// if the the last sequence element did not have a syntax contribution we have
// added block formatting that has not yet been consumed. To prevent it from
// leaking out, we have to remove it.
result.removeFormatRequests(context.getBlockFormattingBetweenElements());
return result;
}
/**
* Serialize the individual elements of a sequence such as: "keyword", "{", or propertyName
*/
public final PrintResult serializeSequenceElement(EObject modelElement, SequenceElement seqElem, PrintContext context,
PrintPolicy policy) throws SyntaxMismatchException {
switch (seqElem.eClass().getClassifierID()) {
case TCSPackage.PROPERTY:
Object value = TcsUtil.getPropertyValue(modelElement, ((Property) seqElem).getPropertyReference());
return serializeProperty(modelElement, (Property) seqElem, value, context, policy);
case TCSPackage.LITERAL_REF:
return serializeLiteral(((LiteralRef) seqElem).getReferredLiteral(), seqElem, context, policy);
case TCSPackage.ALTERNATIVE:
return serializeAlternative(modelElement, (Alternative) seqElem, context, policy);
case TCSPackage.BLOCK:
return serializeBlock(modelElement, (Block) seqElem, context, policy);
case TCSPackage.FUNCTION_CALL:
// create subcontext to clear formatting between elements
ResultContainer result = new ResultContainer(context.getPendingFormattingRequest());
result.merge(serializeSequence(modelElement, ((FunctionCall) seqElem).getCalledFunction().getFunctionSequence(),
result.asSubContext(context), policy));
return result;
case TCSPackage.CONDITIONAL_ELEMENT:
return serializeConditionalElement(modelElement, (ConditionalElement) seqElem, context, policy);
case TCSPackage.CUSTOM_SEPARATOR:
return serializeCustomSeparator((CustomSeparator) seqElem, context);
case TCSPackage.INJECTOR_ACTIONS_BLOCK:
return validateInjectorActionsBlock(modelElement, (InjectorActionsBlock) seqElem, context);
default:
Activator.logger.logError("Unable to serialize unknwon sequence elmenet of type "
+ EMFModelInspector.getTypeName(seqElem) + ". Skipping.");
return new NullResult();
}
}
/**
* Serialize keywords (e.g., "class") or symbols (e.g., ";", "{", ...).
*/
public final PrintResult serializeLiteral(Literal literal, SequenceElement seqElem, PrintContext context, PrintPolicy policy) {
List<DocumentNode> formatting = formatter.translateToTokens(getLeadingSymbolFormatting(literal, seqElem, context, policy), context);
formatting.add(tbFactory.createLexedToken(literal.getValue(), seqElem,
getLengthOf(formatting, context.getNextOffset())));
LeafResult result = new LeafResult(formatting);
appendFollowingSymbolFormatting(literal, result);
return result;
}
private List<FormatRequest> getLeadingSymbolFormatting(Literal literal, SequenceElement seqElem, PrintContext context, PrintPolicy policy) {
List<FormatRequest> formatRequests = new ArrayList<FormatRequest>(context.getPendingFormattingRequest());
if (literal instanceof Symbol) {
Symbol symbol = (Symbol) literal;
if (symbol.getSpaces().contains(SpaceKind.LEFT_NONE)) {
formatRequests.add(FormatRequest.create(Type.SKIP_SPACE));
} else if (symbol.getSpaces().contains(SpaceKind.LEFT_SPACE)) {
formatRequests.add(FormatRequest.create(Type.ADD_SPACE));
}
} else {
formatRequests.add(FormatRequest.create(Type.ADD_OPTIONAL_SPACE));
}
return policy.getOverruledFormattingBetween(formatRequests, context.getLastSequenceElement(), seqElem, literal.getValue());
}
private void appendFollowingSymbolFormatting(Literal literal, LeafResult result) {
if (literal instanceof Symbol) {
Symbol symbol = (Symbol) literal;
if (symbol.getSpaces().contains(SpaceKind.RIGHT_SPACE)) {
result.appendFormatRequest(FormatRequest.create(Type.ADD_SPACE));
} else if (symbol.getSpaces().contains(SpaceKind.RIGHT_NONE)) {
result.appendFormatRequest(FormatRequest.create(Type.SKIP_SPACE));
} else {
result.appendFormatRequest(FormatRequest.create(Type.SKIP_OPTIONAL_SPACE));
}
}
}
/**
* Serialize the given property of the model element
*/
private PrintResult serializeProperty(EObject modelElement, Property seqElem, Object value, PrintContext context,
PrintPolicy policy) throws SyntaxMismatchException {
try {
seValidator.validateBounds(seqElem, value);
if (isPrimitive(value)) {
return templateHandler.serializePrimitiveTemplate(value, seqElem,
templateFinder.findPrimitiveTemplate(seqElem), context, policy);
} else if (value instanceof Enum) {
return templateHandler.serializeEnumerationTemplate(modelElement, seqElem, (Enum<?>) value,
templateFinder.findEnumerationTemplate(seqElem), context, policy);
} else if (value instanceof Collection) {
return serializePropertyWithCollectionValue(modelElement, seqElem, (Collection<?>) value, context, policy);
} else if (isRefererence(seqElem)) {
return serializePropertyWithEObjectReference(seqElem, (EObject) value, context, policy);
} else if (value instanceof EObject) {
return serializeEObjectViaTemplateCall(modelElement, seqElem, (EObject) value,
TcsUtil.getMode(seqElem), context, policy);
} else {
Activator.logger.logError("Unable to serialize property value of unknown type "
+ (value == null ? "null" : value.getClass().getName()) + ". Skipping.");
return new NullResult();
}
} catch (SyntaxMismatchException e) {
if (PropertyArgumentUtil.containsPartialPArg(seqElem)) {
return new NullResult(); // ignore exception
} else {
throw e; // not partial, has to be printed without errors
}
}
}
public PrintResult serializeEObjectViaTemplateCall(EObject modelElement, SequenceElement seqElem, EObject value,
String mode, PrintContext context, PrintPolicy policy) throws NoMatchingTemplateException {
for (ContextTemplate template : policy.getPreferredTemplateOrderOf(modelElement, seqElem, value,
templateFinder.findMatchingContextTemplates(value.eClass(), mode))) {
try {
PrintPolicy subPolicy = policy.getPolicyFor(modelElement, seqElem, value, template);
return templateHandler.serializeContextTemplate(value, template, seqElem, context, subPolicy);
} catch (SyntaxMismatchException e) {
// try next
}
}
throw new NoMatchingTemplateException(value.eClass(), mode);
}
private PrintResult serializePropertyWithEObjectReference(Property seqElem, EObject propertyValue, PrintContext context,
PrintPolicy policy) {
PrimitiveTemplate template = templateFinder.findPrimitiveTemplate(seqElem);
if (propertyValue == null) {
Object reference = policy.getRecoveredReferenceValueFor(seqElem);
return templateHandler.serializePrimitiveTemplate(reference, seqElem, template, context, policy);
} else {
Object reference = getReferenceValue(seqElem, propertyValue);
LeafResult result = templateHandler.serializePrimitiveTemplate(reference, seqElem, template, context, policy);
setReferencedModelElementInResult(result, propertyValue);
return result;
}
}
private Object getReferenceValue(Property seqElem, EObject propertyValue) {
RefersToPArg refersTo = PropertyArgumentUtil.getRefersToPArg(seqElem);
if (refersTo == null) {
try {
return PrettyPrinterUtil.invertReferenceByQuery(propertyValue, seqElem, oclEvaluator);
} catch (ModelAdapterException e) {
throw new RuntimeException("Failed to serialize reference", e);
}
} else {
return EMFModelInspector.get(propertyValue, refersTo.getPropertyName());
}
}
private void setReferencedModelElementInResult(LeafResult result, EObject propertyValue) {
LexedToken token = (LexedToken) result.getNodes().get(result.getNodes().size() - 1);
token.getReferencedElements().add(propertyValue);
}
/**
* Serialize the given property of type Collection<?> by individually serializing all elements in the colleciton.
*/
private PrintResult serializePropertyWithCollectionValue(EObject modelElement, Property seqElem, Collection<?> propertyValue,
PrintContext context, PrintPolicy policy) throws SyntaxMismatchException {
ResultContainer result = new ResultContainer(context.getPendingFormattingRequest());
Sequence separatorSeq = getSeparatorSequence(seqElem);
Iterator<?> iter = policy.getPreferredCollectionOrderOf(modelElement, seqElem, propertyValue).iterator();
while (iter.hasNext()) {
Object currentValue = iter.next();
result.merge(serializeProperty(modelElement, seqElem, currentValue, result.asSubContext(context), policy));
if (iter.hasNext()) {
if (separatorSeq != null) {
result.merge(serializeSequence(modelElement, separatorSeq, result.asSubContext(context), policy));
}
result.appendFormatRequests(context.getBlockFormattingBetweenElements());
}
}
return result;
}
/**
* Execute all injector actions in the given block. If the actual model element values differ from the
* values expected by the actions, backtrack by throwing an exception.
*/
private PrintResult validateInjectorActionsBlock(EObject modelElement, InjectorActionsBlock seqElem, PrintContext context) throws SyntaxMismatchException {
for (InjectorAction action : seqElem.getInjectorActions()) {
switch (action.eClass().getClassifierID()) {
case TCSPackage.PRIMITIVE_PROPERTY_INIT:
seValidator.validatePrimitivePropertyInit(modelElement, (PrimitivePropertyInit) action);
break;
case TCSPackage.LOOKUP_PROPERTY_INIT:
seValidator.validateLookupPropertyInit(modelElement, (LookupPropertyInit) action, context);
break;
default:
Activator.logger.logError("Unable to serialize injector action of unknown type "
+ EMFModelInspector.getTypeName(action) + ". Skipping.");
}
}
return new NullResult();
}
/**
* Serialize forced, custom separators formatting information
*/
private PrintResult serializeCustomSeparator(CustomSeparator seqElem, PrintContext context) {
PrintResult result = new ResultContainer(context.getPendingFormattingRequest());
String name = seqElem.getName();
if (name.equals("no_space")) {
result.appendFormatRequest(FormatRequest.create(Type.SKIP_SPACE));
} else if (name.equals("space")) {
result.appendFormatRequest(FormatRequest.create(Type.ADD_FORCED_SPACE));
} else if (name.equals("newline")) {
result.appendFormatRequest(FormatRequest.createNewline(context.getIndenationLevel()));
} else if (name.equals("tab")) {
result.appendFormatRequest(FormatRequest.create(Type.ADD_TAB));
} else {
Activator.logger.logError("Unable to serialize custom separator of unknown type " + name + ". Skipping.");
return new NullResult();
}
result.hasSyntacticContribution(true);
return result;
}
/**
* Serialize blocks such as: [ ... ]{blockArg=foo}
*/
private PrintResult serializeBlock(EObject modelElement, Block seqElem, PrintContext context, PrintPolicy policy) throws SyntaxMismatchException {
// number of new lines before each element in the block
int numOfNewLinesBeforeEachElement = 1;
NbNLBArg nbNLBArg = BlockArgumentUtil.getNbNLBArg(seqElem);
if (nbNLBArg != null) {
numOfNewLinesBeforeEachElement = nbNLBArg.getValue();
}
// how much to increase the indentation level for elements within the block
IndentIncrBArg indentIncrBArg = BlockArgumentUtil.getIndentIncrBArg(seqElem);
int indentationIncrmentor = 1;
if (indentIncrBArg != null) {
indentationIncrmentor = indentIncrBArg.getValue();
}
// print empty, indented line after the block content was printed
boolean endBlockWithNewLine = true;
EndNLBArg endNLBArg = BlockArgumentUtil.getEndNLBArg(seqElem);
if (endNLBArg != null) {
endBlockWithNewLine = endNLBArg.isValue();
}
int indentation = context.getIndenationLevel() + indentationIncrmentor;
List<FormatRequest> formattingOfBlockStart = getFormattingOfBlockStart(seqElem, context, numOfNewLinesBeforeEachElement, indentation);
List<FormatRequest> formattingBetweenElements = getFormattingBetweenElements(numOfNewLinesBeforeEachElement, indentation);
ResultContainer result = new ResultContainer(context.getPendingFormattingRequest());
result.appendFormatRequests(formattingOfBlockStart);
result.configureIndentationLevelIncrement(indentationIncrmentor);
result.configureFormattingBetweenBlockElements(formattingBetweenElements);
result.merge(serializeSequence(modelElement, seqElem.getBlockSequence(), result.asSubContext(context), policy));
if (endBlockWithNewLine) {
result.appendFormatRequest(FormatRequest.createNewline(context.getIndenationLevel()));
}
return result;
}
private List<FormatRequest> getFormattingBetweenElements(int numOfNewLinesBeforeEachElement, int indentation) {
List<FormatRequest> formattingBetweenElements = null;
if (numOfNewLinesBeforeEachElement == 0) {
formattingBetweenElements = new ArrayList<FormatRequest>(1);
// no new lines wanted; use the standard spacing instead
formattingBetweenElements.add(FormatRequest.create(Type.ADD_SPACE));
} else {
formattingBetweenElements = new ArrayList<FormatRequest>(indentation+1);
for (int i = 0; i < numOfNewLinesBeforeEachElement; i++) {
formattingBetweenElements.add(FormatRequest.createNewline(indentation));
}
}
return formattingBetweenElements;
}
private List<FormatRequest> getFormattingOfBlockStart(Block seqElem, PrintContext context, int numOfNewLinesBeforeEachElement, int indentation) {
// by default, startNbNL = nbNL // says how many lines we want at the beginning of the block
int numOfNewLinesAtBlockStart = numOfNewLinesBeforeEachElement;
StartNbNLBArg startNbNLBArg = BlockArgumentUtil.getStartNbNLBArg(seqElem);
if (startNbNLBArg != null) {
numOfNewLinesAtBlockStart = startNbNLBArg.getValue();
}
// start to print block on a new line
boolean startBlockWithNewLine = true;
StartNLBArg startNLBArg = BlockArgumentUtil.getStartNLBArg(seqElem);
if (startNLBArg != null) {
startBlockWithNewLine = startNLBArg.isValue();
}
List<FormatRequest> formattingOfBlockStart = new ArrayList<FormatRequest>(context.getPendingFormattingRequest());
if (startBlockWithNewLine) {
for (int i = 0; i < numOfNewLinesAtBlockStart; i++) {
formattingOfBlockStart.add(FormatRequest.createNewline(indentation));
}
}
return formattingOfBlockStart;
}
/**
* Serialize an alternative with different choices such as: [[ choiceA | choiceB | ... ]]
*/
private PrintResult serializeAlternative(EObject modelElement, Alternative seqElem, PrintContext context, PrintPolicy policy)
throws AlternativeChoiceMismatch {
ResultContainer result = new ResultContainer(context.getPendingFormattingRequest());
result.configureAlternativeNestingLevel(1, context);
Collection<SyntaxMismatchException> exceptions = new ArrayList<SyntaxMismatchException>(seqElem.getSequences().size());
for (SequenceInAlternative choice : policy.getPreferredAlternativeChoiceOrderOf(seqElem.getSequences())) {
try {
PrintResult subResult = serializeSequence(modelElement, choice, result.asSubContext(context), policy);
// serialization worked
result.mergeChosenAlternative(seqElem, getSequenceChoiceIndex(choice, seqElem), subResult);
return result;
} catch (SyntaxMismatchException e) {
exceptions.add(e);
// try next
}
}
if (seqElem.isIsMulti()) {
return result;
} else {
throw new AlternativeChoiceMismatch(seqElem, exceptions);
}
}
private int getSequenceChoiceIndex(Sequence choice, Alternative alt) {
return alt.getSequences().indexOf(choice);
}
/**
* Serialize a conditional such as: (isDefined(propertyName) ? propertyName)
*/
private PrintResult serializeConditionalElement(EObject modelElement, ConditionalElement seqElem, PrintContext context,
PrintPolicy policy) throws SyntaxMismatchException {
ResultContainer result = new ResultContainer(context.getPendingFormattingRequest());
result.configureAlternativeNestingLevel(1, context);
if (TCSConditionEvaluator.eval(modelElement, seqElem.getCondition())) {
Sequence tseq = seqElem.getThenSequence();
if (tseq != null) {
PrintResult subResult = serializeSequence(modelElement, tseq, result.asSubContext(context), policy);
result.mergeChosenAlternative(seqElem, 0, subResult);
}
} else {
Sequence eseq = seqElem.getElseSequence();
if (eseq != null) {
PrintResult subResult = serializeSequence(modelElement, eseq, result.asSubContext(context), policy);
result.mergeChosenAlternative(seqElem, 1, subResult);
}
}
return result;
}
private static Sequence getSeparatorSequence(Property seqElem) {
SeparatorPArg separator = PropertyArgumentUtil.getSeparatorPArg(seqElem);
if (separator == null) {
return null;
} else {
return separator.getSeparatorSequence();
}
}
private static boolean isRefererence(Property seqElem) {
return PropertyArgumentUtil.containsReferenceByPArg(seqElem) || PropertyArgumentUtil.containsRefersToArg(seqElem);
}
private static boolean isPrimitive(Object value) {
return ((value instanceof String) || (value instanceof Boolean) || (value instanceof Double) || (value instanceof Integer));
}
}