0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.editor.ext;
0043:
0044: import java.io.IOException;
0045: import java.io.Writer;
0046: import java.util.List;
0047: import java.util.ArrayList;
0048: import java.util.HashMap;
0049: import java.util.Iterator;
0050: import javax.swing.text.BadLocationException;
0051: import javax.swing.text.Document;
0052: import javax.swing.text.Position;
0053: import org.netbeans.editor.BaseKit;
0054: import org.netbeans.editor.BaseDocument;
0055: import org.netbeans.editor.Syntax;
0056: import org.netbeans.editor.SettingsNames;
0057: import org.netbeans.editor.TokenID;
0058: import org.netbeans.editor.TokenContextPath;
0059: import org.netbeans.editor.TokenItem;
0060: import org.netbeans.editor.Utilities;
0061: import org.netbeans.editor.EditorDebug;
0062:
0063: /**
0064: * Formatting writter accepts the input-text, formats it
0065: * and writes the output to the underlying writer.
0066: * The data written to the writer are immediately splitted
0067: * into token-items and the chain of token-items is created
0068: * from them. Then the writer then waits for the flush() or close()
0069: * method call which then makes the real formatting.
0070: * The formatting is done through going through the format-layers
0071: * registered in <tt>ExtFormatter</tt> and asking them for formatting.
0072: * These layers go through the chain and possibly add or remove
0073: * the tokens as necessary. The good thing is that the layers
0074: * can ask the tokens before those written to the writer.
0075: * In that case they will get the tokens from the document at point
0076: * the formatting should be done. The advantage is that the chain
0077: * is compact so the border between the tokens written to the writer
0078: * and those that come from the document is invisible.
0079: *
0080: * @author Miloslav Metelka
0081: * @version 1.00
0082: */
0083:
0084: public final class FormatWriter extends Writer {
0085:
0086: /** Whether debug messages should be displayed */
0087: public static final boolean debug = Boolean
0088: .getBoolean("netbeans.debug.editor.format"); // NOI18N
0089:
0090: /** Whether debug messages should be displayed */
0091: public static final boolean debugModify = Boolean
0092: .getBoolean("netbeans.debug.editor.format.modify"); // NOI18N
0093:
0094: private static final char[] EMPTY_BUFFER = new char[0];
0095:
0096: /** Formatter related to this format-writer */
0097: private ExtFormatter formatter;
0098:
0099: /** Document being formatted */
0100: private Document doc;
0101:
0102: /** Offset at which the formatting occurs */
0103: private int offset;
0104:
0105: /** Underlying writer */
0106: private Writer underWriter;
0107:
0108: /** Syntax scanning the characters passed to the writer. For non-BaseDocuments
0109: * it also scans the characters preceding the format offset. The goal here
0110: * is to maintain the formatted tokens consistent with the scanning context
0111: * at the offset in the document. This is achieved differently
0112: * depending on whether the document is an instance of the BaseDocument
0113: * or not.
0114: * If the document is an instance of the <tt>BaseDocument</tt>,
0115: * the syntax is used solely for the scanning of the formatted tokens
0116: * and it is first prepared to scan right after the offset.
0117: * For non-BaseDocuments the syntax first scans the whole are
0118: * from the begining of the document till the offset and then continues
0119: * on with the formatted tokens.
0120: */
0121: private Syntax syntax;
0122:
0123: /** Whether the purpose is to find an indentation
0124: * instead of formatting the tokens. In this mode only the '\n' is written
0125: * to the format-writer, and the layers should insert the appropriate
0126: * white-space tokens before the '\n' that will form
0127: * the indentation of the line.
0128: */
0129: private boolean indentOnly;
0130:
0131: /** Buffer being scanned */
0132: private char[] buffer;
0133:
0134: /** Number of the valid chars in the buffer */
0135: private int bufferSize;
0136:
0137: /** Support for creating the positions. */
0138: private FormatTokenPositionSupport ftps;
0139:
0140: /** Prescan at the offset position */
0141: private int offsetPreScan;
0142:
0143: /** Whether the first flush() is being done.
0144: * it must respect the offsetPreScan.
0145: */
0146: private boolean firstFlush;
0147:
0148: /** Last token-item in the chain */
0149: private ExtTokenItem lastToken;
0150:
0151: /** Position where the formatting should start. */
0152: private FormatTokenPosition formatStartPosition;
0153:
0154: /** The first position that doesn't belong to the document. */
0155: private FormatTokenPosition textStartPosition;
0156:
0157: /** This flag is set automatically if the new removal or insertion
0158: * into chain occurs. The formatter can use this flag to detect whether
0159: * a particular format-layer changed the chain.
0160: */
0161: private boolean chainModified;
0162:
0163: /** Whether the format should be restarted. */
0164: private boolean restartFormat;
0165:
0166: /** Flag that helps to avoid unnecessary formatting when
0167: * calling flush() periodically without calling write()
0168: */
0169: private boolean lastFlush;
0170:
0171: /** Shift resulting indentation position to which the caret is moved.
0172: * By default the caret goes to the first non-whitespace character
0173: * on the formatted line. If the line is empty then to the end of the
0174: * indentation whitespace. This variable enables to move the resulting
0175: * position either left or right.
0176: */
0177: private int indentShift;
0178:
0179: /** Whether this format writer runs in the simple mode.
0180: * In simple mode the input is directly written to output.
0181: */
0182: private boolean simple;
0183:
0184: /** Added to fix #5620 */
0185: private boolean reformatting;
0186:
0187: /** Added to fix #5620 */
0188: void setReformatting(boolean reformatting) {
0189: this .reformatting = reformatting;
0190: }
0191:
0192: /** The format writers should not be extended to enable
0193: * operating of the layers on all the writers even for different
0194: * languages.
0195: * @param underWriter underlying writer
0196: */
0197: FormatWriter(ExtFormatter formatter, Document doc, int offset,
0198: Writer underWriter, boolean indentOnly) {
0199: this .formatter = formatter;
0200: this .doc = doc;
0201: this .offset = offset;
0202: this .underWriter = underWriter;
0203: this .setIndentOnly(indentOnly);
0204:
0205: if (debug) {
0206: System.err.println("FormatWriter() created, formatter="
0207: + formatter // NOI18N
0208: + ", document=" + doc.getClass()
0209: + ", expandTabs="
0210: + formatter.expandTabs() // NOI18N
0211: + ", spacesPerTab="
0212: + formatter.getSpacesPerTab() // NOI18N
0213: + ", tabSize=" + ((doc instanceof BaseDocument) // NOI18N
0214: ? ((BaseDocument) doc).getTabSize()
0215: : formatter.getTabSize()) + ", shiftWidth="
0216: + ((doc instanceof BaseDocument) // NOI18N
0217: ? ((BaseDocument) doc).getShiftWidth()
0218: : formatter.getShiftWidth()));
0219: }
0220:
0221: // Return now for simple formatter
0222: if (formatter.isSimple()) {
0223: simple = true;
0224: return;
0225: }
0226:
0227: buffer = EMPTY_BUFFER;
0228: firstFlush = true;
0229:
0230: // Hack for getting the right kit and then syntax
0231: Class kitClass = (doc instanceof BaseDocument) ? ((BaseDocument) doc)
0232: .getKitClass()
0233: : formatter.getKitClass();
0234:
0235: if (kitClass != null
0236: && BaseKit.class.isAssignableFrom(kitClass)) {
0237: syntax = BaseKit.getKit(kitClass).createFormatSyntax(doc);
0238: } else {
0239: simple = true;
0240: return;
0241: }
0242:
0243: if (!formatter.acceptSyntax(syntax)) {
0244: simple = true; // turn to simple format writer
0245: return;
0246: }
0247:
0248: ftps = new FormatTokenPositionSupport(this );
0249:
0250: if (doc instanceof BaseDocument) {
0251: try {
0252: BaseDocument bdoc = (BaseDocument) doc;
0253:
0254: /* Init syntax right at the formatting offset so it will
0255: * contain the prescan characters only. The non-last-buffer
0256: * is inforced (even when at the document end) because the
0257: * text will follow the current document text.
0258: */
0259: bdoc.getSyntaxSupport().initSyntax(syntax, offset,
0260: offset, false, true);
0261: offsetPreScan = syntax.getPreScan();
0262:
0263: if (debug) {
0264: System.err.println("FormatWriter: preScan="
0265: + offsetPreScan + " at offset=" + offset);
0266: }
0267:
0268: if (offset > 0) { // only if not formatting from the start of the document
0269: ExtSyntaxSupport sup = (ExtSyntaxSupport) bdoc
0270: .getSyntaxSupport();
0271: Integer lines = (Integer) bdoc
0272: .getProperty(SettingsNames.LINE_BATCH_SIZE);
0273:
0274: int startOffset = Utilities.getRowStart(bdoc, Math
0275: .max(offset - offsetPreScan, 0), -Math.max(
0276: lines.intValue(), 1));
0277:
0278: if (startOffset < 0) { // invalid line
0279: startOffset = 0;
0280: }
0281:
0282: // Parse tokens till the offset
0283: TokenItem ti = sup.getTokenChain(startOffset,
0284: offset);
0285:
0286: if (ti != null
0287: && ti.getOffset() < offset - offsetPreScan) {
0288: lastToken = new FilterDocumentItem(ti, null,
0289: false);
0290:
0291: if (debug) {
0292: System.err
0293: .println("FormatWriter: first doc token="
0294: + lastToken); // NOI18N
0295: }
0296:
0297: // Iterate through the chain till the last item
0298: while (lastToken.getNext() != null
0299: && lastToken.getNext().getOffset() < offset
0300: - offsetPreScan) {
0301: lastToken = (ExtTokenItem) lastToken
0302: .getNext();
0303:
0304: if (debug) {
0305: System.err
0306: .println("FormatWriter: doc token="
0307: + lastToken); // NOI18N
0308: }
0309: }
0310:
0311: // Terminate the end of chain so it doesn't try
0312: // to append the next token from the document
0313: ((FilterDocumentItem) lastToken).terminate();
0314:
0315: }
0316: }
0317:
0318: } catch (BadLocationException e) {
0319: Utilities.annotateLoggable(e);
0320: }
0321:
0322: } else { // non-BaseDocument
0323: try {
0324: String text = doc.getText(0, offset);
0325: char[] buffer = text.toCharArray();
0326:
0327: // Force non-last buffer
0328: syntax.load(null, buffer, 0, buffer.length, false, 0);
0329:
0330: TokenID tokenID = syntax.nextToken();
0331: while (tokenID != null) {
0332: int tokenOffset = syntax.getTokenOffset();
0333: lastToken = new FormatTokenItem(tokenID, syntax
0334: .getTokenContextPath(), tokenOffset, text
0335: .substring(tokenOffset, tokenOffset
0336: + syntax.getTokenLength()),
0337: lastToken);
0338:
0339: if (debug) {
0340: System.err
0341: .println("FormatWriter: non-bd token="
0342: + lastToken);
0343: }
0344:
0345: ((FormatTokenItem) lastToken).markWritten();
0346: tokenID = syntax.nextToken();
0347: }
0348:
0349: // Assign the preScan
0350: offsetPreScan = syntax.getPreScan();
0351:
0352: } catch (BadLocationException e) {
0353: Utilities.annotateLoggable(e);
0354: }
0355: }
0356:
0357: // Write the preScan characters
0358: char[] buf = syntax.getBuffer();
0359: int bufOffset = syntax.getOffset();
0360:
0361: if (debug) {
0362: System.err.println("FormatWriter: writing preScan chars='" // NOI18N
0363: + EditorDebug.debugChars(buf, bufOffset
0364: - offsetPreScan, offsetPreScan) + "'" // NOI18N
0365: + ", length=" + offsetPreScan // NOI18N
0366: );
0367: }
0368:
0369: // Write the preScan chars to the buffer
0370: addToBuffer(buf, bufOffset - offsetPreScan, offsetPreScan);
0371: }
0372:
0373: public final ExtFormatter getFormatter() {
0374: return formatter;
0375: }
0376:
0377: /** Get the document being formatted */
0378: public final Document getDocument() {
0379: return doc;
0380: }
0381:
0382: /** Get the starting offset of the formatting */
0383: public final int getOffset() {
0384: return offset;
0385: }
0386:
0387: /** Whether the purpose of this writer is to find the proper indentation
0388: * instead of formatting the tokens. It allows to have a modified
0389: * formatting behavior for the cases when user presses Enter or a key
0390: * that causes immediate reformatting of the line.
0391: */
0392: public final boolean isIndentOnly() {
0393: return indentOnly;
0394: }
0395:
0396: /** Sets whether the purpose of this writer is to find the proper indentation
0397: * instead of formatting the tokens.
0398: * @see isIndentOnly()
0399: */
0400: public void setIndentOnly(boolean indentOnly) {
0401: this .indentOnly = indentOnly;
0402: }
0403:
0404: /** Get the first token that should be formatted.
0405: * This can change as the format-layers continue to change the token-chain.
0406: * If the caller calls flush(), this method will return null. After
0407: * additional writing to the writer, new tokens will be added and
0408: * the first one of them will become the first token to be formatted.
0409: * @return the first token that should be formatted. It can be null
0410: * in case some layer removes all the tokens that should be formatted.
0411: * Most of the layers probably do nothing in case this value is null.
0412: */
0413: public FormatTokenPosition getFormatStartPosition() {
0414: return formatStartPosition;
0415: }
0416:
0417: /** Get the first position that doesn't belong to the document.
0418: * Initially it's the same as the <tt>getFormatStartPosition()</tt> but
0419: * if there are multiple flushes performed on the writer they will differ.
0420: */
0421: public FormatTokenPosition getTextStartPosition() {
0422: return textStartPosition;
0423: }
0424:
0425: /** Get the last token in the chain. It can be null
0426: * if there are no tokens in the chain.
0427: */
0428: public TokenItem getLastToken() {
0429: return lastToken;
0430: }
0431:
0432: /** Find the first token in the chain. It should be used only when necessary
0433: * and possibly in situations when the start of the chain
0434: * was already reached by other methods, because this method
0435: * will extend the chain till the begining of the document.
0436: * @param token token from which the search for previous tokens will
0437: * start. It can be null in which case the last document token or last
0438: * token are attempted instead.
0439: */
0440: public TokenItem findFirstToken(TokenItem token) {
0441: if (token == null) {
0442: // Try textStartPosition first
0443: token = (textStartPosition != null) ? textStartPosition
0444: .getToken() : null;
0445:
0446: if (token == null) {
0447: // Try starting of the formatting position next
0448: token = formatStartPosition.getToken();
0449: if (token == null) {
0450: token = lastToken;
0451: if (token == null) {
0452: return null;
0453: }
0454: }
0455: }
0456: }
0457:
0458: while (token.getPrevious() != null) {
0459: token = token.getPrevious();
0460: }
0461: return token;
0462: }
0463:
0464: /** It checks whether the tested token is after some other token in the chain.
0465: * @param testedToken token to test (whether it's after afterToken or not)
0466: * @param afterToken token to be compared to the testedToken
0467: * @return true whether the testedToken is after afterToken or not.
0468: * Returns false if the token == afterToken
0469: * or not or if token is before the afterToken or not.
0470: */
0471: public boolean isAfter(TokenItem testedToken, TokenItem afterToken) {
0472: while (afterToken != null) {
0473: afterToken = afterToken.getNext();
0474:
0475: if (afterToken == testedToken) {
0476: return true;
0477: }
0478: }
0479:
0480: return false;
0481: }
0482:
0483: /** Checks whether the tested position is after some other position. */
0484: public boolean isAfter(FormatTokenPosition testedPosition,
0485: FormatTokenPosition afterPosition) {
0486: if (testedPosition.getToken() == afterPosition.getToken()) {
0487: return (testedPosition.getOffset() > afterPosition
0488: .getOffset());
0489:
0490: } else { // different tokens
0491: return isAfter(testedPosition.getToken(), afterPosition
0492: .getToken());
0493: }
0494: }
0495:
0496: /** Check whether the given token has empty text and if so
0497: * start searching for token with non-empty text in the given
0498: * direction. If there's no non-empty token in the given direction
0499: * the method returns null.
0500: * @param token token to start to search from. If it has zero
0501: * length, the search for non-empty token is performed in the given
0502: * direction.
0503: */
0504: public TokenItem findNonEmptyToken(TokenItem token, boolean backward) {
0505: while (token != null && token.getImage().length() == 0) {
0506: token = backward ? token.getPrevious() : token.getNext();
0507: }
0508:
0509: return token;
0510: }
0511:
0512: /** Check whether a new token can be inserted into the chain
0513: * before the given token-item. The token
0514: * can be inserted only into the tokens that come
0515: * from the text that was written to the format-writer
0516: * but was not yet written to the underlying writer.
0517: * @param beforeToken token-item before which
0518: * the new token-item is about to be inserted. It can
0519: * be null to append the new token to the end of the chain.
0520: */
0521: public boolean canInsertToken(TokenItem beforeToken) {
0522: return beforeToken == null // appending to the end
0523: || !((ExtTokenItem) beforeToken).isWritten();
0524: }
0525:
0526: /** Create a new token-item and insert it before
0527: * the token-item given as parameter.
0528: * The <tt>canInsertToken()</tt> should be called
0529: * first to determine whether the given token can
0530: * be inserted into the chain or not. The token
0531: * can be inserted only into the tokens that come
0532: * from the text that was written to the format-writer
0533: * but was not yet written to the underlying writer.
0534: * @param beforeToken token-item before which
0535: * the new token-item is about to be inserted. It can
0536: * be null to append the new token to the end of the chain.
0537: * @param tokenID token-id of the new token-item
0538: * @param tokenContextPath token-context-path of the new token-item
0539: * @param tokenImage image of the new token-item
0540: */
0541: public TokenItem insertToken(TokenItem beforeToken,
0542: TokenID tokenID, TokenContextPath tokenContextPath,
0543: String tokenImage) {
0544: if (debugModify) {
0545: System.err
0546: .println("FormatWriter.insertToken(): beforeToken="
0547: + beforeToken // NOI18N
0548: + ", tokenID=" + tokenID + ", contextPath="
0549: + tokenContextPath // NOI18N
0550: + ", tokenImage='" + tokenImage + "'" // NOI18N
0551: );
0552: }
0553:
0554: if (!canInsertToken(beforeToken)) {
0555: throw new IllegalStateException(
0556: "Can't insert token into chain"); // NOI18N
0557: }
0558:
0559: // #5620
0560: if (reformatting) {
0561: try {
0562: doc.insertString(getDocOffset(beforeToken), tokenImage,
0563: null);
0564: } catch (BadLocationException e) {
0565: e.printStackTrace();
0566: }
0567: }
0568:
0569: FormatTokenItem fti;
0570: if (beforeToken != null) {
0571: fti = ((FormatTokenItem) beforeToken).insertToken(tokenID,
0572: tokenContextPath, -1, tokenImage);
0573:
0574: } else { // beforeToken is null
0575: fti = new FormatTokenItem(tokenID, tokenContextPath, -1,
0576: tokenImage, lastToken);
0577: lastToken = fti;
0578: }
0579:
0580: // Update token-positions
0581: ftps.tokenInsert(fti);
0582:
0583: chainModified = true;
0584:
0585: return fti;
0586: }
0587:
0588: /** Added to fix #5620 */
0589: private int getDocOffset(TokenItem token) {
0590: int len = 0;
0591: if (token != null) {
0592: token = token.getPrevious();
0593:
0594: } else { // after last token
0595: token = lastToken;
0596: }
0597:
0598: while (token != null) {
0599: len += token.getImage().length();
0600: if (token instanceof FilterDocumentItem) {
0601: return len + token.getOffset();
0602: }
0603: token = token.getPrevious();
0604: }
0605:
0606: return len;
0607: }
0608:
0609: /** Whether the token-item can be removed. It can be removed
0610: * only in case it doesn't come from the document's text
0611: * and it wasn't yet written to the underlying writer.
0612: */
0613: public boolean canRemoveToken(TokenItem token) {
0614: return !((ExtTokenItem) token).isWritten();
0615: }
0616:
0617: /** Remove the token-item from the chain. It can be removed
0618: * only in case it doesn't come from the document's text
0619: * and it wasn't yet written to the underlying writer.
0620: */
0621: public void removeToken(TokenItem token) {
0622: if (debugModify) {
0623: System.err.println("FormatWriter.removeToken(): token="
0624: + token); // NOI18N
0625: }
0626:
0627: if (!canRemoveToken(token)) {
0628: if (true) { // !!!
0629: return;
0630: }
0631: throw new IllegalStateException(
0632: "Can't remove token from chain"); // NOI18N
0633: }
0634:
0635: // #5620
0636: if (reformatting) {
0637: try {
0638: doc.remove(getDocOffset(token), token.getImage()
0639: .length());
0640: } catch (BadLocationException e) {
0641: e.printStackTrace();
0642: }
0643: }
0644:
0645: // Update token-positions
0646: ftps.tokenRemove(token);
0647:
0648: if (lastToken == token) {
0649: lastToken = (ExtTokenItem) token.getPrevious();
0650: }
0651: ((FormatTokenItem) token).remove(); // remove self from chain
0652:
0653: chainModified = true;
0654: }
0655:
0656: public boolean canSplitStart(TokenItem token, int startLength) {
0657: return !((ExtTokenItem) token).isWritten();
0658: }
0659:
0660: /** Create the additional token from the text at the start
0661: * of the given token.
0662: * @param token token being split.
0663: * @param startLength length of the text at the begining of the token
0664: * for which the additional token will be created.
0665: * @param tokenID token-id that will be assigned to the new token
0666: * @param tokenContextPath token-context-path that will be assigned
0667: * to the new token
0668: */
0669: public TokenItem splitStart(TokenItem token, int startLength,
0670: TokenID newTokenID, TokenContextPath newTokenContextPath) {
0671: if (!canSplitStart(token, startLength)) {
0672: throw new IllegalStateException("Can't split the token="
0673: + token); // NOI18N
0674: }
0675:
0676: String text = token.getImage();
0677: if (startLength > text.length()) {
0678: throw new IllegalArgumentException("startLength="
0679: + startLength // NOI18N
0680: + " is greater than token length=" + text.length()); // NOI18N
0681: }
0682:
0683: String newText = text.substring(0, startLength);
0684: ExtTokenItem newToken = (ExtTokenItem) insertToken(token,
0685: newTokenID, newTokenContextPath, newText);
0686:
0687: // Update token-positions
0688: ftps.splitStartTokenPositions(token, startLength);
0689:
0690: remove(token, 0, startLength);
0691: return newToken;
0692: }
0693:
0694: public boolean canSplitEnd(TokenItem token, int endLength) {
0695: int splitOffset = token.getImage().length() - endLength;
0696: return (((ExtTokenItem) token).getWrittenLength() <= splitOffset);
0697: }
0698:
0699: /** Create the additional token from the text at the end
0700: * of the given token.
0701: * @param token token being split.
0702: * @param endLength length of the text at the end of the token
0703: * for which the additional token will be created.
0704: * @param tokenID token-id that will be assigned to the new token
0705: * @param tokenContextPath token-context-path that will be assigned
0706: * to the new token
0707: */
0708: public TokenItem splitEnd(TokenItem token, int endLength,
0709: TokenID newTokenID, TokenContextPath newTokenContextPath) {
0710: if (!canSplitEnd(token, endLength)) {
0711: throw new IllegalStateException("Can't split the token="
0712: + token); // NOI18N
0713: }
0714:
0715: String text = token.getImage();
0716: if (endLength > text.length()) {
0717: throw new IllegalArgumentException("endLength=" + endLength // NOI18N
0718: + " is greater than token length=" + text.length()); // NOI18N
0719: }
0720:
0721: String newText = text.substring(0, endLength);
0722: ExtTokenItem newToken = (ExtTokenItem) insertToken(token
0723: .getNext(), newTokenID, newTokenContextPath, newText);
0724:
0725: // Update token-positions
0726: ftps.splitEndTokenPositions(token, endLength);
0727:
0728: remove(token, text.length() - endLength, endLength);
0729: return newToken;
0730: }
0731:
0732: /** Whether the token can be modified either by insertion or removal
0733: * at the given offset.
0734: */
0735: public boolean canModifyToken(TokenItem token, int offset) {
0736: int wrLen = ((ExtTokenItem) token).getWrittenLength();
0737: return (offset >= 0 && wrLen <= offset);
0738: }
0739:
0740: /** Insert the text at the offset inside the given token.
0741: * All the token-positions at and after the offset will
0742: * be increased by <tt>text.length()</tt>.
0743: * <tt>IllegalArgumentException</tt> is thrown if offset
0744: * is wrong.
0745: * @param token token in which the text is inserted.
0746: * @param offset offset at which the text will be inserted.
0747: * @param text text that will be inserted at the offset.
0748: */
0749: public void insertString(TokenItem token, int offset, String text) {
0750: // Check debugging
0751: if (debugModify) {
0752: System.err.println("FormatWriter.insertString(): token="
0753: + token // NOI18N
0754: + ", offset=" + offset + ", text='" + text + "'"); // NOI18N
0755: }
0756:
0757: // Check empty insert
0758: if (text.length() == 0) {
0759: return;
0760: }
0761:
0762: // Check whether modification is allowed
0763: if (!canModifyToken(token, offset)) {
0764: if (true) { // !!!
0765: return;
0766: }
0767: throw new IllegalStateException("Can't insert into token="
0768: + token // NOI18N
0769: + ", at offset=" + offset + ", text='" + text + "'"); // NOI18N
0770: }
0771:
0772: // #5620
0773: if (reformatting) {
0774: try {
0775: doc.insertString(getDocOffset(token) + offset, text,
0776: null);
0777: } catch (BadLocationException e) {
0778: e.printStackTrace();
0779: }
0780: }
0781:
0782: // Update token-positions
0783: ftps.tokenTextInsert(token, offset, text.length());
0784:
0785: String image = token.getImage();
0786: ((ExtTokenItem) token).setImage(image.substring(0, offset)
0787: + text + image.substring(offset));
0788: }
0789:
0790: /** Remove the length of the characters at the given
0791: * offset inside the given token.
0792: * <tt>IllegalArgumentException</tt> is thrown if offset
0793: * or length are wrong.
0794: * @param token token in which the text is removed.
0795: * @param offset offset at which the text will be removed.
0796: * @param length length of the removed text.
0797: */
0798: public void remove(TokenItem token, int offset, int length) {
0799: // Check debugging
0800: if (debugModify) {
0801: String removedText;
0802: if (offset >= 0 && length >= 0
0803: && offset + length <= token.getImage().length()) {
0804: removedText = token.getImage().substring(offset,
0805: offset + length);
0806:
0807: } else {
0808: removedText = "<INVALID>"; // NOI18N
0809: }
0810:
0811: System.err.println("FormatWriter.remove(): token=" + token // NOI18N
0812: + ", offset=" + offset + ", length=" + length // NOI18N
0813: + "removing text='" + removedText + "'"); // NOI18N
0814: }
0815:
0816: // Check empty remove
0817: if (length == 0) {
0818: return;
0819: }
0820:
0821: // Check whether modification is allowed
0822: if (!canModifyToken(token, offset)) {
0823: if (true) { // !!!
0824: return;
0825: }
0826: throw new IllegalStateException("Can't remove from token="
0827: + token // NOI18N
0828: + ", at offset=" + offset + ", length=" + length); // NOI18N
0829: }
0830:
0831: // #5620
0832: if (reformatting) {
0833: try {
0834: doc.remove(getDocOffset(token) + offset, length);
0835: } catch (BadLocationException e) {
0836: e.printStackTrace();
0837: }
0838: }
0839:
0840: // Update token-positions
0841: ftps.tokenTextRemove(token, offset, length);
0842:
0843: String text = token.getImage();
0844: ((ExtTokenItem) token).setImage(text.substring(0, offset)
0845: + text.substring(offset + length));
0846: }
0847:
0848: /** Get the token-position that corresponds to the given
0849: * offset inside the given token. The returned position is persistent
0850: * and if the token is removed from chain the position
0851: * is assigned to the end of the previous token or to the begining
0852: * of the next token if there's no previous token.
0853: * @param token token in which the position is created.
0854: * @param offset inside the token at which the position
0855: * will be created.
0856: * @param bias forward or backward bias
0857: */
0858: public FormatTokenPosition getPosition(TokenItem token, int offset,
0859: Position.Bias bias) {
0860: return ftps.getTokenPosition(token, offset, bias);
0861: }
0862:
0863: /** Check whether this is the first position in the chain of tokens. */
0864: public boolean isChainStartPosition(FormatTokenPosition pos) {
0865: TokenItem token = pos.getToken();
0866: return (pos.getOffset() == 0)
0867: && ((token == null && getLastToken() == null) // no tokens
0868: || (token != null && token.getPrevious() == null));
0869: }
0870:
0871: /** Add the given chars to the current buffer of chars to format. */
0872: private void addToBuffer(char[] buf, int off, int len) {
0873: // If necessary increase the buffer size
0874: if (len > buffer.length - bufferSize) {
0875: char[] tmp = new char[len + 2 * buffer.length];
0876: System.arraycopy(buffer, 0, tmp, 0, bufferSize);
0877: buffer = tmp;
0878: }
0879:
0880: // Copy the characters
0881: System.arraycopy(buf, off, buffer, bufferSize, len);
0882: bufferSize += len;
0883: }
0884:
0885: public void write(char[] cbuf, int off, int len) throws IOException {
0886: if (simple) {
0887: underWriter.write(cbuf, off, len);
0888: return;
0889: }
0890:
0891: write(cbuf, off, len, null, null);
0892: }
0893:
0894: public synchronized void write(char[] cbuf, int off, int len,
0895: int[] saveOffsets, Position.Bias[] saveBiases)
0896: throws IOException {
0897: if (simple) {
0898: underWriter.write(cbuf, off, len);
0899: return;
0900: }
0901:
0902: if (saveOffsets != null) {
0903: ftps.addSaveSet(bufferSize, len, saveOffsets, saveBiases);
0904: }
0905:
0906: lastFlush = false; // signal write() was the last so flush() can be done
0907:
0908: if (debug) {
0909: System.err.println("FormatWriter.write(): '" // NOI18N
0910: + org.netbeans.editor.EditorDebug.debugChars(cbuf,
0911: off, len) + "', length="
0912: + len
0913: + ", bufferSize=" + bufferSize); // NOI18N
0914: }
0915:
0916: // Add the chars to the buffer for formatting
0917: addToBuffer(cbuf, off, len);
0918: }
0919:
0920: /** Return the flag that is set automatically if the new removal or insertion
0921: * into chain occurs. The formatter can use this flag to detect whether
0922: * a particular format-layer changed the chain or not.
0923: */
0924: public boolean isChainModified() {
0925: return chainModified;
0926: }
0927:
0928: public void setChainModified(boolean chainModified) {
0929: this .chainModified = chainModified;
0930: }
0931:
0932: /** Return whether the layer requested to restart the format. The formatter
0933: * can use this flag to restart the formatting from the first layer.
0934: */
0935: public boolean isRestartFormat() {
0936: return restartFormat;
0937: }
0938:
0939: public void setRestartFormat(boolean restartFormat) {
0940: this .restartFormat = restartFormat;
0941: }
0942:
0943: public int getIndentShift() {
0944: return indentShift;
0945: }
0946:
0947: public void setIndentShift(int indentShift) {
0948: this .indentShift = indentShift;
0949: }
0950:
0951: public void flush() throws IOException {
0952: if (debug) {
0953: System.err.println("FormatWriter.flush() called"); // NOI18N
0954: }
0955:
0956: if (simple) {
0957: underWriter.flush();
0958: return;
0959: }
0960:
0961: if (lastFlush) { // flush already done
0962: return;
0963: }
0964: lastFlush = true; // flush is being done
0965:
0966: int startOffset = 0; // offset where syntax will start scanning
0967: if (firstFlush) { // must respect the offsetPreScan
0968: startOffset = offsetPreScan;
0969: }
0970:
0971: syntax.relocate(buffer, startOffset, bufferSize - startOffset,
0972: true, -1);
0973:
0974: // Reset formatStartPosition so that it will get filled with new value
0975: formatStartPosition = null;
0976:
0977: TokenID tokenID = syntax.nextToken();
0978: if (firstFlush) { // doing first flush
0979: if (startOffset > 0) { // check whether there's a preScan
0980: while (true) {
0981: String text = new String(buffer, syntax
0982: .getTokenOffset(), syntax.getTokenLength());
0983: // add a new token-item to the chain
0984: lastToken = new FormatTokenItem(tokenID, syntax
0985: .getTokenContextPath(), -1, text, lastToken);
0986:
0987: if (debug) {
0988: System.err
0989: .println("FormatWriter.flush(): doc&format token=" // NOI18N
0990: + lastToken);
0991: }
0992:
0993: // Set that it was only partially written
0994: lastToken.setWrittenLength(startOffset);
0995:
0996: // If the start position is inside this token, assign it
0997: if (text.length() > startOffset) {
0998: formatStartPosition = getPosition(lastToken,
0999: startOffset, Position.Bias.Backward);
1000: }
1001:
1002: tokenID = syntax.nextToken(); // get next token
1003:
1004: // When force last buffer is true, the XML token chain can be split
1005: // into more than one token. This does not happen for Java tokens.
1006: // Because of this split must all tokens which are part of preScan
1007: // (means which have end position smaller than startOffset), be changed to
1008: // "unmodifiable" and only the last one token will be used.
1009: // see issue 12701
1010: if (text.length() >= startOffset)
1011: break;
1012: else {
1013: lastToken.setWrittenLength(Integer.MAX_VALUE);
1014: startOffset -= text.length();
1015: }
1016: }
1017:
1018: }
1019: }
1020:
1021: while (tokenID != null) {
1022: String text = new String(buffer, syntax.getTokenOffset(),
1023: syntax.getTokenLength());
1024: // add a new token-item to the chain
1025: lastToken = new FormatTokenItem(tokenID, syntax
1026: .getTokenContextPath(), -1, text, lastToken);
1027:
1028: if (formatStartPosition == null) {
1029: formatStartPosition = getPosition(lastToken, 0,
1030: Position.Bias.Backward);
1031: }
1032:
1033: if (debug) {
1034: System.err
1035: .println("FormatWriter.flush(): format token="
1036: + lastToken);
1037: }
1038:
1039: tokenID = syntax.nextToken();
1040: }
1041:
1042: // Assign formatStartPosition even if there are no tokens
1043: if (formatStartPosition == null) {
1044: formatStartPosition = getPosition(null, 0,
1045: Position.Bias.Backward);
1046: }
1047:
1048: // Assign textStartPosition if this is the first flush
1049: if (firstFlush) {
1050: textStartPosition = formatStartPosition;
1051: }
1052:
1053: bufferSize = 0; // reset the current buffer size
1054:
1055: if (debug) {
1056: System.err.println("FormatWriter.flush(): formatting ...");
1057: }
1058:
1059: // Format the tokens
1060: formatter.format(this );
1061:
1062: // Write the output tokens to the underlying writer marking them as written
1063: StringBuffer sb = new StringBuffer();
1064: ExtTokenItem token = (ExtTokenItem) formatStartPosition
1065: .getToken();
1066: ExtTokenItem prevToken = null;
1067: if (token != null) {
1068: // Process the first token
1069: switch (token.getWrittenLength()) {
1070: case -1: // write whole token
1071: sb.append(token.getImage());
1072: break;
1073:
1074: case Integer.MAX_VALUE:
1075: throw new IllegalStateException(
1076: "Wrong formatStartPosition"); // NOI18N
1077:
1078: default:
1079: sb.append(token.getImage().substring(
1080: formatStartPosition.getOffset()));
1081: break;
1082: }
1083:
1084: token.markWritten();
1085: prevToken = token;
1086: token = (ExtTokenItem) token.getNext();
1087:
1088: // Process additional tokens
1089: while (token != null) {
1090: // First mark the previous token that it can't be extended
1091: prevToken.setWrittenLength(Integer.MAX_VALUE);
1092:
1093: // Write current token and mark it as written
1094: sb.append(token.getImage());
1095: token.markWritten();
1096:
1097: // Goto next token
1098: prevToken = token;
1099: token = (ExtTokenItem) token.getNext();
1100: }
1101: }
1102:
1103: // Write to the underlying writer
1104: if (sb.length() > 0) {
1105: char[] outBuf = new char[sb.length()];
1106: sb.getChars(0, outBuf.length, outBuf, 0);
1107:
1108: if (debug) {
1109: System.err
1110: .println("FormatWriter.flush(): chars to underlying writer='" // NOI18N
1111: + EditorDebug.debugChars(outBuf, 0,
1112: outBuf.length) + "'"); // NOI18N
1113: }
1114:
1115: underWriter.write(outBuf, 0, outBuf.length);
1116: }
1117:
1118: underWriter.flush();
1119:
1120: firstFlush = false; // no more first flush
1121: }
1122:
1123: public void close() throws IOException {
1124: if (debug) {
1125: System.err
1126: .println("FormatWriter: close() called (-> flush())"); // NOI18N
1127: }
1128:
1129: flush();
1130: underWriter.close();
1131: }
1132:
1133: /** Check the chain whether it's OK. */
1134: public void checkChain() {
1135: // Check whether the lastToken is really last
1136: TokenItem lt = getLastToken();
1137: if (lt.getNext() != null) {
1138: throw new IllegalStateException(
1139: "Successor of last token exists."); // NOI18N
1140: }
1141:
1142: // Check whether formatStartPosition is non-null
1143: FormatTokenPosition fsp = getFormatStartPosition();
1144: if (fsp == null) {
1145: throw new IllegalStateException(
1146: "getFormatStartPosition() returns null."); // NOI18N
1147: }
1148:
1149: // Check whether formatStartPosition follows textStartPosition
1150: checkFSPFollowsTSP();
1151:
1152: // !!! implement checks:
1153: // Check whether all the document tokens have written flag true
1154: // Check whether all formatted tokens are writable
1155: }
1156:
1157: /** Check whether formatStartPosition follows the textStartPosition */
1158: private void checkFSPFollowsTSP() {
1159: if (!(formatStartPosition.equals(textStartPosition) || isAfter(
1160: formatStartPosition, textStartPosition))) {
1161: throw new IllegalStateException(
1162: "formatStartPosition doesn't follow textStartPosition"); // NOI18N
1163: }
1164: }
1165:
1166: public String chainToString(TokenItem token) {
1167: return chainToString(token, 5);
1168: }
1169:
1170: /** Debug the current state of the chain.
1171: * @param token mark this token as current one. It can be null.
1172: * @param maxDocumentTokens how many document tokens should be shown.
1173: */
1174: public String chainToString(TokenItem token, int maxDocumentTokens) {
1175: // First check the chain whether it's correct
1176: checkChain();
1177:
1178: StringBuffer sb = new StringBuffer();
1179: sb
1180: .append("D - document tokens, W - written tokens, F - tokens being formatted\n"); // NOI18N
1181:
1182: // Check whether format-start position follows textStartPosition
1183: checkFSPFollowsTSP();
1184:
1185: TokenItem tst = getTextStartPosition().getToken();
1186: TokenItem fst = getFormatStartPosition().getToken();
1187:
1188: // Goto maxDocumentTokens back from the tst
1189: TokenItem t = tst;
1190: if (t == null) {
1191: t = getLastToken();
1192: }
1193:
1194: // Go back through document tokens
1195: while (t != null && t.getPrevious() != null
1196: && --maxDocumentTokens > 0) {
1197: t = t.getPrevious();
1198: }
1199:
1200: // Display the document tokens
1201: while (t != tst) {
1202: sb.append((t == token) ? '>' : ' '); // NOI18N
1203: sb.append("D "); // NOI18N
1204: sb.append(t.toString());
1205: sb.append('\n'); // NOI18N
1206:
1207: t = t.getNext();
1208: }
1209:
1210: while (t != fst) {
1211: sb.append((t == token) ? '>' : ' ');
1212: if (t == tst) { // found last document token
1213: sb.append("D(" + getTextStartPosition().getOffset()
1214: + ')'); // NOI18N
1215: }
1216: sb.append("W "); // NOI18N
1217: sb.append(t.toString());
1218: sb.append('\n'); // NOI18N
1219:
1220: t = t.getNext();
1221: }
1222:
1223: sb.append((t == token) ? '>' : ' ');
1224: if (getFormatStartPosition().getOffset() > 0) {
1225: if (fst == tst) {
1226: sb.append('D');
1227:
1228: } else { // means something was already formatted
1229: sb.append('W'); // NOI18N
1230: }
1231: }
1232: sb.append("F "); // NOI18N
1233: sb.append((t != null) ? t.toString() : "NULL"); // NOI18N
1234: sb.append('\n'); // NOI18N
1235:
1236: if (t != null) {
1237: t = t.getNext();
1238: }
1239:
1240: while (t != null) {
1241: sb.append((t == token) ? '>' : ' '); // NOI18N
1242: sb.append("F "); // NOI18N
1243: sb.append(t.toString());
1244: sb.append('\n'); // NOI18N
1245:
1246: t = t.getNext();
1247: }
1248:
1249: return sb.toString();
1250: }
1251:
1252: /** Token-item created for the tokens that come from the text
1253: * written to the writer.
1254: */
1255: static class FormatTokenItem extends TokenItem.AbstractItem
1256: implements ExtTokenItem {
1257:
1258: /** How big part of the token was already written
1259: * to the underlying writer. -1 means nothing was written yet,
1260: * Integer.MAX_VALUE means everything was written and the token
1261: * cannot be extended and some other value means how big part
1262: * was already written.
1263: */
1264: int writtenLength = -1;
1265:
1266: /** Next token in chain */
1267: TokenItem next;
1268:
1269: /** Previous token in chain */
1270: TokenItem previous;
1271:
1272: /** Image of the token. It's changeable. */
1273: String image;
1274:
1275: /** Save offset used to give the relative position
1276: * to the start of the current formatting. It's used
1277: * by the FormatTokenPositionSupport.
1278: */
1279: int saveOffset;
1280:
1281: FormatTokenItem(TokenID tokenID,
1282: TokenContextPath tokenContextPath, int offset,
1283: String image, TokenItem previous) {
1284: super (tokenID, tokenContextPath, offset, image);
1285: this .image = image;
1286: this .previous = previous;
1287: if (previous instanceof ExtTokenItem) {
1288: ((ExtTokenItem) previous).setNext(this );
1289: }
1290: }
1291:
1292: public TokenItem getNext() {
1293: return next;
1294: }
1295:
1296: public TokenItem getPrevious() {
1297: return previous;
1298: }
1299:
1300: public void setNext(TokenItem next) {
1301: this .next = next;
1302: }
1303:
1304: public void setPrevious(TokenItem previous) {
1305: this .previous = previous;
1306: }
1307:
1308: public boolean isWritten() {
1309: return (writtenLength >= 0);
1310: }
1311:
1312: public void markWritten() {
1313: if (writtenLength == Integer.MAX_VALUE) {
1314: throw new IllegalStateException(
1315: "Already marked unextendable."); // NOI18N
1316: }
1317:
1318: writtenLength = getImage().length();
1319: }
1320:
1321: public int getWrittenLength() {
1322: return writtenLength;
1323: }
1324:
1325: public void setWrittenLength(int writtenLength) {
1326: if (writtenLength <= this .writtenLength) {
1327: throw new IllegalArgumentException(
1328: "this.writtenLength=" + this .writtenLength // NOI18N
1329: + " < writtenLength=" + writtenLength); // NOI18N
1330: }
1331:
1332: this .writtenLength = writtenLength;
1333: }
1334:
1335: public String getImage() {
1336: return image;
1337: }
1338:
1339: public void setImage(String image) {
1340: this .image = image;
1341: }
1342:
1343: FormatTokenItem insertToken(TokenID tokenID,
1344: TokenContextPath tokenContextPath, int offset,
1345: String image) {
1346: FormatTokenItem fti = new FormatTokenItem(tokenID,
1347: tokenContextPath, offset, image, previous);
1348: fti.next = this ;
1349: this .previous = fti;
1350: return fti;
1351: }
1352:
1353: void remove() {
1354: if (previous instanceof ExtTokenItem) {
1355: ((ExtTokenItem) this .previous).setNext(next);
1356: }
1357: if (next instanceof ExtTokenItem) {
1358: ((ExtTokenItem) this .next).setPrevious(previous);
1359: }
1360: }
1361:
1362: int getSaveOffset() {
1363: return saveOffset;
1364: }
1365:
1366: void setSaveOffset(int saveOffset) {
1367: this .saveOffset = saveOffset;
1368: }
1369:
1370: }
1371:
1372: /** This token item wraps every token-item that comes
1373: * from the syntax-support.
1374: */
1375: static class FilterDocumentItem extends TokenItem.FilterItem
1376: implements ExtTokenItem {
1377:
1378: private static final FilterDocumentItem NULL_ITEM = new FilterDocumentItem(
1379: null, null, false);
1380:
1381: private TokenItem previous;
1382:
1383: private TokenItem next;
1384:
1385: FilterDocumentItem(TokenItem delegate,
1386: FilterDocumentItem neighbour,
1387: boolean isNeighbourPrevious) {
1388: super (delegate);
1389: if (neighbour != null) {
1390: if (isNeighbourPrevious) {
1391: previous = neighbour;
1392:
1393: } else { // neighbour is next
1394: next = neighbour;
1395: }
1396: }
1397: }
1398:
1399: public TokenItem getNext() {
1400: if (next == null) {
1401: TokenItem ti = super .getNext();
1402: if (ti != null) {
1403: next = new FilterDocumentItem(ti, this , true);
1404: }
1405: }
1406:
1407: return (next != NULL_ITEM) ? next : null;
1408: }
1409:
1410: public void setNext(TokenItem next) {
1411: this .next = next;
1412: }
1413:
1414: /** Change the next item to the NULL one. */
1415: public void terminate() {
1416: setNext(NULL_ITEM);
1417: }
1418:
1419: public void setPrevious(TokenItem previous) {
1420: this .previous = previous;
1421: }
1422:
1423: public boolean isWritten() {
1424: return true;
1425: }
1426:
1427: public void markWritten() {
1428: }
1429:
1430: public int getWrittenLength() {
1431: return Integer.MAX_VALUE;
1432: }
1433:
1434: public void setWrittenLength(int writtenLength) {
1435: if (writtenLength != Integer.MAX_VALUE) {
1436: throw new IllegalArgumentException(
1437: "Wrong writtenLength=" // NOI18N
1438: + writtenLength);
1439: }
1440: }
1441:
1442: public void setImage(String image) {
1443: throw new IllegalStateException(
1444: "Cannot set image of the document-token."); // NOI18N
1445: }
1446:
1447: public TokenItem getPrevious() {
1448: if (previous == null) {
1449: TokenItem ti = super .getPrevious();
1450: if (ti != null) {
1451: previous = new FilterDocumentItem(ti, this , false);
1452: }
1453: }
1454:
1455: return previous;
1456: }
1457:
1458: }
1459:
1460: interface ExtTokenItem extends TokenItem {
1461:
1462: /** Set the next item */
1463: public void setNext(TokenItem next);
1464:
1465: /** Set the previous item */
1466: public void setPrevious(TokenItem previous);
1467:
1468: /** Was this item already flushed to the underlying writer
1469: * or does it belong to the document?
1470: */
1471: public boolean isWritten();
1472:
1473: /** Set the flag marking the item as written to the underlying writer. */
1474: public void markWritten();
1475:
1476: /** Get the length that was written to the output writer. It can be used
1477: * when determining whether there can be insert of the text made
1478: * at the particular offset.
1479: * @return -1 to signal that token wasn't written at all.
1480: * Or Integer.MAX_VALUE to signal that the token was written and cannot
1481: * be extended.
1482: * Or something else that means how long part of the token
1483: * was already written to the file.
1484: */
1485: public int getWrittenLength();
1486:
1487: /** Set the length of the written part of the token. */
1488: public void setWrittenLength(int writtenLength);
1489:
1490: /** Set the image of the token to the specified string. */
1491: public void setImage(String image);
1492:
1493: }
1494:
1495: }
|