/** * */ package com.sap.furcas.runtime.textblocks; import java.util.List; 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.TextBlock; /** * util class performing navigation operations in TextBlocks tree, which can be * complex because of the nature of the tree. Special care has to be taken to * avoid performance issues, since most operations perform badly when invoked on * TextBlocks with hundreds of subnodes. * * This class assumes that there are no empty blocks (with no sub-blocks and * sub-tokens). */ public class TbNavigationUtil { public static AbstractToken firstToken(DocumentNode node) { while (!isToken(node) && node != null) { node = getSubNodeAt(((TextBlock) node), 0); } return (AbstractToken) node; } public static AbstractToken firstTokenWithoutBOS(DocumentNode node) { while (!isToken(node) && node != null) { DocumentNode newNode = null; int index = 0; newNode = getSubNodeAt(((TextBlock) node), index++); if (newNode instanceof Bostoken) { newNode = getSubNodeAt(((TextBlock) node), index++); } while(newNode != null && newNode instanceof TextBlock && getSubNodesSize((TextBlock)newNode) == 0) { newNode = getSubNodeAt(((TextBlock) node), index++); if(newNode == null) { //only one empty block contained in the block newNode = ((TextBlock)node).getParent(); if(((TextBlock)newNode).getParent() != null) { index = getSubNodesIndex(newNode) + 1; DocumentNode siblingNode = getSubNodeAt(((TextBlock)newNode).getParent(), index); while(siblingNode == null && ((TextBlock)newNode).getParent() != null) { newNode = ((TextBlock)newNode).getParent(); index = getSubNodesIndex(newNode) + 1; siblingNode = getSubNodeAt(((TextBlock)newNode).getParent(), index); } if(siblingNode != null) { newNode = siblingNode; } } } } node = newNode; } return (AbstractToken) node; } /** * Returns the last token of the subtree. * * @param node * @param version * @return */ public static AbstractToken lastToken(DocumentNode node) { while (!isToken(node)) { // node is textBlock int size = getSubNodesSize(((TextBlock) node)); // List<DocumentNode> subNodes = (List<DocumentNode>) // getSubNodes(((TextBlock) node)); node = getSubNodeAt(((TextBlock) node), size - 1); } return (AbstractToken) node; } /** * Fetches the next sibling element within a subtree for element * <code>node</code> * * @param node * @return */ public static DocumentNode getNextInSubTree(DocumentNode node) { // root node has no siblings if (node == null || node.getParent() == null) { return null; } int index = node.getParent().getSubNodes().indexOf(node); return getSubNodeAt(node.getParent(), index + 1); } /** * Returns a merged list of elements from tokens and subBlocks of a given * textblock sorting them using their offsets (ascending). * * Sorting assumes that the blocks and the tokens are already sorted * according to their offset (ascending). * * @param textBlock * @return */ public static List<? extends DocumentNode> getSubNodes(TextBlock textBlock) { return textBlock.getSubNodes(); } public static int getSubNodesIndex(DocumentNode searchNode) { TextBlock parent = searchNode.getParent(); if (parent == null) { // this is the root node return 0; } return parent.getSubNodes().indexOf(searchNode); } public static DocumentNode getSubNodeAt(TextBlock textBlock, int searchIndex) { if (textBlock == null) { return null; } if (searchIndex > textBlock.getSubNodes().size() - 1 || searchIndex < 0) { return null; } return textBlock.getSubNodes().get(searchIndex); } public static int getSubNodesSize(TextBlock textBlock) { return textBlock.getSubNodes().size(); } /** * * @param node * @return <code>true</code> if node is the last in its parents child * elements, <code>false</code> else. */ public static boolean isLastInSubTree(DocumentNode node) { if (node.getParent() == null) { return true; } TextBlock textBlock = node.getParent(); DocumentNode lastInTree = textBlock.getSubNodes().get(textBlock.getSubNodes().size()-1); return node.equals(lastInTree); } /** * Fetches the next token within the token stream by traversing the node * tree depth first. * * @param tok * @return */ public static AbstractToken nextToken(AbstractToken tok) { TextBlock parent = tok.getParent(); if (parent == null) { return null; } if (isLastInSubTree(tok)) { while (parent != null && isLastInSubTree(parent)) { // its the last element, so traverse to the next subtree and // find the first leaf there parent = parent.getParent(); } DocumentNode parentNextSibling = getNextInSubTree(parent); if (parentNextSibling == null) { // we're at root node, no more tokens return null; } return firstToken(parentNextSibling); } else { DocumentNode nextSibling = getNextInSubTree(tok); if (!isToken(nextSibling)) { return firstToken(nextSibling); } return (AbstractToken) nextSibling; } } /** * Fetch the previous token from the token stream by traversing the node * tree depths first. * * @param tok * @return */ public static AbstractToken previousToken(AbstractToken tok) { TextBlock parent = tok.getParent(); if (isFirstInSubTree(tok)) { while (parent.getParent() != null && isFirstInSubTree(parent)) { // its the first element, so traverse to the previous subtree // and // find the last leaf there parent = parent.getParent(); } // now parent is either root or some TextBlock which is not first in // subtree DocumentNode parentPreviousSibling = getPreviousInSubTree(parent); if (parentPreviousSibling == null) { // we're at root node, no more tokens return null; } return lastToken(parentPreviousSibling); } else { DocumentNode previousSibling = getPreviousInSubTree(tok); if (!isToken(previousSibling)) { return lastToken(previousSibling); } return (AbstractToken) previousSibling; } } /** * * @param node * @return <code>true</code> if node is the first in its parents child elements, <code>false</code> else. */ public static boolean isFirstInSubTree(DocumentNode node) { if ( /* root is always first in subtree */node.getParent() == null) { return true; } else { TextBlock parent = node.getParent(); DocumentNode first = getSubNodeAt(parent, 0); // getSubNodesIndex(node) == 0; return node.equals(first); } } /** * Fetches the previous sibling element within a subtree for element <code>node</code> * * @param node * @return */ public static DocumentNode getPreviousInSubTree(DocumentNode node) { TextBlock parent = node.getParent(); if (parent == null) { return null; } int nodeIndex = getSubNodesIndex(node); return getSubNodeAt(parent, nodeIndex - 1); } public static boolean isToken(DocumentNode node) { return node instanceof AbstractToken; } /** * Replaces the subtree represented by <code>previousParentBlock</code> with the subtree represented by * <code>currentTextBlock</code> * * Cannot replace the root node. * * @param previousParentBlock * subtree to be replaced * @param currentTextBlock * subtree that will be used in the new version * * @exception IllegalArgumentException * cannot replace a root textblock */ public static void replaceSubTree(TextBlock previous, TextBlock current) throws IllegalArgumentException { TextBlock parent = previous.getParent(); if (parent != null) { int oldIndex = parent.getSubNodes().indexOf(previous); parent.getSubNodes().remove(previous); parent.getSubNodes().add(oldIndex, current); } else { throw new IllegalArgumentException("cannot replace a root textblock"); } } public static int getLevel(DocumentNode node) { // root Block is level 0 int level = 0; TextBlock parent = node.getParent(); while (parent != null) { level++; parent = parent.getParent(); } return level; } /** * Retrieves the right adjecent textblock of the given textblock. If no further block exists <code>null</code> is * returned; * * @param currentTextBlock * @return */ public static TextBlock nextBlockInSubTree(TextBlock currentTextBlock) { TextBlock parent = currentTextBlock.getParent(); if (parent == null) { return null; } List<TextBlock> subBlocks = parent.getSubBlocks(); int indexOf = subBlocks.indexOf(currentTextBlock); if (indexOf == subBlocks.size() - 1) { // it is the last block return null; } return subBlocks.get(indexOf + 1); } /** * Retrieves the left adjecent textblock of the given textblock. If no further block exists <code>null</code> is * returned; * * @param currentTextBlock * @return */ public static TextBlock previousBlockInSubTree(TextBlock currentTextBlock) { TextBlock parent = currentTextBlock.getParent(); if (parent == null) { return null; } List<TextBlock> subBlocks = parent.getSubBlocks(); int indexOf = subBlocks.indexOf(currentTextBlock); if (indexOf == 0) { // it is the last block return null; } return subBlocks.get(indexOf - 1); } /** * Navigates to the ultra root of the given node. TODO: Once added to the MM UltraRoot has to be checked here * * @param node * @return */ public static TextBlock getUltraRoot(DocumentNode node) { if (node instanceof AbstractToken) { return getUltraRoot(((AbstractToken) node).getParent()); } else if (node instanceof TextBlock) { if (isUltraRoot(node)) { return (TextBlock) node; } else { return getUltraRoot(((TextBlock) node).getParent()); } } else { throw new RuntimeException(node + "can not be asked for UltraRoot, only TextBlocks and AbstractTokens can have that."); } } /** * Checks if the given {@link TextBlock} is the root of a textblock tree. * * @param node * @return */ public static boolean isUltraRoot(DocumentNode node) { // TODO If parent instance of UltraRoot if there will be an extra class for this kind of element return node.getParent() == null; } }