package com.sap.furcas.runtime.textblocks.model; import java.util.ArrayList; import java.util.List; 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.furcas.metamodel.FURCAS.textblocks.Version; import com.sap.furcas.runtime.textblocks.TbUtil; public class VersionedTextBlockNavigator { private Version activeVersion = Version.REFERENCE; public VersionedTextBlockNavigator(Version activeVersion) { super(); this.activeVersion = activeVersion; } /** * @return the activeVersion */ public Version getActiveVersion() { return activeVersion; } /** * @param activeVersion the activeVersion to set */ public void setActiveVersion(Version activeVersion) { this.activeVersion = activeVersion; } /** * Searches the floor DocumentNode compared by it's offset. * * Assumes, that the list is already sorted, or the results are undefined. * Assumes, that the list implements RandomAccess to run in lg(n). * * @param nodes * list of nodes to search * @param offset * offset, to get floor entry of * @return nearest DocumentNode in the list, that has an offset smaller or * equal than offset. if all elements in the list are larger, * returns null */ public <Type extends DocumentNode> Type floorSearchByOffset( List<Type> nodes, int offset) { int low = 0; int high = nodes.size() - 1; int mid; Type floorEntry = null; while (low <= high) { mid = (low + high) / 2; Type midNode = nodes.get(mid); if (midNode.getOffset() > offset) { high = mid - 1; } else if (midNode.getOffset() < offset) { low = mid + 1; floorEntry = midNode; } else { return midNode; } } return floorEntry; } /** * Returns the token at the offset, or the last token before the offset. * Uses a binary search algorithm, as the subnodes are always sorted. * * Important: ignores the bos and eos tokens of the root-block. * * @param block * TextBlock to start looking * @param offset * offset relative to the textblock. equals to absolute offset if * passed the rootblock. * @return token at the offset, or last token before offset, or null, if no * tokens were found before the offset */ public AbstractToken getFloorToken(TextBlock block, int offset) { DocumentNode floorToken = null; if (block.getParent() == null) { // root block, don't search bos and eos floorToken = floorSearchByOffset(TbUtil.withoutBosEos(getTokens(block)), offset); } else { floorToken = floorSearchByOffset(getTokens(block), offset); } DocumentNode floorBlock = floorSearchByOffset(getSubBlocks(block), offset); if (floorToken != null) { if (floorBlock != null) { // both floor token and block found if (floorToken.getOffset() < floorBlock.getOffset()) { AbstractToken subFloorToken = getFloorToken((TextBlock) floorBlock, offset - floorBlock.getOffset()); // with gaps, it may happen the subBlock has no tokens at that position if (subFloorToken == null) { return (AbstractToken) floorToken; } return subFloorToken; } } return (AbstractToken) floorToken; } else { if (floorBlock != null) { // only floor block exists return getFloorToken((TextBlock) floorBlock, offset - floorBlock.getOffset()); } } // no floor token exists return null; } private List<TextBlock> getSubBlocks(TextBlock block) { ArrayList<TextBlock> result = new ArrayList<TextBlock>(); for (DocumentNode node : block.getSubNodes()) { if (node instanceof TextBlock) { result.add((TextBlock) node); } } return result; } private List<AbstractToken> getTokens(TextBlock block) { ArrayList<AbstractToken> result = new ArrayList<AbstractToken>(); for (DocumentNode node : block.getSubNodes()) { if (node instanceof AbstractToken) { result.add((AbstractToken) node); } } return result; } /** * Returns the token at the offset. * Uses a binary search algorithm, as the subnodes are always sorted. * * Important: ignores the bos and eos tokens of the root-block. * * @param currentBlock * TextBlock to start looking * @param offset * offset relative to the textblock. equals to absolute offset if * passed the rootblock. * @return token at the offset, or last token before offset, or null, if no * tokens were found before the offset */ public DocumentNode getLeafNode(TextBlock currentBlock, int offset) { if (currentBlock == null) { return null; // int blockOffset = getAbsoluteOffset(block); // if (blockOffset>offset || blockOffset + block.getLength() < offset) return null; // or rather illegalArgument? } DocumentNode floorToken = null; if (currentBlock.getParent() == null) { // root block, don't search bos and eos floorToken = floorSearchByOffset(TbUtil.withoutBosEos(getTokens(currentBlock)), offset); } else { floorToken = floorSearchByOffset(getTokens(currentBlock), offset); } DocumentNode floorBlock = floorSearchByOffset(getSubBlocks(currentBlock), offset); if (floorToken != null) { if (floorBlock != null) { // both floor token and block found if (floorToken.getOffset() < floorBlock.getOffset()) { // floorblock after floortoken, ignore token DocumentNode subLeafNode = getLeafNode((TextBlock) floorBlock, offset - floorBlock.getOffset()); // with gaps, it may happen the subBlock has no tokens at that position if (subLeafNode == null || subLeafNode == floorBlock) { if (floorBlock.getOffset() + floorBlock.getLength() > offset) { return floorBlock; // this is the difference to getFloorToken } else { return currentBlock; } } return subLeafNode; } } if (floorToken.getOffset() <= offset && floorToken.getOffset() + floorToken.getLength() > offset) { return floorToken; } return currentBlock; } else { // no floor token found in this block if (floorBlock != null) { // only floor block exists if (floorBlock.getOffset() <= offset && floorBlock.getOffset() + floorBlock.getLength() > offset) { DocumentNode subLeafNode = getLeafNode((TextBlock) floorBlock, offset - floorBlock.getOffset()); if (subLeafNode != null) { return subLeafNode; } else { return floorBlock; } } else { // floor block is not leaf at offset position return currentBlock; } } else { // neither subtoken nor subBlock found within this block return currentBlock; } } } }