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.AbstractList;
045: import java.util.ArrayList;
046: import java.util.ArrayList;
047: import java.util.Collections;
048: import java.util.ConcurrentModificationException;
049: import java.util.Iterator;
050: import java.util.List;
051: import java.util.NoSuchElementException;
052: import org.netbeans.api.lexer.LanguagePath;
053: import org.netbeans.api.lexer.TokenId;
054: import org.netbeans.api.lexer.TokenSequence;
055:
056: /**
057: * List of token lists that collects all token lists for a given language path.
058: *
059: * @author Miloslav Metelka
060: */
061:
062: public final class TokenSequenceList extends
063: AbstractList<TokenSequence<?>> {
064:
065: private TokenHierarchyOperation<?, ?> operation;
066:
067: private final TokenListList tokenListList;
068:
069: private final List<TokenSequence<?>> tokenSequences;
070:
071: private final int endOffset;
072:
073: private final int expectedModCount;
074:
075: /**
076: * Index of the last item retrieved from tokenListList.
077: * It may be equal to Integer.MAX_VALUE when searching thgroughout the token lists
078: * was finished.
079: */
080: private int tokenListIndex;
081:
082: public TokenSequenceList(TokenHierarchyOperation<?, ?> operation,
083: LanguagePath languagePath, int startOffset, int endOffset) {
084: this .operation = operation;
085: this .endOffset = endOffset;
086: this .expectedModCount = operation.modCount();
087:
088: if (languagePath.size() == 1) { // Is supported too
089: this .tokenListList = null;
090: tokenListIndex = Integer.MAX_VALUE; // Mark no mods to tokenSequences
091: TokenList<?> rootTokenList = operation.rootTokenList();
092: if (rootTokenList.languagePath() == languagePath) {
093: TokenSequence<?> rootTS = LexerApiPackageAccessor.get()
094: .createTokenSequence(
095: checkWrapTokenList(rootTokenList,
096: startOffset, endOffset));
097: tokenSequences = Collections
098: .<TokenSequence<?>> singletonList(rootTS);
099: } else {
100: tokenSequences = Collections.emptyList();
101: }
102:
103: } else { // languagePath.size() >= 2
104: this .tokenListList = operation.tokenListList(languagePath);
105: // Possibly skip initial token lists accroding to startOffset
106: int size = tokenListList.size();
107: int high = size - 1;
108: // Find the token list which has the end offset above or equal to the requested startOffset
109: EmbeddedTokenList<?> firstTokenList;
110: if (startOffset > 0) {
111: while (tokenListIndex <= high) {
112: int mid = (tokenListIndex + high) / 2;
113: EmbeddedTokenList<?> etl = tokenListList.get(mid);
114: // Update end offset before querying
115: etl.embeddingContainer().updateStatusImpl();
116: int tlEndOffset = etl.endOffset(); // updateStatusImpl() just called
117: if (tlEndOffset < startOffset) {
118: tokenListIndex = mid + 1;
119: } else if (tlEndOffset > startOffset) {
120: high = mid - 1;
121: } else { // tl ends exactly at start offset
122: tokenListIndex = mid + 1; // take the first above this
123: break;
124: }
125: }
126: // If not found exactly -> take the higher one (index variable)
127: firstTokenList = tokenListList
128: .getOrNull(tokenListIndex);
129: if (tokenListIndex == size) { // Right above the ones that existed at begining of bin search
130: while (firstTokenList != null) {
131: firstTokenList.embeddingContainer()
132: .updateStatusImpl();
133: if (firstTokenList.endOffset() >= startOffset) { // updateStatusImpl() just called
134: break;
135: }
136: firstTokenList = tokenListList
137: .getOrNull(++tokenListIndex);
138: }
139: }
140:
141: } else { // startOffset == 0
142: firstTokenList = tokenListList.getOrNull(0);
143: }
144:
145: if (firstTokenList != null) {
146: firstTokenList.embeddingContainer().updateStatusImpl();
147: tokenSequences = new ArrayList<TokenSequence<?>>(4);
148: tokenSequences.add(LexerApiPackageAccessor.get()
149: .createTokenSequence(
150: checkWrapTokenList(firstTokenList,
151: startOffset, endOffset)));
152:
153: } else {// firstTokenList == null
154: tokenSequences = Collections.emptyList();
155: tokenListIndex = Integer.MAX_VALUE; // No token sequences at all
156: }
157: }
158: }
159:
160: private TokenList<?> checkWrapTokenList(TokenList<?> tokenList,
161: int startOffset, int endOffset) {
162: // Expected that updateStatusImpl() was just called
163: boolean wrapStart = ((startOffset > 0)
164: && (tokenList.startOffset() < startOffset) && (startOffset < tokenList
165: .endOffset()));
166: boolean wrapEnd = ((endOffset != Integer.MAX_VALUE)
167: && (tokenList.startOffset() < endOffset) && (endOffset < tokenList
168: .endOffset()));
169: if (wrapStart || wrapEnd) // Must create sub sequence
170: tokenList = SubSequenceTokenList.create(tokenList,
171: startOffset, endOffset);
172: if (wrapEnd) { // Also this will be the last one list
173: tokenListIndex = Integer.MAX_VALUE;
174: }
175: return tokenList;
176: }
177:
178: @Override
179: public Iterator<TokenSequence<?>> iterator() {
180: return new Itr();
181: }
182:
183: public TokenSequence<?> get(int index) {
184: findTokenSequenceWithIndex(index);
185: return tokenSequences.get(index); // Will fail naturally if index too high
186: }
187:
188: public TokenSequence<?> getOrNull(int index) {
189: findTokenSequenceWithIndex(index);
190: return (index < tokenSequences.size()) ? tokenSequences
191: .get(index) : null;
192: }
193:
194: public int size() {
195: findTokenSequenceWithIndex(Integer.MAX_VALUE);
196: return tokenSequences.size();
197: }
198:
199: private void findTokenSequenceWithIndex(int index) {
200: while (index >= tokenSequences.size()
201: && tokenListIndex != Integer.MAX_VALUE) {
202: EmbeddedTokenList<?> etl = tokenListList
203: .getOrNull(++tokenListIndex);
204: if (etl != null
205: && (endOffset == Integer.MAX_VALUE || etl
206: .startOffset() < endOffset)) {
207: etl.embeddingContainer().updateStatus();
208: boolean wrapEnd = ((endOffset != Integer.MAX_VALUE)
209: && (etl.startOffset() < endOffset) && (endOffset < etl
210: .endOffset()));
211: if (wrapEnd) {
212: tokenSequences.add(LexerApiPackageAccessor.get()
213: .createTokenSequence(
214: SubSequenceTokenList.create(etl, 0,
215: endOffset)));
216: tokenListIndex = Integer.MAX_VALUE;
217: } else {
218: tokenSequences.add(LexerApiPackageAccessor.get()
219: .createTokenSequence(etl));
220: }
221: } else { // Singnal no more token sequences
222: tokenListIndex = Integer.MAX_VALUE;
223: }
224: }
225: }
226:
227: void checkForComodification() {
228: if (expectedModCount != operation.modCount())
229: throw new ConcurrentModificationException(
230: "Caller uses obsolete TokenSequenceList: expectedModCount="
231: + expectedModCount + // NOI18N
232: " != modCount=" + operation.modCount());
233: }
234:
235: @Override
236: public String toString() {
237: return tokenListList.toString();
238: }
239:
240: private class Itr implements Iterator<TokenSequence<?>> {
241:
242: private int cursor = 0;
243:
244: private TokenSequence<?> next;
245:
246: public boolean hasNext() {
247: checkFetchNext();
248: return (next != null);
249: }
250:
251: public TokenSequence<?> next() {
252: checkFetchNext();
253: if (next == null)
254: throw new NoSuchElementException();
255: TokenSequence<?> ret = next;
256: next = null;
257: return ret;
258: }
259:
260: private void checkFetchNext() {
261: if (next == null) {
262: checkForComodification();
263: next = getOrNull(cursor++); // can increase cursor even if (next == null)
264: }
265: }
266:
267: public void remove() {
268: throw new UnsupportedOperationException(); // underlying list is immutable
269: }
270:
271: }
272:
273: }
|