001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.lib.lexer;
043:
044: import java.util.Set;
045: import org.netbeans.api.lexer.InputAttributes;
046: import org.netbeans.api.lexer.LanguagePath;
047: import org.netbeans.api.lexer.TokenId;
048: import org.netbeans.lib.lexer.token.AbstractToken;
049:
050: /**
051: * Filtering token list used by a token sub sequence.
052: * <br/>
053: * As the tokens are created lazily this list won't call tokenList.tokenCount()
054: * until tokenCount() is called on itself.
055: *
056: * <p>
057: * This list assumes single-threaded use only.
058: * </p>
059: *
060: * @author Miloslav Metelka
061: * @version 1.00
062: */
063:
064: public final class SubSequenceTokenList<T extends TokenId> implements
065: TokenList<T> {
066:
067: public static <T extends TokenId> SubSequenceTokenList<T> create(
068: TokenList<T> tokenList, int limitStartOffset,
069: int limitEndOffset) {
070: return new SubSequenceTokenList<T>(tokenList, limitStartOffset,
071: limitEndOffset);
072: }
073:
074: /**
075: * Token list to which this filtering token list delegates.
076: */
077: private TokenList<T> tokenList;
078:
079: /**
080: * Limit of start offset under which the token sequence cannot move.
081: * Integer.MIN_VALUE for no limit.
082: */
083: private final int limitStartOffset;
084:
085: /**
086: * Limit of the end offset under which the token sequence cannot move.
087: * Integer.MAX_VALUE for no limit.
088: */
089: private final int limitEndOffset;
090:
091: /**
092: * Index of a first token in the underlying token list that this list provides.
093: */
094: private int limitStartIndex;
095:
096: /**
097: * Initially Integer.MAX_VALUE to be computed lazily.
098: */
099: private int limitEndIndex;
100:
101: /**
102: * Create new subsequence token list
103: * @param tokenList non-null token list over which the subsequence gets created.
104: * @param limitStartOffset lower offset bound offset or 0 for none.
105: * @param limitEndOffset upper offset bound or Integer.MAX_VALUE for none.
106: */
107: public SubSequenceTokenList(TokenList<T> tokenList,
108: int limitStartOffset, int limitEndOffset) {
109: this .tokenList = tokenList;
110: this .limitStartOffset = limitStartOffset;
111: this .limitEndOffset = limitEndOffset;
112:
113: if (limitEndOffset == Integer.MAX_VALUE) { // No limit
114: // No upper bound for end index so use tokenCount() (can be improved if desired)
115: limitEndIndex = tokenList.tokenCount();
116: } else { // Valid limit end offset
117: limitEndIndex = tokenList.tokenCountCurrent(); // presently created token count
118: if (limitEndIndex == 0) { // no tokens yet -> attempt to create at least one
119: if (tokenList.tokenOrEmbeddingContainer(0) != null) { // some tokens exist
120: // Re-get the present token count (could be created a chunk of tokens at once)
121: limitEndIndex = tokenList.tokenCountCurrent();
122: }
123: }
124:
125: if (limitEndIndex > 0) {
126: // tokenCount surely >0
127: int tokenOffset = tokenList
128: .tokenOffset(limitEndIndex - 1);
129: if (limitEndOffset > tokenOffset) { // may need to create further tokens if they do not exist
130: // Force token list to create subsequent tokens
131: // Cannot subtract offset by each token's length because
132: // there may be gaps between tokens due to token id filter use.
133: AbstractToken<?> token = token(limitEndIndex - 1);
134: int tokenLength = token.length();
135: while (limitEndOffset > tokenOffset + tokenLength) { // above present token
136: Object tokenOrEmbeddingContainer = tokenList
137: .tokenOrEmbeddingContainer(limitEndIndex);
138: if (tokenOrEmbeddingContainer != null) {
139: token = LexerUtilsConstants
140: .token(tokenOrEmbeddingContainer);
141: if (tokenList.isContinuous()
142: || token.isFlyweight()) {
143: tokenOffset += tokenLength;
144: } else { // retrieve offset
145: tokenOffset = tokenList
146: .tokenOffset(limitEndIndex);
147: }
148: tokenLength = token.length();
149: limitEndIndex++;
150: } else { // no more tokens => break
151: break;
152: }
153: }
154:
155: } else { // end index within existing tokens
156: // The offset is within the currently recognized tokens
157: // Use binary search
158: int low = 0;
159: limitEndIndex--;
160:
161: while (low <= limitEndIndex) {
162: int mid = (low + limitEndIndex) / 2;
163: int midStartOffset = tokenList.tokenOffset(mid);
164:
165: if (midStartOffset < limitEndOffset) {
166: low = mid + 1;
167: } else if (midStartOffset > limitEndOffset) {
168: limitEndIndex = mid - 1;
169: } else { // Token starting exactly at offset found
170: limitEndIndex = mid - 1;
171: break;
172: }
173: }
174: limitEndIndex++; // Increase from 'high' to end index
175: }
176: }
177: }
178:
179: // Compute limitStartIndex (currently == 0)
180: if (limitEndIndex > 0 && limitStartOffset > 0) {
181: int high = limitEndIndex - 1;
182: while (limitStartIndex <= high) {
183: int mid = (limitStartIndex + high) / 2;
184: int midStartOffset = tokenList.tokenOffset(mid);
185:
186: if (midStartOffset < limitStartOffset) {
187: limitStartIndex = mid + 1;
188: } else if (midStartOffset > limitStartOffset) {
189: high = mid - 1;
190: } else { // Token starting exactly at offset found
191: limitStartIndex = mid + 1;
192: break;
193: }
194: }
195: // Include previous token if it "includes" limitStartOffset (also handles gaps between tokens properly)
196: if (limitStartIndex > 0
197: && tokenList.tokenOffset(limitStartIndex - 1)
198: + token(limitStartIndex - 1).length() > limitStartOffset) {
199: limitStartIndex--;
200: }
201: }
202: }
203:
204: public TokenList<T> delegate() {
205: return tokenList;
206: }
207:
208: public int limitStartOffset() {
209: return limitStartOffset;
210: }
211:
212: public int limitEndOffset() {
213: return limitEndOffset;
214: }
215:
216: public Object tokenOrEmbeddingContainer(int index) {
217: index += limitStartIndex;
218: return (index < limitEndIndex) ? tokenList
219: .tokenOrEmbeddingContainer(index) : null;
220: }
221:
222: public int tokenOffset(int index) {
223: index += limitStartIndex;
224: if (index >= limitEndIndex)
225: throw new IndexOutOfBoundsException("index=" + index
226: + " >= limitEndIndex=" + limitEndIndex);
227: return tokenList.tokenOffset(index);
228: }
229:
230: public int tokenCount() {
231: return limitEndIndex - limitStartIndex;
232: }
233:
234: public int tokenCountCurrent() {
235: return limitEndIndex - limitStartIndex;
236: }
237:
238: public AbstractToken<T> replaceFlyToken(int index,
239: AbstractToken<T> flyToken, int offset) {
240: return tokenList.replaceFlyToken(index + limitStartIndex,
241: flyToken, offset);
242: }
243:
244: public int modCount() {
245: return tokenList.modCount();
246: }
247:
248: public LanguagePath languagePath() {
249: return tokenList.languagePath();
250: }
251:
252: public int childTokenOffset(int rawOffset) {
253: throw new IllegalStateException("Unexpected call.");
254: }
255:
256: public char childTokenCharAt(int rawOffset, int index) {
257: throw new IllegalStateException("Unexpected call.");
258: }
259:
260: public void wrapToken(int index,
261: EmbeddingContainer<T> embeddingContainer) {
262: tokenList
263: .wrapToken(limitStartIndex + index, embeddingContainer);
264: }
265:
266: public TokenList<?> root() {
267: return tokenList.root();
268: }
269:
270: public TokenHierarchyOperation<?, ?> tokenHierarchyOperation() {
271: return tokenList.tokenHierarchyOperation();
272: }
273:
274: public InputAttributes inputAttributes() {
275: return tokenList.inputAttributes();
276: }
277:
278: public int lookahead(int index) {
279: // Can be used by LexerTestUtilities.lookahead()
280: return tokenList.lookahead(index);
281: }
282:
283: public Object state(int index) {
284: return tokenList.state(index);
285: }
286:
287: public boolean isContinuous() {
288: return tokenList.isContinuous();
289: }
290:
291: public Set<T> skipTokenIds() {
292: return tokenList.skipTokenIds();
293: }
294:
295: public int startOffset() {
296: if (tokenCountCurrent() > 0 || tokenCount() > 0)
297: return tokenOffset(0);
298: return limitStartOffset;
299: }
300:
301: public int endOffset() {
302: int cntM1 = tokenCount() - 1;
303: if (cntM1 >= 0)
304: return tokenOffset(cntM1) + token(cntM1).length();
305: return limitStartOffset;
306: }
307:
308: public boolean isRemoved() {
309: return tokenList.isRemoved();
310: }
311:
312: private AbstractToken<T> token(int index) {
313: return LexerUtilsConstants.token(tokenList, index);
314: }
315:
316: }
|