/**
* Copyright (c) 2013 Puppet Labs, Inc. and other contributors, as listed below.
* 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:
* Puppet Labs
*/
package com.puppetlabs.geppetto.pp.dsl.ppformatting;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.ILeafNode;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.parsetree.reconstr.impl.NodeIterator;
import org.eclipse.xtext.parsetree.reconstr.impl.TokenUtil;
import org.eclipse.xtext.util.Pair;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterators;
import com.google.common.collect.Multimap;
import com.google.inject.Inject;
/**
* Provides a mapping from semantic objects to comments before and after the object.
*
*/
public class FormattingCommentAssociator {
public static class CommentAssociations {
Multimap<EObject, INode> beforeMap;
Multimap<EObject, INode> afterMap;
public CommentAssociations() {
beforeMap = ArrayListMultimap.create();
afterMap = ArrayListMultimap.create();
}
protected void acceptAfter(EObject obj, Collection<? extends INode> commentNodes) {
for(INode n : commentNodes)
afterMap.put(obj, n);
commentNodes.clear();
}
protected void acceptAfter(EObject obj, INode node) {
afterMap.put(obj, node);
}
protected void acceptBefore(EObject obj, Collection<? extends INode> commentNodes) {
for(INode n : commentNodes)
beforeMap.put(obj, n);
commentNodes.clear();
}
protected void acceptBefore(EObject obj, INode node) {
beforeMap.put(obj, node);
}
public Iterator<INode> after(EObject obj) {
Collection<INode> val = afterMap.get(obj);
if(val == null)
return Iterators.emptyIterator();
return val.iterator();
}
public Iterator<INode> before(EObject obj) {
Collection<INode> val = beforeMap.get(obj);
if(val == null)
return Iterators.emptyIterator();
return val.iterator();
}
}
@Inject
protected TokenUtil tokenUtil;
/**
* This implementation associates each comment with the next following semantic token's EObject, except for the
* case, where a line of the document end by a semantic element followed by a comment. Then, the the comment is
* associated with this preceding semantic token.
*/
protected void associateCommentsWithSemanticEObjects(CommentAssociations mapping, ICompositeNode rootNode) {
// System.out.println(EmfFormatter.objToStr(rootNode));
EObject currentEObject = null;
List<ILeafNode> currentComments = new ArrayList<ILeafNode>();
NodeIterator nodeIterator = new NodeIterator(rootNode);
// rewind to previous token with token owner
while(nodeIterator.hasPrevious() && currentEObject == null) {
INode node = nodeIterator.previous();
if(tokenUtil.isToken(node)) {
currentEObject = tokenUtil.getTokenOwner(node);
}
}
while(nodeIterator.hasNext()) {
INode node = nodeIterator.next();
if(tokenUtil.isCommentNode(node)) {
currentComments.add((ILeafNode) node);
}
boolean isToken = tokenUtil.isToken(node);
if((node instanceof ILeafNode || isToken) && node.getStartLine() != node.getEndLine() &&
currentEObject != null) {
// found a newline -> associating existing comments with currentEObject
mapping.acceptAfter(currentEObject, currentComments);
// addMapping(mapping, currentComments, currentEObject);
currentEObject = null;
}
if(isToken) {
Pair<List<ILeafNode>, List<ILeafNode>> leadingAndTrailingHiddenTokens = tokenUtil.getLeadingAndTrailingHiddenTokens(node);
for(ILeafNode leadingHiddenNode : leadingAndTrailingHiddenTokens.getFirst()) {
if(tokenUtil.isCommentNode(leadingHiddenNode)) {
currentComments.add(leadingHiddenNode);
}
}
nodeIterator.prune();
currentEObject = tokenUtil.getTokenOwner(node);
if(currentEObject != null) {
mapping.acceptBefore(currentEObject, currentComments);
// addMapping(mapping, currentComments, currentEObject);
if(node.getOffset() > rootNode.getOffset() + rootNode.getLength()) {
// found next EObject outside rootNode
break;
}
}
}
}
if(!currentComments.isEmpty()) {
if(currentEObject != null) {
mapping.acceptBefore(currentEObject, currentComments);
// addMapping(mapping, currentComments, currentEObject);
}
else {
EObject objectForRemainingComments = getEObjectForRemainingComments(rootNode);
if(objectForRemainingComments != null) {
mapping.acceptBefore(currentEObject, currentComments);
// addMapping(mapping, currentComments, objectForRemainingComments);
}
}
}
}
public CommentAssociations associateCommentsWithSemanticEObjects(EObject model, Set<ICompositeNode> roots) {
CommentAssociations mapping = new CommentAssociations();
for(ICompositeNode rootNode : roots)
associateCommentsWithSemanticEObjects(mapping, rootNode);
return mapping;
}
protected EObject getEObjectForRemainingComments(ICompositeNode rootNode) {
TreeIterator<INode> i = rootNode.getAsTreeIterable().iterator();
while(i.hasNext()) {
INode o = i.next();
if(o.hasDirectSemanticElement())
return o.getSemanticElement();
}
return null;
}
}