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.ArrayList;
045: import java.util.Collections;
046: import java.util.HashMap;
047: import java.util.List;
048: import java.util.Map;
049: import java.util.logging.Level;
050: import java.util.logging.Logger;
051: import org.netbeans.api.lexer.LanguagePath;
052: import org.netbeans.api.lexer.TokenId;
053: import org.netbeans.lib.lexer.inc.IncTokenList;
054: import org.netbeans.lib.lexer.inc.MutableTokenList;
055: import org.netbeans.lib.lexer.inc.TokenHierarchyEventInfo;
056: import org.netbeans.lib.lexer.inc.TokenListChange;
057: import org.netbeans.lib.lexer.inc.TokenListUpdater;
058: import static org.netbeans.lib.lexer.LexerUtilsConstants.INVALID_STATE;
059:
060: /**
061: * Request for updating of token hierarchy after text modification
062: * or custom embedding creation/removal.
063: * <br/>
064: * This class contains all the data and methods related to updating.
065: *
066: * @author Miloslav Metelka
067: */
068:
069: public final class TokenHierarchyUpdate {
070:
071: // -J-Dorg.netbeans.lib.lexer.TokenHierarchyUpdate.level=FINE
072: static final Logger LOG = Logger
073: .getLogger(TokenHierarchyUpdate.class.getName());
074:
075: final TokenHierarchyEventInfo eventInfo;
076:
077: private Map<LanguagePath, TLLInfo> path2info;
078:
079: /**
080: * Infos ordered from higher top levels of the hierarchy to lower levels.
081: * Useful for top-down updating at the end.
082: */
083: private List<List<TLLInfo>> levelInfos;
084:
085: TokenListChange<?> rootChange;
086:
087: public TokenHierarchyUpdate(TokenHierarchyEventInfo eventInfo) {
088: this .eventInfo = eventInfo;
089: }
090:
091: public void update(IncTokenList<?> incTokenList) {
092: incTokenList.incrementModCount();
093: // Update top-level token list first
094: // It does not need any updateStatusImpl() since it's only for embedded token lists
095: rootChange = updateTokenListByModification(incTokenList, null);
096: eventInfo.setTokenChangeInfo(rootChange.tokenChangeInfo());
097:
098: if (LOG.isLoggable(Level.FINE)) {
099: LOG.fine("ROOT CHANGE: " + rootChange.toString(0) + "\n"); // NOI18N
100: }
101:
102: // If there is an active lexer input operation (for not-yet finished
103: // top-level token list lexing) refresh it because it would be damaged
104: // by the performed token list update
105: if (!incTokenList.isFullyLexed()) {
106: incTokenList.refreshLexerInputOperation();
107: }
108:
109: // Now the update goes to possibly embedded token list lists
110: // based on the top-level change. If there are embeddings that join sections
111: // this becomes a fairly complex thing.
112: // 1. The updating must always go from upper levels to lower levels of the token hierarchy
113: // to ensure that the tokens of the possible joined embeddings get updated properly
114: // as the tokens created/removed at upper levels may contain embeddings that will
115: // need to be added/removed from token list lists on lower level.
116: // 2. A single insert/remove may produce token updates at several
117: // places in the document. A top-level change of token with embedding
118: // will request the embedded token list update and that token list
119: // may be connected with another joined token list(s) with the same language path
120: // and the update may continue into these joined token lists.
121:
122: // 3. The algorithm must collect both removed and added token lists
123: // in the TLLInfo.
124: // 4. For a removed token list the updating must check nested embedded token lists
125: // because some embedded tokens of the removed embedded token list might contain
126: // another embedding that might also be maintained as token list list
127: // and need to be updated.
128: // 5. The parent token list lists
129: // are always maintained too which simplifies the updating algorithm
130: // and speeds it up because the token list list marks whether it has any children
131: // or not and so the deep traversing only occurs if there are any children present.
132: // 6. Additions may produce nested additions too so they need to be makred
133: // similarly to removals.
134: if (rootChange.isBoundsChange()) {
135: processBoundsChangeEmbeddings(rootChange, null);
136: } else {
137: // Mark changed area based on start of first mod.token and end of last mod.token
138: // of the root-level change
139: eventInfo.setMinAffectedStartOffset(rootChange.offset());
140: eventInfo.setMaxAffectedEndOffset(rootChange
141: .addedEndOffset());
142: processNonBoundsChange(rootChange);
143: }
144:
145: processLevelInfos();
146: }
147:
148: public void updateCreateEmbedding(
149: EmbeddedTokenList<?> addedTokenList) {
150: TLLInfo info = info(addedTokenList.languagePath());
151: if (info != NO_INFO) {
152: if (LOG.isLoggable(Level.FINE)) {
153: LOG.fine("THU.updateCreateEmbedding(): "
154: + addedTokenList.toStringHeader());
155: }
156: info.markAdded(addedTokenList);
157: processLevelInfos();
158: }
159: }
160:
161: /**
162: * update-status must be called by the caller.
163: * @param removedTokenList token list removed by TS.removeEmbedding().
164: */
165: public void updateRemoveEmbedding(
166: EmbeddedTokenList<?> removedTokenList) {
167: TLLInfo info = info(removedTokenList.languagePath());
168: if (info != NO_INFO) {
169: if (LOG.isLoggable(Level.FINE)) {
170: LOG.fine("THU.updateRemoveEmbedding(): "
171: + removedTokenList.toStringHeader());
172: }
173: // update-status called by caller.
174: info.markRemoved(removedTokenList);
175: processLevelInfos();
176: }
177: }
178:
179: void processBoundsChangeEmbeddings(TokenListChange<?> change,
180: TokenListChange<?> parentChange) {
181: // Add an embedded change to the parent change (if exists)
182: if (parentChange != null) {
183: parentChange.tokenChangeInfo().addEmbeddedChange(
184: change.tokenChangeInfo());
185: }
186: Object tokenOrEC = change.tokenChangeInfo().removedTokenList()
187: .tokenOrEmbeddingContainer(0);
188: if (tokenOrEC.getClass() == EmbeddingContainer.class) {
189: TLLInfo info;
190: boolean hasChildren;
191: if (change.languagePath().size() > 1) {
192: info = info(change.languagePath());
193: hasChildren = (info != NO_INFO) ? info.tokenListList()
194: .hasChildren() : false;
195: } else { // root-level
196: info = NO_INFO;
197: hasChildren = (eventInfo.tokenHierarchyOperation()
198: .maxTokenListListPathSize() > 0);
199: }
200: EmbeddingContainer<?> ec = (EmbeddingContainer<?>) tokenOrEC;
201: rewrapECToken(ec, change); // Includes updateStatusImpl()
202: EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
203: if (etl != null
204: && etl != EmbeddedTokenList.NO_DEFAULT_EMBEDDING) {
205: // Check the text length beyond modification => end skip length must not be affected
206: int modRelOffset = eventInfo.modificationOffset()
207: - change.offset();
208: int beyondModLength = change.addedEndOffset()
209: - (eventInfo.modificationOffset() + eventInfo
210: .diffLengthOrZero());
211: EmbeddedTokenList<?> prevEtl = null;
212: do {
213: TLLInfo childInfo = hasChildren ? info(etl
214: .languagePath()) : NO_INFO;
215: // Check whether the change was not in the start or end skip lengths
216: // and if so then remove the embedding
217: if (modRelOffset >= etl.embedding()
218: .startSkipLength()
219: && beyondModLength >= etl.embedding()
220: .endSkipLength()) { // Modification within embedding's bounds => embedding can stay
221: // Mark that the embedding should be updated
222: if (childInfo != NO_INFO) {
223: // update-status called by rewrap-ec-token above
224: childInfo.markBoundsChange(etl);
225: } else { // No child but want to update nested possible bounds changes
226: if (etl.isInited()) {
227: parentChange = change;
228: // Perform change in child - it surely does not join the sections
229: // since otherwise the childInfo could not be null
230: // update-status done above for the embedding container
231: change = updateTokenListByModification(
232: etl, null);
233: if (change.isBoundsChange()) {
234: processBoundsChangeEmbeddings(
235: change, parentChange);
236: } else {
237: eventInfo
238: .setMinAffectedStartOffset(change
239: .offset());
240: eventInfo
241: .setMaxAffectedEndOffset(change
242: .addedEndOffset());
243: }
244: }
245: }
246: prevEtl = etl;
247: etl = etl.nextEmbeddedTokenList();
248:
249: } else { // Mod in skip lengths => Remove the etl from chain
250: if (childInfo != NO_INFO) {
251: // update-status already done as part of rewrap-token
252: childInfo.markRemoved(etl);
253: }
254: // Remove embedding and get the next embedded token list (prevEtl stays the same)
255: etl = ec.removeEmbeddedTokenList(prevEtl, etl);
256: }
257: } while (etl != null
258: && etl != EmbeddedTokenList.NO_DEFAULT_EMBEDDING);
259: }
260: }
261: }
262:
263: void processNonBoundsChange(TokenListChange<?> change) {
264: TLLInfo info;
265: boolean hasChildren;
266: if (change.languagePath().size() >= 2) {
267: info = info(change.languagePath());
268: hasChildren = (info != NO_INFO && info.tokenListList()
269: .hasChildren());
270: } else { // root change
271: info = NO_INFO;
272: hasChildren = (eventInfo.tokenHierarchyOperation()
273: .maxTokenListListPathSize() > 0);
274: }
275: if (hasChildren) {
276: // First mark the removed embeddings
277: TokenList<?> removedTokenList = change.tokenChangeInfo()
278: .removedTokenList();
279: if (removedTokenList != null) {
280: markRemovedEmbeddings(removedTokenList);
281: }
282:
283: // Now mark added embeddings
284: TokenList<?> currentTokenList = change.tokenChangeInfo()
285: .currentTokenList();
286: markAddedEmbeddings(currentTokenList, change.index(),
287: change.addedTokensOrBranchesCount());
288: }
289: }
290:
291: /**
292: * Collect removed embeddings for the given token list recursively
293: * and nest deep enough for all maintained children
294: * token list lists.
295: */
296: private void markRemovedEmbeddings(TokenList<?> removedTokenList) {
297: int tokenCount = removedTokenList.tokenCountCurrent();
298: for (int i = 0; i < tokenCount; i++) {
299: Object tokenOrEC = removedTokenList
300: .tokenOrEmbeddingContainer(i);
301: if (tokenOrEC.getClass() == EmbeddingContainer.class) {
302: EmbeddingContainer<?> ec = (EmbeddingContainer<?>) tokenOrEC;
303: ec.updateStatusImpl(); // Update status since markRemoved() will need it
304: EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
305: while (etl != null
306: && etl != EmbeddedTokenList.NO_DEFAULT_EMBEDDING) {
307: TLLInfo info = info(etl.languagePath());
308: if (info != NO_INFO) {
309: // update-status called above
310: info.markRemoved(etl);
311: }
312: etl = etl.nextEmbeddedTokenList();
313: }
314: }
315: }
316: }
317:
318: private void markAddedEmbeddings(TokenList<?> tokenList, int index,
319: int addedCount) {
320: for (int i = 0; i < addedCount; i++) {
321: // Ensure that the default embedding gets possibly created
322: EmbeddedTokenList<?> etl = EmbeddingContainer
323: .embeddedTokenList(tokenList, index + i, null);
324: if (etl != null) {
325: TLLInfo info = info(etl.languagePath());
326: if (info != NO_INFO) {
327: // Mark that there was a new embedded token list added
328: // There should be no updateStatusImpl() necessary since the token lists are new
329: // and the parent embedding container was surely updated by the updating process.
330: info.markAdded(etl);
331: }
332: }
333: }
334: }
335:
336: private void processLevelInfos() {
337: // Now relex the changes in affected token list lists
338: // i.e. fix the tokens after the token lists removals/additions.
339: // The higher-level updates
340: if (levelInfos != null) {
341: // The list can be extended by additional items dynamically during iteration
342: for (int i = 0; i < levelInfos.size(); i++) {
343: List<TLLInfo> infos = levelInfos.get(i);
344: // The "infos" list should not be extended by additional items dynamically during iteration
345: // However an extra items can be added at the deeper levels.
346: for (int j = 0; j < infos.size(); j++) {
347: infos.get(j).update();
348: }
349: }
350: }
351:
352: // Assert that update was called on all infos
353: if (LOG.isLoggable(Level.FINER) && levelInfos != null) {
354: for (List<TLLInfo> infos : levelInfos) {
355: for (TLLInfo info : infos) {
356: if (!info.updateCalled) {
357: throw new IllegalStateException(
358: "Update not called on tokenListList\n" + // NOI18N
359: info.tokenListList);
360: }
361: }
362: }
363: }
364: }
365:
366: <T extends TokenId> TokenListChange<T> updateTokenListByModification(
367: MutableTokenList<T> tokenList, Object zeroIndexRelexState) {
368: TokenListChange<T> change = new TokenListChange<T>(tokenList);
369: // if (tokenList instanceof EmbeddedTokenList) {
370: // ((EmbeddedTokenList)tokenList).embeddingContainer().checkStatusUpdated();
371: // }
372: TokenListUpdater.update(tokenList, eventInfo
373: .modificationOffset(), eventInfo.insertedLength(),
374: eventInfo.removedLength(), change, zeroIndexRelexState);
375: return change;
376: }
377:
378: /**
379: * Return tll info or NO_INFO if the token list list is not maintained
380: * for the given language path.
381: */
382: private TLLInfo info(LanguagePath languagePath) {
383: if (path2info == null) { // Init since it will contain NO_INFO
384: path2info = new HashMap<LanguagePath, TLLInfo>(4, 0.5f);
385: }
386: TLLInfo info = path2info.get(languagePath);
387: if (info == null) {
388: TokenListList tll = eventInfo.tokenHierarchyOperation()
389: .existingTokenListList(languagePath);
390: if (tll != null) {
391: info = new TLLInfo(this , tll);
392: int index = languagePath.size() - 2;
393: if (levelInfos == null) {
394: levelInfos = new ArrayList<List<TLLInfo>>(index + 1);
395: }
396: while (levelInfos.size() <= index) {
397: levelInfos.add(new ArrayList<TLLInfo>(2));
398: }
399: levelInfos.get(index).add(info);
400: } else { // No token list list for the given language path
401: info = NO_INFO;
402: }
403: path2info.put(languagePath, info);
404: }
405: return info;
406: }
407:
408: private <T extends TokenId> void rewrapECToken(
409: EmbeddingContainer<T> ec, TokenListChange<?> change) {
410: @SuppressWarnings("unchecked")
411: TokenListChange<T> tChange = (TokenListChange<T>) change;
412: ec.reinit(tChange.addedToken(0));
413: ec.updateStatusImpl();
414: tChange.tokenList().wrapToken(tChange.index(), ec);
415: }
416:
417: /**
418: * Special constant value to avoid double map search for token list lists updating.
419: */
420: static final TLLInfo NO_INFO = new TLLInfo(null, null);
421:
422: /**
423: * Information about update in a single token list list.
424: */
425: static final class TLLInfo {
426:
427: final TokenHierarchyUpdate update;
428:
429: final TokenListList tokenListList;
430:
431: int index;
432:
433: int removeCount;
434:
435: List<TokenList<?>> added;
436:
437: TokenListChange<?> change;
438:
439: boolean updateCalled;
440:
441: public TLLInfo(TokenHierarchyUpdate update,
442: TokenListList tokenListList) {
443: this .update = update;
444: this .tokenListList = tokenListList;
445: this .index = -1;
446: this .added = Collections.emptyList();
447: }
448:
449: public TokenListList tokenListList() {
450: return tokenListList;
451: }
452:
453: /**
454: * Mark the given token list as removed in this token list list.
455: * All removed token lists should be marked by their increasing offset
456: * so it should be necessary to search for the index just once.
457: * <br/>
458: * It's expected that updateStatusImpl() was already called
459: * on the corresponding embedding container.
460: */
461: public void markRemoved(EmbeddedTokenList<?> removedTokenList) {
462: boolean indexWasMinusOne; // Used for possible exception cause debugging
463: // removedTokenList.embeddingContainer().checkStatusUpdated();
464: if (index == -1) {
465: checkUpdateNotCalledYet();
466: indexWasMinusOne = true;
467: index = tokenListList.findIndexDuringUpdate(
468: removedTokenList, update.eventInfo
469: .modificationOffset(), update.eventInfo
470: .removedLength());
471: assert (index >= 0) : "index=" + index + " < 0"; // NOI18N
472: } else { // Index already initialized
473: indexWasMinusOne = false;
474: }
475: TokenList<?> markedForRemoveTokenList = tokenListList
476: .getOrNull(index + removeCount);
477: if (markedForRemoveTokenList != removedTokenList) {
478: int realIndex = tokenListList.indexOf(removedTokenList);
479: throw new IllegalStateException("Removing at index="
480: + index
481: + // NOI18N
482: " but real index is "
483: + realIndex
484: + // NOI18N
485: " (indexWasMinusOne="
486: + indexWasMinusOne
487: + ").\n"
488: + // NOI18N
489: "Wishing to remove tokenList\n"
490: + removedTokenList
491: + // NOI18N
492: "\nbut marked-for-remove tokenList is \n"
493: + markedForRemoveTokenList
494: + // NOI18N
495: "\nfrom tokenListList\n" + tokenListList
496: + // NOI18N
497: "\n\nModification description:\n"
498: + update.eventInfo
499: .modificationDescription(true) // NOI18N
500: );
501: }
502: removeCount++;
503: }
504:
505: /**
506: * Mark the given token list to be added to this list of token lists.
507: * At the end first the token lists marked for removal will be removed
508: * and then the token lists marked for addition will be added.
509: * <br/>
510: * It's expected that updateStatusImpl() was already called
511: * on the corresponding embedding container.
512: */
513: public void markAdded(EmbeddedTokenList<?> addedTokenList) {
514: // addedTokenList.embeddingContainer().checkStatusUpdated();
515: if (added.size() == 0) {
516: checkUpdateNotCalledYet();
517: if (index == -1) {
518: index = tokenListList.findIndex(addedTokenList
519: .startOffset());
520: assert (index >= 0) : "index=" + index + " < 0"; // NOI18N
521: }
522: added = new ArrayList<TokenList<?>>(4);
523: }
524: added.add(addedTokenList);
525: }
526:
527: /**
528: * Mark that a parent's token list's bounds change need to be propagated
529: * into the given (child) token list.
530: * <br/>
531: * It's expected that updateStatusImpl() was already called
532: * on the corresponding embedding container.
533: */
534: public void markBoundsChange(EmbeddedTokenList<?> etl) {
535: assert (index == -1) : "index=" + index + " != -1"; // Should be the first one
536: // etl.embeddingContainer().checkStatusUpdated();
537: checkUpdateNotCalledYet();
538: index = tokenListList.findIndex(etl.startOffset());
539: }
540:
541: public void update() {
542: checkUpdateNotCalledYet();
543: updateCalled = true;
544: // Update this level (and language path).
545: // All the removed and added sections resulting from parent change(s)
546: // are already marked.
547: if (index == -1)
548: return; // Nothing to do
549:
550: if (removeCount == 0 && added.size() == 0) { // Bounds change only
551: if (LOG.isLoggable(Level.FINE)) {
552: LOG.fine("TLLInfo.update(): BOUNDS-CHANGE: "
553: + tokenListList.languagePath().mimePath() + // NOI18N
554: " index=" + index + // NOI18N
555: '\n');
556: }
557:
558: EmbeddedTokenList<?> etl = tokenListList.get(index);
559: etl.embeddingContainer().updateStatusImpl();
560: Object matchState = LexerUtilsConstants.endState(etl);
561: Object relexState = tokenListList.relexState(index);
562: // update-status called above
563: TokenListChange<?> chng = update
564: .updateTokenListByModification(etl, relexState);
565: relexState = LexerUtilsConstants.endState(etl,
566: relexState);
567: // Prevent bounds change in case the states at the end of the section would not match
568: // which leads to relexing of the next section.
569: if (chng.isBoundsChange()
570: && LexerUtilsConstants.statesEqual(relexState,
571: matchState)) {
572: TokenListChange<?> parentChange = (tokenListList
573: .languagePath().size() == 2) ? update.rootChange
574: : update.info(tokenListList.languagePath()
575: .parent()).change;
576: update.processBoundsChangeEmbeddings(chng,
577: parentChange);
578: } else { // Regular change
579: update.processNonBoundsChange(chng);
580: }
581: relexAfterLastModifiedSection(index + 1, relexState,
582: matchState);
583:
584: } else { // Non-bounds change
585: if (LOG.isLoggable(Level.FINE)) {
586: LOG.fine("TLLInfo.update(): REPLACE: "
587: + tokenListList.languagePath().mimePath() + // NOI18N
588: " index=" + index + // NOI18N
589: ", removeCount=" + removeCount + // NOI18N
590: ", added.size()=" + added.size() + // NOI18N
591: '\n');
592: }
593:
594: TokenList<?>[] removed = tokenListList.replace(index,
595: removeCount, added);
596: // Mark embeddings of removed token lists as removed
597: if (tokenListList.hasChildren()) {
598: for (int i = 0; i < removed.length; i++) {
599: TokenList<?> removedTokenList = removed[i];
600: update.markRemovedEmbeddings(removedTokenList);
601: }
602: }
603:
604: Object relexState; // State from which the relexing will start
605: Object matchState = INVALID_STATE; // State that needs to be reached by relexing
606: if (tokenListList.joinSections()) { // Need to find the right relexState
607: // Must update the token list by incremental algorithm
608: // Find non-empty token list and take last token's state
609: relexState = tokenListList.relexState(index);
610: for (int i = removed.length - 1; i >= 0
611: && matchState == INVALID_STATE; i--) {
612: matchState = LexerUtilsConstants
613: .endState((EmbeddedTokenList<?>) removed[i]);
614: }
615: // Find the start state as the previous non-empty section's last token's state
616: // for case there would be no token lists added or all the added sections
617: // would be empty.
618: if (matchState == INVALID_STATE) // None or just empty sections were removed
619: matchState = relexState;
620:
621: } else { // Not joining the sections
622: relexState = null;
623: }
624:
625: // Relex all the added token lists (just by asking for tokenCount - init() will be done)
626: for (int i = 0; i < added.size(); i++) {
627: EmbeddedTokenList<?> tokenList = (EmbeddedTokenList<?>) added
628: .get(i);
629: assert (!tokenList.isInited());
630: tokenList.init(relexState);
631: if (tokenList.embedding().joinSections()) {
632: tokenListList.setJoinSections(true);
633: }
634: relexState = LexerUtilsConstants.endState(
635: (EmbeddedTokenList<?>) tokenList,
636: relexState);
637: if (tokenListList.hasChildren()) {
638: update.markAddedEmbeddings(tokenList, 0,
639: tokenList.tokenCount());
640: }
641: // Added token lists should not require updateStatus()
642: update.eventInfo.setMaxAffectedEndOffset(tokenList
643: .endOffset());
644: }
645:
646: if (tokenListList.joinSections()) {
647: index += added.size();
648: relexAfterLastModifiedSection(index, relexState,
649: matchState);
650: }
651: }
652:
653: // for (EmbeddedTokenList<?> etl : tokenListList) {
654: // etl.embeddingContainer().updateStatusImpl();
655: // if (etl.embeddingContainer().isRemoved())
656: // throw new IllegalStateException();
657: // }
658: // Set index to -1 to simplify correctness checking
659: index = -1;
660: }
661:
662: void checkUpdateNotCalledYet() {
663: if (updateCalled) {
664: throw new IllegalStateException(
665: "Update already called on \n" + tokenListList);
666: }
667: }
668:
669: private void relexAfterLastModifiedSection(int index,
670: Object relexState, Object matchState) {
671: // Must continue relexing existing section(s) (from a different start state)
672: // until the relexing will stop before the last token of the given section.
673: EmbeddedTokenList<?> etl;
674: while (!LexerUtilsConstants.statesEqual(relexState,
675: matchState)
676: && (etl = tokenListList.getOrNull(index)) != null) {
677: etl.embeddingContainer().updateStatusImpl();
678: if (etl.tokenCount() > 0) {
679: // Remember state after the last token of the given section
680: matchState = etl.state(etl.tokenCount() - 1);
681: // updateStatusImpl() just called
682: TokenListChange<?> chng = updateTokenListAtStart(
683: etl, etl.startOffset(), relexState);
684: update.processNonBoundsChange(chng);
685: // Since the section is non-empty (checked above) there should be >0 tokens
686: relexState = etl.state(etl.tokenCount() - 1);
687: }
688: index++;
689: }
690: }
691:
692: private <T extends TokenId> TokenListChange<T> updateTokenListAtStart(
693: EmbeddedTokenList<T> etl, int offset,
694: Object zeroIndexRelexState) {
695: TokenListChange<T> chng = new TokenListChange<T>(etl);
696: // etl.embeddingContainer().checkStatusUpdated();
697: TokenListUpdater.update(etl, offset, 0, 0, chng,
698: zeroIndexRelexState);
699: update.eventInfo.setMaxAffectedEndOffset(chng
700: .addedEndOffset());
701: return chng;
702: }
703:
704: }
705:
706: }
|