package com.sap.ide.refactoring.core.textual;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import org.eclipse.jface.text.source.ISourceViewer;
import com.sap.furcas.metamodel.textblocks.Bostoken;
import com.sap.furcas.metamodel.textblocks.DocumentNode;
import com.sap.furcas.metamodel.textblocks.Eostoken;
import com.sap.ide.cts.parser.incremental.IncrementalParser.TextBlocksTarjanTreeContentProvider;
import com.sap.ide.cts.parser.incremental.util.TarjansLCA;
import com.sap.mi.textual.parsing.textblocks.TbNavigationUtil;
import com.sap.mi.textual.textblocks.model.TextBlocksModel;
/**
* Helper to access the underlying domain model through the textblocks model.
* Works based on the current cursor position and text selection.
*
* @author Stephan Erb (d049157)
*
*/
public class SourcePositionModelLocator {
private final TextBlocksModel textBlockModel;
private final ISourceViewer sourceViewer;
public SourcePositionModelLocator(RefactoringEditorFacade facade) {
this(facade.getTextBlocksModel(), facade.getSourceViewer());
}
SourcePositionModelLocator(TextBlocksModel model, ISourceViewer viewer) {
textBlockModel = model;
sourceViewer = viewer;
}
public Collection<DocumentNode> findSelecetedTextBlocks() {;
int offsetFrom = sourceViewer.getSelectedRange().x;
int offsetTo = offsetFrom + sourceViewer.getSelectedRange().y;
DocumentNode leftMostSelectedNode = textBlockModel.getFloorTokenInRoot(offsetFrom);
DocumentNode rightMostSelectedNode = textBlockModel.getFloorTokenInRoot(offsetTo);
// Handle special case that the user has exactly selected a whole textblock.
// We use this LCA shortcut as it is (much) faster than #getNodesBetweenAsRootSet.
TarjansLCA<DocumentNode> lca = new TarjansLCA<DocumentNode>(new TextBlocksTarjanTreeContentProvider());
DocumentNode commonAncestor = lca.lcaSearch(textBlockModel.getRoot(), leftMostSelectedNode, rightMostSelectedNode);
DocumentNode leftMostAncestorNode = TbNavigationUtil.firstToken(commonAncestor); // could be BOS token
DocumentNode rightMostAncestorNode = TbNavigationUtil.lastToken(commonAncestor); // could be EOS token
// special case handling is for omitted BOS/EOS token
boolean equalLeftMostToken = leftMostAncestorNode.equals(leftMostSelectedNode) || leftMostAncestorNode instanceof Bostoken &&
TbNavigationUtil.getNextInSubTree(leftMostAncestorNode).equals(leftMostSelectedNode);
boolean equalRightMostToken = rightMostAncestorNode.equals(rightMostSelectedNode) || rightMostAncestorNode instanceof Eostoken &&
TbNavigationUtil.getPreviousInSubTree(rightMostAncestorNode).equals(rightMostSelectedNode);
if (equalLeftMostToken && equalRightMostToken) {
return Collections.singletonList(commonAncestor);
} else {
return textBlockModel.getNodesBetweenAsRootSet(textBlockModel.getRoot(), offsetFrom, offsetTo);
}
}
/**
* Find the corresponding ModelElements of the current selection.
*
* It may happen that only TextBlocks are selected which do not contain any
* corresponding ModelElements. In this case the the algorithm moves up the
* TextBlocks hierarchy level after level. Once there is a level reached where
* atleast one of the TextBlocks (parents of our original selected TextBlocks)
* has defined a corresponding ModelElement, all known corresponding ModelElements
* on this level are returned.
*/
public Collection<RefObject> findSelectedCorrespondingModelElements() {
Collection<DocumentNode> selectedTextBlocks = findSelecetedTextBlocks();
Collection<RefObject> selectedElements = new HashSet<RefObject>();
while (selectedElements.isEmpty()) {
for (DocumentNode node : selectedTextBlocks) {
selectedElements.addAll(node.getCorrespondingModelElements());
}
if (selectedElements.isEmpty()) {
selectedTextBlocks = getNodeParents(selectedTextBlocks);
}
if (selectedTextBlocks.isEmpty()) {
break; // root node reached
}
}
return selectedElements;
}
private Collection<DocumentNode> getNodeParents(Collection<DocumentNode> selectedTextBlocks) {
Collection<DocumentNode> parentNodes = new HashSet<DocumentNode>();
for (DocumentNode node : selectedTextBlocks) {
DocumentNode parent = TbNavigationUtil.getParentBlock(node);
if (parent != null) {
parentNodes.add(parent);
}
}
return parentNodes;
}
/**
* Finds the referenced elements within the current selection.
* Does NOT move up the TextBlocks hierarchy.
*/
public Collection<RefObject> findSelectedReferencedModelElements() {
Collection<DocumentNode> selectedTextBlocks = findSelecetedTextBlocks();
Collection<RefObject> selectedElements = new HashSet<RefObject>();
for (DocumentNode node : selectedTextBlocks) {
selectedElements.addAll(node.getReferencedElements());
}
return selectedElements;
}
}