/*******************************************************************************
* 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 java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.emf.ecore.EObject;
import com.sap.furcas.metamodel.FURCAS.TCS.ContextTemplate;
import com.sap.furcas.metamodel.FURCAS.TCS.Property;
import com.sap.furcas.metamodel.FURCAS.TCS.SequenceElement;
import com.sap.furcas.metamodel.FURCAS.TCS.SequenceInAlternative;
import com.sap.furcas.metamodel.FURCAS.textblocks.AbstractToken;
import com.sap.furcas.metamodel.FURCAS.textblocks.Bostoken;
import com.sap.furcas.metamodel.FURCAS.textblocks.DocumentNode;
import com.sap.furcas.metamodel.FURCAS.textblocks.Eostoken;
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.prettyprinter.Formatter.FormatRequest;
import com.sap.furcas.prettyprinter.policy.DefaultPrintPolicy;
import com.sap.furcas.prettyprinter.policy.PrintPolicy;
import com.sap.furcas.runtime.tcs.TcsUtil;
/**
* A {@link PrintPolicy} which allows to re-use information from old/preexisting {@link TextBlock}s.
*
* Stated goal is re-use as much as possible, including formatting, comments, serialization order
* of unordered collections, chosen alternatives, names for for currently un-resolvable elements, ...
*
* A {@link TextBlock} represent a single policy. Its sub-blocks are realized as sub-policies
* (see {@link #getPolicyFor}).
*
* @author Stephan Erb
*
*/
public class TextBlockBasedPrintPolicy implements PrintPolicy {
private final TextBlock textBlock;
private final HashMap<SequenceElement, Integer> firstPreceedingWhiteSpaceTokenIndexPerSequenceElement = new HashMap<SequenceElement, Integer>();
private final HashMap<String, Integer> firstPreceedingWhiteSpaceTokenIndexPerTokenValue = new HashMap<String, Integer>();
private final TextBlockIndex index;
private final ContextTemplate template;
public TextBlockBasedPrintPolicy(TextBlock oldBlock, ContextTemplate template, TextBlockIndex index) {
this.textBlock = oldBlock;
this.template = template;
this.index = index;
initializeSequenceElementStore(textBlock.getSubNodes());
}
private void initializeSequenceElementStore(List<DocumentNode> tokensOfBlock) {
int currentleftMostWhiteSpace = getUndefinedIndex();
for (int i=0; i<tokensOfBlock.size(); i++) {
DocumentNode node = tokensOfBlock.get(i);
if (node instanceof LexedToken) {
int tokenIndex = isUndefinedIndex(currentleftMostWhiteSpace) ? i : currentleftMostWhiteSpace;
firstPreceedingWhiteSpaceTokenIndexPerSequenceElement.put(((LexedToken) node).getSequenceElement(), tokenIndex);
firstPreceedingWhiteSpaceTokenIndexPerTokenValue.put(((LexedToken) node).getValue(), tokenIndex);
currentleftMostWhiteSpace = getUndefinedIndex();
} else if (isWhiteSpace(node) && isUndefinedIndex(currentleftMostWhiteSpace)) {
currentleftMostWhiteSpace = i;
} else if (node instanceof TextBlock) {
currentleftMostWhiteSpace = getUndefinedIndex();
}
}
// Special case handling for the whitespace/comments at the end of the block
firstPreceedingWhiteSpaceTokenIndexPerSequenceElement.put(null, isUndefinedIndex(currentleftMostWhiteSpace) ? null : currentleftMostWhiteSpace);
}
private static boolean isUndefinedIndex(int i) {
return i < 0;
}
private static int getUndefinedIndex() {
return -1;
}
private static boolean isPseudoToken(DocumentNode node) {
return node instanceof Bostoken || node instanceof Eostoken;
}
private static boolean isWhiteSpace(DocumentNode node) {
return node instanceof OmittedToken;
}
@Override
public PrintPolicy getPolicyFor(EObject modelElement, SequenceElement seqElem, EObject valueToBePrinted, ContextTemplate template) {
return DefaultPrintPolicy.getPolicyFor(index, valueToBePrinted, template);
}
@Override
public Collection<?> getPreferredCollectionOrderOf(EObject modelElement, Property seqElem, Collection<?> elements) {
return elements;
}
@Override
public Collection<ContextTemplate> getPreferredTemplateOrderOf(EObject modelElement, SequenceElement seqElem, EObject value,
Collection<ContextTemplate> elements) {
return elements;
}
@Override
public Collection<SequenceInAlternative> getPreferredAlternativeChoiceOrderOf(Collection<SequenceInAlternative> sequences) {
// This is slow in theory, but seems fast enough for now...
// Find the chosen alternative in the old textblock
SequenceInAlternative executedAlternative = null;
for (SequenceInAlternative seq : sequences) {
if (seq.getElements().isEmpty()) {
executedAlternative = seq;
continue; // only use if we there wasn't another one being executed.
}
if (TcsUtil.wasExecuted(template, textBlock.getParentAltChoices(), seq.getElements().iterator().next())) {
executedAlternative = seq;
break; // there can only be one
}
}
if (executedAlternative == null) {
return sequences;
} else {
// put it to front: It should be tested first
List<SequenceInAlternative> reordered = new ArrayList<SequenceInAlternative>(sequences);
reordered.remove(executedAlternative);
reordered.add(0, executedAlternative);
return reordered;
}
}
@Override
public Object getRecoveredReferenceValueFor(SequenceElement seqElem) {
// The reference is broken. We now have to recover the text used to reference the deleted object, so that
// the reference can be re-established at a later point in time.
for (DocumentNode node : textBlock.getSubNodes()) {
// FIXME: this might fail if we have a multi-value reference and more than two references
// break at the same time. We don't know which value shall be re-used here
if (node instanceof LexedToken && seqElem.equals(node.getSequenceElement())) {
return ((LexedToken) node).getValue();
}
}
return "";
}
@Override
public List<FormatRequest> getOverruledFormattingBetween(List<FormatRequest> pendingFormattingRequest,
SequenceElement previousSeqElement, SequenceElement followingSeqElement, String followingTokenValue) {
Integer targetTokenIndex = getTargetToken(followingSeqElement, followingTokenValue);
if (targetTokenIndex == null) {
return pendingFormattingRequest;
} else {
ListIterator<DocumentNode> iter = textBlock.getSubNodes().listIterator(targetTokenIndex);
return getFormattingRequestsForNextWhiteSpaces(iter);
}
}
private Integer getTargetToken(SequenceElement followingSeqElement, String followingTokenValue) {
if (followingSeqElement == null) {
// get the content behind the last sequence element
return firstPreceedingWhiteSpaceTokenIndexPerSequenceElement.get(followingSeqElement);
} else if (TcsUtil.isFirstSequenceElement(followingSeqElement) &&
followingSeqElement.getElementSequence().equals(template.getTemplateSequence())) {
// get the content before the first sequence element
return 0;
} else {
Integer tokenIndex = firstPreceedingWhiteSpaceTokenIndexPerSequenceElement.get(followingSeqElement);
if (tokenIndex == null) {
// token value based fallback.
tokenIndex = firstPreceedingWhiteSpaceTokenIndexPerTokenValue.get(followingTokenValue);
}
return tokenIndex;
}
}
private List<FormatRequest> getFormattingRequestsForNextWhiteSpaces(Iterator<DocumentNode> iter) {
List<FormatRequest> requests = new ArrayList<FormatRequest>();
while (iter.hasNext()) {
DocumentNode node = iter.next();
if (isPseudoToken(node)) {
continue;
}
if (isWhiteSpace(node)) {
requests.add(FormatRequest.createCustom(((AbstractToken) node).getValue()));
} else {
break;
}
}
return requests;
}
}