001: /*
002: * TokenMarker.java - Generic token marker
003: * Copyright (C) 1998, 1999 Slava Pestov
004: *
005: * You may use and modify this package for any purpose. Redistribution is
006: * permitted, in both source and binary form, provided that this notice
007: * remains intact in all source distributions of this package.
008: */
009: package workbench.gui.editor;
010:
011: import java.util.ArrayList;
012: import javax.swing.text.Segment;
013:
014: /**
015: * A token marker that splits lines of text into tokens. Each token carries
016: * a length field and an indentification tag that can be mapped to a color
017: * for painting that token.<p>
018: *
019: * For performance reasons, the linked list of tokens is reused after each
020: * line is tokenized. Therefore, the return value of <code>markTokens</code>
021: * should only be used for immediate painting. Notably, it cannot be
022: * cached.
023: *
024: * @author Slava Pestov, Thomas Kellerer
025: * @see Token
026: */
027: public abstract class TokenMarker {
028: /**
029: * Stores the first {@link Token} for each line that
030: * has been tokenized
031: */
032: protected ArrayList<Token> lineStartTokens = new ArrayList<Token>(
033: 250);
034:
035: /**
036: * The number of lines in the model being tokenized. This can be
037: * less than the length of the <code>lineInfo</code> array.
038: */
039: protected int length;
040:
041: /**
042: * Creates a new <code>TokenMarker</code>. This DOES NOT create
043: * a lineInfo array; an initial call to <code>insertLines()</code>
044: * does that.
045: */
046: protected TokenMarker() {
047: }
048:
049: public Token getLastTokenInLine(int lineIndex) {
050: Token start = getFirstTokenInLine(lineIndex);
051:
052: if (start == null)
053: return null;
054:
055: while (start != null) {
056: if (start.next == null) {
057: return start;
058: }
059: start = start.next;
060: }
061: return null;
062: }
063:
064: public synchronized Token getFirstTokenInLine(int lineIndex) {
065: if (lineIndex < 0 || lineIndex >= length)
066: return null;
067: return lineStartTokens.get(lineIndex);
068: }
069:
070: public synchronized Token getToken(Segment line, int lineIndex) {
071: Token token = getFirstTokenInLine(lineIndex);
072: if (token == null) {
073: token = markTokens(line, lineIndex);
074: }
075: return token;
076: }
077:
078: /**
079: * A wrapper for the lower-level <code>markTokensImpl</code> method
080: * that is called to split a line up into tokens.
081: * @param line The line
082: * @param lineIndex The line number
083: */
084: public synchronized Token markTokens(Segment line, int lineIndex) {
085: if (lineIndex >= length)
086: return null;
087:
088: Token prev = getLastTokenInLine(lineIndex - 1);
089:
090: lineStartTokens.set(lineIndex, null);
091: markTokensImpl(prev, line, lineIndex);
092:
093: // tell the last token if it has a pending literal character (" or ')
094: Token t = getLastTokenInLine(lineIndex);
095: if (t != null)
096: t.setPendingLiteralChar(getPendingLiteralChar());
097:
098: return lineStartTokens.get(lineIndex);
099: }
100:
101: /**
102: * An abstract method that splits a line up into tokens. It
103: * should parse the line, and call <code>addToken()</code> to
104: * add syntax tokens to the token list. Then, it should return
105: * the initial token type for the next line.<p>
106: *
107: * For example if the current line contains the start of a
108: * multiline comment that doesn't end on that line, this method
109: * should return the comment token type so that it continues on
110: * the next line.
111: *
112: * @param token The initial token type for this line
113: * @param line The line to be tokenized
114: * @param lineIndex The index of the line in the document,
115: * starting at 0
116: * @return The initial token type for the next line
117: */
118: protected abstract void markTokensImpl(Token lastToken,
119: Segment line, int lineIndex);
120:
121: public abstract char getPendingLiteralChar();
122:
123: public void dispose() {
124: this .lineStartTokens.clear();
125: this .lineStartTokens.trimToSize();
126: }
127:
128: /**
129: * Informs the token marker that lines have been inserted into
130: * the document. This inserts a gap in the <code>lineInfo</code>
131: * array.
132: * @param index The first line number
133: * @param lines The number of lines
134: */
135: public void insertLines(int index, int lines) {
136: if (lines <= 0)
137: return;
138: length += lines;
139:
140: // Expand the array, so that the subsequent for-next loop
141: // does not re-allocated the internal array each time
142: lineStartTokens.ensureCapacity(length);
143:
144: int len = index + lines;
145: for (int i = index; i < len; i++) {
146: lineStartTokens.add(index, null);
147: }
148: }
149:
150: /**
151: * Informs the token marker that line have been deleted from
152: * the document. This removes the lines in question from the
153: * <code>lineInfo</code> array.
154: * @param index The first line number
155: * @param lines The number of lines
156: */
157: public void deleteLines(int index, int lines) {
158: if (lines <= 0)
159: return;
160: int end = index + lines;
161: length -= lines;
162: for (int i = index; i < end; i++) {
163: lineStartTokens.remove(index);
164: }
165: }
166:
167: /**
168: * Returns the number of lines in this token marker.
169: */
170: public int getLineCount() {
171: return length;
172: }
173:
174: /**
175: * Adds a token to the token list.
176: *
177: * @param length The length of the token
178: * @param id The id of the token
179: */
180: protected synchronized Token addToken(int lineIndex, int length,
181: byte id) {
182: if (id >= Token.INTERNAL_FIRST && id <= Token.INTERNAL_LAST) {
183: throw new InternalError("Invalid id: " + id);
184: }
185:
186: if (length == 0)
187: return null;
188:
189: Token newToken = new Token(length, id);
190: Token firstToken = lineStartTokens.get(lineIndex);
191: if (firstToken == null) {
192: lineStartTokens.set(lineIndex, newToken);
193: } else {
194: while (firstToken.next != null) {
195: firstToken = firstToken.next;
196: }
197: firstToken.next = newToken;
198: }
199: return newToken;
200: }
201:
202: }
|