package com.sap.ide.refactoring.core.textual;
import static com.sap.ide.refactoring.core.textual.TextBlockRefactoringUtil.findCorrespondingTextBlocks;
import static com.sap.ide.refactoring.core.textual.TextBlockRefactoringUtil.findReferencedTextBlocks;
import static com.sap.ide.refactoring.core.textual.TextBlockRefactoringUtil.findTextBlockRootDomainRootObjectTuples;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import com.sap.furcas.metamodel.FURCAS.textblocks.AbstractToken;
import com.sap.furcas.metamodel.FURCAS.textblocks.DocumentNode;
import com.sap.furcas.metamodel.FURCAS.textblocks.TextBlock;
import com.sap.ide.refactoring.core.textual.ModelElementDocumentNodeChangeDescriptor.ChangeType;
/**
* For a set of changed model elements, determines the minimal TextBlock trees that need
* to be pretty printed.
*
* We are interested in a minimal tree because a) we want to retain formatting
* as far as possible and b) it does not make sense to e.g. re-print a TextBlock if we already
* pretty printed it's composite parent.
*
* See {@link TextBlocksNeedingPrettyPrintChangeListener}
*
* TODO: Refactor this class. Looks like we can drop the whole root tuple handling
* and can just use the rootblock instead. Should kill some looping.
*
* @author Stephan Erb (d049157)
*
*/
public class TextBlockInChangeCalculator {
private final Map<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> correspondingTextBlocksPerRootObject = new HashMap<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>>();
private final Map<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> referencingTextBlocksPerRootObject = new HashMap<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>>();
public void add(RefObject modelElement, ChangeType changeType) {
if (modelElement instanceof DocumentNode) {
// this is a shortcut. We only care about real model elements that
// changed, but about no events
// related to textblocks that happened during pretty printing
return;
}
if (changeType.equals(ChangeType.CREATED)) {
// TODO: If a new element is created and it is a root element, we
// might want to automatically
// generate a TB tree. Otherwise now preview is shown and the TB
// model will only be created if an editor is hopened.
} else {
addCorrespondingTextBlocks(modelElement, changeType);
addReferencedTextBlocks(modelElement, changeType);
}
}
private void addCorrespondingTextBlocks(RefObject modelElement, ChangeType changeType) {
for (DocumentNode tbToAdd : findCorrespondingTextBlocks(modelElement)) {
if (!isValidTextBlock(tbToAdd)) {
continue;
}
// FIXME: looks like fetching the rootobject tuples is not needed.
for (RootElementTextBlockTuple rootTuple : findTextBlockRootDomainRootObjectTuples(tbToAdd)) {
if (tbToAdd instanceof AbstractToken) {
// Individual tokens cannot be pretty printed. Add their
// parent instead.
addParentTextBlockForToken(modelElement, changeType, rootTuple);
} else {
assert tbToAdd instanceof TextBlock;
mergeTextBlockIntoList(modelElement, tbToAdd, changeType, getCorrespondingTextBlockListOfComposite(rootTuple));
}
}
}
}
private void addParentTextBlockForToken(RefObject modelElement, ChangeType changeType, RootElementTextBlockTuple rootTuple) {
// The following is just a heuristic and could probably be improved.
// (it might happen that we pretty print to much)
RefObject parent = findParentWithCorrespondingTextBlocks(modelElement, rootTuple);
for (DocumentNode documentNode : findCorrespondingTextBlocks(parent)) {
// filter AbstractTokens to prevent recursion
if (documentNode instanceof TextBlock) {
mergeTextBlockIntoList(parent, documentNode, changeType, getCorrespondingTextBlockListOfComposite(rootTuple));
}
}
}
private void addReferencedTextBlocks(RefObject modelElement, ChangeType changeType) {
for (DocumentNode tbToAdd : findReferencedTextBlocks(modelElement)) {
if (!isValidTextBlock(tbToAdd)) {
continue;
}
// FIXME: looks like fetching the rootobject tuples is not needed.
for (RootElementTextBlockTuple rootTuple : findTextBlockRootDomainRootObjectTuples(tbToAdd)) {
mergeTextBlockIntoList(modelElement, tbToAdd, changeType, getReferencingTextBlockListOfComposite(rootTuple));
}
}
}
private boolean isValidTextBlock(DocumentNode tbToAdd) {
if (!TbUtil.getNewestVersion(tbToAdd).equals(tbToAdd)) {
return false;
}
return true;
}
private void mergeTextBlockIntoList(RefObject modelElement, DocumentNode tbToAdd, ChangeType changeType,
List<ModelElementDocumentNodeChangeDescriptor> textBlocks) {
int insertPosition = findInsertPosition(tbToAdd, textBlocks);
if (insertPosition >= 0) {
removeChildBlocks(insertPosition, tbToAdd, textBlocks);
textBlocks.add(insertPosition, new ModelElementDocumentNodeChangeDescriptor(modelElement, tbToAdd, changeType));
} else {
// No need to add tbToAdd. We have already stored a parent Block.
}
}
private int findInsertPosition(DocumentNode tbToAdd, List<ModelElementDocumentNodeChangeDescriptor> textBlocks) {
ListIterator<ModelElementDocumentNodeChangeDescriptor> iter = textBlocks.listIterator();
int insertPosition = 0;
while (iter.hasNext()) {
insertPosition = iter.nextIndex();
DocumentNode currentTb = iter.next().documentNode;
if (currentTb.getAbsoluteOffset() < tbToAdd.getAbsoluteOffset()) {
// currentTb is left of our insert position
// If iter.hasNext() is false we will spring out of the while
// lopp in the next run.
// In this special case the following incrementation is
// required, because we want to insert
// behind the last element, not before.
insertPosition++;
continue;
} else {
// found the insert position
break;
}
}
// Check if the block really needs to be added
if (iter.hasPrevious()) {
DocumentNode tbLeftOfInsertPosition = iter.previous().documentNode;
int coveredRange = tbLeftOfInsertPosition.getAbsoluteOffset() + tbLeftOfInsertPosition.getLength();
if (coveredRange >= tbToAdd.getAbsoluteOffset() + tbToAdd.getLength()) {
// tbToAdd is a subBlock of tbLeftOfInsertPosition. No need to
// add tbToAdd
return -1;
} else {
return insertPosition;
}
} else {
assert insertPosition == 0 : "Only the first element cannot have a previous element.";
return 0;
}
}
private void removeChildBlocks(int insertPosition, DocumentNode tbToAdd,
List<ModelElementDocumentNodeChangeDescriptor> textBlocks) {
if (insertPosition == textBlocks.size()) {
// insert at the end, so there is nothing behind us and therefore
// nothing to delete.
} else {
ListIterator<ModelElementDocumentNodeChangeDescriptor> iter = textBlocks.listIterator(insertPosition);
while (iter.hasNext()) {
DocumentNode tbRightOfInsertPosition = iter.next().documentNode;
int coveredRange = tbRightOfInsertPosition.getAbsoluteOffset() + tbRightOfInsertPosition.getLength();
if (coveredRange <= tbToAdd.getAbsoluteOffset() + tbToAdd.getLength()) {
// tbToAdd is a superBlock of tbRightOfInsertPosition. We
// can delete it.
iter.remove();
}
}
}
}
/**
* Find a suitable parent for pretty printing (this implies that the parent
* has corresponding ModelElements)
*/
private RefObject findParentWithCorrespondingTextBlocks(RefObject modelElement, RootElementTextBlockTuple rootTuple) {
RefObject parent = (RefObject) modelElement.refImmediateComposite();
while (parent != null) {
for (DocumentNode parentBlock : findCorrespondingTextBlocks(parent)) {
if (TbNavigationUtil.getUltraRoot(parentBlock).equals(rootTuple.textBlock)) {
return parent;
}
}
parent = (RefObject) parent.refImmediateComposite();
}
return rootTuple.modelElement; // fallback if we did not return earlier
}
private List<ModelElementDocumentNodeChangeDescriptor> getCorrespondingTextBlockListOfComposite(
RootElementTextBlockTuple rootTuple) {
if (!correspondingTextBlocksPerRootObject.containsKey(rootTuple)) {
correspondingTextBlocksPerRootObject.put(rootTuple, new LinkedList<ModelElementDocumentNodeChangeDescriptor>());
}
return correspondingTextBlocksPerRootObject.get(rootTuple);
}
private List<ModelElementDocumentNodeChangeDescriptor> getReferencingTextBlockListOfComposite(
RootElementTextBlockTuple rootTuple) {
if (!referencingTextBlocksPerRootObject.containsKey(rootTuple)) {
referencingTextBlocksPerRootObject.put(rootTuple, new LinkedList<ModelElementDocumentNodeChangeDescriptor>());
}
return referencingTextBlocksPerRootObject.get(rootTuple);
}
public Map<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> getTextBlocksNeedingPrettyPrinting() {
Map<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> cpy = new HashMap<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>>(
correspondingTextBlocksPerRootObject);
for (Entry<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> entry : cpy.entrySet()) {
cpy.put(entry.getKey(), new LinkedList<ModelElementDocumentNodeChangeDescriptor>(entry.getValue()));
}
return cpy;
}
public Map<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> getTextBlocksNeedingShortPrettyPrinting() {
Map<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> cpy = new HashMap<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>>(
referencingTextBlocksPerRootObject);
for (Entry<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> entry : cpy.entrySet()) {
cpy.put(entry.getKey(), new LinkedList<ModelElementDocumentNodeChangeDescriptor>(entry.getValue()));
}
return cpy;
}
}