/*******************************************************************************
* Copyright (c) 2008-2010 itemis AG (http://www.itemis.eu) 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
*******************************************************************************/
package net.sf.orcc.cal.parser.impl;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Action;
import org.eclipse.xtext.Alternatives;
import org.eclipse.xtext.CompoundElement;
import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.Group;
import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.XtextPackage;
import org.eclipse.xtext.nodemodel.BidiIterator;
import org.eclipse.xtext.nodemodel.BidiTreeIterator;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.ILeafNode;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.impl.AbstractNode;
import org.eclipse.xtext.nodemodel.impl.CompositeNode;
import org.eclipse.xtext.nodemodel.impl.NodeModelBuilder;
import org.eclipse.xtext.nodemodel.impl.SyntheticCompositeNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.parser.IParseResult;
import org.eclipse.xtext.parser.IParser;
import org.eclipse.xtext.parser.ParseException;
import org.eclipse.xtext.parser.ParseResult;
import org.eclipse.xtext.parser.antlr.IPartialParsingHelper;
import org.eclipse.xtext.parser.antlr.IReferableElementsUnloader;
import org.eclipse.xtext.parser.impl.PartialParsingPointers;
import org.eclipse.xtext.parser.impl.Range;
import org.eclipse.xtext.parser.impl.TokenRegionProvider;
import org.eclipse.xtext.util.ReplaceRegion;
import org.eclipse.xtext.util.XtextSwitch;
import com.google.inject.Inject;
/*
* [2013-09-13 - alorence] This class resolve an issue With Xtext 2.4.x. It should be removed when
* the bug is fixed (planned for 2.5 release). See
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=416913 for more information
*/
/**
* @author Jan K�hnlein - Initial contribution and API
* @author Sebastian Zarnekow
*/
public class PatchedPartialParsingHelper implements IPartialParsingHelper {
private static final Logger log = Logger
.getLogger(PatchedPartialParsingHelper.class);
@Inject
private IReferableElementsUnloader unloader;
@Inject
private NodeModelBuilder nodeModelBuilder = new NodeModelBuilder();
@Inject(optional = true)
private TokenRegionProvider tokenRegionProvider;
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public IParseResult reparse(IParser parser,
IParseResult previousParseResult, ReplaceRegion changedRegion) {
if (parser == null) {
throw new NullPointerException("parser may not be null");
}
if (previousParseResult == null) {
throw new NullPointerException(
"previousParseResult and previousParseResult.rootNode may not be null");
}
ICompositeNode oldRootNode = previousParseResult.getRootNode();
if (changedRegion.getEndOffset() > oldRootNode.getTotalLength()) {
log.error("Invalid " + changedRegion + " originalLength="
+ oldRootNode.getTotalLength());
return fullyReparse(parser, previousParseResult, changedRegion);
}
if (changedRegion.getOffset() >= oldRootNode.getTotalLength()
&& changedRegion.getText().trim().length() == 0) {
return fullyReparse(parser, previousParseResult, changedRegion);
}
ReplaceRegion replaceRegion = tokenRegionProvider
.getTokenReplaceRegion(
insertChangeIntoReplaceRegion(oldRootNode,
changedRegion), changedRegion);
if (isNullEdit(oldRootNode, replaceRegion)) {
return previousParseResult;
}
PartialParsingPointers parsingPointers = calculatePartialParsingPointers(
previousParseResult, replaceRegion.getOffset(),
replaceRegion.getLength());
List<ICompositeNode> validReplaceRootNodes = parsingPointers
.getValidReplaceRootNodes();
ICompositeNode oldCompositeNode = null;
String reparseRegion = "";
for (int i = validReplaceRootNodes.size() - 1; i >= 0; --i) {
oldCompositeNode = validReplaceRootNodes.get(i);
if (!(oldCompositeNode instanceof SyntheticCompositeNode)
&& !isRangePartOfExceedingLookAhead(
(CompositeNode) oldCompositeNode, replaceRegion)) {
boolean replaceAtEnd = oldCompositeNode.getTotalEndOffset() == replaceRegion
.getEndOffset();
reparseRegion = insertChangeIntoReplaceRegion(oldCompositeNode,
replaceRegion);
if (!"".equals(reparseRegion)) {
if (!replaceAtEnd
|| !Character.isWhitespace(reparseRegion
.charAt(reparseRegion.length() - 1))) {
if (log.isDebugEnabled()) {
log.debug("replace region: ["
+ oldCompositeNode.getTotalOffset()
+ " / length: "
+ oldCompositeNode.getTotalLength()
+ " of [" + oldRootNode.getTotalOffset()
+ " / lenght: "
+ oldRootNode.getTotalLength() + "]");
}
break;
}
}
}
}
if (oldCompositeNode == null || reparseRegion.equals("")
|| oldCompositeNode == oldRootNode) {
return fullyReparse(parser, previousParseResult, replaceRegion);
}
EObject entryRuleOrRuleCall = parsingPointers
.findEntryRuleOrRuleCall(oldCompositeNode);
IParseResult newParseResult = null;
try {
if (entryRuleOrRuleCall instanceof RuleCall) {
newParseResult = parser.parse((RuleCall) entryRuleOrRuleCall,
new StringReader(reparseRegion),
oldCompositeNode.getLookAhead());
} else {
newParseResult = parser.parse((ParserRule) entryRuleOrRuleCall,
new StringReader(reparseRegion));
}
} catch (ParseException exc) {
}
if (newParseResult == null || newParseResult.hasSyntaxErrors()) {
// TODO: Should we reparse if the complete input was parsed?
// on error fully reparse
return fullyReparse(parser, previousParseResult, replaceRegion);
}
if (oldRootNode.equals(oldCompositeNode)) {
unloadSemanticObject(previousParseResult.getRootASTElement());
return newParseResult;
}
EObject oldSemanticParentElement = oldCompositeNode.getParent()
.getSemanticElement();
EObject oldSemanticElement = null;
if (oldCompositeNode.hasDirectSemanticElement()) {
oldSemanticElement = oldCompositeNode.getSemanticElement();
} else {
List<ICompositeNode> nodesEnclosingRegion = parsingPointers
.getNodesEnclosingRegion();
for (int i = nodesEnclosingRegion.size() - 1; i >= 0; --i) {
ICompositeNode enclosingNode = nodesEnclosingRegion.get(i);
if (enclosingNode == oldCompositeNode) {
break;
}
if (enclosingNode.hasDirectSemanticElement()) {
oldSemanticElement = enclosingNode.getSemanticElement();
}
}
if (oldSemanticElement == null) {
return fullyReparse(parser, previousParseResult, replaceRegion);
}
}
if (oldSemanticElement == oldSemanticParentElement) {
throw new IllegalStateException("oldParent == oldElement");
}
if (oldSemanticParentElement != null) {
EStructuralFeature feature = oldSemanticElement
.eContainingFeature();
if (feature == null) {
return fullyReparse(parser, previousParseResult, replaceRegion);
}
// 8< the fix is here
oldSemanticParentElement = oldSemanticElement.eContainer();
// >8
if (feature.isMany()) {
List featureValueList = (List) oldSemanticParentElement
.eGet(feature);
int index = featureValueList.indexOf(oldSemanticElement);
unloadSemanticObject(oldSemanticElement);
featureValueList.set(index, newParseResult.getRootASTElement());
} else {
unloadSemanticObject(oldSemanticElement);
oldSemanticParentElement.eSet(feature,
newParseResult.getRootASTElement());
}
((ParseResult) newParseResult)
.setRootASTElement(previousParseResult.getRootASTElement());
} else {
unloadSemanticObject(oldSemanticElement);
}
if (oldCompositeNode != oldRootNode) {
nodeModelBuilder.replaceAndTransferLookAhead(oldCompositeNode,
newParseResult.getRootNode());
((ParseResult) newParseResult).setRootNode(oldRootNode);
StringBuilder builder = new StringBuilder(oldRootNode.getText());
replaceRegion.applyTo(builder);
nodeModelBuilder
.setCompleteContent(oldRootNode, builder.toString());
}
return newParseResult;
}
private boolean isRangePartOfExceedingLookAhead(CompositeNode node,
ReplaceRegion replaceRegion) {
TreeIterator<AbstractNode> iterator = node.basicIterator();
int lookAhead = node.getLookAhead();
if (lookAhead == 0) {
return false;
}
while (iterator.hasNext()) {
AbstractNode child = iterator.next();
if (child instanceof CompositeNode) {
if (child.getTotalOffset() < replaceRegion.getEndOffset()) {
lookAhead = Math.max(
((CompositeNode) child).getLookAhead(), lookAhead);
}
} else if (!((ILeafNode) child).isHidden()) {
lookAhead--;
if (lookAhead == 0) {
if (child.getTotalOffset() >= replaceRegion.getEndOffset()) {
return false;
}
}
}
}
return lookAhead > 0;
}
private boolean isNullEdit(INode oldRootNode, ReplaceRegion replaceRegion) {
if (replaceRegion.getLength() == replaceRegion.getText().length()) {
String replacedText = oldRootNode.getText().substring(
replaceRegion.getOffset(), replaceRegion.getEndOffset());
if (replaceRegion.getText().equals(replacedText)) {
return true;
}
}
return false;
}
protected IParseResult fullyReparse(IParser parser,
IParseResult previousParseResult, ReplaceRegion replaceRegion) {
unloadSemanticObject(previousParseResult.getRootASTElement());
String reparseRegion = insertChangeIntoReplaceRegion(
previousParseResult.getRootNode(), replaceRegion);
return parser.parse(new StringReader(reparseRegion));
}
public void unloadNode(INode node) {
if (node != null) {
EObject semantic = node.getSemanticElement();
unloadSemanticObject(semantic);
}
}
public void unloadSemanticObject(EObject object) {
if (unloader != null && object != null) {
unloader.unloadRoot(object);
}
}
public String insertChangeIntoReplaceRegion(ICompositeNode rootNode,
ReplaceRegion region) {
final StringBuilder builder = new StringBuilder(rootNode.getText());
region.shiftBy(0 - rootNode.getTotalOffset()).applyTo(builder);
return builder.toString();
}
public PartialParsingPointers calculatePartialParsingPointers(
IParseResult previousParseResult, final int offset,
int replacedTextLength) {
int myOffset = offset;
int myReplacedTextLength = replacedTextLength;
ICompositeNode oldRootNode = previousParseResult.getRootNode();
if (myOffset == oldRootNode.getTotalLength() && myOffset != 0) {
// newText is appended, so look for the last original character
// instead
--myOffset;
myReplacedTextLength = 1;
}
// include any existing parse errors
Range range = new Range(myOffset, myReplacedTextLength + myOffset);
if (previousParseResult.hasSyntaxErrors()) {
range.mergeAllSyntaxErrors(oldRootNode);
}
myOffset = range.getOffset();
List<ICompositeNode> nodesEnclosingRegion = collectNodesEnclosingChangeRegion(
oldRootNode, range);
List<ICompositeNode> validReplaceRootNodes = internalFindValidReplaceRootNodeForChangeRegion(
nodesEnclosingRegion, range);
filterInvalidRootNodes(oldRootNode, validReplaceRootNodes);
if (validReplaceRootNodes.isEmpty()) {
validReplaceRootNodes = Collections.singletonList(oldRootNode);
}
return new PartialParsingPointers(oldRootNode, myOffset,
myReplacedTextLength, validReplaceRootNodes,
nodesEnclosingRegion);
}
protected void filterInvalidRootNodes(ICompositeNode oldRootNode,
List<ICompositeNode> validReplaceRootNodes) {
ListIterator<ICompositeNode> iter = validReplaceRootNodes
.listIterator(validReplaceRootNodes.size());
while (iter.hasPrevious()) {
ICompositeNode candidate = iter.previous();
if (isInvalidRootNode(oldRootNode, candidate)) {
iter.remove();
} else {
return;
}
}
}
protected boolean isInvalidRootNode(ICompositeNode rootNode,
ICompositeNode candidate) {
int endOffset = candidate.getTotalEndOffset();
if (candidate instanceof SyntheticCompositeNode) {
return true;
}
if (candidate.getGrammarElement() instanceof RuleCall) {
AbstractRule rule = ((RuleCall) candidate.getGrammarElement())
.getRule();
if (!(rule instanceof ParserRule)
|| GrammarUtil.isDatatypeRule((ParserRule) rule)) {
return true;
} else if (isInvalidDueToPredicates((AbstractElement) candidate
.getGrammarElement())) {
return true;
}
}
if (candidate.getGrammarElement() instanceof Action) {
return true;
}
if (endOffset == rootNode.getTotalEndOffset()) {
INode lastChild = getLastChild(candidate);
if (lastChild instanceof ICompositeNode) {
INode lastLeaf = getLastLeaf(candidate);
if (isInvalidLastChildNode(candidate, lastLeaf)) {
return true;
}
}
if (isInvalidLastChildNode(candidate, lastChild)) {
return true;
}
}
return false;
}
/**
* @since 2.3
*/
protected boolean isInvalidDueToPredicates(AbstractElement element) {
// if(element.isPredicated())
// return true;
// else if(element instanceof RuleCall) {
// AbstractRule rule = ((RuleCall) element).getRule();
// if(rule.getAlternatives() instanceof Group) {
// boolean result = isInvalidDueToPredicates(((Group)
// rule.getAlternatives()).getElements().get(0));
// return result;
// }
// }
return false;
}
protected boolean isInvalidLastChildNode(ICompositeNode candidate,
INode lastChild) {
if (lastChild != null && lastChild.getSyntaxErrorMessage() != null) {
EObject lastChildGrammarElement = lastChild.getGrammarElement();
if (lastChildGrammarElement == null) {
return true;
}
AbstractElement candidateElement = getCandidateElement(candidate
.getGrammarElement());
if (candidateElement != null) {
if (isCalledBy(lastChildGrammarElement, candidateElement)) {
while (candidate != null) {
if (candidateElement != null
&& hasMandatoryFollowElements(candidateElement)) {
return true;
}
candidate = candidate.getParent();
if (candidate != null) {
candidateElement = getCandidateElement(candidate
.getGrammarElement());
}
}
}
return true;
}
}
return false;
}
private boolean isCalledBy(final EObject child, AbstractElement parent) {
return new XtextSwitch<Boolean>() {
private final Set<ParserRule> rules = new HashSet<ParserRule>(4);
@Override
public Boolean caseCompoundElement(CompoundElement object) {
for (AbstractElement elem : object.getElements()) {
if (doSwitch(elem)) {
return true;
}
}
return false;
}
@Override
public Boolean caseAbstractElement(AbstractElement object) {
return object == child;
}
@Override
public Boolean caseRuleCall(RuleCall object) {
return object == child || doSwitch(object.getRule());
}
@Override
public Boolean caseAbstractRule(AbstractRule object) {
return object == child;
}
@Override
public Boolean caseParserRule(ParserRule object) {
return object == child
|| (rules.add(object) && doSwitch(object
.getAlternatives()));
}
}.doSwitch(parent);
}
private boolean hasMandatoryFollowElements(AbstractElement lastParsedElement) {
if (lastParsedElement.eContainer() instanceof AbstractElement) {
AbstractElement directParent = (AbstractElement) lastParsedElement
.eContainer();
if (directParent instanceof Group) {
Group group = (Group) directParent;
int idx = group.getElements().indexOf(lastParsedElement) + 1;
for (int i = idx; i < group.getElements().size(); i++) {
if (isMandatory(group.getElements().get(i))) {
return true;
}
}
}
return hasMandatoryFollowElements(directParent);
}
return false;
}
private boolean isMandatory(AbstractElement element) {
return new XtextSwitch<Boolean>() {
private final Set<ParserRule> rules = new HashSet<ParserRule>(4);
@Override
public Boolean caseAction(Action object) {
return false;
}
@Override
public Boolean caseCompoundElement(CompoundElement object) {
if (GrammarUtil.isOptionalCardinality(object)) {
return false;
}
for (AbstractElement child : object.getElements()) {
if (doSwitch(child)) {
return true;
}
}
return false;
}
@Override
public Boolean caseAlternatives(Alternatives object) {
if (GrammarUtil.isOptionalCardinality(object)) {
return false;
}
for (AbstractElement child : object.getElements()) {
if (!doSwitch(child)) {
return false;
}
}
return true;
}
@Override
public Boolean caseAbstractElement(AbstractElement object) {
return !GrammarUtil.isOptionalCardinality(object);
}
@Override
public Boolean caseRuleCall(RuleCall object) {
return !GrammarUtil.isOptionalCardinality(object)
|| doSwitch(object.getRule());
}
@Override
public Boolean caseAbstractRule(AbstractRule object) {
return true;
}
@Override
public Boolean caseParserRule(ParserRule object) {
return rules.add(object) && doSwitch(object.getAlternatives());
}
}.doSwitch(element);
}
private AbstractElement getCandidateElement(EObject grammarElement) {
if (grammarElement instanceof AbstractElement) {
return (AbstractElement) grammarElement;
}
return null;
}
private INode getLastChild(ICompositeNode parent) {
BidiTreeIterator<? extends INode> iterator = parent.getAsTreeIterable()
.iterator();
while (iterator.hasPrevious()) {
INode previous = iterator.previous();
if (previous instanceof ILeafNode) {
return previous;
} else if (previous instanceof ICompositeNode) {
if (!((ICompositeNode) previous).hasChildren()) {
return previous;
}
}
}
return parent;
}
private INode getLastLeaf(ICompositeNode parent) {
BidiTreeIterator<? extends INode> iterator = parent.getAsTreeIterable()
.iterator();
while (iterator.hasPrevious()) {
INode previous = iterator.previous();
if (previous instanceof ILeafNode) {
return previous;
}
}
return null;
}
/**
* Collects a list of all nodes containing the change region
*/
private List<ICompositeNode> collectNodesEnclosingChangeRegion(
ICompositeNode parent, Range range) {
List<ICompositeNode> nodesEnclosingRegion = new ArrayList<ICompositeNode>();
if (nodeEnclosesRegion(parent, range)) {
collectNodesEnclosingChangeRegion(parent, range,
nodesEnclosingRegion);
}
return nodesEnclosingRegion;
}
private void collectNodesEnclosingChangeRegion(ICompositeNode parent,
Range range, List<ICompositeNode> nodesEnclosingRegion) {
nodesEnclosingRegion.add(parent);
BidiIterator<INode> iterator = parent.getChildren().iterator();
while (iterator.hasPrevious()) {
INode prev = iterator.previous();
if (prev instanceof ICompositeNode) {
if (nodeEnclosesRegion((ICompositeNode) prev, range)) {
collectNodesEnclosingChangeRegion((ICompositeNode) prev,
range, nodesEnclosingRegion);
break;
}
}
}
}
protected boolean nodeEnclosesRegion(ICompositeNode node, Range range) {
boolean result = node.getTotalOffset() <= range.getOffset()
&& node.getTotalEndOffset() >= range.getEndOffset();
return result;
}
/**
* Investigates the composite nodes containing the changed region and
* collects a list of nodes which could possibly replaced by a partial
* parse. Such a node has a parent that consumes all his current lookahead
* tokens and all of these tokens are located before the changed region.
*/
private List<ICompositeNode> internalFindValidReplaceRootNodeForChangeRegion(
List<ICompositeNode> nodesEnclosingRegion, Range range) {
List<ICompositeNode> result = new ArrayList<ICompositeNode>();
boolean mustSkipNext = false;
ICompositeNode previous = null;
/*
* set to 'true' as soon as the lookahead of an enclosing exceeds the
* given range
*/
boolean done = false;
for (int i = 0; i < nodesEnclosingRegion.size() && !done; i++) {
ICompositeNode node = nodesEnclosingRegion.get(i);
if (node.getGrammarElement() != null) {
if (!mustSkipNext) {
boolean process = true;
if (previous != null && !node.hasNextSibling()) {
if (previous.getLookAhead() == node.getLookAhead()
&& previous.getLookAhead() == 0) {
process = false;
}
}
EObject semanticElement = NodeModelUtils
.findActualSemanticObjectFor(node);
if (semanticElement != null) {
ICompositeNode actualNode = NodeModelUtils
.findActualNodeFor(semanticElement);
if (actualNode != null
&& (actualNode.getTotalOffset() < node
.getTotalOffset() || actualNode
.getTotalEndOffset() > node
.getTotalEndOffset())) {
process = false;
}
}
if (process) {
int remainingLookAhead = node.getLookAhead();
if (remainingLookAhead != 0) {
Iterator<ILeafNode> iterator = node.getLeafNodes()
.iterator();
while (iterator.hasNext() && remainingLookAhead > 0) {
ILeafNode leaf = iterator.next();
if (!leaf.isHidden()) {
if (remainingLookAhead > 0) {
remainingLookAhead--;
}
if (remainingLookAhead == 0) {
if (leaf.getTotalEndOffset() <= range
.getOffset()) {
result.add(node);
previous = node;
if (isActionNode(node)) {
mustSkipNext = true;
}
break;
} else {
// lookahead ends left of the range,
// don't dive into child nodes
done = true;
}
}
}
}
if (remainingLookAhead != 0) {
done = true;
}
} else {
result.add(node);
previous = node;
if (isActionNode(node)) {
mustSkipNext = true;
}
}
}
} else { // !mustSkipNext
mustSkipNext = isActionNode(node);
}
}
}
return result;
}
protected boolean isActionNode(ICompositeNode node) {
return node.getGrammarElement() != null
&& node.getGrammarElement().eClass() == XtextPackage.Literals.ACTION;
}
public void setUnloader(IReferableElementsUnloader unloader) {
this.unloader = unloader;
}
public IReferableElementsUnloader getUnloader() {
return unloader;
}
/**
* @since 2.3
*/
public void setTokenRegionProvider(TokenRegionProvider tokenRegionProvider) {
this.tokenRegionProvider = tokenRegionProvider;
}
}