/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun * Microsystems, Inc. All Rights Reserved. */ package org.netbeans.editor.ext; import org.netbeans.editor.*; import javax.swing.text.Document; import javax.swing.text.Position; /** * Format support presents a set of operations over the format-writer * that is specific for the given set of formatting layers. * It presents the way how to extend the low level methods * offered by the format-writer. * In general there can be more format-layers that use one type * of the format-support. * * @author Miloslav Metelka * @version 1.00 */ public class FormatSupport { /** Format-writer over which this support is constructed. */ private FormatWriter formatWriter; public FormatSupport(FormatWriter formatWriter) { this.formatWriter = formatWriter; } /** Getter for the format-writer associated with this format-support. */ public FormatWriter getFormatWriter() { return formatWriter; } public int getTabSize() { Document doc = formatWriter.getDocument(); return (doc instanceof BaseDocument) ? ((BaseDocument)doc).getTabSize() : formatWriter.getFormatter().getTabSize(); } public int getShiftWidth() { Document doc = formatWriter.getDocument(); return (doc instanceof BaseDocument) ? ((BaseDocument)doc).getShiftWidth() : formatWriter.getFormatter().getShiftWidth(); } public boolean expandTabs() { return formatWriter.getFormatter().expandTabs(); } public int getSpacesPerTab() { return formatWriter.getFormatter().getSpacesPerTab(); } public Object getSettingValue(String settingName) { return formatWriter.getFormatter().getSettingValue(settingName); } public Object getSettingValue(String settingName, Object defaultValue) { Object value = getSettingValue(settingName); return (value != null) ? value : defaultValue; } public boolean getSettingBoolean(String settingName, Boolean defaultValue) { return ((Boolean)getSettingValue(settingName, defaultValue)).booleanValue(); } public boolean getSettingBoolean(String settingName, boolean defaultValue) { return ((Boolean)getSettingValue(settingName, (defaultValue ? Boolean.TRUE : Boolean.FALSE))).booleanValue(); } public int getSettingInteger(String settingName, Integer defaultValue) { return ((Integer)getSettingValue(settingName, defaultValue)).intValue(); } public int getSettingInteger(String settingName, int defaultValue) { Object value = getSettingValue(settingName); return (value instanceof Integer) ? ((Integer)value).intValue() : defaultValue; } /** Delegation to the same method in format-writer. */ public final boolean isIndentOnly() { return formatWriter.isIndentOnly(); } /** Delegation to the same method in format-writer. */ public FormatTokenPosition getFormatStartPosition() { return formatWriter.getFormatStartPosition(); } public FormatTokenPosition getTextStartPosition() { return formatWriter.getTextStartPosition(); } /** Get the first token in chain. */ public TokenItem findFirstToken(TokenItem token) { return formatWriter.findFirstToken(token); } /** Delegation to the same method in format-writer. */ public TokenItem getLastToken() { return formatWriter.getLastToken(); } public FormatTokenPosition getLastPosition() { TokenItem lt = findNonEmptyToken(getLastToken(), true); return (lt == null) ? null : getPosition(lt, lt.getImage().length() - 1); } /** Delegation to the same method in format-writer. */ public boolean canInsertToken(TokenItem beforeToken) { return formatWriter.canInsertToken(beforeToken); } /** Delegation to the same method in format-writer. */ public TokenItem insertToken(TokenItem beforeToken, TokenID tokenID, TokenContextPath tokenContextPath, String tokenImage) { return formatWriter.insertToken(beforeToken, tokenID, tokenContextPath, tokenImage); } public void insertSpaces(TokenItem beforeToken, int spaceCount) { TokenID whitespaceTokenID = getWhitespaceTokenID(); if (whitespaceTokenID == null) { throw new IllegalStateException("Valid whitespace token-id required."); // NOI18N } insertToken(beforeToken, whitespaceTokenID, null, new String(Analyzer.getSpacesBuffer(spaceCount), 0, spaceCount)); } /** Whether the token-item can be removed. It can be removed * only in case it doesn't come from the document's text * and it wasn't yet written to the underlying writer. */ public boolean canRemoveToken(TokenItem token) { return formatWriter.canRemoveToken(token); } /** Remove the token-item from the chain. It can be removed * only in case it doesn't come from the document's text * and it wasn't yet written to the underlying writer. */ public void removeToken(TokenItem token) { formatWriter.removeToken(token); } /** Remove all the tokens between start and end token inclusive. */ public void removeTokenChain(TokenItem startToken, TokenItem endToken) { while (startToken != null && startToken != endToken) { TokenItem t = startToken.getNext(); removeToken(startToken); startToken = t; } } public TokenItem splitStart(TokenItem token, int startLength, TokenID newTokenID, TokenContextPath newTokenContextPath) { return formatWriter.splitStart(token, startLength, newTokenID, newTokenContextPath); } public TokenItem splitEnd(TokenItem token, int endLength, TokenID newTokenID, TokenContextPath newTokenContextPath) { return formatWriter.splitEnd(token, endLength, newTokenID, newTokenContextPath); } public void insertString(TokenItem token, int offset, String text) { formatWriter.insertString(token, offset, text); } public void insertString(FormatTokenPosition pos, String text) { TokenItem token = pos.getToken(); int offset = pos.getOffset(); if (token == null) { // ending position token = getLastToken(); if (token == null) { throw new IllegalStateException("Cannot insert string. No tokens."); } offset = token.getImage().length(); } insertString(token, offset, text); } public void remove(TokenItem token, int offset, int length) { formatWriter.remove(token, offset, length); } public void remove(FormatTokenPosition pos, int length) { remove(pos.getToken(), pos.getOffset(), length); } /** Check whether the given token has empty text and if so * start searching for token with non-empty text in the given * direction. If there's no non-empty token in the given direction * the method returns null. */ public TokenItem findNonEmptyToken(TokenItem token, boolean backward) { return formatWriter.findNonEmptyToken(token, backward); } /** Get the token position that corresponds to the given token and offset. * @param token token for which the token-position is being created. * @param offset offset inside the token. */ public FormatTokenPosition getPosition(TokenItem token, int offset) { return getPosition(token, offset, Position.Bias.Forward); } public FormatTokenPosition getPosition(TokenItem token, int offset, Position.Bias bias) { return formatWriter.getPosition(token, offset, bias); } /** Get the next position of the position given by parameters. * It can be either just offset increasing but it can be movement * to the next token for the token boundary. * @return next token-position or null for the EOT position */ public FormatTokenPosition getNextPosition(TokenItem token, int offset, Position.Bias bias) { if (token == null) { // end of chain return null; } else { // regular token offset++; if (offset >= token.getImage().length()) { token = token.getNext(); offset = 0; } return getPosition(token, offset, bias); } } /** Get the previous position of the position given by parameters. * It can be either just offset decreasing but it can be movement * to the previous token for the token boundary. * @return next token-position or null for the first position in the chain */ public FormatTokenPosition getPreviousPosition(TokenItem token, int offset, Position.Bias bias) { FormatTokenPosition ret = null; if (token == null) { // end of chain TokenItem lastToken = findNonEmptyToken(getLastToken(), true); if (lastToken != null) { // regular last token ret = getPosition(lastToken, lastToken.getImage().length() - 1, Position.Bias.Forward); } } else { // regular token offset--; if (offset < 0) { token = token.getPrevious(); if (token != null) { // was first pos in first token ret = getPosition(token, token.getImage().length() - 1, Position.Bias.Forward); } } else { // still inside token ret = getPosition(token, offset, Position.Bias.Forward); } } return ret; } /** Get the token-position preceeding the given one. Use the same * bias like the given position has. */ public FormatTokenPosition getPreviousPosition(FormatTokenPosition pos) { return getPreviousPosition(pos.getToken(), pos.getOffset(), pos.getBias()); } /** Get the token-position preceeding the given one. * @param bias bias that the returned position will have. */ public FormatTokenPosition getPreviousPosition(FormatTokenPosition pos, Position.Bias bias) { return getPreviousPosition(pos.getToken(), pos.getOffset(), bias); } public FormatTokenPosition getPreviousPosition(TokenItem token, int offset) { return getPreviousPosition(token, offset, Position.Bias.Forward); } /** Get the next successive token-position after the given one. * Use the same bias like the given position has. */ public FormatTokenPosition getNextPosition(FormatTokenPosition pos) { return getNextPosition(pos.getToken(), pos.getOffset(), pos.getBias()); } /** Get the token-position preceeding the given one. * @param bias bias that the returned position will have. */ public FormatTokenPosition getNextPosition(FormatTokenPosition pos, Position.Bias bias) { return getNextPosition(pos.getToken(), pos.getOffset(), bias); } public FormatTokenPosition getNextPosition(TokenItem token, int offset) { return getNextPosition(token, offset, Position.Bias.Forward); } public boolean isAfter(TokenItem testedToken, TokenItem afterToken) { return formatWriter.isAfter(testedToken, afterToken); } public boolean isAfter(FormatTokenPosition testedPosition, FormatTokenPosition afterPosition) { return formatWriter.isAfter(testedPosition, afterPosition); } public boolean isChainStartPosition(FormatTokenPosition pos) { return formatWriter.isChainStartPosition(pos); } /** Whether the given token can be replaced or not. It's * identical to whether the token can be removed. */ public boolean canReplaceToken(TokenItem token) { return canRemoveToken(token); } /** Replace the given token with the new token. * @param originalToken original token to be replaced. * @param tokenID token-id of the new token-item * @param tokenContextPath token-context-path of the new token-item * @param tokenImage token-text of the new token-item */ public void replaceToken(TokenItem originalToken, TokenID tokenID, TokenContextPath tokenContextPath, String tokenImage) { if (!canReplaceToken(originalToken)) { throw new IllegalStateException("Cannot insert token into chain"); } TokenItem next = originalToken.getNext(); removeToken(originalToken); insertToken(next, tokenID, tokenContextPath, tokenImage); } /** Delegation to the same method in format-writer. */ public boolean isRestartFormat() { return formatWriter.isRestartFormat(); } /** Delegation to the same method in format-writer. */ public void setRestartFormat(boolean restartFormat) { formatWriter.setRestartFormat(restartFormat); } /** Delegation to the same method in format-writer. */ public int getIndentShift() { return formatWriter.getIndentShift(); } /** Delegation to the same method in format-writer. */ public void setIndentShift(int indentShift) { formatWriter.setIndentShift(indentShift); } /** Compare token-id of the compare-token with the given token-id. * Token text and token-context-path are ignored in comparison. * @param compareToken token to compare * @param withTokenID token-id with which the token's token-id is compared * @return true if the token-ids match, false otherwise */ public boolean tokenEquals(TokenItem compareToken, TokenID withTokenID) { return tokenEquals(compareToken, withTokenID, null, null); } /** Compare token-id of the compare-token with the given token-id and * token-context-path. Token text is ignored in comparison. * @param compareToken token to compare * @param withTokenID token-id with which the token's token-id is compared. * @param withTokenContextPath token-context-path to which * the token's token-context-path is compared. * @return true if the token-ids match, false otherwise */ public boolean tokenEquals(TokenItem compareToken, TokenID withTokenID, TokenContextPath withTokenContextPath) { return tokenEquals(compareToken, withTokenID, withTokenContextPath, null); } /** Compare token-id of the compare-token with the given token-id and * given token-text. * @param compareToken token to compare * @param withTokenID token-id with which the token's token-id is compared. * It can be null in which case the token-id is ignored from comparison. * @param withTokenContextPath token-context-path to which * the token's token-context-path is compared. * It can be null in which case the token-context-path is ignored from comparison. * @param withTokenImage token-text with which the token's token-text is compared. * It can be null in which case the token-text is ignored from comparison. * @return true if the token-ids and token-texts match, false otherwise */ public boolean tokenEquals(TokenItem compareToken, TokenID withTokenID, TokenContextPath withTokenContextPath, String withTokenImage) { return (withTokenID == null || compareToken.getTokenID() == withTokenID) && (withTokenContextPath == null || compareToken.getTokenContextPath() == withTokenContextPath) && (withTokenImage == null || compareToken.getImage().equals(withTokenImage)); } /** Decide whether the character at the given offset in the given token * is whitespace. */ public boolean isWhitespace(TokenItem token, int offset) { return Character.isWhitespace(token.getImage().charAt(offset)); } public boolean isWhitespace(FormatTokenPosition pos) { return isWhitespace(pos.getToken(), pos.getOffset()); } /** Get the starting position of the line. It searches for the new-line * character in backward direction and returns the position * of the character following * the new-line character or the first character of the first token * in the chain. * @param pos any token-position on the line. */ public FormatTokenPosition findLineStart(FormatTokenPosition pos) { if (isChainStartPosition(pos)) { // begining of the chain return pos; } // Go to the previous char pos = getPreviousPosition(pos); TokenItem token = pos.getToken(); int offset = pos.getOffset(); while (true) { String text = token.getImage(); while (offset >= 0) { if (text.charAt(offset) == '\n') { return getNextPosition(token, offset); } offset--; } if (token.getPrevious() == null) { // This is the first token in chain, return position 0 return getPosition(token, 0); } token = token.getPrevious(); offset = token.getImage().length() - 1; } } /** Get the ending position of the line. It searches for the new-line * character and returns the position of it * or the position after the last character of the last token * in the chain. * @param pos any token-position on the line. */ public FormatTokenPosition findLineEnd(FormatTokenPosition pos) { TokenItem token = pos.getToken(); int offset = pos.getOffset(); if (token == null) { // end of whole chain is EOL too return pos; } while (true) { String text = token.getImage(); int textLen = text.length(); while (offset < textLen) { if (text.charAt(offset) == '\n') { return getPosition(token, offset); } offset++; } if (token.getNext() == null) { // This is the first token in chain, return end position return getPosition(null, 0); } token = token.getNext(); offset = 0; } } /** Return the first non-whitespace character on the line * or null if there is no non-WS char on the line. */ public FormatTokenPosition findLineFirstNonWhitespace(FormatTokenPosition pos) { pos = findLineStart(pos); TokenItem token = pos.getToken(); int offset = pos.getOffset(); if (token == null) { // no line start, no WS return null; } while (true) { String text = token.getImage(); int textLen = text.length(); while (offset < textLen) { if (text.charAt(offset) == '\n') { return null; } if (!isWhitespace(token, offset)) { return getPosition(token, offset); } offset++; } if (token.getNext() == null) { return null; } token = token.getNext(); offset = 0; } } /** Return the ending whitespace on the line or null * if there's no such token on the given line. */ public FormatTokenPosition findLineEndWhitespace(FormatTokenPosition pos) { pos = findLineEnd(pos); if (isChainStartPosition(pos)) { // empty first line return pos; } else { pos = getPreviousPosition(pos); } TokenItem token = pos.getToken(); int offset = pos.getOffset(); while (true) { String text = token.getImage(); int textLen = text.length(); while (offset >= 0) { if (offset < textLen && ((text.charAt(offset) == '\n') || !isWhitespace(token, offset)) ) { return getNextPosition(token, offset); } offset--; } if (token.getPrevious() == null) { // This is the first token in chain, return position 0 return getPosition(token, 0); } token = token.getPrevious(); offset = token.getImage().length() - 1; } } /** Get the first EOL in backward direction. The current position * is ignored by the search. * @return first EOL in backward direction or null if there * is no such token. */ public FormatTokenPosition findPreviousEOL(FormatTokenPosition pos) { pos = getPreviousPosition(pos); if (pos == null) { // was the start position return null; } TokenItem token = pos.getToken(); int offset = pos.getOffset(); while (true) { String text = token.getImage(); while (offset >= 0) { if (text.charAt(offset) == '\n') { return getPosition(token, offset); } offset--; } if (token.getPrevious() == null) { return null; } token = token.getPrevious(); offset = token.getImage().length() - 1; } } /** Get the first EOL in forward direction. * @param pos starting token-position that is ignored by the search * so it can be even EOL. * @return first EOL token-position in the forward direction or null if there * is no such token. */ public FormatTokenPosition findNextEOL(FormatTokenPosition pos) { pos = getNextPosition(pos); if (pos == null) { return null; } TokenItem token = pos.getToken(); int offset = pos.getOffset(); if (token == null) { // right at the end return null; } while (true) { String text = token.getImage(); int textLen = text.length(); while (offset < textLen) { if (text.charAt(offset) == '\n') { return getPosition(token, offset); } offset++; } if (token.getNext() == null) { return null; } token = token.getNext(); offset = 0; } } /** Check whether there are no tokens except the ending EOL * on the given line. * @param pos any position on the line */ public boolean isLineEmpty(FormatTokenPosition pos) { return findLineStart(pos).equals(findLineEnd(pos)); } /** Check whether there are only the whitespace tokens * on the given line. * @param token any token on the line. It doesn't have to be the first one. */ public boolean isLineWhite(FormatTokenPosition pos) { FormatTokenPosition lineStart = findLineStart(pos); return findLineEndWhitespace(pos).equals(lineStart); } /** Get the column-offset of the tokenItem on its line. The tabs * are expanded according to the tab-size. */ public int getVisualColumnOffset(FormatTokenPosition pos) { TokenItem targetToken = pos.getToken(); int targetOffset = pos.getOffset(); FormatTokenPosition lineStart = findLineStart(pos); TokenItem token = lineStart.getToken(); int offset = lineStart.getOffset(); int col = 0; int tabSize = formatWriter.getFormatter().getTabSize(); while (token != null) { String text = token.getImage(); int textLen = text.length(); while (offset < textLen) { if (token == targetToken && offset == targetOffset) { return col; } switch (text.charAt(offset)) { case '\t': col = (col + tabSize) / tabSize * tabSize; break; default: col++; } offset++; } token = token.getNext(); offset = 0; } return col; } /** Get the first non-whitespace position in the given direction. * @param startPosition position at which the search starts. * For the backward search the character right at startPosition * is not considered as part of the search. * @param limitPosition the token where the search will be broken * reporting that nothing was found. It can be null to search * till the end or begining of the chain (depending on direction). * For forward search the char at the limitPosition * is not considered to be part of search, * but for backward search it is. * @param stopOnEOL whether stop and return EOL position * or continue search if EOL token is found. * @param backward whether search in backward direction. * @return first non-whitespace position or EOL or null if all the tokens * till the begining of the chain are whitespaces. */ public FormatTokenPosition findNonWhitespace(FormatTokenPosition startPosition, FormatTokenPosition limitPosition, boolean stopOnEOL, boolean backward) { // Return immediately for equal positions if (startPosition.equals(limitPosition)) { return null; } if (backward) { // Backward search TokenItem limitToken; int limitOffset; if (limitPosition == null) { limitToken = null; limitOffset = 0; } else { // valid limit position limitPosition = getPreviousPosition(limitPosition); if (limitPosition == null) { limitToken = null; limitOffset = 0; } else { // valid limit position limitToken = limitPosition.getToken(); limitOffset = limitPosition.getOffset(); } } startPosition = getPreviousPosition(startPosition); if (startPosition == null) { return null; } TokenItem token = startPosition.getToken(); int offset = startPosition.getOffset(); while (true) { String text = token.getImage(); while (offset >= 0) { if (stopOnEOL && text.charAt(offset) == '\n') { return null; } if (!isWhitespace(token, offset)) { return getPosition(token, offset); } if (token == limitToken && offset == limitOffset) { return null; } offset--; } token = token.getPrevious(); if (token == null) { return null; } offset = token.getImage().length() - 1; } } else { // Forward direction TokenItem limitToken; int limitOffset; if (limitPosition == null) { limitToken = null; limitOffset = 0; } else { // valid limit position limitToken = limitPosition.getToken(); limitOffset = limitPosition.getOffset(); } TokenItem token = startPosition.getToken(); int offset = startPosition.getOffset(); while (true) { String text = token.getImage(); int textLen = text.length(); while (offset < textLen) { if (token == limitToken && offset == limitOffset) { return null; } if (stopOnEOL && text.charAt(offset) == '\n') { return null; } if (!isWhitespace(token, offset)) { return getPosition(token, offset); } offset++; } token = token.getNext(); if (token == null) { return null; } offset = 0; } } } /** Get the previous token or last token if the argument is null. */ public TokenItem getPreviousToken(TokenItem token) { return (token == null) ? getLastToken() : token.getPrevious(); } /** Get the token-id that should be assigned to the token * that consists of the indentation whitespace only. This method should * be overriden in the descendants. */ public TokenID getWhitespaceTokenID() { return null; } /** Get the valid whitespace token-id by calling <tt>getWhitespaceTokenID()</tt>. * Throw <tt>IllegalStateException</tt> if the whitespace-token-id is null. */ public TokenID getValidWhitespaceTokenID() { TokenID wsID = getWhitespaceTokenID(); if (wsID == null) { throw new IllegalStateException("Null whitespace token-id"); } return wsID; } /** Get the token-context-path that should be assigned to the token * that consists of the indentation whitespace only. This method should * be overriden in the descendants. */ public TokenContextPath getWhitespaceTokenContextPath() { return null; } /** Get the valid whitespace token-context-path * by calling <tt>getWhitespaceTokenContextPath()</tt>. * Throw <tt>IllegalStateException</tt> if the whitespace-token-id is null. */ public TokenContextPath getValidWhitespaceTokenContextPath() { TokenContextPath wsTCP = getWhitespaceTokenContextPath(); if (wsTCP == null) { throw new IllegalStateException("Null whitespace token-context-path"); } return wsTCP; } /** Check whether the given token enables modifying * of a whitespace in it. This method should be overriden * in the descendants. */ public boolean canModifyWhitespace(TokenItem inToken) { return false; } /** This delegates to the same method in formatter. */ public String getIndentString(int indent) { return formatWriter.getFormatter().getIndentString(indent); } /** Get the indentation of the line. * @param formatTokenPosition any position on the line. * It doesn't have to be the first one. * @param zeroForWSLine If set to true the method will return zero * in case the line consist of whitespace only. If false * the method will return the indentation even for whitespace * lines. */ public int getLineIndent(FormatTokenPosition pos, boolean zeroForWSLine) { FormatTokenPosition firstNWS = findLineFirstNonWhitespace(pos); if (firstNWS == null) { // no non-WS char on the line if (zeroForWSLine) { return 0; } else { // return indent even for WS lines firstNWS = findLineEnd(pos); } } return getVisualColumnOffset(firstNWS); } /** Change the indentation of the line. This method should * be always called for all the lines because it ensures * that the indentation will contain exactly the characters from * the indentation string. * @param pos any position on the line being checked. * @param indent the indentation for the line. * @return some position on the line */ public FormatTokenPosition changeLineIndent(FormatTokenPosition pos, int indent) { pos = findLineStart(pos); // go to line begining String indentString = getIndentString(indent); int indentStringLen = indentString.length(); int indentStringInd = 0; // current index in the indentString TokenItem token = pos.getToken(); int offset = pos.getOffset(); if (token == null) { // last line is empty, append the indent string if (indentString.length() > 0) { token = insertToken(null, getValidWhitespaceTokenID(), getValidWhitespaceTokenContextPath(), indentString); } return pos; // return original end-of-chain position } while (true) { String text = token.getImage(); int textLen = text.length(); while (indentStringInd < indentStringLen && offset < textLen) { if (indentString.charAt(indentStringInd) != text.charAt(offset)) { if (canModifyWhitespace(token)) { // modify token text to insert the whitespace insertString(token, offset, indentString.substring(indentStringInd)); offset += indentStringLen - indentStringInd; // skip WS indentStringInd = indentStringLen; } else { // cannot modify the whitespace of this token if (isWhitespace(token, offset) || offset > 0) { throw new IllegalStateException( "Cannot modify token=" + token); // NOI18N } else { // nonWS token at begining, will insert WS insertToken(token, getValidWhitespaceTokenID(), getValidWhitespaceTokenContextPath(), indentString.substring(indentStringInd) ); return getPosition(token, 0); } } } else { // current char matches indentString indentStringInd++; // advance inside indentString offset++; } } if (indentStringInd < indentStringLen) { // move to next token token = token.getNext(); if (token == null) { // was last token, insert WS token token = insertToken(null, getValidWhitespaceTokenID(), getValidWhitespaceTokenContextPath(), indentString.substring(indentStringInd) ); return getPosition(token, 0); } else { // non-null token offset = 0; } } else { // indent already done, need to remove all the resting WS while (true) { text = token.getImage(); textLen = text.length(); int removeInd = -1; while (offset < textLen) { if (!isWhitespace(token, offset) || text.charAt(offset) == '\n') { if (removeInd >= 0) { remove(token, removeInd, offset - removeInd); offset = removeInd; } return getPosition(token, offset); } else { // whitespace char found if (removeInd < 0) { removeInd = offset; } } offset++; } if (removeInd == -1) { // nothing to remove token = token.getNext(); // was right at the end } else if (removeInd == 0) { // remove whole token TokenItem nextToken = token.getNext(); removeToken(token); token = nextToken; } else { // remove just end part of token remove(token, removeInd, textLen - removeInd); token = token.getNext(); } offset = 0; if (token == null) { return getPosition(null, 0); } } } } } /** Debug the current state of the chain. * @param token mark this token as current one. It can be null. */ public String chainToString(TokenItem token) { return formatWriter.chainToString(token); } public String chainToString(TokenItem token, int maxDocumentTokens) { return formatWriter.chainToString(token, maxDocumentTokens); } }