This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 117450
Collapse All | Expand All

(-)a/editor.util/manifest.mf (-1 / +1 lines)
Lines 1-5 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.netbeans.modules.editor.util/1
2
OpenIDE-Module: org.netbeans.modules.editor.util/1
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/lib/editor/util/Bundle.properties
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/lib/editor/util/Bundle.properties
4
OpenIDE-Module-Specification-Version: 1.23
4
OpenIDE-Module-Specification-Version: 1.24
5
AutoUpdate-Show-In-Client: false
5
AutoUpdate-Show-In-Client: false
(-)a/editor.util/src/org/netbeans/lib/editor/util/CharSequenceUtilities.java (-7 / +20 lines)
Lines 516-536 Link Here
516
    /**
516
    /**
517
     * Ensure that the given start and end parameters are valid indices
517
     * Ensure that the given start and end parameters are valid indices
518
     * of the given text.
518
     * of the given text.
519
     * @throws IndexOutOfBoundsException if the start or end are not within bounds
519
     * @param start must be >=0 and <=end.
520
     *  of the given text.
520
     * @param end must be >=start and <=textLength.
521
     * @param length total length of a charsequence.
522
     * @throws IndexOutOfBoundsException if the start or end are not within bounds.
521
     */
523
     */
522
    public static void checkIndexesValid(CharSequence text, int start, int end) {
524
    public static void checkIndexesValid(int start, int end, int length) {
523
        if (start < 0) {
525
        if (start < 0) {
524
            throw new IndexOutOfBoundsException("start=" + start + " < 0"); // NOI18N
526
            throw new IndexOutOfBoundsException("start=" + start + " < 0"); // NOI18N
525
        }
527
        }
526
        if (end < start) {
528
        if (end < start) {
527
            throw new IndexOutOfBoundsException("end=" + end + " < start=" + start); // NOI18N
529
            throw new IndexOutOfBoundsException("end=" + end + " < start=" + start); // NOI18N
528
        }
530
        }
529
        if (end > text.length()) {
531
        if (end > length) {
530
            throw new IndexOutOfBoundsException("end=" + end // NOI18N
532
            throw new IndexOutOfBoundsException("end=" + end + " > length()=" + length); // NOI18N
531
            + " > text.length()=" + text.length()); // NOI18N
532
        }
533
        }
533
    }
534
    }
534
    
535
536
    /**
537
     * Ensure that the given start and end parameters are valid indices
538
     * of the given text.
539
     * @param text non-null char sequence.
540
     * @param start must be &gt;=0 and &lt;=end.
541
     * @param end must be &gt;=start and &lt;=<code>text.length()</code>.
542
     * @throws IndexOutOfBoundsException if the start or end are not within bounds
543
     *  of the given text.
544
     */
545
    public static void checkIndexesValid(CharSequence text, int start, int end) {
546
        checkIndexesValid(start, end, text.length());
547
    }
535
548
536
}
549
}
(-)a/editor.util/src/org/netbeans/lib/editor/util/FlyOffsetGapList.java (-1 / +6 lines)
Lines 60-67 Link Here
60
    private int offsetGapLength = Integer.MAX_VALUE / 2; // 32 bytes
60
    private int offsetGapLength = Integer.MAX_VALUE / 2; // 32 bytes
61
61
62
    public FlyOffsetGapList() {
62
    public FlyOffsetGapList() {
63
        this(10);
63
    }
64
    }
64
    
65
66
    public FlyOffsetGapList(int initialCapacity) {
67
        super(initialCapacity);
68
    }
69
65
    /**
70
    /**
66
     * Get the raw offset of the given element currently stored in the list.
71
     * Get the raw offset of the given element currently stored in the list.
67
     *
72
     *
(-)a/editor.util/src/org/netbeans/lib/editor/util/OffsetGapList.java (-1 / +6 lines)
Lines 82-89 Link Here
82
    private int offsetGapLength = Integer.MAX_VALUE / 2; // 32 bytes
82
    private int offsetGapLength = Integer.MAX_VALUE / 2; // 32 bytes
83
83
84
    public OffsetGapList() {
84
    public OffsetGapList() {
85
        this(10);
85
    }
86
    }
86
    
87
88
    public OffsetGapList(int initialCapacity) {
89
        super(initialCapacity);
90
    }
91
87
    /**
92
    /**
88
     * Get the raw offset of the given element currently stored in the list.
93
     * Get the raw offset of the given element currently stored in the list.
89
     *
94
     *
(-)a/editor.util/test/unit/src/org/netbeans/lib/editor/util/CharSequenceUtilitiesTest.java (+9 lines)
Lines 138-143 Link Here
138
        
138
        
139
        // endsWith
139
        // endsWith
140
        assertTrue(CharSequenceUtilities.endsWith(string, string.substring(CHARS_LENGTH - SUBSTR_LENGTH)));
140
        assertTrue(CharSequenceUtilities.endsWith(string, string.substring(CHARS_LENGTH - SUBSTR_LENGTH)));
141
        
142
        CharSequenceUtilities.checkIndexesValid(0, 3, 3); // start,end,length
143
        CharSequenceUtilities.checkIndexesValid(1, 3, 3);
144
        try {
145
            CharSequenceUtilities.checkIndexesValid(1, 4, 3);
146
            TestCase.fail("IndexOutOfBoundsException was expected.");
147
        } catch (IndexOutOfBoundsException e) {
148
            // Expected
149
        }
141
    }
150
    }
142
    
151
    
143
    public void generateChars(char[] chars) {
152
    public void generateChars(char[] chars) {
(-)a/lexer/apichanges.xml (+19 lines)
Lines 113-118 Link Here
113
<!-- ACTUAL CHANGES BEGIN HERE: -->
113
<!-- ACTUAL CHANGES BEGIN HERE: -->
114
114
115
  <changes>
115
  <changes>
116
  <change id="Joined-sections-Lexing">
117
      <api name="api"/>
118
      <summary>Joined Sections Lexing</summary>
119
      <version major="1" minor="28"/>
120
      <date day="28" month="5" year="2008"/>
121
      <author login="mmetelka"/>
122
      <compatibility source="compatible" binary="compatible" semantic="compatible" deletion="no" addition="yes" modification="no"/>
123
      <description>
124
        <p>
125
            Embeddings that request input sections to be joined before lexing
126
            are now lexed as a single section.
127
            <br/>
128
            <code>Token.isRemoved()</code> was added to check whether a particular token
129
            is still present in token hierarchy or whether it was removed as part of a modification.
130
        </p>
131
      </description>
132
      <issue number="117450"/>
133
  </change>
134
116
  <change id="Lexer-API-Cleanup">
135
  <change id="Lexer-API-Cleanup">
117
      <api name="api"/>
136
      <api name="api"/>
118
      <summary>Lexer API Cleanup</summary>
137
      <summary>Lexer API Cleanup</summary>
(-)a/lexer/nbproject/project.properties (-1 / +1 lines)
Lines 43-46 Link Here
43
javadoc.arch=${basedir}/arch.xml
43
javadoc.arch=${basedir}/arch.xml
44
javadoc.apichanges=${basedir}/apichanges.xml
44
javadoc.apichanges=${basedir}/apichanges.xml
45
javadoc.docfiles=${basedir}/api/doc
45
javadoc.docfiles=${basedir}/api/doc
46
spec.version.base=1.27.0
46
spec.version.base=1.28.0
(-)a/lexer/nbproject/project.xml (-1 / +1 lines)
Lines 52-58 Link Here
52
                    <compile-dependency/>
52
                    <compile-dependency/>
53
                    <run-dependency>
53
                    <run-dependency>
54
                        <release-version>1</release-version>
54
                        <release-version>1</release-version>
55
                        <specification-version>1.15</specification-version>
55
                        <specification-version>1.24</specification-version>
56
                    </run-dependency>
56
                    </run-dependency>
57
                </dependency>
57
                </dependency>
58
                <dependency>
58
                <dependency>
(-)a/lexer/src/org/netbeans/api/lexer/Token.java (-1 / +37 lines)
Lines 40-45 Link Here
40
 */
40
 */
41
41
42
package org.netbeans.api.lexer;
42
package org.netbeans.api.lexer;
43
44
import java.util.List;
43
45
44
/**
46
/**
45
 * Token describes a lexical element of input text.
47
 * Token describes a lexical element of input text.
Lines 207-218 Link Here
207
     * @return true if the token is flyweight or false otherwise.
209
     * @return true if the token is flyweight or false otherwise.
208
     */
210
     */
209
    public abstract boolean isFlyweight();
211
    public abstract boolean isFlyweight();
212
213
    /**
214
     * Check whether this token is no longer part of the token hierarchy.
215
     * 
216
     * @return true if the token was removed from the token hierarchy
217
     *  or false if it's still present in the hierarchy.
218
     */
219
    public abstract boolean isRemoved();
210
    
220
    
211
    /**
221
    /**
212
     * Check whether this token represents a complete token
222
     * Check whether this token represents a complete token
213
     * or whether it's a part of a complete token.
223
     * or whether it's a particular part of a complete token.
224
     * <br/>
225
     * Some lexers may also use this information to express an incomplete token.
226
     * For example an unclosed block comment at the end of java source
227
     * is represented as a BLOCK_COMMENT token id and {@link PartType#START}.
228
     * 
229
     * @return {@link PartType#COMPLETE} for regular token or other part types
230
     *  for particular token parts.
214
     */
231
     */
215
    public abstract PartType partType();
232
    public abstract PartType partType();
233
    
234
    /**
235
     * Get a complete token that is joined from multiple parts (this token is one of those parts).
236
     * 
237
     * @return complete token or null if this token is not a part of any token.
238
     */
239
    public abstract Token<T> joinToken();
240
    
241
    /**
242
     * Get all token parts comprising this token ordered from lowest to highest part's offset.
243
     * <br/>
244
     * It's guaranteed that each token part is continuous in the input text
245
     * (there are no gaps inside the token part's text).
246
     * <br/>
247
     * On the other hand there may be textual gaps between two adajcent token parts.
248
     * 
249
     * @return list of token parts or null if the token is continuous.
250
     */
251
    public abstract List<? extends Token<T>> joinedParts();
216
252
217
    /**
253
    /**
218
     * Quickly determine whether this token has any extra properties.
254
     * Quickly determine whether this token has any extra properties.
(-)a/lexer/src/org/netbeans/api/lexer/TokenHierarchyEvent.java (-1 / +1 lines)
Lines 123-129 Link Here
123
     *  if this event's type is not {@link TokenHierarchyEventType#MODIFICATION}.
123
     *  if this event's type is not {@link TokenHierarchyEventType#MODIFICATION}.
124
     */
124
     */
125
    public int modificationOffset() {
125
    public int modificationOffset() {
126
        return info.modificationOffset();
126
        return info.modOffset();
127
    }
127
    }
128
    
128
    
129
    /**
129
    /**
(-)a/lexer/src/org/netbeans/api/lexer/TokenSequence.java (-116 / +92 lines)
Lines 42-53 Link Here
42
package org.netbeans.api.lexer;
42
package org.netbeans.api.lexer;
43
43
44
import java.util.ConcurrentModificationException;
44
import java.util.ConcurrentModificationException;
45
import org.netbeans.lib.lexer.EmbeddedTokenList;
45
import org.netbeans.lib.lexer.EmbeddingContainer;
46
import org.netbeans.lib.lexer.EmbeddingContainer;
46
import org.netbeans.lib.lexer.LexerUtilsConstants;
47
import org.netbeans.lib.lexer.JoinTokenList;
47
import org.netbeans.lib.lexer.SubSequenceTokenList;
48
import org.netbeans.lib.lexer.SubSequenceTokenList;
48
import org.netbeans.lib.lexer.LexerUtilsConstants;
49
import org.netbeans.lib.lexer.LexerUtilsConstants;
49
import org.netbeans.lib.lexer.TokenList;
50
import org.netbeans.lib.lexer.TokenList;
50
import org.netbeans.lib.lexer.token.AbstractToken;
51
import org.netbeans.lib.lexer.token.AbstractToken;
52
import org.netbeans.lib.lexer.TokenOrEmbedding;
51
53
52
/**
54
/**
53
 * Token sequence allows to iterate between tokens
55
 * Token sequence allows to iterate between tokens
Lines 246-252 Link Here
246
    public int offset() {
248
    public int offset() {
247
        checkTokenNotNull();
249
        checkTokenNotNull();
248
        if (tokenOffset == -1) {
250
        if (tokenOffset == -1) {
249
            tokenOffset = tokenList.tokenOffset(tokenIndex);
251
            tokenOffset = tokenList.tokenOffsetByIndex(tokenIndex);
250
        }
252
        }
251
        return tokenOffset;
253
        return tokenOffset;
252
    }
254
    }
Lines 296-312 Link Here
296
     */
298
     */
297
    public TokenSequence<?> embedded() {
299
    public TokenSequence<?> embedded() {
298
        checkTokenNotNull();
300
        checkTokenNotNull();
299
        return embeddedImpl(null);
301
        return embeddedImpl(null, false);
300
    }
301
    
302
    private <ET extends TokenId> TokenSequence<ET> embeddedImpl(Language<ET> embeddedLanguage) {
303
        if (token.isFlyweight())
304
            return null;
305
        TokenList<ET> embeddedTokenList = LexerUtilsConstants.embeddedTokenList(
306
                tokenList, tokenIndex, embeddedLanguage);
307
        return (embeddedTokenList != null)
308
                ? new TokenSequence<ET>(embeddedTokenList)
309
                : null;
310
    }
302
    }
311
303
312
    /**
304
    /**
Lines 318-324 Link Here
318
     */
310
     */
319
    public <ET extends TokenId> TokenSequence<ET> embedded(Language<ET> embeddedLanguage) {
311
    public <ET extends TokenId> TokenSequence<ET> embedded(Language<ET> embeddedLanguage) {
320
        checkTokenNotNull();
312
        checkTokenNotNull();
321
        return embeddedImpl(embeddedLanguage);
313
        return embeddedImpl(embeddedLanguage, false);
314
    }
315
316
    /**
317
     * Get embedded token sequence that possibly joins multiple embeddings
318
     * with the same language paths (if the embeddings allow it - see
319
     * {@link LanguageEmbedding#joinSections()}) into a single input text
320
     * which is then lexed as a single continuous text.
321
     * <br/>
322
     * If any of the resulting tokens crosses embedding's boundaries then the token
323
     * is split into multiple part tokens.
324
     * <br/>
325
     * If the embedding does not join sections then this method behaves
326
     * like {@link #embedded()}.
327
     * 
328
     * @return embedded sequence or null if no embedding exists for this token.
329
     *  The token sequence will be positioned before first token of this embedding
330
     *  or to a join token in case the first token of this embedding is part of the join token.
331
     */
332
    public TokenSequence<?> embeddedJoined() {
333
        checkTokenNotNull();
334
        return embeddedImpl(null, true);
335
    }
336
337
    /**
338
     * Get embedded token sequence if the token
339
     * to which this token sequence is currently positioned
340
     * has a language embedding.
341
     * 
342
     * @throws IllegalStateException if {@link #token()} returns null.
343
     */
344
    public <ET extends TokenId> TokenSequence<ET> embeddedJoined(Language<ET> embeddedLanguage) {
345
        checkTokenNotNull();
346
        return embeddedImpl(embeddedLanguage, true);
347
    }
348
349
    private <ET extends TokenId> TokenSequence<ET> embeddedImpl(Language<ET> embeddedLanguage, boolean joined) {
350
        if (token.isFlyweight())
351
            return null;
352
353
        EmbeddedTokenList<ET> embeddedTokenList
354
                = EmbeddingContainer.embeddedTokenList(tokenList, tokenIndex, embeddedLanguage, true);
355
        if (embeddedTokenList != null) {
356
            embeddedTokenList.embeddingContainer().updateStatus();
357
            TokenSequence<ET> tse;
358
            JoinTokenList<ET> joinTokenList;
359
            if (joined && (joinTokenList = embeddedTokenList.joinTokenList()) != null) {
360
                tse = new TokenSequence<ET>(joinTokenList);
361
                // Position to this etl's index
362
                tse.moveIndex(joinTokenList.activeStartJoinIndex());
363
            } else { // Request regular TS or no joining available
364
                tse = new TokenSequence<ET>(embeddedTokenList);
365
            }
366
            return tse;
367
        }
368
        return null;
322
    }
369
    }
323
370
324
    /**
371
    /**
Lines 402-411 Link Here
402
        checkModCount();
449
        checkModCount();
403
        if (token != null) // Token already fetched
450
        if (token != null) // Token already fetched
404
            tokenIndex++;
451
            tokenIndex++;
405
        Object tokenOrEmbeddingContainer = tokenList.tokenOrEmbeddingContainer(tokenIndex);
452
        TokenOrEmbedding<T> tokenOrEmbedding = tokenList.tokenOrEmbedding(tokenIndex);
406
        if (tokenOrEmbeddingContainer != null) {
453
        if (tokenOrEmbedding != null) { // Might be null if no more tokens available
407
            AbstractToken origToken = token;
454
            AbstractToken<T> origToken = token;
408
            token = LexerUtilsConstants.token(tokenOrEmbeddingContainer);
455
            token = tokenOrEmbedding.token();
409
            // If origToken == null then the right offset might already be pre-computed from move()
456
            // If origToken == null then the right offset might already be pre-computed from move()
410
            if (tokenOffset != -1) {
457
            if (tokenOffset != -1) {
411
                if (origToken != null) {
458
                if (origToken != null) {
Lines 446-454 Link Here
446
    public boolean movePrevious() {
493
    public boolean movePrevious() {
447
        checkModCount();
494
        checkModCount();
448
        if (tokenIndex > 0) {
495
        if (tokenIndex > 0) {
449
            AbstractToken origToken = token;
496
            AbstractToken<T> origToken = token;
450
            tokenIndex--;
497
            tokenIndex--;
451
            token = LexerUtilsConstants.token(tokenList.tokenOrEmbeddingContainer(tokenIndex));
498
            token = tokenList.tokenOrEmbedding(tokenIndex).token();
452
            if (tokenOffset != -1) {
499
            if (tokenOffset != -1) {
453
                // If the token list is continuous or the original token
500
                // If the token list is continuous or the original token
454
                // is flyweight (there cannot be a gap before flyweight token)
501
                // is flyweight (there cannot be a gap before flyweight token)
Lines 501-513 Link Here
501
    public int moveIndex(int index) {
548
    public int moveIndex(int index) {
502
        checkModCount();
549
        checkModCount();
503
        if (index >= 0) {
550
        if (index >= 0) {
504
            Object tokenOrEmbeddingContainer = tokenList.tokenOrEmbeddingContainer(index);
551
            TokenOrEmbedding<T> tokenOrEmbedding = tokenList.tokenOrEmbedding(index);
505
            if (tokenOrEmbeddingContainer != null) { // enough tokens
552
            if (tokenOrEmbedding != null) { // enough tokens
506
                resetTokenIndex(index);
553
                resetTokenIndex(index, -1);
507
            } else // Token at the requested index does not exist - leave orig. index
554
            } else {// Token at the requested index does not exist - leave orig. index
508
                resetTokenIndex(tokenCount());
555
                resetTokenIndex(tokenCount(), -1);
509
        } else // index < 0
556
            }
510
            resetTokenIndex(0);
557
        } else {// index < 0
558
            resetTokenIndex(0, -1);
559
        }
511
        return index - tokenIndex;
560
        return index - tokenIndex;
512
    }
561
    }
513
    
562
    
Lines 555-561 Link Here
555
     * <p>
604
     * <p>
556
     * If token filtering is used there may be gaps that are not covered
605
     * If token filtering is used there may be gaps that are not covered
557
     * by any tokens and if the offset is contained in such gap then
606
     * by any tokens and if the offset is contained in such gap then
558
     * the token sequence will be positioned before the token that follows the gap.
607
     * the token sequence will be positioned before the token that precedes the gap.
559
     * </p>
608
     * </p>
560
     *
609
     *
561
     *
610
     *
Lines 563-658 Link Here
563
     * @return difference between the reqeuested offset
612
     * @return difference between the reqeuested offset
564
     *  and the start offset of the token
613
     *  and the start offset of the token
565
     *  before which the the token sequence gets positioned.
614
     *  before which the the token sequence gets positioned.
615
     *  <br/>
616
     *  If positioned right after the last token then (offset - last-token-end-offset)
617
     *  is returned.
566
     * 
618
     * 
567
     * @throws ConcurrentModificationException if this token sequence
619
     * @throws ConcurrentModificationException if this token sequence
568
     *  is no longer valid because of an underlying mutable input source modification.
620
     *  is no longer valid because of an underlying mutable input source modification.
569
     */
621
     */
570
    public int move(int offset) {
622
    public int move(int offset) {
571
        checkModCount();
623
        checkModCount();
572
        // Token count in the list may change as possibly other threads
624
        int[] indexAndTokenOffset = tokenList.tokenIndex(offset);
573
        // keep asking for tokens. Root token list impls create tokens lazily
625
        if (indexAndTokenOffset[0] != -1) { // Valid index and token-offset
574
        // when asked by clients.
626
            resetTokenIndex(indexAndTokenOffset[0], indexAndTokenOffset[1]);
575
        int tokenCount = tokenList.tokenCountCurrent(); // presently created token count
627
        } else { // No tokens in token list (indexAndOffset[1] == 0)
576
        if (tokenCount == 0) { // no tokens yet -> attempt to create at least one
628
            resetTokenIndex(0, -1); // Set Index to zero and offset to invalid
577
            if (tokenList.tokenOrEmbeddingContainer(0) == null) { // really no tokens at all
578
                // In this case the token sequence could not be positioned yet
579
                // so no need to reset "index" or other vars
580
                resetTokenIndex(0);
581
                return offset;
582
            }
583
            // Re-get the present token count (could be created a chunk of tokens at once)
584
            tokenCount = tokenList.tokenCountCurrent();
585
        }
629
        }
586
630
        return offset - indexAndTokenOffset[1];
587
        // tokenCount surely >0
588
        int prevTokenOffset = tokenList.tokenOffset(tokenCount - 1);
589
        if (offset > prevTokenOffset) { // may need to create further tokens if they do not exist
590
            // Force token list to create subsequent tokens
591
            // Cannot subtract offset by each token's length because
592
            // there may be gaps between tokens due to token id filter use.
593
            int tokenLength = LexerUtilsConstants.token(tokenList, tokenCount - 1).length();
594
            while (offset >= prevTokenOffset + tokenLength) { // above present token
595
                Object tokenOrEmbeddingContainer = tokenList.tokenOrEmbeddingContainer(tokenCount);
596
                if (tokenOrEmbeddingContainer != null) {
597
                    AbstractToken t = LexerUtilsConstants.token(tokenOrEmbeddingContainer);
598
                    if (t.isFlyweight()) { // need to use previous tokenLength
599
                        prevTokenOffset += tokenLength;
600
                    } else { // non-flyweight token - retrieve offset
601
                        prevTokenOffset = tokenList.tokenOffset(tokenCount);
602
                    }
603
                    tokenLength = t.length();
604
                    tokenCount++;
605
606
                } else { // no more tokens => position behind last token
607
                    resetTokenIndex(tokenCount);
608
                    tokenOffset = prevTokenOffset + tokenLength; // May assign the token's offset in advance
609
                    return offset - tokenOffset;
610
                }
611
            }
612
            resetTokenIndex(tokenCount - 1);
613
            tokenOffset = prevTokenOffset; // May assign the token's offset in advance
614
            return offset - prevTokenOffset;
615
        }
616
        
617
        // The offset is within the currently recognized tokens
618
        // Use binary search
619
        int low = 0;
620
        int high = tokenCount - 1;
621
        
622
        while (low <= high) {
623
            int mid = (low + high) / 2;
624
            int midStartOffset = tokenList.tokenOffset(mid);
625
            
626
            if (midStartOffset < offset) {
627
                low = mid + 1;
628
            } else if (midStartOffset > offset) {
629
                high = mid - 1;
630
            } else {
631
                // Token starting exactly at offset found
632
                resetTokenIndex(mid);
633
                tokenOffset = midStartOffset;
634
                return 0; // right at the token begining
635
            }
636
        }
637
        
638
        // Not found exactly and high + 1 == low => high < low
639
        // BTW there may be gaps between tokens; if offset is in gap then position to higher token
640
        if (high >= 0) { // could be -1
641
            AbstractToken t = LexerUtilsConstants.token(tokenList, high);
642
            prevTokenOffset = tokenList.tokenOffset(high);
643
            // If gaps allowed check whether the token at "high" contains the offset
644
            if (!tokenList.isContinuous() && offset > prevTokenOffset + t.length()) {
645
                // Offset in the gap above the "high" token
646
                high++;
647
                prevTokenOffset += t.length();
648
            }
649
        } else { // at least one token exists => use token at index 0
650
            high = 0;
651
            prevTokenOffset = tokenList.tokenOffset(0); // result may differ from 0
652
        }
653
        resetTokenIndex(high);
654
        tokenOffset = prevTokenOffset;
655
        return offset - prevTokenOffset;
656
    }
631
    }
657
    
632
    
658
    /**
633
    /**
Lines 663-669 Link Here
663
     * @see #tokenCount()
638
     * @see #tokenCount()
664
     */
639
     */
665
    public boolean isEmpty() {
640
    public boolean isEmpty() {
666
        return (tokenIndex == 0 && tokenList.tokenOrEmbeddingContainer(0) == null);
641
        return (tokenIndex == 0 && tokenList.tokenOrEmbedding(0) == null);
667
    }
642
    }
668
643
669
    /**
644
    /**
Lines 713-720 Link Here
713
            tl = stl.delegate();
688
            tl = stl.delegate();
714
            startOffset = Math.max(startOffset, stl.limitStartOffset());
689
            startOffset = Math.max(startOffset, stl.limitStartOffset());
715
            endOffset = Math.min(endOffset, stl.limitEndOffset());
690
            endOffset = Math.min(endOffset, stl.limitEndOffset());
716
        } else // Regular token list
691
        } else {// Regular token list
717
            tl = tokenList;
692
            tl = tokenList;
693
        }
718
        return new TokenSequence<T>(new SubSequenceTokenList<T>(tl, startOffset, endOffset));
694
        return new TokenSequence<T>(new SubSequenceTokenList<T>(tl, startOffset, endOffset));
719
    }
695
    }
720
    
696
    
Lines 733-746 Link Here
733
    @Override
709
    @Override
734
    public String toString() {
710
    public String toString() {
735
        return LexerUtilsConstants.appendTokenList(null, tokenList,
711
        return LexerUtilsConstants.appendTokenList(null, tokenList,
736
                tokenIndex, 0, Integer.MAX_VALUE, true, 0).toString();
712
                tokenIndex, 0, Integer.MAX_VALUE, true, 0, true).toString();
737
    }
713
    }
738
    
714
    
739
    private void resetTokenIndex(int index) {
715
    private void resetTokenIndex(int index, int offset) {
740
        // Position to the given index e.g. by move() and moveIndex()
716
        // Position to the given index e.g. by move() and moveIndex()
741
        tokenIndex = index;
717
        tokenIndex = index;
742
        token = null;
718
        token = null;
743
        tokenOffset = -1;
719
        tokenOffset = offset;
744
    }
720
    }
745
721
746
    private void checkTokenNotNull() {
722
    private void checkTokenNotNull() {
(-)06a7890f802e (+247 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer;
43
44
import java.util.ArrayList;
45
import java.util.Set;
46
import org.netbeans.api.lexer.Language;
47
import org.netbeans.api.lexer.LanguagePath;
48
import org.netbeans.api.lexer.InputAttributes;
49
import org.netbeans.api.lexer.TokenId;
50
import org.netbeans.lib.lexer.token.AbstractToken;
51
import org.netbeans.lib.lexer.token.TextToken;
52
53
54
/**
55
 * Token list used for immutable inputs.
56
 *
57
 * @author Miloslav Metelka
58
 * @version 1.00
59
 */
60
61
public final class BatchTokenList<T extends TokenId>
62
extends ArrayList<TokenOrEmbedding<T>> implements TokenList<T> {
63
    
64
    /** Flag for additional correctness checks (may degrade performance). */
65
    private static final boolean testing = Boolean.getBoolean("netbeans.debug.lexer.test");
66
    
67
    private static boolean maintainLAState;
68
    
69
    /**
70
     * Check whether lookaheads and states are stored for testing purposes.
71
     */
72
    public static boolean isMaintainLAState() {
73
        return maintainLAState;
74
    }
75
    
76
    public static void setMaintainLAState(boolean maintainLAState) {
77
        BatchTokenList.maintainLAState = maintainLAState;
78
    }
79
    
80
    private final TokenHierarchyOperation<?,T> tokenHierarchyOperation;
81
    
82
    private final CharSequence inputSourceText;
83
84
    private final LanguagePath languagePath;
85
    
86
    private final Set<T> skipTokenIds;
87
    
88
    private final InputAttributes inputAttributes;
89
    
90
    /**
91
     * Lexer input used for lexing of the input.
92
     */
93
    private LexerInputOperation<T> lexerInputOperation;
94
95
    private LAState laState;
96
    
97
    
98
    public BatchTokenList(TokenHierarchyOperation<?,T> tokenHierarchyOperation, CharSequence inputText,
99
    Language<T> language, Set<T> skipTokenIds, InputAttributes inputAttributes) {
100
        this.tokenHierarchyOperation = tokenHierarchyOperation;
101
        this.inputSourceText = inputText;
102
        this.languagePath = LanguagePath.get(language);
103
        this.skipTokenIds = skipTokenIds;
104
        this.inputAttributes = inputAttributes;
105
        if (testing) { // Maintain lookaheads and states when in test environment
106
            laState = LAState.empty();
107
        }
108
        this.lexerInputOperation = createLexerInputOperation();
109
    }
110
111
    protected LexerInputOperation<T> createLexerInputOperation() {
112
        return new TextLexerInputOperation<T>(this);
113
    }
114
115
    public TokenList<?> rootTokenList() {
116
        return this; // this list should always be the root list of the token hierarchy
117
    }
118
    
119
    public CharSequence inputSourceText() {
120
        return inputSourceText;
121
    }
122
123
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
124
        return tokenHierarchyOperation;
125
    }
126
    
127
    public LanguagePath languagePath() {
128
        return languagePath;
129
    }
130
    
131
    public synchronized int tokenCount() {
132
        if (lexerInputOperation != null) { // still lexing
133
            tokenOrEmbeddingImpl(Integer.MAX_VALUE);
134
        }
135
        return size();
136
    }
137
    
138
    public int tokenCountCurrent() {
139
        return size();
140
    }
141
142
    public int tokenOffset(AbstractToken<T> token) {
143
        int rawOffset = token.rawOffset();
144
        // Children offsets should be absolute
145
        return rawOffset;
146
    }
147
148
    public int tokenOffsetByIndex(int index) {
149
        AbstractToken<T> token = existingToken(index);
150
        int offset;
151
        if (token.isFlyweight()) {
152
            offset = 0;
153
            while (--index >= 0) {
154
                token = existingToken(index);
155
                offset += token.length();
156
                if (!token.isFlyweight()) {
157
                    offset += token.offset(null);
158
                    break;
159
                }
160
            }
161
        } else { // non-flyweight offset
162
            offset = token.offset(null);
163
        }
164
        return offset;
165
    }
166
167
    public int[] tokenIndex(int offset) {
168
        return LexerUtilsConstants.tokenIndexLazyTokenCreation(this, offset);
169
    }
170
171
    public synchronized TokenOrEmbedding<T> tokenOrEmbedding(int index) {
172
        return tokenOrEmbeddingImpl(index);
173
    }
174
    
175
    private TokenOrEmbedding<T> tokenOrEmbeddingImpl(int index) {
176
        while (lexerInputOperation != null && index >= size()) {
177
            AbstractToken<T> token = lexerInputOperation.nextToken();
178
            if (token != null) { // lexer returned valid token
179
                add(token);
180
                if (laState != null) { // maintaining lookaheads and states
181
                    laState = laState.add(lexerInputOperation.lookahead(),
182
                            lexerInputOperation.lexerState());
183
                }
184
            } else { // no more tokens from lexer
185
                lexerInputOperation.release();
186
                lexerInputOperation = null;
187
                trimToSize();
188
            }
189
        }
190
        return (index < size()) ? get(index) : null;
191
    }
192
    
193
    private AbstractToken<T> existingToken(int index) {
194
        return get(index).token();
195
    }
196
197
    public int lookahead(int index) {
198
        return (laState != null) ? laState.lookahead(index) : -1;
199
    }
200
201
    public Object state(int index) {
202
        return (laState != null) ? laState.state(index) : null;
203
    }
204
205
    public int startOffset() {
206
        return 0;
207
    }
208
209
    public int endOffset() {
210
        int cntM1 = tokenCount() - 1;
211
        if (cntM1 >= 0)
212
            return tokenOffsetByIndex(cntM1) + tokenOrEmbeddingImpl(cntM1).token().length();
213
        return 0;
214
    }
215
216
    public boolean isRemoved() {
217
        return false;
218
    }
219
220
    public int modCount() {
221
        return LexerUtilsConstants.MOD_COUNT_IMMUTABLE_INPUT; // immutable input
222
    }
223
    
224
    public synchronized AbstractToken<T> replaceFlyToken(
225
    int index, AbstractToken<T> flyToken, int offset) {
226
        TextToken<T> nonFlyToken = ((TextToken<T>)flyToken).createCopy(this, offset);
227
        set(index, nonFlyToken);
228
        return nonFlyToken;
229
    }
230
231
    public void wrapToken(int index, EmbeddingContainer<T> embeddingContainer) {
232
        set(index, embeddingContainer);
233
    }
234
235
    public InputAttributes inputAttributes() {
236
        return inputAttributes;
237
    }
238
    
239
    public boolean isContinuous() {
240
        return (skipTokenIds == null);
241
    }
242
    
243
    public Set<T> skipTokenIds() {
244
        return skipTokenIds;
245
    }
246
247
}
(-)a/lexer/src/org/netbeans/lib/lexer/CharPreprocessorOperation.java (-6 / +6 lines)
Lines 241-248 Link Here
241
    public void notifyError(String errorMessage) {
241
    public void notifyError(String errorMessage) {
242
        if (lexerInputOperation != null) {
242
        if (lexerInputOperation != null) {
243
            int parentIndex = parent.readIndex(); // Get the 
243
            int parentIndex = parent.readIndex(); // Get the 
244
            lexerInputOperation.notifyPreprocessorError(
244
//            lexerInputOperation.notifyPreprocessorError(
245
                new CharPreprocessorError(errorMessage, parent.deepRawLength(parentIndex)));
245
//                new CharPreprocessorError(errorMessage, parent.deepRawLength(parentIndex)));
246
        }
246
        }
247
    }
247
    }
248
248
Lines 288-297 Link Here
288
        return tokenLength;
288
        return tokenLength;
289
    }
289
    }
290
    
290
    
291
    public void tokenRecognized(int tokenLength) {
291
    public void assignTokenLength(int tokenLength, boolean skipToken) {
292
        this.tokenLength = tokenLength;
292
        this.tokenLength = tokenLength;
293
        // Modify tokenLength for preprocessed characters
293
        // Modify tokenLength for preprocessed characters
294
        parent.tokenRecognized(parentLength(tokenLength));
294
        parent.assignTokenLength(parentLength(tokenLength), skipToken);
295
    }
295
    }
296
    
296
    
297
    public PreprocessedTextStorage createPreprocessedTextStorage(CharSequence rawText,
297
    public PreprocessedTextStorage createPreprocessedTextStorage(CharSequence rawText,
Lines 390-396 Link Here
390
     * This method is called after the token has been recognized
390
     * This method is called after the token has been recognized
391
     * to clear internal data related to processing of token's characters.
391
     * to clear internal data related to processing of token's characters.
392
     */
392
     */
393
    public void tokenApproved() {
393
    public void consumeTokenLength() {
394
        if (prepStartIndex != lookaheadIndex) { // some prep chars (may be after token length)
394
        if (prepStartIndex != lookaheadIndex) { // some prep chars (may be after token length)
395
            if (prepStartIndex < tokenLength) { // prep chars before token end
395
            if (prepStartIndex < tokenLength) { // prep chars before token end
396
                if (prepEndIndex <= tokenLength) { // no preprocessed chars past token end
396
                if (prepEndIndex <= tokenLength) { // no preprocessed chars past token end
Lines 417-423 Link Here
417
417
418
        readIndex -= tokenLength;
418
        readIndex -= tokenLength;
419
        lookaheadIndex -= tokenLength;
419
        lookaheadIndex -= tokenLength;
420
        parent.tokenApproved();
420
        parent.consumeTokenLength();
421
421
422
        if (testing)
422
        if (testing)
423
            consistencyCheck();
423
            consistencyCheck();
(-)a/lexer/src/org/netbeans/lib/lexer/CharProvider.java (-7 / +8 lines)
Lines 101-118 Link Here
101
     * token length in the root lexer input operation due to character
101
     * token length in the root lexer input operation due to character
102
     * preprocessing.
102
     * preprocessing.
103
     * <br/>
103
     * <br/>
104
     * The tokenLength should be cached by this provider.
104
     * The tokenLength at a particular level should be cached by the corresponding provider.
105
     * @param skip whether the token will be skipped due to filtering of its id.
105
     *
106
     * @param skipToken whether the token will be skipped due to filtering of its id.
106
     * @return true if the token is preprocessed or false otherwise.
107
     * @return true if the token is preprocessed or false otherwise.
107
     */
108
     */
108
    void tokenRecognized(int tokenLength);
109
    void assignTokenLength(int tokenLength, boolean skipToken);
109
    
110
    
110
    /**
111
    /**
111
     * Notify this provider that the token was approved and
112
     * Notify this provider that the token was created and
112
     * that the tokenLength number of characters should be skipped
113
     * that the tokenLength number of characters should be consumed
113
     * (tokenLength should be cached by the provider).
114
     * (tokenLength should continue to be held by the provider).
114
     */
115
     */
115
    void tokenApproved();
116
    void consumeTokenLength();
116
    
117
    
117
    /**
118
    /**
118
     * Collect extra preprocessed characters from the parent providers.
119
     * Collect extra preprocessed characters from the parent providers.
(-)06a7890f802e (+122 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer;
43
44
/**
45
 * Class that wraps a each embedded token list contained in join token list.
46
 * 
47
 * @author Miloslav Metelka
48
 */
49
50
public final class EmbeddedJoinInfo {
51
    
52
    public EmbeddedJoinInfo(JoinTokenListBase base, int rawJoinTokenIndex, int rawTokenListIndex) {
53
        assert (base != null);
54
        this.base = base;
55
        this.rawJoinTokenIndex = rawJoinTokenIndex;
56
        this.rawTokenListIndex = rawTokenListIndex;
57
    }
58
    
59
    /**
60
     * Reference to join token list base as a join-related extension
61
     * of this ETL.
62
     * In fact this is the only field through which the join token list base instance
63
     * is referenced.
64
     */
65
    public final JoinTokenListBase base; // 12 bytes (8-super + 4)
66
67
    /**
68
     * Index in terms of join token list
69
     * that corresponds to first token of wrapped ETL.
70
     * <br/>
71
     * The index must be gap-preprocessed.
72
     */
73
    int rawJoinTokenIndex; // 16 bytes
74
75
    /**
76
     * Index of related ETL in a join token list (base).
77
     * <br/>
78
     * The index must be gap-preprocessed.
79
     */
80
    int rawTokenListIndex; // 20 bytes
81
82
    /**
83
     * Number of items to go forward to reach last part of a join token.
84
     * Zero otherwise.
85
     */
86
    private int joinTokenLastPartShift; // 24 bytes
87
88
    public int joinTokenIndex() {
89
        return base.joinTokenIndex(rawJoinTokenIndex);
90
    }
91
92
    public void setRawJoinTokenIndex(int rawJoinTokenIndex) {
93
        this.rawJoinTokenIndex = rawJoinTokenIndex;
94
    }
95
96
    public int tokenListIndex() {
97
        return base.tokenListIndex(rawTokenListIndex);
98
    }
99
100
    public int joinTokenLastPartShift() {
101
        return joinTokenLastPartShift;
102
    }
103
    
104
    public void setJoinTokenLastPartShift(int joinTokenLastPartShift) {
105
        this.joinTokenLastPartShift = joinTokenLastPartShift;
106
    }
107
108
    public StringBuilder dumpInfo(StringBuilder sb) {
109
        if (sb == null)
110
            sb = new StringBuilder(70);
111
        sb.append("jti=").append(joinTokenIndex());
112
        sb.append(", tli=").append(tokenListIndex());
113
        sb.append(", lps=").append(joinTokenLastPartShift());
114
        return sb;
115
    }
116
117
    @Override
118
    public String toString() {
119
        return dumpInfo(null).toString();
120
    }
121
122
}
(-)a/lexer/src/org/netbeans/lib/lexer/EmbeddedTokenList.java (-189 / +197 lines)
Lines 47-57 Link Here
47
import org.netbeans.lib.editor.util.FlyOffsetGapList;
47
import org.netbeans.lib.editor.util.FlyOffsetGapList;
48
import org.netbeans.lib.lexer.inc.MutableTokenList;
48
import org.netbeans.lib.lexer.inc.MutableTokenList;
49
import org.netbeans.api.lexer.InputAttributes;
49
import org.netbeans.api.lexer.InputAttributes;
50
import org.netbeans.api.lexer.Token;
51
import org.netbeans.api.lexer.TokenId;
50
import org.netbeans.api.lexer.TokenId;
52
import org.netbeans.lib.lexer.inc.TokenListChange;
51
import org.netbeans.lib.lexer.inc.TokenListChange;
53
import org.netbeans.spi.lexer.LanguageEmbedding;
52
import org.netbeans.spi.lexer.LanguageEmbedding;
54
import org.netbeans.lib.lexer.token.AbstractToken;
53
import org.netbeans.lib.lexer.token.AbstractToken;
54
import org.netbeans.lib.lexer.token.JoinToken;
55
import org.netbeans.lib.lexer.token.TextToken;
55
import org.netbeans.lib.lexer.token.TextToken;
56
56
57
57
Lines 73-79 Link Here
73
 */
73
 */
74
74
75
public final class EmbeddedTokenList<T extends TokenId>
75
public final class EmbeddedTokenList<T extends TokenId>
76
extends FlyOffsetGapList<Object> implements MutableTokenList<T> {
76
extends FlyOffsetGapList<TokenOrEmbedding<T>> implements MutableTokenList<T> {
77
    
77
    
78
    /** Flag for additional correctness checks (may degrade performance). */
78
    /** Flag for additional correctness checks (may degrade performance). */
79
    private static final boolean testing = Boolean.getBoolean("netbeans.debug.lexer.test");
79
    private static final boolean testing = Boolean.getBoolean("netbeans.debug.lexer.test");
Lines 83-89 Link Here
83
     * made but was unsuccessful.
83
     * made but was unsuccessful.
84
     */
84
     */
85
    public static final EmbeddedTokenList<TokenId> NO_DEFAULT_EMBEDDING
85
    public static final EmbeddedTokenList<TokenId> NO_DEFAULT_EMBEDDING
86
            = new EmbeddedTokenList<TokenId>(null, null, null, null);
86
            = new EmbeddedTokenList<TokenId>(null, null, null);
87
    
87
    
88
    /**
88
    /**
89
     * Embedding container carries info about the token into which this
89
     * Embedding container carries info about the token into which this
Lines 114-178 Link Here
114
     */
114
     */
115
    private EmbeddedTokenList<?> nextEmbeddedTokenList; // 52 bytes
115
    private EmbeddedTokenList<?> nextEmbeddedTokenList; // 52 bytes
116
    
116
    
117
    /**
118
     * Additional information in case this ETL is contained in a JoinTokenList.
119
     * <br/>
120
     * Through this info a reference to the JoinTokenList is held. There is no other
121
     * indexed structure so the EmbeddedTokenList members of TokenListList
122
     * must be binary-searched.
123
     */
124
    public EmbeddedJoinInfo joinInfo; // 56 bytes
125
117
    
126
    
118
    public EmbeddedTokenList(EmbeddingContainer<?> embeddingContainer,
127
    public EmbeddedTokenList(EmbeddingContainer<?> embeddingContainer,
119
    LanguagePath languagePath, LanguageEmbedding<T> embedding,
128
            LanguagePath languagePath, LanguageEmbedding<T> embedding
120
    EmbeddedTokenList<?> nextEmbedding) {
129
    ) {
130
        super(1); // Suitable for adding join-token parts
121
        this.embeddingContainer = embeddingContainer;
131
        this.embeddingContainer = embeddingContainer;
122
        this.languagePath = languagePath;
132
        this.languagePath = languagePath;
123
        this.embedding = embedding;
133
        this.embedding = embedding;
124
        this.nextEmbeddedTokenList = nextEmbedding;
125
134
126
        if (embeddingContainer != null) { // ec may be null for NO_DEFAULT_EMBEDDING only
135
        if (embeddingContainer != null) { // ec may be null for NO_DEFAULT_EMBEDDING only
127
            laState = LAState.initState();
136
            initLAState();
128
            embeddingContainer.updateStatusImpl(); // Ensure startOffset() is up-to-date
129
        }
137
        }
130
    }
138
    }
131
139
132
    private void init() {
140
    public void initAllTokens() {
133
        if (embedding.joinSections()) {
141
        assert (!embedding.joinSections()); // Joined token creation must be used instead
134
            // Find the token list list - it should also init this token list
142
//        initLAState();
135
            root().tokenHierarchyOperation().tokenListList(languagePath);
136
        } else { // not joining => can lex individually
137
            init(null);
138
        }
139
    }
140
    
141
    public void init(Object relexState) {
142
        laState = (modCount() != -1 || testing) ? LAState.empty() : null;
143
144
        // Lex the whole input represented by token at once
143
        // Lex the whole input represented by token at once
145
        LexerInputOperation<T> lexerInputOperation = createLexerInputOperation(
144
        LexerInputOperation<T> lexerInputOperation = createLexerInputOperation(
146
                0, startOffset(), relexState);
145
                0, startOffset(), null);
147
        AbstractToken<T> token = lexerInputOperation.nextToken();
146
        AbstractToken<T> token = lexerInputOperation.nextToken();
148
        while (token != null) {
147
        while (token != null) {
149
            updateElementOffsetAdd(token); // must subtract startOffset()
148
            addToken(token, lexerInputOperation);
150
            add(token);
151
            if (laState != null) {
152
                laState = laState.add(lexerInputOperation.lookahead(),
153
                        lexerInputOperation.lexerState());
154
            }
155
            token = lexerInputOperation.nextToken();
149
            token = lexerInputOperation.nextToken();
156
        }
150
        }
157
        lexerInputOperation.release();
151
        lexerInputOperation.release();
158
        lexerInputOperation = null;
152
        lexerInputOperation = null;
153
        trimStorageToSize();
154
    }
155
    
156
    private void initLAState() {
157
        this.laState = (modCount() != LexerUtilsConstants.MOD_COUNT_IMMUTABLE_INPUT || testing)
158
                ? LAState.empty() // Will collect LAState
159
                : null;
160
    }
159
161
162
    /**
163
     * Return join token list with active token list positioned to this ETL
164
     * or return null if this.joinInfo == null.
165
     */
166
    public JoinTokenList<T> joinTokenList() {
167
        if (joinInfo != null) {
168
            TokenListList<T> tokenListList = rootTokenList().tokenHierarchyOperation().existingTokenListList(languagePath);
169
            int etlIndex = tokenListList.findIndex(startOffset());
170
            int tokenListStartIndex = etlIndex - joinInfo.tokenListIndex();
171
            JoinTokenList<T> jtl = new JoinTokenList<T>(tokenListList, joinInfo.base, tokenListStartIndex);
172
            // Position to this etl's join index
173
            jtl.setActiveTokenListIndex(etlIndex - tokenListStartIndex);
174
            return jtl;
175
        }
176
        return null;
177
    }
178
179
    /**
180
     * Add token without touching laState - suitable for JoinToken's handling.
181
     *
182
     * @param token non-null token
183
     */
184
    public void addToken(AbstractToken<T> token) {
185
        updateElementOffsetAdd(token); // must subtract startOffset()
186
        add(token);
187
    }
188
189
    public void addToken(AbstractToken<T> token, LexerInputOperation<T> lexerInputOperation) {
190
        addToken(token);
191
        if (laState != null) { // maintaining lookaheads and states
192
            // Only get LA and state when necessary (especially lexerState() may be costly)
193
            laState = laState.add(lexerInputOperation.lookahead(), lexerInputOperation.lexerState());
194
        }
195
    }
196
197
    /**
198
     * Used when dealing with PartToken instances.
199
     */
200
    public void addToken(AbstractToken<T> token, int lookahead, Object state) {
201
        addToken(token);
202
        if (laState != null) { // maintaining lookaheads and states
203
            laState = laState.add(lookahead, state);
204
        }
205
    }
206
207
    public void trimStorageToSize() {
160
        trimToSize(); // Compact storage
208
        trimToSize(); // Compact storage
161
        if (laState != null)
209
        if (laState != null)
162
            laState.trimToSize();
210
            laState.trimToSize();
163
    }
211
    }
164
    
212
    
165
    /**
213
    public EmbeddedTokenList<?> nextEmbeddedTokenList() {
166
     * Check whether this embedded token list is initialized.
167
     * <br/>
168
     * If not then the updating process should not touch it unless
169
     * the token list list exists for this particular language path.
170
     */
171
    public boolean isInited() {
172
        return (laState != LAState.initState());
173
    }
174
    
175
    EmbeddedTokenList<?> nextEmbeddedTokenList() {
176
        return nextEmbeddedTokenList;
214
        return nextEmbeddedTokenList;
177
    }
215
    }
178
    
216
    
Lines 189-211 Link Here
189
    }
227
    }
190
228
191
    public int tokenCount() {
229
    public int tokenCount() {
192
        synchronized (root()) {
230
        return tokenCountCurrent();
193
            if (laState == LAState.initState())
194
                init();
195
            return size();
196
        }
197
    }
231
    }
198
    
232
    
199
    public Object tokenOrEmbeddingContainer(int index) {
233
    public int tokenCountCurrent() {
200
        synchronized (root()) {
234
        return size();
201
            if (laState == LAState.initState())
235
    }
202
                init();
236
237
    public int joinTokenCount() {
238
        int tokenCount = tokenCountCurrent();
239
        if (tokenCount > 0 && joinInfo.joinTokenLastPartShift() > 0)
240
            tokenCount--;
241
        return tokenCount;
242
    }
243
244
    public TokenOrEmbedding<T> tokenOrEmbedding(int index) {
245
        synchronized (rootTokenList()) {
203
            return (index < size()) ? get(index) : null;
246
            return (index < size()) ? get(index) : null;
204
        }
247
        }
205
    }
206
    
207
    private Token existingToken(int index) {
208
        return LexerUtilsConstants.token(tokenOrEmbeddingContainer(index));
209
    }
248
    }
210
    
249
    
211
    public int lookahead(int index) {
250
    public int lookahead(int index) {
Lines 223-441 Link Here
223
     * For token hierarchy snapshots the returned value is corrected
262
     * For token hierarchy snapshots the returned value is corrected
224
     * in the TokenSequence explicitly by adding TokenSequence.tokenOffsetDiff.
263
     * in the TokenSequence explicitly by adding TokenSequence.tokenOffsetDiff.
225
     */
264
     */
226
    public int tokenOffset(int index) {
265
    public int tokenOffsetByIndex(int index) {
227
//        embeddingContainer().checkStatusUpdated();
266
//        embeddingContainer().checkStatusUpdated();
228
        return elementOffset(index);
267
        return elementOffset(index);
229
    }
268
    }
230
269
231
    public int childTokenOffset(int rawOffset) {
270
    public int tokenOffset(AbstractToken<T> token) {
232
        // Need to make sure that the startOffset is up-to-date
271
        if (token.getClass() == JoinToken.class) {
233
        embeddingContainer.updateStatus();
272
            return token.offset(null);
234
        return childTokenOffsetNoUpdate(rawOffset);
273
        }
235
    }
274
        int rawOffset = token.rawOffset();
236
    
237
    public int childTokenOffsetNoUpdate(int rawOffset) {
238
//        embeddingContainer().checkStatusUpdated();
275
//        embeddingContainer().checkStatusUpdated();
239
        return embeddingContainer.tokenStartOffset() + embedding.startSkipLength()
276
        int relOffset = (rawOffset < offsetGapStart())
240
            + childTokenRelOffset(rawOffset);
277
                ? rawOffset
278
                : rawOffset - offsetGapLength();
279
        return startOffset() + relOffset;
241
    }
280
    }
242
281
243
    /**
282
    public int[] tokenIndex(int offset) {
244
     * Get difference between start offset of the particular child token
283
        return LexerUtilsConstants.tokenIndexBinSearch(this, offset, tokenCountCurrent());
245
     * against start offset of the root token.
246
     */
247
    public int childTokenOffsetShift(int rawOffset) {
248
        // Need to make sure that the startOffsetShift is up-to-date
249
        embeddingContainer.updateStatus();
250
        return embeddingContainer.rootTokenOffsetShift() + childTokenRelOffset(rawOffset);
251
    }
252
253
    /**
254
     * Get child token's real offset which is always a relative value
255
     * to startOffset value.
256
     */
257
    private int childTokenRelOffset(int rawOffset) {
258
        return (rawOffset < offsetGapStart())
259
                ? rawOffset
260
                : rawOffset - offsetGapLength();
261
    }
262
263
    public char childTokenCharAt(int rawOffset, int index) {
264
//        embeddingContainer().checkStatusUpdated();
265
        // Do not update the start offset shift - the token.text()
266
        // did it before returning its result and its contract
267
        // specifies that.
268
        // Return chars by delegating to rootToken
269
        return embeddingContainer.charAt(
270
                embedding.startSkipLength() + childTokenRelOffset(rawOffset) + index);
271
    }
284
    }
272
285
273
    public int modCount() {
286
    public int modCount() {
274
        // Delegate to root to have the most up-to-date value for token sequence's check.
287
        // Mod count of EC must be returned to allow custom removed embeddings to work
275
        // Extra synchronization should not be necessary since the TokenSequence.embedded()
288
        //  - they set LexerUtilsConstants.MOD_COUNT_REMOVED as cachedModCount.
276
        // calls EmbeddingContainer.embeddedTokenList()
277
        // which calls which contains the synchronization and calls updateStatusImpl().
278
        return embeddingContainer.cachedModCount();
289
        return embeddingContainer.cachedModCount();
279
    }
290
    }
280
    
291
    
281
    @Override
292
    @Override
282
    public int startOffset() { // used by FlyOffsetGapList
293
    public int startOffset() { // used by FlyOffsetGapList
283
//        embeddingContainer.checkStatusUpdated();
294
//        embeddingContainer.checkStatusUpdated();
284
        return embeddingContainer.tokenStartOffset() + embedding.startSkipLength();
295
        return embeddingContainer.branchTokenStartOffset() + embedding.startSkipLength();
285
    }
296
    }
286
    
297
    
287
    public int endOffset() {
298
    public int endOffset() {
288
//        embeddingContainer.checkStatusUpdated();
299
//        embeddingContainer.checkStatusUpdated();
289
        return embeddingContainer.tokenStartOffset() + embeddingContainer.token().length()
300
        return embeddingContainer.branchTokenStartOffset() + embeddingContainer.token().length()
290
                - embedding.endSkipLength();
301
                - embedding.endSkipLength();
291
    }
302
    }
292
    
303
    
304
    public int textLength() {
305
        return embeddingContainer.token().length() - embedding.startSkipLength() - embedding.endSkipLength();
306
    }
307
    
293
    public boolean isRemoved() {
308
    public boolean isRemoved() {
294
        embeddingContainer.updateStatusImpl();
295
        return embeddingContainer.isRemoved();
309
        return embeddingContainer.isRemoved();
296
    }
310
    }
297
311
298
    public TokenList<?> root() {
312
    public TokenList<?> rootTokenList() {
299
        return embeddingContainer.rootTokenList();
313
        return embeddingContainer.rootTokenList();
300
    }
314
    }
301
    
315
316
    public CharSequence inputSourceText() {
317
        return rootTokenList().inputSourceText();
318
    }
319
302
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
320
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
303
        return root().tokenHierarchyOperation();
321
        return rootTokenList().tokenHierarchyOperation();
304
    }
322
    }
305
    
323
    
306
    public AbstractToken<?> rootToken() {
324
    protected int elementRawOffset(TokenOrEmbedding<T> elem) {
307
        return embeddingContainer.rootToken();
325
        return elem.token().rawOffset();
308
    }
326
    }
309
327
310
    protected int elementRawOffset(Object elem) {
328
    protected void setElementRawOffset(TokenOrEmbedding<T> elem, int rawOffset) {
311
        return (elem.getClass() == EmbeddingContainer.class)
329
        elem.token().setRawOffset(rawOffset);
312
            ? ((EmbeddingContainer)elem).token().rawOffset()
313
            : ((AbstractToken<?>)elem).rawOffset();
314
    }
315
316
    protected void setElementRawOffset(Object elem, int rawOffset) {
317
        if (elem.getClass() == EmbeddingContainer.class)
318
            ((EmbeddingContainer)elem).token().setRawOffset(rawOffset);
319
        else
320
            ((AbstractToken<?>)elem).setRawOffset(rawOffset);
321
    }
330
    }
322
    
331
    
323
    protected boolean isElementFlyweight(Object elem) {
332
    protected boolean isElementFlyweight(TokenOrEmbedding<T> elem) {
324
        // token wrapper always contains non-flyweight token
333
        return elem.token().isFlyweight();
325
        return (elem.getClass() != EmbeddingContainer.class)
326
            && ((AbstractToken<?>)elem).isFlyweight();
327
    }
334
    }
328
    
335
    
329
    protected int elementLength(Object elem) {
336
    protected int elementLength(TokenOrEmbedding<T> elem) {
330
        return LexerUtilsConstants.token(elem).length();
337
        return elem.token().length();
331
    }
338
    }
332
    
339
    
333
    public AbstractToken<T> replaceFlyToken(
340
    public AbstractToken<T> replaceFlyToken(
334
    int index, AbstractToken<T> flyToken, int offset) {
341
    int index, AbstractToken<T> flyToken, int offset) {
335
        synchronized (root()) {
342
        synchronized (rootTokenList()) {
336
            TextToken<T> nonFlyToken = ((TextToken<T>)flyToken).createCopy(this, offset2Raw(offset));
343
            TextToken<T> nonFlyToken = ((TextToken<T>)flyToken).createCopy(this, offset2Raw(offset));
337
            set(index, nonFlyToken);
344
            set(index, nonFlyToken);
338
            return nonFlyToken;
345
            return nonFlyToken;
339
        }
346
        }
340
    }
347
    }
341
348
342
    public void wrapToken(int index, EmbeddingContainer embeddingContainer) {
349
    public void wrapToken(int index, EmbeddingContainer<T> embeddingContainer) {
343
        synchronized (root()) {
350
        synchronized (rootTokenList()) {
344
            set(index, embeddingContainer);
351
            set(index, embeddingContainer);
345
        }
352
        }
346
    }
353
    }
347
354
348
    public InputAttributes inputAttributes() {
355
    public InputAttributes inputAttributes() {
349
        return root().inputAttributes();
356
        return rootTokenList().inputAttributes();
350
    }
357
    }
351
358
352
    // MutableTokenList extra methods
359
    // MutableTokenList extra methods
353
    public Object tokenOrEmbeddingContainerUnsync(int index) {
360
    public TokenOrEmbedding<T> tokenOrEmbeddingUnsync(int index) {
354
        return get(index);
361
        return get(index);
355
    }
356
357
    public int tokenCountCurrent() {
358
        return size();
359
    }
362
    }
360
363
361
    public LexerInputOperation<T> createLexerInputOperation(
364
    public LexerInputOperation<T> createLexerInputOperation(
362
    int tokenIndex, int relexOffset, Object relexState) {
365
    int tokenIndex, int relexOffset, Object relexState) {
363
//        embeddingContainer.checkStatusUpdated();
366
//        embeddingContainer.checkStatusUpdated();
364
        CharSequence tokenText = embeddingContainer.token().text();
367
//        AbstractToken<?> branchToken = embeddingContainer.token();
365
        int tokenStartOffset = embeddingContainer.tokenStartOffset();
368
        int endOffset = endOffset();
366
        if (tokenText == null) { // Should not normally happen - debug the state
369
//        assert (!branchToken.isRemoved()) : "No lexing when token is removed";
367
            throw new IllegalStateException("Text of parent token is null. tokenStartOffset=" + tokenStartOffset +
370
//        assert (relexOffset >= startOffset()) : "Invalid relexOffset=" + relexOffset + " < startOffset()=" + startOffset();
368
                    ", tokenIndex=" + tokenIndex + ", relexOffset=" + relexOffset + ", relexState=" + relexState +
371
        assert (relexOffset <= endOffset) : "Invalid relexOffset=" + relexOffset + " > endOffset()=" + endOffset;
369
                    ", languagePath=" + languagePath() + ", inited=" + isInited()
372
        return new TextLexerInputOperation<T>(this, tokenIndex, relexState, relexOffset, endOffset);
370
            );
371
        }
372
        int endOffset = tokenStartOffset + tokenText.length()
373
            - embedding.endSkipLength();
374
        return new TextLexerInputOperation<T>(this, tokenIndex, relexState, tokenText,
375
                tokenStartOffset, relexOffset, endOffset);
376
    }
373
    }
377
374
378
    public boolean isFullyLexed() {
375
    public boolean isFullyLexed() {
379
        return true;
376
        return true;
380
    }
377
    }
381
378
382
    public void replaceTokens(TokenListChange<T> change, int removeTokenCount, int diffLength) {
379
    public void replaceTokens(TokenListChange<T> change, int diffLength) {
383
        int index = change.index();
380
        int index = change.index();
384
        // Remove obsolete tokens (original offsets are retained)
381
        // Remove obsolete tokens (original offsets are retained)
385
        Object[] removedTokensOrEmbeddingContainers = new Object[removeTokenCount];
382
        int removedTokenCount = change.removedTokenCount();
386
        copyElements(index, index + removeTokenCount, removedTokensOrEmbeddingContainers, 0);
383
        int rootModCount = rootTokenList().modCount();
387
        int offset = change.offset();
384
        AbstractToken<T> firstRemovedToken = null;
388
        for (int i = 0; i < removeTokenCount; i++) {
385
        if (removedTokenCount > 0) {
389
            Object tokenOrEmbeddingContainer = removedTokensOrEmbeddingContainers[i];
386
            @SuppressWarnings("unchecked")
390
            AbstractToken<?> token;
387
            TokenOrEmbedding<T>[] removedTokensOrEmbeddings = new TokenOrEmbedding[removedTokenCount];
391
            // It's necessary to update-status of all removed tokens' contained embeddings
388
            copyElements(index, index + removedTokenCount, removedTokensOrEmbeddings, 0);
392
            // since otherwise (if they would not be up-to-date) they could not be updated later
389
            firstRemovedToken = removedTokensOrEmbeddings[0].token();
393
            // as they lose their parent token list which the update-status relies on.
390
            for (int i = 0; i < removedTokenCount; i++) {
394
            if (tokenOrEmbeddingContainer.getClass() == EmbeddingContainer.class) {
391
                TokenOrEmbedding<T> tokenOrEmbedding = removedTokensOrEmbeddings[i];
395
                EmbeddingContainer<?> ec = (EmbeddingContainer<?>)tokenOrEmbeddingContainer;
392
                // It's necessary to update-status of all removed tokens' contained embeddings
396
                ec.updateStatusAndInvalidate();
393
                // since otherwise (if they would not be up-to-date) they could not be updated later
397
                token = ec.token();
394
                // as they lose their parent token list which the update-status relies on.
398
            } else { // Regular token
395
                EmbeddingContainer<T> ec = tokenOrEmbedding.embedding();
399
                token = (AbstractToken<?>)tokenOrEmbeddingContainer;
396
                if (ec != null) {
397
                    ec.updateStatusUnsyncAndMarkRemoved();
398
                    assert (ec.cachedModCount() != rootModCount) : "ModCount already updated"; // NOI18N
399
                }
400
                AbstractToken<T> token = tokenOrEmbedding.token();
401
                if (!token.isFlyweight()) {
402
                    updateElementOffsetRemove(token);
403
                    token.setTokenList(null);
404
                }
400
            }
405
            }
401
            if (!token.isFlyweight()) {
406
            remove(index, removedTokenCount); // Retain original offsets
402
                updateElementOffsetRemove(token);
407
            laState.remove(index, removedTokenCount); // Remove lookaheads and states
403
                token.setTokenList(null);
408
            change.setRemovedTokens(removedTokensOrEmbeddings);
409
        }
410
411
        if (diffLength != 0) { // JoinTokenList may pass 0 to not do any offset updates
412
            // Move and fix the gap according to the performed modification.
413
            // Instead of modOffset the gap is located at first relexed token's start
414
            // because then the already precomputed index corresponding to the given offset
415
            // can be reused. Otherwise there would have to be another binary search for index.
416
            int startOffset = startOffset(); // updateStatus() should already be called
417
            if (offsetGapStart() != change.offset() - startOffset) {
418
                // Minimum of the index of the first removed index and original computed index
419
                moveOffsetGap(change.offset() - startOffset, change.index());
404
            }
420
            }
405
            offset += token.length();
421
            updateOffsetGapLength(-diffLength);
406
        }
422
        }
407
        remove(index, removeTokenCount); // Retain original offsets
408
        laState.remove(index, removeTokenCount); // Remove lookaheads and states
409
        change.setRemovedTokens(removedTokensOrEmbeddingContainers);
410
        change.setRemovedEndOffset(offset);
411
412
        // Move and fix the gap according to the performed modification.
413
        int startOffset = startOffset(); // updateStatus() should already be called
414
        if (offsetGapStart() != change.offset() - startOffset) {
415
            // Minimum of the index of the first removed index and original computed index
416
            moveOffsetGap(change.offset() - startOffset, Math.min(index, change.offsetGapIndex()));
417
        }
418
        updateOffsetGapLength(-diffLength);
419
423
420
        // Add created tokens.
424
        // Add created tokens.
421
        // This should be called early when all the members are true tokens
425
        // This should be called early when all the members are true tokens
422
        List<Object> addedTokensOrBranches = change.addedTokensOrBranches();
426
        List<TokenOrEmbedding<T>> addedTokenOrEmbeddings = change.addedTokenOrEmbeddings();
423
        if (addedTokensOrBranches != null) {
427
        if (addedTokenOrEmbeddings != null) {
424
            for (Object tokenOrBranch : addedTokensOrBranches) {
428
            for (TokenOrEmbedding<T> tokenOrEmbedding : addedTokenOrEmbeddings) {
425
                @SuppressWarnings("unchecked")
429
                updateElementOffsetAdd(tokenOrEmbedding.token());
426
                AbstractToken<T> token = (AbstractToken<T>)tokenOrBranch;
427
                updateElementOffsetAdd(token);
428
            }
430
            }
429
            addAll(index, addedTokensOrBranches);
431
            addAll(index, addedTokenOrEmbeddings);
430
            laState = laState.addAll(index, change.laState());
432
            laState = laState.addAll(index, change.laState());
431
            change.syncAddedTokenCount();
433
            change.syncAddedTokenCount();
432
            // Check for bounds change only
434
            // Check for bounds change only
433
            if (removeTokenCount == 1 && addedTokensOrBranches.size() == 1) {
435
            if (removedTokenCount == 1 && addedTokenOrEmbeddings.size() == 1) {
434
                // Compare removed and added token ids and part types
436
                // Compare removed and added token ids and part types
435
                AbstractToken<T> removedToken = LexerUtilsConstants.token(removedTokensOrEmbeddingContainers[0]);
436
                AbstractToken<T> addedToken = change.addedToken(0);
437
                AbstractToken<T> addedToken = change.addedToken(0);
437
                if (removedToken.id() == addedToken.id()
438
                if (firstRemovedToken.id() == addedToken.id()
438
                    && removedToken.partType() == addedToken.partType()
439
                    && firstRemovedToken.partType() == addedToken.partType()
439
                ) {
440
                ) {
440
                    change.markBoundsChange();
441
                    change.markBoundsChange();
441
                }
442
                }
Lines 459-478 Link Here
459
        this.embeddingContainer = embeddingContainer;
460
        this.embeddingContainer = embeddingContainer;
460
    }
461
    }
461
    
462
    
462
    public String toStringHeader() {
463
    public StringBuilder dumpInfo(StringBuilder sb) {
463
        StringBuilder sb = new StringBuilder(50);
464
        if (sb == null) {
464
        sb.append("ETL: <").append(startOffset());
465
            sb = new StringBuilder(50);
466
        }
467
        sb.append("ETL<").append(startOffset());
465
        sb.append(",").append(endOffset());
468
        sb.append(",").append(endOffset());
466
        sb.append(">");
469
        sb.append("> TC=").append(tokenCountCurrent());
467
        sb.append(" IHC=").append(System.identityHashCode(this));
470
        sb.append("(").append(joinTokenCount()).append(')');
471
        if (joinInfo != null) {
472
            sb.append(" JI:");
473
            joinInfo.dumpInfo(sb);
474
        }
475
        sb.append(", IHC=").append(System.identityHashCode(this));
468
        sb.append('\n');
476
        sb.append('\n');
469
        return sb.toString();
477
        return sb;
470
    }
478
    }
471
479
472
    @Override
480
    @Override
473
    public String toString() {
481
    public String toString() {
474
        StringBuilder sb = new StringBuilder(256);
482
        StringBuilder sb = new StringBuilder(256);
475
        sb.append(toStringHeader());
483
        dumpInfo(sb);
476
        LexerUtilsConstants.appendTokenList(sb, this);
484
        LexerUtilsConstants.appendTokenList(sb, this);
477
        return sb.toString();
485
        return sb.toString();
478
    }
486
    }
(-)a/lexer/src/org/netbeans/lib/lexer/EmbeddingContainer.java (-170 / +169 lines)
Lines 41-46 Link Here
41
41
42
package org.netbeans.lib.lexer;
42
package org.netbeans.lib.lexer;
43
43
44
import org.netbeans.lib.lexer.inc.TokenHierarchyUpdate;
44
import org.netbeans.api.lexer.Language;
45
import org.netbeans.api.lexer.Language;
45
import org.netbeans.api.lexer.LanguagePath;
46
import org.netbeans.api.lexer.LanguagePath;
46
import org.netbeans.api.lexer.TokenHierarchyEventType;
47
import org.netbeans.api.lexer.TokenHierarchyEventType;
Lines 68-75 Link Here
68
 * @version 1.00
69
 * @version 1.00
69
 */
70
 */
70
71
71
public final class EmbeddingContainer<T extends TokenId> {
72
public final class EmbeddingContainer<T extends TokenId> implements TokenOrEmbedding<T> {
72
    
73
73
    /**
74
    /**
74
     * Get embedded token list.
75
     * Get embedded token list.
75
     *
76
     *
Lines 79-106 Link Here
79
     *  should be obtained.
80
     *  should be obtained.
80
     * @param language whether only language embeddding of the particular language
81
     * @param language whether only language embeddding of the particular language
81
     *  was requested. It may be null if any embedding should be returned.
82
     *  was requested. It may be null if any embedding should be returned.
83
     * @param initTokensInNew true if tokens should be created when a new ETL gets created.
84
     *  False in case this is called from TokenListList to grab ETLs for sections joining.
82
     */
85
     */
83
    public static <T extends TokenId, ET extends TokenId> EmbeddedTokenList<ET> embeddedTokenList(
86
    public static <T extends TokenId, ET extends TokenId> EmbeddedTokenList<ET> embeddedTokenList(
84
    TokenList<T> tokenList, int index, Language<ET> embeddedLanguage) {
87
    TokenList<T> tokenList, int index, Language<ET> embeddedLanguage, boolean initTokensInNew) {
85
        EmbeddingContainer<T> ec;
88
        TokenList<?> rootTokenList = tokenList.rootTokenList();
86
        AbstractToken<T> token;
87
        EmbeddingPresence ep;
88
        TokenList<?> rootTokenList = tokenList.root();
89
        synchronized (rootTokenList) {
89
        synchronized (rootTokenList) {
90
            Object tokenOrEmbeddingContainer = tokenList.tokenOrEmbeddingContainer(index);
90
            TokenOrEmbedding<T> tokenOrEmbedding = tokenList.tokenOrEmbedding(index);
91
            if (tokenOrEmbeddingContainer.getClass() == EmbeddingContainer.class) {
91
            EmbeddingContainer<T> ec = tokenOrEmbedding.embedding();
92
                // Embedding container exists
92
            AbstractToken<T> token = tokenOrEmbedding.token();
93
                @SuppressWarnings("unchecked")
93
            EmbeddingPresence ep;
94
                EmbeddingContainer<T> ecUC = (EmbeddingContainer<T>)tokenOrEmbeddingContainer;
94
            if (ec != null) {
95
                ec = ecUC;
96
                token = ec.token();
97
                ep = null;
95
                ep = null;
98
99
            } else { // No embedding was created yet
96
            } else { // No embedding was created yet
100
                ec = null;
101
                @SuppressWarnings("unchecked")
102
                AbstractToken<T> t = (AbstractToken<T>)tokenOrEmbeddingContainer;
103
                token = t;
104
                // Check embedding presence
97
                // Check embedding presence
105
                ep = LexerUtilsConstants.innerLanguageOperation(tokenList.languagePath()).embeddingPresence(token.id());
98
                ep = LexerUtilsConstants.innerLanguageOperation(tokenList.languagePath()).embeddingPresence(token.id());
106
                if (ep == EmbeddingPresence.NONE) {
99
                if (ep == EmbeddingPresence.NONE) {
Lines 112-118 Link Here
112
            // need to be processed to find the embedded token list for requested language.
105
            // need to be processed to find the embedded token list for requested language.
113
            EmbeddedTokenList<?> prevEtl;
106
            EmbeddedTokenList<?> prevEtl;
114
            if (ec != null) {
107
            if (ec != null) {
115
                ec.updateStatusImpl();
108
                ec.updateStatusUnsync();
116
                EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
109
                EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
117
                prevEtl = null;
110
                prevEtl = null;
118
                while (etl != null) {
111
                while (etl != null) {
Lines 154-161 Link Here
154
                            setEmbeddingPresence(token.id(), EmbeddingPresence.ALWAYS_QUERY);
147
                            setEmbeddingPresence(token.id(), EmbeddingPresence.ALWAYS_QUERY);
155
                }
148
                }
156
                // Check whether the token contains enough text to satisfy embedding's start and end skip lengths
149
                // Check whether the token contains enough text to satisfy embedding's start and end skip lengths
157
                CharSequence text = token.text(); // Should not be null here but rather check
150
                if (token.isRemoved() || embedding.startSkipLength() + embedding.endSkipLength() > token.length()) {
158
                if (text == null || embedding.startSkipLength() + embedding.endSkipLength() > text.length()) {
159
                    return null;
151
                    return null;
160
                }
152
                }
161
                if (ec == null) {
153
                if (ec == null) {
Lines 165-174 Link Here
165
                LanguagePath embeddedLanguagePath = LanguagePath.get(languagePath,
157
                LanguagePath embeddedLanguagePath = LanguagePath.get(languagePath,
166
                        embedding.language());
158
                        embedding.language());
167
                EmbeddedTokenList<ET> etl = new EmbeddedTokenList<ET>(ec,
159
                EmbeddedTokenList<ET> etl = new EmbeddedTokenList<ET>(ec,
168
                        embeddedLanguagePath, embedding, null);
160
                        embeddedLanguagePath, embedding);
169
                // Preceding code should ensure that (prevEtl.nextEmbeddedTokenList == null)
161
                // Preceding code should ensure that (prevEtl.nextEmbeddedTokenList == null)
170
                // so no need to call etl.setNextEmbeddedTokenList(prevEtl.nextEmbeddedTokenList())
162
                // so no need to call etl.setNextEmbeddedTokenList(prevEtl.nextEmbeddedTokenList())
171
                ec.addEmbeddedTokenList(prevEtl, etl, true);
163
                ec.addEmbeddedTokenList(prevEtl, etl, true);
164
165
                if (initTokensInNew) {
166
                    if (embedding.joinSections()) {
167
                        // Init corresponding TokenListList
168
                        rootTokenList.tokenHierarchyOperation().tokenListList(embeddedLanguagePath);
169
                    } else { // sections not joined
170
                        etl.initAllTokens();
171
                    }
172
                }
172
                return (embeddedLanguage == null || embeddedLanguage == embedding.language()) ? etl : null;
173
                return (embeddedLanguage == null || embeddedLanguage == embedding.language()) ? etl : null;
173
            }
174
            }
174
            // Update embedding presence to NONE
175
            // Update embedding presence to NONE
Lines 200-206 Link Here
200
    public static <T extends TokenId, ET extends TokenId> boolean createEmbedding(
201
    public static <T extends TokenId, ET extends TokenId> boolean createEmbedding(
201
    TokenList<T> tokenList, int index, Language<ET> embeddedLanguage,
202
    TokenList<T> tokenList, int index, Language<ET> embeddedLanguage,
202
    int startSkipLength, int endSkipLength, boolean joinSections) {
203
    int startSkipLength, int endSkipLength, boolean joinSections) {
203
        TokenList<?> rootTokenList = tokenList.root();
204
        TokenList<?> rootTokenList = tokenList.rootTokenList();
204
        // Only create embedddings for valid operations so not e.g. for removed token list
205
        // Only create embedddings for valid operations so not e.g. for removed token list
205
        AbstractToken<T> token;
206
        AbstractToken<T> token;
206
        EmbeddingContainer<T> ec;
207
        EmbeddingContainer<T> ec;
Lines 217-228 Link Here
217
            tokenHierarchyOperation = tokenList.tokenHierarchyOperation();
218
            tokenHierarchyOperation = tokenList.tokenHierarchyOperation();
218
            tokenHierarchyOperation.ensureWriteLocked();
219
            tokenHierarchyOperation.ensureWriteLocked();
219
220
220
            Object tokenOrEmbeddingContainer = tokenList.tokenOrEmbeddingContainer(index);
221
            TokenOrEmbedding<T> tokenOrEmbedding = tokenList.tokenOrEmbedding(index);
221
            if (tokenOrEmbeddingContainer.getClass() == EmbeddingContainer.class) {
222
            ec = tokenOrEmbedding.embedding();
222
                // Embedding container exists
223
            token = tokenOrEmbedding.token();
223
                @SuppressWarnings("unchecked")
224
            if (ec != null) {
224
                EmbeddingContainer<T> ecUC = (EmbeddingContainer<T>)tokenOrEmbeddingContainer;
225
                ec = ecUC;
226
                EmbeddedTokenList etl2 = ec.firstEmbeddedTokenList();
225
                EmbeddedTokenList etl2 = ec.firstEmbeddedTokenList();
227
                while (etl2 != null) {
226
                while (etl2 != null) {
228
                    if (embeddedLanguage == etl2.languagePath().innerLanguage()) {
227
                    if (embeddedLanguage == etl2.languagePath().innerLanguage()) {
Lines 230-240 Link Here
230
                    }
229
                    }
231
                    etl2 = etl2.nextEmbeddedTokenList();
230
                    etl2 = etl2.nextEmbeddedTokenList();
232
                }
231
                }
233
                token = ec.token();
234
            } else {
232
            } else {
235
                @SuppressWarnings("unchecked")
236
                AbstractToken<T> t = (AbstractToken<T>)tokenOrEmbeddingContainer;
237
                token = t;
238
                if (token.isFlyweight()) { // embedding cannot exist for this flyweight token
233
                if (token.isFlyweight()) { // embedding cannot exist for this flyweight token
239
                    return false;
234
                    return false;
240
                }
235
                }
Lines 253-264 Link Here
253
            tokenHierarchyOperation.addLanguagePath(embeddedLanguagePath);
248
            tokenHierarchyOperation.addLanguagePath(embeddedLanguagePath);
254
            // Make the embedded token list to be the first in the list
249
            // Make the embedded token list to be the first in the list
255
            etl = new EmbeddedTokenList<ET>(
250
            etl = new EmbeddedTokenList<ET>(
256
                    ec, embeddedLanguagePath, embedding, ec.firstEmbeddedTokenList());
251
                    ec, embeddedLanguagePath, embedding);
257
            ec.addEmbeddedTokenList(null, etl, false);
252
            ec.addEmbeddedTokenList(null, etl, false);
258
            
253
            
259
            // Fire the embedding creation to the clients
254
            // Fire the embedding creation to the clients
260
            // Threading model may need to be changed if necessary
255
            // Threading model may need to be changed if necessary
261
            tokenStartOffset = ec.tokenStartOffset();
256
            tokenStartOffset = ec.branchTokenStartOffset();
262
            eventInfo = new TokenHierarchyEventInfo(
257
            eventInfo = new TokenHierarchyEventInfo(
263
                    tokenHierarchyOperation,
258
                    tokenHierarchyOperation,
264
                    TokenHierarchyEventType.EMBEDDING_CREATED,
259
                    TokenHierarchyEventType.EMBEDDING_CREATED,
Lines 269-285 Link Here
269
            // When joining sections ensure that the token list list gets created
264
            // When joining sections ensure that the token list list gets created
270
            // and the embedded tokens get created because they must exist
265
            // and the embedded tokens get created because they must exist
271
            // before possible next updating of the token list.
266
            // before possible next updating of the token list.
272
            TokenListList tll = tokenHierarchyOperation.existingTokenListList(etl.languagePath());
267
            TokenListList<ET> tll = tokenHierarchyOperation.existingTokenListList(etl.languagePath());
268
            if (!embedding.joinSections()) {
269
                etl.initAllTokens();
270
            }
273
            if (tll != null) {
271
            if (tll != null) {
274
                // Update tll by embedding creation
272
                // Update tll by embedding creation
275
                new TokenHierarchyUpdate(eventInfo).updateCreateEmbedding(etl);
273
                new TokenHierarchyUpdate(eventInfo).updateCreateOrRemoveEmbedding(etl, true);
276
            } else { // tll == null
274
            } else { // tll == null
277
                if (embedding.joinSections()) {
275
                if (embedding.joinSections()) {
278
                    // Force token list list creation only when joining sections
276
                    // Force token list list creation only when joining sections
279
                    tll = tokenHierarchyOperation.tokenListList(etl.languagePath());
277
                    tll = tokenHierarchyOperation.tokenListList(etl.languagePath());
280
                }
278
                }
281
            }
279
            }
282
283
        }
280
        }
284
281
285
        // Construct outer token change info
282
        // Construct outer token change info
Lines 304-327 Link Here
304
    
301
    
305
    public static <T extends TokenId, ET extends TokenId> boolean removeEmbedding(
302
    public static <T extends TokenId, ET extends TokenId> boolean removeEmbedding(
306
    TokenList<T> tokenList, int index, Language<ET> embeddedLanguage) {
303
    TokenList<T> tokenList, int index, Language<ET> embeddedLanguage) {
307
        TokenList<?> rootTokenList = tokenList.root();
304
        TokenList<?> rootTokenList = tokenList.rootTokenList();
308
        // Only create embedddings for valid operations so not e.g. for removed token list
305
        // Only create embedddings for valid operations so not e.g. for removed token list
309
        EmbeddingContainer<T> ec;
306
        EmbeddingContainer<T> ec;
310
        synchronized (rootTokenList) {
307
        synchronized (rootTokenList) {
311
            // Check TL.isRemoved() under syncing of rootTokenList
308
            // Check TL.isRemoved() under syncing of rootTokenList
312
            if (tokenList.isRemoved()) // Do not create embedding for removed TLs
309
            if (tokenList.isRemoved()) // Do not remove embedding for removed TLs
313
                return false;
310
                return false;
314
            // If TL is not removed then THO should be non-null
311
            // If TL is not removed then THO should be non-null
315
            TokenHierarchyOperation<?,?> tokenHierarchyOperation = tokenList.tokenHierarchyOperation();
312
            TokenHierarchyOperation<?,?> tokenHierarchyOperation = tokenList.tokenHierarchyOperation();
316
            tokenHierarchyOperation.ensureWriteLocked();
313
            tokenHierarchyOperation.ensureWriteLocked();
317
314
318
            Object tokenOrEmbeddingContainer = tokenList.tokenOrEmbeddingContainer(index);
315
            TokenOrEmbedding<T> tokenOrEmbedding = tokenList.tokenOrEmbedding(index);
319
            if (tokenOrEmbeddingContainer.getClass() == EmbeddingContainer.class) {
316
            ec = tokenOrEmbedding.embedding();
320
                // Embedding container exists
317
            if (ec != null) {
321
                @SuppressWarnings("unchecked")
318
                ec.updateStatusUnsync();
322
                EmbeddingContainer<T> ecUC = (EmbeddingContainer<T>)tokenOrEmbeddingContainer;
323
                ec = ecUC;
324
                ec.updateStatusImpl();
325
                EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
319
                EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
326
                EmbeddedTokenList<?> prevEtl = null;
320
                EmbeddedTokenList<?> prevEtl = null;
327
                while (etl != null) {
321
                while (etl != null) {
Lines 337-346 Link Here
337
                        // State that the removed embedding was not default - should not matter anyway
331
                        // State that the removed embedding was not default - should not matter anyway
338
                        ec.addEmbeddedTokenList(null, etl, false);
332
                        ec.addEmbeddedTokenList(null, etl, false);
339
                        etl.setEmbeddingContainer(ec);
333
                        etl.setEmbeddingContainer(ec);
340
                        ec.invalidateChildren();
334
                        ec.markChildrenRemovedDeep();
341
335
342
                        // Fire the embedding creation to the clients
336
                        // Fire the embedding creation to the clients
343
                        int startOffset = ec.tokenStartOffset();
337
                        int startOffset = ec.branchTokenStartOffset();
344
                        TokenHierarchyEventInfo eventInfo = new TokenHierarchyEventInfo(
338
                        TokenHierarchyEventInfo eventInfo = new TokenHierarchyEventInfo(
345
                                tokenHierarchyOperation,
339
                                tokenHierarchyOperation,
346
                                TokenHierarchyEventType.EMBEDDING_REMOVED,
340
                                TokenHierarchyEventType.EMBEDDING_REMOVED,
Lines 369-375 Link Here
369
                        TokenListList tll = tokenHierarchyOperation.existingTokenListList(etl.languagePath());
363
                        TokenListList tll = tokenHierarchyOperation.existingTokenListList(etl.languagePath());
370
                        if (tll != null) {
364
                        if (tll != null) {
371
                            // update-status already called
365
                            // update-status already called
372
                            new TokenHierarchyUpdate(eventInfo).updateRemoveEmbedding(etl);
366
                            new TokenHierarchyUpdate(eventInfo).updateCreateOrRemoveEmbedding(etl, false);
373
                        }
367
                        }
374
368
375
                        // Fire the change
369
                        // Fire the change
Lines 383-446 Link Here
383
        return false;
377
        return false;
384
    }
378
    }
385
379
386
    private AbstractToken<T> token; // 12 bytes (8-super + 4)
380
    /**
381
     * Token wrapped by this EC.
382
     */
383
    private AbstractToken<T> branchToken; // 12 bytes (8-super + 4)
384
    
385
    /**
386
     * Root token list of the hierarchy should never be null and is final.
387
     * 
388
     */
389
    private final TokenList<?> rootTokenList; // 16 bytes
387
    
390
    
388
    /**
391
    /**
389
     * Cached modification count allows to determine whether the start offset
392
     * Cached modification count allows to determine whether the start offset
390
     * needs to be recomputed.
393
     * needs to be recomputed.
391
     */
394
     */
392
    private int cachedModCount; // 16 bytes
395
    private int cachedModCount; // 20 bytes
393
396
394
    /**
395
     * Root token list of the hierarchy.
396
     * 
397
     */
398
    private final TokenList<?> rootTokenList; // 20 bytes
399
    
400
    /**
401
     * The root embedding container to which this embedding container relates.
402
     * <br/>
403
     * It's used for getting of the start offset of the contained tokens
404
     * and for getting of their text.
405
     */
406
    private AbstractToken<?> rootToken; // 24 bytes
407
    
408
    /**
397
    /**
409
     * Cached start offset of the token for which this embedding container
398
     * Cached start offset of the token for which this embedding container
410
     * was created.
399
     * was created.
400
     * <br/>
401
     * Its value may be shared by multiple embedded token lists.
411
     */
402
     */
412
    private int tokenStartOffset; // 28 bytes
403
    private int branchTokenStartOffset; // 24 bytes
413
404
414
    /**
405
    /**
415
     * First embedded token list in the single-linked list.
406
     * First embedded token list in the single-linked list.
416
     */
407
     */
417
    private EmbeddedTokenList<?> firstEmbeddedTokenList; // 32 bytes
408
    private EmbeddedTokenList<?> firstEmbeddedTokenList; // 28 bytes
418
409
419
    /**
420
     * Difference between start offset of the first token in this token list
421
     * against the start offset of the root token.
422
     * <br>
423
     * The offset gets refreshed upon <code>updateStartOffset()</code>.
424
     */
425
    private int offsetShiftFromRootToken; // 36 bytes
426
    
427
    /**
410
    /**
428
     * Embedded token list that represents the default embedding.
411
     * Embedded token list that represents the default embedding.
429
     * It may be <code>EmbeddedTokenList.NO_DEFAULT_EMBEDDING</code>
412
     * It may be <code>EmbeddedTokenList.NO_DEFAULT_EMBEDDING</code>
430
     * for failed attempt to create a default embedding.
413
     * for failed attempt to create a default embedding.
431
     */
414
     */
432
    private EmbeddedTokenList<?> defaultEmbeddedTokenList; // 40 bytes
415
    private EmbeddedTokenList<?> defaultEmbeddedTokenList; // 32 bytes
433
    
416
    
434
    EmbeddingContainer(AbstractToken<T> token, TokenList<?> rootTokenList) {
417
    EmbeddingContainer(AbstractToken<T> branchToken, TokenList<?> rootTokenList) {
435
        this.token = token;
418
        if (branchToken == null)
419
            throw new IllegalArgumentException("branchToken cannot be null");
420
        if (rootTokenList == null)
421
            throw new IllegalArgumentException("rootTokenList cannot be null");
422
        this.branchToken = branchToken;
436
        this.rootTokenList = rootTokenList;
423
        this.rootTokenList = rootTokenList;
437
        this.rootToken = token; // Has to be non-null since updateStatusImpl() would not update null rootToken
438
        // cachedModCount must differ from root's one to sync offsets
424
        // cachedModCount must differ from root's one to sync offsets
439
        // Root mod count can be >= 0 or -1 for non-incremental token lists
425
        // Root mod count can be >= 0 or -1 for non-incremental token lists
440
        this.cachedModCount = -2;
426
        this.cachedModCount = LexerUtilsConstants.MOD_COUNT_EMBEDDED_INITIAL;
441
        // Update the tokenStartOffset etc. - this assumes that the token
427
        // Update the tokenStartOffset etc. - this assumes that the token
442
        // is already parented till the root token list.
428
        // is already parented till the root token list.
443
        updateStatusImpl();
429
        updateStatusUnsync();
444
    }
430
    }
445
    
431
    
446
    /**
432
    /**
Lines 456-479 Link Here
456
     * @param ec non-null existing embedding container.
442
     * @param ec non-null existing embedding container.
457
     */
443
     */
458
    EmbeddingContainer(EmbeddingContainer<T> ec) {
444
    EmbeddingContainer(EmbeddingContainer<T> ec) {
459
        this(ec.token(), ec.rootTokenList()); // Force init of tokenStartOffset and rootTokenOffsetShift
445
        this(ec.token(), ec.rootTokenList()); // Force init of tokenStartOffset
460
        invalidate();
446
        markRemoved();
461
    }
447
    }
462
    
448
    
463
    private void invalidate() {
449
    private void markRemoved() {
464
        this.rootToken = null;
450
        // Set cachedModCount to LexerUtilsConstants.MOD_COUNT_REMOVED which should not occur
465
        // Set cachedModCount to -2 which should not occur for regular cases
451
        // for regular cases which should force existing token sequences to be invalidated.
466
        // which should force existing token sequences to be invalidated.
452
        this.cachedModCount = LexerUtilsConstants.MOD_COUNT_REMOVED;
467
        this.cachedModCount = -2;
468
    }
453
    }
469
    
454
    
470
    void invalidateChildren() {
455
    void markChildrenRemovedDeep() { // Used by custom embedding removal
471
        EmbeddedTokenList etl = firstEmbeddedTokenList;
456
        EmbeddedTokenList etl = firstEmbeddedTokenList;
472
        while (etl != null && etl != EmbeddedTokenList.NO_DEFAULT_EMBEDDING) {
457
        while (etl != null && etl != EmbeddedTokenList.NO_DEFAULT_EMBEDDING) {
473
            for (int i = etl.tokenCountCurrent() - 1; i >= 0; i--) {
458
            for (int i = etl.tokenCountCurrent() - 1; i >= 0; i--) {
474
                Object tokenOrEC = etl.tokenOrEmbeddingContainerUnsync(i);
459
                EmbeddingContainer ec = etl.tokenOrEmbeddingUnsync(i).embedding();
475
                if (tokenOrEC.getClass() == EmbeddingContainer.class) {
460
                if (ec != null) {
476
                    ((EmbeddingContainer)tokenOrEC).invalidateChildren();
461
                    ec.updateStatusUnsync(); // First update the status
462
                    ec.markChildrenRemovedDeep();
463
                    ec.markRemoved(); // Mark removed with the correctly updated offsets
477
                }
464
                }
478
            }
465
            }
479
            etl = etl.nextEmbeddedTokenList();
466
            etl = etl.nextEmbeddedTokenList();
Lines 484-509 Link Here
484
        return cachedModCount;
471
        return cachedModCount;
485
    }
472
    }
486
    
473
    
487
    /**
474
    public final AbstractToken<T> token() {
488
     * Check if this embedding container is up-to-date (updateStatusImpl() was called on it)
475
        return branchToken;
489
     * which is useful for missing-update-status checks.
490
     */
491
    public void checkStatusUpdated() {
492
        if (cachedModCount != -2 && cachedModCount != rootTokenList.modCount()
493
                && !checkStatusUpdatedThrowingException
494
        ) {
495
            // Prevent OOME because of nested throwing of exc
496
            checkStatusUpdatedThrowingException = true;
497
            String excMsg = "!!!INTERNAL ERROR!!! Status not updated on " +
498
                    this + "\nin token hierarchy\n" + rootTokenList.tokenHierarchyOperation();
499
            checkStatusUpdatedThrowingException = false;
500
            throw new IllegalStateException(excMsg);
501
        }
502
    }
476
    }
503
    private static boolean checkStatusUpdatedThrowingException;
477
    
504
478
    public final EmbeddingContainer<T> embedding() {
505
    public AbstractToken<T> token() {
479
        return this;
506
        return token;
507
    }
480
    }
508
    
481
    
509
    /**
482
    /**
Lines 511-548 Link Here
511
     * The updateStatusImpl() should be called afterwards to update tokenStartOffset etc.
484
     * The updateStatusImpl() should be called afterwards to update tokenStartOffset etc.
512
     */
485
     */
513
    public void reinit(AbstractToken<T> token) {
486
    public void reinit(AbstractToken<T> token) {
514
        this.token = token;
487
        this.branchToken = token;
515
        TokenList<?> parentTokenList = token.tokenList();
488
        cachedModCount = LexerUtilsConstants.MOD_COUNT_EMBEDDED_INITIAL;
516
        assert (parentTokenList != null);
489
        updateStatusUnsync();
517
        if (parentTokenList.getClass() == EmbeddedTokenList.class) {
518
            rootToken = ((EmbeddedTokenList<?>)parentTokenList).rootToken();
519
        } else { // parent is a root token list: rootToken == token
520
            rootToken = token;
521
        }
522
        updateStatusImpl();
523
    }
490
    }
524
    
491
    
525
    public TokenList<?> rootTokenList() {
492
    public TokenList<?> rootTokenList() {
526
        return rootTokenList;
493
        return rootTokenList;
527
    }
494
    }
528
    
495
    
529
    public AbstractToken<?> rootToken() {
496
    public int branchTokenStartOffset() {
530
        return rootToken;
531
    }
532
533
    public int tokenStartOffset() {
534
//        checkStatusUpdated();
497
//        checkStatusUpdated();
535
        return tokenStartOffset;
498
        return branchTokenStartOffset;
536
    }
537
    
538
    public int rootTokenOffsetShift() {
539
//        checkStatusUpdated();
540
        return offsetShiftFromRootToken;
541
    }
542
    
543
    public char charAt(int tokenRelOffset) {
544
//        checkStatusUpdated();
545
        return rootToken.charAt(offsetShiftFromRootToken + tokenRelOffset);
546
    }
499
    }
547
    
500
    
548
    public EmbeddedTokenList<?> firstEmbeddedTokenList() {
501
    public EmbeddedTokenList<?> firstEmbeddedTokenList() {
Lines 606-651 Link Here
606
     */
559
     */
607
    public boolean isRemoved() {
560
    public boolean isRemoved() {
608
//        checkStatusUpdated();
561
//        checkStatusUpdated();
609
        return (rootToken == null);
562
        return (cachedModCount == LexerUtilsConstants.MOD_COUNT_REMOVED);
610
    }
563
    }
611
    
564
    
612
    public void updateStatusAndInvalidate() {
565
    public void updateStatusUnsyncAndMarkRemoved() {
613
        updateStatusImpl();
566
        updateStatusUnsync();
614
        invalidate();
567
        markRemoved();
615
    }
616
    
617
    public boolean updateStatus() {
618
        synchronized (rootTokenList) {
619
            return (updateStatusImpl() != null);
620
        }
621
    }
568
    }
622
    
569
    
623
    /**
570
    /**
624
     * Update and return root token corresponding to this embedding container.
571
     * Update status of this container in a synchronized way
572
     * ensuring that no other thread will interfere - this is suitable
573
     * for cases when there may be multiple concurrent readers
574
     * using a token hierarchy and calling Token.offset() for example.
575
     * <br/>
576
     * Status updating fixes value of cached start offset of wrapped branch token
577
     * (calling branch token(s) on upper level(s) for multiple embeddings' nesting).
578
     * 
579
     * @return true if token is still part of token hierarchy or false
580
     *  if it was removed.
625
     */
581
     */
626
    public AbstractToken<?> updateStatusImpl() {
582
    public boolean updateStatus() {
627
        if (rootToken == null)
583
        synchronized (rootTokenList) {
628
            return null; // Removed from hierarchy
584
            return updateStatusUnsync();
629
        int rootModCount;
585
        }
630
        if (cachedModCount != (rootModCount = rootTokenList.modCount())) {
586
    }
631
            cachedModCount = rootModCount;
587
632
            TokenList<?> parentTokenList = token.tokenList();
588
    /**
633
            if (parentTokenList == null) {
589
     * Unsynced synchronization of container - this method should only be used
634
                rootToken = null;
590
     * when there may be only a single thread accessing token hierarchy i.e. during
635
            } else if (parentTokenList.getClass() == EmbeddedTokenList.class) {
591
     * token hierarchy modifications upon mutable input source modifications.
636
                EmbeddedTokenList<?> parentEtl = (EmbeddedTokenList<?>)parentTokenList;
592
     * 
637
                rootToken = parentEtl.embeddingContainer().updateStatusImpl();
593
     * @return true if token is still part of token hierarchy or false
638
                tokenStartOffset = parentEtl.childTokenOffsetNoUpdate(token.rawOffset());
594
     *  if it was removed.
639
                EmbeddingContainer parentEC = parentEtl.embeddingContainer();
595
     * @see #updateStatus()
640
                offsetShiftFromRootToken = tokenStartOffset - parentEC.tokenStartOffset()
596
     */
641
                        + parentEC.rootTokenOffsetShift();
597
    public boolean updateStatusUnsync() {
642
            } else { // parent is a root token list: rootToken == token
598
        return (updateStatusImpl(rootTokenList.modCount()) != LexerUtilsConstants.MOD_COUNT_REMOVED);
643
                rootToken = token;
599
    }
644
                tokenStartOffset = token.offset(null);
600
645
                offsetShiftFromRootToken = 0;
601
    /**
602
     * Update the status of this embedding container when current mod count
603
     * of a root token list is given.
604
     *
605
     * @param rootModCount modCount of a root token list. The token list either
606
     *  updates to it or to LexerUtilsConstants.MOD_COUNT_REMOVED if it's removed
607
     *  from a token hierarchy. If called by nested embeddings they should finally
608
     *  update to the same value.
609
     * @return current modCount of this container.
610
     */
611
    protected int updateStatusImpl(int rootModCount) {
612
        if (cachedModCount != LexerUtilsConstants.MOD_COUNT_REMOVED &&
613
            cachedModCount != rootModCount
614
        ) {
615
            TokenList<T> parentTokenList = branchToken.tokenList();
616
            if (parentTokenList == null) { // branch token removed from its parent token list
617
                cachedModCount = LexerUtilsConstants.MOD_COUNT_REMOVED;
618
            } else if (parentTokenList.getClass() == EmbeddedTokenList.class) { // deeper level embedding
619
                EmbeddedTokenList<T> parentEtl = (EmbeddedTokenList<T>)parentTokenList;
620
                cachedModCount = parentEtl.embeddingContainer().updateStatusImpl(rootModCount);
621
                // After status of parent(s) was updated get the current branch token's offset
622
                branchTokenStartOffset = parentEtl.tokenOffset(branchToken);
623
            } else { // parent of branch token is a non-null root token list.
624
                cachedModCount = rootModCount;
625
                branchTokenStartOffset = parentTokenList.tokenOffset(branchToken);
646
            }
626
            }
647
        }
627
        }
648
        return rootToken;
628
        return cachedModCount;
649
    }
629
    }
650
630
631
    /**
632
     * Check if this embedding container is up-to-date (updateStatusImpl() was called on it)
633
     * which is useful for missing-update-status checks.
634
     */
635
    public void checkStatusUpdated() {
636
        if (cachedModCount != LexerUtilsConstants.MOD_COUNT_REMOVED
637
                && cachedModCount != rootTokenList.modCount()
638
                && !checkStatusUpdatedThrowingException
639
        ) {
640
            // Prevent OOME because of nested throwing of exc
641
            checkStatusUpdatedThrowingException = true;
642
            String excMsg = "!!!INTERNAL ERROR!!! Status not updated on " +
643
                    this + "\nin token hierarchy\n" + rootTokenList.tokenHierarchyOperation();
644
            checkStatusUpdatedThrowingException = false;
645
            throw new IllegalStateException(excMsg);
646
        }
647
    }
648
    private static boolean checkStatusUpdatedThrowingException;
649
651
}
650
}
(-)06a7890f802e (+415 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer;
43
44
import java.util.List;
45
import org.netbeans.api.lexer.PartType;
46
import org.netbeans.api.lexer.TokenId;
47
import org.netbeans.lib.editor.util.ArrayUtilities;
48
import org.netbeans.spi.lexer.LexerInput;
49
import org.netbeans.lib.lexer.token.AbstractToken;
50
import org.netbeans.lib.lexer.token.JoinToken;
51
import org.netbeans.lib.lexer.token.PartToken;
52
import org.netbeans.spi.lexer.TokenPropertyProvider;
53
54
/**
55
 * Lexer input operation over multiple joined sections (embedded token lists).
56
 * <br/>
57
 * It produces regular tokens (to be added directly into ETL represented by
58
 * {@link #activeTokenList()} and also special {@link #JoinToken} instances
59
 * in case a token spans boundaries of multiple ETLs.
60
 * <br/>
61
 * It can either work over JoinTokenList directly or, during a modification,
62
 * it simulates that certain token lists are already removed/added to underlying token list.
63
 * <br/>
64
 * 
65
 * {@link #recognizedTokenLastInTokenList()} gives information whether the lastly
66
 * produced token ends right at boundary of the activeTokenList.
67
 *
68
 * @author Miloslav Metelka
69
 * @version 1.00
70
 */
71
72
public class JoinLexerInputOperation<T extends TokenId> extends LexerInputOperation<T> {
73
    
74
    CharSequence inputSourceText;
75
76
    private TokenListText readText; // For servicing read()
77
    
78
    private TokenListText readExistingText;
79
    
80
    /**
81
     * Token list in which the last recognized token started.
82
     */
83
    private EmbeddedTokenList<T> activeTokenList;
84
    
85
    /**
86
     * Index of activeTokenList in JTL.
87
     */
88
    private int activeTokenListIndex;
89
    
90
    /**
91
     * End offset of the active token list.
92
     */
93
    private int activeTokenListEndOffset;
94
    
95
    /**
96
     * Real token's start offset used to derive the token's offset in ETL.
97
     * Since tokenStartOffset is affected by TokenListList.readOffsetShift
98
     * it cannot be used for this purpose.
99
     */
100
    private int realTokenStartOffset;
101
    
102
    private boolean recognizedTokenJoined; // Whether recognized token will consist of parts
103
    
104
105
    public JoinLexerInputOperation(JoinTokenList<T> joinTokenList, int relexJoinIndex, Object lexerRestartState,
106
            int activeTokenListIndex, int relexOffset
107
    ) {
108
        super(joinTokenList, relexJoinIndex, lexerRestartState);
109
        this.inputSourceText = joinTokenList.inputSourceText();
110
        this.activeTokenListIndex = activeTokenListIndex;
111
        tokenStartOffset = relexOffset;
112
        readOffset = relexOffset;
113
        // Assign realTokenStartOffset after fetchActiveTokenList() since it would overwrite it
114
        realTokenStartOffset = relexOffset;
115
    }
116
117
    public final void init() {
118
        // Following code uses tokenList() method overriden in MutableJoinLexerInputOperation
119
        // so the following code would fail when placed in constructor since the constructor of MJLIO would not yet run.
120
        fetchActiveTokenList();
121
        readText = new TokenListText();
122
        readText.init();
123
    }
124
125
    /**
126
     * Get active ETL into which the last produced token should be added.
127
     * For join tokens there is an ETL into which a last part of JT should be added.
128
     */
129
    public EmbeddedTokenList<T> activeTokenList() {
130
        return activeTokenList;
131
    }
132
    
133
    /**
134
     * Get index of active ETL into which the last produced token should be added.
135
     * For join tokens there is an index of the last ETL into which a last part of JT should be added.
136
     */
137
    public int activeTokenListIndex() {
138
        return activeTokenListIndex;
139
    }
140
    
141
    /**
142
     * True if the last returned token is last in {@link #activeTokenList()}.
143
     * For join tokens this applies to the last part of join token.
144
     */
145
    public boolean recognizedTokenLastInTokenList() {
146
        // realTokenStartOffset is set to the end of last recognized token
147
        return (realTokenStartOffset == activeTokenListEndOffset);
148
    }
149
150
    @Override
151
    public int lastTokenEndOffset() {
152
        return realTokenStartOffset;
153
    }
154
155
    public int read(int offset) { // index >= 0 is guaranteed by contract
156
        return readText.read(offset);
157
    }
158
159
    public char readExisting(int offset) {
160
        if (readText.isInBounds(offset)) {
161
            return readText.inBoundsChar(offset);
162
        }
163
        if (readExistingText == null) {
164
            readExistingText = new TokenListText();
165
            readExistingText.initFrom(readText);
166
        }
167
        return readExistingText.existingChar(offset);
168
    }
169
170
    @Override
171
    public void assignTokenLength(int tokenLength) {
172
        super.assignTokenLength(tokenLength);
173
        // Check whether activeTokenList needs to be changed due to various flags
174
        if (recognizedTokenLastInTokenList()) { // Advance to next token list
175
            // Since this is done when recognizing a next token it should be ok when recognizing
176
            // last token in the last ETL (it should not go beyond last ETL).
177
            do {
178
                activeTokenListIndex++;
179
                fetchActiveTokenList();
180
            } while (realTokenStartOffset == activeTokenListEndOffset); // Skip empty ETLs
181
        }
182
        // Advance to end of currently recognized token
183
        realTokenStartOffset += tokenLength;
184
        // Joined token past ETL's boundary
185
        recognizedTokenJoined = (realTokenStartOffset > activeTokenListEndOffset);
186
    }
187
    
188
    private void fetchActiveTokenList() {
189
        activeTokenList = tokenList(activeTokenListIndex);
190
        realTokenStartOffset = activeTokenList.startOffset();
191
        activeTokenListEndOffset = activeTokenList.endOffset();
192
    }
193
    
194
    public EmbeddedTokenList<T> tokenList(int tokenListIndex) { // Also used by JoinTokenListChange
195
        return ((JoinTokenList<T>) tokenList).tokenList(tokenListIndex);
196
    }
197
198
    protected int tokenListCount() {
199
        return ((JoinTokenList<T>) tokenList).tokenListCount();
200
    }
201
202
    protected void fillTokenData(AbstractToken<T> token) {
203
        if (!recognizedTokenJoined) {
204
            token.setTokenList(activeTokenList);
205
            // Subtract tokenLength since this is already advanced to end of token
206
            token.setRawOffset(realTokenStartOffset - tokenLength);
207
        }
208
    }
209
    
210
    @Override
211
    protected boolean isFlyTokenAllowed() {
212
        return super.isFlyTokenAllowed() && !recognizedTokenJoined;
213
    }
214
    
215
    @Override
216
    protected AbstractToken<T> createDefaultTokenInstance(T id) {
217
        if (recognizedTokenJoined) {
218
            return createJoinToken(id, null, PartType.COMPLETE);
219
        } else { // Regular case
220
            return super.createDefaultTokenInstance(id);
221
        }
222
    }
223
224
    @Override
225
    protected AbstractToken<T> createPropertyTokenInstance(T id,
226
    TokenPropertyProvider<T> propertyProvider, PartType partType) {
227
        if (recognizedTokenJoined) {
228
            return createJoinToken(id, null, partType);
229
        } else { // Regular case
230
            return super.createPropertyTokenInstance(id, propertyProvider, partType);
231
        }
232
    }
233
    
234
    private AbstractToken<T> createJoinToken(T id,
235
    TokenPropertyProvider<T> propertyProvider, PartType partType) {
236
        // Create join token
237
        // realTokenStartOffset is already advanced by tokenLength so first decrease it
238
        realTokenStartOffset -= tokenLength;
239
        JoinToken<T> joinToken = new JoinToken<T>(id, tokenLength, propertyProvider, partType);
240
        int joinPartCountEstimate = readText.tokenListIndex - activeTokenListIndex + 1;
241
        @SuppressWarnings("unchecked")
242
        PartToken<T>[] parts = new PartToken[joinPartCountEstimate];
243
        int partLength = activeTokenListEndOffset - realTokenStartOffset;
244
        PartToken<T> partToken = new PartToken<T>(id, partLength, propertyProvider, PartType.START, joinToken, 0, 0);
245
        partToken.setTokenList(activeTokenList);
246
        partToken.setRawOffset(realTokenStartOffset); // realTokenStartOffset already decreased by tokenLength
247
        parts[0] = partToken;
248
        int partIndex = 1;
249
        int partTextOffset = partLength; // Length of created parts so far
250
        int firstPartTokenListIndex = activeTokenListIndex;
251
        do {
252
            activeTokenListIndex++;
253
            fetchActiveTokenList();
254
            // realTokenStartOffset set to start activeTokenList
255
            PartType partPartType;
256
            // Attempt total ETL's length as partLength
257
            partLength = activeTokenListEndOffset - realTokenStartOffset;
258
            if (partLength == 0) {
259
                continue;
260
            }
261
            if (partTextOffset + partLength >= tokenLength) { // Last part
262
                partLength = tokenLength - partTextOffset;
263
                // If the partType of the join token is not complete then this will be PartType.MIDDLE
264
                partPartType = (partType == PartType.START) ? PartType.MIDDLE : PartType.END;
265
            } else { // Non-last part
266
                partPartType = PartType.MIDDLE;
267
            }
268
269
            partToken = new PartToken<T>(id, partLength, propertyProvider, partPartType, joinToken, partIndex, partTextOffset);
270
            // realTokenStartOffset still points to start of activeTokenList
271
            partToken.setRawOffset(realTokenStartOffset); // ETL.startOffset() will be subtracted upon addition to ETL
272
            partToken.setTokenList(activeTokenList);
273
            partTextOffset += partLength;
274
            parts[partIndex++] = partToken;
275
        } while (partTextOffset < tokenLength);
276
        // Update realTokenStartOffset which pointed to start of activeTokenList
277
        realTokenStartOffset += partLength;
278
        // Check that the array does not have any extra items
279
        if (partIndex < parts.length) {
280
            @SuppressWarnings("unchecked")
281
            PartToken<T>[] tmp = new PartToken[partIndex];
282
            System.arraycopy(parts, 0, tmp, 0, partIndex);
283
            parts = tmp;
284
        }
285
        List<PartToken<T>> partList = ArrayUtilities.unmodifiableList(parts);
286
        joinToken.setJoinedParts(partList, activeTokenListIndex - firstPartTokenListIndex);
287
        // joinToken.setTokenList() makes no sense - JoinTokenList instances are temporary
288
        // joinToken.setRawOffset() makes no sense - offset taken from initial part
289
        return joinToken;
290
    }
291
    
292
    /**
293
     * Class for reading of text of subsequent ETLs - it allows to see their text
294
     * as a consecutive character sequence (inputSourceText is used as a backing char sequence)
295
     * with an increasing readIndex (it's not decremented after token's recognition).
296
     */
297
    final class TokenListText {
298
299
        int tokenListIndex;
300
301
        int tokenListStartOffset;
302
303
        int tokenListEndOffset;
304
305
        /**
306
         * A constant added to readOffset to allow a smoothly increasing reading offset
307
         * when reading through multiple ETLs with gaps among them.
308
         */
309
        int readOffsetShift;
310
311
        void init() {
312
            EmbeddedTokenList<T> etl = tokenList(activeTokenListIndex);
313
            tokenListStartOffset = etl.startOffset();
314
            tokenListEndOffset = etl.endOffset();
315
            // No extra shift for first token
316
        }
317
318
        void initFrom(TokenListText text) {
319
            this.tokenListIndex = text.tokenListIndex;
320
            this.tokenListStartOffset = text.tokenListStartOffset;
321
            this.tokenListEndOffset = text.tokenListEndOffset;
322
            this.readOffsetShift = text.readOffsetShift;
323
        }
324
325
        /**
326
         * Read next char or return EOF.
327
         */
328
        int read(int offset) {
329
            offset += readOffsetShift;
330
            if (offset < tokenListEndOffset) {
331
                return inputSourceText.charAt(offset);
332
            } else {
333
                while (++tokenListIndex < tokenListCount()) {
334
                    EmbeddedTokenList etl = tokenList(tokenListIndex);
335
                    tokenListStartOffset = etl.startOffset();
336
                    // Increase offset shift by the size of gap between ETLs
337
                    readOffsetShift += tokenListStartOffset - tokenListEndOffset;
338
                    // Also shift given offset value
339
                    offset += tokenListStartOffset - tokenListEndOffset;
340
                    tokenListEndOffset = etl.endOffset();
341
                    if (readOffset < tokenListEndOffset) { // ETL might be empty
342
                        return inputSourceText.charAt(offset);
343
                    }
344
                }
345
                tokenListIndex--; // Return to (tokenListCount() - 1)
346
                return LexerInput.EOF;
347
            }
348
        }
349
350
        /**
351
         * Check whether currently set text covers the given relative index.
352
         * 
353
         * @param index index in the same metrics as readIndex.
354
         * @return whether the given index is within current bounds.
355
         */
356
        boolean isInBounds(int offset) {
357
            offset += readOffsetShift;
358
            return offset >= tokenListStartOffset && offset < tokenListEndOffset;
359
        }
360
        
361
        /**
362
         * Get char that was previously verified to be within bounds.
363
         */
364
        char inBoundsChar(int offset) {
365
            offset += readOffsetShift;
366
            return inputSourceText.charAt(offset);
367
        }
368
        
369
        char existingChar(int offset) {
370
            offset += readOffsetShift;
371
            if (offset < tokenListStartOffset) {
372
                while (true) { // Char should exist
373
                    tokenListIndex--;
374
                    EmbeddedTokenList etl = tokenList(tokenListIndex);
375
                    tokenListEndOffset = etl.endOffset();
376
                    // Decrease offset shift by the size of gap between ETLs
377
                    readOffsetShift -= tokenListStartOffset - tokenListEndOffset;
378
                    // Also shift given offset value
379
                    offset -= tokenListStartOffset - tokenListEndOffset;
380
                    tokenListStartOffset = etl.startOffset();
381
                    if (readOffset >= tokenListStartOffset) { // ETL might be empty
382
                        return inputSourceText.charAt(offset);
383
                    }
384
                }
385
                
386
            } else if (offset >= tokenListEndOffset) {
387
                while (true) { // Char should exist
388
                    tokenListIndex++;
389
                    EmbeddedTokenList etl = tokenList(tokenListIndex);
390
                    tokenListStartOffset = etl.startOffset();
391
                    // Increase offset shift by the size of gap between ETLs
392
                    readOffsetShift += tokenListStartOffset - tokenListEndOffset;
393
                    // Also shift given offset value
394
                    offset += tokenListStartOffset - tokenListEndOffset;
395
                    tokenListEndOffset = etl.endOffset();
396
                    if (readOffset < tokenListEndOffset) { // ETL might be empty
397
                        return inputSourceText.charAt(offset);
398
                    }
399
                }
400
                
401
            }
402
            // Index within current bounds
403
            return inputSourceText.charAt(offset);
404
        }
405
        
406
    }
407
    
408
    @Override
409
    public String toString() {
410
        return super.toString() + ", realTokenStartOffset=" + realTokenStartOffset + // NOI18N
411
                ", activeTokenListIndex=" + activeTokenListIndex + // NOI18N
412
                ", activeTokenListEndOffset=" + activeTokenListEndOffset; // NOI18N
413
    }
414
415
}
(-)06a7890f802e (+661 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer;
43
44
import java.util.List;
45
import java.util.Set;
46
import org.netbeans.api.lexer.LanguagePath;
47
import org.netbeans.api.lexer.InputAttributes;
48
import org.netbeans.api.lexer.TokenId;
49
import org.netbeans.lib.editor.util.ArrayUtilities;
50
import org.netbeans.lib.lexer.token.AbstractToken;
51
import org.netbeans.lib.lexer.token.JoinToken;
52
import org.netbeans.lib.lexer.token.PartToken;
53
54
55
/**
56
 * Join token list over certain range of ETLs of a TokenListList.
57
 * <br/>
58
 * There must always be at least one ETL in a JTL since otherwise there would be nobody
59
 * holding EmbeddedJoinInfo that holds JoinTokenListBase which is crucial for JTL.
60
 * <br/>
61
 * It does not have any physical storage for tokens. Regular tokens
62
 * are stored in individual ETLs. Tokens split across multiple ETLs
63
 * are represented as PartToken in each ETL referencing a JoinToken.
64
 * The only "countable" part is a last part of a JoinToken.
65
 * <br/>
66
 * Lookaheads and states are assigned to a last part of the JoinToken
67
 * and it's stored normally in ETL like for regular tokens.
68
 * 
69
 * @author Miloslav Metelka
70
 */
71
72
public class JoinTokenList<T extends TokenId> implements TokenList<T> {
73
    
74
    /**
75
     * Create join token list over an uninitialized set of embedded token lists
76
     * and this method will perform initial lexing of the contained embedded token lists.
77
     * 
78
     * @param tokenListList non-null tokenListList
79
     * @param tokenListStartIndex index of first ETL contained in the desired JTL.
80
     * @param tokenListCount total number of ETLs contained in the created JTL.
81
     * @return non-null JTL.
82
     */
83
    public static <T extends TokenId> JoinTokenList<T> init(
84
            TokenListList<T> tokenListList, int tokenListStartIndex, int tokenListCount
85
    ) {
86
        assert (tokenListCount > 0) : "tokenListCount must be >0";
87
        JoinTokenListBase base = new JoinTokenListBase(tokenListCount, tokenListList.rootTokenList().modCount());
88
        // Create join token list - just init first ETL's join info (read by JTL's constructor)
89
        JoinTokenList<T> jtl = new JoinTokenList<T>(tokenListList, base, tokenListStartIndex);
90
        // Notify will initialize all ETL.joinInfo except the first one (already inited)
91
        JoinLexerInputOperation<T> lexerInputOperation = new JoinLexerInputOperation<T>(jtl, 0, null, 0,
92
                tokenListList.get(tokenListStartIndex).startOffset());
93
        lexerInputOperation.init();
94
        AbstractToken<T> token;
95
        int joinTokenCount = 0;
96
        int activeTokenListIndex = -1;
97
        EmbeddedTokenList<T> activeTokenList = null;
98
        while ((token = lexerInputOperation.nextToken()) != null) {
99
            if (activeTokenList != lexerInputOperation.activeTokenList()) {
100
                do {
101
                    // There may be empty ETLs and ETLs that will contain part tokens
102
                    activeTokenListIndex++;
103
                    activeTokenList = jtl.tokenList(activeTokenListIndex);
104
                    activeTokenList.joinInfo = new EmbeddedJoinInfo(base, joinTokenCount, activeTokenListIndex);
105
                } while (activeTokenListIndex < lexerInputOperation.activeTokenListIndex());
106
            }
107
            if (token.getClass() == JoinToken.class) {
108
                // ETL for last part
109
                JoinToken<T> joinToken = (JoinToken<T>) token;
110
                List<PartToken<T>> joinedParts = joinToken.joinedParts();
111
                // Index for active list of addition
112
                // There may be ETLs so token list count may differ from part count
113
                int extraTokenListSpanCount = joinToken.extraTokenListSpanCount();
114
                int tokenListIndex = activeTokenListIndex - extraTokenListSpanCount;
115
                int joinedPartIndex = 0;
116
                // Only add without the last part (will be added normally outside the loop)
117
                // The last ETL can not be empty (must contain the last non-empty token part)
118
                for (int i = 0; i < extraTokenListSpanCount; i++) {
119
                    EmbeddedTokenList<T> etl = tokenListList.get(tokenListStartIndex + tokenListIndex + i);
120
                    // Check whether token list is non-empty by checking a text length that it covers.
121
                    // Do not use elt.tokenCount() since the tokens are just being added into ETL.
122
                    if (etl.textLength() > 0) {
123
                        etl.addToken(joinedParts.get(joinedPartIndex++), 0, null);
124
                    }
125
                    etl.joinInfo.setJoinTokenLastPartShift(extraTokenListSpanCount - i);
126
                    // Increase total number of tokens - use just the first part's ETL since others are ignored.
127
                    // There's a single token part in ETL only
128
                }
129
                // Last part will be added normally by subsequent code
130
                token = joinedParts.get(joinedPartIndex); // Should be (joinedParts.size()-1)
131
            }
132
            activeTokenList.addToken(token, lexerInputOperation);
133
            joinTokenCount++;
134
        }
135
        // Init possible empty ETL at the end
136
        while (activeTokenListIndex < lexerInputOperation.activeTokenListIndex()) {
137
            EmbeddedTokenList<T> etl = jtl.tokenList(activeTokenListIndex);
138
            assert (etl.joinInfo == null);
139
            etl.joinInfo = new EmbeddedJoinInfo(base, joinTokenCount, activeTokenListIndex);
140
            activeTokenListIndex++;
141
        }
142
        // Trim storage of all ETLs to their current size
143
        for (int i = tokenListCount - 1; i >= 0; i--) {
144
            tokenListList.get(tokenListStartIndex + i).trimStorageToSize();
145
        }
146
        base.joinTokenCount = joinTokenCount;
147
        // Could possibly subtract gap lengths but should not be necessary
148
        return jtl;
149
    }
150
    
151
    /** Backing token list list that holds ETLs. */
152
    protected final TokenListList<T> tokenListList; // 16 bytes
153
    
154
    /** Info about token list count and join token index gap. */
155
    protected final JoinTokenListBase base; // 12 bytes (8-super + 4)
156
    
157
    /** Start index of ETLs in TLL used by this JTL. */
158
    protected final int tokenListStartIndex; // 20 bytes
159
    
160
    /** Index of active token list. */
161
    protected int activeTokenListIndex; // 24 bytes
162
163
    /** Token list currently servicing requests. */
164
    protected  EmbeddedTokenList<T> activeTokenList; // 28 bytes
165
    
166
    /** Start join index of activeTokenList */
167
    protected int activeStartJoinIndex; // 32 bytes
168
    
169
    /** End join index of activeTokenList */
170
    protected int activeEndJoinIndex; // 36 bytes
171
172
    public JoinTokenList(TokenListList<T> tokenListList, JoinTokenListBase base, int tokenListStartIndex) {
173
        this.tokenListList = tokenListList;
174
        this.base = base;
175
        this.tokenListStartIndex = tokenListStartIndex;
176
        this.activeTokenListIndex = -1; // Signal invalid value
177
    }
178
179
    public LanguagePath languagePath() {
180
        return tokenListList.languagePath();
181
    }
182
183
    public TokenListList<T> tokenListList() {
184
        return tokenListList;
185
    }
186
187
    public JoinTokenListBase base() {
188
        return base;
189
    }
190
191
    public int tokenListStartIndex() {
192
        return tokenListStartIndex;
193
    }
194
195
    /**
196
     * Get token list contained in this join token list.
197
     * 
198
     * @param index >=0 index of the token list in this joined token list.
199
     * @return non-null embedded token list at the given index.
200
     */
201
    public EmbeddedTokenList<T> tokenList(int index) {
202
        if (index < 0)
203
            throw new IndexOutOfBoundsException("index=" + index + " < 0"); // NOI18N
204
        if (index >= base.tokenListCount)
205
            throw new IndexOutOfBoundsException("index=" + index + " >= size()=" + base.tokenListCount); // NOI18N
206
        return tokenListList.get(tokenListStartIndex + index);
207
    }
208
    
209
    public int tokenListCount() {
210
        return base.tokenListCount;
211
    }
212
    
213
    
214
    public int tokenCountCurrent() {
215
        return base.joinTokenCount;
216
    }
217
218
    public int tokenCount() {
219
        return tokenCountCurrent();
220
    }
221
    
222
    public int activeStartJoinIndex() { // Use by TS.embeddedImpl()
223
        return activeStartJoinIndex;
224
    }
225
226
    public int activeEndJoinIndex() { // Use by TokenListUpdater
227
        return activeEndJoinIndex;
228
    }
229
230
    public int activeTokenListIndex() {
231
        return activeTokenListIndex;
232
    }
233
234
    public void setActiveTokenListIndex(int activeTokenListIndex) { // Used by ETL.joinTokenList()
235
        if (this.activeTokenListIndex != activeTokenListIndex) {
236
            this.activeTokenListIndex = activeTokenListIndex;
237
            fetchActiveTokenListData();
238
        }
239
    }
240
241
    public EmbeddedTokenList<T> activeTokenList() {
242
        return activeTokenList;
243
    }
244
245
    public TokenOrEmbedding<T> tokenOrEmbedding(int index) {
246
        locateTokenListByIndex(index);
247
        TokenOrEmbedding<T> tokenOrEmbedding = activeTokenList.tokenOrEmbedding(index - activeStartJoinIndex);
248
        // Need to return complete token in case a token part was retrieved
249
        AbstractToken<T> token;
250
        if (index == activeStartJoinIndex && // token part can only be the first in ETL
251
            tokenOrEmbedding != null && // could be beyond end?
252
            (token = tokenOrEmbedding.token()).getClass() == PartToken.class
253
        ) {
254
            tokenOrEmbedding = ((PartToken<T>)token).joinTokenOrEmbedding();
255
        }
256
        return tokenOrEmbedding;
257
    }
258
259
    public int tokenOffset(AbstractToken<T> token) {
260
        // Should never be called for any token instances
261
        throw new IllegalStateException("Internal error - should never be called");
262
    }
263
264
    public int tokenOffsetByIndex(int index) {
265
        locateTokenListByIndex(index);
266
        // Need to treat specially token parts - return offset of complete token
267
        AbstractToken<T> token;
268
        if (index == activeStartJoinIndex && // token part can only be the first in ETL
269
            (token = activeTokenList.tokenOrEmbedding(index - activeStartJoinIndex).token()).getClass() == PartToken.class
270
        ) {
271
            return ((PartToken<T>)token).joinToken().offset(null);
272
        }
273
        return activeTokenList.tokenOffsetByIndex(index - activeStartJoinIndex);
274
    }
275
276
    public int tokenListIndex(int offset, int startIndex, int endIndex) {
277
        // First find the right ETL for the given offset and store it in activeTokenListIndex
278
        // Use binary search
279
        int low = startIndex;
280
        int high = endIndex - 1;
281
        while (low <= high) {
282
            int mid = (low + high) >>> 1;
283
            int midStartOffset = tokenList(mid).startOffset();
284
            
285
            if (midStartOffset < offset) {
286
                low = mid + 1;
287
            } else if (midStartOffset > offset) {
288
                high = mid - 1;
289
            } else {
290
                // Token starting exactly at ETL.startOffset()
291
                high = mid;
292
                break;
293
            }
294
        }
295
        // Use lower index => high
296
        return high; // May return -1
297
    }
298
299
    public int[] tokenIndex(int offset) {
300
        // Check if the current active token list covers the given offset.
301
        // If not covered then only search below/above the current active ETL.
302
        // It not only improves performance but it is NECESSARY for proper functionality
303
        // of TokenListUpdater.updateJoined() since it may skip removed ETLs
304
        // by manually using setActiveTokenListIndex() in the area below/above the removed ETLs.
305
        boolean activeStartsBelowOffset = ((offset >= activeTokenList.startOffset()) || activeTokenListIndex == 0);
306
        if (activeStartsBelowOffset) {
307
            if (offset < activeTokenList.endOffset() ||
308
                (activeTokenListIndex + 1 == tokenListCount() ||
309
                    offset < tokenList(activeTokenListIndex + 1).startOffset())
310
            ) {
311
                // Current active ETL covers the area
312
            } else if (activeTokenListIndex + 1 < tokenListCount()) { // Search above
313
                activeTokenListIndex = tokenListIndex(offset, activeTokenListIndex + 1, tokenListCount());
314
                fetchActiveTokenListData();
315
            }
316
        } else if (activeTokenListIndex > 0) { // Search below
317
            activeTokenListIndex = tokenListIndex(offset, 0, activeTokenListIndex);
318
            if (activeTokenListIndex < 0) {
319
                activeTokenListIndex = 0;
320
            }
321
            fetchActiveTokenListData();
322
        }
323
324
        // Now search within a single ETL by binary search
325
        EmbeddedJoinInfo joinInfo = activeTokenList.joinInfo;
326
        int joinTokenLastPartShift = joinInfo.joinTokenLastPartShift();
327
        int searchETLTokenCount = activeTokenList.joinTokenCount();
328
        int[] indexAndTokenOffset = LexerUtilsConstants.tokenIndexBinSearch(activeTokenList, offset, searchETLTokenCount);
329
        int etlIndex = indexAndTokenOffset[0]; // Index inside etl
330
        indexAndTokenOffset[0] += joinInfo.joinTokenIndex(); // Make the index joinIndex
331
        if (etlIndex == searchETLTokenCount && joinTokenLastPartShift > 0) { // Must move activeTokenList to last part
332
            // Get last part and find out how much forward is the last part
333
            activeTokenListIndex += joinTokenLastPartShift;
334
            fetchActiveTokenListData();
335
            PartToken<T> lastPartToken = (PartToken<T>) activeTokenList.tokenOrEmbeddingUnsync(0).token();
336
            indexAndTokenOffset[1] = lastPartToken.joinToken().offset(null);
337
            
338
        } else if (etlIndex == 0) { // Possibly last part of a join token
339
            AbstractToken<T> token = activeTokenList.tokenOrEmbedding(0).token();
340
            if (token.getClass() == PartToken.class) {
341
                // indexAndTokenOffset[0] is already ok - just fix token's offset
342
                indexAndTokenOffset[1] = ((PartToken<T>)token).joinToken().offset(null);
343
            }
344
        }
345
        return indexAndTokenOffset;
346
    }
347
348
    public AbstractToken<T> replaceFlyToken(int index, AbstractToken<T> flyToken, int offset) {
349
        locateTokenListByIndex(index);
350
        return activeTokenList.replaceFlyToken(index - activeStartJoinIndex, flyToken, offset);
351
    }
352
353
    public void wrapToken(int index, EmbeddingContainer<T> embeddingContainer) {
354
        locateTokenListByIndex(index);
355
        // !!! TBD - must not wrap complete tokens of join token list.
356
        // Instead wrap all part tokens with another join token list
357
        activeTokenList.wrapToken(index - activeStartJoinIndex, embeddingContainer);
358
    }
359
360
    public final int modCount() {
361
        return rootTokenList().modCount();
362
    }
363
    
364
    public InputAttributes inputAttributes() {
365
        return rootTokenList().inputAttributes();
366
    }
367
    
368
    public int lookahead(int index) {
369
        // Locate embedded token list for the last token part (only that one stores the LA)
370
        locateTokenListByIndex(index);
371
        return activeTokenList.lookahead(index - activeStartJoinIndex);
372
    }
373
374
    public Object state(int index) {
375
        // Locate embedded token list for the last token part (only that one stores the state)
376
        locateTokenListByIndex(index);
377
        return activeTokenList.state(index - activeStartJoinIndex);
378
    }
379
380
    public final TokenList<?> rootTokenList() {
381
        return tokenListList.rootTokenList();
382
    }
383
384
    public CharSequence inputSourceText() {
385
        return rootTokenList().inputSourceText();
386
    }
387
388
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
389
        return rootTokenList().tokenHierarchyOperation();
390
    }
391
    
392
    public boolean isContinuous() {
393
        return false; // TBD can be partially continuous - could be improved
394
    }
395
396
    public Set<T> skipTokenIds() {
397
        return null; // Not a top-level list -> no skip token ids
398
    }
399
400
    public int startOffset() {
401
        return tokenList(0).startOffset();
402
    }
403
404
    public int endOffset() {
405
        return tokenList(base.tokenListCount - 1).endOffset();
406
    }
407
    
408
    public void updateStatus() {
409
        synchronized (rootTokenList()) {
410
            updateStatusUnsync();
411
        }
412
    }
413
414
    /**
415
     * Unsynced synchronization of join token list - this method should only be used
416
     * when there may be only a single thread accessing token hierarchy i.e. during
417
     * token hierarchy modifications upon mutable input source modifications.
418
     * 
419
     * @see #updateStatus()
420
     */
421
    public void updateStatusUnsync() {
422
        int rootModCount;
423
        if (base.lastModCount != (rootModCount = rootTokenList().modCount())) {
424
            base.lastModCount = rootModCount;
425
            // Update status of all the contained ETLs
426
            for (int i = 0; i < base.tokenListCount; i++) {
427
                tokenList(i).embeddingContainer().updateStatus();
428
            }
429
        }
430
    }
431
432
    public boolean isRemoved() {
433
        return false; // Should never be parented
434
    }
435
436
    /**
437
     * Get index of token list where a token for a particular joinInde starts
438
     * and index where it's located.
439
     *
440
     * @param index index in JTL.
441
     * @return [0] contains token-list-index and [1] index of token-start info.
442
     */
443
    public int tokenStartLocalIndex(int index) {
444
        if (index == tokenCount()) {
445
            if (activeTokenListIndex < tokenListCount() - 1) {
446
                activeTokenListIndex = tokenListCount() - 1;
447
                fetchActiveTokenListData();
448
            }
449
            return activeTokenList.tokenCountCurrent(); // Index at end (can't be join tokens)
450
        }
451
452
        locateTokenListByIndex(index);
453
        AbstractToken<T> token = activeTokenList.tokenOrEmbeddingUnsync(index - activeStartJoinIndex).token();
454
        if (token.getClass() == PartToken.class) { // Last part of join token
455
            PartToken<T> partToken = (PartToken<T>) token;
456
            activeTokenListIndex -= partToken.joinToken().extraTokenListSpanCount();
457
            fetchActiveTokenListData();
458
            // The first part of join token is last in the active ETL
459
            return activeTokenList.tokenCountCurrent() - 1;
460
        }
461
        return index - activeStartJoinIndex;
462
    }
463
464
    /**
465
     * Locate the right activeTokenList to service the requested join index.
466
     *
467
     * @param joinIndex index in a join token list.
468
     * @throws IndexOutOfBoundsException for joinIndex below zero.
469
     */
470
    protected final void locateTokenListByIndex(int joinIndex) {
471
        if (joinIndex < activeStartJoinIndex) {
472
            if (joinIndex < 0)
473
                throw new IndexOutOfBoundsException("index=" + joinIndex + " < 0");
474
            // Must be lower segment - first try the one below
475
            activeTokenListIndex--;
476
            fetchActiveTokenListData();
477
            if (joinIndex < activeStartJoinIndex) { // Still not covered
478
                // Do binary search on <0, activeTokenListIndex - 1>
479
                positionToJoinIndex(joinIndex, 0, activeTokenListIndex - 1);
480
            }
481
482
        } else if (joinIndex >= activeEndJoinIndex) {
483
            if (activeTokenListIndex + 1 < tokenListCount()) {
484
                activeTokenListIndex++;
485
                fetchActiveTokenListData();
486
                if (joinIndex >= activeEndJoinIndex) { // Still too high
487
                    // Do binary search on <activeTokenListIndex + 1, tokenListCount-1>
488
                    positionToJoinIndex(joinIndex, activeTokenListIndex + 1, base.tokenListCount - 1);
489
                }
490
            }
491
        }
492
        // The index is within bounds of activeTokenList
493
    }
494
495
    private void positionToJoinIndex(int joinIndex, int low, int high) {
496
        while (low <= high) {
497
            activeTokenListIndex = (low + high) >>> 1;
498
            fetchActiveTokenListData();
499
            if (activeStartJoinIndex < joinIndex) {
500
                low = activeTokenListIndex + 1;
501
            } else if (activeStartJoinIndex > joinIndex) {
502
                high = activeTokenListIndex - 1;
503
            } else { // first token of the active token list
504
                return;
505
            }
506
        }
507
        // low == high + 1
508
        if (activeTokenListIndex != high) {
509
            activeTokenListIndex = high;
510
            fetchActiveTokenListData();
511
        }
512
    }
513
514
    protected final void fetchActiveTokenListData() {
515
        activeTokenList = tokenList(activeTokenListIndex);
516
        activeStartJoinIndex = activeTokenList.joinInfo.joinTokenIndex();
517
        activeEndJoinIndex = activeStartJoinIndex + activeTokenList.joinTokenCount();
518
    }
519
520
    public String checkConsistency() {
521
        // Check regular consistency without checking embeddings
522
        String error = LexerUtilsConstants.checkConsistencyTokenList(this, false);
523
        if (error == null) {
524
            // Check individual ETLs and their join infos
525
            int joinTokenCount = 0;
526
            JoinToken<T> activeJoinToken = null;
527
            int joinedPartCount = 0;
528
            int nextCheckPartIndex = 0;
529
            for (int tokenListIndex = 0; tokenListIndex < tokenListCount(); tokenListIndex++) {
530
                EmbeddedTokenList<T> etl = tokenList(tokenListIndex);
531
                error = LexerUtilsConstants.checkConsistencyTokenList(etl, false);
532
                if (error != null)
533
                    return error;
534
535
                if (etl.joinInfo == null) {
536
                    return "Null joinInfo for ETL at token-list-index " + tokenListIndex; // NOI18N
537
                }
538
                if (joinTokenCount != etl.joinInfo.joinTokenIndex()) {
539
                    return "joinTokenIndex=" + joinTokenCount + " != etl.joinInfo.joinTokenIndex()=" + // NOI18N
540
                            etl.joinInfo.joinTokenIndex() + " at token-list-index " + tokenListIndex; // NOI18N
541
                }
542
                if (tokenListIndex != etl.joinInfo.tokenListIndex()) {
543
                    return "token-list-index=" + tokenListIndex + " != etl.joinInfo.tokenListIndex()=" + // NOI18N
544
                            etl.joinInfo.tokenListIndex();
545
                }
546
547
                int etlTokenCount = etl.tokenCount();
548
                int etlJoinTokenCount = etlTokenCount;
549
                if (etlTokenCount > 0) {
550
                    AbstractToken<T> token = etl.tokenOrEmbeddingUnsync(0).token();
551
                    int startCheckIndex = 0;
552
                    // Check first token (may also be the last token)
553
                    if (activeJoinToken != null) {
554
                        if (token.getClass() != PartToken.class) {
555
                            return "Unfinished joinToken at token-list-index=" + tokenListIndex; // NOI18N
556
                        }
557
                        error = checkConsistencyJoinToken(activeJoinToken, token, nextCheckPartIndex++, tokenListIndex);
558
                        if (error != null) {
559
                            return error;
560
                        }
561
                        if (nextCheckPartIndex == joinedPartCount) {
562
                            activeJoinToken = null; // activeJoinToken ended
563
                        } else { // For non-last there must be no other tokens in the list
564
                            if (etlTokenCount > 1) {
565
                                return "More than one token and non-last part of unfinished join token" +  // NOI18N
566
                                        " at token-list-index " + tokenListIndex; // NOI18N
567
                            }
568
                            // etlTokenCount so the first token is last too
569
                            // and this is an ETL with single token part that continues activeJoinToken
570
                            etlJoinTokenCount--;
571
                        }
572
                        startCheckIndex = 1;
573
                    }
574
                    // Check last token
575
                    if (etlTokenCount > startCheckIndex) {
576
                        assert (activeJoinToken == null);
577
                        token = etl.tokenOrEmbeddingUnsync(etlTokenCount - 1).token();
578
                        if (token.getClass() == PartToken.class) {
579
                            etlJoinTokenCount--;
580
                            activeJoinToken = ((PartToken<T>) token).joinToken();
581
                            joinedPartCount = activeJoinToken.joinedParts().size();
582
                            nextCheckPartIndex = 0;
583
                            if (joinedPartCount < 2) {
584
                                return "joinedPartCount=" + joinedPartCount + " < 2";
585
                            }
586
                            error = checkConsistencyJoinToken(activeJoinToken, token, nextCheckPartIndex++, tokenListIndex);
587
                            if (error != null)
588
                                return error;
589
                        }
590
                    }
591
                    // Check that no other token are part tokens than the relevant ones
592
                    for (int j = startCheckIndex; j < etlJoinTokenCount; j++) {
593
                        if (etl.tokenOrEmbeddingUnsync(j).token().getClass() == PartToken.class) {
594
                            return "Inside PartToken at index " + j + "; joinTokenCount=" + etlJoinTokenCount; // NOI18N
595
                        }
596
                    }
597
                }
598
                if (etlJoinTokenCount != etl.joinTokenCount()) {
599
                    return "joinTokenCount=" + etlJoinTokenCount + " != etl.joinTokenCount()=" + // NOI18N
600
                            etl.joinTokenCount() + " at token-list-index " + tokenListIndex; // NOI18N
601
                }
602
                joinTokenCount += etlJoinTokenCount;
603
            } // end-of-for over ETLs
604
            if (activeJoinToken != null) {
605
                return "Unfinished join token at end";
606
            }
607
            if (joinTokenCount != base.joinTokenCount) {
608
                return "joinTokenCount=" + joinTokenCount + " != base.joinTokenCount=" + base.joinTokenCount; // NOI18N
609
            }
610
        }
611
        // Check placement of index gap
612
        return error;
613
    }
614
615
    private String checkConsistencyJoinToken(JoinToken<T> joinToken, AbstractToken<T> token, int partIndex, int tokenListIndex) {
616
        PartToken<T> partToken = (PartToken<T>) token;
617
        if (joinToken.joinedParts().get(partIndex) != token) {
618
            return "activeJoinToken.joinedParts().get(" + partIndex + // NOI18N
619
                    ") != token at token-list-index" + tokenListIndex; // NOI18N
620
        }
621
        if (partToken.joinToken() != joinToken) {
622
            return "Invalid join token of part at partIndex " + partIndex + // NOI18N
623
                    " at token-list-index " + tokenListIndex; // NOI18N
624
        }
625
        EmbeddedTokenList<T> etl = tokenList(tokenListIndex);
626
        int lps = etl.joinInfo.joinTokenLastPartShift();
627
        if (lps < 0) {
628
            return "lps=" + lps + " < 0";
629
        }
630
631
        if (tokenListIndex + lps >= tokenListCount()) {
632
            return "Invalid lps=" + lps + // NOI18N
633
                    " at token-list-index " + tokenListIndex + // NOI18N
634
                    "; tokenListCount=" + tokenListCount(); // NOI18N
635
        }
636
        AbstractToken<T> lastPart = tokenList(tokenListIndex + lps).tokenOrEmbeddingUnsync(0).token();
637
        if (lastPart.getClass() != PartToken.class) {
638
            return "Invalid lps: lastPart not PartToken " + lastPart.dumpInfo(null, null, true, 0) + // NOI18N
639
                    " at token-list-index " + tokenListIndex; // NOI18N
640
        }
641
        if (((PartToken<T>)lastPart).joinToken().lastPart() != lastPart) {
642
            return "Invalid lps: Not last part " + lastPart.dumpInfo(null, null, true, 0) + // NOI18N
643
                    " at token-list-index " + tokenListIndex; // NOI18N
644
        }
645
        return null;
646
    }
647
648
    @Override
649
    public String toString() {
650
        StringBuilder sb = new StringBuilder(256);
651
        int tokenListCount = tokenListCount();
652
        int digitCount = String.valueOf(tokenListCount - 1).length();
653
        for (int i = 0; i < tokenListCount; i++) {
654
            ArrayUtilities.appendBracketedIndex(sb, i, digitCount);
655
            tokenList(i).dumpInfo(sb); // includes '\n'
656
        }
657
        return LexerUtilsConstants.appendTokenList(sb, this).toString();
658
    }
659
660
}
661
(-)06a7890f802e (+174 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer;
43
44
/**
45
 * Structure holding a token list count that bases a JoinTokenList.
46
 * <br/>
47
 * There is also an information regarding a join token index gap inside the series
48
 * of contained embedded token lists.
49
 * <br/>
50
 * Join token list can be created as a wrapper around this class with a knowledge
51
 * of token list list and an token list index at which the join token list would start.
52
 * 
53
 * @author Miloslav Metelka
54
 */
55
56
public final class JoinTokenListBase {
57
    
58
    private static final int INDEX_GAP_LENGTH_INITIAL_SIZE = (Integer.MAX_VALUE >> 1);
59
    
60
    /**
61
     * Number of embedded token lists contained in join token list.
62
     */
63
    int tokenListCount; // 12 bytes (8-super + 4)
64
    
65
    /**
66
     * Total count of tokens contained in JoinTokenList.
67
     */
68
    int joinTokenCount; // 16 bytes
69
    
70
    /**
71
     * Last modCount for which the JoinTokenList was updated.
72
     * <br/>
73
     * JoinTokenList.updateStatus() may be used to update.
74
     */
75
    int lastModCount; // 20 bytes
76
    
77
    /**
78
     * Index among contained embedded token lists where both index-related gaps are located.
79
     */
80
    int indexGapsIndex; // 24 bytes
81
    
82
    /**
83
     * Length of an index gap for computation of indexes in a JoinTokenList
84
     * based on ETLs.
85
     * <br/>
86
     * The above gap checking is done by checking whether the index is above gap length
87
     * since the initial gap length is so high that the indexes should never reach
88
     * its size (even decreased by added items).
89
     */
90
    int joinTokenIndexGapLength = INDEX_GAP_LENGTH_INITIAL_SIZE; // 28 bytes
91
    
92
    /**
93
     * Length of an index gap for computation of index of ETL in a JoinTokenList
94
     * which is useful for finding of a start-token-list-index of the join token list.
95
     * <br/>
96
     * The above gap checking is done by checking whether the index is above gap length
97
     * since the initial gap length is so high that the indexes should never reach
98
     * its size (even decreased by added items).
99
     */
100
    int tokenListIndexGapLength = INDEX_GAP_LENGTH_INITIAL_SIZE; // 32 bytes
101
    
102
    JoinTokenListBase(int tokenListCount, int lastModCount) {
103
        this.tokenListCount = tokenListCount;
104
        this.lastModCount = lastModCount;
105
        // Move index gap to be at the end of all contained token lists
106
        this.indexGapsIndex = tokenListCount;
107
    }
108
    
109
    int joinTokenIndex(int rawJoinTokenIndex) {
110
        return (rawJoinTokenIndex < joinTokenIndexGapLength)
111
                ? rawJoinTokenIndex
112
                : rawJoinTokenIndex - joinTokenIndexGapLength;
113
    }
114
    
115
    int tokenListIndex(int rawTokenListIndex) {
116
        return (rawTokenListIndex < tokenListIndexGapLength)
117
                ? rawTokenListIndex
118
                : rawTokenListIndex - tokenListIndexGapLength;
119
    }
120
    
121
    /**
122
     * Move both gaps in sync so that ETL.JoinInfo in an ETL at "index" is above both gaps.
123
     * 
124
     * @param tokenListList non-null TLL.
125
     * @param tokenListStartIndex points to first list belonging to a JTL.
126
     * @param index index to which the gaps should be moved.
127
     */
128
    
129
    public void moveIndexGap(TokenListList<?> tokenListList, int tokenListStartIndex, int index) {
130
        if (index < indexGapsIndex) {
131
            // Items above index should be moved to be above gap
132
            do {
133
                EmbeddedJoinInfo joinInfo = tokenListList.get(tokenListStartIndex + index).joinInfo;
134
                joinInfo.rawTokenListIndex += tokenListIndexGapLength;
135
                joinInfo.rawJoinTokenIndex += joinTokenIndexGapLength;
136
            } while (++index < indexGapsIndex);
137
        } else if (index > indexGapsIndex) {
138
            // Items above index should be moved to be above gap
139
            do {
140
                index--;
141
                EmbeddedJoinInfo joinInfo = tokenListList.get(tokenListStartIndex + index).joinInfo;
142
                joinInfo.rawTokenListIndex += tokenListIndexGapLength;
143
                joinInfo.rawJoinTokenIndex += joinTokenIndexGapLength;
144
            } while (index > indexGapsIndex);
145
        }
146
    }
147
148
    public void tokenListModNotify(int tokenListCountDiff) {
149
        // Gap assumed to be above last added token list or above
150
        indexGapsIndex += tokenListCountDiff; // Move gap above added 
151
        tokenListCount += tokenListCountDiff;
152
        tokenListIndexGapLength -= tokenListCountDiff;
153
    }
154
155
    public int joinTokenCount() {
156
        return joinTokenCount;
157
    }
158
159
    public void updateJoinTokenCount(int joinTokenCountDiff) {
160
        joinTokenCount += joinTokenCountDiff;
161
        joinTokenIndexGapLength -= joinTokenCountDiff;
162
    }
163
164
    @Override
165
    public String toString() {
166
        StringBuilder sb = new StringBuilder(70);
167
        sb.append("tokenListCount=").append(tokenListCount);
168
        sb.append(", tokenCount=").append(joinTokenCount);
169
        sb.append(", lastModCount=").append(lastModCount);
170
        sb.append(", indexGapsIndex=").append(indexGapsIndex);
171
        return sb.toString();
172
    }
173
174
}
(-)a/lexer/src/org/netbeans/lib/lexer/LAState.java (-9 / +13 lines)
Lines 52-70 Link Here
52
52
53
    private static final LAState EMPTY = new NoState(0);
53
    private static final LAState EMPTY = new NoState(0);
54
    
54
    
55
    private static final LAState INIT_STATE = new NoState(0);
56
57
    public static LAState empty() {
55
    public static LAState empty() {
58
        return EMPTY;
56
        return EMPTY;
59
    }
57
    }
60
    
58
    
61
    /**
62
     * Special state for marking that an embedded token list was not inited yet.
63
     */
64
    public static LAState initState() {
65
        return INIT_STATE;
66
    }
67
68
    static int withExtraCapacity(int capacity) {
59
    static int withExtraCapacity(int capacity) {
69
        return capacity * 3 / 2 + 4;
60
        return capacity * 3 / 2 + 4;
70
    }
61
    }
Lines 105-110 Link Here
105
        return capacity() - gapLength;
96
        return capacity() - gapLength;
106
    }
97
    }
107
98
99
    /**
100
     * Add a particular lookahead and state.
101
     *
102
     * @param lookahead
103
     * @param state
104
     * @return either same or a new LAState containing the given lookahead and state.
105
     */
108
    public final LAState add(int lookahead, Object state) {
106
    public final LAState add(int lookahead, Object state) {
109
        LAState ret;
107
        LAState ret;
110
        if (gapLength > 0) { // enough space
108
        if (gapLength > 0) { // enough space
Lines 154-159 Link Here
154
152
155
    protected abstract LAState upgrade(int capacity, Class laStateClass);
153
    protected abstract LAState upgrade(int capacity, Class laStateClass);
156
154
155
    /**
156
     * Whether an upgrade is necessary when the given laStateClass needs to be used.
157
     *
158
     * @param laStateClass non-null requested laStateClass
159
     * @return true if upgrade is necessary.
160
     */
157
    protected abstract boolean isUpgrade(Class laStateClass);
161
    protected abstract boolean isUpgrade(Class laStateClass);
158
162
159
    protected abstract Class addToGapStart(int lookahead, Object state);
163
    protected abstract Class addToGapStart(int lookahead, Object state);
(-)a/lexer/src/org/netbeans/lib/lexer/LanguageOperation.java (-37 / +41 lines)
Lines 109-116 Link Here
109
        if (!existingLanguagePaths.contains(lp)) {
109
        if (!existingLanguagePaths.contains(lp)) {
110
            newLanguagePaths.add(lp);
110
            newLanguagePaths.add(lp);
111
        }
111
        }
112
        @SuppressWarnings("unchecked")
112
        Language<T> language = (Language<T>)LexerUtilsConstants.<T>innerLanguage(lp);
113
        Language<T> language = (Language<T>)lp.innerLanguage();
114
        if (!exploredLanguages.contains(language)) {
113
        if (!exploredLanguages.contains(language)) {
115
            exploredLanguages.add(language);
114
            exploredLanguages.add(language);
116
            Set<T> ids = language.tokenIds();
115
            Set<T> ids = language.tokenIds();
Lines 162-167 Link Here
162
        WeakListeners.create(PropertyChangeListener.class, this, LanguageManager.getInstance()));
161
        WeakListeners.create(PropertyChangeListener.class, this, LanguageManager.getInstance()));
163
    }
162
    }
164
    
163
    
164
    public Language<T> language() {
165
        return language;
166
    }
167
    
165
    public synchronized TokenValidator<T> tokenValidator(T id) {
168
    public synchronized TokenValidator<T> tokenValidator(T id) {
166
        if (tokenValidators == null) {
169
        if (tokenValidators == null) {
167
            tokenValidators = allocateTokenValidatorArray(language.maxOrdinal() + 1);
170
            tokenValidators = allocateTokenValidatorArray(language.maxOrdinal() + 1);
Lines 188-218 Link Here
188
        }
191
        }
189
        FlyItem<T> item = flyItems[id.ordinal()];
192
        FlyItem<T> item = flyItems[id.ordinal()];
190
        if (item == null) {
193
        if (item == null) {
191
            token = new TextToken<T>(id, text); // create flyweight token
194
            item = new FlyItem<T>(id, text);
192
            token.makeFlyweight();
195
            flyItems[id.ordinal()] = item;
193
            flyItems[id.ordinal()] = new FlyItem<T>(token);
194
        } else { // already a valid item
195
            token = item.token();
196
            if (token.text() != text) {
197
                token = item.token2();
198
                if (token == null || token.text() != text) {
199
                    token = item.token();
200
                    if (!CharSequenceUtilities.textEquals(token.text(), text)) {
201
                        token = item.token2();
202
                        if (token == null || !CharSequenceUtilities.textEquals(token.text(), text)) {
203
                            // Create new token
204
                            token = new TextToken<T>(id, text);
205
                            token.makeFlyweight();
206
                        }
207
                        item.pushToken(token);
208
                    }
209
                } else { // found token2
210
                    item.pushToken(token);
211
                }
212
            }
213
        }
196
        }
214
        assert (token != null); // Should return non-null token
197
        return item.flyToken(id, text);
215
        return token;
216
    }
198
    }
217
    
199
    
218
    public synchronized EmbeddingPresence embeddingPresence(T id) {
200
    public synchronized EmbeddingPresence embeddingPresence(T id) {
Lines 322-348 Link Here
322
        return (TokenValidator<T>[]) new TokenValidator[length];
304
        return (TokenValidator<T>[]) new TokenValidator[length];
323
    }
305
    }
324
306
307
    
325
    private static final class FlyItem<T extends TokenId> {
308
    private static final class FlyItem<T extends TokenId> {
326
        
309
        
327
        private TextToken<T> token;
310
        private TextToken<T> token; // Most used (first candidate)
328
        
311
        
329
        private TextToken<T> token2;
312
        private TextToken<T> token2; // Second most used
330
        
313
        
331
        public FlyItem(TextToken<T> token) {
314
        FlyItem(T id, String text) {
332
            this.token = token;
315
            newToken(id, text);
316
            token2 = token; // Make both item non-null
333
        }
317
        }
334
        
318
335
        public TextToken<T> token() {
319
        TextToken<T> flyToken(T id, String text) {
320
            // First do a quick check for equality only in both items
321
            if (token.text() != text) {
322
                if (token2.text() == text) {
323
                    // Swap token and token2 => token will contain the right value
324
                    swap();
325
                } else { // token.text() != text && token2.text() != text
326
                    // Now deep-compare of text of both tokens
327
                    if (!CharSequenceUtilities.textEquals(token.text(), text)) {
328
                        if (!CharSequenceUtilities.textEquals(token2.text(), text)) {
329
                            token2 = token;
330
                            newToken(id, text);
331
                        } else { // swap
332
                            swap();
333
                        }
334
                    }
335
                }
336
            }
336
            return token;
337
            return token;
337
        }
338
        }
338
        
339
        
339
        public TextToken<T> token2() {
340
        void newToken(T id, String text) {
340
            return token2;
341
            // Create new token
342
            token = new TextToken<T>(id, text);
343
            token.makeFlyweight();
341
        }
344
        }
342
        
345
        
343
        public void pushToken(TextToken<T> token) {
346
        private void swap() {
344
            this.token2 = this.token;
347
            TextToken<T> tmp = token;
345
            this.token = token;
348
            token = token2;
349
            token2 = tmp;
346
        }
350
        }
347
        
351
        
348
    }
352
    }
(-)a/lexer/src/org/netbeans/lib/lexer/LexerInputOperation.java (-278 / +259 lines)
Lines 41-61 Link Here
41
41
42
package org.netbeans.lib.lexer;
42
package org.netbeans.lib.lexer;
43
43
44
import java.util.List;
45
import java.util.Set;
44
import java.util.Set;
46
import org.netbeans.api.lexer.InputAttributes;
45
import java.util.logging.Level;
46
import java.util.logging.Logger;
47
import org.netbeans.api.lexer.Language;
47
import org.netbeans.api.lexer.Language;
48
import org.netbeans.api.lexer.LanguagePath;
48
import org.netbeans.api.lexer.LanguagePath;
49
import org.netbeans.api.lexer.PartType;
49
import org.netbeans.api.lexer.TokenId;
50
import org.netbeans.api.lexer.TokenId;
50
import org.netbeans.lib.editor.util.GapList;
51
import org.netbeans.lib.editor.util.CharSequenceUtilities;
51
import org.netbeans.lib.lexer.token.ComplexToken;
52
import org.netbeans.spi.lexer.Lexer;
52
import org.netbeans.spi.lexer.Lexer;
53
import org.netbeans.spi.lexer.LexerInput;
53
import org.netbeans.spi.lexer.LexerInput;
54
import org.netbeans.lib.lexer.token.AbstractToken;
54
import org.netbeans.lib.lexer.token.AbstractToken;
55
import org.netbeans.lib.lexer.token.ComplexToken;
55
import org.netbeans.lib.lexer.token.CustomTextToken;
56
import org.netbeans.lib.lexer.token.DefaultToken;
57
import org.netbeans.lib.lexer.token.PropertyToken;
56
import org.netbeans.spi.lexer.LanguageHierarchy;
58
import org.netbeans.spi.lexer.LanguageHierarchy;
57
import org.netbeans.spi.lexer.LexerRestartInfo;
59
import org.netbeans.spi.lexer.LexerRestartInfo;
58
import org.netbeans.spi.lexer.TokenFactory;
60
import org.netbeans.spi.lexer.TokenFactory;
61
import org.netbeans.spi.lexer.TokenPropertyProvider;
59
62
60
/**
63
/**
61
 * Implementation of the functionality related to lexer input.
64
 * Implementation of the functionality related to lexer input.
Lines 64-80 Link Here
64
 * @version 1.00
67
 * @version 1.00
65
 */
68
 */
66
69
67
public abstract class LexerInputOperation<T extends TokenId> implements CharProvider {
70
public abstract class LexerInputOperation<T extends TokenId> {
71
72
    // -J-Dorg.netbeans.lib.lexer.LexerInputOperation.level=FINE
73
    static final Logger LOG = Logger.getLogger(LexerInputOperation.class.getName());
68
    
74
    
69
    /** Flag for additional correctness checks (may degrade performance). */
75
    protected final TokenList<T> tokenList;
70
    private static final boolean testing = Boolean.getBoolean("netbeans.debug.lexer.test");
71
    
76
    
72
    /**
77
    /**
73
     * Current reading index in the operation.
78
     * Current reading index which usually corresponds to real offset.
74
     * At all times it must be &gt;=0.
79
     * <br/>
80
     * It should be set to its initial value in the constructor by descendants.
75
     */
81
     */
76
    private int readIndex;
82
    protected int readOffset;
77
    
83
    
84
    /**
85
     * A value that designates a start of a token being currently recognized.
86
     */
87
    protected int tokenStartOffset;
88
78
    /**
89
    /**
79
     * Maximum index from which the char was fetched for current
90
     * Maximum index from which the char was fetched for current
80
     * (or previous) tokens recognition.
91
     * (or previous) tokens recognition.
Lines 82-411 Link Here
82
     * The index is updated lazily - only when EOF is reached
93
     * The index is updated lazily - only when EOF is reached
83
     * and when backup() is called.
94
     * and when backup() is called.
84
     */
95
     */
85
    private int lookaheadIndex;
96
    private int lookaheadOffset;
86
    
97
    
87
    /**
98
    /**
88
     * Active preprocessor or null if there is no preprocessor.
99
     * Token length computed by assignTokenLength().
89
     */
100
     */
90
    private CharPreprocessorOperation preprocessorOperation;
101
    protected int tokenLength;
91
    
102
    
92
    /**
103
    protected Lexer<T> lexer;
93
     * Computed and cached token length.
94
     */
95
    private int tokenLength;
96
    
104
    
97
    private final TokenList<T> tokenList;
105
    protected final LanguageOperation<T> innerLanguageOperation;
98
    
99
    private final boolean mutableInput;
100
    
101
    private final Lexer<T> lexer;
102
    
103
    /**
104
     * Start of the token being currently recognized.
105
     */
106
    private int tokenStartIndex;
107
106
108
    private boolean lexerFinished;
109
    
107
    
110
    /**
108
    /**
111
     * How many flyweight tokens were created in a row.
109
     * How many flyweight tokens were created in a row.
112
     */
110
     */
113
    private int flySequenceLength;
111
    private int flyTokenSequenceLength;
114
    
112
    
115
    private List<CharPreprocessorError> preprocessErrorList;
116
    
117
    /**
118
     * Total count of preprocessors used during lexing.
119
     * It's used to determine whether extra preprocessed chars need to be used.
120
     */
121
    protected int preprocessingLevelCount;
122
123
    private CharProvider.ExtraPreprocessedChars extraPreprocessedChars;
124
    
125
    private Language<T> language;
126
127
    public LexerInputOperation(TokenList<T> tokenList, int tokenIndex, Object lexerRestartState) {
113
    public LexerInputOperation(TokenList<T> tokenList, int tokenIndex, Object lexerRestartState) {
128
        this.tokenList = tokenList;
114
        this.tokenList = tokenList;
129
        this.mutableInput = (tokenList.modCount() != -1);
115
        LanguagePath languagePath = tokenList.languagePath();
130
        // Determine flySequenceLength setting
116
        this.innerLanguageOperation = LexerUtilsConstants.innerLanguageOperation(languagePath);
131
        while (--tokenIndex >= 0 && LexerUtilsConstants.token(
117
        
132
                tokenList, tokenIndex).isFlyweight()
118
        // Determine flyTokenSequenceLength setting
133
        ) {
119
        while (--tokenIndex >= 0 && tokenList.tokenOrEmbedding(tokenIndex).token().isFlyweight()) {
134
            flySequenceLength++;
120
            flyTokenSequenceLength++;
135
        }
121
        }
136
        
122
137
        LanguagePath languagePath = tokenList.languagePath();
123
        LanguageHierarchy<T> languageHierarchy = LexerApiPackageAccessor.get().languageHierarchy(
138
        language = LexerUtilsConstants.innerLanguage(languagePath);
124
                LexerUtilsConstants.<T>innerLanguage(languagePath));
139
        LanguageHierarchy<T> languageHierarchy = LexerApiPackageAccessor.get().languageHierarchy(language);
140
        TokenFactory<T> tokenFactory = LexerSpiPackageAccessor.get().createTokenFactory(this);
125
        TokenFactory<T> tokenFactory = LexerSpiPackageAccessor.get().createTokenFactory(this);
141
        
126
        LexerInput lexerInput = LexerSpiPackageAccessor.get().createLexerInput(this);
142
        // Check whether character preprocessing is necessary
143
//        CharPreprocessor p = LexerSpiPackageAccessor.get().createCharPreprocessor(languageHierarchy);
144
//        if (p != null) {
145
//            preprocessingLevelCount++;
146
//            preprocessorOperation = new CharPreprocessorOperation(
147
//                    ((preprocessorOperation != null)
148
//                        ? (CharProvider)preprocessorOperation
149
//                        : this),
150
//                    p,
151
//                    this
152
//            );
153
//        }
154
        
155
        LexerInput lexerInput = LexerSpiPackageAccessor.get().createLexerInput(
156
                (preprocessorOperation != null) ? preprocessorOperation : this);
157
127
158
        LexerRestartInfo<T> info = LexerSpiPackageAccessor.get().createLexerRestartInfo(
128
        LexerRestartInfo<T> info = LexerSpiPackageAccessor.get().createLexerRestartInfo(
159
                lexerInput, tokenFactory, lexerRestartState,
129
                lexerInput, tokenFactory, lexerRestartState,
160
                tokenList.languagePath(), inputAttributes());
130
                languagePath, tokenList.inputAttributes());
161
        lexer = LexerSpiPackageAccessor.get().createLexer(languageHierarchy, info);
131
        lexer = LexerSpiPackageAccessor.get().createLexer(languageHierarchy, info);
162
    }
132
    }
163
133
164
    public abstract int read(int index);
134
    public abstract int read(int offset);
165
    
135
166
    public abstract char readExisting(int index);
136
    public abstract char readExisting(int offset);
167
    
137
168
    public abstract void approveToken(AbstractToken<T> token);
138
    /**
169
    
139
     * Fill appropriate data like token list and offset into a non-flyweight token.
170
    public Set<T> skipTokenIds() {
140
     * <br/>
171
        return tokenList.skipTokenIds();
141
     * This method should also move over the token's characters by increasing
172
    }
142
     * starting offset of the token and possibly other related variables.
143
     * 
144
     * @param token non-null non-flyweight token.
145
     */
146
    protected abstract void fillTokenData(AbstractToken<T> token);
173
    
147
    
174
    public final int read() {
148
    public final int read() {
175
        int c = read(readIndex++);
149
        int c = read(readOffset++);
176
        if (c == LexerInput.EOF) {
150
        if (c == LexerInput.EOF) {
177
            lookaheadIndex = readIndex; // count EOF char into lookahead
151
            lookaheadOffset = readOffset; // count EOF char into lookahead
178
            readIndex--; // readIndex must not include EOF
152
            readOffset--; // readIndex must not include EOF
179
        }
153
        }
180
        return c;
154
        return c;
181
    }
155
    }
182
    
156
    
183
    public int deepRawLength(int length) {
157
    public final int readLength() {
184
        // No preprocessing by default
158
        return readOffset - tokenStartOffset;
185
        return length;
186
    }
159
    }
187
    
160
    
188
    public int deepRawLengthShift(int index) {
161
    public final char readExistingAtIndex(int index) {
189
        // No preprocessing by default
162
        return readExisting(tokenStartOffset + index);
190
        return index;
191
    }
192
    
193
    public final int readIndex() {
194
        return readIndex;
195
    }
163
    }
196
    
164
    
197
    public final void backup(int count) {
165
    public final void backup(int count) {
198
        if (lookaheadIndex < readIndex) {
166
        if (lookaheadOffset < readOffset) {
199
            lookaheadIndex = readIndex;
167
            lookaheadOffset = readOffset;
200
        }
168
        }
201
        readIndex -= count;
169
        readOffset -= count;
202
    }
170
    }
203
    
171
    
172
    public final int lookahead() {
173
        return (lookaheadOffset > readOffset) ? (lookaheadOffset - readOffset) : 0;
174
    }
175
176
    public AbstractToken<T> nextToken() {
177
        while (true) {
178
            AbstractToken<T> token = (AbstractToken<T>)lexer.nextToken();
179
            if (token == null) {
180
                checkLexerInputFinished();
181
                return null;
182
            }
183
            // Check if token id of the new token belongs to the language
184
            Language<T> language = innerLanguageOperation.language();
185
            // Check that the id belongs to the language
186
            if (!isSkipToken(token) && !language.tokenIds().contains(token.id())) {
187
                String msgPrefix = "Invalid TokenId=" + token.id()
188
                        + " returned from lexer="
189
                        + lexer + " for language=" + language + ":\n";
190
                if (token.id().ordinal() > language.maxOrdinal()) {
191
                    throw new IllegalStateException(msgPrefix +
192
                            "Language.maxOrdinal()=" + language.maxOrdinal() + " < " + token.id().ordinal());
193
                } else { // Ordinal ok but different id with that ordinal contained in language
194
                    throw new IllegalStateException(msgPrefix +
195
                            "Language contains no or different tokenId with ordinal="
196
                            + token.id().ordinal() + ": " + language.tokenId(token.id().ordinal()));
197
                }
198
            }
199
            // Skip token's chars
200
            tokenStartOffset += tokenLength;
201
            if (!isSkipToken(token))
202
                return token;
203
        } // Continue to fetch non-skip token
204
    }
205
204
    /**
206
    /**
205
     * Get a distance between the index of the rightmost character already returned
207
     * Used by token list updater after nextToken() to determine start offset of a token 
206
     * by previous {@link #read()} operations and the present read index.
208
     * to be recognized next. Overriden for join token lists since join tokens
207
     * <br/>
209
     * may span multiple ETLs.
208
     * If there were no {@link #backup(int)} operation performed
210
     * 
209
     * the lookahead will be zero except the case when EOF was already returned.
211
     * @return start offset of a next token that would be recognized.
210
     *
211
     * @return &gt;=0 number of characters between the rightmost reading index reached
212
     *   and the present read position.
213
     *   <br/>
214
     *   The EOF (when reached by reading) is treated as a single character
215
     *   in lookahead.
216
     *   <br/>
217
     *   If there is an active character preprocessor the returned value
218
     *   is a raw length of the lookahead.
219
     */
212
     */
220
    public final int lookahead() {
213
    public int lastTokenEndOffset() {
221
        return (lookaheadIndex > readIndex)
214
        return tokenStartOffset;
222
                ? ((preprocessorOperation != null)
215
    }
223
                        ? preprocessorOperation.deepRawLength(lookaheadIndex - readIndex)
216
224
                        : (lookaheadIndex - readIndex))
217
    public AbstractToken<T> getFlyweightToken(T id, String text) {
225
                : 0;
218
        assert (text.length() <= readLength());
219
        // Compare each recognized char with the corresponding char in text
220
        if (LOG.isLoggable(Level.FINE)) {
221
            for (int i = 0; i < text.length(); i++) {
222
                if (text.charAt(i) != readExisting(i)) {
223
                    throw new IllegalArgumentException("Flyweight text in " + // NOI18N
224
                            "TokenFactory.getFlyweightToken(" + id + ", \"" + // NOI18N
225
                            CharSequenceUtilities.debugText(text) + "\") " + // NOI18N
226
                            "differs from recognized text: '" + // NOI18N
227
                            CharSequenceUtilities.debugChar(readExisting(i)) +
228
                            "' != '" + CharSequenceUtilities.debugChar(text.charAt(i)) + // NOI18N
229
                            "' at index=" + i // NOI18N
230
                    );
231
                }
232
            }
233
        }
234
235
        assignTokenLength(text.length());
236
        AbstractToken<T> token;
237
        if ((token = checkSkipToken(id)) == null) {
238
            if (isFlyTokenAllowed()) {
239
                token = innerLanguageOperation.getFlyweightToken(id, text);
240
                flyTokenSequenceLength++;
241
            } else { // Create regular token
242
                token = createDefaultTokenInstance(id);
243
                fillTokenData(token);
244
                flyTokenSequenceLength = 0;
245
            }
246
        }
247
        return token;
226
    }
248
    }
227
    
249
    
228
    public final int tokenLength() {
250
    private AbstractToken<T> checkSkipToken(T id) {
251
        if (isSkipTokenId(id)) {
252
            // Prevent fly token occurrence after skip token to have a valid offset
253
            flyTokenSequenceLength = LexerUtilsConstants.MAX_FLY_SEQUENCE_LENGTH;
254
            return skipToken();
255
        }
256
        return null;
257
    }
258
    
259
    public AbstractToken<T> createToken(T id, int length) {
260
        assignTokenLength(length);
261
        AbstractToken<T> token;
262
        if ((token = checkSkipToken(id)) == null) {
263
            token = createDefaultTokenInstance(id);
264
            fillTokenData(token);
265
            flyTokenSequenceLength = 0;
266
        }
267
        return token;
268
    }
269
270
    protected AbstractToken<T> createDefaultTokenInstance(T id) {
271
        return new DefaultToken<T>(id, tokenLength);
272
    }
273
274
    public AbstractToken<T> createToken(T id, int length, PartType partType) {
275
        if (partType == null)
276
            throw new IllegalArgumentException("partType must be non-null");
277
        if (partType == PartType.COMPLETE)
278
            return createToken(id, length);
279
280
        return createPropertyToken(id, length, null, partType);
281
    }
282
283
    public AbstractToken<T> createPropertyToken(T id, int length,
284
    TokenPropertyProvider<T> propertyProvider, PartType partType) {
285
        if (partType == null)
286
            partType = PartType.COMPLETE;
287
        
288
        assignTokenLength(length);
289
        AbstractToken<T> token;
290
        if ((token = checkSkipToken(id)) == null) {
291
            token = createPropertyTokenInstance(id, propertyProvider, partType);
292
            fillTokenData(token);
293
            flyTokenSequenceLength = 0;
294
        }
295
        return token;
296
    }
297
298
    protected AbstractToken<T> createPropertyTokenInstance(T id,
299
    TokenPropertyProvider<T> propertyProvider, PartType partType) {
300
        return new PropertyToken<T>(id, tokenLength, propertyProvider, partType);
301
    }
302
303
    public AbstractToken<T> createCustomTextToken(T id, int length, CharSequence customText) {
304
        assignTokenLength(length);
305
        AbstractToken<T> token;
306
        if ((token = checkSkipToken(id)) == null) {
307
            token = createCustomTextTokenInstance(id, customText);
308
            fillTokenData(token);
309
            flyTokenSequenceLength = 0;
310
        }
311
        return token;
312
    }
313
    
314
    protected AbstractToken<T> createCustomTextTokenInstance(T id, CharSequence customText) {
315
        return new CustomTextToken<T>(id, customText, tokenLength);
316
    }
317
318
    public boolean isSkipTokenId(T id) {
319
        Set<T> skipTokenIds = tokenList.skipTokenIds();
320
        return (skipTokenIds != null && skipTokenIds.contains(id));
321
    }
322
323
    protected final int tokenLength() {
229
        return tokenLength;
324
        return tokenLength;
230
    }
325
    }
231
    
326
232
    public void tokenRecognized(int tokenLength) {
327
    public void assignTokenLength(int tokenLength) {
233
        if (tokenLength > readIndex()) {
328
        if (tokenLength > readLength()) {
234
            throw new IndexOutOfBoundsException("tokenLength=" + tokenLength // NOI18N
329
            throw new IndexOutOfBoundsException("tokenLength=" + tokenLength // NOI18N
235
                    + " >" + readIndex());
330
                    + " >" + readLength());
236
        }
331
        }
237
        this.tokenLength = tokenLength;
332
        this.tokenLength = tokenLength;
238
    }
239
    
240
    public void tokenApproved() {
241
        tokenStartIndex += tokenLength;
242
        readIndex -= tokenLength;
243
        lookaheadIndex -= tokenLength;
244
    }
245
    
246
    protected final TokenList<T> tokenList() {
247
        return tokenList;
248
    }
249
    
250
    protected final int tokenStartIndex() {
251
        return tokenStartIndex;
252
    }
253
254
    public final void setTokenStartIndex(int tokenStartIndex) {
255
        this.tokenStartIndex = tokenStartIndex;
256
    }
257
258
    protected final CharPreprocessorOperation preprocessor() {
259
        return preprocessorOperation;
260
    }
261
    
262
    public final boolean isMutableInput() {
263
        return mutableInput;
264
    }
265
    
266
    public final boolean isStoreLookaheadAndState() {
267
        return isMutableInput() || testing;
268
    }
269
    
270
    public AbstractToken<T> nextToken() {
271
        assert (!lexerFinished);
272
        while (true) {
273
            @SuppressWarnings("unchecked")
274
            AbstractToken<T> token = (AbstractToken<T>)lexer().nextToken();
275
            if (token == null) {
276
                LexerUtilsConstants.checkLexerInputFinished(
277
                        (preprocessorOperation != null) ? (CharProvider)preprocessorOperation : this, this);
278
                lexerFinished = true;
279
                return null;
280
            } else {
281
                // Check that the id belongs to the language
282
                if (token != TokenFactory.SKIP_TOKEN && !language.tokenIds().contains(token.id())) {
283
                    String msgPrefix = "Invalid TokenId=" + token.id()
284
                            + " returned from lexer="
285
                            + lexer() + " for language=" + language + ":\n";
286
                    if (token.id().ordinal() > language.maxOrdinal()) {
287
                        throw new IllegalStateException(msgPrefix +
288
                                "Language.maxOrdinal()=" + language.maxOrdinal() + " < " + token.id().ordinal());
289
                    } else { // Ordinal ok but different id with that ordinal contained in language
290
                        throw new IllegalStateException(msgPrefix +
291
                                "Language contains no or different tokenId with ordinal="
292
                                + token.id().ordinal() + ": " + language.tokenId(token.id().ordinal()));
293
                    }
294
                }
295
                approveToken(token);
296
            }
297
            if (token == TokenFactory.SKIP_TOKEN)
298
                continue; // Fetch next token
299
            return token;
300
        }
301
    }
302
    
303
    /**
304
     * Notification that the token was recognized.
305
     * @param tokenLength length of the recognized token.
306
     * @param skip whether the token should be skipped
307
     * @return true if the token holding preprocessed text should be created.
308
     *  If skip is true then false is returned.
309
     */
310
    public final boolean tokenRecognized(int tokenLength, boolean skip) {
311
        if (preprocessorOperation != null) {
312
            preprocessorOperation.tokenRecognized(tokenLength);
313
        } else { // no preprocessor
314
            tokenRecognized(tokenLength);
315
        }
316
317
        // If the token is not skipped check whether preprocessed token
318
        // should be created instead of the regular token.
319
        if (!skip && tokenLength != this.tokenLength
320
                || (preprocessErrorList != null 
321
                    && preprocessErrorList.get(0).index() < this.tokenLength)
322
        ) {
323
            if (extraPreprocessedChars == null && preprocessingLevelCount > 1) {
324
                // For more than one preprocessing level need to handle
325
                // extra preprocessed chars before and after the main ones
326
                // on the parent levels.
327
                extraPreprocessedChars = new CharProvider.ExtraPreprocessedChars();
328
            }
329
            return true;
330
        }
331
        return false;
332
    }
333
    
334
    public void notifyPreprocessorError(CharPreprocessorError error) {
335
        if (preprocessErrorList == null) {
336
            preprocessErrorList = new GapList<CharPreprocessorError>();
337
        }
338
        preprocessErrorList.add(error);
339
    }
340
341
//    public final void initPreprocessedToken(AbstractToken<T> token) {
342
//        CharPreprocessorError error = null;
343
//        if (preprocessErrorList != null && preprocessErrorList.size() > 0) {
344
//            for (int i = preprocessErrorList.size() - 1; i >= 0; i--) {
345
//                error = preprocessErrorList.get(i);
346
//                if (error.index() < tokenLength) {
347
//                    preprocessErrorList.remove(i);
348
//                } else {// Above errors for this token
349
//                    // Relocate - subtract token length
350
//                    error.updateIndex(-tokenLength);
351
//                    error = null;
352
//                }
353
//            }
354
//        }
355
//        
356
//        PreprocessedTextStorage storage = preprocessorOperation.createPreprocessedTextStorage(
357
//                token.text(), extraPreprocessedChars);
358
//        
359
//        if (token.getClass() == ComplexToken.class) {
360
//            ((ComplexToken)token).initPrep(storage, error);
361
//        } else {
362
//            ((PreprocessedTextToken)token).initPrep(storage, error);
363
//        }
364
//    }
365
    
366
    public void collectExtraPreprocessedChars(CharProvider.ExtraPreprocessedChars epc,
367
    int prepStartIndex, int prepEndIndex, int topPrepEndIndex) {
368
        // No extra preprocessed characters
369
    }
370
    
371
    public final LanguageOperation<T> languageOperation() {
372
        return LexerUtilsConstants.innerLanguageOperation(tokenList.languagePath());
373
    }
333
    }
374
    
334
    
375
    public final Object lexerState() {
335
    public final Object lexerState() {
376
        return lexer.state();
336
        return lexer.state();
377
    }
337
    }
378
338
379
    public final boolean isFlyTokenAllowed() {
339
    protected boolean isFlyTokenAllowed() {
380
        return (flySequenceLength < LexerUtilsConstants.MAX_FLY_SEQUENCE_LENGTH);
340
        return (flyTokenSequenceLength < LexerUtilsConstants.MAX_FLY_SEQUENCE_LENGTH);
381
    }
341
    }
382
    
342
    
383
    protected final void flyTokenAdded() {
343
    public final boolean isSkipToken(AbstractToken<T> token) {
384
        flySequenceLength++;
344
        return (token == LexerUtilsConstants.SKIP_TOKEN);
385
    }
345
    }
386
    
346
    
387
    protected final void preventFlyToken() {
347
    @SuppressWarnings("unchecked")
388
        flySequenceLength = LexerUtilsConstants.MAX_FLY_SEQUENCE_LENGTH;
348
    public final AbstractToken<T> skipToken() {
349
        return (AbstractToken<T>)LexerUtilsConstants.SKIP_TOKEN;
350
    }
351
352
    /**
353
     * Release the underlying lexer. This method can be called multiple times.
354
     */
355
    public final void release() {
356
        if (lexer != null) {
357
            lexer.release();
358
            lexer = null;
359
        }
389
    }
360
    }
390
    
361
    
391
    protected final void clearFlySequence() {
362
    /**
392
        flySequenceLength = 0;
363
     * Check that there are no more characters to be read from the given
364
     * lexer input operation.
365
     */
366
    private void checkLexerInputFinished() {
367
        if (read() != LexerInput.EOF) {
368
            throw new IllegalStateException(
369
                "Lexer " + lexer + // NOI18N
370
                " returned null token" + // NOI18N
371
                " but EOF was not read from lexer input yet." + // NOI18N
372
                " Fix the lexer."// NOI18N
373
            );
374
        }
375
        if (readLength() > 0) {
376
            throw new IllegalStateException(
377
                "Lexer " + lexer + // NOI18N
378
                " returned null token but lexerInput.readLength()=" + // NOI18N
379
                readLength() +
380
                " - these characters need to be tokenized." + // NOI18N
381
                " Fix the lexer." // NOI18N
382
            );
383
        }
393
    }
384
    }
394
    
385
395
    protected final boolean isSkipToken(AbstractToken<T> token) {
386
    @Override
396
        return (token == TokenFactory.SKIP_TOKEN);
387
    public String toString() {
388
        return "tokenStartOffset=" + tokenStartOffset + ", readOffset=" + readOffset + // NOI18N
389
                ", lookaheadOffset=" + lookaheadOffset;
397
    }
390
    }
398
    
391
399
    public final Lexer lexer() {
400
        return lexer;
401
    }
402
    
403
    public final InputAttributes inputAttributes() {
404
        return tokenList.inputAttributes();
405
    }
406
    
407
    public final void release() {
408
        lexer.release();
409
    }
410
    
411
}
392
}
(-)a/lexer/src/org/netbeans/lib/lexer/LexerSpiPackageAccessor.java (-1 / +1 lines)
Lines 107-113 Link Here
107
107
108
    public abstract <T extends TokenId> boolean isRetainTokenText(LanguageHierarchy<T> languageHierarchy, T id);
108
    public abstract <T extends TokenId> boolean isRetainTokenText(LanguageHierarchy<T> languageHierarchy, T id);
109
109
110
    public abstract LexerInput createLexerInput(CharProvider charProvider);
110
    public abstract LexerInput createLexerInput(LexerInputOperation<?> operation);
111
    
111
    
112
    public abstract Language<?> language(MutableTextInput<?> mti);
112
    public abstract Language<?> language(MutableTextInput<?> mti);
113
    
113
    
(-)a/lexer/src/org/netbeans/lib/lexer/LexerUtilsConstants.java (-146 / +320 lines)
Lines 45-59 Link Here
45
import org.netbeans.api.lexer.InputAttributes;
45
import org.netbeans.api.lexer.InputAttributes;
46
import org.netbeans.api.lexer.Language;
46
import org.netbeans.api.lexer.Language;
47
import org.netbeans.api.lexer.LanguagePath;
47
import org.netbeans.api.lexer.LanguagePath;
48
import org.netbeans.api.lexer.Token;
49
import org.netbeans.api.lexer.TokenHierarchy;
48
import org.netbeans.api.lexer.TokenHierarchy;
50
import org.netbeans.api.lexer.TokenId;
49
import org.netbeans.api.lexer.TokenId;
51
import org.netbeans.lib.editor.util.ArrayUtilities;
50
import org.netbeans.lib.editor.util.ArrayUtilities;
52
import org.netbeans.lib.lexer.inc.FilterSnapshotTokenList;
53
import org.netbeans.lib.lexer.inc.SnapshotTokenList;
51
import org.netbeans.lib.lexer.inc.SnapshotTokenList;
52
import org.netbeans.lib.lexer.inc.TokenHierarchyEventInfo;
54
import org.netbeans.spi.lexer.LanguageHierarchy;
53
import org.netbeans.spi.lexer.LanguageHierarchy;
55
import org.netbeans.spi.lexer.LexerInput;
56
import org.netbeans.lib.lexer.token.AbstractToken;
54
import org.netbeans.lib.lexer.token.AbstractToken;
55
import org.netbeans.lib.lexer.token.TextToken;
57
import org.netbeans.spi.lexer.LanguageEmbedding;
56
import org.netbeans.spi.lexer.LanguageEmbedding;
58
57
59
/**
58
/**
Lines 75-128 Link Here
75
    public static final int MAX_FLY_SEQUENCE_LENGTH = 5;
74
    public static final int MAX_FLY_SEQUENCE_LENGTH = 5;
76
    
75
    
77
    /**
76
    /**
78
     * Minimum number of characters that will be lexed
77
     * Token list's modCount for the case when the source input is unmodifiable.
79
     * at once in a mutable input setup.
80
     * <br>
81
     * The created tokens will be notified in one token change event.
82
     * <br>
83
     * This should roughly cover a single page with text
84
     * (so that an initial page of text is lexed at once)
85
     * but it's not strictly necessary.
86
     */
78
     */
87
    public static final int MIN_LEXED_AREA_LENGTH = 4096;
79
    public static final int MOD_COUNT_IMMUTABLE_INPUT = -1;
88
    
80
    
89
    /**
81
    /**
90
     * Fraction of the mutable input size that will be lexed at once.
82
     * ModCount when the particular token list was removed from the token hierarchy.
91
     * <br>
92
     * This should avoid notifying of token creations too many times
93
     * for large inputs.
94
     */
83
     */
95
    public static final int LEXED_AREA_INPUT_SIZE_FRACTION = 10;
84
    public static final int MOD_COUNT_REMOVED = -2;
96
    
85
    
97
    /**
86
    /**
98
     * Marker value to recognize an uninitialized state variable.
87
     * Maximum token length that has the TokenLength objects cached by TokenLength.CACHE.
99
     */
88
     */
100
    public static final Object INVALID_STATE = new Object();
89
    public static final int MAX_CACHED_TOKEN_LENGTH = 200;
101
    
90
    
102
    /**
91
    /**
103
     * Check that there are no more characters to be read from the given
92
     * Threshold (used by TokenLength) above which the DefaultToken implementations will
104
     * lexer input operation.
93
     * start to cache the Token.text().toString() result in itself.
105
     */
94
     */
106
    public static void checkLexerInputFinished(CharProvider input, LexerInputOperation operation) {
95
    public static final short CACHE_TOKEN_TO_STRING_THRESHOLD = 900;
107
        if (input.read() != LexerInput.EOF) {
96
    
108
            throw new IllegalStateException(
97
    /**
109
                "Lexer " + operation.lexer() + // NOI18N
98
     * Threshold similar to TOKEN_TEXT_STRING_THRESHOLD but for a case when a root token list's text
110
                " returned null token" + // NOI18N
99
     * is a String instance. In that case a String.substring(start, end) will be used
111
                " but EOF was not read from lexer input yet." + // NOI18N
100
     * which is considerably cheaper than a regular case because the character data
112
                " Fix the lexer."// NOI18N
101
     * will be shared with the root text and there will be no character copying.
113
            );
102
     */
114
        }
103
    public static final short INPUT_TEXT_STRING_THRESHOLD = 300;
115
        if (input.readIndex() > 0) {
104
    
116
            throw new IllegalStateException(
105
    /**
117
                "Lexer " + operation.lexer() + // NOI18N
106
     * Used by TokenLength as a measure of a String instance production.
118
                " returned null token but lexerInput.readLength()=" + // NOI18N
107
     */
119
                input.readIndex() +
108
    public static final short TOKEN_LENGTH_STRING_CREATION_FACTOR = 50;
120
                " - these characters need to be tokenized." + // NOI18N
109
    
121
                " Fix the lexer." // NOI18N
110
    /**
122
            );
111
     * Initial size of a buffer for copying a text of a Reader.
123
        }
112
     */
113
    public static final int READER_TEXT_BUFFER_SIZE = 4096;
114
    
115
    static {
116
        // Require the following to only use THRESHOLD in certain checks
117
        assert (CACHE_TOKEN_TO_STRING_THRESHOLD >= INPUT_TEXT_STRING_THRESHOLD);
124
    }
118
    }
119
120
    public static final AbstractToken<?> SKIP_TOKEN
121
        = new TextToken<TokenId>(
122
            new TokenIdImpl("skip-token-id; special id of TokenFactory.SKIP_TOKEN; " + // NOI18N
123
                    " It should never be part of token sequence", 0, null), // NOI18N
124
            "" // empty skip token text NOI18N
125
        );
125
    
126
    
127
    /**
128
     * Initial embedded token list's modCount prior it was synced
129
     * with the root token list's modCount.
130
     */
131
    public static final int MOD_COUNT_EMBEDDED_INITIAL = -3;
132
126
    public static void tokenLengthZeroOrNegative(int tokenLength) {
133
    public static void tokenLengthZeroOrNegative(int tokenLength) {
127
        if (tokenLength == 0) {
134
        if (tokenLength == 0) {
128
            throw new IllegalArgumentException(
135
            throw new IllegalArgumentException(
Lines 195-201 Link Here
195
     * and if no embedding is found then the <code>LanguageProvider.findLanguageEmbedding()</code>.
202
     * and if no embedding is found then the <code>LanguageProvider.findLanguageEmbedding()</code>.
196
     */
203
     */
197
    public static <T extends TokenId> LanguageEmbedding<?>
204
    public static <T extends TokenId> LanguageEmbedding<?>
198
    findEmbedding(LanguageHierarchy<T> languageHierarchy, Token<T> token,
205
    findEmbedding(LanguageHierarchy<T> languageHierarchy, AbstractToken<T> token,
199
    LanguagePath languagePath, InputAttributes inputAttributes) {
206
    LanguagePath languagePath, InputAttributes inputAttributes) {
200
        LanguageEmbedding<?> embedding =
207
        LanguageEmbedding<?> embedding =
201
                LexerSpiPackageAccessor.get().embedding(
208
                LexerSpiPackageAccessor.get().embedding(
Lines 210-234 Link Here
210
        return embedding;
217
        return embedding;
211
    }
218
    }
212
    
219
    
213
    /**
214
     * Returns token from the given object which is either the token
215
     * or an embedding container.
216
     * <br/>
217
     * The method casts the resulting token to the generic type requested by the caller.
218
     */
219
    public static <T extends TokenId> AbstractToken<T> token(Object tokenOrEmbeddingContainer) {
220
        @SuppressWarnings("unchecked")
221
        AbstractToken<T> token = (AbstractToken<T>)
222
            ((tokenOrEmbeddingContainer.getClass() == EmbeddingContainer.class)
223
                ? ((EmbeddingContainer)tokenOrEmbeddingContainer).token()
224
                : (AbstractToken<?>)tokenOrEmbeddingContainer);
225
        return token;
226
    }
227
228
    public static <T extends TokenId> AbstractToken<T> token(TokenList<T> tokenList, int index) {
229
        return token(tokenList.tokenOrEmbeddingContainer(index));
230
    }
231
    
232
    public static int maxLanguagePathSize(Set<LanguagePath> paths) {
220
    public static int maxLanguagePathSize(Set<LanguagePath> paths) {
233
        int maxPathSize = 0;
221
        int maxPathSize = 0;
234
        for (LanguagePath lp : paths) {
222
        for (LanguagePath lp : paths) {
Lines 236-262 Link Here
236
        }
224
        }
237
        return maxPathSize;
225
        return maxPathSize;
238
    }
226
    }
227
    
228
    /**
229
     * Get index of the token that "contains" the given offset.
230
     * If the offset is beyond the existing tokens the method asks
231
     * for next tokens by <code>tokenList.tokenOrEmbedding()</code>.
232
     * 
233
     * @param offset offset for which the token index should be found.
234
     * @return array of two items where the [0] is token's index and [1] is its offset.
235
     *  <br/>
236
     *  If offset &gt;= last-token-end-offset then [0] contains token-count and
237
     *  [1] conains last-token-end-offset.
238
     *  <br/>
239
     *  [0] may contain -1 to indicate that there are no tokens in the token list
240
     *  ([1] then contains zero).
241
     */
242
    public static int[] tokenIndexLazyTokenCreation(TokenList<?> tokenList, int offset) {
243
        // Token count in the list may change as possibly other threads
244
        // keep asking for tokens. Root token list impls create tokens lazily
245
        // when asked by clients.
246
        // The intent is to not force creation of all token (because of using a binary search)
247
        // so first a last token is checked whether it covers the requested offset.
248
        int tokenCount = tokenList.tokenCountCurrent(); // presently created token count
249
        if (tokenCount == 0) { // no tokens yet -> attempt to create at least one
250
            if (tokenList.tokenOrEmbedding(0) == null) { // really no tokens at all
251
                return new int[] { -1, 0 };
252
            }
253
            // Re-get the present token count (could be created a chunk of tokens at once)
254
            tokenCount = tokenList.tokenCountCurrent();
255
        }
256
257
        // tokenCount surely >0
258
        int prevTokenOffset = tokenList.tokenOffsetByIndex(tokenCount - 1);
259
        if (offset > prevTokenOffset) { // may need to create further tokens if they do not exist
260
            // Force token list to create subsequent tokens
261
            // Cannot subtract offset by each token's length because
262
            // there may be gaps between tokens due to token id filter use.
263
            int tokenLength = tokenList.tokenOrEmbedding(tokenCount - 1).token().length();
264
            while (offset >= prevTokenOffset + tokenLength) { // above present token
265
                TokenOrEmbedding<?> tokenOrEmbedding = tokenList.tokenOrEmbedding(tokenCount);
266
                if (tokenOrEmbedding != null) {
267
                    AbstractToken<?> t = tokenOrEmbedding.token();
268
                    if (t.isFlyweight()) { // need to use previous tokenLength
269
                        prevTokenOffset += tokenLength;
270
                    } else { // non-flyweight token - retrieve offset
271
                        prevTokenOffset = tokenList.tokenOffsetByIndex(tokenCount);
272
                    }
273
                    tokenLength = t.length();
274
                    tokenCount++;
275
276
                } else { // no more tokens => position behind last token
277
                    return new int[] { tokenCount, prevTokenOffset + tokenLength };
278
                }
279
            }
280
            return new int[] { tokenCount - 1, prevTokenOffset };
281
        }
282
        // Now do a regular binary search
283
        return tokenIndexBinSearch(tokenList, offset, tokenCount);
284
    }
285
    
286
    /**
287
     * Get index of the token that "contains" the given offset by using binary search
288
     * in existing tokens.
289
     * 
290
     * @param offset offset for which the token index should be found.
291
     * @return array of two items where the [0] is token's index and [1] is its offset.
292
     *  <br/>
293
     *  If offset &gt;= last-token-end-offset then [0] contains token-count and
294
     *  [1] conains last-token-end-offset.
295
     *  <br/>
296
     *  [0] may contain -1 to indicate that there are no tokens in the token list
297
     *  ([1] then contains zero).
298
     */
299
    public static int[] tokenIndexBinSearch(TokenList<?> tokenList, int offset, int tokenCount) {
300
        // The offset is within the currently recognized tokens
301
        // Use binary search
302
        int low = 0;
303
        int high = tokenCount - 1;
304
        int mid = -1;
305
        int midStartOffset = -1;
306
        while (low <= high) {
307
            mid = (low + high) >>> 1;
308
            midStartOffset = tokenList.tokenOffsetByIndex(mid);
309
            
310
            if (midStartOffset < offset) {
311
                low = mid + 1;
312
            } else if (midStartOffset > offset) {
313
                high = mid - 1;
314
            } else {
315
                // Token starting exactly at offset found
316
                return new int[] { mid, midStartOffset}; // right at the token begining
317
            }
318
        }
319
        
320
        // Not found exactly and high + 1 == low => high < low
321
        // BTW there may be gaps between tokens; if offset is in gap then position to lower token
322
        if (high >= 0) { // could be -1
323
            if (low == tokenCount) { // Could be beyond end of last token
324
                AbstractToken<?> t = tokenList.tokenOrEmbedding(high).token();
325
                // Use current midStartOffset
326
                if (offset >= midStartOffset + t.length()) { // beyond end of last token
327
                    // Offset in the gap above the "high" token
328
                    high++;
329
                    midStartOffset += t.length();
330
                } else if (mid != high) {
331
                    midStartOffset = tokenList.tokenOffsetByIndex(high);
332
                }
333
            } else if (mid != high) {
334
                midStartOffset = tokenList.tokenOffsetByIndex(high);
335
            }
336
        } else { // high == -1 => mid == 0
337
            if (tokenCount == 0) { // Need to return -1
338
                return new int[] { -1, 0 };
339
            }
340
            high = 0;
341
            // Use current midStartOffset
342
        }
343
        return new int[] { high, midStartOffset };
344
    }
345
346
    public static int updatedStartOffset(EmbeddedTokenList<?> etl, TokenHierarchyEventInfo eventInfo) {
347
        etl.embeddingContainer().updateStatusUnsync();
348
        int startOffset = etl.startOffset();
349
        return (etl.isRemoved() && startOffset > eventInfo.modOffset())
350
                ? Math.max(startOffset - eventInfo.removedLength(), eventInfo.modOffset())
351
                : startOffset;
352
    }
239
353
240
    public static <T extends TokenId> StringBuilder appendTokenList(StringBuilder sb, TokenList<T> tokenList) {
354
    public static <T extends TokenId> StringBuilder appendTokenList(StringBuilder sb, TokenList<T> tokenList) {
241
        return appendTokenList(sb, tokenList, -1, 0, Integer.MAX_VALUE, true, 0);
355
        return appendTokenList(sb, tokenList, -1, 0, Integer.MAX_VALUE, true, 0, true);
242
    }
356
    }
243
357
244
    public static <T extends TokenId> StringBuilder appendTokenListIndented(
358
    public static <T extends TokenId> StringBuilder appendTokenListIndented(
245
        StringBuilder sb, TokenList<T> tokenList, int indent
359
        StringBuilder sb, TokenList<T> tokenList, int indent
246
    ) {
360
    ) {
247
        return appendTokenList(sb, tokenList, -1, 0, Integer.MAX_VALUE, false, indent);
361
        return appendTokenList(sb, tokenList, -1, 0, Integer.MAX_VALUE, false, indent, true);
248
    }
362
    }
249
363
250
    public static <T extends TokenId> StringBuilder appendTokenList(StringBuilder sb,
364
    public static <T extends TokenId> StringBuilder appendTokenList(StringBuilder sb,
251
    TokenList<T> tokenList, int currentIndex, int startIndex, int endIndex, boolean appendEmbedded, int indent) {
365
            TokenList<T> tokenList, int currentIndex, int startIndex, int endIndex,
366
            boolean appendEmbedded, int indent, boolean dumpTokenText
367
    ) {
252
        if (sb == null) {
368
        if (sb == null) {
253
            sb = new StringBuilder();
369
            sb = new StringBuilder(200);
254
        }
370
        }
255
        TokenHierarchy<?> tokenHierarchy;
371
        TokenHierarchy<?> tokenHierarchy;
256
        if (tokenList instanceof SnapshotTokenList) {
372
        if (tokenList instanceof SnapshotTokenList) {
257
                tokenHierarchy = ((SnapshotTokenList<T>)tokenList).snapshot().tokenHierarchy();
373
            tokenHierarchy = ((SnapshotTokenList<T>)tokenList).snapshot().tokenHierarchy();
258
        } else {
374
        } else {
259
                tokenHierarchy = null;
375
            tokenHierarchy = null;
260
        }
376
        }
261
377
262
        endIndex = Math.min(tokenList.tokenCountCurrent(), endIndex);
378
        endIndex = Math.min(tokenList.tokenCountCurrent(), endIndex);
Lines 266-272 Link Here
266
            sb.append((i == currentIndex) ? '*' : 'T');
382
            sb.append((i == currentIndex) ? '*' : 'T');
267
            ArrayUtilities.appendBracketedIndex(sb, i, digitCount);
383
            ArrayUtilities.appendBracketedIndex(sb, i, digitCount);
268
            appendTokenInfo(sb, tokenList, i, tokenHierarchy,
384
            appendTokenInfo(sb, tokenList, i, tokenHierarchy,
269
                    appendEmbedded, indent);
385
                    appendEmbedded, indent, dumpTokenText);
270
            sb.append('\n');
386
            sb.append('\n');
271
        }
387
        }
272
        return sb;
388
        return sb;
Lines 277-372 Link Here
277
            || (state1 != null && state1.equals(state2));
393
            || (state1 != null && state1.equals(state2));
278
    }
394
    }
279
    
395
    
280
    /**
281
     * Get end state of the given token list that may be used for relexing
282
     * of the next section.
283
     * <br/>
284
     * If the section is empty but it does not join the sections then null state
285
     * is returned.
286
     * 
287
     * @param tokenList non-null token list.
288
     * @return end state or {@link #INVALID_STATE} if previous token list must be queried.
289
     */
290
    public static Object endState(EmbeddedTokenList<?> tokenList) {
291
        int tokenCount = tokenList.tokenCount();
292
        return (tokenCount > 0)
293
                ? tokenList.state(tokenList.tokenCount() - 1)
294
                : tokenList.embedding().joinSections() ? INVALID_STATE : null;
295
    }
296
    
297
    /**
298
     * Get end state of the given token list that may be used for relexing
299
     * of the next section.
300
     * <br/>
301
     * If the section is empty but it does not join the sections then null state
302
     * is returned.
303
     * 
304
     * @param tokenList non-null token list.
305
     * @param lastEndState current state that will be overriden in case this section
306
     *  is not empty while joining the sections.
307
     * @return end state or {@link #INVALID_STATE} if previous token list must be queried.
308
     */
309
    public static Object endState(EmbeddedTokenList<?> tokenList, Object lastEndState) {
310
        int tokenCount = tokenList.tokenCount();
311
        return (tokenCount > 0)
312
                ? tokenList.state(tokenList.tokenCount() - 1)
313
                : tokenList.embedding().joinSections() ? lastEndState : null;
314
    }
315
    
316
    public static String idToString(TokenId id) {
396
    public static String idToString(TokenId id) {
317
        return id.name() + '[' + id.ordinal() + ']'; // NOI18N;
397
        return id.name() + '[' + id.ordinal() + ']'; // NOI18N;
318
    }
398
    }
319
    
399
    
320
    public static <T extends TokenId, ET extends TokenId> TokenList<ET> embeddedTokenList(
400
    public static <T extends TokenId> void appendTokenInfo(StringBuilder sb,
321
    TokenList<T> tokenList, int tokenIndex, Language<ET> embeddedLanguage) {
401
            TokenList<T> tokenList, int index,
322
        TokenList<ET> embeddedTokenList
402
            TokenHierarchy tokenHierarchy, boolean appendEmbedded, int indent,
323
                = EmbeddingContainer.embeddedTokenList(tokenList, tokenIndex, embeddedLanguage);
403
            boolean dumpTokenText
324
        if (embeddedTokenList != null) {
325
            ((EmbeddedTokenList)embeddedTokenList).embeddingContainer().updateStatus();
326
            TokenList<T> tl = tokenList;
327
            if (tokenList.getClass() == SubSequenceTokenList.class) {
328
                tl = ((SubSequenceTokenList<T>)tokenList).delegate();
329
            }
330
331
            if (tl.getClass() == FilterSnapshotTokenList.class) {
332
                embeddedTokenList = new FilterSnapshotTokenList<ET>(embeddedTokenList,
333
                        ((FilterSnapshotTokenList<T>)tl).tokenOffsetDiff());
334
335
            } else if (tl.getClass() == SnapshotTokenList.class) {
336
                Token<T> token = token(tokenList, tokenIndex);
337
                embeddedTokenList = new FilterSnapshotTokenList<ET>(embeddedTokenList,
338
                        tokenList.tokenOffset(tokenIndex) - token.offset(null));
339
            }
340
            return embeddedTokenList;
341
        }
342
        return null;
343
    }
344
    
345
    public static void appendTokenInfo(StringBuilder sb, TokenList tokenList, int index,
346
            TokenHierarchy tokenHierarchy, boolean appendEmbedded, int indent
347
    ) {
404
    ) {
348
        appendTokenInfo(sb, tokenList.tokenOrEmbeddingContainer(index),
405
        appendTokenInfo(sb, tokenList.tokenOrEmbedding(index),
349
                tokenList.lookahead(index), tokenList.state(index),
406
                tokenList.lookahead(index), tokenList.state(index),
350
                tokenHierarchy, appendEmbedded, indent);
407
                tokenHierarchy, appendEmbedded, indent, dumpTokenText);
351
    }
408
    }
352
409
353
    public static void appendTokenInfo(StringBuilder sb, Object tokenOrEmbeddingContainer,
410
    public static <T extends TokenId> void appendTokenInfo(StringBuilder sb,
354
            int lookahead, Object state,
411
            TokenOrEmbedding<T> tokenOrEmbedding, int lookahead, Object state,
355
            TokenHierarchy<?> tokenHierarchy, boolean appendEmbedded, int indent
412
            TokenHierarchy<?> tokenHierarchy, boolean appendEmbedded, int indent,
413
            boolean dumpTokenText
356
    ) {
414
    ) {
357
        if (tokenOrEmbeddingContainer == null) {
415
        if (tokenOrEmbedding == null) {
358
            sb.append("<NULL-TOKEN>");
416
            sb.append("<NULL-TOKEN>");
359
        } else { // regular token
417
        } else { // regular token
360
            Token<?> token; 
418
            EmbeddingContainer<T> ec = tokenOrEmbedding.embedding();
361
            EmbeddingContainer<?> ec;
419
            AbstractToken<T> token = tokenOrEmbedding.token();
362
            if (tokenOrEmbeddingContainer.getClass() == EmbeddingContainer.class) {
420
            token.dumpInfo(sb, tokenHierarchy, dumpTokenText, indent);
363
                ec = (EmbeddingContainer<?>)tokenOrEmbeddingContainer;
364
                token = ec.token();
365
            } else {
366
                ec = null;
367
                token = (Token<?>)tokenOrEmbeddingContainer;
368
            }
369
            sb.append(((AbstractToken<?>)token).dumpInfo(tokenHierarchy));
370
            appendLAState(sb, lookahead, state);
421
            appendLAState(sb, lookahead, state);
371
            sb.append(", ");
422
            sb.append(", ");
372
            appendIdentityHashCode(sb, token);
423
            appendIdentityHashCode(sb, token);
Lines 384-390 Link Here
384
                    ArrayUtilities.appendSpaces(sb, indent);
435
                    ArrayUtilities.appendSpaces(sb, indent);
385
                    sb.append("Embedding[").append(index).append("]: \"").append(etl.languagePath().mimePath()).append("\"\n");
436
                    sb.append("Embedding[").append(index).append("]: \"").append(etl.languagePath().mimePath()).append("\"\n");
386
                    if (appendEmbedded) {
437
                    if (appendEmbedded) {
387
                        appendTokenList(sb, etl, -1, 0, Integer.MAX_VALUE, appendEmbedded, indent);
438
                        appendTokenList(sb, etl, -1, 0, Integer.MAX_VALUE, appendEmbedded, indent, true);
388
                    }
439
                    }
389
                    etl = etl.nextEmbeddedTokenList();
440
                    etl = etl.nextEmbeddedTokenList();
390
                    index++;
441
                    index++;
Lines 413-418 Link Here
413
        }
464
        }
414
    }
465
    }
415
    
466
    
467
    public static String checkConsistencyTokenList(TokenList<?> tokenList, boolean checkEmbedded) {
468
        return checkConsistencyTokenList(tokenList, checkEmbedded, ArrayUtilities.emptyIntArray(), tokenList.startOffset());
469
    }
470
471
    private static String checkConsistencyTokenList(TokenList<?> tokenList, boolean checkEmbedded,
472
    int[] parentIndexes, int firstTokenOffset) {
473
        int tokenCountCurrent = tokenList.tokenCountCurrent();
474
        int[] indexes = ArrayUtilities.intArray(parentIndexes, parentIndexes.length + 1);
475
        boolean continuous = tokenList.isContinuous();
476
        int lastOffset = firstTokenOffset;
477
        for (int i = 0; i < tokenCountCurrent; i++) {
478
            TokenOrEmbedding<?> tokenOrEmbedding = tokenList.tokenOrEmbedding(i);
479
            if (tokenOrEmbedding == null) {
480
                return dumpContext("Null token", tokenList, i, parentIndexes); // NOI18N
481
            }
482
            AbstractToken<?> token = tokenOrEmbedding.token();
483
            if (token.isRemoved()) {
484
                return dumpContext("Token is removed", tokenList, i, parentIndexes);
485
            }
486
            // Check whether tokenList.startOffset() corresponds to the start of first token
487
            if (i == 0 && continuous && tokenCountCurrent > 0 && !token.isFlyweight()) {
488
                if (token.offset(null) != tokenList.startOffset()) {
489
                    return dumpContext("firstToken.offset()=" + token.offset(null) +
490
                            " != tokenList.startOffset()=" + tokenList.startOffset(),
491
                            tokenList, i, parentIndexes);
492
                }
493
            }
494
            if (!token.isFlyweight() && token.tokenList() != tokenList && !(tokenList instanceof JoinTokenList)) {
495
                return dumpContext("Invalid token.tokenList()=" + token.tokenList(),
496
                        tokenList, i, parentIndexes);
497
            }
498
            if (token.text() == null) {
499
                return dumpContext("Null token.text()=" + token.tokenList(),
500
                        tokenList, i, parentIndexes);
501
            }
502
            int offset = (token.isFlyweight()) ? lastOffset : token.offset(null);
503
            if (offset < 0) {
504
                return dumpContext("Token offset=" + offset + " < 0", tokenList, i, parentIndexes); // NOI18N
505
            }
506
            if (offset < lastOffset) {
507
                return dumpContext("Token offset=" + offset + " < lastOffset=" + lastOffset,
508
                        tokenList, i, parentIndexes);
509
            }
510
            if (offset > lastOffset && continuous) {
511
                return dumpContext("Gap between tokens; offset=" + offset + ", lastOffset=" + lastOffset,
512
                        tokenList, i, parentIndexes);
513
            }
514
            lastOffset = offset + token.length();
515
            EmbeddingContainer<?> ec = tokenOrEmbedding.embedding();
516
            if (ec != null && checkEmbedded) {
517
                EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
518
                while (etl != null) {
519
                    String error = checkConsistencyTokenList(etl, checkEmbedded, indexes,
520
                            offset + etl.embedding().startSkipLength());
521
                    if (error != null)
522
                        return error;
523
                    etl = etl.nextEmbeddedTokenList();
524
                }
525
            }
526
        }
527
        return null;
528
    }
529
    
530
    private static String dumpContext(String msg, TokenList<?> tokenList, int index, int[] parentIndexes) {
531
        StringBuilder sb = new StringBuilder();
532
        sb.append(msg);
533
        sb.append(" at index="); // NOI18N
534
        sb.append(index);
535
        sb.append(" of tokens of language "); // NOI18N
536
        sb.append(tokenList.languagePath().innerLanguage().mimeType());
537
        sb.append('\n');
538
        LexerUtilsConstants.appendTokenList(sb, tokenList, index, index - 2, index + 3, false, 0, true);
539
        sb.append("\nParents:\n"); // NOI18N
540
        sb.append(tracePath(parentIndexes, tokenList));
541
        return sb.toString();
542
    }
543
    
544
    public static String findTokenContext(AbstractToken<?> token) {
545
        return findTokenContext(token, token.tokenList().rootTokenList(), ArrayUtilities.emptyIntArray());
546
    }
547
548
    private static String findTokenContext(AbstractToken<?> token, TokenList<?> tokenList, int[] parentIndexes) {
549
        int tokenCountCurrent = tokenList.tokenCountCurrent();
550
        int[] indexes = ArrayUtilities.intArray(parentIndexes, parentIndexes.length + 1);
551
        for (int i = 0; i < tokenCountCurrent; i++) {
552
            TokenOrEmbedding<?> tokenOrEmbedding = tokenList.tokenOrEmbedding(i);
553
            if (tokenOrEmbedding == null) {
554
                continue;
555
            }
556
            EmbeddingContainer<?> ec = tokenOrEmbedding.embedding();
557
            if (ec != null) {
558
                if (ec.token() == token) {
559
                    return dumpContext("Token found.", tokenList, i, indexes);
560
                }
561
                EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
562
                while (etl != null) {
563
                    String context = findTokenContext(token, etl, indexes);
564
                    if (context != null)
565
                        return context;
566
                    etl = etl.nextEmbeddedTokenList();
567
                }
568
569
            } else if (tokenOrEmbedding == token) {
570
                return dumpContext("Token found.", tokenList, i, indexes);
571
            }
572
        }
573
        return null;
574
    }
575
576
    private static String tracePath(int[] indexes, TokenList<?> tokenList) {
577
        StringBuilder sb  = new StringBuilder();
578
        LanguagePath languagePath = tokenList.languagePath();
579
        TokenList<?> rootTokenList = tokenList.rootTokenList();
580
        tokenList = rootTokenList;
581
        for (int i = 0; i < indexes.length; i++) {
582
            appendTokenInfo(sb, tokenList, i, rootTokenList.tokenHierarchyOperation().tokenHierarchy(), false, 0, true);
583
            // Assign language to variable to get rid of javac bug for incremental compilation on 1.5 
584
            Language<?> language = languagePath.language(i);
585
            tokenList = EmbeddingContainer.embeddedTokenList(tokenList, indexes[i], language, true);
586
        }
587
        return sb.toString();
588
    }
589
    
416
    private LexerUtilsConstants() {
590
    private LexerUtilsConstants() {
417
        // no instances
591
        // no instances
418
    }
592
    }
(-)a/lexer/src/org/netbeans/lib/lexer/PreprocessedTextLexerInputOperation.java (-26 / +10 lines)
Lines 64-100 Link Here
64
    private int lastRawLengthShift;
64
    private int lastRawLengthShift;
65
    
65
    
66
    private int tokenEndRawLengthShift;
66
    private int tokenEndRawLengthShift;
67
67
    
68
    public PreprocessedTextLexerInputOperation(TokenList<T> tokenList, PreprocessedTextStorage prepText) {
68
    private int tokenStartIndex; // Extra added to compile
69
        this(tokenList, 0, null, prepText, 0, 0, prepText.length());
70
    }
71
69
72
    public PreprocessedTextLexerInputOperation(TokenList<T> tokenList, int tokenIndex,
70
    public PreprocessedTextLexerInputOperation(TokenList<T> tokenList, int tokenIndex,
73
    Object lexerRestartState, PreprocessedTextStorage prepText, int prepTextStartOffset,
71
    Object lexerRestartState, PreprocessedTextStorage prepText, int prepTextStartOffset,
74
    int startOffset, int endOffset) {
72
    int startOffset, int endOffset) {
75
        super(tokenList, tokenIndex, lexerRestartState, prepText,
73
        super(tokenList, tokenIndex, lexerRestartState, startOffset, endOffset);
76
                prepTextStartOffset, startOffset, endOffset);
77
        this.preprocessedText = prepText;
74
        this.preprocessedText = prepText;
78
        int index = startOffset - prepTextStartOffset;
75
        int index = startOffset - prepTextStartOffset;
79
        if (index > 0) {
76
        if (index > 0) {
80
            tokenStartRawLengthShift = preprocessedText.rawLengthShift(index);
77
            tokenStartRawLengthShift = preprocessedText.rawLengthShift(index);
81
            lastRawLengthShift = tokenStartRawLengthShift;
78
            lastRawLengthShift = tokenStartRawLengthShift;
82
        }
79
        }
83
        preprocessingLevelCount++; // extra level of preprocessing
84
    }
85
86
    public int deepRawLength(int length) {
87
        return length + preprocessedText.rawLengthShift(tokenStartIndex() + length - 1)
88
                - tokenStartRawLengthShift;
89
    }
90
    
91
    public int deepRawLengthShift(int index) {
92
        return preprocessedText.rawLengthShift(tokenStartIndex() + index)
93
                - tokenStartRawLengthShift;
94
    }
80
    }
95
81
96
    public int read(int index) { // index >= 0 is guaranteed by contract
82
    public int read(int index) { // index >= 0 is guaranteed by contract
97
        index += tokenStartIndex();
83
        index += tokenStartIndex;
98
        if (index < readEndIndex()) {
84
        if (index < readEndIndex()) {
99
            // Check whether the char is preprocessed
85
            // Check whether the char is preprocessed
100
            int rls = preprocessedText.rawLengthShift(index);
86
            int rls = preprocessedText.rawLengthShift(index);
Lines 111-123 Link Here
111
        }
97
        }
112
    }
98
    }
113
99
114
    public void tokenRecognized(int tokenLength) {
100
    public void assignTokenLength(int tokenLength) {
115
        super.tokenRecognized(tokenLength);
116
        tokenEndRawLengthShift = preprocessedText.rawLengthShift(
101
        tokenEndRawLengthShift = preprocessedText.rawLengthShift(
117
                tokenStartIndex() + tokenLength() - 1);
102
                tokenStartIndex + tokenLength - 1);
118
    }
103
    }
119
    
104
    
120
    public void tokenApproved() {
105
    protected void tokenApproved() {
121
        // Increase base raw length shift by the token's last-char shift
106
        // Increase base raw length shift by the token's last-char shift
122
        tokenStartRawLengthShift += tokenEndRawLengthShift;
107
        tokenStartRawLengthShift += tokenEndRawLengthShift;
123
108
Lines 135-141 Link Here
135
                prepEndIndex -= tokenLength();
120
                prepEndIndex -= tokenLength();
136
            }
121
            }
137
        }
122
        }
138
        super.tokenApproved();
139
    }
123
    }
140
    
124
    
141
    public void collectExtraPreprocessedChars(CharProvider.ExtraPreprocessedChars epc,
125
    public void collectExtraPreprocessedChars(CharProvider.ExtraPreprocessedChars epc,
Lines 153-159 Link Here
153
                    // for the present token and the ending chars could possibly
137
                    // for the present token and the ending chars could possibly
154
                    // be non-preprocessed (prepEndIndex > tokenLength)
138
                    // be non-preprocessed (prepEndIndex > tokenLength)
155
                    while (--i >= prepStartIndex && postCount > 0
139
                    while (--i >= prepStartIndex && postCount > 0
156
                            && preprocessedText.rawLengthShift(i + tokenStartIndex()) == tokenEndRawLengthShift
140
                            && preprocessedText.rawLengthShift(i + tokenStartIndex) == tokenEndRawLengthShift
157
                    ) { // not preprocessed
141
                    ) { // not preprocessed
158
                        postCount--;
142
                        postCount--;
159
                    }
143
                    }
Lines 167-177 Link Here
167
            assert (preCount >= 0 && postCount >= 0);
151
            assert (preCount >= 0 && postCount >= 0);
168
            epc.ensureExtraLength(preCount + postCount);
152
            epc.ensureExtraLength(preCount + postCount);
169
            while (--preCount >= 0) {
153
            while (--preCount >= 0) {
170
                epc.insert(readExisting(prepStartIndex - 1), deepRawLength(prepStartIndex) - prepStartIndex);
154
//                epc.insert(readExisting(prepStartIndex - 1), deepRawLength(prepStartIndex) - prepStartIndex);
171
                prepStartIndex--;
155
                prepStartIndex--;
172
            }
156
            }
173
            while (--postCount >= 0) {
157
            while (--postCount >= 0) {
174
                epc.append(readExisting(prepEndIndex), deepRawLength(prepEndIndex) - topPrepEndIndex);
158
//                epc.append(readExisting(prepEndIndex), deepRawLength(prepEndIndex) - topPrepEndIndex);
175
                prepEndIndex++;
159
                prepEndIndex++;
176
                topPrepEndIndex++;
160
                topPrepEndIndex++;
177
            }
161
            }
(-)a/lexer/src/org/netbeans/lib/lexer/SubSequenceTokenList.java (-90 / +36 lines)
Lines 110-193 Link Here
110
            // No upper bound for end index so use tokenCount() (can be improved if desired)
110
            // No upper bound for end index so use tokenCount() (can be improved if desired)
111
            limitEndIndex = tokenList.tokenCount();
111
            limitEndIndex = tokenList.tokenCount();
112
        } else { // Valid limit end offset
112
        } else { // Valid limit end offset
113
            limitEndIndex = tokenList.tokenCountCurrent(); // presently created token count
113
            int[] indexAndTokenOffset = tokenList.tokenIndex(limitEndOffset);
114
            if (limitEndIndex == 0) { // no tokens yet -> attempt to create at least one
114
            limitEndIndex = indexAndTokenOffset[0];
115
                if (tokenList.tokenOrEmbeddingContainer(0) != null) { // some tokens exist
115
            if (limitEndIndex != -1) {
116
                    // Re-get the present token count (could be created a chunk of tokens at once)
116
                // If the limitStartOffset is "inside" a token and it's not at or beyond end of TL
117
                    limitEndIndex = tokenList.tokenCountCurrent();
117
                if (limitEndOffset != indexAndTokenOffset[1] && limitEndIndex < tokenList.tokenCountCurrent()) {
118
                    limitEndIndex++; // Include the token that contains the offset
118
                }
119
                }
119
            }
120
            } else { // No tokens at all
120
            
121
                limitEndIndex = 0;
121
            if (limitEndIndex > 0) {
122
                // tokenCount surely >0
123
                int tokenOffset = tokenList.tokenOffset(limitEndIndex - 1);
124
                if (limitEndOffset > tokenOffset) { // may need to create further tokens if they do not exist
125
                    // Force token list to create subsequent tokens
126
                    // Cannot subtract offset by each token's length because
127
                    // there may be gaps between tokens due to token id filter use.
128
                    AbstractToken<?> token = token(limitEndIndex - 1);
129
                    int tokenLength = token.length();
130
                    while (limitEndOffset > tokenOffset + tokenLength) { // above present token
131
                        Object tokenOrEmbeddingContainer = tokenList.tokenOrEmbeddingContainer(limitEndIndex);
132
                        if (tokenOrEmbeddingContainer != null) {
133
                            token = LexerUtilsConstants.token(tokenOrEmbeddingContainer);
134
                            if (tokenList.isContinuous() || token.isFlyweight()) {
135
                                tokenOffset += tokenLength;
136
                            } else { // retrieve offset
137
                                tokenOffset = tokenList.tokenOffset(limitEndIndex);
138
                            }
139
                            tokenLength = token.length();
140
                            limitEndIndex++;
141
                        } else { // no more tokens => break
142
                            break;
143
                        }
144
                    }
145
146
                } else { // end index within existing tokens
147
                    // The offset is within the currently recognized tokens
148
                    // Use binary search
149
                    int low = 0;
150
                    limitEndIndex--;
151
152
                    while (low <= limitEndIndex) {
153
                        int mid = (low + limitEndIndex) / 2;
154
                        int midStartOffset = tokenList.tokenOffset(mid);
155
156
                        if (midStartOffset < limitEndOffset) {
157
                            low = mid + 1;
158
                        } else if (midStartOffset > limitEndOffset) {
159
                            limitEndIndex = mid - 1;
160
                        } else { // Token starting exactly at offset found
161
                            limitEndIndex = mid - 1;
162
                            break;
163
                        }
164
                    }
165
                    limitEndIndex++; // Increase from 'high' to end index
166
                }
167
            }
122
            }
168
        }
123
        }
169
            
124
            
170
        // Compute limitStartIndex (currently == 0)
125
        // Compute limitStartIndex (currently == 0)
171
        if (limitEndIndex > 0 && limitStartOffset > 0) {
126
        if (limitEndIndex > 0 && limitStartOffset > 0) {
172
            int high = limitEndIndex - 1;
127
            // Although the binary search could only be in <0,limitEndIndex> bounds
173
            while (limitStartIndex <= high) {
128
            // use regular TL.tokenIndex() because it has substantially better performance
174
                int mid = (limitStartIndex + high) / 2;
129
            // e.g. in JoinTokenList.
175
                int midStartOffset = tokenList.tokenOffset(mid);
130
            int[] indexAndTokenOffset = tokenList.tokenIndex(limitStartOffset);
176
131
            limitStartIndex = indexAndTokenOffset[0];
177
                if (midStartOffset < limitStartOffset) {
132
            // Check if the limitStartOffset is not in gap after end of token at limitStartIndex
178
                    limitStartIndex = mid + 1;
133
            if (limitStartIndex < tokenList.tokenCountCurrent() &&
179
                } else if (midStartOffset > limitStartOffset) {
134
                indexAndTokenOffset[1] + tokenList.tokenOrEmbedding(limitStartIndex).token().length() <= limitStartOffset
180
                    high = mid - 1;
181
                } else { // Token starting exactly at offset found
182
                    limitStartIndex = mid + 1;
183
                    break;
184
                }
185
            }
186
            // Include previous token if it "includes" limitStartOffset (also handles gaps between tokens properly)
187
            if (limitStartIndex > 0 &&
188
                    tokenList.tokenOffset(limitStartIndex - 1) + token(limitStartIndex - 1).length() > limitStartOffset
189
            ) {
135
            ) {
190
                limitStartIndex--;
136
                limitStartIndex++;
191
            }
137
            }
192
        }
138
        }
193
    }
139
    }
Lines 204-225 Link Here
204
        return limitEndOffset;
150
        return limitEndOffset;
205
    }
151
    }
206
    
152
    
207
    public Object tokenOrEmbeddingContainer(int index) {
153
    public TokenOrEmbedding<T> tokenOrEmbedding(int index) {
208
        index += limitStartIndex;
154
        index += limitStartIndex;
209
        return (index < limitEndIndex)
155
        return (index < limitEndIndex)
210
            ? tokenList.tokenOrEmbeddingContainer(index)
156
            ? tokenList.tokenOrEmbedding(index)
211
            : null;
157
            : null;
212
    }
158
    }
213
159
214
    public int tokenOffset(int index) {
160
    public int tokenOffsetByIndex(int index) {
215
        index += limitStartIndex;
161
        index += limitStartIndex;
216
        if (index >= limitEndIndex)
162
        if (index >= limitEndIndex)
217
            throw new IndexOutOfBoundsException("index=" + index + " >= limitEndIndex=" + limitEndIndex);
163
            throw new IndexOutOfBoundsException("index=" + index + " >= limitEndIndex=" + limitEndIndex);
218
        return tokenList.tokenOffset(index);
164
        return tokenList.tokenOffsetByIndex(index);
165
    }
166
167
    public int[] tokenIndex(int offset) {
168
        return LexerUtilsConstants.tokenIndexBinSearch(this, offset, tokenCountCurrent());
219
    }
169
    }
220
170
221
    public int tokenCount() {
171
    public int tokenCount() {
222
        return limitEndIndex - limitStartIndex;
172
        return tokenCountCurrent();
223
    }
173
    }
224
174
225
    public int tokenCountCurrent() {
175
    public int tokenCountCurrent() {
Lines 238-257 Link Here
238
        return tokenList.languagePath();
188
        return tokenList.languagePath();
239
    }
189
    }
240
190
241
    public int childTokenOffset(int rawOffset) {
191
    public int tokenOffset(AbstractToken<T> token) {
242
        throw new IllegalStateException("Unexpected call.");
192
        return tokenList.tokenOffset(token);
243
    }
244
245
    public char childTokenCharAt(int rawOffset, int index) {
246
        throw new IllegalStateException("Unexpected call.");
247
    }
193
    }
248
194
249
    public void wrapToken(int index, EmbeddingContainer<T> embeddingContainer) {
195
    public void wrapToken(int index, EmbeddingContainer<T> embeddingContainer) {
250
        tokenList.wrapToken(limitStartIndex + index, embeddingContainer);
196
        tokenList.wrapToken(limitStartIndex + index, embeddingContainer);
251
    }
197
    }
252
198
253
    public TokenList<?> root() {
199
    public TokenList<?> rootTokenList() {
254
        return tokenList.root();
200
        return tokenList.rootTokenList();
201
    }
202
203
    public CharSequence inputSourceText() {
204
        return rootTokenList().inputSourceText();
255
    }
205
    }
256
206
257
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
207
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
Lines 281-294 Link Here
281
    
231
    
282
    public int startOffset() {
232
    public int startOffset() {
283
        if (tokenCountCurrent() > 0 || tokenCount() > 0)
233
        if (tokenCountCurrent() > 0 || tokenCount() > 0)
284
            return tokenOffset(0);
234
            return tokenOffsetByIndex(0);
285
        return limitStartOffset;
235
        return limitStartOffset;
286
    }
236
    }
287
237
288
    public int endOffset() {
238
    public int endOffset() {
289
        int cntM1 = tokenCount() - 1;
239
        int cntM1 = tokenCount() - 1;
290
        if (cntM1 >= 0)
240
        if (cntM1 >= 0)
291
            return tokenOffset(cntM1) + token(cntM1).length();
241
            return tokenOffsetByIndex(cntM1) + tokenList.tokenOrEmbedding(cntM1).token().length();
292
        return limitStartOffset;
242
        return limitStartOffset;
293
    }
243
    }
294
    
244
    
Lines 296-303 Link Here
296
        return tokenList.isRemoved();
246
        return tokenList.isRemoved();
297
    }
247
    }
298
248
299
    private AbstractToken<T> token(int index) {
300
        return LexerUtilsConstants.token(tokenList, index);
301
    }
302
    
303
}
249
}
(-)a/lexer/src/org/netbeans/lib/lexer/TextLexerInputOperation.java (-45 / +27 lines)
Lines 51-126 Link Here
51
 * @author Miloslav Metelka
51
 * @author Miloslav Metelka
52
 * @version 1.00
52
 * @version 1.00
53
 */
53
 */
54
55
public class TextLexerInputOperation<T extends TokenId> extends LexerInputOperation<T> {
54
public class TextLexerInputOperation<T extends TokenId> extends LexerInputOperation<T> {
56
55
    
57
    /**
56
    /**
58
     * Input text from which the reading of characters is done.
57
     * Input text from which the reading of characters is done.
59
     */
58
     */
60
    private final CharSequence inputText;
59
    private final CharSequence inputSourceText;
61
60
62
    private final int inputTextStartOffset;
63
    
64
    /**
61
    /**
65
     * End of valid chars in readCharArray (points to first invalid char).
62
     * Point beyond which the reading cannot go.
66
     */
63
     */
67
    private int readEndIndex;
64
    private int readEndOffset;
68
    
69
65
70
    public TextLexerInputOperation(TokenList<T> tokenList, CharSequence inputText) {
66
71
        this(tokenList, 0, null, inputText, 0, 0, inputText.length());
67
    public TextLexerInputOperation(TokenList<T> tokenList) {
68
        this(tokenList, 0, null, 0, -1);
72
    }
69
    }
73
70
74
    public TextLexerInputOperation(TokenList<T> tokenList, int tokenIndex,
71
    public TextLexerInputOperation(TokenList<T> tokenList, int tokenIndex,
75
    Object lexerRestartState, CharSequence inputText, int inputTextStartOffset,
72
    Object lexerRestartState, int startOffset, int endOffset) {
76
    int startOffset, int endOffset) {
77
        super(tokenList, tokenIndex, lexerRestartState);
73
        super(tokenList, tokenIndex, lexerRestartState);
78
        this.inputText = inputText;
74
        this.inputSourceText = tokenList.inputSourceText();
79
        this.inputTextStartOffset = inputTextStartOffset;
75
        if (endOffset == -1) {
80
76
            endOffset = inputSourceText.length();
81
        // Make the offsets relative to the input start offset
77
        }
82
        startOffset -= inputTextStartOffset;
83
        endOffset -= inputTextStartOffset;
84
        assert (0 <= startOffset) && (startOffset <= endOffset)
78
        assert (0 <= startOffset) && (startOffset <= endOffset)
85
            && (endOffset <= inputText.length())
79
            && (endOffset <= inputSourceText.length())
86
            : "startOffset=" + startOffset + ", endOffset=" + endOffset
80
            : "startOffset=" + startOffset + ", endOffset=" + endOffset
87
                + ", inputText.length()=" + inputText.length();
81
                + ", inputSourceText.length()=" + inputSourceText.length();
88
        setTokenStartIndex(startOffset);
82
        tokenStartOffset = startOffset;
89
        readEndIndex = endOffset;
83
        readOffset = tokenStartOffset;
84
        readEndOffset = endOffset;
90
    }
85
    }
91
    
86
    
92
    public int read(int index) { // index >= 0 is guaranteed by contract
87
    public int read(int offset) {
93
        index += tokenStartIndex();
88
        if (offset < readEndOffset) {
94
        if (index < readEndIndex) {
89
            return inputSourceText.charAt(offset);
95
            return inputText.charAt(index);
96
        } else { // must read next or return EOF
90
        } else { // must read next or return EOF
97
            return LexerInput.EOF;
91
            return LexerInput.EOF;
98
        }
92
        }
99
    }
93
    }
100
94
101
    public char readExisting(int index) {
95
    public char readExisting(int offset) {
102
        return inputText.charAt(tokenStartIndex() + index);
96
        return inputSourceText.charAt(offset);
103
    }
97
    }
104
98
105
    public void approveToken(AbstractToken<T> token) {
99
    protected void fillTokenData(AbstractToken<T> token) {
106
        if (isSkipToken(token)) {
100
        token.setTokenList(tokenList);
107
            preventFlyToken();
101
        token.setRawOffset(tokenStartOffset);
108
109
        } else if (token.isFlyweight()) {
110
            assert isFlyTokenAllowed();
111
            flyTokenAdded();
112
113
        } else { // non-flyweight token
114
            token.setTokenList(tokenList());
115
            token.setRawOffset(inputTextStartOffset + tokenStartIndex());
116
            clearFlySequence();
117
        }
118
119
        tokenApproved();
120
    }
102
    }
121
103
    
122
    protected final int readEndIndex() {
104
    protected final int readEndIndex() {
123
        return readEndIndex;
105
        return readEndOffset;
124
    }
106
    }
125
107
126
}
108
}
(-)a/lexer/src/org/netbeans/lib/lexer/TokenHierarchyOperation.java (-206 / +80 lines)
Lines 41-46 Link Here
41
41
42
package org.netbeans.lib.lexer;
42
package org.netbeans.lib.lexer;
43
43
44
import java.io.IOException;
44
import java.io.Reader;
45
import java.io.Reader;
45
import java.util.Collections;
46
import java.util.Collections;
46
import java.util.HashMap;
47
import java.util.HashMap;
Lines 55-62 Link Here
55
import org.netbeans.api.lexer.TokenHierarchyEvent;
56
import org.netbeans.api.lexer.TokenHierarchyEvent;
56
import org.netbeans.api.lexer.TokenHierarchyListener;
57
import org.netbeans.api.lexer.TokenHierarchyListener;
57
import org.netbeans.api.lexer.TokenHierarchy;
58
import org.netbeans.api.lexer.TokenHierarchy;
58
import org.netbeans.lib.lexer.batch.CopyTextTokenList;
59
import org.netbeans.lib.lexer.batch.TextTokenList;
60
import org.netbeans.lib.lexer.inc.IncTokenList;
59
import org.netbeans.lib.lexer.inc.IncTokenList;
61
import org.netbeans.lib.lexer.inc.TokenHierarchyEventInfo;
60
import org.netbeans.lib.lexer.inc.TokenHierarchyEventInfo;
62
import org.netbeans.spi.lexer.MutableTextInput;
61
import org.netbeans.spi.lexer.MutableTextInput;
Lines 66-73 Link Here
66
import org.netbeans.api.lexer.TokenId;
65
import org.netbeans.api.lexer.TokenId;
67
import org.netbeans.api.lexer.TokenSequence;
66
import org.netbeans.api.lexer.TokenSequence;
68
import org.netbeans.lib.editor.util.ArrayUtilities;
67
import org.netbeans.lib.editor.util.ArrayUtilities;
68
import org.netbeans.lib.lexer.inc.TokenHierarchyUpdate;
69
import org.netbeans.lib.lexer.inc.TokenListChange;
69
import org.netbeans.lib.lexer.inc.TokenListChange;
70
import org.netbeans.lib.lexer.token.AbstractToken;
71
70
72
/**
71
/**
73
 * Token hierarchy operation services tasks of its associated token hierarchy.
72
 * Token hierarchy operation services tasks of its associated token hierarchy.
Lines 79-86 Link Here
79
 */
78
 */
80
79
81
public final class TokenHierarchyOperation<I, T extends TokenId> { // "I" stands for input
80
public final class TokenHierarchyOperation<I, T extends TokenId> { // "I" stands for input
82
    
81
83
    static final Logger LOG = TokenHierarchyUpdate.LOG;
82
    // -J-Dorg.netbeans.lib.lexer.TokenHierarchyOperation.level=FINE
83
    static final Logger LOG = Logger.getLogger(TokenHierarchyOperation.class.getName());
84
    
84
    
85
    // -J-Dorg.netbeans.spi.lexer.MutableTextInput.level=FINE
85
    // -J-Dorg.netbeans.spi.lexer.MutableTextInput.level=FINE
86
    private static final Logger LOG_LOCK = Logger.getLogger(MutableTextInput.class.getName()); // Logger for read/write-lock
86
    private static final Logger LOG_LOCK = Logger.getLogger(MutableTextInput.class.getName()); // Logger for read/write-lock
Lines 101-108 Link Here
101
     * Mutable text input for mutable token hierarchy or null otherwise.
101
     * Mutable text input for mutable token hierarchy or null otherwise.
102
     */
102
     */
103
    private MutableTextInput<I> mutableTextInput;
103
    private MutableTextInput<I> mutableTextInput;
104
    
104
105
    private TokenList<T> rootTokenList;
105
    /**
106
     * Root token list of this hierarchy. It is created in constructor and never changed
107
     * during the whole lifetime of the token hierarchy.
108
     */
109
    private final TokenList<T> rootTokenList;
106
    
110
    
107
    /**
111
    /**
108
     * The hierarchy can be made inactive to release the tokens
112
     * The hierarchy can be made inactive to release the tokens
Lines 141-147 Link Here
141
     * If a token list list is contained then all its parents
145
     * If a token list list is contained then all its parents
142
     * with the shorter language path are also mandatorily maintained.
146
     * with the shorter language path are also mandatorily maintained.
143
     */
147
     */
144
    private Map<LanguagePath,TokenListList> path2tokenListList;
148
    private Map<LanguagePath,TokenListList<?>> path2tokenListList;
145
    
149
    
146
    private int maxTokenListListPathSize;
150
    private int maxTokenListListPathSize;
147
    
151
    
Lines 159-165 Link Here
159
        @SuppressWarnings("unchecked")
163
        @SuppressWarnings("unchecked")
160
        I input = (I)inputReader;
164
        I input = (I)inputReader;
161
        this.inputSource = input;
165
        this.inputSource = input;
162
        this.rootTokenList = new CopyTextTokenList<T>(this, inputReader,
166
167
        // Instead of using an original CopyTextTokenList that allowed to skip
168
        // individual characters of all flyweight tokens do just a copy of all chars
169
        // from the Reader. TBD - do a lazy reading instead of pre-reading.
170
        char[] chars = new char[LexerUtilsConstants.READER_TEXT_BUFFER_SIZE];
171
        int offset = 0;
172
        try {
173
            while (true) {
174
                int readLen = inputReader.read(chars, offset, chars.length - offset);
175
                if (readLen == -1) // End of stream
176
                    break;
177
                offset += readLen;
178
                if (offset == chars.length) { // Full buffer
179
                    chars = ArrayUtilities.charArray(chars); // Double array size
180
                }
181
            }
182
        } catch (IOException e) {
183
            // Ignored silently - there should be a wrapping reader catching and properly handling
184
            // this IOException.
185
        } finally {
186
            // Attempt to close the Reader
187
            try {
188
                inputReader.close();
189
            } catch (IOException e) {
190
                // Ignored silently - there should be a wrapping reader catching and properly handling
191
                // this IOException.
192
            }
193
        }
194
        String inputText = new String(chars, 0, offset); // Copy of reader's whole text
195
196
        this.rootTokenList = new BatchTokenList<T>(this, inputText,
163
                language, skipTokenIds, inputAttributes);
197
                language, skipTokenIds, inputAttributes);
164
        init();
198
        init();
165
        activity = Activity.ACTIVE;
199
        activity = Activity.ACTIVE;
Lines 178-187 Link Here
178
        @SuppressWarnings("unchecked")
212
        @SuppressWarnings("unchecked")
179
        I input = (I)inputText;
213
        I input = (I)inputText;
180
        this.inputSource = input;
214
        this.inputSource = input;
181
        this.rootTokenList = copyInputText
215
        if (copyInputText) {
182
                ? new CopyTextTokenList<T>(this, inputText,
216
            // Instead of using an original CopyTextTokenList (that allowed to skip
183
                        language, skipTokenIds, inputAttributes)
217
            // individual characters of all flyweight tokens) do just a copy of the full text
184
                : new TextTokenList<T>(this, inputText,
218
            // and use regular BatchTokenList.
219
            inputText = inputText.toString();
220
        }
221
        this.rootTokenList = new BatchTokenList<T>(this, inputText,
185
                        language, skipTokenIds, inputAttributes);
222
                        language, skipTokenIds, inputAttributes);
186
        init();
223
        init();
187
        activity = Activity.ACTIVE;
224
        activity = Activity.ACTIVE;
Lines 260-269 Link Here
260
                    return;
297
                    return;
261
                }
298
                }
262
            } else { // Wishing to be inactive
299
            } else { // Wishing to be inactive
263
                change = new TokenListChange<T>(incTokenList);
300
                change = TokenListChange.createRebuildChange(incTokenList);
264
//                change.setIndex(0);
301
                incTokenList.replaceTokens(change, 0);
265
//                change.setOffset(0);
266
                incTokenList.replaceTokens(change, incTokenList.tokenCountCurrent(), 0);
267
                incTokenList.setLanguagePath(null);
302
                incTokenList.setLanguagePath(null);
268
                incTokenList.reinit();
303
                incTokenList.reinit();
269
            }
304
            }
Lines 354-360 Link Here
354
        ensureReadLocked();
389
        ensureReadLocked();
355
        synchronized (rootTokenList) {
390
        synchronized (rootTokenList) {
356
            return isActiveImpl()
391
            return isActiveImpl()
357
                ? new TokenSequenceList(this, languagePath, startOffset, endOffset)
392
                ? new TokenSequenceList(rootTokenList, languagePath, startOffset, endOffset)
358
                : null;
393
                : null;
359
        }
394
        }
360
    }
395
    }
Lines 364-374 Link Here
364
     * <br/>
399
     * <br/>
365
     * If the list needs to be created or it was non-mandatory.
400
     * If the list needs to be created or it was non-mandatory.
366
     */
401
     */
367
    public TokenListList tokenListList(LanguagePath languagePath) {
402
    public <ET extends TokenId> TokenListList<ET> tokenListList(LanguagePath languagePath) {
368
        assert isActiveNoInit() : "Token hierarchy expected to be active.";
403
        assert isActiveNoInit() : "Token hierarchy expected to be active.";
369
        TokenListList tll = path2tokenListList().get(languagePath);
404
        @SuppressWarnings("unchecked")
405
        TokenListList<ET> tll = (TokenListList<ET>) path2tokenListList().get(languagePath);
370
        if (tll == null) {
406
        if (tll == null) {
371
            tll = new TokenListList(this, languagePath);
407
            tll = new TokenListList<ET>(rootTokenList, languagePath);
372
            path2tokenListList.put(languagePath, tll);
408
            path2tokenListList.put(languagePath, tll);
373
            maxTokenListListPathSize = Math.max(languagePath.size(), maxTokenListListPathSize);
409
            maxTokenListListPathSize = Math.max(languagePath.size(), maxTokenListListPathSize);
374
            // Also create parent token list lists if they don't exist yet
410
            // Also create parent token list lists if they don't exist yet
Lines 379-401 Link Here
379
        return tll;
415
        return tll;
380
    }
416
    }
381
    
417
    
382
    private Map<LanguagePath,TokenListList> path2tokenListList() {
418
    /**
419
     * Get existing token list list or null if the TLL does not exist yet.
420
     */
421
    public <ET extends TokenId> TokenListList<ET> existingTokenListList(LanguagePath languagePath) {
422
        synchronized (rootTokenList()) {
423
            @SuppressWarnings("unchecked")
424
            TokenListList<ET> tll = (path2tokenListList != null) 
425
                    ? (TokenListList<ET>) path2tokenListList.get(languagePath)
426
                    : null;
427
            return tll;
428
        }
429
    }
430
431
    private Map<LanguagePath,TokenListList<?>> path2tokenListList() {
383
        if (path2tokenListList == null) {
432
        if (path2tokenListList == null) {
384
            path2tokenListList = new HashMap<LanguagePath,TokenListList>(4, 0.5f);
433
            path2tokenListList = new HashMap<LanguagePath,TokenListList<?>>(4, 0.5f);
385
        }
434
        }
386
        return path2tokenListList;
435
        return path2tokenListList;
387
    }
436
    }
388
    
437
    
389
    /**
438
    public int maxTokenListListPathSize() {
390
     * Get existing token list list or null if the TLL does not exist yet.
391
     */
392
    public TokenListList existingTokenListList(LanguagePath languagePath) {
393
        synchronized (rootTokenList()) {
394
            return (path2tokenListList != null) ? path2tokenListList.get(languagePath) : null;
395
        }
396
    }
397
398
    int maxTokenListListPathSize() {
399
        return maxTokenListListPathSize;
439
        return maxTokenListListPathSize;
400
    }
440
    }
401
441
Lines 405-419 Link Here
405
            if (isActiveNoInit()) {
445
            if (isActiveNoInit()) {
406
                IncTokenList<T> incTokenList = (IncTokenList<T>)rootTokenList;
446
                IncTokenList<T> incTokenList = (IncTokenList<T>)rootTokenList;
407
                incTokenList.incrementModCount();
447
                incTokenList.incrementModCount();
408
                TokenListChange<T> change = new TokenListChange<T>(incTokenList);
409
                CharSequence text = LexerSpiPackageAccessor.get().text(mutableTextInput);
448
                CharSequence text = LexerSpiPackageAccessor.get().text(mutableTextInput);
410
                TokenHierarchyEventInfo eventInfo = new TokenHierarchyEventInfo(
449
                TokenHierarchyEventInfo eventInfo = new TokenHierarchyEventInfo(
411
                    this, TokenHierarchyEventType.REBUILD, 0, 0, "", 0);
450
                    this, TokenHierarchyEventType.REBUILD, 0, 0, "", 0);
412
                change.setIndex(0);
451
                TokenListChange<T> change = TokenListChange.createRebuildChange(incTokenList);
413
                change.setOffset(0);
452
                incTokenList.replaceTokens(change, 0);
414
                change.setAddedEndOffset(0); // Tokens will be recreated lazily
415
416
                incTokenList.replaceTokens(change, incTokenList.tokenCountCurrent(), 0);
417
                incTokenList.reinit(); // Will relex tokens lazily
453
                incTokenList.reinit(); // Will relex tokens lazily
418
454
419
                eventInfo.setTokenChangeInfo(change.tokenChangeInfo());
455
                eventInfo.setTokenChangeInfo(change.tokenChangeInfo());
Lines 453-507 Link Here
453
            TokenHierarchyEventInfo eventInfo = new TokenHierarchyEventInfo(
489
            TokenHierarchyEventInfo eventInfo = new TokenHierarchyEventInfo(
454
                    this, TokenHierarchyEventType.MODIFICATION,
490
                    this, TokenHierarchyEventType.MODIFICATION,
455
                    offset, removedLength, removedText, insertedLength);
491
                    offset, removedLength, removedText, insertedLength);
456
            // First a top-level token list will be updated then the embedded ones.
492
            new TokenHierarchyUpdate(eventInfo).update();
457
            IncTokenList<T> incTokenList = (IncTokenList<T>)rootTokenList;
458
459
            if (LOG.isLoggable(Level.FINEST)) {
460
                // Display current state of the hierarchy by faking its text
461
                // through original text
462
                CharSequence text = incTokenList.text();
463
                assert (text != null);
464
                incTokenList.setText(eventInfo.originalText());
465
                // Dump all contents
466
                LOG.finest(toString());
467
                // Return the original text
468
                incTokenList.setText(text);
469
            }
470
            
471
            if (LOG.isLoggable(Level.FINE)) {
472
                StringBuilder sb = new StringBuilder(150);
473
                sb.append("<<<<<<<<<<<<<<<<<< LEXER CHANGE START ------------------\n"); // NOI18N
474
                sb.append(eventInfo.modificationDescription(false));
475
                TokenHierarchyUpdate.LOG.fine(sb.toString());
476
            }
477
478
            new TokenHierarchyUpdate(eventInfo).update(incTokenList);
479
            
480
            if (LOG.isLoggable(Level.FINE)) {
481
                LOG.fine("AFFECTED: " + eventInfo.dumpAffected() + "\n"); // NOI18N
482
                String extraMsg = "";
483
                if (LOG.isLoggable(Level.FINER)) {
484
                    // Check consistency of the whole token hierarchy
485
                    String error = checkConsistency();
486
                    if (error != null) {
487
                        String msg = "!!!CONSISTENCY-ERROR!!!: " + error + "\n";
488
                        if (LOG.isLoggable(Level.FINEST)) {
489
                            throw new IllegalStateException(msg);
490
                        } else {
491
                            LOG.finer(msg);
492
                        }
493
                    } else {
494
                        extraMsg = "(TokenHierarchy Check OK) ";
495
                    }
496
                }
497
                LOG.fine(">>>>>>>>>>>>>>>>>> LEXER CHANGE END " + extraMsg + "------------------\n"); // NOI18N
498
            }
499
500
            if (LOG.isLoggable(Level.FINEST)) {
501
                LOG.finest("AFTER UPDATE:\n");
502
                LOG.finest(toString());
503
            }
504
505
            fireTokenHierarchyChanged(eventInfo);
493
            fireTokenHierarchyChanged(eventInfo);
506
        }
494
        }
507
    }
495
    }
Lines 553-559 Link Here
553
//
541
//
554
//    private void checkIsSnapshot() {
542
//    private void checkIsSnapshot() {
555
//        if (!isSnapshot()) {
543
//        if (!isSnapshot()) {
556
//            throw new IllegalStateException("Not a snapshot");
544
//            throw new IllegalStateException("Not a snapshot");    
557
//        }
545
//        }
558
//    }
546
//    }
559
//
547
//
Lines 681-697 Link Here
681
     */
669
     */
682
    public String checkConsistency() {
670
    public String checkConsistency() {
683
        // Check root token list first
671
        // Check root token list first
684
        String error = checkConsistencyTokenList(rootTokenList(), ArrayUtilities.emptyIntArray(), 0);
672
        String error = LexerUtilsConstants.checkConsistencyTokenList(rootTokenList(), true);
685
        // Check token-list lists
673
        // Check token-list lists
686
        if (error == null && path2tokenListList != null) {
674
        if (error == null && path2tokenListList != null) {
687
            for (TokenListList tll : path2tokenListList.values()) {
675
            for (TokenListList<?> tll : path2tokenListList.values()) {
688
                // Check token-list list consistency
676
                // Check token-list list consistency
689
                error = tll.checkConsistency();
677
                error = tll.checkConsistency();
690
                if (error != null)
678
                if (error != null)
691
                    return error;
679
                    return error;
692
                // Check each individual token list in token-list list
680
                // Check each individual token list in token-list list
693
                for (TokenList<?> tl : tll) {
681
                for (TokenList<?> tl : tll) {
694
                    error = checkConsistencyTokenList(tl, ArrayUtilities.emptyIntArray(), tl.startOffset());
682
                    error = LexerUtilsConstants.checkConsistencyTokenList(tl, false);
695
                    if (error != null) {
683
                    if (error != null) {
696
                        return error;
684
                        return error;
697
                    }
685
                    }
Lines 699-818 Link Here
699
            }
687
            }
700
        }
688
        }
701
        return error;
689
        return error;
702
    }
703
    
704
    private String checkConsistencyTokenList(TokenList<?> tokenList,
705
    int[] parentIndexes, int firstTokenOffset) {
706
        int tokenCountCurrent = tokenList.tokenCountCurrent();
707
        int[] indexes = ArrayUtilities.intArray(parentIndexes, parentIndexes.length + 1);
708
        boolean continuous = tokenList.isContinuous();
709
        int lastOffset = firstTokenOffset;
710
        for (int i = 0; i < tokenCountCurrent; i++) {
711
            Object tokenOrEmbeddingContainer = tokenList.tokenOrEmbeddingContainer(i);
712
            if (tokenOrEmbeddingContainer == null) {
713
                return dumpContext("Null token", tokenList, i, parentIndexes); // NOI18N
714
            }
715
            AbstractToken<?> token = LexerUtilsConstants.token(tokenOrEmbeddingContainer);
716
            // Check whether tokenList.startOffset() corresponds to the start of first token
717
            if (i == 0 && continuous && tokenCountCurrent > 0 && !token.isFlyweight()) {
718
                if (token.offset(null) != tokenList.startOffset()) {
719
                    return dumpContext("firstToken.offset()=" + token.offset(null) +
720
                            " != tokenList.startOffset()=" + tokenList.startOffset(),
721
                            tokenList, i, parentIndexes);
722
                }
723
            }
724
            if (!token.isFlyweight() && token.tokenList() != tokenList) {
725
                return dumpContext("Invalid token.tokenList()=" + token.tokenList(),
726
                        tokenList, i, parentIndexes);
727
            }
728
            if (token.text() == null) {
729
                return dumpContext("Null token.text()=" + token.tokenList(),
730
                        tokenList, i, parentIndexes);
731
            }
732
            int offset = (token.isFlyweight()) ? lastOffset : token.offset(null);
733
            if (offset < 0) {
734
                return dumpContext("Token offset=" + offset + " < 0", tokenList, i, parentIndexes); // NOI18N
735
            }
736
            if (offset < lastOffset) {
737
                return dumpContext("Token offset=" + offset + " < lastOffset=" + lastOffset,
738
                        tokenList, i, parentIndexes);
739
            }
740
            if (offset > lastOffset && continuous) {
741
                return dumpContext("Gap between tokens; offset=" + offset + ", lastOffset=" + lastOffset,
742
                        tokenList, i, parentIndexes);
743
            }
744
            lastOffset = offset + token.length();
745
            if (tokenOrEmbeddingContainer.getClass() == EmbeddingContainer.class) {
746
                EmbeddingContainer<?> ec = (EmbeddingContainer<?>)tokenOrEmbeddingContainer;
747
                EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
748
                while (etl != null) {
749
                    String error = checkConsistencyTokenList(etl, indexes, offset + etl.embedding().startSkipLength());
750
                    if (error != null)
751
                        return error;
752
                    etl = etl.nextEmbeddedTokenList();
753
                }
754
            }
755
        }
756
        return null;
757
    }
758
    
759
    private String dumpContext(String msg, TokenList<?> tokenList, int index, int[] parentIndexes) {
760
        StringBuilder sb = new StringBuilder();
761
        sb.append(msg);
762
        sb.append(" at index="); // NOI18N
763
        sb.append(index);
764
        sb.append(" of tokens of language "); // NOI18N
765
        sb.append(tokenList.languagePath().innerLanguage().mimeType());
766
        sb.append('\n');
767
        LexerUtilsConstants.appendTokenList(sb, tokenList, index, index - 2, index + 3, false, 0);
768
        sb.append("\nParents:\n"); // NOI18N
769
        sb.append(tracePath(parentIndexes, tokenList.languagePath()));
770
        return sb.toString();
771
    }
772
    
773
    public String findTokenContext(AbstractToken<?> token) {
774
        return findTokenContext(token, rootTokenList(), ArrayUtilities.emptyIntArray());
775
    }
776
777
    private String findTokenContext(AbstractToken<?> token, TokenList<?> tokenList, int[] parentIndexes) {
778
        int tokenCountCurrent = tokenList.tokenCountCurrent();
779
        int[] indexes = ArrayUtilities.intArray(parentIndexes, parentIndexes.length + 1);
780
        for (int i = 0; i < tokenCountCurrent; i++) {
781
            Object tokenOrEmbeddingContainer = tokenList.tokenOrEmbeddingContainer(i);
782
            if (tokenOrEmbeddingContainer == null) {
783
                continue;
784
            }
785
            if (tokenOrEmbeddingContainer.getClass() == EmbeddingContainer.class) {
786
                EmbeddingContainer<?> ec = (EmbeddingContainer<?>)tokenOrEmbeddingContainer;
787
                if (ec.token() == token) {
788
                    return dumpContext("Token found.", tokenList, i, indexes);
789
                }
790
                EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
791
                while (etl != null) {
792
                    String context = findTokenContext(token, etl, indexes);
793
                    if (context != null)
794
                        return context;
795
                    etl = etl.nextEmbeddedTokenList();
796
                }
797
798
            } else if (tokenOrEmbeddingContainer == token) {
799
                return dumpContext("Token found.", tokenList, i, indexes);
800
            }
801
        }
802
        return null;
803
    }
804
805
    private String tracePath(int[] indexes, LanguagePath languagePath) {
806
        StringBuilder sb  = new StringBuilder();
807
        TokenList<?> tokenList = rootTokenList();
808
        for (int i = 0; i < indexes.length; i++) {
809
            LexerUtilsConstants.appendTokenInfo(sb, tokenList, i,
810
                    tokenHierarchy(), false, 0);
811
            // Assign language to variable to get rid of javac bug for incremental compilation on 1.5 
812
            Language<?> language = languagePath.language(i);
813
            tokenList = EmbeddingContainer.embeddedTokenList(tokenList, indexes[i], language);
814
        }
815
        return sb.toString();
816
    }
690
    }
817
    
691
    
818
//    private final class SnapshotRef extends WeakReference<TokenHierarchyOperation<I,T>> implements Runnable {
692
//    private final class SnapshotRef extends WeakReference<TokenHierarchyOperation<I,T>> implements Runnable {
(-)a/lexer/src/org/netbeans/lib/lexer/TokenHierarchyUpdate.java (-628 lines)
Removed Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer;
43
44
import java.util.ArrayList;
45
import java.util.Collections;
46
import java.util.HashMap;
47
import java.util.List;
48
import java.util.Map;
49
import java.util.logging.Level;
50
import java.util.logging.Logger;
51
import org.netbeans.api.lexer.LanguagePath;
52
import org.netbeans.api.lexer.TokenId;
53
import org.netbeans.lib.lexer.inc.IncTokenList;
54
import org.netbeans.lib.lexer.inc.MutableTokenList;
55
import org.netbeans.lib.lexer.inc.TokenHierarchyEventInfo;
56
import org.netbeans.lib.lexer.inc.TokenListChange;
57
import org.netbeans.lib.lexer.inc.TokenListUpdater;
58
import static org.netbeans.lib.lexer.LexerUtilsConstants.INVALID_STATE;
59
60
/**
61
 * Request for updating of token hierarchy after text modification
62
 * or custom embedding creation/removal.
63
 * <br/>
64
 * This class contains all the data and methods related to updating.
65
 *
66
 * @author Miloslav Metelka
67
 */
68
69
public final class TokenHierarchyUpdate {
70
    
71
    // -J-Dorg.netbeans.lib.lexer.TokenHierarchyUpdate.level=FINE
72
    static final Logger LOG = Logger.getLogger(TokenHierarchyUpdate.class.getName());
73
74
    final TokenHierarchyEventInfo eventInfo;
75
    
76
    private Map<LanguagePath,TLLInfo> path2info;
77
    
78
    /**
79
     * Infos ordered from higher top levels of the hierarchy to lower levels.
80
     * Useful for top-down updating at the end.
81
     */
82
    private List<List<TLLInfo>> levelInfos;
83
    
84
    TokenListChange<?> rootChange;
85
86
    public TokenHierarchyUpdate(TokenHierarchyEventInfo eventInfo) {
87
        this.eventInfo = eventInfo;
88
    }
89
90
    public void update(IncTokenList<?> incTokenList) {
91
        incTokenList.incrementModCount();
92
        // Update top-level token list first
93
        // It does not need any updateStatusImpl() since it's only for embedded token lists
94
        rootChange = updateTokenListByModification(incTokenList, null);
95
        eventInfo.setTokenChangeInfo(rootChange.tokenChangeInfo());
96
97
        if (LOG.isLoggable(Level.FINE)) {
98
            LOG.fine("ROOT CHANGE: " + rootChange.toString(0) + "\n"); // NOI18N
99
        }
100
101
        // If there is an active lexer input operation (for not-yet finished
102
        // top-level token list lexing) refresh it because it would be damaged
103
        // by the performed token list update
104
        if (!incTokenList.isFullyLexed()) {
105
            incTokenList.refreshLexerInputOperation();
106
        }
107
108
        // Now the update goes to possibly embedded token list lists
109
        // based on the top-level change. If there are embeddings that join sections
110
        // this becomes a fairly complex thing.
111
        // 1. The updating must always go from upper levels to lower levels of the token hierarchy
112
        //    to ensure that the tokens of the possible joined embeddings get updated properly
113
        //    as the tokens created/removed at upper levels may contain embeddings that will
114
        //    need to be added/removed from token list lists on lower level.
115
        // 2. A single insert/remove may produce token updates at several
116
        //    places in the document. A top-level change of token with embedding
117
        //    will request the embedded token list update and that token list
118
        //    may be connected with another joined token list(s) with the same language path
119
        //    and the update may continue into these joined token lists.
120
121
        // 3. The algorithm must collect both removed and added token lists
122
        //    in the TLLInfo.
123
        // 4. For a removed token list the updating must check nested embedded token lists
124
        //    because some embedded tokens of the removed embedded token list might contain
125
        //    another embedding that might also be maintained as token list list
126
        //    and need to be updated.
127
        // 5. The parent token list lists
128
        //    are always maintained too which simplifies the updating algorithm
129
        //    and speeds it up because the token list list marks whether it has any children
130
        //    or not and so the deep traversing only occurs if there are any children present.
131
        // 6. Additions may produce nested additions too so they need to be makred
132
        //    similarly to removals.
133
        if (rootChange.isBoundsChange()) {
134
            processBoundsChangeEmbeddings(rootChange, null);
135
        } else {
136
            // Mark changed area based on start of first mod.token and end of last mod.token
137
            // of the root-level change
138
            eventInfo.setMinAffectedStartOffset(rootChange.offset());
139
            eventInfo.setMaxAffectedEndOffset(rootChange.addedEndOffset());
140
            processNonBoundsChange(rootChange);
141
        }
142
143
        processLevelInfos();
144
    }
145
    
146
    public void updateCreateEmbedding(EmbeddedTokenList<?> addedTokenList) {
147
        TLLInfo info = info(addedTokenList.languagePath());
148
        if (info != NO_INFO) {
149
            if (LOG.isLoggable(Level.FINE)) {
150
                LOG.fine("THU.updateCreateEmbedding(): " + addedTokenList.toStringHeader());
151
            }
152
            info.markAdded(addedTokenList);
153
            processLevelInfos();
154
        }
155
    }
156
    
157
    /**
158
     * update-status must be called by the caller.
159
     * @param removedTokenList token list removed by TS.removeEmbedding().
160
     */
161
    public void updateRemoveEmbedding(EmbeddedTokenList<?> removedTokenList) {
162
        TLLInfo info = info(removedTokenList.languagePath());
163
        if (info != NO_INFO) {
164
            if (LOG.isLoggable(Level.FINE)) {
165
                LOG.fine("THU.updateRemoveEmbedding(): " + removedTokenList.toStringHeader());
166
            }
167
            // update-status called by caller.
168
            info.markRemoved(removedTokenList);
169
            processLevelInfos();
170
        }
171
    }
172
    
173
    void processBoundsChangeEmbeddings(TokenListChange<?> change, TokenListChange<?> parentChange) {
174
        // Add an embedded change to the parent change (if exists)
175
        if (parentChange != null) {
176
            parentChange.tokenChangeInfo().addEmbeddedChange(change.tokenChangeInfo());
177
        }
178
        Object tokenOrEC = change.tokenChangeInfo().removedTokenList().tokenOrEmbeddingContainer(0);
179
        if (tokenOrEC.getClass() == EmbeddingContainer.class) {
180
            TLLInfo info;
181
            boolean hasChildren;
182
            if (change.languagePath().size() > 1) {
183
                info = info(change.languagePath());
184
                hasChildren = (info != NO_INFO) ? info.tokenListList().hasChildren() : false;
185
            } else { // root-level
186
                info = NO_INFO;
187
                hasChildren = (eventInfo.tokenHierarchyOperation().maxTokenListListPathSize() > 0);
188
            }
189
            EmbeddingContainer<?> ec = (EmbeddingContainer<?>)tokenOrEC;
190
            rewrapECToken(ec, change); // Includes updateStatusImpl()
191
            EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
192
            if (etl != null && etl != EmbeddedTokenList.NO_DEFAULT_EMBEDDING) {
193
                // Check the text length beyond modification => end skip length must not be affected
194
                int modRelOffset = eventInfo.modificationOffset() - change.offset();
195
                int beyondModLength = change.addedEndOffset() - (eventInfo.modificationOffset() + eventInfo.diffLengthOrZero());
196
                EmbeddedTokenList<?> prevEtl = null;
197
                do {
198
                    TLLInfo childInfo = hasChildren ? info(etl.languagePath()) : NO_INFO;
199
                    // Check whether the change was not in the start or end skip lengths
200
                    // and if so then remove the embedding
201
                    if (modRelOffset >= etl.embedding().startSkipLength()
202
                            && beyondModLength >= etl.embedding().endSkipLength()
203
                    ) { // Modification within embedding's bounds => embedding can stay
204
                        // Mark that the embedding should be updated
205
                        if (childInfo != NO_INFO) {
206
                            // update-status called by rewrap-ec-token above
207
                            childInfo.markBoundsChange(etl);
208
                        } else { // No child but want to update nested possible bounds changes
209
                            if (etl.isInited()) {
210
                                parentChange = change;
211
                                // Perform change in child - it surely does not join the sections
212
                                // since otherwise the childInfo could not be null
213
                                // update-status done above for the embedding container
214
                                change = updateTokenListByModification(etl, null);
215
                                if (change.isBoundsChange()) {
216
                                    processBoundsChangeEmbeddings(change, parentChange);
217
                                } else {
218
                                    eventInfo.setMinAffectedStartOffset(change.offset());
219
                                    eventInfo.setMaxAffectedEndOffset(change.addedEndOffset());
220
                                }
221
                            }
222
                        }
223
                        prevEtl = etl;
224
                        etl = etl.nextEmbeddedTokenList();
225
226
                    } else { // Mod in skip lengths => Remove the etl from chain
227
                        if (childInfo != NO_INFO) {
228
                            // update-status already done as part of rewrap-token
229
                            childInfo.markRemoved(etl);
230
                        }
231
                        // Remove embedding and get the next embedded token list (prevEtl stays the same)
232
                        etl = ec.removeEmbeddedTokenList(prevEtl, etl);
233
                    }
234
                } while (etl != null && etl != EmbeddedTokenList.NO_DEFAULT_EMBEDDING);
235
            }
236
        } 
237
    }
238
    
239
    void processNonBoundsChange(TokenListChange<?> change) {
240
        TLLInfo info;
241
        boolean hasChildren;
242
        if (change.languagePath().size() >= 2) {
243
            info = info(change.languagePath());
244
            hasChildren = (info != NO_INFO && info.tokenListList().hasChildren());
245
        } else { // root change
246
            info = NO_INFO;
247
            hasChildren = (eventInfo.tokenHierarchyOperation().maxTokenListListPathSize() > 0);
248
        }
249
        if (hasChildren) {
250
            // First mark the removed embeddings
251
            TokenList<?> removedTokenList = change.tokenChangeInfo().removedTokenList();
252
            if (removedTokenList != null) {
253
                markRemovedEmbeddings(removedTokenList);
254
            }
255
256
            // Now mark added embeddings
257
            TokenList<?> currentTokenList = change.tokenChangeInfo().currentTokenList();
258
            markAddedEmbeddings(currentTokenList, change.index(), change.addedTokensOrBranchesCount());
259
        }
260
    }
261
    
262
    /**
263
     * Collect removed embeddings for the given token list recursively
264
     * and nest deep enough for all maintained children
265
     * token list lists.
266
     */
267
    private void markRemovedEmbeddings(TokenList<?> removedTokenList) {
268
        int tokenCount = removedTokenList.tokenCountCurrent();
269
        for (int i = 0; i < tokenCount; i++) {
270
            Object tokenOrEC = removedTokenList.tokenOrEmbeddingContainer(i);
271
            if (tokenOrEC.getClass() == EmbeddingContainer.class) {
272
                EmbeddingContainer<?> ec = (EmbeddingContainer<?>)tokenOrEC;
273
                ec.updateStatusImpl(); // Update status since markRemoved() will need it
274
                EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
275
                while (etl != null && etl != EmbeddedTokenList.NO_DEFAULT_EMBEDDING) {
276
                    TLLInfo info = info(etl.languagePath());
277
                    if (info != NO_INFO) {
278
                        // update-status called above
279
                        info.markRemoved(etl);
280
                    }
281
                    etl = etl.nextEmbeddedTokenList();
282
                }
283
            }
284
        }
285
    }
286
    
287
    private void markAddedEmbeddings(TokenList<?> tokenList, int index, int addedCount) {
288
        for (int i = 0; i < addedCount; i++) {
289
            // Ensure that the default embedding gets possibly created
290
            EmbeddedTokenList<?> etl = EmbeddingContainer.embeddedTokenList(tokenList, index + i, null);
291
            if (etl != null) {
292
                TLLInfo info = info(etl.languagePath());
293
                if (info != NO_INFO) {
294
                    // Mark that there was a new embedded token list added
295
                    // There should be no updateStatusImpl() necessary since the token lists are new
296
                    // and the parent embedding container was surely updated by the updating process.
297
                    info.markAdded(etl);
298
                }
299
            }
300
        }
301
    }
302
303
    private void processLevelInfos() {
304
        // Now relex the changes in affected token list lists
305
        // i.e. fix the tokens after the token lists removals/additions.
306
        // The higher-level updates
307
        if (levelInfos != null) {
308
            // The list can be extended by additional items dynamically during iteration
309
            for (int i = 0; i < levelInfos.size(); i++) {
310
                List<TLLInfo> infos = levelInfos.get(i);
311
                // The "infos" list should not be extended by additional items dynamically during iteration
312
                // However an extra items can be added at the deeper levels.
313
                for (int j = 0; j < infos.size(); j++) {
314
                    infos.get(j).update();
315
                }
316
            }
317
        }
318
        
319
        // Assert that update was called on all infos
320
        if (LOG.isLoggable(Level.FINER) && levelInfos != null) {
321
            for (List<TLLInfo> infos : levelInfos) {
322
                for (TLLInfo info : infos) {
323
                    if (!info.updateCalled) {
324
                        throw new IllegalStateException("Update not called on tokenListList\n" + // NOI18N
325
                                info.tokenListList);
326
                    }
327
                }
328
            }
329
        }
330
    }
331
    
332
    <T extends TokenId> TokenListChange<T> updateTokenListByModification(
333
    MutableTokenList<T> tokenList, Object zeroIndexRelexState) {
334
        TokenListChange<T> change = new TokenListChange<T>(tokenList);
335
//        if (tokenList instanceof EmbeddedTokenList) {
336
//            ((EmbeddedTokenList)tokenList).embeddingContainer().checkStatusUpdated();
337
//        }
338
        TokenListUpdater.update(tokenList, eventInfo.modificationOffset(),
339
                eventInfo.insertedLength(), eventInfo.removedLength(), change, zeroIndexRelexState);
340
        return change;
341
    }
342
343
    /**
344
     * Return tll info or NO_INFO if the token list list is not maintained
345
     * for the given language path.
346
     */
347
    private TLLInfo info(LanguagePath languagePath) {
348
        if (path2info == null) { // Init since it will contain NO_INFO
349
            path2info = new HashMap<LanguagePath,TLLInfo>(4, 0.5f);
350
        }
351
        TLLInfo info = path2info.get(languagePath);
352
        if (info == null) {
353
            TokenListList tll = eventInfo.tokenHierarchyOperation().existingTokenListList(languagePath);
354
            if (tll != null) {
355
                info = new TLLInfo(this, tll);
356
                int index = languagePath.size() - 2;
357
                if (levelInfos == null) {
358
                    levelInfos = new ArrayList<List<TLLInfo>>(index + 1);
359
                }
360
                while (levelInfos.size() <= index) {
361
                    levelInfos.add(new ArrayList<TLLInfo>(2));
362
                }
363
                levelInfos.get(index).add(info);
364
            } else { // No token list list for the given language path
365
                info = NO_INFO;
366
            }
367
            path2info.put(languagePath, info);
368
        }
369
        return info;
370
    }
371
    
372
    private <T extends TokenId> void rewrapECToken(EmbeddingContainer<T> ec, TokenListChange<?> change) {
373
        @SuppressWarnings("unchecked")
374
        TokenListChange<T> tChange = (TokenListChange<T>)change;
375
        ec.reinit(tChange.addedToken(0));
376
        ec.updateStatusImpl();
377
        tChange.tokenList().wrapToken(tChange.index(), ec);
378
    }
379
380
    /**
381
     * Special constant value to avoid double map search for token list lists updating.
382
     */
383
    static final TLLInfo NO_INFO = new TLLInfo(null, null);
384
        
385
    /**
386
     * Information about update in a single token list list.
387
     */
388
    static final class TLLInfo {
389
        
390
        final TokenHierarchyUpdate update;
391
        
392
        final TokenListList tokenListList;
393
394
        int index;
395
396
        int removeCount;
397
398
        List<TokenList<?>> added;
399
        
400
        TokenListChange<?> change;
401
        
402
        boolean updateCalled;
403
        
404
        public TLLInfo(TokenHierarchyUpdate update, TokenListList tokenListList) {
405
            this.update = update;
406
            this.tokenListList = tokenListList;
407
            this.index = -1;
408
            this.added = Collections.emptyList();
409
        }
410
        
411
        public TokenListList tokenListList() {
412
            return tokenListList;
413
        }
414
        
415
        /**
416
         * Mark the given token list as removed in this token list list.
417
         * All removed token lists should be marked by their increasing offset
418
         * so it should be necessary to search for the index just once.
419
         * <br/>
420
         * It's expected that updateStatusImpl() was already called
421
         * on the corresponding embedding container.
422
         */
423
        public void markRemoved(EmbeddedTokenList<?> removedTokenList) {
424
            boolean indexWasMinusOne; // Used for possible exception cause debugging
425
//            removedTokenList.embeddingContainer().checkStatusUpdated();
426
            if (index == -1) {
427
                checkUpdateNotCalledYet();
428
                indexWasMinusOne = true;
429
                index = tokenListList.findIndexDuringUpdate(removedTokenList,
430
                        update.eventInfo.modificationOffset(), update.eventInfo.removedLength());
431
                assert (index >= 0) : "index=" + index + " < 0"; // NOI18N
432
            } else { // Index already initialized
433
                indexWasMinusOne = false;
434
            }
435
            TokenList<?> markedForRemoveTokenList = tokenListList.getOrNull(index + removeCount);
436
            if (markedForRemoveTokenList != removedTokenList) {
437
                int realIndex = tokenListList.indexOf(removedTokenList);
438
                throw new IllegalStateException("Removing at index=" + index + // NOI18N
439
                        " but real index is " + realIndex + // NOI18N
440
                        " (indexWasMinusOne=" + indexWasMinusOne + ").\n" + // NOI18N
441
                        "Wishing to remove tokenList\n" + removedTokenList + // NOI18N
442
                        "\nbut marked-for-remove tokenList is \n" + markedForRemoveTokenList + // NOI18N
443
                        "\nfrom tokenListList\n" + tokenListList + // NOI18N
444
                        "\n\nModification description:\n" + update.eventInfo.modificationDescription(true) // NOI18N
445
                );
446
            }
447
            removeCount++;
448
        }
449
450
        /**
451
         * Mark the given token list to be added to this list of token lists.
452
         * At the end first the token lists marked for removal will be removed
453
         * and then the token lists marked for addition will be added.
454
         * <br/>
455
         * It's expected that updateStatusImpl() was already called
456
         * on the corresponding embedding container.
457
         */
458
        public void markAdded(EmbeddedTokenList<?> addedTokenList) {
459
//            addedTokenList.embeddingContainer().checkStatusUpdated();
460
            if (added.size() == 0) {
461
                checkUpdateNotCalledYet();
462
                if (index == -1) {
463
                    index = tokenListList.findIndex(addedTokenList.startOffset());
464
                    assert (index >= 0) : "index=" + index + " < 0"; // NOI18N
465
                }
466
                added = new ArrayList<TokenList<?>>(4);
467
            }
468
            added.add(addedTokenList);
469
        }
470
        
471
        /**
472
         * Mark that a parent's token list's bounds change need to be propagated
473
         * into the given (child) token list.
474
         * <br/>
475
         * It's expected that updateStatusImpl() was already called
476
         * on the corresponding embedding container.
477
         */
478
        public void markBoundsChange(EmbeddedTokenList<?> etl) {
479
            assert (index == -1) : "index=" + index + " != -1"; // Should be the first one
480
//            etl.embeddingContainer().checkStatusUpdated();
481
            checkUpdateNotCalledYet();
482
            index = tokenListList.findIndex(etl.startOffset());
483
        }
484
        
485
        public void update() {
486
            checkUpdateNotCalledYet();
487
            updateCalled = true;
488
            // Update this level (and language path).
489
            // All the removed and added sections resulting from parent change(s)
490
            // are already marked.
491
            if (index == -1)
492
                return; // Nothing to do
493
494
            if (removeCount == 0 && added.size() == 0) { // Bounds change only
495
                if (LOG.isLoggable(Level.FINE)) {
496
                    LOG.fine("TLLInfo.update(): BOUNDS-CHANGE: " + tokenListList.languagePath().mimePath() + // NOI18N
497
                            " index=" + index + // NOI18N
498
                            '\n'
499
                    );
500
                }
501
502
                EmbeddedTokenList<?> etl = tokenListList.get(index);
503
                etl.embeddingContainer().updateStatusImpl();
504
                Object matchState = LexerUtilsConstants.endState(etl);
505
                Object relexState = tokenListList.relexState(index);
506
                // update-status called above
507
                TokenListChange<?> chng = update.updateTokenListByModification(etl, relexState);
508
                relexState = LexerUtilsConstants.endState(etl, relexState);
509
                // Prevent bounds change in case the states at the end of the section would not match
510
                // which leads to relexing of the next section.
511
                if (chng.isBoundsChange() && LexerUtilsConstants.statesEqual(relexState, matchState)) {
512
                    TokenListChange<?> parentChange = (tokenListList.languagePath().size() == 2)
513
                            ? update.rootChange
514
                            : update.info(tokenListList.languagePath().parent()).change;
515
                    update.processBoundsChangeEmbeddings(chng, parentChange);
516
                } else { // Regular change
517
                    update.processNonBoundsChange(chng);
518
                }
519
                relexAfterLastModifiedSection(index + 1, relexState, matchState);
520
521
            } else { // Non-bounds change
522
                if (LOG.isLoggable(Level.FINE)) {
523
                    LOG.fine("TLLInfo.update(): REPLACE: " + tokenListList.languagePath().mimePath() + // NOI18N
524
                            " index=" + index + // NOI18N
525
                            ", removeCount=" + removeCount + // NOI18N
526
                            ", added.size()=" + added.size() + // NOI18N
527
                            '\n'
528
                    );
529
                }
530
531
                TokenList<?>[] removed = tokenListList.replace(index, removeCount, added);
532
                // Mark embeddings of removed token lists as removed
533
                if (tokenListList.hasChildren()) {
534
                    for (int i = 0; i < removed.length; i++) {
535
                        TokenList<?> removedTokenList = removed[i];
536
                        update.markRemovedEmbeddings(removedTokenList);
537
                    }
538
                }
539
540
                Object relexState; // State from which the relexing will start
541
                Object matchState = INVALID_STATE; // State that needs to be reached by relexing
542
                if (tokenListList.joinSections()) { // Need to find the right relexState
543
                    // Must update the token list by incremental algorithm
544
                    // Find non-empty token list and take last token's state
545
                    relexState = tokenListList.relexState(index);
546
                    for (int i = removed.length - 1; i >= 0 && matchState == INVALID_STATE; i--) {
547
                        matchState = LexerUtilsConstants.endState((EmbeddedTokenList<?>)removed[i]);
548
                    }
549
                    // Find the start state as the previous non-empty section's last token's state
550
                    // for case there would be no token lists added or all the added sections
551
                    // would be empty.
552
                    if (matchState == INVALID_STATE) // None or just empty sections were removed
553
                        matchState = relexState;
554
555
                } else { // Not joining the sections
556
                    relexState = null;
557
                }
558
559
                // Relex all the added token lists (just by asking for tokenCount - init() will be done)
560
                for (int i = 0; i < added.size(); i++) {
561
                    EmbeddedTokenList<?> tokenList = (EmbeddedTokenList<?>)added.get(i);
562
                    assert (!tokenList.isInited());
563
                    tokenList.init(relexState);
564
                    if (tokenList.embedding().joinSections()) {
565
                        tokenListList.setJoinSections(true);
566
                    }
567
                    relexState = LexerUtilsConstants.endState((EmbeddedTokenList<?>)tokenList, relexState);
568
                    if (tokenListList.hasChildren()) {
569
                        update.markAddedEmbeddings(tokenList, 0, tokenList.tokenCount());
570
                    }
571
                    // Added token lists should not require updateStatus()
572
                    update.eventInfo.setMaxAffectedEndOffset(tokenList.endOffset());
573
                }
574
575
                if (tokenListList.joinSections()) {
576
                    index += added.size();
577
                    relexAfterLastModifiedSection(index, relexState, matchState);
578
                }
579
            }
580
            
581
//            for (EmbeddedTokenList<?> etl : tokenListList) {
582
//                etl.embeddingContainer().updateStatusImpl();
583
//                if (etl.embeddingContainer().isRemoved())
584
//                    throw new IllegalStateException();
585
//            }
586
            // Set index to -1 to simplify correctness checking
587
            index = -1;
588
        }
589
        
590
        void checkUpdateNotCalledYet() {
591
            if (updateCalled) {
592
                throw new IllegalStateException("Update already called on \n" + tokenListList);
593
            }
594
        }
595
        
596
        private void relexAfterLastModifiedSection(int index, Object relexState, Object matchState) {
597
            // Must continue relexing existing section(s) (from a different start state)
598
            // until the relexing will stop before the last token of the given section.
599
            EmbeddedTokenList<?> etl;
600
            while (!LexerUtilsConstants.statesEqual(relexState, matchState)
601
                && (etl = tokenListList.getOrNull(index)) != null
602
            ) {
603
                etl.embeddingContainer().updateStatusImpl();
604
                if (etl.tokenCount() > 0) {
605
                    // Remember state after the last token of the given section
606
                    matchState = etl.state(etl.tokenCount() - 1);
607
                    // updateStatusImpl() just called
608
                    TokenListChange<?> chng = updateTokenListAtStart(etl, etl.startOffset(), relexState);
609
                    update.processNonBoundsChange(chng);
610
                    // Since the section is non-empty (checked above) there should be >0 tokens
611
                    relexState = etl.state(etl.tokenCount() - 1);
612
                }
613
                index++;
614
            }
615
        }
616
        
617
        private <T extends TokenId> TokenListChange<T> updateTokenListAtStart(
618
        EmbeddedTokenList<T> etl, int offset, Object zeroIndexRelexState) {
619
            TokenListChange<T> chng = new TokenListChange<T>(etl);
620
//            etl.embeddingContainer().checkStatusUpdated();
621
            TokenListUpdater.update(etl, offset, 0, 0, chng, zeroIndexRelexState);
622
            update.eventInfo.setMaxAffectedEndOffset(chng.addedEndOffset());
623
            return chng;
624
        }
625
        
626
    }
627
    
628
}
(-)a/lexer/src/org/netbeans/lib/lexer/TokenList.java (-31 / +49 lines)
Lines 92-98 Link Here
92
     * @param &gt;=0 index of the token in this list.
92
     * @param &gt;=0 index of the token in this list.
93
     * @return valid token or null if the index is too high.
93
     * @return valid token or null if the index is too high.
94
     */
94
     */
95
    Object tokenOrEmbeddingContainer(int index);
95
    TokenOrEmbedding<T> tokenOrEmbedding(int index);
96
96
97
    /**
97
    /**
98
     * Replace flyweight token at the given index with its non-flyweight copy.
98
     * Replace flyweight token at the given index with its non-flyweight copy.
Lines 120-130 Link Here
120
     * Get absolute offset of the token at the given index in the token list.
120
     * Get absolute offset of the token at the given index in the token list.
121
     * <br>
121
     * <br>
122
     * This method can only be called if the token at the given index
122
     * This method can only be called if the token at the given index
123
     * was already fetched by {@link tokenOrEmbeddingContainer(int)}.
123
     * was already fetched by {@link tokenOrEmbedding(int)}.
124
     * <br/>
125
     * For EmbeddedTokenList a EmbeddingContainer.updateStatus() must be called
126
     * prior this method to obtain up-to-date results.
124
     */
127
     */
125
    int tokenOffset(int index);
128
    int tokenOffsetByIndex(int index);
126
    
129
    
127
    /**
130
    /**
131
     * Get absolute offset of a token contained in this token list.
132
     * <br/>
133
     * For EmbeddedTokenList a EmbeddingContainer.updateStatus() must be called
134
     * prior this method to obtain up-to-date results.
135
     *
136
     * @param token non-null child token of this token list.
137
     * @return absolute offset in the input.
138
     */
139
    int tokenOffset(AbstractToken<T> token);
140
    
141
    /**
142
     * Get index of the token that "contains" the given offset.
143
     * <br/>
144
     * The result is in sync with TokenSequence.moveOffset().
145
     * 
146
     * @param offset offset for which the token index should be found.
147
     * @return array of two items where the [0] is token's index and [1] is its offset.
148
     *  <br/>
149
     *  If offset &gt;= last-token-end-offset then [0] contains token-count and
150
     *  [1] conains last-token-end-offset.
151
     *  <br/>
152
     *  [0] may contain -1 to indicate that there are no tokens in the token list
153
     *  ([1] then contains zero).
154
     */
155
    int[] tokenIndex(int offset);
156
    
157
   /**
128
     * Get total count of tokens in the list.
158
     * Get total count of tokens in the list.
129
     * <br/>
159
     * <br/>
130
     * For token lists that create the tokens lazily
160
     * For token lists that create the tokens lazily
Lines 154-164 Link Here
154
     * <p>
184
     * <p>
155
     * This is also used to check whether this token list corresponds to mutable input
185
     * This is also used to check whether this token list corresponds to mutable input
156
     * or not because unmodifiable lists return -1 from this method.
186
     * or not because unmodifiable lists return -1 from this method.
187
     * </p>
157
     *
188
     *
158
     * <p>
189
     * <p>
159
     * For branch token lists the {@link #updateStartOffsetShift()} ensures
190
     * For embedded token lists this value should be update to root's one
160
     * that the value returned by this method is most up-to-date
191
     * prior constructing child token sequence.
161
     * (equals to the root list's one).
192
     * </p>
162
     *
193
     *
163
     * @return number of modifications performed to the list.
194
     * @return number of modifications performed to the list.
164
     *  <br/>
195
     *  <br/>
Lines 167-199 Link Here
167
    int modCount();
198
    int modCount();
168
    
199
    
169
    /**
200
    /**
170
     * Get absolute offset of the child token with the given raw offset
201
     * Get the root token list of the token list hierarchy.
171
     * in the underlying input.
172
     *
173
     * @param rawOffset raw offset of the child token.
174
     * @return absolute offset in the input.
175
     */
202
     */
176
    int childTokenOffset(int rawOffset);
203
    TokenList<?> rootTokenList();
177
    
204
    
178
    /**
205
    /**
179
     * Get character of a token from the character sequence represented
206
     * Get text of the whole input source.
180
     * by this support.
181
     *
182
     * @param rawOffset raw offset of the child token.
183
     *  The given offset value may need to be preprocessed before using (it depends
184
     *  on a nature of the token list).
185
     * @param index index inside the token's text that should be returned.
186
     *  This value cannot be simply added to the previous parameter
187
     *  for mutable token lists as the value could errorneously point
188
     *  into a middle of the offset gap then.
189
     * @return appropriate character that the token has requested.
190
     */
207
     */
191
    char childTokenCharAt(int rawOffset, int index);
208
    CharSequence inputSourceText();
192
193
    /**
194
     * Get the root token list of the token list hierarchy.
195
     */
196
    TokenList<?> root();
197
    
209
    
198
    /**
210
    /**
199
     * Get token hierarchy operation for this token list or null
211
     * Get token hierarchy operation for this token list or null
Lines 271-276 Link Here
271
     * If token filtering is used then the first token may start at higher offset.
283
     * If token filtering is used then the first token may start at higher offset.
272
     * <br/>
284
     * <br/>
273
     * It's guaranteed that there will be no token starting below this offset.
285
     * It's guaranteed that there will be no token starting below this offset.
286
     * <br/>
287
     * For EmbeddedTokenList a EmbeddingContainer.updateStatus() must be called
288
     * prior this method to obtain up-to-date results.
274
     */
289
     */
275
    int startOffset();
290
    int startOffset();
276
    
291
    
Lines 280-295 Link Here
280
     * If token filtering is used then the last token may end at lower offset.
295
     * If token filtering is used then the last token may end at lower offset.
281
     * <br/>
296
     * <br/>
282
     * It's guaranteed that there will be no token ending above this offset.
297
     * It's guaranteed that there will be no token ending above this offset.
298
     * <br/>
299
     * For EmbeddedTokenList a EmbeddingContainer.updateStatus() must be called
300
     * prior this method to obtain up-to-date results.
283
     */
301
     */
284
    int endOffset();
302
    int endOffset();
285
    
303
    
286
    /**
304
    /**
287
     * Check if this token list is removed from token hierarchy.
305
     * Check if this token list is removed from token hierarchy.
288
     * <br/>
306
     * <br/>
289
     * Should only be called under the lock of the root token list.
307
     * Should only be called under a lock of a root token list.
290
     * 
308
     * 
291
     * @return true if the token list was removed or false otherwise.
309
     * @return true if the token list was removed or false otherwise.
292
     */
310
     */
293
    boolean isRemoved();
311
    boolean isRemoved();
294
    
312
295
}
313
}
(-)a/lexer/src/org/netbeans/lib/lexer/TokenListList.java (-104 / +66 lines)
Lines 44-57 Link Here
44
import java.util.List;
44
import java.util.List;
45
import org.netbeans.api.lexer.Language;
45
import org.netbeans.api.lexer.Language;
46
import org.netbeans.api.lexer.LanguagePath;
46
import org.netbeans.api.lexer.LanguagePath;
47
import org.netbeans.api.lexer.TokenId;
47
import org.netbeans.lib.editor.util.ArrayUtilities;
48
import org.netbeans.lib.editor.util.ArrayUtilities;
48
import org.netbeans.lib.editor.util.GapList;
49
import org.netbeans.lib.editor.util.GapList;
49
import static org.netbeans.lib.lexer.LexerUtilsConstants.INVALID_STATE;
50
import org.netbeans.lib.lexer.inc.TokenHierarchyEventInfo;
50
51
51
/**
52
/**
52
 * List of token lists that collects all token lists for a given language path.
53
 * List of token lists that collects all token lists for a given language path.
53
 * <br/>
54
 * <br/>
54
 * There can be both lists with/without joining of the embedded sections.
55
 * There can be both lists with/without joining of the embedded sections.
56
 * Non-joining TLL gets created when someone asks for TokenHierarchy.tokenSequenceList().
57
 * Joining TLL gets created if any of the embeddings for the particular language path
58
 * has LanguageEmbedding.joinSections() set to true.
55
 * <br/>
59
 * <br/>
56
 * Initial implementation attempted to initialize the list of token lists lazily
60
 * Initial implementation attempted to initialize the list of token lists lazily
57
 * upon asking for it by client. However there was a problem with fixing
61
 * upon asking for it by client. However there was a problem with fixing
Lines 81-158 Link Here
81
 * <br/>
85
 * <br/>
82
 * Non-joining embedded token lists' contents will be lexed without token list list assistance.
86
 * Non-joining embedded token lists' contents will be lexed without token list list assistance.
83
 * <br/>
87
 * <br/>
84
 * Joining embedded TLs will need TLL assistance so TLL instance gets created for them.
88
 * JoinTokenList deals with sections joining.
85
 * <br/>
86
 * If there are mixed joining/non-joining language embedding instances for the same
87
 * language path then the non-joining ones can possibly become initialized (lexed)
88
 * without TLL if they are asked individually. Later when first joining embedding is found
89
 * the token list list will be created and contain both joining and non-joining
90
 * embeddings but the joinSections will be respected for individual lexing.
91
 * Non-joining sections will be lexed individually and the join sections will be lexed as joined.
92
 * </p>
89
 * </p>
93
 *
90
 *
94
 * @author Miloslav Metelka
91
 * @author Miloslav Metelka
95
 */
92
 */
96
93
97
public final class TokenListList extends GapList<EmbeddedTokenList<?>> {
94
public final class TokenListList<T extends TokenId> extends GapList<EmbeddedTokenList<T>> {
98
95
99
    private final TokenHierarchyOperation operation;
96
    private final TokenList<?> rootTokenList;
100
97
101
    private final LanguagePath languagePath;
98
    private final LanguagePath languagePath;
102
99
103
    /**
104
     * Whether this token list is holding joint sections embeddings.
105
     * <br/>
106
     * If so it is mandatorily maintained.
107
     */
108
    private boolean joinSections;
100
    private boolean joinSections;
109
    
101
110
    /**
102
    /**
111
     * Total count of children. It's maintained to quickly resolve
103
     * Total count of children. It's maintained to quickly resolve
112
     * whether the list may be released.
104
     * whether the list may be released.
113
     */
105
     */
114
    private int childrenCount;
106
    private int childrenCount;
107
    
115
108
116
109
    public TokenListList(TokenList<?> rootTokenList, LanguagePath languagePath) {
117
    public TokenListList(TokenHierarchyOperation<?,?> operation, LanguagePath languagePath) {
118
        super(4);
110
        super(4);
119
        this.operation = operation;
111
        this.rootTokenList = rootTokenList;
120
        this.languagePath = languagePath;
112
        this.languagePath = languagePath;
121
113
122
        // languagePath has size >= 2
114
        // languagePath has size >= 2
123
        assert (languagePath.size() >= 2);
115
        assert (languagePath.size() >= 2);
124
        Language<?> language = languagePath.innerLanguage();
116
        Language<T> language = LexerUtilsConstants.innerLanguage(languagePath);
125
        if (languagePath.size() > 2) {
117
        if (languagePath.size() > 2) {
126
            Object relexState = null;
118
            TokenListList<?> parentTokenList = rootTokenList.tokenHierarchyOperation().tokenListList(languagePath.parent());
127
            TokenListList parentTokenList = operation.tokenListList(languagePath.parent());
128
            for (int parentIndex = 0; parentIndex < parentTokenList.size(); parentIndex++) {
119
            for (int parentIndex = 0; parentIndex < parentTokenList.size(); parentIndex++) {
129
                TokenList<?> tokenList = parentTokenList.get(parentIndex);
120
                TokenList<?> tokenList = parentTokenList.get(parentIndex);
130
                relexState = scanTokenList(tokenList, language, relexState);
121
                scanTokenList(tokenList, language);
131
            }
122
            }
132
        } else { // Parent is root token list
123
        } else { // Parent is root token list
133
            scanTokenList(operation.rootTokenList(), language, null);
124
            scanTokenList(rootTokenList, language);
125
        }
126
        
127
        if (joinSections) {
128
            JoinTokenList.init(this, 0, size());
129
        } else {
130
            // Init individual lists
131
            for (EmbeddedTokenList<T> etl : this) {
132
                etl.initAllTokens();
133
            }
134
        }
134
        }
135
    }
135
    }
136
    
136
    
137
    private Object scanTokenList(TokenList<?> tokenList, Language<?> language, Object relexState) {
137
    private void scanTokenList(TokenList<?> tokenList, Language<T> language) {
138
        int tokenCount = tokenList.tokenCount();
138
        int tokenCount = tokenList.tokenCount();
139
        for (int i = 0; i < tokenCount; i++) {
139
        for (int i = 0; i < tokenCount; i++) {
140
            EmbeddedTokenList<?> etl = EmbeddingContainer.embeddedTokenList(
140
            // Check for embedded token list of the given language
141
                tokenList, i, language);
141
            EmbeddedTokenList<T> etl = EmbeddingContainer.embeddedTokenList(tokenList, i, language, false);
142
            if (etl != null) {
142
            if (etl != null) {
143
                add(etl);
143
                add(etl);
144
                if (etl.embedding().joinSections()) {
144
                if (etl.embedding().joinSections()) {
145
                    joinSections = true;
145
                    this.joinSections = true;
146
                    if (!etl.isInited()) {
147
                        etl.init(relexState);
148
                        relexState = LexerUtilsConstants.endState(etl, relexState);
149
                    }
150
                } else { // Not joining sections -> next section starts with null state
151
                    relexState = null;
152
                }
146
                }
153
            }
147
            }
154
        }
148
        }
155
        return relexState;
156
    }
149
    }
157
    
150
    
158
    public LanguagePath languagePath() {
151
    public LanguagePath languagePath() {
Lines 183-241 Link Here
183
        return (childrenCount > 0);
176
        return (childrenCount > 0);
184
    }
177
    }
185
    
178
    
186
    public Object relexState(int index) {
187
        // Find the previous non-empty section or non-joining section
188
        Object relexState = INVALID_STATE;
189
        for (int i = index - 1; i >= 0 && relexState == INVALID_STATE; i--) {
190
            relexState = LexerUtilsConstants.endState(get(i));
191
        }
192
        if (relexState == INVALID_STATE) // Start from real begining
193
            relexState = null;
194
        return relexState;
195
    }
196
197
    /**
179
    /**
198
     * Return a valid token list or null if the index is too high.
180
     * Return a valid token list or null if the index is too high.
199
     */
181
     */
200
    public EmbeddedTokenList<?> getOrNull(int index) {
182
    public EmbeddedTokenList<T> getOrNull(int index) {
201
        return (index < size()) ? get(index) : null;
183
        return (index < size()) ? get(index) : null;
202
    }
184
    }
203
    
185
    
204
    private static final EmbeddedTokenList<?>[] EMPTY_TOKEN_LIST_ARRAY = new EmbeddedTokenList<?>[0];
186
    private static final EmbeddedTokenList<?>[] EMPTY_TOKEN_LIST_ARRAY = new EmbeddedTokenList<?>[0];
205
187
206
    public EmbeddedTokenList<?>[] replace(int index, int removeCount, List<? extends TokenList<?>> addTokenLists) {
188
    public EmbeddedTokenList<T>[] replace(int index, int removeTokenListCount, List<EmbeddedTokenList<T>> addTokenLists) {
207
        EmbeddedTokenList<?>[] removed;
189
        @SuppressWarnings("unchecked")
208
        if (removeCount > 0) {
190
        EmbeddedTokenList<T>[] removed = (removeTokenListCount > 0)
209
            removed = new EmbeddedTokenList<?>[removeCount];
191
                ? (EmbeddedTokenList<T>[]) new EmbeddedTokenList[removeTokenListCount]
210
            copyElements(index, index + removeCount, removed, 0);
192
                : (EmbeddedTokenList<T>[]) EMPTY_TOKEN_LIST_ARRAY;
211
            remove(index, removeCount);
193
        if (removeTokenListCount > 0) {
212
        } else {
194
            copyElements(index, index + removeTokenListCount, removed, 0);
213
            removed = EMPTY_TOKEN_LIST_ARRAY;
195
            remove(index, removeTokenListCount);
214
        }
196
        }
215
        @SuppressWarnings("unchecked")
197
        addAll(index, addTokenLists);
216
        List<EmbeddedTokenList<?>> etlLists = (List<EmbeddedTokenList<?>>)addTokenLists;
217
        addAll(index, etlLists);
218
        return removed;
198
        return removed;
199
    }
200
201
    public TokenList<?> rootTokenList() {
202
        return rootTokenList;
219
    }
203
    }
220
204
221
    void childAdded() {
205
    void childAdded() {
222
        throw new UnsupportedOperationException("Not yet implemented");
206
        throw new UnsupportedOperationException("Not yet implemented");
223
    }
207
    }
224
208
    
225
    TokenHierarchyOperation operation() {
226
        return operation;
227
    }
228
229
    int modCount() {
230
        return operation.modCount();
231
    }
232
233
    public int findIndex(int offset) {
209
    public int findIndex(int offset) {
234
        int high = size() - 1;
210
        int high = size() - 1;
235
        int low = 0;
211
        int low = 0;
236
        while (low <= high) {
212
        while (low <= high) {
237
            int mid = (low + high) >> 1;
213
            int mid = (low + high) >>> 1;
238
            EmbeddedTokenList<?> etl = get(mid);
214
            EmbeddedTokenList<T> etl = get(mid);
239
            // Ensure that the startOffset() will be updated
215
            // Ensure that the startOffset() will be updated
240
            etl.embeddingContainer().updateStatus();
216
            etl.embeddingContainer().updateStatus();
241
            int cmp = etl.startOffset() - offset;
217
            int cmp = etl.startOffset() - offset;
Lines 258-279 Link Here
258
     * were removed then these TLs then the token lists beyond the modification point
234
     * were removed then these TLs then the token lists beyond the modification point
259
     * will be forced to update itself which may 
235
     * will be forced to update itself which may 
260
     */
236
     */
261
    public int findIndexDuringUpdate(EmbeddedTokenList targetEtl, int modOffset, int removedLength) {
237
    public int findIndexDuringUpdate(EmbeddedTokenList<T> targetEtl, TokenHierarchyEventInfo eventInfo) {
262
        int high = size() - 1;
238
        int high = size() - 1;
263
        int low = 0;
239
        int low = 0;
264
        int targetStartOffset = targetEtl.startOffset();
240
        int targetStartOffset = LexerUtilsConstants.updatedStartOffset(targetEtl, eventInfo);
265
        if (targetStartOffset > modOffset && targetEtl.embeddingContainer().isRemoved()) {
266
            targetStartOffset = Math.max(targetStartOffset - removedLength, modOffset);
267
        }
268
        while (low <= high) {
241
        while (low <= high) {
269
            int mid = (low + high) >> 1;
242
            int mid = (low + high) >>> 1;
270
            EmbeddedTokenList<?> etl = get(mid);
243
            EmbeddedTokenList<T> etl = get(mid);
271
            // Ensure that the startOffset() will be updated
244
            // Ensure that the startOffset() will be updated
272
            etl.embeddingContainer().updateStatusImpl();
245
            int startOffset = LexerUtilsConstants.updatedStartOffset(etl, eventInfo);
273
            int startOffset = etl.startOffset();
274
            if (startOffset > modOffset && etl.embeddingContainer().isRemoved()) {
275
                startOffset = Math.max(startOffset - removedLength, modOffset);
276
            }
277
            int cmp = startOffset - targetStartOffset;
246
            int cmp = startOffset - targetStartOffset;
278
            if (cmp < 0)
247
            if (cmp < 0)
279
                low = mid + 1;
248
                low = mid + 1;
Lines 286-324 Link Here
286
                // In such case these need to be searched by linear search in both directions
255
                // In such case these need to be searched by linear search in both directions
287
                // from the found one.
256
                // from the found one.
288
                if (etl != targetEtl) {
257
                if (etl != targetEtl) {
289
                    low--;
258
                    while (--low >= 0) {
290
                    while (low >= 0) {
291
                        etl = get(low);
259
                        etl = get(low);
292
                        if (etl == targetEtl) { // Quick check for match
260
                        if (etl == targetEtl) { // Quick check for match
293
                            return low;
261
                            return low;
294
                        }
262
                        }
295
                        // Check whether this was appropriate attempt for match
263
                        // Check whether this was appropriate attempt for match
296
                        etl.embeddingContainer().updateStatusImpl();
264
                        if (LexerUtilsConstants.updatedStartOffset(etl, eventInfo) != targetStartOffset)
297
                        startOffset = etl.startOffset();
298
                        if (startOffset > modOffset && etl.embeddingContainer().isRemoved()) {
299
                            startOffset = Math.max(startOffset - removedLength, modOffset);
300
                        }
301
                        if (startOffset != modOffset)
302
                            break;
265
                            break;
303
                        low--;
304
                    }
266
                    }
305
                    
267
                    
306
                    // Go up from mid
268
                    // Go up from mid
307
                    low = mid + 1;
269
                    low = mid;
308
                    while (low < size()) {
270
                    while (++low < size()) {
309
                        etl = get(low);
271
                        etl = get(low);
310
                        if (etl == targetEtl) { // Quick check for match
272
                        if (etl == targetEtl) { // Quick check for match
311
                            return low;
273
                            return low;
312
                        }
274
                        }
313
                        // Check whether this was appropriate attempt for match
275
                        // Check whether this was appropriate attempt for match
314
                        etl.embeddingContainer().updateStatusImpl();
276
                        if (LexerUtilsConstants.updatedStartOffset(etl, eventInfo) != targetStartOffset)
315
                        startOffset = etl.startOffset();
316
                        if (startOffset > modOffset && etl.embeddingContainer().isRemoved()) {
317
                            startOffset = Math.max(startOffset - removedLength, modOffset);
318
                        }
319
                        if (startOffset != modOffset)
320
                            break;
277
                            break;
321
                        low++;
322
                    }
278
                    }
323
                }
279
                }
324
                break;
280
                break;
Lines 331-338 Link Here
331
        // Check whether the token lists are in a right order
287
        // Check whether the token lists are in a right order
332
        int lastEndOffset = 0;
288
        int lastEndOffset = 0;
333
        for (int i = 0; i < size(); i++) {
289
        for (int i = 0; i < size(); i++) {
334
            EmbeddedTokenList<?> etl = get(i);
290
            EmbeddedTokenList<T> etl = get(i);
335
            etl.embeddingContainer().updateStatusImpl();
291
            etl.embeddingContainer().updateStatusUnsync();
292
            if (etl.isRemoved()) {
293
                return "TOKEN-LIST-LIST Removed token list at index=" + i + '\n' + this;
294
            }
336
            if (etl.startOffset() < lastEndOffset) {
295
            if (etl.startOffset() < lastEndOffset) {
337
                return "TOKEN-LIST-LIST Invalid start offset at index=" + i +
296
                return "TOKEN-LIST-LIST Invalid start offset at index=" + i +
338
                        ": etl[" + i + "].startOffset()=" + etl.startOffset() +
297
                        ": etl[" + i + "].startOffset()=" + etl.startOffset() +
Lines 353-358 Link Here
353
            }
312
            }
354
            lastEndOffset = etl.endOffset();
313
            lastEndOffset = etl.endOffset();
355
        }
314
        }
315
        if (joinSections()) {
316
            return get(0).joinTokenList().checkConsistency();
317
        }
356
        return null;
318
        return null;
357
    }
319
    }
358
320
Lines 370-379 Link Here
370
        sb.append('\n');
332
        sb.append('\n');
371
        int digitCount = ArrayUtilities.digitCount(size());
333
        int digitCount = ArrayUtilities.digitCount(size());
372
        for (int i = 0; i < size(); i++) {
334
        for (int i = 0; i < size(); i++) {
373
            EmbeddedTokenList<?> etl = get(i);
335
            EmbeddedTokenList<T> etl = get(i);
374
            ArrayUtilities.appendBracketedIndex(sb, i, digitCount);
336
            ArrayUtilities.appendBracketedIndex(sb, i, digitCount);
375
            etl.embeddingContainer().updateStatus();
337
            etl.embeddingContainer().updateStatus();
376
            sb.append(etl.toStringHeader());
338
            etl.dumpInfo(sb);
377
            EmbeddingContainer ec = etl.embeddingContainer();
339
            EmbeddingContainer ec = etl.embeddingContainer();
378
            if (ec != null && ec.isRemoved()) {
340
            if (ec != null && ec.isRemoved()) {
379
                sb.append(", <--REMOVED-->");
341
                sb.append(", <--REMOVED-->");
(-)06a7890f802e (+70 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer;
43
44
import org.netbeans.api.lexer.TokenId;
45
import org.netbeans.lib.lexer.token.AbstractToken;
46
47
/**
48
 * Type for having either token or embedding.
49
 *
50
 * @author Miloslav Metelka
51
 */
52
53
public interface TokenOrEmbedding<T extends TokenId> {
54
55
    /**
56
     * Get token reference 
57
     * 
58
     * @return <code>this</code> if this is a token instance or
59
     *  a wrapped token if this is an embedding container.
60
     */
61
    AbstractToken<T> token();
62
    
63
    /**
64
     * Get non-null embedding container if this is embedding.
65
     * 
66
     * @return non-null embedding or null if this is token.
67
     */
68
    EmbeddingContainer<T> embedding();
69
70
}
(-)a/lexer/src/org/netbeans/lib/lexer/TokenSequenceList.java (-14 / +11 lines)
Lines 43-56 Link Here
43
43
44
import java.util.AbstractList;
44
import java.util.AbstractList;
45
import java.util.ArrayList;
45
import java.util.ArrayList;
46
import java.util.ArrayList;
47
import java.util.Collections;
46
import java.util.Collections;
48
import java.util.ConcurrentModificationException;
47
import java.util.ConcurrentModificationException;
49
import java.util.Iterator;
48
import java.util.Iterator;
50
import java.util.List;
49
import java.util.List;
51
import java.util.NoSuchElementException;
50
import java.util.NoSuchElementException;
52
import org.netbeans.api.lexer.LanguagePath;
51
import org.netbeans.api.lexer.LanguagePath;
53
import org.netbeans.api.lexer.TokenId;
54
import org.netbeans.api.lexer.TokenSequence;
52
import org.netbeans.api.lexer.TokenSequence;
55
53
56
/**
54
/**
Lines 61-69 Link Here
61
59
62
public final class TokenSequenceList extends AbstractList<TokenSequence<?>> {
60
public final class TokenSequenceList extends AbstractList<TokenSequence<?>> {
63
    
61
    
64
    private TokenHierarchyOperation<?,?> operation;
62
    private TokenList<?> rootTokenList;
65
63
66
    private final TokenListList tokenListList;
64
    private final TokenListList<?> tokenListList;
67
    
65
    
68
    private final List<TokenSequence<?>> tokenSequences;
66
    private final List<TokenSequence<?>> tokenSequences;
69
67
Lines 78-93 Link Here
78
     */
76
     */
79
    private int tokenListIndex;
77
    private int tokenListIndex;
80
    
78
    
81
    public TokenSequenceList(TokenHierarchyOperation<?,?> operation, LanguagePath languagePath,
79
    public TokenSequenceList(TokenList<?> rootTokenList, LanguagePath languagePath,
82
    int startOffset, int endOffset) {
80
    int startOffset, int endOffset) {
83
        this.operation = operation;
81
        this.rootTokenList = rootTokenList;
84
        this.endOffset = endOffset;
82
        this.endOffset = endOffset;
85
        this.expectedModCount = operation.modCount();
83
        this.expectedModCount = rootTokenList.modCount();
86
84
87
        if (languagePath.size() == 1) { // Is supported too
85
        if (languagePath.size() == 1) { // Is supported too
88
            this.tokenListList = null;
86
            this.tokenListList = null;
89
            tokenListIndex = Integer.MAX_VALUE; // Mark no mods to tokenSequences
87
            tokenListIndex = Integer.MAX_VALUE; // Mark no mods to tokenSequences
90
            TokenList<?> rootTokenList = operation.rootTokenList();
91
            if (rootTokenList.languagePath() == languagePath) {
88
            if (rootTokenList.languagePath() == languagePath) {
92
                TokenSequence<?> rootTS = LexerApiPackageAccessor.get().createTokenSequence(
89
                TokenSequence<?> rootTS = LexerApiPackageAccessor.get().createTokenSequence(
93
                            checkWrapTokenList(rootTokenList, startOffset, endOffset));
90
                            checkWrapTokenList(rootTokenList, startOffset, endOffset));
Lines 97-103 Link Here
97
            }
94
            }
98
95
99
        } else { // languagePath.size() >= 2
96
        } else { // languagePath.size() >= 2
100
            this.tokenListList = operation.tokenListList(languagePath);
97
            this.tokenListList = rootTokenList.tokenHierarchyOperation().tokenListList(languagePath);
101
            // Possibly skip initial token lists accroding to startOffset
98
            // Possibly skip initial token lists accroding to startOffset
102
            int size = tokenListList.size();
99
            int size = tokenListList.size();
103
            int high = size - 1;
100
            int high = size - 1;
Lines 108-114 Link Here
108
                    int mid = (tokenListIndex + high) / 2;
105
                    int mid = (tokenListIndex + high) / 2;
109
                    EmbeddedTokenList<?> etl = tokenListList.get(mid);
106
                    EmbeddedTokenList<?> etl = tokenListList.get(mid);
110
                    // Update end offset before querying
107
                    // Update end offset before querying
111
                    etl.embeddingContainer().updateStatusImpl();
108
                    etl.embeddingContainer().updateStatusUnsync();
112
                    int tlEndOffset = etl.endOffset(); // updateStatusImpl() just called
109
                    int tlEndOffset = etl.endOffset(); // updateStatusImpl() just called
113
                    if (tlEndOffset < startOffset) {
110
                    if (tlEndOffset < startOffset) {
114
                        tokenListIndex = mid + 1;
111
                        tokenListIndex = mid + 1;
Lines 123-129 Link Here
123
                firstTokenList = tokenListList.getOrNull(tokenListIndex);
120
                firstTokenList = tokenListList.getOrNull(tokenListIndex);
124
                if (tokenListIndex == size) { // Right above the ones that existed at begining of bin search
121
                if (tokenListIndex == size) { // Right above the ones that existed at begining of bin search
125
                    while (firstTokenList != null) {
122
                    while (firstTokenList != null) {
126
                        firstTokenList.embeddingContainer().updateStatusImpl();
123
                        firstTokenList.embeddingContainer().updateStatusUnsync();
127
                        if (firstTokenList.endOffset() >= startOffset) { // updateStatusImpl() just called
124
                        if (firstTokenList.endOffset() >= startOffset) { // updateStatusImpl() just called
128
                            break;
125
                            break;
129
                        }
126
                        }
Lines 136-142 Link Here
136
            }
133
            }
137
134
138
            if (firstTokenList != null) {
135
            if (firstTokenList != null) {
139
                firstTokenList.embeddingContainer().updateStatusImpl();
136
                firstTokenList.embeddingContainer().updateStatusUnsync();
140
                tokenSequences = new ArrayList<TokenSequence<?>>(4);
137
                tokenSequences = new ArrayList<TokenSequence<?>>(4);
141
                tokenSequences.add(LexerApiPackageAccessor.get().createTokenSequence(
138
                tokenSequences.add(LexerApiPackageAccessor.get().createTokenSequence(
142
                        checkWrapTokenList(firstTokenList, startOffset, endOffset)));
139
                        checkWrapTokenList(firstTokenList, startOffset, endOffset)));
Lines 207-216 Link Here
207
    }
204
    }
208
    
205
    
209
    void checkForComodification() {
206
    void checkForComodification() {
210
        if (expectedModCount != operation.modCount())
207
        if (expectedModCount != rootTokenList.modCount())
211
            throw new ConcurrentModificationException(
208
            throw new ConcurrentModificationException(
212
                    "Caller uses obsolete TokenSequenceList: expectedModCount=" + expectedModCount + // NOI18N
209
                    "Caller uses obsolete TokenSequenceList: expectedModCount=" + expectedModCount + // NOI18N
213
                    " != modCount=" + operation.modCount()
210
                    " != modCount=" + rootTokenList.modCount()
214
            );
211
            );
215
    }
212
    }
216
213
(-)a/lexer/src/org/netbeans/lib/lexer/batch/BatchTokenList.java (-255 lines)
Removed Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.batch;
43
44
import java.util.ArrayList;
45
import java.util.Set;
46
import org.netbeans.api.lexer.Language;
47
import org.netbeans.api.lexer.LanguagePath;
48
import org.netbeans.lib.lexer.EmbeddingContainer;
49
import org.netbeans.lib.lexer.LAState;
50
import org.netbeans.lib.lexer.TokenList;
51
import org.netbeans.lib.lexer.LexerInputOperation;
52
import org.netbeans.lib.lexer.LexerUtilsConstants;
53
import org.netbeans.api.lexer.InputAttributes;
54
import org.netbeans.api.lexer.Token;
55
import org.netbeans.api.lexer.TokenId;
56
import org.netbeans.lib.lexer.TokenHierarchyOperation;
57
import org.netbeans.lib.lexer.token.AbstractToken;
58
import org.netbeans.lib.lexer.token.TextToken;
59
60
61
/**
62
 * Token list used for root list for immutable inputs.
63
 *
64
 * @author Miloslav Metelka
65
 * @version 1.00
66
 */
67
68
public abstract class BatchTokenList<T extends TokenId>
69
extends ArrayList<Object> implements TokenList<T> {
70
    
71
    /** Flag for additional correctness checks (may degrade performance). */
72
    private static final boolean testing = Boolean.getBoolean("netbeans.debug.lexer.test");
73
    
74
    private static boolean maintainLAState;
75
    
76
    /**
77
     * Check whether lookaheads and states are stored for testing purposes.
78
     */
79
    public static boolean isMaintainLAState() {
80
        return maintainLAState;
81
    }
82
    
83
    public static void setMaintainLAState(boolean maintainLAState) {
84
        BatchTokenList.maintainLAState = maintainLAState;
85
    }
86
    
87
    private final TokenHierarchyOperation<?,T> tokenHierarchyOperation;
88
    
89
    private final LanguagePath languagePath;
90
    
91
    private final Set<T> skipTokenIds;
92
    
93
    private final InputAttributes inputAttributes;
94
    
95
    /**
96
     * Lexer input used for lexing of the input.
97
     */
98
    private LexerInputOperation<T> lexerInputOperation;
99
100
    private LAState laState;
101
    
102
    private boolean inited;
103
    
104
    
105
    public BatchTokenList(TokenHierarchyOperation<?,T> tokenHierarchyOperation,
106
    Language<T> language, Set<T> skipTokenIds, InputAttributes inputAttributes) {
107
        this.tokenHierarchyOperation = tokenHierarchyOperation;
108
        this.languagePath = LanguagePath.get(language);
109
        this.skipTokenIds = skipTokenIds;
110
        this.inputAttributes = inputAttributes;
111
        if (testing) { // Maintain lookaheads and states when in test environment
112
            laState = LAState.empty();
113
        }
114
    }
115
116
    public abstract char childTokenCharAt(int rawOffset, int index);
117
118
    protected abstract LexerInputOperation<T> createLexerInputOperation();
119
120
    protected void init() {
121
        lexerInputOperation = createLexerInputOperation();
122
    }
123
    
124
    public TokenList<?> root() {
125
        return this; // this list should always be the root list of the token hierarchy
126
    }
127
    
128
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
129
        return tokenHierarchyOperation;
130
    }
131
    
132
    public LanguagePath languagePath() {
133
        return languagePath;
134
    }
135
    
136
    public synchronized int tokenCount() {
137
        if (!inited) {
138
            init();
139
            inited = true;
140
        }
141
        if (lexerInputOperation != null) { // still lexing
142
            tokenOrEmbeddingContainerImpl(Integer.MAX_VALUE);
143
        }
144
        return size();
145
    }
146
    
147
    public int tokenCountCurrent() {
148
        return size();
149
    }
150
151
    public int childTokenOffset(int rawOffset) {
152
        // Children offsets should be absolute
153
        return rawOffset;
154
    }
155
156
    public int tokenOffset(int index) {
157
        Token<T> token = existingToken(index);
158
        int offset;
159
        if (token.isFlyweight()) {
160
            offset = 0;
161
            while (--index >= 0) {
162
                token = existingToken(index);
163
                offset += token.length();
164
                if (!token.isFlyweight()) {
165
                    offset += token.offset(null);
166
                    break;
167
                }
168
            }
169
        } else { // non-flyweight offset
170
            offset = token.offset(null);
171
        }
172
        return offset;
173
    }
174
175
    public synchronized Object tokenOrEmbeddingContainer(int index) {
176
        return tokenOrEmbeddingContainerImpl(index);
177
    }
178
    
179
    private Object tokenOrEmbeddingContainerImpl(int index) {
180
        if (!inited) {
181
            init();
182
            inited = true;
183
        }
184
        while (lexerInputOperation != null && index >= size()) {
185
            Token<T> token = lexerInputOperation.nextToken();
186
            if (token != null) { // lexer returned valid token
187
                add(token);
188
                if (laState != null) { // maintaining lookaheads and states
189
                    laState = laState.add(lexerInputOperation.lookahead(),
190
                            lexerInputOperation.lexerState());
191
                }
192
            } else { // no more tokens from lexer
193
                lexerInputOperation.release();
194
                lexerInputOperation = null;
195
                trimToSize();
196
            }
197
        }
198
        return (index < size()) ? get(index) : null;
199
    }
200
    
201
    private Token<T> existingToken(int index) {
202
        return LexerUtilsConstants.token(get(index));
203
    }
204
205
    public int lookahead(int index) {
206
        return (laState != null) ? laState.lookahead(index) : -1;
207
    }
208
209
    public Object state(int index) {
210
        return (laState != null) ? laState.state(index) : null;
211
    }
212
213
    public int startOffset() {
214
        return 0;
215
    }
216
217
    public int endOffset() {
218
        int cntM1 = tokenCount() - 1;
219
        if (cntM1 >= 0)
220
            return tokenOffset(cntM1) + LexerUtilsConstants.token(this, cntM1).length();
221
        return 0;
222
    }
223
224
    public boolean isRemoved() {
225
        return false;
226
    }
227
228
    public int modCount() {
229
        return -1; // immutable input
230
    }
231
    
232
    public synchronized AbstractToken<T> replaceFlyToken(
233
    int index, AbstractToken<T> flyToken, int offset) {
234
        TextToken<T> nonFlyToken = ((TextToken<T>)flyToken).createCopy(this, offset);
235
        set(index, nonFlyToken);
236
        return nonFlyToken;
237
    }
238
239
    public void wrapToken(int index, EmbeddingContainer embeddingContainer) {
240
        set(index, embeddingContainer);
241
    }
242
243
    public InputAttributes inputAttributes() {
244
        return inputAttributes;
245
    }
246
    
247
    public boolean isContinuous() {
248
        return (skipTokenIds == null);
249
    }
250
    
251
    public Set<T> skipTokenIds() {
252
        return skipTokenIds;
253
    }
254
255
}
(-)a/lexer/src/org/netbeans/lib/lexer/batch/CopyTextTokenList.java (-101 lines)
Removed Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.batch;
43
44
import java.io.Reader;
45
import java.util.Set;
46
import org.netbeans.api.lexer.Language;
47
import org.netbeans.lib.lexer.LexerInputOperation;
48
import org.netbeans.api.lexer.InputAttributes;
49
import org.netbeans.api.lexer.TokenId;
50
import org.netbeans.lib.lexer.TokenHierarchyOperation;
51
52
53
/**
54
 * Token list for situation when the input text must be copied.
55
 * It works together with SkimTokenList instances that act
56
 * as a filter over this token list.
57
 *
58
 * @author Miloslav Metelka
59
 * @version 1.00
60
 */
61
62
public final class CopyTextTokenList<T extends TokenId> extends BatchTokenList<T> {
63
    
64
    /** Either reader or char sequence */
65
    private final Object input;
66
    
67
    public CopyTextTokenList(TokenHierarchyOperation<?,T> tokenHierarchyOperation, Reader inputReader,
68
    Language<T> language, Set<T> skipTokenIds, InputAttributes inputAttributes) {
69
        super(tokenHierarchyOperation, language, skipTokenIds, inputAttributes);
70
        this.input = inputReader;
71
    }
72
    
73
    public CopyTextTokenList(TokenHierarchyOperation<?,T> tokenHierarchyOperation, CharSequence inputText,
74
    Language<T> language, Set<T> skipTokenIds, InputAttributes inputAttributes) {
75
        super(tokenHierarchyOperation, language, skipTokenIds, inputAttributes);
76
        this.input = inputText;
77
    }
78
    
79
    public int childTokenOffset(int rawOffset) {
80
        // Cluster should be used so this method should never be called
81
        throwShouldNeverBeCalled();
82
        return 0; // never reached
83
    }
84
85
    public char childTokenCharAt(int rawOffset, int index) {
86
        // Cluster should be used so this method should never be called
87
        throwShouldNeverBeCalled();
88
        return ' '; // never reached
89
    }
90
    
91
    private void throwShouldNeverBeCalled() {
92
        throw new IllegalStateException("Should never be called"); // NOI18N
93
    }
94
95
    protected LexerInputOperation<T> createLexerInputOperation() {
96
        return (input instanceof Reader)
97
            ? new SkimLexerInputOperation<T>(this, (Reader)input)
98
            : new SkimLexerInputOperation<T>(this, (CharSequence)input);
99
    }
100
101
}
(-)a/lexer/src/org/netbeans/lib/lexer/batch/SkimLexerInputOperation.java (-316 lines)
Removed Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.batch;
43
44
import java.io.IOException;
45
import java.io.Reader;
46
import org.netbeans.api.lexer.TokenId;
47
import org.netbeans.lib.lexer.LexerInputOperation;
48
import org.netbeans.lib.lexer.TokenList;
49
import org.netbeans.lib.lexer.token.AbstractToken;
50
import org.netbeans.spi.lexer.LexerInput;
51
52
/**
53
 * Lexer input operation over a {@link java.io.Reader}.
54
 *
55
 * @author Miloslav Metelka
56
 * @version 1.00
57
 */
58
59
public final class SkimLexerInputOperation<T extends TokenId> extends LexerInputOperation<T> {
60
    
61
    private static final char[] EMPTY_CHAR_ARRAY = new char[0];
62
    
63
    /**
64
     * Default size for reading char array.
65
     */
66
    private static final int DEFAULT_READ_CHAR_ARRAY_SIZE = 4096;
67
    
68
    /**
69
     * Minimum size to be read (to have space for reading).
70
     */
71
    private static final int MIN_READ_SIZE = 512;
72
    
73
    private static final int DEFAULT_CLUSTER_SIZE = 4096;
74
    
75
    /**
76
     * Maximum fragmentation factor for token character arrays.
77
     * <br>
78
     * If there is not enough space in the tokenCharArray
79
     * to copy a token's characters there then if the token's length
80
     * will be greater than this threshold then the token will get
81
     * an extra character buffer just for itself and there will
82
     * still be chance to use the present tokenCharArray for tokens
83
     * with lower length.
84
     */
85
    private static final int MAX_UNUSED_CLUSTER_SIZE_FRACTION = 50;
86
    
87
88
    /**
89
     * Reader as a primary source of characters that are further
90
     * copied and cached.
91
     */
92
    private Reader reader;
93
    
94
    /**
95
     * Array holding the read characters.
96
     */
97
    private char[] readCharArray;
98
    
99
    /**
100
     * Character sequence holding the characters to be read.
101
     */
102
    private CharSequence readCharSequence;
103
    
104
    /**
105
     * Index of a first character in the token being currently recognized.
106
     */
107
    private int readStartIndex;
108
    
109
    /**
110
     * End of valid chars in readCharArray (points to first invalid char).
111
     */
112
    private int readEndIndex;
113
    
114
    /**
115
     * Whether EOF was read from reader already or not.
116
     */
117
    private boolean eofRead;
118
    
119
    /**
120
     * Actual token cluster where the tokens are being placed.
121
     */
122
    private SkimTokenList<T> cluster;
123
124
    private int clusterTextEndIndex;
125
    
126
    private int defaultClusterSize = DEFAULT_CLUSTER_SIZE;
127
    
128
    /**
129
     * Starting offset of the cluster currently being used.
130
     */
131
    private int clusterStartOffset;
132
    
133
    /**
134
     * How much the offset is ahead of the token's text offset
135
     * in the cluster. The tokens that get skipped and flyweight tokens
136
     * increase this value because their text is not physically copied
137
     * into the clusters character data but they increase the offset.
138
     */
139
    private int offsetShift;
140
    
141
    public SkimLexerInputOperation(TokenList<T> tokenList, Reader reader) {
142
        super(tokenList, 0, null);
143
        this.reader = reader;
144
        this.readCharArray = new char[DEFAULT_READ_CHAR_ARRAY_SIZE];
145
    }
146
    
147
    public SkimLexerInputOperation(TokenList<T> tokenList, CharSequence readCharSequence) {
148
        super(tokenList, 0, null);
149
        this.readCharSequence = readCharSequence;
150
        this.readEndIndex = readCharSequence.length();
151
    }
152
    
153
    public int read(int index) { // index >= 0 is guaranteed by contract
154
        index += readStartIndex;
155
        if (index < readEndIndex) {
156
            return (readCharArray != null)
157
                ? readCharArray[index]
158
                : readCharSequence.charAt(index);
159
160
        } else { // must read next or return EOF
161
            if (!eofRead) {
162
                eofRead = (readCharArray != null)
163
                    ? readNextCharArray()
164
                    : true; // using readCharSequence -> no more chars
165
166
                return read(index);
167
168
            } else {
169
                return LexerInput.EOF;
170
            }
171
        }
172
    }
173
    
174
    public char readExisting(int index) {
175
        return (readCharArray != null)
176
            ? readCharArray[index]
177
            : readCharSequence.charAt(index);
178
    }
179
    
180
    public void approveToken(AbstractToken<T> token) {
181
        int tokenLength = token.length();
182
        if (isSkipToken(token)) {
183
            preventFlyToken();
184
            skipChars(tokenLength());
185
            
186
        } else if (token.isFlyweight()) {
187
            assert isFlyTokenAllowed();
188
            flyTokenAdded();
189
            skipChars(tokenLength);
190
191
        } else { // non-flyweight token => must be L0Token instance
192
            if (clusterTextEndIndex != 0) { // valid cluster exists
193
                // Check whether token fits into cluster's char array
194
                if (tokenLength + clusterTextEndIndex > cluster.getText().length) {
195
                    // Cannot fit the token's text into current cluster
196
                    finishCluster();
197
                }
198
            }
199
200
            if (clusterTextEndIndex == 0) { // allocate new cluster
201
                int clusterSize = defaultClusterSize;
202
                if (clusterSize < tokenLength) { // cluster just for one token
203
                    clusterSize = tokenLength;
204
                }
205
                defaultClusterSize = clusterSize;
206
                cluster = new SkimTokenList<T>((CopyTextTokenList<T>)tokenList(),
207
                        clusterStartOffset, new char[clusterSize]);
208
            }
209
210
            // Now it's clear that the token will fit into the cluster's text
211
            // TODO for DirectCharSequence use more efficient way
212
            char[] clusterText = cluster.getText();
213
            if (readCharArray != null) {
214
                System.arraycopy(readCharArray, readStartIndex, clusterText,
215
                        clusterTextEndIndex, tokenLength);
216
            } else { // using readCharSequence
217
                for (int i = 0; i < tokenLength; i++) {
218
                    clusterText[clusterTextEndIndex + i]
219
                            = readCharSequence.charAt(readStartIndex + i);
220
                }
221
            }
222
            
223
            int rawOffset = (offsetShift << 16) | clusterTextEndIndex;
224
            token.setTokenList(cluster);
225
            token.setRawOffset(rawOffset);
226
            clusterTextEndIndex += tokenLength;
227
            clearFlySequence();
228
        }
229
230
        readStartIndex += tokenLength;
231
        tokenApproved();
232
    }
233
234
    private void skipChars(int skipLength) {
235
        if (clusterTextEndIndex != 0) { // cluster already populated
236
            if (offsetShift + skipLength > Short.MAX_VALUE) {
237
                // Cannot advance offset shift without overflowing -> cluster is finished
238
                finishCluster();
239
                clusterStartOffset += skipLength;
240
241
            } else { // relOffset will fit into current cluster
242
                offsetShift += skipLength;
243
            }
244
245
        } else { // cluster is null -> can shift cluster's start offset
246
            clusterStartOffset += skipLength;
247
        }
248
    }        
249
    
250
    public void finish() {
251
        if (clusterTextEndIndex != 0) {
252
            finishCluster();
253
        }
254
    }
255
256
    private void finishCluster() {
257
        // If there would be too much unused space in the cluster's char array
258
        // then it will be reallocated.
259
        int clusterTextLength = cluster.getText().length;
260
        if (clusterTextLength / MAX_UNUSED_CLUSTER_SIZE_FRACTION
261
                > (clusterTextLength - clusterTextEndIndex)
262
        ) { // Fragmentation -> reallocate cluster's char array
263
            char[] newText = new char[clusterTextEndIndex];
264
            System.arraycopy(cluster.getText(), 0, newText, 0, clusterTextEndIndex);
265
            cluster.setText(newText);
266
        }
267
        clusterStartOffset += clusterTextEndIndex + offsetShift;
268
        clusterTextEndIndex = 0;
269
        offsetShift = 0;
270
        cluster = null; // cluster no longer valid
271
    }
272
    
273
    private boolean readNextCharArray() {
274
        // Copy everything from present readStartIndex till readEndIndex
275
        int retainLength = readEndIndex - readStartIndex;
276
        int minReadSize = readCharArray.length - retainLength;
277
        char[] newReadCharArray = readCharArray; // by default take original one
278
        if (minReadSize < MIN_READ_SIZE) { // allocate new
279
            // double the current array's size
280
            newReadCharArray = new char[readCharArray.length * 2];
281
        }
282
        System.arraycopy(readCharArray, readStartIndex, newReadCharArray, 0, retainLength);
283
        readCharArray = newReadCharArray;
284
        readStartIndex = 0;
285
        readEndIndex = retainLength;
286
        
287
        boolean eof = false;
288
        while (readEndIndex < readCharArray.length) {
289
            int readSize;
290
            try {
291
                readSize = reader.read(readCharArray, readEndIndex,
292
                    readCharArray.length - readEndIndex);
293
            } catch (IOException e) {
294
                // The exception is silently ignored here
295
                // This should generally not happen - a wrapping reader
296
                // should be used that will catch and process the IO exceptions.
297
                readSize = -1;
298
            }
299
            if (readSize == -1) {
300
                eof = true;
301
                try {
302
                    reader.close();
303
                } catch (IOException e) {
304
                    // The exception is silently ignored here
305
                    // This should generally not happen - a wrapping reader
306
                    // should be used that will catch and process the IO exceptions.
307
                }
308
                break;
309
            } else {
310
                readEndIndex += readSize;
311
            }
312
        }
313
        return eof;
314
    }
315
316
}
(-)a/lexer/src/org/netbeans/lib/lexer/batch/SkimTokenList.java (-178 lines)
Removed Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.batch;
43
44
import java.util.Set;
45
import org.netbeans.api.lexer.LanguagePath;
46
import org.netbeans.lib.lexer.EmbeddingContainer;
47
import org.netbeans.lib.lexer.TokenList;
48
import org.netbeans.api.lexer.InputAttributes;
49
import org.netbeans.api.lexer.TokenId;
50
import org.netbeans.lib.lexer.TokenHierarchyOperation;
51
import org.netbeans.lib.lexer.token.AbstractToken;
52
53
/**
54
 * Filtering token list constructed over character array with an independent
55
 * start offset value.
56
 * <br>
57
 * It is constructed for batch inputs and it implements
58
 * a token list but it only implements translation of raw offsets
59
 * into real offsets and retrieving of the characters of token bodies.
60
 * <br>
61
 * Other operations are delegated to an original
62
 * token list that really holds the tokens.
63
 *
64
 * @author Miloslav Metelka
65
 * @version 1.00
66
 */
67
68
public final class SkimTokenList<T extends TokenId> implements TokenList<T> {
69
    
70
    private CopyTextTokenList<T> tokenList;
71
    
72
    private int startOffset;
73
    
74
    private char[] text;
75
    
76
    
77
    public SkimTokenList(CopyTextTokenList<T> tokenList, int startOffset, char[] text) {
78
        this.tokenList = tokenList;
79
        this.startOffset = startOffset;
80
        this.text = text;
81
    }
82
83
    public CopyTextTokenList<T> getTokenList() {
84
        return tokenList;
85
    }
86
    
87
    public int startOffset() {
88
        return tokenList.startOffset();
89
    }
90
    
91
    public int endOffset() {
92
        return tokenList.endOffset();
93
    }
94
    
95
    public boolean isRemoved() {
96
        return tokenList.isRemoved();
97
    }
98
99
    char[] getText() {
100
        return text;
101
    }
102
    
103
    void setText(char[] text) {
104
        this.text = text;
105
    }
106
107
    public int childTokenOffset(int rawOffset) {
108
        int offsetShift = (rawOffset >> 16);
109
        return startOffset + (rawOffset & 0xFFFF) + offsetShift;
110
    }
111
112
    public char childTokenCharAt(int rawOffset, int index) {
113
        return text[((rawOffset + index) & 0xFFFF)];
114
    }
115
116
    public int modCount() {
117
        return 0;
118
    }
119
120
    public Object tokenOrEmbeddingContainer(int index) {
121
        return tokenList.tokenOrEmbeddingContainer(index);
122
    }
123
    
124
    public AbstractToken<T> replaceFlyToken(
125
    int index, AbstractToken<T> flyToken, int offset) {
126
        return tokenList.replaceFlyToken(index, flyToken, offset);
127
    }
128
    
129
130
    public int lookahead(int index) {
131
        return tokenList.lookahead(index);
132
    }
133
134
    public Object state(int index) {
135
        return tokenList.state(index);
136
    }
137
138
    public int tokenOffset(int index) {
139
        return tokenList.tokenOffset(index);
140
    }
141
142
    public int tokenCount() {
143
        return tokenList.tokenCount();
144
    }
145
    
146
    public int tokenCountCurrent() {
147
        return tokenList.tokenCountCurrent();
148
    }
149
150
    public TokenList<?> root() {
151
        return tokenList.root();
152
    }
153
154
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
155
        return tokenList.tokenHierarchyOperation();
156
    }
157
    
158
    public LanguagePath languagePath() {
159
        return tokenList.languagePath();
160
    }
161
162
    public void wrapToken(int index, EmbeddingContainer embeddingContainer) {
163
        tokenList.wrapToken(index, embeddingContainer);
164
    }
165
166
    public InputAttributes inputAttributes() {
167
        return tokenList.inputAttributes();
168
    }
169
    
170
    public boolean isContinuous() {
171
        return tokenList.isContinuous();
172
    }
173
174
    public Set<T> skipTokenIds() {
175
        return tokenList.skipTokenIds();
176
    }
177
178
}
(-)a/lexer/src/org/netbeans/lib/lexer/batch/TextTokenList.java (-78 lines)
Removed Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.batch;
43
44
import java.util.Set;
45
import org.netbeans.api.lexer.Language;
46
import org.netbeans.lib.lexer.LexerInputOperation;
47
import org.netbeans.lib.lexer.TextLexerInputOperation;
48
import org.netbeans.api.lexer.InputAttributes;
49
import org.netbeans.api.lexer.TokenId;
50
import org.netbeans.lib.lexer.TokenHierarchyOperation;
51
52
53
/**
54
 * Batch token list over text expressed as character sequence.
55
 *
56
 * @author Miloslav Metelka
57
 * @version 1.00
58
 */
59
60
public final class TextTokenList<T extends TokenId> extends BatchTokenList<T> {
61
62
    private CharSequence inputText;
63
64
    public TextTokenList(TokenHierarchyOperation<?,T> tokenHierarchyOperation, CharSequence inputText,
65
    Language<T> language, Set<T> skipTokenIds, InputAttributes inputAttributes) {
66
        super(tokenHierarchyOperation, language, skipTokenIds, inputAttributes);
67
        this.inputText = inputText;
68
    }
69
    
70
    public char childTokenCharAt(int rawOffset, int index) {
71
        return inputText.charAt(rawOffset + index); // rawOffset is absolute
72
    }
73
    
74
    protected LexerInputOperation<T> createLexerInputOperation() {
75
        return new TextLexerInputOperation<T>(this, inputText);
76
    }
77
78
}
(-)a/lexer/src/org/netbeans/lib/lexer/inc/DocumentInput.java (-1 lines)
Lines 48-54 Link Here
48
import javax.swing.text.Document;
48
import javax.swing.text.Document;
49
import org.netbeans.api.lexer.InputAttributes;
49
import org.netbeans.api.lexer.InputAttributes;
50
import org.netbeans.api.lexer.Language;
50
import org.netbeans.api.lexer.Language;
51
import org.netbeans.api.lexer.TokenId;
52
import org.netbeans.lib.editor.util.swing.DocumentListenerPriority;
51
import org.netbeans.lib.editor.util.swing.DocumentListenerPriority;
53
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
52
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
54
import org.netbeans.lib.lexer.LanguageManager;
53
import org.netbeans.lib.lexer.LanguageManager;
(-)a/lexer/src/org/netbeans/lib/lexer/inc/FilterSnapshotTokenList.java (-11 / +21 lines)
Lines 46-53 Link Here
46
import org.netbeans.api.lexer.LanguagePath;
46
import org.netbeans.api.lexer.LanguagePath;
47
import org.netbeans.api.lexer.TokenId;
47
import org.netbeans.api.lexer.TokenId;
48
import org.netbeans.lib.lexer.EmbeddingContainer;
48
import org.netbeans.lib.lexer.EmbeddingContainer;
49
import org.netbeans.lib.lexer.LexerUtilsConstants;
49
import org.netbeans.lib.lexer.TokenHierarchyOperation;
50
import org.netbeans.lib.lexer.TokenHierarchyOperation;
50
import org.netbeans.lib.lexer.TokenList;
51
import org.netbeans.lib.lexer.TokenList;
52
import org.netbeans.lib.lexer.TokenOrEmbedding;
51
import org.netbeans.lib.lexer.token.AbstractToken;
53
import org.netbeans.lib.lexer.token.AbstractToken;
52
54
53
/**
55
/**
Lines 99-118 Link Here
99
        return tokenOffsetDiff;
101
        return tokenOffsetDiff;
100
    }
102
    }
101
    
103
    
102
    public Object tokenOrEmbeddingContainer(int index) {
104
    public TokenOrEmbedding<T> tokenOrEmbedding(int index) {
103
        return tokenList.tokenOrEmbeddingContainer(index);
105
        return tokenList.tokenOrEmbedding(index);
104
    }
106
    }
105
107
106
    public AbstractToken<T> replaceFlyToken(int index, AbstractToken<T> flyToken, int offset) {
108
    public AbstractToken<T> replaceFlyToken(int index, AbstractToken<T> flyToken, int offset) {
107
        return tokenList.replaceFlyToken(index, flyToken, offset);
109
        return tokenList.replaceFlyToken(index, flyToken, offset);
108
    }
110
    }
109
111
110
    public int tokenOffset(int index) {
112
    public int tokenOffsetByIndex(int index) {
111
        return tokenOffsetDiff + tokenList.tokenOffset(index);
113
        return tokenOffsetDiff + tokenList.tokenOffsetByIndex(index);
112
    }
114
    }
113
115
114
    public int modCount() {
116
    public int modCount() {
115
        return -1;
117
        return LexerUtilsConstants.MOD_COUNT_IMMUTABLE_INPUT;
116
    }
118
    }
117
119
118
    public int tokenCount() {
120
    public int tokenCount() {
Lines 127-137 Link Here
127
        return tokenList.languagePath();
129
        return tokenList.languagePath();
128
    }
130
    }
129
131
130
    public int childTokenOffset(int rawOffset) {
132
    public int tokenOffset(AbstractToken<T> token) {
131
        throw new IllegalStateException("Unexpected call.");
133
        return tokenList.tokenOffset(token);
132
    }
134
    }
133
135
134
    public char childTokenCharAt(int rawOffset, int index) {
136
    public int[] tokenIndex(int offset) {
137
        return LexerUtilsConstants.tokenIndexBinSearch(this, offset, tokenCount());
138
    }
139
140
    public char charAt(int offset) {
135
        throw new IllegalStateException("Unexpected call.");
141
        throw new IllegalStateException("Unexpected call.");
136
    }
142
    }
137
143
Lines 139-148 Link Here
139
        tokenList.wrapToken(index, embeddingContainer);
145
        tokenList.wrapToken(index, embeddingContainer);
140
    }
146
    }
141
147
142
    public TokenList<?> root() {
148
    public TokenList<?> rootTokenList() {
143
        return tokenList.root();
149
        return tokenList.rootTokenList();
144
    }
150
    }
145
    
151
152
    public CharSequence inputSourceText() {
153
        return rootTokenList().inputSourceText();
154
    }
155
146
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
156
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
147
        return tokenList.tokenHierarchyOperation();
157
        return tokenList.tokenHierarchyOperation();
148
    }
158
    }
(-)a/lexer/src/org/netbeans/lib/lexer/inc/IncTokenList.java (-101 / +98 lines)
Lines 54-64 Link Here
54
import org.netbeans.lib.lexer.LexerUtilsConstants;
54
import org.netbeans.lib.lexer.LexerUtilsConstants;
55
import org.netbeans.api.lexer.InputAttributes;
55
import org.netbeans.api.lexer.InputAttributes;
56
import org.netbeans.api.lexer.Language;
56
import org.netbeans.api.lexer.Language;
57
import org.netbeans.api.lexer.Token;
58
import org.netbeans.api.lexer.TokenId;
57
import org.netbeans.api.lexer.TokenId;
59
import org.netbeans.lib.lexer.TokenHierarchyOperation;
58
import org.netbeans.lib.lexer.TokenHierarchyOperation;
60
import org.netbeans.lib.lexer.token.AbstractToken;
59
import org.netbeans.lib.lexer.token.AbstractToken;
61
import org.netbeans.lib.lexer.token.TextToken;
60
import org.netbeans.lib.lexer.token.TextToken;
61
import org.netbeans.lib.lexer.TokenOrEmbedding;
62
import org.netbeans.spi.lexer.MutableTextInput;
62
import org.netbeans.spi.lexer.MutableTextInput;
63
63
64
64
Lines 80-92 Link Here
80
 */
80
 */
81
81
82
public final class IncTokenList<T extends TokenId>
82
public final class IncTokenList<T extends TokenId>
83
extends FlyOffsetGapList<Object> implements MutableTokenList<T> {
83
extends FlyOffsetGapList<TokenOrEmbedding<T>> implements MutableTokenList<T> {
84
    
84
    
85
    private final TokenHierarchyOperation<?,T> tokenHierarchyOperation;
85
    private final TokenHierarchyOperation<?,T> tokenHierarchyOperation;
86
86
87
    private LanguagePath languagePath;
87
    private LanguagePath languagePath;
88
    
88
    
89
    private CharSequence text;
89
    private CharSequence inputSourceText;
90
    
90
    
91
    /**
91
    /**
92
     * Lexer input operation used for lexing of the input.
92
     * Lexer input operation used for lexing of the input.
Lines 109-138 Link Here
109
    public void reinit() {
109
    public void reinit() {
110
        if (languagePath != null) {
110
        if (languagePath != null) {
111
            MutableTextInput input = tokenHierarchyOperation.mutableTextInput();
111
            MutableTextInput input = tokenHierarchyOperation.mutableTextInput();
112
            this.text = LexerSpiPackageAccessor.get().text(input);
112
            this.inputSourceText = LexerSpiPackageAccessor.get().text(input);
113
            this.lexerInputOperation = new TextLexerInputOperation<T>(this, text);
113
            this.lexerInputOperation = new TextLexerInputOperation<T>(this);
114
        } else {
114
        } else {
115
            this.text = null;
115
            this.inputSourceText = null;
116
            releaseLexerInputOperation();
116
            releaseLexerInputOperation();
117
        }
117
        }
118
        this.laState = LAState.empty();
118
        this.laState = LAState.empty();
119
    }
119
    }
120
    
120
    
121
    private void releaseLexerInputOperation() {
121
    public void releaseLexerInputOperation() {
122
        if (lexerInputOperation != null)
122
        if (lexerInputOperation != null) {
123
            lexerInputOperation.release();
123
            lexerInputOperation.release();
124
            lexerInputOperation = null;
125
        }
124
    }
126
    }
125
127
126
    public void refreshLexerInputOperation() {
127
        releaseLexerInputOperation();
128
        int lastTokenIndex = tokenCountCurrent() - 1;
129
        lexerInputOperation = createLexerInputOperation(
130
                lastTokenIndex + 1,
131
                existingTokensEndOffset(),
132
                (lastTokenIndex >= 0) ? state(lastTokenIndex) : null
133
        );
134
    }
135
    
136
    public LanguagePath languagePath() {
128
    public LanguagePath languagePath() {
137
        return languagePath;
129
        return languagePath;
138
    }
130
    }
Lines 152-179 Link Here
152
    
144
    
153
    public synchronized int tokenCount() {
145
    public synchronized int tokenCount() {
154
        if (lexerInputOperation != null) { // still lexing
146
        if (lexerInputOperation != null) { // still lexing
155
            tokenOrEmbeddingContainerImpl(Integer.MAX_VALUE);
147
            tokenOrEmbeddingImpl(Integer.MAX_VALUE);
156
        }
148
        }
157
        return size();
149
        return size();
158
    }
150
    }
159
151
160
    public char childTokenCharAt(int rawOffset, int index) {
152
    public int tokenOffset(AbstractToken<T> token) {
161
        index += childTokenOffset(rawOffset);
153
        int rawOffset = token.rawOffset();
162
        return text.charAt(index);
163
    }
164
    
165
    public int childTokenOffset(int rawOffset) {
166
        return (rawOffset < offsetGapStart()
154
        return (rawOffset < offsetGapStart()
167
                ? rawOffset
155
                ? rawOffset
168
                : rawOffset - offsetGapLength());
156
                : rawOffset - offsetGapLength());
169
    }
157
    }
170
    
158
171
    public int tokenOffset(int index) {
159
    public int tokenOffsetByIndex(int index) {
172
        return elementOffset(index);
160
        return elementOffset(index);
173
    }
161
    }
174
    
162
    
175
    public int existingTokensEndOffset() {
163
    public int[] tokenIndex(int offset) {
176
        return elementOrEndOffset(tokenCountCurrent());
164
        return LexerUtilsConstants.tokenIndexLazyTokenCreation(this, offset);
177
    }
165
    }
178
166
179
    /**
167
    /**
Lines 188-208 Link Here
188
        rootModCount++;
176
        rootModCount++;
189
    }
177
    }
190
    
178
    
191
    public synchronized Object tokenOrEmbeddingContainer(int index) {
179
    public synchronized TokenOrEmbedding<T> tokenOrEmbedding(int index) {
192
        return tokenOrEmbeddingContainerImpl(index);
180
        return tokenOrEmbeddingImpl(index);
193
    }
181
    }
194
    
182
    
195
    private Object tokenOrEmbeddingContainerImpl(int index) {
183
    private TokenOrEmbedding<T> tokenOrEmbeddingImpl(int index) {
196
        while (lexerInputOperation != null && index >= size()) {
184
        while (lexerInputOperation != null && index >= size()) {
197
            Token token = lexerInputOperation.nextToken();
185
            AbstractToken<T> token = lexerInputOperation.nextToken();
198
            if (token != null) { // lexer returned valid token
186
            if (token != null) { // lexer returned valid token
199
                updateElementOffsetAdd(token);
187
                updateElementOffsetAdd(token);
200
                add(token);
188
                add(token);
201
                laState = laState.add(lexerInputOperation.lookahead(),
189
                laState = laState.add(lexerInputOperation.lookahead(),
202
                        lexerInputOperation.lexerState());
190
                        lexerInputOperation.lexerState());
203
            } else { // no more tokens from lexer
191
            } else { // no more tokens from lexer
204
                lexerInputOperation.release();
192
                releaseLexerInputOperation();
205
                lexerInputOperation = null;
206
                trimToSize();
193
                trimToSize();
207
                laState.trimToSize();
194
                laState.trimToSize();
208
            }
195
            }
Lines 217-223 Link Here
217
        return nonFlyToken;
204
        return nonFlyToken;
218
    }
205
    }
219
206
220
    public synchronized void wrapToken(int index, EmbeddingContainer embeddingContainer) {
207
    public synchronized void wrapToken(int index, EmbeddingContainer<T> embeddingContainer) {
221
        set(index, embeddingContainer);
208
        set(index, embeddingContainer);
222
    }
209
    }
223
210
Lines 225-260 Link Here
225
        return LexerSpiPackageAccessor.get().inputAttributes(tokenHierarchyOperation.mutableTextInput());
212
        return LexerSpiPackageAccessor.get().inputAttributes(tokenHierarchyOperation.mutableTextInput());
226
    }
213
    }
227
    
214
    
228
    protected int elementRawOffset(Object elem) {
215
    protected int elementRawOffset(TokenOrEmbedding<T> elem) {
229
        return LexerUtilsConstants.token(elem).rawOffset();
216
        return elem.token().rawOffset();
230
    }
217
    }
231
 
218
 
232
    protected void setElementRawOffset(Object elem, int rawOffset) {
219
    protected void setElementRawOffset(TokenOrEmbedding<T> elem, int rawOffset) {
233
        LexerUtilsConstants.token(elem).setRawOffset(rawOffset);
220
        elem.token().setRawOffset(rawOffset);
234
    }
221
    }
235
    
222
    
236
    protected boolean isElementFlyweight(Object elem) {
223
    protected boolean isElementFlyweight(TokenOrEmbedding<T> elem) {
237
        // token wrapper always contains non-flyweight token
224
        // token wrapper always contains non-flyweight token
238
        return (elem.getClass() != EmbeddingContainer.class)
225
        return (elem.embedding() == null)
239
            && ((AbstractToken)elem).isFlyweight();
226
            && elem.token().isFlyweight();
240
    }
227
    }
241
    
228
    
242
    protected int elementLength(Object elem) {
229
    protected int elementLength(TokenOrEmbedding<T> elem) {
243
        return LexerUtilsConstants.token(elem).length();
230
        return elem.token().length();
244
    }
231
    }
245
    
232
    
246
    private AbstractToken<T> existingToken(int index) {
233
    public TokenOrEmbedding<T> tokenOrEmbeddingUnsync(int index) {
247
        // Must use synced tokenOrEmbeddingContainer() because of possible change
248
        // of the underlying list impl when adding lazily requested tokens
249
        return LexerUtilsConstants.token(tokenOrEmbeddingContainer(index));
250
    }
251
252
    public Object tokenOrEmbeddingContainerUnsync(int index) {
253
        // Solely for token list updater or token hierarchy snapshots
234
        // Solely for token list updater or token hierarchy snapshots
254
        // having single-threaded exclusive write access
235
        // having single-threaded exclusive write access
255
        return get(index);
236
        return get(index);
256
    }
237
    }
257
    
238
258
    public int lookahead(int index) {
239
    public int lookahead(int index) {
259
        return laState.lookahead(index);
240
        return laState.lookahead(index);
260
    }
241
    }
Lines 267-274 Link Here
267
        return size();
248
        return size();
268
    }
249
    }
269
250
270
    public TokenList<?> root() {
251
    public TokenList<?> rootTokenList() {
271
        return this;
252
        return this;
253
    }
254
255
    public CharSequence inputSourceText() {
256
        return inputSourceText;
272
    }
257
    }
273
258
274
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
259
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
Lines 277-350 Link Here
277
    
262
    
278
    public LexerInputOperation<T> createLexerInputOperation(
263
    public LexerInputOperation<T> createLexerInputOperation(
279
    int tokenIndex, int relexOffset, Object relexState) {
264
    int tokenIndex, int relexOffset, Object relexState) {
265
        // Possibly release unfinished lexing - will be restarted in replaceTokens()
266
        // Releasing the lexer now allows to share a single backing lexer's impl instance better.
267
        // Do not assign null to lexerInputOperation since the replaceTokens() would not know
268
        // that the lexing was unfinished.
269
        if (lexerInputOperation != null)
270
            lexerInputOperation.release();
271
280
        // Used for mutable lists only so maintain LA and state
272
        // Used for mutable lists only so maintain LA and state
281
        return new TextLexerInputOperation<T>(this, tokenIndex, relexState,
273
        return new TextLexerInputOperation<T>(this, tokenIndex, relexState,
282
                text, 0, relexOffset, text.length());
274
                relexOffset, inputSourceText.length());
283
    }
275
    }
284
276
285
    public boolean isFullyLexed() {
277
    public boolean isFullyLexed() {
286
        return (lexerInputOperation == null);
278
        return (lexerInputOperation == null);
287
    }
279
    }
288
280
289
    public void replaceTokens(TokenListChange<T> change, int removeTokenCount, int diffLength) {
281
    public void replaceTokens(TokenListChange<T> change, int diffLength) {
290
        int index = change.index();
282
        int index = change.index();
291
        // Remove obsolete tokens (original offsets are retained)
283
        // Remove obsolete tokens (original offsets are retained)
292
        Object[] removedTokensOrEmbeddingContainers = new Object[removeTokenCount];
284
        int removeTokenCount = change.removedTokenCount();
293
        copyElements(index, index + removeTokenCount, removedTokensOrEmbeddingContainers, 0);
285
        AbstractToken<T> firstRemovedToken = null;
294
        int offset = change.offset();
286
        if (removeTokenCount > 0) {
295
        for (int i = 0; i < removeTokenCount; i++) {
287
            @SuppressWarnings("unchecked")
296
            Object tokenOrEmbeddingContainer = removedTokensOrEmbeddingContainers[i];
288
            TokenOrEmbedding<T>[] removedTokensOrEmbeddings = new TokenOrEmbedding[removeTokenCount];
297
            AbstractToken<?> token;
289
            copyElements(index, index + removeTokenCount, removedTokensOrEmbeddings, 0);
298
            // It's necessary to update-status of all removed tokens' contained embeddings
290
            firstRemovedToken = removedTokensOrEmbeddings[0].token();
299
            // since otherwise (if they would not be up-to-date) they could not be updated later
291
            for (int i = 0; i < removeTokenCount; i++) {
300
            // as they lose their parent token list which the update-status relies on.
292
                TokenOrEmbedding<T> tokenOrEmbedding = removedTokensOrEmbeddings[i];
301
            if (tokenOrEmbeddingContainer.getClass() == EmbeddingContainer.class) {
293
                // It's necessary to update-status of all removed tokens' contained embeddings
302
                EmbeddingContainer<?> ec = (EmbeddingContainer<?>)tokenOrEmbeddingContainer;
294
                // since otherwise (if they would not be up-to-date) they could not be updated later
303
                ec.updateStatusAndInvalidate();
295
                // as they lose their parent token list which the update-status relies on.
304
                token = ec.token();
296
                EmbeddingContainer<T> ec = tokenOrEmbedding.embedding();
305
            } else { // Regular token
297
                if (ec != null) {
306
                token = (AbstractToken<?>)tokenOrEmbeddingContainer;
298
                    assert (ec.cachedModCount() != rootModCount) : "ModCount already updated"; // NOI18N
299
                    ec.updateStatusUnsyncAndMarkRemoved();
300
                }
301
                AbstractToken<T> token = tokenOrEmbedding.token();
302
                if (!token.isFlyweight()) {
303
                    updateElementOffsetRemove(token);
304
                    token.setTokenList(null);
305
                }
307
            }
306
            }
308
            if (!token.isFlyweight()) {
307
            remove(index, removeTokenCount); // Retain original offsets
309
                updateElementOffsetRemove(token);
308
            laState.remove(index, removeTokenCount); // Remove lookaheads and states
310
                token.setTokenList(null);
309
            change.setRemovedTokens(removedTokensOrEmbeddings);
311
            }
312
            offset += token.length();
313
        }
310
        }
314
        remove(index, removeTokenCount); // Retain original offsets
315
        laState.remove(index, removeTokenCount); // Remove lookaheads and states
316
        change.setRemovedTokens(removedTokensOrEmbeddingContainers);
317
        change.setRemovedEndOffset(offset);
318
311
319
        // Move and fix the gap according to the performed modification.
312
        // Move and fix the gap according to the performed modification.
313
        // Instead of modOffset the gap is located at first relexed token's start
314
        // because then the already precomputed index corresponding to the given offset
315
        // can be reused. Otherwise there would have to be another binary search for index.
320
        if (offsetGapStart() != change.offset()) {
316
        if (offsetGapStart() != change.offset()) {
321
            // Minimum of the index of the first removed index and original computed index
317
            // Minimum of the index of the first removed index and original computed index
322
            moveOffsetGap(change.offset(), Math.min(index, change.offsetGapIndex()));
318
            moveOffsetGap(change.offset(), change.index());
323
        }
319
        }
324
        updateOffsetGapLength(-diffLength);
320
        updateOffsetGapLength(-diffLength);
325
321
326
        // Add created tokens.
322
        // Add created tokens.
327
        List<Object> addedTokensOrBranches = change.addedTokensOrBranches();
323
        List<TokenOrEmbedding<T>> addedTokensOrEmbeddings = change.addedTokenOrEmbeddings();
328
        if (addedTokensOrBranches != null) {
324
        if (addedTokensOrEmbeddings != null && addedTokensOrEmbeddings.size() > 0) {
329
            for (Object tokenOrBranch : addedTokensOrBranches) {
325
            for (TokenOrEmbedding<T> tokenOrEmbedding : addedTokensOrEmbeddings) {
330
                @SuppressWarnings("unchecked")
326
                updateElementOffsetAdd(tokenOrEmbedding.token());
331
                AbstractToken<T> token = (AbstractToken<T>)tokenOrBranch;
332
                updateElementOffsetAdd(token);
333
            }
327
            }
334
            addAll(index, addedTokensOrBranches);
328
            addAll(index, addedTokensOrEmbeddings);
335
            laState = laState.addAll(index, change.laState());
329
            laState = laState.addAll(index, change.laState());
336
            change.syncAddedTokenCount();
330
            change.syncAddedTokenCount();
337
            // Check for bounds change only
331
            // Check for bounds change only
338
            if (removeTokenCount == 1 && addedTokensOrBranches.size() == 1) {
332
            if (removeTokenCount == 1 && addedTokensOrEmbeddings.size() == 1) {
339
                // Compare removed and added token ids and part types
333
                // Compare removed and added token ids and part types
340
                AbstractToken<T> removedToken = LexerUtilsConstants.token(removedTokensOrEmbeddingContainers[0]);
341
                AbstractToken<T> addedToken = change.addedToken(0);
334
                AbstractToken<T> addedToken = change.addedToken(0);
342
                if (removedToken.id() == addedToken.id()
335
                if (firstRemovedToken.id() == addedToken.id()
343
                    && removedToken.partType() == addedToken.partType()
336
                    && firstRemovedToken.partType() == addedToken.partType()
344
                ) {
337
                ) {
345
                    change.markBoundsChange();
338
                    change.markBoundsChange();
346
                }
339
                }
347
            }
340
            }
341
        }
342
343
        // Possibly restart unfinished lexing
344
        if (this.lexerInputOperation != null) { // Lexing was not finished before update
345
            int tokenCount = tokenCountCurrent();
346
            lexerInputOperation = createLexerInputOperation(tokenCount, elementOrEndOffset(tokenCount),
347
                (tokenCount > 0) ? state(tokenCount - 1) : null);
348
        }
348
        }
349
    }
349
    }
350
    
350
    
Lines 356-371 Link Here
356
        return null;
356
        return null;
357
    }
357
    }
358
358
359
    @Override
359
    public int startOffset() {
360
    public int startOffset() {
360
        return 0;
361
        return 0;
361
    }
362
    }
362
363
363
    public int endOffset() {
364
    public int endOffset() {
364
        return text.length();
365
        return inputSourceText.length();
365
    }
366
    }
366
367
367
    public boolean isRemoved() {
368
    public boolean isRemoved() {
368
        return false; // Should never become removed
369
        return false; // Should never become removed
370
    }
371
372
    public void setInputSourceText(CharSequence text) {
373
        this.inputSourceText = text;
369
    }
374
    }
370
375
371
    @Override
376
    @Override
Lines 373-384 Link Here
373
        return LexerUtilsConstants.appendTokenList(null, this).toString();
378
        return LexerUtilsConstants.appendTokenList(null, this).toString();
374
    }
379
    }
375
    
380
    
376
    public CharSequence text() {
377
        return text;
378
    }
379
    
380
    public void setText(CharSequence text) {
381
        this.text = text;
382
    }
383
    
384
}
381
}
(-)06a7890f802e (+282 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.inc;
43
44
import java.util.ArrayList;
45
import java.util.List;
46
import org.netbeans.api.lexer.TokenId;
47
import org.netbeans.lib.lexer.EmbeddedJoinInfo;
48
import org.netbeans.lib.lexer.EmbeddedTokenList;
49
import org.netbeans.lib.lexer.JoinLexerInputOperation;
50
import org.netbeans.lib.lexer.JoinTokenListBase;
51
import org.netbeans.lib.lexer.token.AbstractToken;
52
import org.netbeans.lib.lexer.token.JoinToken;
53
import org.netbeans.lib.lexer.token.PartToken;
54
55
/**
56
 * Token list change for join token lists.
57
 *
58
 * @author Miloslav Metelka
59
 */
60
final class JoinTokenListChange<T extends TokenId> extends TokenListChange<T> {
61
    
62
    /** ETL where character modification occurred. */
63
    EmbeddedTokenList<T> charModTokenList;
64
    
65
    private TokenListListUpdate<T> tokenListListUpdate;
66
67
    private int startRelexTokenListIndex;
68
69
    private List<RelexTokenListChange<T>> relexChanges;
70
    
71
    private JoinLexerInputOperation<T> joinLexerInputOperation;
72
    
73
    public JoinTokenListChange(MutableJoinTokenList<T> tokenList) {
74
        super(tokenList);
75
    }
76
77
    public List<? extends TokenListChange<T>> relexChanges() {
78
        return relexChanges;
79
    }
80
81
    public TokenListListUpdate<T> tokenListListUpdate() {
82
        return tokenListListUpdate;
83
    }
84
    
85
    public void setTokenListListUpdate(TokenListListUpdate<T> tokenListListUpdate) {
86
        this.tokenListListUpdate = tokenListListUpdate;
87
    }
88
    
89
    public void setStartInfo(JoinLexerInputOperation<T> joinLexerInputOperation, int localIndex) {
90
        this.joinLexerInputOperation = joinLexerInputOperation;
91
        this.startRelexTokenListIndex = joinLexerInputOperation.activeTokenListIndex();
92
        this.relexChanges = new ArrayList<RelexTokenListChange<T>>(
93
                tokenListListUpdate.addedTokenLists.size() + 3);
94
        // Add first change now to incorporate starting modified token index
95
        RelexTokenListChange<T> firstChange = new RelexTokenListChange<T>(
96
                joinLexerInputOperation.tokenList(startRelexTokenListIndex));
97
        // Set index in ETL to properly do replaceTokens() in ETL
98
        // Setting both index and offset is BTW necessary in order to properly move offset gap in ETL
99
        firstChange.setIndex(localIndex);
100
        int relexOffset = joinLexerInputOperation.lastTokenEndOffset();
101
        firstChange.setOffset(relexOffset);
102
        firstChange.setMatchOffset(relexOffset); // Due to removeLastAddedToken() and etc.
103
        relexChanges.add(firstChange);
104
    }
105
106
    @Override
107
    public void addToken(AbstractToken<T> token, int lookahead, Object state) {
108
        // Check if lexer-input-operation advanced to next list and possibly add corresponding relex change(s)
109
        int activeTokenListIndex = joinLexerInputOperation.activeTokenListIndex();
110
        while (startRelexTokenListIndex + relexChanges.size() <= activeTokenListIndex) {
111
            // Use JLIO.tokenList() since it already contains the removed/added ETLs.
112
            EmbeddedTokenList<T> etl = joinLexerInputOperation.tokenList(
113
                    startRelexTokenListIndex + relexChanges.size());
114
            RelexTokenListChange<T> relexChange = new RelexTokenListChange<T>(etl);
115
            int startOffset = etl.startOffset();
116
            relexChange.setOffset(startOffset);
117
            relexChanges.add(relexChange);
118
        }
119
        int relexChangeIndex = activeTokenListIndex - startRelexTokenListIndex;
120
        if (token.getClass() == JoinToken.class) {
121
            JoinToken<T> joinToken = (JoinToken<T>) token;
122
            List<PartToken<T>> joinedParts = joinToken.joinedParts();
123
            int extraTokenListSpanCount = joinToken.extraTokenListSpanCount();
124
            int startRelexChangeIndex = relexChangeIndex - extraTokenListSpanCount;
125
            int joinedPartIndex = 0;
126
            // Only add without the last part (will be added normally outside the loop)
127
            // The last ETL can not be empty (must contain the last non-empty token part)
128
            for (int i = 0; i < extraTokenListSpanCount; i++) {
129
                RelexTokenListChange<T> relexChange = relexChanges.get(startRelexChangeIndex + i);
130
                // Check whether token list is non-empty by checking a text length that it covers.
131
                // Do not use etl.tokenCount() since the tokens are just being added into ETL.
132
                EmbeddedTokenList<T> etl = (EmbeddedTokenList<T>) relexChange.tokenList();
133
                if (etl.textLength() > 0) {
134
                    PartToken<T> partToken = joinedParts.get(joinedPartIndex++);
135
                    relexChange.addToken(partToken, 0, null);
136
                }
137
                relexChange.joinTokenLastPartShift = extraTokenListSpanCount - i;
138
            }
139
            // Last part will be added normally by subsequent code
140
            token = joinedParts.get(joinedPartIndex); // Should be (joinedParts.size()-1)
141
        }
142
        RelexTokenListChange<T> relexChange = relexChanges.get(relexChangeIndex);
143
        relexChange.addToken(token, lookahead, state);
144
    }
145
    
146
    @Override
147
    public AbstractToken<T> removeLastAddedToken() {
148
        RelexTokenListChange<T> lastRelexChange;
149
        AbstractToken<T> lastAddedToken = null;
150
        do {
151
            lastRelexChange = relexChanges.get(relexChanges.size() - 1);
152
            int tokenCount = lastRelexChange.tokenList().tokenCountCurrent();
153
            if (lastRelexChange.tokenList().tokenCountCurrent() > 0) { // There might be empty ETLs
154
                lastAddedToken = lastRelexChange.removeLastAddedToken();
155
                tokenCount--;
156
            }
157
            if (tokenCount == 0) {
158
                relexChanges.remove(relexChanges.size() - 1);
159
            }
160
        } while (lastAddedToken.getClass() != PartToken.class ||
161
                ((PartToken<T>) lastAddedToken).joinToken().joinedParts().get(0) != lastAddedToken);
162
        matchIndex--;
163
        matchOffset = lastRelexChange.matchOffset;
164
        // If lastAddedToken is PartToken then its joinToken() should be returned
165
        // but the return value should currently be ignored
166
        return lastAddedToken;
167
    }
168
    
169
    public void replaceTokens(int diffLength) {
170
        // Determine position of matchIndex in token lists
171
        // if matchIndex == jtl.tokenCount() the token list index will be the last list
172
        //   and endLocalIndex will be its tokenCount(). Because of this
173
        //   there must be a check whether token list index is not among removed ETLs.
174
        MutableJoinTokenList<T> jtl = (MutableJoinTokenList<T>) tokenList();
175
        int endLocalIndex = jtl.tokenStartLocalIndex(matchIndex);
176
        int matchTokenListIndex = jtl.activeTokenListIndex();
177
        if (matchTokenListIndex >= tokenListListUpdate.modTokenListIndex + tokenListListUpdate.removedTokenListCount) {
178
            assert (matchIndex == jtl.tokenCountCurrent()); // Should only happen in this situation
179
            // Project into relexChanges
180
            matchTokenListIndex += tokenListListUpdate.tokenListCountDiff();
181
            relexChanges.get(matchTokenListIndex - startRelexTokenListIndex).setMatchIndex(endLocalIndex);
182
            int afterAddIndex = tokenListListUpdate.modTokenListIndex + tokenListListUpdate.addedTokenLists.size();
183
            while (--matchTokenListIndex >= afterAddIndex) {
184
                TokenListChange<T> change = relexChanges.get(matchTokenListIndex - startRelexTokenListIndex);
185
                change.setMatchIndex(change.tokenList().tokenCountCurrent());
186
            }
187
        }
188
        // Fill in the below-mod-ETLs area
189
        int index = tokenListListUpdate.modTokenListIndex;
190
        while (--index >= startRelexTokenListIndex) {
191
            TokenListChange<T> change = relexChanges.get(index - startRelexTokenListIndex);
192
            change.setMatchIndex(change.tokenList().tokenCountCurrent());
193
        }
194
195
        // Physically replace the token lists
196
        JoinTokenListBase base = jtl.base();
197
        if (tokenListListUpdate.isTokenListsMod()) {
198
            // Move gap after last ETL that was relexed (obsolete ETLs still not removed)
199
            int relexEndIndex = startRelexTokenListIndex + relexChanges.size();
200
            int relexEndOldIndex = relexEndIndex - tokenListListUpdate.tokenListCountDiff();
201
            base.moveIndexGap(jtl.tokenListList(), jtl.tokenListStartIndex(), relexEndOldIndex);
202
            // Do physical ETLs replace
203
            jtl.tokenListList().replace(jtl.tokenListStartIndex() + tokenListListUpdate.modTokenListIndex,
204
                    tokenListListUpdate.removedTokenListCount, tokenListListUpdate.addedTokenLists);
205
            base.tokenListModNotify(tokenListListUpdate.tokenListCountDiff());
206
        }
207
208
        // Remember join token count right before the first relexed ETL
209
        int joinTokenIndex;
210
        if (startRelexTokenListIndex > 0) {
211
            EmbeddedTokenList<T> etl = jtl.tokenList(startRelexTokenListIndex - 1);
212
            joinTokenIndex = etl.joinInfo.joinTokenIndex() + etl.joinTokenCount(); // Physical removal already performed
213
        } else {
214
            joinTokenIndex = 0;
215
        }
216
        // Now process each relex change and update join token count etc.
217
        int relexChangesSizeM1 = relexChanges.size() - 1;
218
        int i;
219
        for (i = 0; i <= relexChangesSizeM1; i++) {
220
            RelexTokenListChange<T> change = relexChanges.get(i);
221
            EmbeddedTokenList<T> etl = (EmbeddedTokenList<T>) change.tokenList();
222
            if (etl.joinInfo == null) {
223
                etl.joinInfo = new EmbeddedJoinInfo(base, joinTokenIndex, startRelexTokenListIndex + i);
224
            } else {
225
                etl.joinInfo.setRawJoinTokenIndex(joinTokenIndex);
226
            }
227
            // Set new joinTokenLastPartShift before calling etl.joinTokenCount()
228
            etl.joinInfo.setJoinTokenLastPartShift(change.joinTokenLastPartShift);
229
            // Replace tokens in the individual ETL
230
            int realDiffLength = (etl == charModTokenList) ? diffLength : 0;
231
            etl.replaceTokens(change, realDiffLength);
232
            // Fix join token count
233
            joinTokenIndex += etl.joinTokenCount();
234
        }
235
        
236
        // Now fix the total join token count
237
        int origJoinTokenIndex = (i < jtl.tokenListCount())
238
                ? jtl.tokenList(i).joinInfo.joinTokenIndex()
239
                : base.joinTokenCount();
240
        int joinTokenCountDiff = joinTokenIndex - origJoinTokenIndex;
241
        base.updateJoinTokenCount(joinTokenCountDiff);
242
        
243
        if (relexChangesSizeM1 == 0) { // Only changed single ETL
244
            if (relexChanges.get(0).isBoundsChange()) {
245
                markBoundsChange(); // Joined change treated as bounds change too
246
            }
247
        }
248
    }
249
250
    @Override
251
    public String toString() {
252
        return super.toString() + ", tokenListListUpdate=" + tokenListListUpdate + // NOI18N
253
                ", startRelexTokenListIndex=" + startRelexTokenListIndex + // NOI18N
254
                ", relexChanges.size()=" + relexChanges.size();
255
    }
256
257
    @Override
258
    public String toStringMods(int indent) {
259
        StringBuilder sb = new StringBuilder(100);
260
        for (RelexTokenListChange change : relexChanges) {
261
            sb.append(change.toStringMods(indent));
262
            sb.append('\n');
263
        }
264
        return sb.toString();
265
    }
266
    
267
    static final class RelexTokenListChange<T extends TokenId> extends TokenListChange<T> {
268
        
269
        int joinTokenLastPartShift; // New value for EmbeddedJoinInfo.joinTokenLastPartShift during relex
270
271
        RelexTokenListChange(EmbeddedTokenList<T> tokenList) {
272
            super(tokenList);
273
        }
274
275
        @Override
276
        public String toString() {
277
            return super.toString() + ", lps=" + joinTokenLastPartShift;
278
        }
279
        
280
    }
281
282
}
(-)06a7890f802e (+93 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.inc;
43
44
import org.netbeans.api.lexer.TokenId;
45
import org.netbeans.lib.lexer.EmbeddedTokenList;
46
import org.netbeans.lib.lexer.JoinLexerInputOperation;
47
import org.netbeans.lib.lexer.JoinTokenList;
48
49
/**
50
 * Lexer input operation over multiple joined sections (embedded token lists).
51
 * <br/>
52
 * It produces regular tokens (to be added directly into ETL represented by
53
 * {@link #activeTokenList()} and also special {@link #JoinToken} instances
54
 * in case a token spans boundaries of multiple ETLs.
55
 * <br/>
56
 * It can either work over JoinTokenList directly or, during a modification,
57
 * it simulates that certain token lists are already removed/added to underlying token list.
58
 * <br/>
59
 * 
60
 * {@link #recognizedTokenLastInTokenList()} gives information whether the lastly
61
 * produced token ends right at boundary of the activeTokenList.
62
 *
63
 * @author Miloslav Metelka
64
 * @version 1.00
65
 */
66
67
class MutableJoinLexerInputOperation<T extends TokenId> extends JoinLexerInputOperation<T> {
68
    
69
    private TokenListListUpdate<T> tokenListListUpdate;
70
71
    MutableJoinLexerInputOperation(JoinTokenList<T> joinTokenList, int relexJoinIndex, Object lexerRestartState,
72
            int activeTokenListIndex, int relexOffset, TokenListListUpdate<T> tokenListListUpdate
73
    ) {
74
        super(joinTokenList, relexJoinIndex, lexerRestartState, activeTokenListIndex, relexOffset);
75
        this.tokenListListUpdate = tokenListListUpdate;
76
    }
77
78
    @Override
79
    public EmbeddedTokenList<T> tokenList(int tokenListIndex) {
80
        return tokenListListUpdate.afterUpdateTokenList((JoinTokenList<T>) tokenList, tokenListIndex);
81
    }
82
83
    @Override
84
    protected int tokenListCount() {
85
        return tokenListListUpdate.afterUpdateTokenListCount((JoinTokenList<T>) tokenList);
86
    }
87
88
    @Override
89
    public String toString() {
90
        return super.toString() + ", tokenListListUpdate: " + tokenListListUpdate; // NOI18N
91
    }
92
93
}
(-)06a7890f802e (+96 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.inc;
43
44
import org.netbeans.api.lexer.TokenId;
45
import org.netbeans.lib.lexer.EmbeddedTokenList;
46
import org.netbeans.lib.lexer.JoinTokenList;
47
import org.netbeans.lib.lexer.JoinTokenListBase;
48
import org.netbeans.lib.lexer.LexerInputOperation;
49
import org.netbeans.lib.lexer.TokenListList;
50
import org.netbeans.lib.lexer.TokenOrEmbedding;
51
52
53
/**
54
 * Mutable join token list allows mutations by token list updater.
55
 * 
56
 * @author Miloslav Metelka
57
 */
58
59
class MutableJoinTokenList<T extends TokenId> extends JoinTokenList<T> implements MutableTokenList<T> {
60
    
61
    static <T extends TokenId> MutableJoinTokenList<T> create(TokenListList<T> tokenListList, int etlIndex, EmbeddedTokenList<T> etl) {
62
        int tokenListStartIndex = etlIndex - etl.joinInfo.tokenListIndex();
63
        MutableJoinTokenList<T> jtl = new MutableJoinTokenList<T>(tokenListList, etl.joinInfo.base, tokenListStartIndex);
64
        // Position to this etl's join index
65
        jtl.setActiveTokenListIndex(etlIndex - tokenListStartIndex);
66
        return jtl;
67
    }
68
69
    MutableJoinTokenList(TokenListList<T> tokenListList, JoinTokenListBase base, int tokenListStartIndex) {
70
        super(tokenListList, base, tokenListStartIndex);
71
    }
72
73
    public TokenOrEmbedding<T> tokenOrEmbeddingUnsync(int index) {
74
        return tokenOrEmbedding(index);
75
    }
76
77
    public boolean isFullyLexed() {
78
        return true;
79
    }
80
81
    public void replaceTokens(TokenListChange<T> change, int diffLength) {
82
        ((JoinTokenListChange<T>) change).replaceTokens(diffLength);
83
    }
84
85
    public LexerInputOperation<T> createLexerInputOperation(int tokenIndex, int relexOffset, Object relexState) {
86
        // Should never be called
87
        throw new IllegalStateException("Should never be called"); // NOI18N
88
    }
89
90
    public void resetActiveAfterUpdate() { // Update the active token list after updating
91
        activeTokenListIndex = 0;
92
        fetchActiveTokenListData();
93
    }
94
95
}
96
(-)a/lexer/src/org/netbeans/lib/lexer/inc/MutableTokenList.java (-3 / +4 lines)
Lines 44-49 Link Here
44
import org.netbeans.api.lexer.TokenId;
44
import org.netbeans.api.lexer.TokenId;
45
import org.netbeans.lib.lexer.LexerInputOperation;
45
import org.netbeans.lib.lexer.LexerInputOperation;
46
import org.netbeans.lib.lexer.TokenList;
46
import org.netbeans.lib.lexer.TokenList;
47
import org.netbeans.lib.lexer.TokenOrEmbedding;
47
48
48
/**
49
/**
49
 * Token list that allows mutating by token list mutator.
50
 * Token list that allows mutating by token list mutator.
Lines 61-68 Link Here
61
     * Also do not perform any checks regarding index validity
62
     * Also do not perform any checks regarding index validity
62
     * - only items below {@link #tokenCountCurrent()} will be requested.
63
     * - only items below {@link #tokenCountCurrent()} will be requested.
63
     */
64
     */
64
    Object tokenOrEmbeddingContainerUnsync(int index);
65
    TokenOrEmbedding<T> tokenOrEmbeddingUnsync(int index);
65
    
66
66
    /**
67
    /**
67
     * Create lexer input operation used for relexing of the input.
68
     * Create lexer input operation used for relexing of the input.
68
     */
69
     */
Lines 80-85 Link Here
80
    /**
81
    /**
81
     * Update the token list by replacing tokens according to the given change.
82
     * Update the token list by replacing tokens according to the given change.
82
     */
83
     */
83
    void replaceTokens(TokenListChange<T> change, int removeTokenCount, int diffLength);
84
    void replaceTokens(TokenListChange<T> change, int diffLength);
84
85
85
}
86
}
(-)a/lexer/src/org/netbeans/lib/lexer/inc/RemovedTokenList.java (-17 / +27 lines)
Lines 52-57 Link Here
52
import org.netbeans.lib.lexer.TokenList;
52
import org.netbeans.lib.lexer.TokenList;
53
import org.netbeans.lib.lexer.token.AbstractToken;
53
import org.netbeans.lib.lexer.token.AbstractToken;
54
import org.netbeans.lib.lexer.token.TextToken;
54
import org.netbeans.lib.lexer.token.TextToken;
55
import org.netbeans.lib.lexer.TokenOrEmbedding;
55
56
56
/**
57
/**
57
 * Token list implementation holding added or removed tokens from a list.
58
 * Token list implementation holding added or removed tokens from a list.
Lines 64-84 Link Here
64
    
65
    
65
    private final LanguagePath languagePath;
66
    private final LanguagePath languagePath;
66
    
67
    
67
    private Object[] tokensOrBranches;
68
    private TokenOrEmbedding<T>[] tokenOrEmbeddings;
68
    
69
    
69
    private int removedTokensStartOffset;
70
    private int removedTokensStartOffset;
70
    
71
    
71
    public RemovedTokenList(LanguagePath languagePath, Object[] tokensOrBranches) {
72
    public RemovedTokenList(LanguagePath languagePath, TokenOrEmbedding<T>[] tokensOrBranches) {
72
        this.languagePath = languagePath;
73
        this.languagePath = languagePath;
73
        this.tokensOrBranches = tokensOrBranches;
74
        this.tokenOrEmbeddings = tokensOrBranches;
74
    }
75
    }
75
    
76
    
76
    public LanguagePath languagePath() {
77
    public LanguagePath languagePath() {
77
        return languagePath;
78
        return languagePath;
78
    }
79
    }
79
    
80
    
80
    public Object tokenOrEmbeddingContainer(int index) {
81
    public TokenOrEmbedding<T> tokenOrEmbedding(int index) {
81
        return (index < tokensOrBranches.length) ? tokensOrBranches[index] : null;
82
        return (index < tokenOrEmbeddings.length) ? tokenOrEmbeddings[index] : null;
82
    }
83
    }
83
84
84
    public int lookahead(int index) {
85
    public int lookahead(int index) {
Lines 89-95 Link Here
89
        return null;
90
        return null;
90
    }
91
    }
91
92
92
    public int tokenOffset(int index) {
93
    public int tokenOffsetByIndex(int index) {
93
        Token<?> token = existingToken(index);
94
        Token<?> token = existingToken(index);
94
        if (token.isFlyweight()) {
95
        if (token.isFlyweight()) {
95
            int offset = 0;
96
            int offset = 0;
Lines 109-122 Link Here
109
        }
110
        }
110
    }
111
    }
111
112
113
    public int[] tokenIndex(int offset) {
114
        return LexerUtilsConstants.tokenIndexBinSearch(this, offset, tokenCountCurrent());
115
    }
116
112
    private Token<T> existingToken(int index) {
117
    private Token<T> existingToken(int index) {
113
        return LexerUtilsConstants.token(tokensOrBranches[index]);
118
        return tokenOrEmbeddings[index].token();
114
    }
119
    }
115
120
116
    public synchronized AbstractToken<T> replaceFlyToken(
121
    public synchronized AbstractToken<T> replaceFlyToken(
117
    int index, AbstractToken<T> flyToken, int offset) {
122
    int index, AbstractToken<T> flyToken, int offset) {
118
        TextToken<T> nonFlyToken = ((TextToken<T>)flyToken).createCopy(this, offset);
123
        TextToken<T> nonFlyToken = ((TextToken<T>)flyToken).createCopy(this, offset);
119
        tokensOrBranches[index] = nonFlyToken;
124
        tokenOrEmbeddings[index] = nonFlyToken;
120
        return nonFlyToken;
125
        return nonFlyToken;
121
    }
126
    }
122
127
Lines 125-143 Link Here
125
    }
130
    }
126
131
127
    public int tokenCountCurrent() {
132
    public int tokenCountCurrent() {
128
        return tokensOrBranches.length;
133
        return tokenOrEmbeddings.length;
129
    }
134
    }
130
135
131
    public int modCount() {
136
    public int modCount() {
132
        return -1;
137
        return LexerUtilsConstants.MOD_COUNT_IMMUTABLE_INPUT;
133
    }
138
    }
134
    
139
    
135
    public int childTokenOffset(int rawOffset) {
140
    public int tokenOffset(AbstractToken<T> token) {
141
        int rawOffset = token.rawOffset();
136
        // Offsets of contained tokens are absolute
142
        // Offsets of contained tokens are absolute
137
        return rawOffset;
143
        return rawOffset;
138
    }
144
    }
139
    
145
    
140
    public char childTokenCharAt(int rawOffset, int index) {
146
    public char charAt(int offset) {
141
        throw new IllegalStateException("Querying of text for removed tokens not supported"); // NOI18N
147
        throw new IllegalStateException("Querying of text for removed tokens not supported"); // NOI18N
142
    }
148
    }
143
149
Lines 145-154 Link Here
145
        throw new IllegalStateException("Branching of removed tokens not supported"); // NOI18N
151
        throw new IllegalStateException("Branching of removed tokens not supported"); // NOI18N
146
    }
152
    }
147
    
153
    
148
    public TokenList<?> root() {
154
    public TokenList<?> rootTokenList() {
149
        return this;
155
        return null;
150
    }
156
    }
151
    
157
158
    public CharSequence inputSourceText() {
159
        return null;
160
    }
161
152
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
162
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
153
        return null;
163
        return null;
154
    }
164
    }
Lines 159-172 Link Here
159
169
160
    public int startOffset() {
170
    public int startOffset() {
161
        if (tokenCountCurrent() > 0 || tokenCount() > 0)
171
        if (tokenCountCurrent() > 0 || tokenCount() > 0)
162
            return tokenOffset(0);
172
            return tokenOffsetByIndex(0);
163
        return 0;
173
        return 0;
164
    }
174
    }
165
175
166
    public int endOffset() {
176
    public int endOffset() {
167
        int cntM1 = tokenCount() - 1;
177
        int cntM1 = tokenCount() - 1;
168
        if (cntM1 >= 0)
178
        if (cntM1 >= 0)
169
            return tokenOffset(cntM1) + LexerUtilsConstants.token(this, cntM1).length();
179
            return tokenOffsetByIndex(cntM1) + tokenOrEmbedding(cntM1).token().length();
170
        return 0;
180
        return 0;
171
    }
181
    }
172
182
(-)a/lexer/src/org/netbeans/lib/lexer/inc/SnapshotTokenList.java (-68 / +80 lines)
Lines 44-50 Link Here
44
import java.util.Set;
44
import java.util.Set;
45
import org.netbeans.api.lexer.InputAttributes;
45
import org.netbeans.api.lexer.InputAttributes;
46
import org.netbeans.api.lexer.LanguagePath;
46
import org.netbeans.api.lexer.LanguagePath;
47
import org.netbeans.api.lexer.Token;
48
import org.netbeans.api.lexer.TokenId;
47
import org.netbeans.api.lexer.TokenId;
49
import org.netbeans.lib.editor.util.CompactMap;
48
import org.netbeans.lib.editor.util.CompactMap;
50
import org.netbeans.lib.lexer.EmbeddedTokenList;
49
import org.netbeans.lib.lexer.EmbeddedTokenList;
Lines 52-57 Link Here
52
import org.netbeans.lib.lexer.LexerUtilsConstants;
51
import org.netbeans.lib.lexer.LexerUtilsConstants;
53
import org.netbeans.lib.lexer.TokenHierarchyOperation;
52
import org.netbeans.lib.lexer.TokenHierarchyOperation;
54
import org.netbeans.lib.lexer.TokenList;
53
import org.netbeans.lib.lexer.TokenList;
54
import org.netbeans.lib.lexer.TokenOrEmbedding;
55
import org.netbeans.lib.lexer.token.AbstractToken;
55
import org.netbeans.lib.lexer.token.AbstractToken;
56
import org.netbeans.lib.lexer.token.TextToken;
56
import org.netbeans.lib.lexer.token.TextToken;
57
57
Lines 79-85 Link Here
79
    private int liveTokenOffsetDiff;
79
    private int liveTokenOffsetDiff;
80
80
81
    /** Captured original tokens or branches. */
81
    /** Captured original tokens or branches. */
82
    private Object[] origTokensOrBranches;
82
    private TokenOrEmbedding<T>[] origTokenOrEmbeddings;
83
83
84
    /** Original token's offsets. The array is occupied
84
    /** Original token's offsets. The array is occupied
85
     * and maintained in the same way like origTokensOrBranches.
85
     * and maintained in the same way like origTokensOrBranches.
Lines 118-132 Link Here
118
        return liveTokenList.languagePath();
118
        return liveTokenList.languagePath();
119
    }
119
    }
120
    
120
    
121
    public Object tokenOrEmbeddingContainer(int index) {
121
    public TokenOrEmbedding<T> tokenOrEmbedding(int index) {
122
        if (liveTokenGapStart == -1 || index < liveTokenGapStart) {
122
        if (liveTokenGapStart == -1 || index < liveTokenGapStart) {
123
            return liveTokenList.tokenOrEmbeddingContainer(index);
123
            return liveTokenList.tokenOrEmbedding(index);
124
        }
124
        }
125
        index -= liveTokenGapStart;
125
        index -= liveTokenGapStart;
126
        if (index < origTokenCount) {
126
        if (index < origTokenCount) {
127
            return origTokensOrBranches[origTokenStartIndex + index];
127
            return origTokenOrEmbeddings[origTokenStartIndex + index];
128
        }
128
        }
129
        return liveTokenList.tokenOrEmbeddingContainer(liveTokenGapEnd + index - origTokenCount);
129
        return liveTokenList.tokenOrEmbedding(liveTokenGapEnd + index - origTokenCount);
130
    }
130
    }
131
131
132
    public int lookahead(int index) {
132
    public int lookahead(int index) {
Lines 141-149 Link Here
141
        return null;
141
        return null;
142
    }
142
    }
143
143
144
    public int tokenOffset(int index) {
144
    public int tokenOffsetByIndex(int index) {
145
        if (liveTokenGapStart == -1 || index < liveTokenGapStart) {
145
        if (liveTokenGapStart == -1 || index < liveTokenGapStart) {
146
            return liveTokenList.tokenOffset(index);
146
            return liveTokenList.tokenOffsetByIndex(index);
147
        }
147
        }
148
        index -= liveTokenGapStart;
148
        index -= liveTokenGapStart;
149
        if (index < origTokenCount) {
149
        if (index < origTokenCount) {
Lines 151-182 Link Here
151
        }
151
        }
152
        index -= origTokenCount;
152
        index -= origTokenCount;
153
153
154
        AbstractToken<T> token = LexerUtilsConstants.token(liveTokenList.
154
        AbstractToken<T> token = liveTokenList.tokenOrEmbeddingUnsync(liveTokenGapEnd + index).token();
155
                tokenOrEmbeddingContainerUnsync(liveTokenGapEnd + index));
156
        int offset;
155
        int offset;
157
        if (token.isFlyweight()) {
156
        if (token.isFlyweight()) {
158
            offset = token.length();
157
            offset = token.length();
159
            while (--index >= 0) {
158
            while (--index >= 0) {
160
                token = LexerUtilsConstants.token(liveTokenList.
159
                token = liveTokenList.tokenOrEmbeddingUnsync(liveTokenGapEnd + index).token();
161
                        tokenOrEmbeddingContainerUnsync(liveTokenGapEnd + index));
162
                if (token.isFlyweight()) {
160
                if (token.isFlyweight()) {
163
                    offset += token.length();
161
                    offset += token.length();
164
                } else { // non-flyweight element
162
                } else { // non-flyweight element
165
                    offset += tokenOffset(token, liveTokenList, token.rawOffset());
163
                    offset += tokenOffset(token, liveTokenList);
166
                    break;
164
                    break;
167
                }
165
                }
168
            }
166
            }
169
            if (index == -1) { // below the boundary of above-gap live tokens
167
            if (index == -1) { // below the boundary of above-gap live tokens
170
                index += liveTokenGapStart + origTokenCount;
168
                index += liveTokenGapStart + origTokenCount;
171
                if (index >= 0) {
169
                if (index >= 0) {
172
                    offset += tokenOffset(index);
170
                    offset += tokenOffsetByIndex(index);
173
                }
171
                }
174
            }
172
            }
175
            
173
            
176
        } else { // non-flyweight
174
        } else { // non-flyweight
177
            offset = tokenOffset(token, liveTokenList, token.rawOffset());
175
            offset = tokenOffset(token, liveTokenList);
178
        }
176
        }
179
        return offset;
177
        return offset;
178
    }
179
180
    public int[] tokenIndex(int offset) {
181
        return LexerUtilsConstants.tokenIndexLazyTokenCreation(this, offset);
180
    }
182
    }
181
183
182
    /**
184
    /**
Lines 186-192 Link Here
186
     * @return offset for the particular token.
188
     * @return offset for the particular token.
187
     */
189
     */
188
    public <TT extends TokenId> int tokenOffset(
190
    public <TT extends TokenId> int tokenOffset(
189
    AbstractToken<TT> token, TokenList<TT> tokenList, int rawOffset) {
191
    AbstractToken<TT> token, TokenList<TT> tokenList) {
190
        // The following situations can happen:
192
        // The following situations can happen:
191
        // 1. Token instance is contained in token2offset map so the token's
193
        // 1. Token instance is contained in token2offset map so the token's
192
        //    offset is overriden by the information in the map.
194
        //    offset is overriden by the information in the map.
Lines 205-217 Link Here
205
        //    needs to be corrected if necessary.
207
        //    needs to be corrected if necessary.
206
        if (tokenList.getClass() == EmbeddedTokenList.class) {
208
        if (tokenList.getClass() == EmbeddedTokenList.class) {
207
            EmbeddedTokenList<TT> etl = (EmbeddedTokenList<TT>)tokenList;
209
            EmbeddedTokenList<TT> etl = (EmbeddedTokenList<TT>)tokenList;
208
            AbstractToken<?> rootBranchToken = etl.rootToken();
210
            AbstractToken<?> rootBranchToken = null; // originally etl.rootToken();
209
            Token2OffsetEntry<T> entry = token2offset.get(rootBranchToken);
211
            Token2OffsetEntry<T> entry = token2offset.get(rootBranchToken);
210
            if (entry != null) {
212
            if (entry != null) {
211
                return entry.offset() + etl.childTokenOffsetShift(rawOffset);
213
                return entry.offset();// used to be: + etl.childTokenOffsetShift(rawOffset);
212
            } else { // no special entry => check whether the regular offset is below liveTokenGapStartOffset
214
            } else { // no special entry => check whether the regular offset is below liveTokenGapStartOffset
213
                int offset = etl.childTokenOffset(rawOffset);
215
                int offset = etl.tokenOffset(token);
214
                TokenList rootTokenList = etl.root();
216
                TokenList rootTokenList = etl.rootTokenList();
215
                if (rootTokenList != null && rootTokenList.getClass() == IncTokenList.class) {
217
                if (rootTokenList != null && rootTokenList.getClass() == IncTokenList.class) {
216
                    if (offset >= liveTokenGapStartOffset) {
218
                    if (offset >= liveTokenGapStartOffset) {
217
                        offset += liveTokenOffsetDiff;
219
                        offset += liveTokenOffsetDiff;
Lines 226-239 Link Here
226
            if (entry != null) {
228
            if (entry != null) {
227
                return entry.offset();
229
                return entry.offset();
228
            } else {
230
            } else {
231
                int offset = tokenList.tokenOffset(token);
229
                if (tokenList.getClass() == IncTokenList.class) {
232
                if (tokenList.getClass() == IncTokenList.class) {
230
                    rawOffset = tokenList.childTokenOffset(rawOffset);
233
                    if (offset >= liveTokenGapStartOffset) {
231
                    if (rawOffset >= liveTokenGapStartOffset) {
234
                        offset += liveTokenOffsetDiff;
232
                        rawOffset += liveTokenOffsetDiff;
233
                    }
235
                    }
234
                    return rawOffset;
235
                }
236
                }
236
                return tokenList.childTokenOffset(rawOffset);
237
                return offset;
237
            }
238
            }
238
        }
239
        }
239
    }
240
    }
Lines 253-279 Link Here
253
    }
254
    }
254
255
255
    public int modCount() {
256
    public int modCount() {
256
        return -1;
257
        return LexerUtilsConstants.MOD_COUNT_IMMUTABLE_INPUT;
257
    }
258
    }
258
    
259
    
259
    public int childTokenOffset(int rawOffset) {
260
    public int tokenOffset(AbstractToken<T> token) {
261
        int rawOffset = token.rawOffset();
260
        // Offset of the standalone token is absolute
262
        // Offset of the standalone token is absolute
261
        return rawOffset;
263
        return rawOffset;
262
    }
264
    }
263
    
265
    
264
    public char childTokenCharAt(int rawOffset, int index) {
266
    public char charAt(int offset) {
265
        // No tokens expected to be parented to this token list
267
        // No tokens expected to be parented to this token list
266
        throw new IllegalStateException("Not expected to be called"); // NOI18N
268
        throw new IllegalStateException("Not expected to be called"); // NOI18N
267
    }
269
    }
268
270
269
    public void wrapToken(int index, EmbeddingContainer embeddingContainer) {
271
    public void wrapToken(int index, EmbeddingContainer<T> embeddingContainer) {
270
        // Allow branching
272
        // Allow branching
271
        if (liveTokenGapStart == -1 || index < liveTokenGapStart) {
273
        if (liveTokenGapStart == -1 || index < liveTokenGapStart) {
272
            liveTokenList.wrapToken(index, embeddingContainer);
274
            liveTokenList.wrapToken(index, embeddingContainer);
273
        } else {
275
        } else {
274
            index -= liveTokenGapStart;
276
            index -= liveTokenGapStart;
275
            if (index < origTokenCount) {
277
            if (index < origTokenCount) {
276
                origTokensOrBranches[origTokenStartIndex + index] = embeddingContainer;
278
                origTokenOrEmbeddings[origTokenStartIndex + index] = embeddingContainer;
277
            } else {
279
            } else {
278
                liveTokenList.wrapToken(liveTokenGapEnd + index - origTokenCount, embeddingContainer);
280
                liveTokenList.wrapToken(liveTokenGapEnd + index - origTokenCount, embeddingContainer);
279
            }
281
            }
Lines 288-294 Link Here
288
            index -= liveTokenGapStart;
290
            index -= liveTokenGapStart;
289
            if (index < origTokenCount) {
291
            if (index < origTokenCount) {
290
                nonFlyToken = ((TextToken<T>)flyToken).createCopy(this, offset);
292
                nonFlyToken = ((TextToken<T>)flyToken).createCopy(this, offset);
291
                origTokensOrBranches[origTokenStartIndex + index] = nonFlyToken;
293
                origTokenOrEmbeddings[origTokenStartIndex + index] = nonFlyToken;
292
            } else {
294
            } else {
293
                nonFlyToken = liveTokenList.replaceFlyToken(
295
                nonFlyToken = liveTokenList.replaceFlyToken(
294
                        liveTokenGapEnd + index - origTokenCount,
296
                        liveTokenGapEnd + index - origTokenCount,
Lines 298-307 Link Here
298
        return nonFlyToken;
300
        return nonFlyToken;
299
    }
301
    }
300
    
302
    
301
    public TokenList<?> root() {
303
    public TokenList<?> rootTokenList() {
302
        return this;
304
        return this;
303
    }
305
    }
304
    
306
307
    public CharSequence inputSourceText() {
308
        return rootTokenList().inputSourceText();
309
    }
310
305
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
311
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
306
        return snapshot;
312
        return snapshot;
307
    }
313
    }
Lines 320-333 Link Here
320
326
321
    public int startOffset() {
327
    public int startOffset() {
322
        if (tokenCountCurrent() > 0 || tokenCount() > 0)
328
        if (tokenCountCurrent() > 0 || tokenCount() > 0)
323
            return tokenOffset(0);
329
            return tokenOffsetByIndex(0);
324
        return 0;
330
        return 0;
325
    }
331
    }
326
332
327
    public int endOffset() {
333
    public int endOffset() {
328
        int cntM1 = tokenCount() - 1;
334
        int cntM1 = tokenCount() - 1;
329
        if (cntM1 >= 0)
335
        if (cntM1 >= 0)
330
            return tokenOffset(cntM1) + LexerUtilsConstants.token(this, cntM1).length();
336
            return tokenOffsetByIndex(cntM1) + tokenOrEmbedding(cntM1).token().length();
331
        return 0;
337
        return 0;
332
    }
338
    }
333
    
339
    
Lines 350-357 Link Here
350
            liveTokenGapStart = startRemovedIndex;
356
            liveTokenGapStart = startRemovedIndex;
351
            liveTokenGapEnd = startRemovedIndex;
357
            liveTokenGapEnd = startRemovedIndex;
352
            liveTokenGapStartOffset = change.offset();
358
            liveTokenGapStartOffset = change.offset();
353
            origTokensOrBranches = new Object[removedTokenList.tokenCount()];
359
            @SuppressWarnings("unchecked")
354
            origOffsets = new int[origTokensOrBranches.length];
360
            TokenOrEmbedding<T>[] tokenOrEmbeddings = new TokenOrEmbedding[removedTokenList.tokenCount()];
361
            origTokenOrEmbeddings = tokenOrEmbeddings;
362
            origOffsets = new int[origTokenOrEmbeddings.length];
355
        }
363
        }
356
364
357
        int liveTokenIndexDiff = change.tokenChangeInfo().addedTokenCount()
365
        int liveTokenIndexDiff = change.tokenChangeInfo().addedTokenCount()
Lines 367-395 Link Here
367
            int offset = change.offset();
375
            int offset = change.offset();
368
            liveTokenGapStartOffset = offset;
376
            liveTokenGapStartOffset = offset;
369
            for (index = startRemovedIndex; index < bound; index++) {
377
            for (index = startRemovedIndex; index < bound; index++) {
370
                Object tokenOrEmbeddingContainer = removedTokenList.tokenOrEmbeddingContainer(index - startRemovedIndex);
378
                TokenOrEmbedding<T> tokenOrEmbedding = removedTokenList.tokenOrEmbedding(index - startRemovedIndex);
371
                AbstractToken<T> token = LexerUtilsConstants.token(tokenOrEmbeddingContainer);
379
                AbstractToken<T> token = tokenOrEmbedding.token();
372
                if (!token.isFlyweight()) {
380
                if (!token.isFlyweight()) {
373
                    TokenList<T> tokenList = token.tokenList();
381
                    TokenList<T> tokenList = token.tokenList();
374
                    if (tokenList == null) {
382
                    if (tokenList == null) {
375
                        tokenList = new StandaloneTokenList<T>(change.languagePath(),
383
                        tokenList = null; // new StandaloneTokenList<T>(change.languagePath(),
376
                                eventInfo.originalText().toCharArray(offset, offset + token.length()));
384
                                // eventInfo.originalText().toCharArray(offset, offset + token.length()));
377
                        token.setTokenList(tokenList);
385
                        token.setTokenList(tokenList);
378
                    }
386
                    }
379
                }
387
                }
380
                origOffsets[origTokenStartIndex] = offset;
388
                origOffsets[origTokenStartIndex] = offset;
381
                origTokensOrBranches[origTokenStartIndex++] = tokenOrEmbeddingContainer;
389
                origTokenOrEmbeddings[origTokenStartIndex++] = tokenOrEmbedding;
382
                offset += token.length();
390
                offset += token.length();
383
            }
391
            }
384
392
385
            while (index < liveTokenGapStart) {
393
            while (index < liveTokenGapStart) {
386
                Object tokenOrEmbeddingContainer = liveTokenList.tokenOrEmbeddingContainerUnsync(index + liveTokenIndexDiff);
394
                TokenOrEmbedding<T> tokenOrEmbedding = liveTokenList.tokenOrEmbeddingUnsync(index + liveTokenIndexDiff);
387
                AbstractToken<T> t = LexerUtilsConstants.token(tokenOrEmbeddingContainer);
395
                AbstractToken<T> t = tokenOrEmbedding.token();
388
                if (!t.isFlyweight()) {
396
                if (!t.isFlyweight()) {
389
                    token2offset.putEntry(new Token2OffsetEntry<T>(t, offset));
397
                    token2offset.putEntry(new Token2OffsetEntry<T>(t, offset));
390
                }
398
                }
391
                origOffsets[origTokenStartIndex] = offset;
399
                origOffsets[origTokenStartIndex] = offset;
392
                origTokensOrBranches[origTokenStartIndex++] = tokenOrEmbeddingContainer;
400
                origTokenOrEmbeddings[origTokenStartIndex++] = tokenOrEmbedding;
393
                offset += t.length();
401
                offset += t.length();
394
                index++;
402
                index++;
395
            }
403
            }
Lines 406-419 Link Here
406
            int index = endRemovedIndex;
414
            int index = endRemovedIndex;
407
            int offset = change.removedEndOffset();
415
            int offset = change.removedEndOffset();
408
            for (index = endRemovedIndex - 1; index >= bound; index--) {
416
            for (index = endRemovedIndex - 1; index >= bound; index--) {
409
                Object tokenOrEmbeddingContainer = removedTokenList.tokenOrEmbeddingContainer(index - startRemovedIndex);
417
                TokenOrEmbedding<T> tokenOrEmbedding = removedTokenList.tokenOrEmbedding(index - startRemovedIndex);
410
                AbstractToken<T> token = LexerUtilsConstants.token(tokenOrEmbeddingContainer);
418
                AbstractToken<T> token = tokenOrEmbedding.token();
411
                offset -= token.length();
419
                offset -= token.length();
412
                if (!token.isFlyweight()) {
420
                if (!token.isFlyweight()) {
413
                    TokenList<T> tokenList = token.tokenList();
421
                    TokenList<T> tokenList = token.tokenList();
414
                    if (tokenList == null) {
422
                    if (tokenList == null) {
415
                        tokenList = new StandaloneTokenList<T>(change.languagePath(),
423
                        tokenList = null; // new StandaloneTokenList<T>(change.languagePath(),
416
                                eventInfo.originalText().toCharArray(offset, offset + token.length()));
424
                                // eventInfo.originalText().toCharArray(offset, offset + token.length()));
417
                        token.setTokenList(tokenList);
425
                        token.setTokenList(tokenList);
418
                    }
426
                    }
419
                }
427
                }
Lines 422-440 Link Here
422
                if (liveTokenOffsetDiff != 0) {
430
                if (liveTokenOffsetDiff != 0) {
423
                    token2offset.putEntry(new Token2OffsetEntry<T>(token, origOffsets[origTokenIndex]));
431
                    token2offset.putEntry(new Token2OffsetEntry<T>(token, origOffsets[origTokenIndex]));
424
                }
432
                }
425
                origTokensOrBranches[origTokenIndex--] = tokenOrEmbeddingContainer;
433
                origTokenOrEmbeddings[origTokenIndex--] = tokenOrEmbedding;
426
            }
434
            }
427
435
428
            while (index >= liveTokenGapEnd) {
436
            while (index >= liveTokenGapEnd) {
429
                Object tokenOrEmbeddingContainer = liveTokenList.tokenOrEmbeddingContainerUnsync(index + liveTokenIndexDiff);
437
                TokenOrEmbedding<T> tokenOrEmbedding = liveTokenList.tokenOrEmbeddingUnsync(index + liveTokenIndexDiff);
430
                AbstractToken<T> token = LexerUtilsConstants.token(tokenOrEmbeddingContainer);
438
                AbstractToken<T> token = tokenOrEmbedding.token();
431
                offset -= token.length();
439
                offset -= token.length();
432
                if (!token.isFlyweight()) {
440
                if (!token.isFlyweight()) {
433
                    token2offset.putEntry(new Token2OffsetEntry<T>(token, offset));
441
                    token2offset.putEntry(new Token2OffsetEntry<T>(token, offset));
434
                }
442
                }
435
                origOffsets[origTokenIndex] = offset + liveTokenOffsetDiff;
443
                origOffsets[origTokenIndex] = offset + liveTokenOffsetDiff;
436
                token2offset.putEntry(new Token2OffsetEntry<T>(token, origOffsets[origTokenIndex]));
444
                token2offset.putEntry(new Token2OffsetEntry<T>(token, origOffsets[origTokenIndex]));
437
                origTokensOrBranches[origTokenIndex--] = tokenOrEmbeddingContainer;
445
                origTokenOrEmbeddings[origTokenIndex--] = tokenOrEmbedding;
438
                index--;
446
                index--;
439
            }
447
            }
440
            liveTokenGapEnd = endRemovedIndex;
448
            liveTokenGapEnd = endRemovedIndex;
Lines 445-496 Link Here
445
    }
453
    }
446
454
447
    private void ensureOrigTokensStartCapacity(int extraOrigTokenCount) {
455
    private void ensureOrigTokensStartCapacity(int extraOrigTokenCount) {
448
        if (extraOrigTokenCount > origTokensOrBranches.length - origTokenCount) { // will need to reallocate
456
        if (extraOrigTokenCount > origTokenOrEmbeddings.length - origTokenCount) { // will need to reallocate
449
            // Could check for maximum possible token count (origTokenCount + below-and-above live token counts)
457
            // Could check for maximum possible token count (origTokenCount + below-and-above live token counts)
450
            // but would cause init of live tokens above gap which is undesirable
458
            // but would cause init of live tokens above gap which is undesirable
451
            Object[] newOrigTokensOrBranches = new Object[(origTokensOrBranches.length * 3 / 2) + extraOrigTokenCount];
459
            @SuppressWarnings("unchecked")
460
            TokenOrEmbedding<T>[] newOrigTokensOrBranches = new TokenOrEmbedding[
461
                    (origTokenOrEmbeddings.length * 3 / 2) + extraOrigTokenCount];
452
            int[] newOrigOffsets = new int[newOrigTokensOrBranches.length];
462
            int[] newOrigOffsets = new int[newOrigTokensOrBranches.length];
453
            int newIndex = Math.max(extraOrigTokenCount, (newOrigTokensOrBranches.length
463
            int newIndex = Math.max(extraOrigTokenCount, (newOrigTokensOrBranches.length
454
                    - (origTokenCount + extraOrigTokenCount)) / 2);
464
                    - (origTokenCount + extraOrigTokenCount)) / 2);
455
            System.arraycopy(origTokensOrBranches, origTokenStartIndex,
465
            System.arraycopy(origTokenOrEmbeddings, origTokenStartIndex,
456
                    newOrigTokensOrBranches, newIndex, origTokenCount);
466
                    newOrigTokensOrBranches, newIndex, origTokenCount);
457
            System.arraycopy(origOffsets, origTokenStartIndex,
467
            System.arraycopy(origOffsets, origTokenStartIndex,
458
                    newOrigOffsets, newIndex, origTokenCount);
468
                    newOrigOffsets, newIndex, origTokenCount);
459
            origTokensOrBranches = newOrigTokensOrBranches;
469
            origTokenOrEmbeddings = newOrigTokensOrBranches;
460
            origOffsets = newOrigOffsets;
470
            origOffsets = newOrigOffsets;
461
            origTokenStartIndex = newIndex;
471
            origTokenStartIndex = newIndex;
462
472
463
        } else if (extraOrigTokenCount > origTokenStartIndex) { // only move
473
        } else if (extraOrigTokenCount > origTokenStartIndex) { // only move
464
            // Move to the end of the array
474
            // Move to the end of the array
465
            int newIndex = origTokensOrBranches.length - origTokenCount;
475
            int newIndex = origTokenOrEmbeddings.length - origTokenCount;
466
            System.arraycopy(origTokensOrBranches, origTokenStartIndex,
476
            System.arraycopy(origTokenOrEmbeddings, origTokenStartIndex,
467
                    origTokensOrBranches, newIndex, origTokenCount);
477
                    origTokenOrEmbeddings, newIndex, origTokenCount);
468
            System.arraycopy(origOffsets, origTokenStartIndex,
478
            System.arraycopy(origOffsets, origTokenStartIndex,
469
                    origOffsets, newIndex, origTokenCount);
479
                    origOffsets, newIndex, origTokenCount);
470
            origTokenStartIndex = origTokensOrBranches.length - origTokenCount;
480
            origTokenStartIndex = origTokenOrEmbeddings.length - origTokenCount;
471
        }
481
        }
472
    }
482
    }
473
    
483
    
474
    private void ensureOrigTokensEndCapacity(int extraOrigTokenCount) {
484
    private void ensureOrigTokensEndCapacity(int extraOrigTokenCount) {
475
        if (extraOrigTokenCount > origTokensOrBranches.length - origTokenCount) { // will need to reallocate
485
        if (extraOrigTokenCount > origTokenOrEmbeddings.length - origTokenCount) { // will need to reallocate
476
            // Could check for maximum possible token count (origTokenCount + below-and-above live token counts)
486
            // Could check for maximum possible token count (origTokenCount + below-and-above live token counts)
477
            // but would cause init of live tokens above gap which is undesirable
487
            // but would cause init of live tokens above gap which is undesirable
478
            Object[] newOrigTokensOrBranches = new Object[(origTokensOrBranches.length * 3 / 2) + extraOrigTokenCount];
488
            @SuppressWarnings("unchecked")
489
            TokenOrEmbedding<T>[] newOrigTokensOrBranches = new TokenOrEmbedding[
490
                    (origTokenOrEmbeddings.length * 3 / 2) + extraOrigTokenCount];
479
            int[] newOrigOffsets = new int[newOrigTokensOrBranches.length];
491
            int[] newOrigOffsets = new int[newOrigTokensOrBranches.length];
480
            int newIndex = (newOrigTokensOrBranches.length
492
            int newIndex = (newOrigTokensOrBranches.length
481
                    - (origTokenCount + extraOrigTokenCount)) / 2;
493
                    - (origTokenCount + extraOrigTokenCount)) / 2;
482
            System.arraycopy(origTokensOrBranches, origTokenStartIndex,
494
            System.arraycopy(origTokenOrEmbeddings, origTokenStartIndex,
483
                    newOrigTokensOrBranches, newIndex, origTokenCount);
495
                    newOrigTokensOrBranches, newIndex, origTokenCount);
484
            System.arraycopy(origOffsets, origTokenStartIndex,
496
            System.arraycopy(origOffsets, origTokenStartIndex,
485
                    newOrigOffsets, newIndex, origTokenCount);
497
                    newOrigOffsets, newIndex, origTokenCount);
486
            origTokensOrBranches = newOrigTokensOrBranches;
498
            origTokenOrEmbeddings = newOrigTokensOrBranches;
487
            origOffsets = newOrigOffsets;
499
            origOffsets = newOrigOffsets;
488
            origTokenStartIndex = newIndex;
500
            origTokenStartIndex = newIndex;
489
501
490
        } else if (extraOrigTokenCount > origTokensOrBranches.length - origTokenCount - origTokenStartIndex) { // only move
502
        } else if (extraOrigTokenCount > origTokenOrEmbeddings.length - origTokenCount - origTokenStartIndex) { // only move
491
            // Move to the end of the array
503
            // Move to the end of the array
492
            System.arraycopy(origTokensOrBranches, origTokenStartIndex,
504
            System.arraycopy(origTokenOrEmbeddings, origTokenStartIndex,
493
                    origTokensOrBranches, 0, origTokenCount);
505
                    origTokenOrEmbeddings, 0, origTokenCount);
494
            System.arraycopy(origOffsets, origTokenStartIndex,
506
            System.arraycopy(origOffsets, origTokenStartIndex,
495
                    origOffsets, 0, origTokenCount);
507
                    origOffsets, 0, origTokenCount);
496
            origTokenStartIndex = 0;
508
            origTokenStartIndex = 0;
(-)a/lexer/src/org/netbeans/lib/lexer/inc/StandaloneTokenList.java (-162 lines)
Removed Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.inc;
43
44
import java.util.Set;
45
import org.netbeans.api.lexer.InputAttributes;
46
import org.netbeans.api.lexer.LanguagePath;
47
import org.netbeans.api.lexer.TokenId;
48
import org.netbeans.lib.lexer.EmbeddingContainer;
49
import org.netbeans.lib.lexer.LexerUtilsConstants;
50
import org.netbeans.lib.lexer.TokenHierarchyOperation;
51
import org.netbeans.lib.lexer.TokenList;
52
import org.netbeans.lib.lexer.token.AbstractToken;
53
54
55
/**
56
 * Single token list maintains a text for a single token.
57
 * <br/>
58
 * It's used for token hierarchy snapshots only.
59
 *
60
 * @author Miloslav Metelka
61
 * @version 1.00
62
 */
63
64
public final class StandaloneTokenList<T extends TokenId> implements TokenList<T> {
65
66
    private char[] tokenText;
67
68
    private LanguagePath languagePath;
69
    
70
    public StandaloneTokenList(LanguagePath languagePath, char[] tokenText) {
71
        this.languagePath = languagePath;
72
        this.tokenText = tokenText;
73
    }
74
    
75
    public LanguagePath languagePath() {
76
        return languagePath;
77
    }
78
    
79
    public Object tokenOrEmbeddingContainer(int index) {
80
        throw new IllegalStateException("Not expected to be called"); // NOI18N
81
    }
82
83
    public AbstractToken<T> replaceFlyToken(
84
    int index, AbstractToken<T> flyToken, int offset) {
85
        throw new IllegalStateException("Not expected to be called"); // NOI18N
86
    }
87
88
    public int lookahead(int index) {
89
        return -1;
90
    }
91
92
    public Object state(int index) {
93
        return null;
94
    }
95
96
    public int tokenOffset(int index) {
97
        throw new IllegalStateException("Not expected to be called"); // NOI18N
98
    }
99
    
100
    public int tokenCount() {
101
        return 1;
102
    }
103
104
    public int tokenCountCurrent() {
105
        return 1;
106
    }
107
108
    public int modCount() {
109
        return -1;
110
    }
111
    
112
    public int childTokenOffset(int rawOffset) {
113
        // Offset of the standalone token is absolute
114
        return rawOffset;
115
    }
116
    
117
    public char childTokenCharAt(int rawOffset, int index) {
118
        return tokenText[index];
119
    }
120
121
    public void wrapToken(int index, EmbeddingContainer embeddingContainer) {
122
        throw new IllegalStateException("Branching of standalone tokens not supported"); // NOI18N
123
    }
124
    
125
    public TokenList<?> root() {
126
        return this;
127
    }
128
    
129
    public TokenHierarchyOperation<?,?> tokenHierarchyOperation() {
130
        return null;
131
    }
132
    
133
    public InputAttributes inputAttributes() {
134
        throw new IllegalStateException("Not expected to be called"); // NOI18N
135
    }
136
    
137
    public boolean isContinuous() {
138
        return true;
139
    }
140
141
    public Set<T> skipTokenIds() {
142
        return null;
143
    }
144
145
    public int startOffset() {
146
        if (tokenCountCurrent() > 0 || tokenCount() > 0)
147
            return tokenOffset(0);
148
        return 0;
149
    }
150
151
    public int endOffset() {
152
        int cntM1 = tokenCount() - 1;
153
        if (cntM1 >= 0)
154
            return tokenOffset(cntM1) + LexerUtilsConstants.token(this, cntM1).length();
155
        return 0;
156
    }
157
    
158
    public boolean isRemoved() {
159
        return false; // Should be used when part of a snapshot
160
    }
161
162
}
(-)a/lexer/src/org/netbeans/lib/lexer/inc/TokenHierarchyEventInfo.java (-15 / +16 lines)
Lines 43-54 Link Here
43
43
44
import org.netbeans.api.lexer.TokenChange;
44
import org.netbeans.api.lexer.TokenChange;
45
import org.netbeans.api.lexer.TokenHierarchyEventType;
45
import org.netbeans.api.lexer.TokenHierarchyEventType;
46
import org.netbeans.api.lexer.TokenId;
47
import org.netbeans.lib.editor.util.CharSequenceUtilities;
46
import org.netbeans.lib.editor.util.CharSequenceUtilities;
48
import org.netbeans.lib.lexer.LexerApiPackageAccessor;
47
import org.netbeans.lib.lexer.LexerApiPackageAccessor;
49
import org.netbeans.lib.lexer.LexerSpiPackageAccessor;
50
import org.netbeans.lib.lexer.TokenHierarchyOperation;
48
import org.netbeans.lib.lexer.TokenHierarchyOperation;
51
import org.netbeans.spi.lexer.MutableTextInput;
52
49
53
/**
50
/**
54
 * Shared information for all the token list changes
51
 * Shared information for all the token list changes
Lines 66-72 Link Here
66
    
63
    
67
    private TokenChange<?> tokenChange;
64
    private TokenChange<?> tokenChange;
68
65
69
    private final int modificationOffset;
66
    private final int modOffset;
70
67
71
    private final int removedLength;
68
    private final int removedLength;
72
69
Lines 97-103 Link Here
97
94
98
        this.tokenHierarchyOperation = tokenHierarchyOperation;
95
        this.tokenHierarchyOperation = tokenHierarchyOperation;
99
        this.type = type;
96
        this.type = type;
100
        this.modificationOffset = modificationOffset;
97
        this.modOffset = modificationOffset;
101
        this.removedLength = removedLength;
98
        this.removedLength = removedLength;
102
        this.removedText = removedText;
99
        this.removedText = removedText;
103
        this.insertedLength = insertedLength;
100
        this.insertedLength = insertedLength;
Lines 142-149 Link Here
142
        }
139
        }
143
    }
140
    }
144
141
145
    public int modificationOffset() {
142
    public int modOffset() {
146
        return modificationOffset;
143
        return modOffset;
147
    }
144
    }
148
145
149
    public int removedLength() {
146
    public int removedLength() {
Lines 159-165 Link Here
159
    }
156
    }
160
    
157
    
161
    public CharSequence insertedText() {
158
    public CharSequence insertedText() {
162
        return currentText().subSequence(modificationOffset(), modificationOffset() + insertedLength());
159
        return currentText().subSequence(modOffset(), modOffset() + insertedLength());
160
    }
161
    
162
    public int diffLength() {
163
        return insertedLength - removedLength;
163
    }
164
    }
164
    
165
    
165
    /**
166
    /**
Lines 181-187 Link Here
181
                        );
182
                        );
182
            }
183
            }
183
            originalText = new OriginalText(currentText(),
184
            originalText = new OriginalText(currentText(),
184
                    modificationOffset, removedText, insertedLength);
185
                    modOffset, removedText, insertedLength);
185
        }
186
        }
186
        return originalText;
187
        return originalText;
187
    }
188
    }
Lines 193-200 Link Here
193
    public String modificationDescription(boolean detail) {
194
    public String modificationDescription(boolean detail) {
194
        StringBuilder sb = new StringBuilder(originalText().length() + 300);
195
        StringBuilder sb = new StringBuilder(originalText().length() + 300);
195
        if (removedLength() > 0) {
196
        if (removedLength() > 0) {
196
            sb.append("TEXT REMOVED <").append(modificationOffset()).append(","). // NOI18N
197
            sb.append("TEXT REMOVED <").append(modOffset()).append(","). // NOI18N
197
                    append(modificationOffset() + removedLength()).append('>');
198
                    append(modOffset() + removedLength()).append('>');
198
            sb.append(':').append(removedLength());
199
            sb.append(':').append(removedLength());
199
            if (removedText() != null) {
200
            if (removedText() != null) {
200
                sb.append(" \"");
201
                sb.append(" \"");
Lines 204-211 Link Here
204
            sb.append('\n');
205
            sb.append('\n');
205
        }
206
        }
206
        if (insertedLength() > 0) {
207
        if (insertedLength() > 0) {
207
            sb.append("TEXT INSERTED <").append(modificationOffset()).append(","). // NOI18N
208
            sb.append("TEXT INSERTED <").append(modOffset()).append(","). // NOI18N
208
                    append(modificationOffset() + insertedLength()).append(">:"). // NOI18N
209
                    append(modOffset() + insertedLength()).append(">:"). // NOI18N
209
                    append(insertedLength()).append(" \""); // NOI18N
210
                    append(insertedLength()).append(" \""); // NOI18N
210
            CharSequenceUtilities.debugText(sb, insertedText());
211
            CharSequenceUtilities.debugText(sb, insertedText());
211
            sb.append("\"\n");
212
            sb.append("\"\n");
Lines 214-220 Link Here
214
            sb.append("\n\n----------------- ORIGINAL TEXT -----------------\n" + // NOI18N
215
            sb.append("\n\n----------------- ORIGINAL TEXT -----------------\n" + // NOI18N
215
                originalText() +
216
                originalText() +
216
                "\n----------------- BEFORE-CARET TEXT -----------------\n" + // NOI18N
217
                "\n----------------- BEFORE-CARET TEXT -----------------\n" + // NOI18N
217
                originalText().subSequence(0, modificationOffset()) +
218
                originalText().subSequence(0, modOffset()) +
218
                "|<--CARET\n" // NOI18N
219
                "|<--CARET\n" // NOI18N
219
            );
220
            );
220
        }
221
        }
Lines 234-240 Link Here
234
    public String toString() {
235
    public String toString() {
235
        StringBuilder sb = new StringBuilder();
236
        StringBuilder sb = new StringBuilder();
236
        sb.append("modOffset="); // NOI18N
237
        sb.append("modOffset="); // NOI18N
237
        sb.append(modificationOffset());
238
        sb.append(modOffset());
238
        if (removedLength() > 0) {
239
        if (removedLength() > 0) {
239
            sb.append(", removedLength=");
240
            sb.append(", removedLength=");
240
            sb.append(removedLength());
241
            sb.append(removedLength());
(-)06a7890f802e (+525 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.inc;
43
44
import java.util.ArrayList;
45
import java.util.Collections;
46
import java.util.HashMap;
47
import java.util.List;
48
import java.util.Map;
49
import java.util.logging.Level;
50
import java.util.logging.Logger;
51
import org.netbeans.api.lexer.LanguagePath;
52
import org.netbeans.api.lexer.TokenId;
53
import org.netbeans.lib.lexer.EmbeddedTokenList;
54
import org.netbeans.lib.lexer.EmbeddingContainer;
55
import org.netbeans.lib.lexer.TokenHierarchyOperation;
56
import org.netbeans.lib.lexer.TokenList;
57
import org.netbeans.lib.lexer.TokenListList;
58
59
/**
60
 * Request for updating of token hierarchy after text modification
61
 * or custom embedding creation/removal.
62
 * <br/>
63
 * This class contains all the data and methods related to updating.
64
 *
65
 * @author Miloslav Metelka
66
 */
67
68
public final class TokenHierarchyUpdate {
69
    
70
    // -J-Dorg.netbeans.lib.lexer.TokenHierarchyUpdate.level=FINE
71
    static final Logger LOG = Logger.getLogger(TokenHierarchyUpdate.class.getName());
72
73
    /**
74
     * Special constant value to avoid double map search for token list lists updating.
75
     */
76
    private static final UpdateItem<?> NO_ITEM = new UpdateItem<TokenId>(null);
77
    
78
    final TokenHierarchyEventInfo eventInfo;
79
    
80
    /**
81
     * Infos ordered from higher top levels of the hierarchy to lower levels.
82
     * Useful for top-down updating at the end.
83
     */
84
    private List<List<UpdateItem<?>>> itemLevels;
85
    
86
    /**
87
     * Mapping of LP to UpdateItem for a joined ETLs.
88
     */
89
    private Map<LanguagePath,UpdateItem<?>> path2Item;
90
    
91
    private LanguagePath lastPath2ItemPath;
92
    private UpdateItem<?> lastPath2ItemItem;
93
    
94
    public TokenHierarchyUpdate(TokenHierarchyEventInfo eventInfo) {
95
        this.eventInfo = eventInfo;
96
    }
97
98
    public void update() {
99
        TokenHierarchyOperation<?,?> operation = eventInfo.tokenHierarchyOperation();
100
        IncTokenList<?> incTokenList = (IncTokenList<?>) operation.rootTokenList();
101
102
        if (LOG.isLoggable(Level.FINE)) {
103
            if (LOG.isLoggable(Level.FINEST)) {
104
                // Display current state of the hierarchy by faking its text
105
                // through original text
106
                CharSequence text = incTokenList.inputSourceText();
107
                assert (text != null);
108
                incTokenList.setInputSourceText(eventInfo.originalText());
109
                // Dump all contents
110
                LOG.finest(toString());
111
                // Return the original text
112
                incTokenList.setInputSourceText(text);
113
            }
114
115
            StringBuilder sb = new StringBuilder(150);
116
            sb.append("<<<<<<<<<<<<<<<<<< LEXER CHANGE START ------------------\n"); // NOI18N
117
            sb.append(eventInfo.modificationDescription(false));
118
            TokenHierarchyUpdate.LOG.fine(sb.toString());
119
        }
120
121
        updateImpl(incTokenList, (operation.maxTokenListListPathSize() > 0));
122
123
        if (LOG.isLoggable(Level.FINE)) {
124
            LOG.fine("AFFECTED: " + eventInfo.dumpAffected() + "\n"); // NOI18N
125
            String extraMsg = "";
126
            if (LOG.isLoggable(Level.FINER)) {
127
                // Check consistency of the whole token hierarchy
128
                String error = operation.checkConsistency();
129
                if (error != null) {
130
                    String msg = "!!!CONSISTENCY-ERROR!!!: " + error + "\n";
131
                    if (LOG.isLoggable(Level.FINEST)) {
132
                        throw new IllegalStateException(msg);
133
                    } else {
134
                        LOG.finer(msg);
135
                    }
136
                } else {
137
                    extraMsg = "(TokenHierarchy Check OK) ";
138
                }
139
            }
140
            LOG.fine(">>>>>>>>>>>>>>>>>> LEXER CHANGE END " + extraMsg + "------------------\n"); // NOI18N
141
        }
142
143
        if (LOG.isLoggable(Level.FINEST)) {
144
            LOG.finest("AFTER UPDATE:\n");
145
            LOG.finest(toString());
146
        }
147
    }
148
149
    private <T extends TokenId> void updateImpl(IncTokenList<T> incTokenList, boolean tllChildrenMayExist) {
150
        incTokenList.incrementModCount();
151
        
152
        // Update starts at the top language path an goes to possibly embedded-token-lists (ETLs)
153
        // based on the top-level change. If there are embeddings that join sections
154
        // a token-list-list (TLL) exists for the given language path that maintains
155
        // all ETLs for the whole input source.
156
        // 1. The updating must always go from upper levels to more embedded levels of the token hierarchy
157
        //    to ensure that the tokens of the possible joined ETLs get updated properly
158
        //    as the tokens created/removed at upper levels may contain embeddings that will
159
        //    need to be added/removed from TLL of more embedded level.
160
        // 2. A single insert/remove may produce token updates at several
161
        //    places in the document due to joining of ETLs. In turn the added/removed
162
        //    ETLs may affect more embedded levels so the update can affect
163
        //    multiple places of input source.
164
        // 3. The algorithm must collect both removed and added ETLs
165
        //    and process them prior calling the TokenListUpdater to update actual tokens.
166
        // 4. For a removed ETL the updating must check and collect nested ETLs
167
        //    because some embedded tokens of the removed ETL might contain
168
        //    another ETL that might be maintained as TLL.
169
        // 5. Added ETLs must also be inspected for nested ETLs maintained in a TLL.
170
        //    Initialization of added ETLs is done when the particular level is processed
171
        //    because TLL can join sections so they must be lexed once the definite additions
172
        //    and removals of ETLs are known. For non-joining ETLs this could be done
173
        //    immediately but it is not necessary so it's done at the same time as well.
174
        // 6. For all TLLs their parent TLLs (for language path with last language stripped)
175
        //    are also maintained mandatorily.
176
        // 7. Algorithm maintains "item-levels" to respect top-down processing
177
        //    according to language-path-depth.
178
        
179
        itemLevels = new ArrayList<List<UpdateItem<?>>>(3); // Suffice for two-level embedding without realloc
180
        // Create root item first for root token list
181
        UpdateItem<T> rootItem = new UpdateItem<T>(this);
182
        rootItem.tokenListChange = new TokenListChange<T>(incTokenList);
183
        rootItem.tllChildrenMayExist = tllChildrenMayExist;
184
        addItem(rootItem, 0);
185
        processLevelInfos();
186
    }
187
    
188
    public <T extends TokenId> void updateCreateOrRemoveEmbedding(EmbeddedTokenList<T> addedOrRemovedTokenList, boolean add) {
189
        LanguagePath languagePath = addedOrRemovedTokenList.languagePath();
190
        int level = languagePath.size() - 1;
191
        itemLevels = new ArrayList<List<UpdateItem<?>>>(level + 2); // One extra level for growth
192
        UpdateItem<T> item = tokenListListItem(languagePath);
193
        if (item != null) {
194
            if (LOG.isLoggable(Level.FINE)) {
195
                LOG.fine("THU.updateCreateOrRemoveEmbedding() add=" + add + ": " + addedOrRemovedTokenList.dumpInfo(null));
196
            }
197
            if (add) {
198
                item.tokenListListUpdate.markAddedMember(addedOrRemovedTokenList);
199
            } else {
200
                item.tokenListListUpdate.markRemovedMember(addedOrRemovedTokenList, eventInfo);
201
            }
202
            processLevelInfos();
203
        }
204
    }
205
    
206
    private void processLevelInfos() {
207
        // Process item levels which can extend the list by new items at the same level
208
        // or in the next levels. Therefore iterate by INDEX.since size() may change.
209
        for (int i = 0; i < itemLevels.size(); i++) {
210
            List<UpdateItem<?>> items = itemLevels.get(i);
211
            // The "items" list should not be extended by additional items dynamically during iteration.
212
            for (UpdateItem<?> item : items) {
213
                item.update();
214
            }
215
        }
216
    }
217
    
218
    void addItem(UpdateItem<?> item, int level) {
219
        while (level >= itemLevels.size()) {
220
            itemLevels.add(new ArrayList<UpdateItem<?>>(3));
221
        }
222
        List<UpdateItem<?>> items = itemLevels.get(level);
223
        items.add(item);
224
    }
225
226
    void collectAddedRemovedEmbeddings(TokenListChange<?> change) {
227
        // Only called when tll children exist
228
        // First collect the removed embeddings
229
        TokenList<?> removedTokenList = change.tokenChangeInfo().removedTokenList();
230
        if (removedTokenList != null) {
231
            collectRemovedEmbeddings(removedTokenList);
232
        }
233
        // Now collect added embeddings
234
        TokenList<?> currentTokenList = change.tokenList();
235
        collectAddedEmbeddings(currentTokenList, change.index(), change.addedTokenOrEmbeddingsCount());
236
    }
237
238
    /**
239
     * Collect removed embeddings for the given token list recursively
240
     * and nest deep enough for all maintained children
241
     * token list lists.
242
     */
243
    void collectRemovedEmbeddings(TokenList<?> removedTokenList) {
244
        int tokenCount = removedTokenList.tokenCountCurrent();
245
        for (int i = 0; i < tokenCount; i++) { // Must go from first to last
246
            EmbeddingContainer<?> ec = removedTokenList.tokenOrEmbedding(i).embedding();
247
            if (ec != null) {
248
                ec.updateStatusUnsync(); // Update status since markRemoved() will need it
249
                EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
250
                while (etl != null && etl != EmbeddedTokenList.NO_DEFAULT_EMBEDDING) {
251
                    internalMarkAddedRemovedMember(etl, false);
252
                    etl = etl.nextEmbeddedTokenList();
253
                }
254
            }
255
        }
256
    }
257
258
    void collectAddedEmbeddings(TokenList<?> tokenList, int index, int addedCount) {
259
        for (int i = 0; i < addedCount; i++) {
260
            // Ensure that the default embedding gets possibly created
261
            EmbeddedTokenList<?> etl = EmbeddingContainer.embeddedTokenList(tokenList, index + i, null, false);
262
            while (etl != null) {
263
                internalMarkAddedRemovedMember(etl, true);
264
                etl = etl.nextEmbeddedTokenList();
265
            }
266
        }
267
    }
268
269
    /**
270
     * This code is extracted from collectAdded/RemovedEmbeddings() for convenient generification
271
     * over a type ET.
272
     */
273
    private <ET extends TokenId> void internalMarkAddedRemovedMember(EmbeddedTokenList<ET> etl, boolean add) {
274
        UpdateItem<ET> item = tokenListListItem(etl.languagePath());
275
        if (item != null) {
276
            // update-status called in caller
277
            if (add) {
278
                item.tokenListListUpdate.markAddedMember(etl);
279
            } else {
280
                item.tokenListListUpdate.markRemovedMember(etl, eventInfo);
281
            }
282
        }
283
    }
284
285
    /**
286
     * Return tll info or null if the token list list is not maintained
287
     * for the given language path.
288
     */
289
    private <T extends TokenId> UpdateItem<T> tokenListListItem(LanguagePath languagePath) {
290
        if (languagePath == lastPath2ItemPath) { // Use last queried one
291
            @SuppressWarnings("unchecked")
292
            UpdateItem<T> item = (UpdateItem<T>) lastPath2ItemItem;
293
            return item;
294
295
        } else { // Not last returned item
296
            if (path2Item == null) { // Init since it will contain either target item or noInfo()
297
                path2Item = new HashMap<LanguagePath,UpdateItem<?>>(4, 0.5f);
298
            }
299
            @SuppressWarnings("unchecked")
300
            UpdateItem<T> item = (UpdateItem<T>)path2Item.get(languagePath);
301
            if (item == NO_ITEM) { // Marker value for null (to query just single map - this one)
302
                item = null;
303
            } else if (item == null) {
304
                TokenListList<T> tokenListList = eventInfo.tokenHierarchyOperation().existingTokenListList(languagePath);
305
                if (tokenListList != null) {
306
                    item = new UpdateItem<T>(this);
307
                    item.setTokenListList(tokenListList);
308
                    item.tllChildrenMayExist = tokenListList.hasChildren();
309
                    int level = languagePath.size() - 1;
310
                    addItem(item, level); // Add item to be scheduled for processing
311
                    path2Item.put(languagePath, item);
312
                } else { // Use NO_ITEM marker value to immediately know that there's no tokenListList for the given LP
313
                    path2Item.put(languagePath, NO_ITEM); // NO_ITEM is of type UpdateItem<?>
314
                }
315
            } // else - regular valid item
316
            lastPath2ItemItem = item; // Remember unmasked value i.e. "null" directly
317
            return item;
318
        }
319
    }
320
    
321
    /**
322
     * Information about update in a particular token list or a particular token list list.
323
     */
324
    static final class UpdateItem<T extends TokenId> {
325
326
        private static final EmbeddedTokenList[] EMPTY_ETL_ARRAY = new EmbeddedTokenList[0];
327
328
        final TokenHierarchyUpdate update;
329
330
        UpdateItem<?> parentItem;
331
332
        /**
333
         * Token list change performed during this update.
334
         */
335
        TokenListChange<T> tokenListChange;
336
337
        TokenListListUpdate<T> tokenListListUpdate;
338
        
339
        boolean tllChildrenMayExist;
340
341
        public UpdateItem(TokenHierarchyUpdate update) {
342
            this.update = update;
343
        }
344
345
        void setParentItem(UpdateItem<?> parentItem) {
346
            assert (this.parentItem == null);
347
            this.parentItem = parentItem;
348
        }
349
        
350
        void setTokenListList(TokenListList<T> tokenListList) {
351
            this.tokenListListUpdate = new TokenListListUpdate<T>(tokenListList);
352
        }
353
354
        void initTokenListChange(EmbeddedTokenList<T> etl) {
355
            assert (tokenListChange == null);
356
            if (tokenListListUpdate != null) {
357
                // ETL managed by a TokenListList. If the TLL joins sections
358
                // then a JoinTokenListChange needs to be created.
359
                tokenListChange = tokenListListUpdate.createTokenListChange(etl);
360
361
            } else { // No child managed by TLL but want to process nested possible bounds changes as deep as possible
362
                // Perform change in child - it surely does not join the sections
363
                // since otherwise the tllItem could not be null.
364
                // Token list change is surely non-join since there is no TLLInfo
365
                tokenListChange = new TokenListChange<T>(etl);
366
            }
367
        }
368
        
369
        /**
370
         * Update token list(s) after added and removed embedded token lists
371
         * are known and in place.
372
         */
373
        void update() {
374
            TokenHierarchyEventInfo eventInfo = update.eventInfo;
375
            if (tokenListChange == null) { // Joined or unjoined ETLs
376
                assert (tokenListListUpdate != null);
377
                if (tokenListListUpdate.tokenListList.joinSections()) {
378
                    tokenListChange = tokenListListUpdate.createJoinTokenListChange();
379
                }
380
            } // else tokenListChange != null
381
382
            // Use always non-null List for added token lists
383
            if (tokenListListUpdate != null && tokenListListUpdate.addedTokenLists == null) {
384
                tokenListListUpdate.addedTokenLists = Collections.emptyList();
385
            }
386
            
387
            // Process the token list change by calling token list updater
388
            if (tokenListChange != null) { // Updating a concrete token list as a bounds change or joined change
389
                if (tokenListChange.getClass() == JoinTokenListChange.class) {
390
                    JoinTokenListChange<T> jChange = (JoinTokenListChange<T>) tokenListChange;
391
                    assert (tokenListListUpdate != null);
392
                    assert (tokenListListUpdate.modTokenListIndex != -1);
393
                    jChange.setTokenListListUpdate(tokenListListUpdate);
394
                    TokenListUpdater.updateJoined(jChange, eventInfo);
395
396
                } else { // non-joined update
397
                    TokenListUpdater.updateRegular(tokenListChange, eventInfo);
398
                    if (parentItem == null) {
399
                        eventInfo.setTokenChangeInfo(tokenListChange.tokenChangeInfo());
400
                    }
401
                }
402
403
                // Possibly process bounds change
404
                if (tokenListChange.isBoundsChange()) {
405
                    TokenListChange<T> change;
406
                    if (tokenListChange.getClass() == JoinTokenListChange.class) {
407
                        // Process the one embedded change
408
                        JoinTokenListChange<T> jChange = (JoinTokenListChange<T>) tokenListChange;
409
                        assert (jChange.relexChanges().size() == 1);
410
                        change = jChange.relexChanges().get(0);
411
                    } else {
412
                        change = tokenListChange;
413
                    }
414
                    processBoundsChange(change);
415
416
                } else { // Non-bounds change
417
                    // Mark changed area based on start of first mod.token and end of last mod.token
418
                    // of the root-level change
419
                    eventInfo.setMinAffectedStartOffset(tokenListChange.offset());
420
                    eventInfo.setMaxAffectedEndOffset(tokenListChange.addedEndOffset());
421
                    if (tllChildrenMayExist) { // If there are any possible embedded changes with TokenListList
422
                        if (tokenListChange.getClass() == JoinTokenListChange.class) {
423
                            JoinTokenListChange<T> jChange = (JoinTokenListChange<T>) tokenListChange;
424
                            List<? extends TokenListChange<T>> relexChanges = jChange.relexChanges();
425
                            for (TokenListChange<T> change : relexChanges) {
426
                                update.collectAddedRemovedEmbeddings(change); // Process individual changes
427
                            }
428
                        } else {
429
                            update.collectAddedRemovedEmbeddings(tokenListChange);
430
                        }
431
                    } // else: there is no embedding with TLL; existing ETLs will be abandoned; new one created on demand
432
                }
433
434
            } else if (tokenListListUpdate != null) { // Only service added/removed ETLs
435
                tokenListListUpdate.addRemoveTokenLists(update, tllChildrenMayExist);
436
            }
437
        }
438
439
        /**
440
         * Process a change where just a single token was relexed and it's the same
441
         * just with updated bounds.
442
         *
443
         * @param change non-null change describing the change.
444
         * @param parentChange parent change or null for root change.
445
         */
446
        void processBoundsChange(TokenListChange<T> change) {
447
            // Add an embedded change to the parent change (if exists)
448
            if (parentItem != null) {
449
                parentItem.tokenListChange.tokenChangeInfo().addEmbeddedChange(change.tokenChangeInfo());
450
            }
451
            // Go through all embedded list in a chain and check whether the embeddings are OK
452
            EmbeddingContainer<T> ec = change.tokenChangeInfo().removedTokenList().tokenOrEmbedding(0).embedding();
453
            if (ec != null) { // The only removed token had embeddings
454
                // Rewrap token in ec - use the added token
455
                ec.reinit(change.addedToken(0));
456
                ec.updateStatusUnsync();
457
                change.tokenList().wrapToken(change.index(), ec);
458
                // Go through all ETLs and check whether chars in start/end skip lengths weren't modified
459
                EmbeddedTokenList<?> etl = ec.firstEmbeddedTokenList();
460
                if (etl != null && etl != EmbeddedTokenList.NO_DEFAULT_EMBEDDING) {
461
                    // Check the text length beyond modification => end skip length must not be affected
462
                    TokenHierarchyEventInfo eventInfo = update.eventInfo;
463
                    int modRelOffset = eventInfo.modOffset() - change.offset();
464
                    int beyondModLength = change.addedEndOffset() - (eventInfo.modOffset() + eventInfo.diffLengthOrZero());
465
                    EmbeddedTokenList<?> prevEtl = null;
466
                    do {
467
                        // Check whether chars in start/end skip lengths weren't modified
468
                        if (processBoundsChangeEmbeddedTokenList(etl, modRelOffset, beyondModLength)) { // Embedding saved -> proceed to next ETL
469
                            prevEtl = etl;
470
                            etl = prevEtl.nextEmbeddedTokenList();
471
                        } else {
472
                            etl = ec.removeEmbeddedTokenList(prevEtl, etl);
473
                        }
474
                    } while (etl != null && etl != EmbeddedTokenList.NO_DEFAULT_EMBEDDING);
475
                }
476
            } 
477
        }
478
479
        /**
480
         * This method is extracted from processBoundsChangeEmbeddings() to allow a separate generification
481
         * for each ETL contained in an EC.
482
         * 
483
         * @param etl
484
         * @param ecTokenChange change for token wrapped by EC in which the ETL is hosted.
485
         * @param hasChildren
486
         * @param modRelOffset
487
         * @param beyondModLength
488
         * @return true if the embedding should be saved or false if it should be removed.
489
         */
490
        private <ET extends TokenId> boolean processBoundsChangeEmbeddedTokenList(
491
                EmbeddedTokenList<ET> etl, int modRelOffset, int beyondModLength
492
        ) {
493
            UpdateItem<ET> childItem = tllChildrenMayExist
494
                    ? update.<ET>tokenListListItem(etl.languagePath())
495
                    : null;
496
            // Check whether the change was not in the start or end skip lengths
497
            // and if so then remove the embedding
498
            if (modRelOffset >= etl.embedding().startSkipLength() && beyondModLength >= etl.embedding().endSkipLength()) {
499
                // Modification within embedding's bounds => embedding can stay
500
                // Embedding will be updated once the level gets processed
501
                if (childItem == null) {
502
                    childItem = new UpdateItem<ET>(update);
503
                    int level = etl.languagePath().size() - 1;
504
                    update.addItem(childItem, level);
505
                } else { // TokenListList exists - item already added
506
                    // Mark a bounds change
507
                    childItem.tokenListListUpdate.markChangedMember(etl);
508
                }
509
                childItem.setParentItem(this);
510
                childItem.initTokenListChange(etl);
511
                return true; // Embedding saved -> proceed with next
512
513
            } else { // Mod in start/stop skip length => Remove the etl from chain
514
                if (childItem != null) {
515
                    // update-status already done as part of rewrap-token
516
                    childItem.tokenListListUpdate.markRemovedMember(etl, update.eventInfo);
517
                }
518
                // Signal to remove embedding
519
                return false;
520
            }
521
        }
522
523
    }
524
525
}
(-)a/lexer/src/org/netbeans/lib/lexer/inc/TokenListChange.java (-63 / +82 lines)
Lines 50-55 Link Here
50
import org.netbeans.lib.lexer.LexerUtilsConstants;
50
import org.netbeans.lib.lexer.LexerUtilsConstants;
51
import org.netbeans.lib.lexer.TokenList;
51
import org.netbeans.lib.lexer.TokenList;
52
import org.netbeans.lib.lexer.token.AbstractToken;
52
import org.netbeans.lib.lexer.token.AbstractToken;
53
import org.netbeans.lib.lexer.TokenOrEmbedding;
53
54
54
/**
55
/**
55
 * Description of the change in a token list.
56
 * Description of the change in a token list.
Lines 65-86 Link Here
65
 * @version 1.00
66
 * @version 1.00
66
 */
67
 */
67
68
68
public final class TokenListChange<T extends TokenId> {
69
public class TokenListChange<T extends TokenId> {
70
    
71
    public static <T extends TokenId> TokenListChange<T> createRebuildChange(MutableTokenList<T> tokenList) {
72
        TokenListChange<T> change = new TokenListChange<T>(tokenList);
73
//        change.setIndex(0);
74
//        change.setOffset(0);
75
//        change.addedEndOffset = 0; // Tokens will be recreated lazily
76
        change.matchIndex = tokenList.tokenCountCurrent(); // All tokens removed
77
        return change;
78
    }
69
    
79
    
70
    private final TokenChangeInfo<T> tokenChangeInfo;
80
    private final TokenChangeInfo<T> tokenChangeInfo;
71
    
81
    
72
    /**
82
    /**
73
     * The list may store either tokens or branches as well.
83
     * The list may store either tokens or branches as well.
74
     */
84
     */
75
    private List<Object> addedTokensOrBranches;
85
    private List<TokenOrEmbedding<T>> addedTokenOrEmbeddings;
76
86
77
    private LAState laState;
87
    private LAState laState;
78
79
    private int offsetGapIndex;
80
    
88
    
81
    private int removedEndOffset;
89
    int removedEndOffset;
82
    
90
    
83
    private int addedEndOffset;
91
    protected int matchIndex;
92
    
93
    protected int matchOffset; // Works like addedEndOffset
84
    
94
    
85
    public TokenListChange(MutableTokenList<T> tokenList) {
95
    public TokenListChange(MutableTokenList<T> tokenList) {
86
        tokenChangeInfo = new TokenChangeInfo<T>(tokenList);
96
        tokenChangeInfo = new TokenChangeInfo<T>(tokenList);
Lines 94-109 Link Here
94
        return (MutableTokenList<T>)tokenChangeInfo.currentTokenList();
104
        return (MutableTokenList<T>)tokenChangeInfo.currentTokenList();
95
    }
105
    }
96
    
106
    
107
    public void setMatchIndex(int matchIndex) {
108
        this.matchIndex = matchIndex;
109
    }
110
111
    public void setMatchOffset(int matchOffset) {
112
        this.matchOffset = matchOffset;
113
    }
114
115
    public int increaseMatchIndex() {
116
        matchOffset += tokenList().tokenOrEmbeddingUnsync(matchIndex++).token().length();
117
        return matchOffset;
118
    }
119
97
    public LanguagePath languagePath() {
120
    public LanguagePath languagePath() {
98
        return tokenList().languagePath();
121
        return tokenList().languagePath();
99
    }
122
    }
100
123
    
101
    public int index() {
124
    public int index() {
102
        return tokenChangeInfo.index();
125
        return tokenChangeInfo.index();
103
    }
126
    }
104
127
105
    public void setIndex(int tokenIndex) {
128
    public void setIndex(int index) {
106
        tokenChangeInfo.setIndex(tokenIndex);
129
        tokenChangeInfo.setIndex(index);
107
    }
130
    }
108
    
131
    
109
    public int offset() {
132
    public int offset() {
Lines 113-177 Link Here
113
    public void setOffset(int offset) {
136
    public void setOffset(int offset) {
114
        tokenChangeInfo.setOffset(offset);
137
        tokenChangeInfo.setOffset(offset);
115
    }
138
    }
116
    
139
117
    public int offsetGapIndex() {
140
    public int removedTokenCount() {
118
        return offsetGapIndex;
141
        return matchIndex - index();
119
    }
142
    }
120
143
121
    public void setOffsetGapIndex(int offsetGapIndex) {
144
    public int removedEndOffset() {
122
        this.offsetGapIndex = offsetGapIndex;
145
        return matchOffset; // In after-mod coordinates
146
    }
147
148
    public int addedEndOffset() {
149
        return matchOffset;
123
    }
150
    }
124
151
125
    public void addToken(AbstractToken<T> token, int lookahead, Object state) {
152
    public void addToken(AbstractToken<T> token, int lookahead, Object state) {
126
        if (addedTokensOrBranches == null) {
153
        if (addedTokenOrEmbeddings == null) {
127
            addedTokensOrBranches = new ArrayList<Object>(2);
154
            addedTokenOrEmbeddings = new ArrayList<TokenOrEmbedding<T>>(2);
128
            laState = LAState.empty();
155
            laState = LAState.empty();
129
        }
156
        }
130
        addedTokensOrBranches.add(token);
157
        addedTokenOrEmbeddings.add(token);
131
        laState = laState.add(lookahead, state);
158
        laState = laState.add(lookahead, state);
132
    }
159
    }
133
    
160
    
134
    public List<Object> addedTokensOrBranches() {
161
    public List<TokenOrEmbedding<T>> addedTokenOrEmbeddings() {
135
        return addedTokensOrBranches;
162
        return addedTokenOrEmbeddings;
136
    }
163
    }
137
    
164
    
138
    public int addedTokensOrBranchesCount() {
165
    public int addedTokenOrEmbeddingsCount() {
139
        return (addedTokensOrBranches != null) ? addedTokensOrBranches.size() : 0;
166
        return (addedTokenOrEmbeddings != null) ? addedTokenOrEmbeddings.size() : 0;
140
    }
167
    }
141
    
168
    
142
    public void removeLastAddedToken() {
169
    /**
143
        int lastIndex = addedTokensOrBranches.size() - 1;
170
     * @return end offset of previous (retained) token.
144
        addedTokensOrBranches.remove(lastIndex);
171
     */
172
    public AbstractToken<T> removeLastAddedToken() {
173
        int lastIndex = addedTokenOrEmbeddings.size() - 1;
174
        AbstractToken<T> token = addedTokenOrEmbeddings.remove(lastIndex).token();
145
        laState.remove(lastIndex, 1);
175
        laState.remove(lastIndex, 1);
176
        matchIndex--;
177
        matchOffset -= token.length();
178
        return token;
146
    }
179
    }
147
    
180
    
148
    public AbstractToken<T> addedToken(int index) {
181
    public AbstractToken<T> addedToken(int index) {
149
        return LexerUtilsConstants.token(addedTokensOrBranches.get(0));
182
        return addedTokenOrEmbeddings.get(0).token();
150
    }
183
    }
151
    
184
    
152
    public void syncAddedTokenCount() {
185
    public void syncAddedTokenCount() {
153
        tokenChangeInfo.setAddedTokenCount(addedTokensOrBranches.size());
186
        tokenChangeInfo.setAddedTokenCount(addedTokenOrEmbeddings.size());
154
    }
187
    }
155
188
156
    public void setRemovedTokens(Object[] removedTokensOrBranches) {
189
    public void setRemovedTokens(TokenOrEmbedding<T>[] removedTokensOrBranches) {
157
        tokenChangeInfo.setRemovedTokenList(new RemovedTokenList<T>(
190
        tokenChangeInfo.setRemovedTokenList(new RemovedTokenList<T>(
158
                languagePath(), removedTokensOrBranches));
191
                languagePath(), removedTokensOrBranches));
159
    }
160
    
161
    public int removedEndOffset() {
162
        return removedEndOffset;
163
    }
164
    
165
    public void setRemovedEndOffset(int removedEndOffset) {
166
        this.removedEndOffset = removedEndOffset;
167
    }
168
    
169
    public int addedEndOffset() {
170
        return addedEndOffset;
171
    }
172
    
173
    public void setAddedEndOffset(int addedEndOffset) {
174
        this.addedEndOffset = addedEndOffset;
175
    }
192
    }
176
    
193
    
177
    public boolean isBoundsChange() {
194
    public boolean isBoundsChange() {
Lines 188-229 Link Here
188
205
189
    @Override
206
    @Override
190
    public String toString() {
207
    public String toString() {
191
        return toString(0);
208
        StringBuilder sb = new StringBuilder();
209
        sb.append('"').append(languagePath().innerLanguage().mimeType());
210
        sb.append("\", ind=").append(index());
211
        sb.append(", off=").append(offset());
212
        sb.append(", mInd=").append(matchIndex);
213
        sb.append(", mOff=").append(matchOffset);
214
        sb.append(", Add:").append(addedTokenOrEmbeddingsCount());
215
        sb.append(", tCnt=").append(tokenList().tokenCountCurrent());
216
        if (isBoundsChange()) {
217
            sb.append(", BoChan");
218
        }
219
        return sb.toString();
192
    }
220
    }
193
221
    
194
    public String toString(int indent) {
222
    public String toStringMods(int indent) {
195
        StringBuilder sb = new StringBuilder();
223
        StringBuilder sb = new StringBuilder();
196
        sb.append('"');
197
        sb.append(languagePath().innerLanguage().mimeType());
198
        sb.append("\", index=");
199
        sb.append(index());
200
        sb.append(", offset=");
201
        sb.append(offset());
202
        if (isBoundsChange()) {
203
            sb.append(", boundsChange");
204
        }
205
        TokenList<T> removedTL = tokenChangeInfo.removedTokenList();
224
        TokenList<T> removedTL = tokenChangeInfo.removedTokenList();
206
        if (removedTL != null && removedTL.tokenCount() > 0) {
225
        if (removedTL != null && removedTL.tokenCount() > 0) {
207
            int digitCount = ArrayUtilities.digitCount(removedTL.tokenCount() - 1);
226
            int digitCount = ArrayUtilities.digitCount(removedTL.tokenCount() - 1);
208
            for (int i = 0; i < removedTL.tokenCount(); i++) {
227
            for (int i = 0; i < removedTL.tokenCount(); i++) {
209
                sb.append('\n');
228
                sb.append('\n');
210
                ArrayUtilities.appendSpaces(sb, indent);
229
                ArrayUtilities.appendSpaces(sb, indent);
211
                sb.append("R[");
230
                sb.append("Rem[");
212
                ArrayUtilities.appendIndex(sb, i, digitCount);
231
                ArrayUtilities.appendIndex(sb, i, digitCount);
213
                sb.append("]: ");
232
                sb.append("]: ");
214
                LexerUtilsConstants.appendTokenInfo(sb, removedTL, i, null, false, 0);
233
                LexerUtilsConstants.appendTokenInfo(sb, removedTL, i, null, false, 0, true);
215
            }
234
            }
216
        }
235
        }
217
        if (addedTokensOrBranches() != null) {
236
        if (addedTokenOrEmbeddings() != null) {
218
            int digitCount = ArrayUtilities.digitCount(addedTokensOrBranches().size() - 1);
237
            int digitCount = ArrayUtilities.digitCount(addedTokenOrEmbeddings().size() - 1);
219
            for (int i = 0; i < addedTokensOrBranches().size(); i++) {
238
            for (int i = 0; i < addedTokenOrEmbeddings().size(); i++) {
220
                sb.append('\n');
239
                sb.append('\n');
221
                ArrayUtilities.appendSpaces(sb, indent);
240
                ArrayUtilities.appendSpaces(sb, indent);
222
                sb.append("A[");
241
                sb.append("Add[");
223
                ArrayUtilities.appendIndex(sb, i, digitCount);
242
                ArrayUtilities.appendIndex(sb, i, digitCount);
224
                sb.append("]: ");
243
                sb.append("]: ");
225
                LexerUtilsConstants.appendTokenInfo(sb, addedTokensOrBranches.get(i),
244
                LexerUtilsConstants.appendTokenInfo(sb, addedTokenOrEmbeddings.get(i),
226
                        laState.lookahead(i), laState.state(i), null, false, 0);
245
                        laState.lookahead(i), laState.state(i), null, false, 0, true);
227
            }
246
            }
228
        }
247
        }
229
        return sb.toString();
248
        return sb.toString();
(-)06a7890f802e (+208 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.inc;
43
44
import java.util.ArrayList;
45
import java.util.List;
46
import org.netbeans.api.lexer.TokenId;
47
import org.netbeans.lib.lexer.EmbeddedTokenList;
48
import org.netbeans.lib.lexer.JoinTokenList;
49
import org.netbeans.lib.lexer.TokenList;
50
import org.netbeans.lib.lexer.TokenListList;
51
52
/**
53
 * Change of a particular TokenListList.
54
 *
55
 * @author Miloslav Metelka
56
 */
57
58
final class TokenListListUpdate<T extends TokenId> {
59
    
60
    /**
61
     * Token list list for the case when the particular language path
62
     * corresponds to joined.
63
     */
64
    final TokenListList<T> tokenListList;
65
66
    int modTokenListIndex;
67
68
    int removedTokenListCount;
69
70
    List<EmbeddedTokenList<T>> addedTokenLists;
71
72
    TokenListListUpdate(TokenListList<T> tokenListList) {
73
        this.tokenListList = tokenListList;
74
        this.modTokenListIndex = -1;
75
    }
76
77
    public boolean isTokenListsMod() { // If any ETL was removed/added
78
        return (removedTokenListCount != 0) || addedTokenLists.size() > 0;
79
    }
80
81
    public int tokenListCountDiff() {
82
        return addedTokenLists.size() - removedTokenListCount;
83
    }
84
85
    public EmbeddedTokenList<T> afterUpdateTokenList(JoinTokenList<T> jtl, int tokenListIndex) {
86
        EmbeddedTokenList<T> etl;
87
        if (tokenListIndex < modTokenListIndex) {
88
            etl = jtl.tokenList(tokenListIndex);
89
        } else if (tokenListIndex - modTokenListIndex < addedTokenLists.size()) {
90
            etl = addedTokenLists.get(tokenListIndex - modTokenListIndex);
91
        } else { // Last part after removed and added
92
            etl = jtl.tokenList(modTokenListIndex + removedTokenListCount - addedTokenLists.size());
93
        }
94
        return etl;
95
    }
96
97
    protected int afterUpdateTokenListCount(JoinTokenList<T> jtl) {
98
        return jtl.tokenListCount() - removedTokenListCount + addedTokenLists.size();
99
    }
100
101
    void markChangedMember(EmbeddedTokenList<T> changedTokenList) {
102
        assert (modTokenListIndex == -1);
103
        modTokenListIndex = tokenListList.findIndex(changedTokenList.startOffset());
104
        assert (tokenListList.get(modTokenListIndex) == changedTokenList);
105
    }
106
107
    void markChageBetween(int offset) { // Nothing added/removed and mod outside of bounds of an ETL
108
        assert (modTokenListIndex == -1);
109
        modTokenListIndex = tokenListList.findIndex(offset);
110
    }
111
112
    /**
113
     * Mark the given token list as removed in the token list list.
114
     * All removed token lists should be marked subsequently their increasing offset
115
     * so it should be necessary to search for the index just once.
116
     * <br/>
117
     * It's expected that updateStatusImpl() was already called
118
     * on the corresponding embedding container.
119
     */
120
    void markRemovedMember(EmbeddedTokenList<T> removedTokenList, TokenHierarchyEventInfo eventInfo) {
121
        boolean indexWasMinusOne; // Used for possible exception cause debugging
122
//            removedTokenList.embeddingContainer().checkStatusUpdated();
123
        if (modTokenListIndex == -1) {
124
            indexWasMinusOne = true;
125
            modTokenListIndex = tokenListList.findIndexDuringUpdate(removedTokenList, eventInfo);
126
            assert (modTokenListIndex >= 0) : "tokenListIndex=" + modTokenListIndex + " < 0"; // NOI18N
127
        } else { // tokenListIndex already initialized
128
            indexWasMinusOne = false;
129
        }
130
        TokenList<T> markedForRemoveTokenList = tokenListList.getOrNull(modTokenListIndex + removedTokenListCount);
131
        if (markedForRemoveTokenList != removedTokenList) {
132
            int realIndex = tokenListList.indexOf(removedTokenList);
133
            throw new IllegalStateException("Removing at tokenListIndex=" + modTokenListIndex + // NOI18N
134
                    " but real tokenListIndex is " + realIndex + // NOI18N
135
                    " (indexWasMinusOne=" + indexWasMinusOne + ").\n" + // NOI18N
136
                    "Wishing to remove tokenList\n" + removedTokenList + // NOI18N
137
                    "\nbut marked-for-remove tokenList is \n" + markedForRemoveTokenList + // NOI18N
138
                    "\nfrom tokenListList\n" + tokenListList + // NOI18N
139
                    "\n\nModification description:\n" + eventInfo.modificationDescription(true) // NOI18N
140
                    );
141
        }
142
        removedTokenListCount++;
143
    }
144
145
    /**
146
     * Mark the given token list to be added to this list of token lists.
147
     * At the end first the token lists marked for removal will be removed
148
     * and then the token lists marked for addition will be added.
149
     * <br/>
150
     * It's expected that updateStatusImpl() was already called
151
     * on the corresponding embedding container.
152
     */
153
    void markAddedMember(EmbeddedTokenList<T> addedTokenList) {
154
//            addedTokenList.embeddingContainer().checkStatusUpdated();
155
        if (addedTokenLists == null) {
156
            if (modTokenListIndex == -1) {
157
                modTokenListIndex = tokenListList.findIndex(addedTokenList.startOffset());
158
                assert (modTokenListIndex >= 0) : "tokenListIndex=" + modTokenListIndex + " < 0"; // NOI18N
159
            }
160
            addedTokenLists = new ArrayList<EmbeddedTokenList<T>>(4);
161
        }
162
        addedTokenLists.add(addedTokenList);
163
    }
164
165
    void addRemoveTokenLists(TokenHierarchyUpdate update, boolean tllChildrenMayExist) {
166
        assert (removedTokenListCount > 0 || addedTokenLists != null);
167
        EmbeddedTokenList<T>[] removedTokenLists = tokenListList.replace(
168
                modTokenListIndex, removedTokenListCount, addedTokenLists);
169
        if (tllChildrenMayExist) {
170
            for (int i = 0; i < removedTokenLists.length; i++) {
171
                update.collectRemovedEmbeddings(removedTokenLists[i]);
172
            }
173
            for (int i = 0; i < addedTokenLists.size(); i++) {
174
                EmbeddedTokenList<T> addedEtl = addedTokenLists.get(i);
175
                update.collectAddedEmbeddings(addedEtl, 0, addedEtl.tokenCountCurrent());
176
            }
177
        }
178
    }
179
180
    TokenListChange<T> createTokenListChange(EmbeddedTokenList<T> etl) {
181
        assert (etl != null);
182
        TokenListChange<T> etlTokenListChange;
183
        if (tokenListList.joinSections()) {
184
            MutableJoinTokenList<T> jtl = MutableJoinTokenList.create(tokenListList, modTokenListIndex, etl);
185
            etlTokenListChange = new JoinTokenListChange<T>(jtl);
186
        } else { // Non-joining
187
            etlTokenListChange = new TokenListChange<T>(etl);
188
        }
189
        return etlTokenListChange;
190
    }
191
192
    TokenListChange<T> createJoinTokenListChange() {
193
        assert (tokenListList.joinSections());
194
        // In case when adding at jtl.tokenListCount() a last ETL must be used
195
        int etlIndex = Math.min(modTokenListIndex, tokenListList.size() - 1);
196
        MutableJoinTokenList<T> jtl = MutableJoinTokenList.create(tokenListList, etlIndex, tokenListList.get(etlIndex));
197
        return new JoinTokenListChange<T>(jtl);
198
    }
199
200
    @Override
201
    public String toString() {
202
        return " modTokenListIndex=" + modTokenListIndex + // NOI18N
203
                "; Rem:" + removedTokenListCount + // NOI18N
204
                " Add:" + addedTokenLists.size() + // NOI18N
205
                " Size:" + tokenListList.size(); // NOI18N
206
    }
207
208
}
(-)a/lexer/src/org/netbeans/lib/lexer/inc/TokenListUpdater.java (-382 / +492 lines)
Lines 44-58 Link Here
44
import java.util.logging.Level;
44
import java.util.logging.Level;
45
import java.util.logging.Logger;
45
import java.util.logging.Logger;
46
import org.netbeans.api.lexer.TokenId;
46
import org.netbeans.api.lexer.TokenId;
47
import org.netbeans.lib.lexer.LanguageOperation;
47
import org.netbeans.lib.editor.util.CharSequenceUtilities;
48
import org.netbeans.lib.lexer.EmbeddedJoinInfo;
49
import org.netbeans.lib.lexer.EmbeddedTokenList;
50
import org.netbeans.lib.lexer.JoinLexerInputOperation;
51
import org.netbeans.lib.lexer.JoinTokenList;
48
import org.netbeans.lib.lexer.LexerInputOperation;
52
import org.netbeans.lib.lexer.LexerInputOperation;
49
import org.netbeans.lib.lexer.LexerUtilsConstants;
53
import org.netbeans.lib.lexer.LexerUtilsConstants;
50
import org.netbeans.lib.lexer.token.AbstractToken;
54
import org.netbeans.lib.lexer.token.AbstractToken;
51
import org.netbeans.spi.lexer.TokenValidator;
55
import org.netbeans.lib.lexer.token.PartToken;
52
56
53
57
54
/**
58
/**
55
 * Token updater fixes a list of tokens constructed for a document
59
 * Token list updater fixes a list of tokens constructed for a document
56
 * after text of the document gets modified.
60
 * after text of the document gets modified.
57
 * <br>
61
 * <br>
58
 * Subclasses need to define all the abstract methods
62
 * Subclasses need to define all the abstract methods
Lines 62-86 Link Here
62
 * Updater looks similar to list iterator
66
 * Updater looks similar to list iterator
63
 * but there are differences in the semantics
67
 * but there are differences in the semantics
64
 * of iterator's modification operations.
68
 * of iterator's modification operations.
65
 * <p>
69
 * <br/>
66
 * The algorithm used in the {@link #update(int, int)}
70
 * The algorithm used in the {@link #update(int, int)}
67
 * is based on "General Incremental Lexical Analysis" written
71
 * is based on "General Incremental Lexical Analysis" written
68
 * by Tim A. Wagner and Susan L. Graham, University
72
 * by Tim A. Wagner and Susan L. Graham, University
69
 * of California, Berkeley. It's available online
73
 * of California, Berkeley. It's available online
70
 * at <a href="http://www.cs.berkeley.edu/Research/Projects/harmonia/papers/twagner-lexing.pdf">
74
 * at <a href="http://www.cs.berkeley.edu/Research/Projects/harmonia/papers/twagner-lexing.pdf">
71
 * twagner-lexing.pdf</a>.
75
 * twagner-lexing.pdf</a>.
72
 * <br>
76
 * <br/>
73
 * Ending <code>EOF</code> token is not used but the lookahead
77
 * Ending <code>EOF</code> token is not used but the lookahead
74
 * of the ending token(s) is increased by one (past the end of the input)
78
 * of the ending token(s) is increased by one (past the end of the input)
75
 * if they have reached the EOF.
79
 * if they have reached the EOF.
76
 * <br>
80
 * <br/>
77
 * Non-startable tokens are not supported.
81
 * Non-startable tokens are not supported.
78
 * <br>
82
 * <br/>
79
 * When updating a token with lookback one as a result
83
 * When updating a token with lookback one as a result
80
 * of modification the lookahead of the preceding token is inspected
84
 * of modification the lookahead of the preceding token is inspected
81
 * to find out whether the modification has really affected it.
85
 * to find out whether the modification has really affected it.
82
 * This can often save the previous token from being relexed.
86
 * This can often save the previous token from being relexed.
83
 * <br>
87
 * <br/>
84
 * Currently the algorithm computes the lookback values on the fly
88
 * Currently the algorithm computes the lookback values on the fly
85
 * and it does not store the lookback in the tokens. For typical languages
89
 * and it does not store the lookback in the tokens. For typical languages
86
 * the lookback is reasonably small (0, 1 or 2) so it's usually not worth
90
 * the lookback is reasonably small (0, 1 or 2) so it's usually not worth
Lines 88-106 Link Here
88
 * There would also be an additional overhead of updating the lookback
92
 * There would also be an additional overhead of updating the lookback
89
 * values in the tokens after the modification and the algorithm code would
93
 * values in the tokens after the modification and the algorithm code would
90
 * be somewhat less readable.
94
 * be somewhat less readable.
95
 * </p>
91
 *
96
 *
92
 * <p>
97
 * <p>
93
 * The algorithm removes the affected tokens in the natural order as they
98
 * The algorithm removes the affected tokens in the natural order as they
94
 * follow in the token stream. That can be used when the removed tokens
99
 * follow in the token stream. That can be used when the removed tokens
95
 * need to be collected (e.g. in an array).
100
 * need to be collected (e.g. in an array).
96
 * <br>
101
 * <br/>
97
 * If the offset and state after token recognition matches
102
 * If the offset and state after token recognition matches
98
 * the end offset and state after recognition of the originally present
103
 * the end offset and state after recognition of the originally present
99
 * token then the relexing is stopped because a match was found and the newly
104
 * token then the relexing is stopped because a match was found and the newly
100
 * produced tokens would match the present ones.
105
 * produced tokens would match the present ones.
101
 * <br>
106
 * <br/>
102
 * Otherwise the token(s) in the list are removed and replaced
107
 * Otherwise the token(s) in the list are removed and replaced
103
 * by the relexed token and the relexing continues until a match is reached.
108
 * by the relexed token and the relexing continues until a match is reached.
109
 * </p>
110
 * 
111
 * <p>
112
 * When using token list updater with JoinTokenList.Mutable there is a special treatment
113
 * of offsets independent of the underlying JoinTokenListChange and LexerInputOperation.
114
 * The updater treats the modOffset to be relative (in the number of characters)
115
 * to the relexOffset point (which is a real first relexed token's offset; it's necessary
116
 * for restarting of the lexer input operation) so when going over a JoinToken
117
 * the modOffset must be recomputed to not contain the gaps between individual join token parts.
118
 * </p>
104
 *
119
 *
105
 * @author Miloslav Metelka
120
 * @author Miloslav Metelka
106
 * @version 1.00
121
 * @version 1.00
Lines 112-354 Link Here
112
    private static final Logger LOG = Logger.getLogger(TokenListUpdater.class.getName());
127
    private static final Logger LOG = Logger.getLogger(TokenListUpdater.class.getName());
113
128
114
    /**
129
    /**
115
     * Use incremental algorithm to update the list of tokens
130
     * Use incremental algorithm to update a regular list of tokens (IncTokenList or EmbeddedTokenList)
116
     * after a modification done in the underlying storage.
131
     * after a modification done in the underlying storage.
117
     * 
132
     * 
118
     * @param tokenList non-null token list that is being updated. It may be top-level list
133
     * @param change non-null change that will incorporate the performed chagnes.
119
     *  or embedded token list.
120
     * @param modOffset offset where the modification occurred.
134
     * @param modOffset offset where the modification occurred.
121
     * @param insertedLength number of characters inserted at modOffset.
135
     * @param insertedLength number of characters inserted at modOffset.
122
     * @param removedLength number of characters removed at modOffset.
136
     * @param removedLength number of characters removed at modOffset.
123
     * @param change non-null change that will incorporate the performed chagnes.
124
     * @param zeroIndexRelexState state used for relexing at index 0.
125
     */
137
     */
126
    public static <T extends TokenId> void update(MutableTokenList<T> tokenList,
138
    public static <T extends TokenId> void updateRegular(TokenListChange<T> change, TokenHierarchyEventInfo eventInfo) {
127
    int modOffset, int insertedLength, int removedLength,
139
        MutableTokenList<T> tokenList = change.tokenList();
128
    TokenListChange<T> change, Object zeroIndexRelexState) {
140
        int tokenCount = tokenList.tokenCountCurrent();
129
        // Fetch offset where the modification occurred
130
        LanguageOperation<T> languageOperation = LexerUtilsConstants.innerLanguageOperation(
131
                tokenList.languagePath());
132
133
        int tokenCount = tokenList.tokenCountCurrent(); // presently created token count
134
        // Now determine which token is the first to be relexed.
135
        // If it would be either modified token or previous-of-modified token
136
        // (for modification right at the begining of modified token)
137
        // then the token will be attempted to be validated (without running
138
        // a lexer).
139
        AbstractToken<T> modToken;
140
        // modTokenOffset holds begining of the token in which the modification occurred.
141
        int modTokenOffset;
142
        // index points to the modified token
143
        int index;
144
145
        boolean loggable = LOG.isLoggable(Level.FINE);
141
        boolean loggable = LOG.isLoggable(Level.FINE);
146
        if (loggable) {
142
        if (loggable) {
147
            LOG.log(Level.FINE, "TokenListUpdater.update() STARTED\nmodOffset=" + modOffset
143
            logModification(tokenList.inputSourceText(), eventInfo, tokenCount, false);
148
                    + ", insertedLength=" + insertedLength
144
        }
149
                    + ", removedLength=" + removedLength
145
        
150
                    + ", tokenCount=" + tokenCount + "\n");
146
        // Find modified token by binary search in existing tokens
147
        // Use LexerUtilsConstants.tokenIndexBinSearch() to NOT lazily create new tokens here
148
        int[] indexAndTokenOffset = LexerUtilsConstants.tokenIndexBinSearch(tokenList, eventInfo.modOffset(), tokenCount);
149
        // Index and offset from which the relexing will start
150
        int relexIndex = indexAndTokenOffset[0];
151
        // relexOffset points to begining of a token in which the modification occurred
152
        // or which is affected by a modification (its lookahead points beyond modification point).
153
        int relexOffset = indexAndTokenOffset[1];
154
        if (relexIndex == -1) { // No tokens at all
155
            relexIndex = 0;
156
            relexOffset = tokenList.startOffset();
151
        }
157
        }
152
158
153
        if (tokenCount == 0) { // no tokens yet or all removed
159
        // Index of token before which the relexing will end (or == tokenCount)
154
            if (!tokenList.isFullyLexed()) {
160
        int matchIndex = relexIndex;
155
                // No tokens created yet (they get created lazily).
161
        // Offset of token at matchIndex
162
        int matchOffset = relexOffset;
163
164
        if (relexIndex == tokenCount) { // Change right at end of last token or beyond it (if not fully lexed)
165
            // relexOffset set to end offset of the last token
166
            if (!tokenList.isFullyLexed() && eventInfo.modOffset() >= relexOffset +
167
                    ((relexIndex > 0) ? tokenList.lookahead(relexIndex - 1) : 0)
168
            ) { // Do nothing if beyond last token's lookahed
169
                // Check whether the last token could be affected at all
170
                // by checking whether the modification was performed
171
                // in the last token's lookahead.
172
                // For fully lexed inputs the characters added to the end
173
                // must be properly lexed and notified (even if the last present
174
                // token has zero lookahead).
156
                if (loggable) {
175
                if (loggable) {
157
                    LOG.log(Level.FINE, "TokenListUpdater.update() FINISHED: Not fully lexed yet.\n");
176
                    LOG.log(Level.FINE, "TLU.updateRegular() FINISHED: Not fully lexed yet. rOff=" +
177
                            relexOffset + ", mOff=" + eventInfo.modOffset() + "\n");
158
                }
178
                }
159
                return; // Do nothing in this case
179
                change.setIndex(relexIndex);
160
            }
180
                change.setOffset(relexOffset);
161
            // If fully lexed and no tokens then the tokens should start
181
                change.setMatchIndex(matchIndex);
162
            // right at the modification offset
182
                change.setMatchOffset(matchOffset);
163
            modToken = null;
183
                tokenList.replaceTokens(change, eventInfo.diffLength());
164
            modTokenOffset = modOffset;
184
                return; // not affected at all
165
            index = 0;
185
            } // change.setIndex() will be performed later in relex()
166
186
167
        } else { // at least one token exists
187
            // Leave matchOffset as is (will possibly end relexing at tokenCount and unfinished relexing
168
            // Check whether the modification at modOffset might affect existing tokens
188
            // will be continued by replaceTokens()).
169
            // Get index of the token in which the modification occurred
189
            // For fully lexed lists it is necessary to lex till the end of input.
170
            // Get the offset of the last token into modTokenOffset variable
190
            if (tokenList.isFullyLexed())
171
            index = tokenCount - 1;
191
                matchOffset = Integer.MAX_VALUE;
172
            modTokenOffset = tokenList.tokenOffset(index);
173
            if (modOffset >= modTokenOffset) { // inside or above the last token?
174
                modToken = token(tokenList, index);
175
                int modTokenEndOffset = modTokenOffset + modToken.length();
176
                if (modOffset >= modTokenEndOffset) { // above last token
177
                    // Modification was right at the end boundary of the last token
178
                    // or above it (token list can be created lazily so that is valid case).
179
                    // Check whether the last token could be affected at all
180
                    // by checking the last token's lookahead.
181
                    // For fully lexed inputs the characters added to the end
182
                    // must be properly lexed and notified (even if the last present
183
                    // token has zero lookahead).
184
                    if (!tokenList.isFullyLexed()
185
                        && modOffset >= modTokenEndOffset + tokenList.lookahead(index)
186
                    ) {
187
                        if (loggable) {
188
                            LOG.log(Level.FINE, "TokenListUpdater.update() FINISHED: Not fully lexed yet. modTokenOffset="
189
                                    + modTokenOffset + ", modToken.length()=" + modToken.length() + "\n");
190
                        }
191
                        return; // not affected at all
192
                    }
193
192
194
                    index++;
193
        } else { // relexIndex < tokenCount
195
                    modToken = null;
194
            // Possibly increase matchIndex and matchOffset by skipping the tokens in the removed area
196
                    modTokenOffset = modTokenEndOffset;
195
            if (eventInfo.removedLength() > 0) { // At least remove token at relexOffset
197
                } // else -> modification inside the last token
196
                matchOffset += tokenList.tokenOrEmbeddingUnsync(matchIndex++).token().length();
198
197
                int removedEndOffset = eventInfo.modOffset() + eventInfo.removedLength();
199
            } else { // modification in non-last token
198
                while (matchOffset < removedEndOffset && matchIndex < tokenCount) {
200
                // Find modified token by binary search
199
                    matchOffset += tokenList.tokenOrEmbeddingUnsync(matchIndex++).token().length();
201
                int low = 0; // use index as 'high'
202
                while (low <= index) {
203
                    int mid = (low + index) / 2;
204
                    int midStartOffset = tokenList.tokenOffset(mid);
205
206
                    if (midStartOffset < modOffset) {
207
                        low = mid + 1;
208
                    } else if (midStartOffset > modOffset) {
209
                        index = mid - 1;
210
                    } else {
211
                        // Token starting exactly at modOffset found
212
                        index = mid;
213
                        modTokenOffset = midStartOffset;
214
                        break;
215
                    }
216
                }
200
                }
217
                if (index < low) { // no token starting right at 'modOffset'
201
            } else { // For inside-token inserts match on the next token
218
                    modTokenOffset = tokenList.tokenOffset(index);
202
                if (matchOffset < eventInfo.modOffset()) {
219
                }
203
                    matchOffset += tokenList.tokenOrEmbeddingUnsync(matchIndex++).token().length();
220
                modToken = token(tokenList, index);
221
                if (loggable) {
222
                    LOG.log(Level.FINE, "BIN-SEARCH: index=" + index
223
                            + ", modTokenOffset=" + modTokenOffset
224
                            + ", modToken.id()=" + modToken.id() + "\n");
225
                }
204
                }
226
            }
205
            }
206
            // Update the matchOffset so that it corresponds to the state
207
            // after the modification
208
            matchOffset += eventInfo.diffLength();
227
        }
209
        }
228
210
229
        // Store the index that points to the modified token
211
        // Check whether modification affected previous token
230
        // i.e. modification at its begining or inside.
212
        while (relexIndex > 0 && relexOffset + tokenList.lookahead(relexIndex - 1) > eventInfo.modOffset()) {
231
        // Index variable can later be modified but present value is important
213
            relexIndex--;
232
        // for moving of the offset gap later.
214
            if (loggable) {
233
        change.setOffsetGapIndex(index);
215
                LOG.log(Level.FINE, "    Token at rInd=" + relexIndex + " affected (la=" + // NOI18N
234
216
                        tokenList.lookahead(relexIndex) + ") => relex it\n"); // NOI18N
235
        // Index and offset from which the relexing will start.
236
        int relexIndex;
237
        int relexOffset;
238
        // Whether the token validation should be attempted or not.
239
        boolean attemptValidation = false;
240
241
        if (index == 0) { // modToken is first in the list
242
            relexIndex = index;
243
            relexOffset = modTokenOffset;
244
            // Can validate modToken if removal does not span whole token
245
            if (modToken != null && removedLength < modToken.length()) {
246
                attemptValidation = true;
247
            }
217
            }
248
218
            AbstractToken<T> token = tokenList.tokenOrEmbeddingUnsync(relexIndex).token();
249
        } else { // Previous token exists
219
            relexOffset -= token.length();
250
            // Check for insert-only right at the end of the previous token
251
            if (modOffset == modTokenOffset && removedLength == 0) {
252
                index--; // move to previous token
253
                modToken = token(tokenList, index);
254
                modTokenOffset -= modToken.length();
255
            }
256
257
            // Check whether modification affected previous token
258
            if (index == 0 || modTokenOffset + tokenList.lookahead(index - 1) <= modOffset) {
259
                // Modification did not affect previous token
260
                relexIndex = index;
261
                relexOffset = modTokenOffset;
262
                // Check whether modification was localized to modToken only
263
                if (modOffset + removedLength < modTokenOffset + modToken.length()) {
264
                    attemptValidation = true;
265
                }
266
267
            } else { // at least previous token affected
268
                relexOffset = modTokenOffset - token(tokenList, index - 1).length();
269
                relexIndex = index - 2; // Start with token below previous token
270
                
271
                // Go back and mark all affected tokens for removals
272
                while (relexIndex >= 0) {
273
                    AbstractToken<T> token = token(tokenList, relexIndex);
274
                    // Check if token was not affected by modification
275
                    if (relexOffset + tokenList.lookahead(relexIndex) <= modOffset) {
276
                        break;
277
                    }
278
                    relexIndex--;
279
                    relexOffset -= token.length();
280
                }
281
                relexIndex++; // Next token will be relexed
282
            }
283
        }
220
        }
284
        
221
        
285
        // The lowest offset at which the relexing can end
222
        // Check whether actual relexing is necessary
286
        // (the relexing may end at higher offset if the relexed
287
        // tokens will end at different boundaries than the original
288
        // tokens or if the states after the tokens' recognition
289
        // will differ from the original states in the original tokens.
290
        int matchOffset;
291
292
        // Perform token validation of modToken if possible.
293
        // The index variable will hold the token index right before the matching point.
294
        if (attemptValidation) {
295
            matchOffset = modTokenOffset + modToken.length();
296
            TokenValidator tokenValidator = languageOperation.tokenValidator(modToken.id());
297
            if (tokenValidator != null && (tokenList.getClass() != IncTokenList.class)) {
298
                    
299
//                if (tokenValidator.validateToken(modToken, modOffset - modTokenOffset, modRelOffset,
300
//                        removedLength, insertedLength)
301
//                ) {
302
//                    // Update positions
303
//                                change.initRemovedAddedOffsets()
304
305
//                    return; // validated successfully
306
//                }
307
            }
308
309
        } else { // Validation cannot be attempted
310
            // Need to compute matchOffset and matchIndex
311
            // by iterating forward
312
            if (index < tokenCount) {
313
                matchOffset = modTokenOffset + modToken.length();
314
                int removeEndOffset = modOffset + removedLength;
315
                while (matchOffset < removeEndOffset && index + 1 < tokenCount) {
316
                    index++;
317
                    matchOffset += token(tokenList, index).length();
318
                }
319
320
            } else // After last token
321
                matchOffset = modTokenOffset;
322
        }
323
324
        // State from which the lexer can be started
223
        // State from which the lexer can be started
325
        Object relexState = (relexIndex > 0) ? tokenList.state(relexIndex - 1) : zeroIndexRelexState;
224
        Object relexState = (relexIndex > 0) ? tokenList.state(relexIndex - 1) : null;
326
        // Update the matchOffset so that it corresponds to the state
225
        change.setIndex(relexIndex);
327
        // after the modification
328
        matchOffset += insertedLength - removedLength;
329
        change.setOffset(relexOffset);
226
        change.setOffset(relexOffset);
227
        change.setMatchIndex(matchIndex);
228
        change.setMatchOffset(matchOffset);
330
        
229
        
331
        // Variables' values:
332
        // 'index' - points to modified token. Or index == tokenCount for modification
333
        //     past the last token.
334
        // 'tokenCount' - token count in the original token list.
335
        // 'relexIndex' - points to the token that will be relexed as first.
336
        // 'relexOffset' - points to begining of the token that will be relexed as first.
337
        // 'matchOffset' - points to end of token after which the fixed token list could
338
        //     possibly match the original token list. Points to end of token at 'index'
339
        //     variable if 'index < tokenCount' and to the end of the last token
340
        //     if 'index == tokenCount'.
341
342
        // Check whether relexing is necessary.
230
        // Check whether relexing is necessary.
343
        // Necessary condition for no-relexing is that the matchToken
231
        // Necessary condition for no-relexing is a removal at token's boundary
344
        // has zero lookahead (if lookahead would be >0 
232
        // and the token right before modOffset must have zero lookahead (if lookahead would be >0 
345
        // then the matchToken would be affected and relexOffset != matchOffset).
233
        // then the token would be affected) and the states before relexIndex must equal
346
        // The states before relex token must match the state after the modified token
234
        // to the state before matchIndex.
347
        // In case of removal starting and ending at token boundaries
348
        // the relexing might not be necessary.
349
        boolean relex = (relexOffset != matchOffset)
235
        boolean relex = (relexOffset != matchOffset)
350
                || index >= tokenCount
236
                || (eventInfo.insertedLength() > 0)
351
                || !LexerUtilsConstants.statesEqual(relexState, tokenList.state(index));
237
                || (matchIndex == 0) // ensure the tokenList.state(matchIndex - 1) will not fail with IOOBE
238
                || !LexerUtilsConstants.statesEqual(relexState, tokenList.state(matchIndex - 1));
352
239
353
        // There is an extra condition that the lookahead of the matchToken
240
        // There is an extra condition that the lookahead of the matchToken
354
        // must not span the next (retained) token. This condition helps to ensure
241
        // must not span the next (retained) token. This condition helps to ensure
Lines 356-547 Link Here
356
        // As the empty tokens are not allowed the situation may only occur
243
        // As the empty tokens are not allowed the situation may only occur
357
        // for lookahead > 1.
244
        // for lookahead > 1.
358
        int lookahead;
245
        int lookahead;
359
        if (!relex && (lookahead = tokenList.lookahead(index)) > 1 && index + 1 < tokenCount) {
246
        if (!relex && (lookahead = tokenList.lookahead(matchIndex - 1)) > 1 && matchIndex < tokenCount) {
360
            relex = (lookahead > token(tokenList, index + 1).length()); // check next token
247
            // Check whether lookahead of the token before match point exceeds the whole token right after match point
248
            relex = (lookahead > tokenList.tokenOrEmbeddingUnsync(matchIndex).token().length()); // check next token
361
        }
249
        }
362
250
363
        if (loggable) {
251
        if (loggable) {
364
            LOG.log(Level.FINE, "BEFORE-RELEX: index=" + index + ", modTokenOffset=" + modTokenOffset
252
            StringBuilder sb = new StringBuilder(200);
365
                    + ", relexIndex=" + relexIndex + ", relexOffset=" + relexOffset
253
            sb.append("BEFORE-RELEX: relex=").append(relex);
366
                    + ", relexState=" + relexState
254
            sb.append(", rInd=").append(relexIndex).append(", rOff=").append(relexOffset);
367
                    + ", matchOffset=" + matchOffset
255
            sb.append(", mInd=").append(matchIndex).append(", mOff=").append(matchOffset).append('\n');
368
                    + ", perform relex: " + relex + "\n");
256
            sb.append(", rSta=").append(relexState).append(", tokenList-part:\n");
257
            LexerUtilsConstants.appendTokenList(sb, tokenList, matchIndex, matchIndex - 3, matchIndex + 3, false, 4, false);
258
            sb.append('\n');
259
            LOG.log(Level.FINE, sb.toString());
260
        }
261
262
        assert (relexIndex >= 0);
263
        if (relex) {
264
            // Create lexer input operation for the given token list
265
            LexerInputOperation<T> lexerInputOperation
266
                    = tokenList.createLexerInputOperation(relexIndex, relexOffset, relexState);
267
            relex(change, lexerInputOperation, tokenCount);
268
        }
269
270
        tokenList.replaceTokens(change, eventInfo.diffLength());
271
        if (loggable) {
272
            LOG.log(Level.FINE, "TLU.updateRegular() FINISHED: change:" + change + "\nMods:" + change.toStringMods(4));
273
        }
274
    }
275
276
277
    /**
278
     * Use incremental algorithm to update a JoinTokenList after a modification done in the underlying storage.
279
     * <br>
280
     * The assumption is that there may only be two states:
281
     * <ul>
282
     *   <li> There is a local input source modification bounded to a particular ETL.
283
     *        In such case there should be NO token lists removed/added.
284
     *   </li>
285
     *   <li> The modification spans multiple ETLs and all the affected ETLs will be removed.
286
     *        The modification is "bounded" by the removed ETLs i.e.
287
     *            modOffset &gt;= first-removed-ETL.startOffset()
288
     *        and modOffset + removedLength &lt;= last-removed-ETL.endOffset()
289
     *   </li>
290
     * </ul>
291
     * 
292
     * @param change non-null change that will incorporate the performed chagnes.
293
     * @param modOffset offset where the modification occurred.
294
     *  For join token lists if modification is done inside a JoinToken
295
     *  the modOffset must be a logical distance from token's begining
296
     *  that corresponds to the modificaion's point (i.e. like if the token
297
     *  would be continuous).
298
     * @param insertedLength number of characters inserted at modOffset.
299
     * @param removedLength number of characters removed at modOffset.
300
     */
301
    public static <T extends TokenId> void updateJoined(JoinTokenListChange<T> change, TokenHierarchyEventInfo eventInfo) {
302
        MutableJoinTokenList<T> jtl = (MutableJoinTokenList<T>) change.tokenList();
303
        TokenListListUpdate<T> tokenListListUpdate = change.tokenListListUpdate();
304
        int tokenCount = jtl.tokenCount();
305
        boolean loggable = LOG.isLoggable(Level.FINE);
306
        if (loggable) {
307
            logModification(jtl.inputSourceText(), eventInfo, tokenCount, true);
369
        }
308
        }
370
        
309
        
371
        if (relex) { // Start relexing
310
        // First determine what area is affected by removed/added ETLs
372
            LexerInputOperation<T> lexerInputOperation
311
        int relexJoinIndex;
373
                    = tokenList.createLexerInputOperation(relexIndex, relexOffset, relexState);
312
        int modOffset = eventInfo.modOffset();
313
        int relexTokenListIndex = tokenListListUpdate.modTokenListIndex; // Index of ETL where a change occurred.
314
        // Relative distance of mod against relex point (or point of ETLs added/removed)
315
        int relModOffset;
316
        if (tokenListListUpdate.isTokenListsMod()) {
317
            // Find relexJoinIndex by examining ETL at relexTokenListIndex-1.
318
            // This way the code is more uniform than examining ETL at relexTokenListIndex.
319
            if (relexTokenListIndex > 0) { // non-first ETL
320
                relexTokenListIndex--;
321
                jtl.setActiveTokenListIndex(relexTokenListIndex);
322
                EmbeddedTokenList<T> relexEtl = jtl.activeTokenList();
323
                EmbeddedJoinInfo joinInfo = relexEtl.joinInfo;
324
                relexJoinIndex = jtl.activeEndJoinIndex();
325
                if (joinInfo.joinTokenLastPartShift() > 0) { // Mod points inside join token
326
                    // Find first non-empty ETL below to determine partTextOffset()
327
                    while (relexEtl.tokenCountCurrent() == 0) { // No tokens in ETL
328
                        jtl.setActiveTokenListIndex(--relexTokenListIndex);
329
                        relexEtl = jtl.activeTokenList();
330
                    }
331
                    // relexEtl is non-empty - last token is PartToken
332
                    PartToken<T> partToken = (PartToken<T>) relexEtl.tokenOrEmbeddingUnsync(
333
                            relexEtl.tokenCountCurrent() - 1).token();
334
                    relModOffset = partToken.partTextOffset();
335
                } else { // Not a join token => use first token at relexTokenListIndex
336
                    relexTokenListIndex++;
337
                    relModOffset = 0;
338
                }
339
            } else { // (relexTokenListIndex == 0)
340
                relexJoinIndex = 0;
341
                jtl.setActiveTokenListIndex(0);
342
                relModOffset = 0;
343
            }
374
344
375
            do { // Fetch new tokens from lexer as necessary
345
        } else { // No token list mod
376
                AbstractToken<T> token = lexerInputOperation.nextToken();
346
            assert ((eventInfo.insertedLength() > 0) || (eventInfo.removedLength() > 0)) : "No modification";
377
                if (token == null) {
347
            jtl.setActiveTokenListIndex(relexTokenListIndex);
378
                    attemptValidation = false;
348
            EmbeddedTokenList<T> relexEtl = jtl.activeTokenList();
349
            change.charModTokenList = relexEtl;
350
            // Search within releEtl only - can use binary search safely (unlike on JTL with removed ETLs)
351
            int[] indexAndTokenOffset = relexEtl.tokenIndex(modOffset); // Index could be -1 TBD
352
            relexJoinIndex = relexEtl.joinInfo.joinTokenIndex() + indexAndTokenOffset[0];
353
            relModOffset = modOffset - indexAndTokenOffset[1];
354
        }
355
356
        // Matching point index and offset. Matching point vars are assigned early
357
        // and relex-vars are possibly shifted down first and then the match-vars are updated.
358
        // That's because otherwise the "working area" of JTL (above/below token list mod)
359
        // would have to be switched below and above.
360
        int matchJoinIndex = relexJoinIndex;
361
        int matchOffset = modOffset - relModOffset; // Suitable for single-ETL update (will be corrected later)
362
        
363
        // Update relex-vars according to lookahead of tokens before relexJoinIndex
364
        while (relexJoinIndex > 0 && jtl.lookahead(relexJoinIndex - 1) > relModOffset) {
365
            AbstractToken<T> relexToken = jtl.tokenOrEmbeddingUnsync(--relexJoinIndex).token();
366
            relModOffset += relexToken.length(); // User regular token.length() here
367
            if (loggable) {
368
                LOG.log(Level.FINE, "    Token at rInd=" + relexJoinIndex + " affected (la=" + // NOI18N
369
                        jtl.lookahead(relexJoinIndex) + ") => relex it\n"); // NOI18N
370
            }
371
        }
372
373
        // Create lexer input operation now since JTL should be positioned before removed ETLs
374
        // and JLIO needs to scan tokens backwards for fly sequence length.
375
        Object relexState = (relexJoinIndex > 0) ? jtl.state(relexJoinIndex - 1) : null;
376
        int relexLocalIndex = jtl.tokenStartLocalIndex(relexJoinIndex);
377
        relexTokenListIndex = jtl.activeTokenListIndex();
378
        int relexOffset = jtl.activeTokenList().tokenOffsetByIndex(relexLocalIndex);
379
        JoinLexerInputOperation<T> lexerInputOperation = new MutableJoinLexerInputOperation<T>(
380
                jtl, relexJoinIndex, relexState, relexTokenListIndex, relexOffset, tokenListListUpdate);
381
        lexerInputOperation.init();
382
        change.setIndex(relexJoinIndex);
383
        change.setOffset(relexOffset);
384
        change.setStartInfo(lexerInputOperation, relexLocalIndex);
385
        // setMatchIndex() and setMatchOffset() called later below
386
387
        // Index of token before which the relexing will end (or == tokenCount)
388
        if (tokenListListUpdate.isTokenListsMod()) { // Assign first token after last removed ETL
389
            int afterModTokenListIndex = tokenListListUpdate.modTokenListIndex + tokenListListUpdate.removedTokenListCount;
390
            if (afterModTokenListIndex == jtl.tokenListCount()) { // Removed till end
391
                matchJoinIndex = tokenCount;
392
                matchOffset = Integer.MAX_VALUE;
393
            } else { // Removed inside 
394
                EmbeddedTokenList<T> afterModEtl = jtl.tokenList(afterModTokenListIndex);
395
                matchJoinIndex = afterModEtl.joinInfo.joinTokenIndex();
396
                // Check if the first token of afterModEtl is not an end of join token
397
                // or that the afterModEtl does not participate in a join token (may be empty)
398
                if (afterModEtl.tokenCountCurrent() > 0) {
399
                    AbstractToken<T> token = afterModEtl.tokenOrEmbeddingUnsync(0).token();
400
                    if (token.getClass() == PartToken.class) {
401
                        matchJoinIndex++;
402
                        matchOffset = afterModEtl.startOffset() + token.length();
403
                    } else {
404
                        matchOffset = afterModEtl.startOffset();
405
                    }
406
                } else { // No tokens in this ETL
407
                    int joinTokenLastPartShift = afterModEtl.joinInfo.joinTokenLastPartShift();
408
                    if (joinTokenLastPartShift > 0) { // Part of join token
409
                        afterModTokenListIndex += joinTokenLastPartShift;
410
                        matchJoinIndex++;
411
                        matchOffset = afterModEtl.startOffset();
412
                    } else { // Empty ETL but not a part of JoinToken - ending empty ETL(s)
413
                        matchOffset = afterModEtl.startOffset();
414
                    }
415
                }
416
                // Move jtl past removed/added token lists
417
                jtl.setActiveTokenListIndex(afterModTokenListIndex);
418
419
            }
420
        } else { // No token ETLs removed/added
421
            // matchOffset already initialized to (modOffset - orig-relModOffset)
422
            if (eventInfo.removedLength() > 0) { // At least remove token at relexOffset
423
                matchOffset += jtl.tokenOrEmbeddingUnsync(matchJoinIndex++).token().length();
424
                int removedEndOffset = eventInfo.modOffset() + eventInfo.removedLength();
425
                while (matchOffset < removedEndOffset) {
426
                    matchOffset += jtl.tokenOrEmbeddingUnsync(matchJoinIndex++).token().length();
427
                }
428
            } else { // For inside-token inserts match on the next token
429
                if (matchOffset < eventInfo.modOffset()) {
430
                    matchOffset += jtl.tokenOrEmbeddingUnsync(matchJoinIndex++).token().length();
431
                }
432
            }
433
            // Update the matchOffset so that it corresponds to the state
434
            // after the modification
435
            matchOffset += eventInfo.diffLength();
436
        }
437
438
        // TBD relexing necessity optimizations like in updateRegular()
439
        change.setMatchIndex(matchJoinIndex);
440
        change.setMatchOffset(matchOffset);
441
        relex(change, lexerInputOperation, tokenCount);
442
        jtl.replaceTokens(change, eventInfo.diffLength());
443
        if (loggable) {
444
            LOG.log(Level.FINE, "TLU.updateRegular() FINISHED: change:" + change + // NOI18N
445
                    "\nMods:" + change.toStringMods(4)); // NOI18N
446
        }
447
    }
448
449
450
    /**
451
     * Relex part of input to create new tokens. This method may sometimes be skipped e.g. for removal of chars
452
     * corresponding to a single token preceded by a token with zero lookahead.
453
     * <br/>
454
     * This code is common for both updateRegular() and updateJoined().
455
     * 
456
     * @param tokenList non-null token list that is being updated. It may be top-level list
457
     *  or embedded token list.
458
     * @param change token list change into which the created tokens are being added.
459
     * @param tokenCount current token count in tokenList.
460
     */
461
    private static <T extends TokenId> void relex(TokenListChange<T> change,
462
            LexerInputOperation<T> lexerInputOperation, int tokenCount
463
    ) {
464
        boolean loggable = LOG.isLoggable(Level.FINE);
465
        MutableTokenList<T> tokenList = change.tokenList();
466
        // Remember the match index below which the comparison of extra relexed tokens
467
        // (matching the original ones) cannot go.
468
        int lowestMatchIndex = change.matchIndex;
469
470
        AbstractToken<T> token;
471
        int relexOffset = lexerInputOperation.lastTokenEndOffset();
472
        while ((token = lexerInputOperation.nextToken()) != null) {
473
            // Get lookahead and state; Will certainly use them both since updater runs for inc token lists only
474
            int lookahead = lexerInputOperation.lookahead();
475
            Object state = lexerInputOperation.lexerState();
476
            if (loggable) {
477
                StringBuilder sb = new StringBuilder(100);
478
                sb.append("LEXED-TOKEN: ");
479
                int tokenEndOffset = lexerInputOperation.lastTokenEndOffset();
480
                CharSequence inputSourceText = tokenList.inputSourceText();
481
                if (tokenEndOffset > inputSourceText.length()) {
482
                    sb.append(tokenEndOffset).append("!! => ");
483
                    tokenEndOffset = inputSourceText.length();
484
                    sb.append(tokenEndOffset);
485
                }
486
                sb.append('"');
487
                CharSequenceUtilities.debugText(sb, inputSourceText.subSequence(relexOffset, tokenEndOffset));
488
                sb.append('"');
489
                sb.append(" ").append(token.id());
490
                sb.append(", <").append(relexOffset);
491
                sb.append(", ").append(relexOffset + token.length());
492
                sb.append("> LA=").append(lookahead);
493
                sb.append(", state=").append(state);
494
                sb.append(", IHC=").append(System.identityHashCode(token));
495
                sb.append("\n");
496
                LOG.log(Level.FINE, sb.toString());
497
            }
498
499
            change.addToken(token, lookahead, state);
500
            // Here add regular token length even for JoinToken instances
501
            // since this is used solely for comparing with matchOffset which
502
            // also uses the per-input-chars coordinates. Real token's offset is independent value
503
            // assigned by the underlying TokenListChange and LexerInputOperation.
504
            relexOffset = lexerInputOperation.lastTokenEndOffset();
505
            // Marks all original tokens that would cover the area of just lexed token as removed.
506
            // 'matchIndex' will point right above the last token that was removed
507
            // 'matchOffset' will point to the end of the last removed token
508
            if (relexOffset > change.matchOffset) {
509
                do { // Mark all tokens below
510
                    if (change.matchIndex == tokenCount) { // index == tokenCount
511
                        if (tokenList.isFullyLexed()) {
512
                            change.matchOffset = Integer.MAX_VALUE; // Force lexing till end of input
513
                        } else { // Not fully lexed -> stop now
514
                            // Fake the conditions to break the relexing loop
515
                            change.matchOffset = relexOffset;
516
                            state = tokenList.state(change.matchIndex - 1);
517
                        }
518
                        break;
519
                    }
520
                    // Skip the token at matchIndex and also increase matchOffset
521
                    // The default (increasing matchOffset by token.length()) is overriden for join token list.
522
                    change.increaseMatchIndex();
523
                } while (relexOffset > change.matchOffset);
524
            }
525
526
            // Check whether the new token ends at matchOffset with the same state
527
            // like the original which typically means end of relexing
528
            if (relexOffset == change.matchOffset
529
                && LexerUtilsConstants.statesEqual(state, 
530
                    (change.matchIndex > 0) ? tokenList.state(change.matchIndex - 1) : null)
531
            ) {
532
                // Here it's a potential match and the relexing could end.
533
                // However there are additional SAME-LOOKAHEAD requirements
534
                // that are checked here and if not satisfied the relexing will continue.
535
                // SimpleLexerRandomTest.test() contains detailed description.
536
                
537
                // If there are no more original tokens to be removed then stop since
538
                // there are no tokens ahead that would possibly have to be relexed because of LA differences.
539
                if (change.matchIndex == tokenCount)
540
                    break;
541
542
                int matchPointOrigLookahead = (change.matchIndex > 0)
543
                        ? tokenList.lookahead(change.matchIndex - 1)
544
                        : 0;
545
                // If old and new LAs are the same it should be safe to stop relexing.
546
                // Also since all tokens are non-empty it's enough to just check
547
                // LA > 1 (because LA <= 1 cannot span more than one token).
548
                // The same applies for current LA.
549
                if (lookahead == matchPointOrigLookahead ||
550
                    matchPointOrigLookahead <= 1 && lookahead <= 1
551
                ) {
552
                    break;
553
                }
554
                
555
                int afterMatchPointTokenLength = tokenList.tokenOrEmbeddingUnsync(change.matchIndex).token().length();
556
                if (matchPointOrigLookahead <= afterMatchPointTokenLength &&
557
                    lookahead <= afterMatchPointTokenLength
558
                ) {
559
                    // Here both the original and relexed before-match-point token
560
                    // have their LAs ending within bounds of the after-match-point token so it's OK
379
                    break;
561
                    break;
380
                }
562
                }
381
563
382
                lookahead = lexerInputOperation.lookahead();
564
                // It's true that nothing can be generally predicted about LA if the token after match point
383
                Object state = lexerInputOperation.lexerState();
565
                // would be relexed (compared to the original's token LA). However the following criteria
384
                if (loggable) {
566
                // should possibly suffice.
385
                    LOG.log(Level.FINE, "LEXED-TOKEN: id=" + token.id()
567
                int afterMatchPointOrigTokenLookahead = tokenList.lookahead(change.matchIndex);
386
                            + ", length=" + token.length()
568
                if (lookahead - afterMatchPointTokenLength <= afterMatchPointOrigTokenLookahead &&
387
                            + ", lookahead=" + lookahead
569
                    (matchPointOrigLookahead <= afterMatchPointTokenLength ||
388
                            + ", state=" + state + "\n");
570
                        lookahead >= matchPointOrigLookahead)
389
                }
571
                ) {
390
                
572
                    // The orig LA of after-match-point token cannot be lower than the currently lexed  LA's projection into it.
391
                change.addToken(token, lookahead, state);
573
                    // Also check that the orig lookahead ended in the after-match-point token
392
574
                    // or otherwise require the relexed before-match-point token to have >= lookahead of the original
393
                relexOffset += token.length();
575
                    // before-match-point token).
394
                // Remove obsolete tokens that would cover the area of just lexed token
576
                    break;
395
                // 'index' will point to the last token that was removed
396
                // 'matchOffset' will point to the end of the last removed token
397
                if (relexOffset > matchOffset && index < tokenCount) {
398
                    attemptValidation = false;
399
                    do {
400
                        index++;
401
                        if (index == tokenCount) {
402
                            // Make sure the number of removed tokens will be computed properly later
403
                            modToken = null;
404
                            // Check whether it should lex till the end
405
                            // or whether 'Match at anything' should be done
406
                            if (tokenList.isFullyLexed()) {
407
                                // Will lex till the end of input
408
                                matchOffset = Integer.MAX_VALUE;
409
                            } else {
410
                                // Force stop lexing
411
                                relex = false;
412
                            }
413
                            break;
414
                        }
415
                        matchOffset += token(tokenList, index).length();
416
                    } while (relexOffset > matchOffset);
417
                }
577
                }
418
578
419
                // Check whether the new token ends at matchOffset with the same state
579
                // The token at matchIndex must be relexed
420
                // like the original which typically means end of relexing
580
                if (loggable) {
421
                if (relexOffset == matchOffset
581
                    LOG.log(Level.FINE, "    EXTRA-RELEX: mInd=" + change.matchIndex + ", LA=" + lookahead + "\n");
422
                    && (index < tokenCount)
582
                }
423
                    && LexerUtilsConstants.statesEqual(state, tokenList.state(index))
583
                // Skip the token at matchIndex
424
                ) {
584
                change.increaseMatchIndex();
425
                    // Here it's a potential match and the relexing could end.
585
                // Continue by fetching next token
426
                    // However there are additional conditions that need to be checked.
586
            }
427
                    // 1. Check whether lookahead of the last relexed token
587
        }
428
                    //  does not exceed length plus LA of the subsequent (original) token.
588
        lexerInputOperation.release();
429
                    //  See initial part of SimpleRandomTest.test() verifies this.
430
                    // 2. Algorithm attempts to have the same lookaheads in tokens
431
                    //  like the regular batch scanning would produce.
432
                    //  Although not strictly necessary requirement
433
                    //  it helps to simplify the debugging in case the lexer does not work
434
                    //  well in the incremental setup.
435
                    //  The following code checks that the lookahead of the original match token
436
                    //  (i.e. the token right before matchOffset) does "end" inside
437
                    //  the next token - if not then relexing the next token is done.
438
                    //  The second part of SimpleRandomTest.test() verifies this.
439
589
440
                    // 'index' points to the last token that was removed
590
        // If at least two tokens were lexed it's possible that e.g. the last added token
441
                    int matchTokenLookahead = tokenList.lookahead(index);
591
        // will be the same like the last removed token and in such case
442
                    // Optimistically suppose that the relexing will end
592
        // the addition of the last token should be 'undone'.
443
                    relex = false;
593
        // This all may happen due to the fact that for larger lookaheads
444
                    // When assuming non-empty tokens the lookahead 1
594
        // the algorithm must relex the token(s) within lookahead (see the code above).
445
                    // just reaches the end of the next token
595
        int lastAddedTokenIndex = change.addedTokenOrEmbeddingsCount() - 1;
446
                    // so lookhead < 1 is always fine from this point of view.
596
        // There should remain at least one added token since that one
447
                    if (matchTokenLookahead > 1 || lookahead > 1) {
597
        // may not be the same like the original removed one because
448
                        // Start with token right after the last removed token starting at matchOffset
598
        // token lengths would differ because of the input source modification.
449
                        int i = index + 1;
599
        
450
                        // Process additional removals by increasing 'index'
600
        if (change.matchOffset != Integer.MAX_VALUE) { // would not make sense when lexing past end of existing tokens
451
                        // 'lookahead' holds
601
            while (lastAddedTokenIndex >= 1 && // At least one token added
452
                        while (i < tokenCount) {
602
                    change.matchIndex > lowestMatchIndex // At least one token removed
453
                            int tokenLength = token(tokenList, i).length();
603
            ) {
454
                            lookahead -= tokenLength; // decrease extra lookahead
604
                AbstractToken<T> lastAddedToken = change.addedTokenOrEmbeddings().get(lastAddedTokenIndex).token();
455
                            matchTokenLookahead -= tokenLength;
605
                AbstractToken<T> lastRemovedToken = tokenList.tokenOrEmbeddingUnsync(change.matchIndex - 1).token();
456
                            if (lookahead <= 0 && matchTokenLookahead <=0) {
606
                if (lastAddedToken.id() != lastRemovedToken.id()
457
                                break; // No more work
607
                    || lastAddedToken.length() != lastRemovedToken.length()
458
                            }
608
                    || change.laState().lookahead(lastAddedTokenIndex) != tokenList.lookahead(change.matchIndex - 1)
459
                            if (lookahead != tokenList.lookahead(i)
460
                                    || matchTokenLookahead > 0
461
                            ) {
462
                                // This token must be relexed
463
                                if (loggable) {
464
                                    LOG.log(Level.FINE, "EXTRA-RELEX: index=" + index + ", lookahead=" + lookahead
465
                                            + ", tokenLength=" + tokenLength + "\n");
466
                                }
467
                                index = i;
468
                                matchOffset += tokenLength;
469
                                relex = true;
470
                                // Continue - further tokens may be affected
471
                            }
472
                            i++;
473
                        }
474
                    }
475
476
                    if (!relex) {
477
                        if (attemptValidation) {
478
//                            if (modToken.id() == token.id()
479
//                                    && tokenList.lookahead(index) == lookahead
480
//                                    && !modToken.isFlyweight()
481
//                                    && !token.isFlyweight()
482
//                                    && (tokenList.getClass() != IncTokenList.class
483
//                                        || change.tokenHierarchyOperation().canModifyToken(index, modToken))
484
//                                    && LexerSpiTokenPackageAccessor.get().restoreToken(
485
//                                            languageOperation.tokenHandler(),
486
//                                            modToken, token)
487
//                            ) {
488
//                                // Restored successfully
489
//                                // TODO implement - fix token's length and return
490
//                                // now default in fact to failed validation
491
//                            }
492
                            attemptValidation = false;
493
                        }
494
                    }
495
                }
496
            } while (relex); // End of the relexing loop
497
            lexerInputOperation.release();
498
499
            // If at least two tokens were lexed it's possible that e.g. the last added token
500
            // will be the same like the last removed token and in such case
501
            // the addition of the last token should be 'undone'.
502
            // This all may happen due to the fact that for larger lookaheads
503
            // the algorithm must relex the token(s) within lookahead (see the code above).
504
            int lastAddedTokenIndex = change.addedTokensOrBranchesCount() - 1;
505
            // There should remain at least one added token since that one
506
            // may not be the same like the original removed one because
507
            // token lengths would differ because of the input source modification.
508
            while (lastAddedTokenIndex >= 1 && index > relexIndex && index < tokenCount) {
509
                AbstractToken<T> addedToken = LexerUtilsConstants.token(
510
                        change.addedTokensOrBranches().get(lastAddedTokenIndex));
511
                AbstractToken<T> removedToken = token(tokenList, index);
512
                if (addedToken.id() != removedToken.id()
513
                    || addedToken.length() != removedToken.length()
514
                    || change.laState().lookahead(lastAddedTokenIndex) != tokenList.lookahead(index)
515
                    || !LexerUtilsConstants.statesEqual(change.laState().state(lastAddedTokenIndex),
609
                    || !LexerUtilsConstants.statesEqual(change.laState().state(lastAddedTokenIndex),
516
                        tokenList.state(index))
610
                        tokenList.state(change.matchIndex - 1))
517
                ) {
611
                ) {
518
                    break;
612
                    break;
519
                }
613
                }
520
                // Last removed and added tokens are the same so undo the addition
614
                // Last removed and added tokens are the same so undo the addition
521
                if (loggable) {
615
                if (loggable) {
522
                    LOG.log(Level.FINE, "RETAIN-ORIGINAL: index=" + index + ", id=" + removedToken.id() + "\n");
616
                    LOG.log(Level.FINE, "    RETAIN-ORIGINAL at (mInd-1)=" + (change.matchIndex-1) +
617
                            ", id=" + lastRemovedToken.id() + "\n");
523
                }
618
                }
524
                lastAddedTokenIndex--;
619
                lastAddedTokenIndex--;
525
                index--;
620
                change.removeLastAddedToken(); // Includes decreasing of matchIndex and matchOffset
526
                relexOffset -= addedToken.length();
527
                change.removeLastAddedToken();
528
            }
621
            }
622
        } else { // matchOffset == Integer.MAX_VALUE
623
            // Fix matchOffset to point to end of last token since it's used
624
            //   as last-added-token-end-offset in event notifications
625
            change.setMatchOffset(relexOffset);
529
        }
626
        }
627
    }
530
628
531
        // Now ensure that the original tokens will be replaced by the relexed ones.
629
    private static void logModification(CharSequence inputSourceText, TokenHierarchyEventInfo eventInfo,
532
        int removedTokenCount = (modToken != null) ? (index - relexIndex + 1) : (index - relexIndex);
630
            int tokenCount, boolean updateJoined
533
        if (loggable) {
631
    ) {
534
            LOG.log(Level.FINE, "TokenListUpdater.update() FINISHED: Removed:"
632
        int modOffset = eventInfo.modOffset();
535
                    + removedTokenCount + ", Added:" + change.addedTokensOrBranchesCount() + " tokens.\n");
633
        int removedLength = eventInfo.removedLength();
634
        int insertedLength = eventInfo.insertedLength();
635
        String insertedText = "";
636
        if (insertedLength > 0) {
637
            insertedText = ", insTxt:\"" + CharSequenceUtilities.debugText(
638
                    inputSourceText.subSequence(modOffset, modOffset + insertedLength)) + '"';
536
        }
639
        }
537
        change.setIndex(relexIndex);
640
        // Debug 10 chars around modOffset
538
        change.setAddedEndOffset(relexOffset);
641
        int afterInsertOffset = modOffset + insertedLength;
539
        tokenList.replaceTokens(change, removedTokenCount, insertedLength - removedLength);
642
        CharSequence beforeText = inputSourceText.subSequence(Math.max(afterInsertOffset - 5, 0), afterInsertOffset);
540
    }
643
        CharSequence afterText = inputSourceText.subSequence(afterInsertOffset,
541
    
644
                Math.min(afterInsertOffset + 5, inputSourceText.length()));
542
    private static <T extends TokenId> AbstractToken<T> token(MutableTokenList<T> tokenList, int index) {
645
        StringBuilder sb = new StringBuilder(200);
543
        Object tokenOrEmbeddingContainer = tokenList.tokenOrEmbeddingContainerUnsync(index); // Unsync impl suffices
646
        sb.append("TLU.update");
544
        return LexerUtilsConstants.token(tokenOrEmbeddingContainer);
647
        sb.append(updateJoined ? "Joined" : "Regular");
648
        sb.append("() modOff=").append(modOffset);
649
        sb.append(", text-around:\"").append(beforeText).append('|');
650
        sb.append(afterText).append("\", insLen=");
651
        sb.append(insertedLength).append(insertedText);
652
        sb.append(", remLen=").append(removedLength);
653
        sb.append(", tCnt=").append(tokenCount).append('\n');
654
        LOG.log(Level.FINE, sb.toString());
545
    }
655
    }
546
656
547
}
657
}
(-)a/lexer/src/org/netbeans/lib/lexer/token/AbstractToken.java (-83 / +87 lines)
Lines 41-70 Link Here
41
41
42
package org.netbeans.lib.lexer.token;
42
package org.netbeans.lib.lexer.token;
43
43
44
import org.netbeans.lib.lexer.TokenOrEmbedding;
45
import java.util.List;
44
import org.netbeans.api.lexer.PartType;
46
import org.netbeans.api.lexer.PartType;
45
import org.netbeans.api.lexer.Token;
47
import org.netbeans.api.lexer.Token;
46
import org.netbeans.api.lexer.TokenHierarchy;
48
import org.netbeans.api.lexer.TokenHierarchy;
47
import org.netbeans.api.lexer.TokenId;
49
import org.netbeans.api.lexer.TokenId;
48
import org.netbeans.lib.editor.util.CharSequenceUtilities;
50
import org.netbeans.lib.editor.util.CharSequenceUtilities;
49
import org.netbeans.lib.lexer.EmbeddedTokenList;
51
import org.netbeans.lib.lexer.EmbeddedTokenList;
50
import org.netbeans.lib.lexer.LexerApiPackageAccessor;
52
import org.netbeans.lib.lexer.EmbeddingContainer;
51
import org.netbeans.lib.lexer.LexerUtilsConstants;
52
import org.netbeans.lib.lexer.TokenList;
53
import org.netbeans.lib.lexer.TokenList;
53
54
54
/**
55
/**
55
 * Abstract token is base class of all token implementations used in the lexer module.
56
 * Abstract token is base class of all token implementations used in the lexer module.
57
 * <br/>
58
 * Two descendants of AbstractToken:
59
 * <ul>
60
 *   <li>{@link DefaultToken} - by default does not contain a text but points
61
 *       into a text storage of its token list instead. It may however cache
62
 *       its text as string in itself.
63
 *       <ul>
64
 *           <li></li>
65
 *       </ul>
66
 *   </li>
67
 *   <li>{@link TextToken} - contains text that it represents; may act as flyweight token.
68
 *       {@link CustomTextToken} allows a token to have a custom text independent
69
 *       of text of an actual storage.
70
 *   </li>
71
 * 
72
 * 
73
 *
56
 *
74
 *
57
 * @author Miloslav Metelka
75
 * @author Miloslav Metelka
58
 * @version 1.00
76
 * @version 1.00
59
 */
77
 */
60
78
61
public abstract class AbstractToken<T extends TokenId> extends Token<T> implements CharSequence {
79
public abstract class AbstractToken<T extends TokenId> extends Token<T>
80
implements TokenOrEmbedding<T> {
62
    
81
    
63
    private final T id; // 12 bytes (8-super + 4)
82
    private final T id; // 12 bytes (8-super + 4)
64
83
65
    private TokenList<T> tokenList; // 16 bytes
84
    protected TokenList<T> tokenList; // 16 bytes
66
    
85
    
67
    private int rawOffset; // 20 bytes
86
    protected int rawOffset; // 20 bytes
68
87
69
    /**
88
    /**
70
     * @id non-null token id.
89
     * @id non-null token id.
Lines 80-87 Link Here
80
        this.rawOffset = rawOffset;
99
        this.rawOffset = rawOffset;
81
    }
100
    }
82
    
101
    
83
    public abstract int length();
84
    
85
    /**
102
    /**
86
     * Get identification of this token.
103
     * Get identification of this token.
87
     *
104
     *
Lines 90-111 Link Here
90
    @Override
107
    @Override
91
    public final T id() {
108
    public final T id() {
92
        return id;
109
        return id;
93
    }
94
95
    /**
96
     * Get text represented by this token.
97
     */
98
    @Override
99
    public CharSequence text() {
100
        if (tokenList != null) {
101
            if (tokenList.getClass() == EmbeddedTokenList.class) {
102
                EmbeddedTokenList<?> etl = (EmbeddedTokenList<?>)tokenList;
103
                return etl.embeddingContainer().updateStatus() ? this : null;
104
            }
105
            return this;
106
        } else {
107
            return null;
108
        }
109
    }
110
    }
110
111
111
    /**
112
    /**
Lines 160-173 Link Here
160
    }
161
    }
161
162
162
    @Override
163
    @Override
163
    public final int offset(TokenHierarchy<?> tokenHierarchy) {
164
    public int offset(TokenHierarchy<?> tokenHierarchy) {
164
        if (rawOffset == -1) { // flyweight token
165
        if (tokenList != null) {
165
            return -1;
166
            if (tokenList.getClass() == EmbeddedTokenList.class) // Sync status first
167
                ((EmbeddedTokenList)tokenList).embeddingContainer().updateStatus();
168
            return tokenList.tokenOffset(this);
166
        }
169
        }
167
170
        return rawOffset; // Covers the case of flyweight token that will return -1
168
        return (tokenList != null)
169
                ? tokenList.childTokenOffset(rawOffset)
170
                : rawOffset;
171
//        if (tokenHierarchy != null) {
171
//        if (tokenHierarchy != null) {
172
//            return LexerApiPackageAccessor.get().tokenHierarchyOperation(
172
//            return LexerApiPackageAccessor.get().tokenHierarchyOperation(
173
//                    tokenHierarchy).tokenOffset(this, tokenList, rawOffset);
173
//                    tokenHierarchy).tokenOffset(this, tokenList, rawOffset);
Lines 177-183 Link Here
177
//                : rawOffset;
177
//                : rawOffset;
178
//        }
178
//        }
179
    }
179
    }
180
    
180
181
    @Override
181
    @Override
182
    public boolean hasProperties() {
182
    public boolean hasProperties() {
183
        return false;
183
        return false;
Lines 188-227 Link Here
188
        return null;
188
        return null;
189
    }
189
    }
190
190
191
    // CharSequence methods
191
    @Override
192
    /**
192
    public Token<T> joinToken() {
193
     * Implementation of <code>CharSequence.charAt()</code>
193
        return null;
194
     */
195
    public final char charAt(int index) {
196
        if (index < 0 || index >= length()) {
197
            throw new IndexOutOfBoundsException(
198
                "index=" + index + ", length=" + length() // NOI18N
199
            );
200
        }
201
        if (tokenList == null) { // Should normally not happen
202
            // A bit strange to throw IOOBE but it's more practical since
203
            // TokenHierarchy's dump can overcome IOOBE and deliver a useful debug but not NPEs etc.
204
            throw new IndexOutOfBoundsException("index=" + index + ", length=" + length() +
205
                    " but tokenList==null for token " + dumpInfo(null));
206
        }
207
        return tokenList.childTokenCharAt(rawOffset, index);
208
    }
194
    }
209
195
210
    public final CharSequence subSequence(int start, int end) {
196
    @Override
211
        return CharSequenceUtilities.toString(this, start, end);
197
    public List<? extends Token<T>> joinedParts() {
198
        return null;
199
    }
200
201
    // Implements TokenOrEmbedding
202
    public final AbstractToken<T> token() {
203
        return this;
212
    }
204
    }
213
    
205
    
214
    /**
206
    // Implements TokenOrEmbedding
215
     * This method is in fact <code>CharSequence.toString()</code> implementation.
207
    public final EmbeddingContainer<T> embedding() {
216
     */
208
        return null;
209
    }
210
217
    @Override
211
    @Override
218
    public String toString() {
212
    public boolean isRemoved() {
219
        // To prevent NPEs when token.toString() would called without checking
213
        if (tokenList != null) {
220
        // (text() == null) there is an extra check for that.
214
            if (tokenList.getClass() == EmbeddedTokenList.class)
221
        CharSequence text = text();
215
                ((EmbeddedTokenList)tokenList).embeddingContainer().updateStatus();
222
        return (text != null)
216
            return tokenList.isRemoved();
223
                ? CharSequenceUtilities.toString(this, 0, length())
217
        }
224
                : "<null>";
218
        return !isFlyweight();
219
    }
220
221
    public String dumpInfo() {
222
        return dumpInfo(null, null, true, 0).toString();
225
    }
223
    }
226
224
227
    /**
225
    /**
Lines 234-267 Link Here
234
     *
232
     *
235
     * @param tokenHierarchy <code>null</code> should be passed
233
     * @param tokenHierarchy <code>null</code> should be passed
236
     *  (the parameter is reserved for future use when token hierarchy snapshots will be implemented).
234
     *  (the parameter is reserved for future use when token hierarchy snapshots will be implemented).
235
     * @param dumpText whether text should be dumped (not for TokenListUpdater
236
     *  when text is already shifted).
237
     * @return dump of the thorough token information. If token's text is longer
237
     * @return dump of the thorough token information. If token's text is longer
238
     *  than 400 characters it will be shortened.
238
     *  than 400 characters it will be shortened.
239
     */
239
     */
240
    public String dumpInfo(TokenHierarchy<?> tokenHierarchy) {
240
    public StringBuilder dumpInfo(StringBuilder sb, TokenHierarchy<?> tokenHierarchy, boolean dumpTokenText, int indent) {
241
        StringBuilder sb = new StringBuilder();
241
        if (sb == null) {
242
        CharSequence text = text();
242
            sb = new StringBuilder(50);
243
        if (text != null) {
243
        }
244
            sb.append('"');
244
        if (dumpTokenText) {
245
            int textLength = text.length();
245
            CharSequence text = text();
246
            for (int i = 0; i < textLength; i++) {
246
            if (text != null) {
247
                if (textLength > 400 && i >= 200 && i < textLength - 200) {
247
                sb.append('"');
248
                    i = textLength - 200;
248
                int textLength = text.length();
249
                    sb.append(" ...<TEXT-SHORTENED>... "); // NOI18N
249
                for (int i = 0; i < textLength; i++) {
250
                    continue;
250
                    if (textLength > 400 && i >= 200 && i < textLength - 200) {
251
                        i = textLength - 200;
252
                        sb.append(" ...<TEXT-SHORTENED>... "); // NOI18N
253
                        continue;
254
                    }
255
                    try {
256
                        CharSequenceUtilities.debugChar(sb, text.charAt(i));
257
                    } catch (IndexOutOfBoundsException e) {
258
                        // For debugging purposes it's better than to completely fail
259
                        sb.append("IOOBE at index=").append(i).append("!!!"); // NOI18N
260
                        break;
261
                    }
251
                }
262
                }
252
                try {
263
                sb.append('"');
253
                    CharSequenceUtilities.debugChar(sb, text.charAt(i));
264
            } else {
254
                } catch (IndexOutOfBoundsException e) {
265
                sb.append("<null-text>"); // NOI18N
255
                    // For debugging purposes it's better than to completely fail
256
                    sb.append("IOOBE at index=").append(i).append("!!!"); // NOI18N
257
                    break;
258
                }
259
            }
266
            }
260
            sb.append('"');
267
            sb.append(' ');
261
        } else {
262
            sb.append("<null-text>"); // NOI18N
263
        }
268
        }
264
        sb.append(' ');
265
        if (isFlyweight()) {
269
        if (isFlyweight()) {
266
            sb.append("F(").append(length()).append(')');
270
            sb.append("F(").append(length()).append(')');
267
        } else {
271
        } else {
Lines 271-277 Link Here
271
        }
275
        }
272
        sb.append(' ').append(id != null ? id.name() + '[' + id.ordinal() + ']' : "<null-id>"); // NOI18N
276
        sb.append(' ').append(id != null ? id.name() + '[' + id.ordinal() + ']' : "<null-id>"); // NOI18N
273
        sb.append(" ").append(dumpInfoTokenType());
277
        sb.append(" ").append(dumpInfoTokenType());
274
        return sb.toString();
278
        return sb;
275
    }
279
    }
276
    
280
    
277
    protected String dumpInfoTokenType() {
281
    protected String dumpInfoTokenType() {
(-)a/lexer/src/org/netbeans/lib/lexer/token/ComplexToken.java (-80 lines)
Removed Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.token;
43
44
import org.netbeans.api.lexer.PartType;
45
import org.netbeans.api.lexer.TokenId;
46
import org.netbeans.spi.lexer.TokenPropertyProvider;
47
48
/**
49
 * Token that may hold custom text and also additional properties.
50
 *
51
 * @author Miloslav Metelka
52
 * @version 1.00
53
 */
54
55
public final class ComplexToken<T extends TokenId> extends CustomTextToken<T> {
56
57
    private final TokenPropertyProvider propertyProvider; // 36 bytes
58
    
59
    public ComplexToken(T id, int length, CharSequence customText, PartType partType,
60
    TokenPropertyProvider propertyProvider) {
61
        super(id, length, customText, partType);
62
        this.propertyProvider = propertyProvider;
63
    }
64
65
    @Override
66
    public boolean hasProperties() {
67
        return (propertyProvider != null);
68
    }
69
70
    @Override
71
    public Object getProperty(Object key) {
72
        return (propertyProvider != null) ? propertyProvider.getValue(this, key) : null;
73
    }
74
    
75
    @Override
76
    protected String dumpInfoTokenType() {
77
        return "ComT"; // NOI18N "ComplexToken"
78
    }
79
    
80
}
(-)a/lexer/src/org/netbeans/lib/lexer/token/CustomTextToken.java (-14 / +15 lines)
Lines 41-52 Link Here
41
41
42
package org.netbeans.lib.lexer.token;
42
package org.netbeans.lib.lexer.token;
43
43
44
import org.netbeans.api.lexer.PartType;
45
import org.netbeans.api.lexer.TokenId;
44
import org.netbeans.api.lexer.TokenId;
46
45
47
/**
46
/**
48
 * Token with a custom text and the token length likely different
47
 * Token with a custom text and the token length likely different
49
 * from text's length.
48
 * from text's length. It can be used to shrink size of the input chars
49
 * being referenced from skim token list by referencing some fixed characters.
50
 * <br/>
50
 * <br/>
51
 * Token with the custom text cannot be branched by a language embedding.
51
 * Token with the custom text cannot be branched by a language embedding.
52
 *
52
 *
Lines 54-80 Link Here
54
 * @version 1.00
54
 * @version 1.00
55
 */
55
 */
56
56
57
public class CustomTextToken<T extends TokenId> extends DefaultToken<T> {
57
public class CustomTextToken<T extends TokenId> extends TextToken<T> {
58
    
58
    
59
    private final CharSequence text; // 28 bytes (24-super + 4)
59
    private final int length; // 28 bytes (24-super + 4)
60
    
61
    private final PartType partType; // 32 bytes
62
    
60
    
63
    /**
61
    /**
64
     * @param id non-null identification of the token.
62
     * @param id non-null identification of the token.
63
     * @param text non-null text of the token.
65
     * @param length length of the token.
64
     * @param length length of the token.
66
     * @param text non-null text of the token.
67
     */
65
     */
68
    public CustomTextToken(T id, int length, CharSequence text, PartType partType) {
66
    public CustomTextToken(T id, CharSequence text, int length) {
69
        super(id, length);
67
        super(id, text);
70
        assert (text != null);
68
        this.length = length;
71
        this.text = text;
72
        this.partType = partType;
73
    }
69
    }
74
    
70
    
75
    @Override
71
    @Override
76
    public final CharSequence text() {
72
    public boolean isCustomText() {
77
        return text;
73
        return true;
74
    }
75
76
    @Override
77
    public final int length() {
78
        return length;
78
    }
79
    }
79
    
80
    
80
    @Override
81
    @Override
(-)a/lexer/src/org/netbeans/lib/lexer/token/DefaultToken.java (-5 / +206 lines)
Lines 42-47 Link Here
42
package org.netbeans.lib.lexer.token;
42
package org.netbeans.lib.lexer.token;
43
43
44
import org.netbeans.api.lexer.TokenId;
44
import org.netbeans.api.lexer.TokenId;
45
import org.netbeans.lib.editor.util.CharSequenceUtilities;
46
import org.netbeans.lib.lexer.LexerUtilsConstants;
45
47
46
/**
48
/**
47
 * Default token which by default obtains text from its background storage.
49
 * Default token which by default obtains text from its background storage.
Lines 61-67 Link Here
61
63
62
public class DefaultToken<T extends TokenId> extends AbstractToken<T> implements CharSequence {
64
public class DefaultToken<T extends TokenId> extends AbstractToken<T> implements CharSequence {
63
    
65
    
64
    private final int length; // 24 bytes (20-super + 4)
66
    /**
67
     * Used in Token.text() to decide whether "this" should be returned and
68
     * a text.charAt() will be slower or, for larger tokens,
69
     * a subsequence of input source text should be created and returned instead..
70
     */
71
    private static final int INPUT_SOURCE_SUBSEQUENCE_THRESHOLD = 30;
72
    
73
    /**
74
     * Field that is of type CharSequence and is either length of the token
75
     * or cached text of the token as String.
76
     */
77
    private CharSequence tokenLengthOrCachedText; // 24 bytes (20-super + 4)
65
    
78
    
66
    /**
79
    /**
67
     * Construct new default token.
80
     * Construct new default token.
Lines 69-75 Link Here
69
    public DefaultToken(T id, int length) {
82
    public DefaultToken(T id, int length) {
70
        super(id);
83
        super(id);
71
        assert (length > 0) : "Token length=" + length + " <= 0"; // NOI18N
84
        assert (length > 0) : "Token length=" + length + " <= 0"; // NOI18N
72
        this.length = length;
85
        this.tokenLengthOrCachedText = TokenLength.get(length);
73
    }
86
    }
74
    
87
    
75
    /**
88
    /**
Lines 77-88 Link Here
77
     */
90
     */
78
    public DefaultToken(T id) {
91
    public DefaultToken(T id) {
79
        super(id);
92
        super(id);
80
        this.length = 0;
93
        this.tokenLengthOrCachedText = TokenLength.get(0);
81
    }
94
    }
82
95
83
    @Override
96
    @Override
84
    public final int length() {
97
    public int length() {
85
        return length;
98
        return tokenLengthOrCachedText.length();
86
    }
99
    }
87
100
88
    @Override
101
    @Override
Lines 90-93 Link Here
90
        return "DefT"; // NOI18N "TextToken" or "FlyToken"
103
        return "DefT"; // NOI18N "TextToken" or "FlyToken"
91
    }
104
    }
92
    
105
    
106
    /**
107
     * Get text represented by this token.
108
     */
109
    @Override
110
    public CharSequence text() {
111
        CharSequence text;
112
        if (tokenLengthOrCachedText.getClass() == TokenLength.class) {
113
            if (!isRemoved()) { // Updates status for EmbeddedTokenList; tokenList != null
114
                int len = tokenLengthOrCachedText.length();
115
                if (len >= INPUT_SOURCE_SUBSEQUENCE_THRESHOLD) {
116
                    // Create subsequence of input source text
117
                    CharSequence inputSourceText = tokenList.inputSourceText();
118
                    int tokenOffset = tokenList.tokenOffset(this);
119
                    text = new InputSourceSubsequence(this, inputSourceText,
120
                            tokenOffset, tokenOffset + len, tokenOffset, tokenOffset + len);
121
                } else { // Small token
122
                    text = this;
123
                }
124
            } else { // Token is removed
125
                text = null;
126
            }
127
        } else { // tokenLength contains cached text
128
            text = tokenLengthOrCachedText;
129
        }
130
        return text;
131
    }
132
133
    /**
134
     * Implementation of <code>CharSequence.charAt()</code>
135
     * for case when this token is used as token's text char sequence.
136
     */
137
    public final char charAt(int index) {
138
        if (index < 0 || index >= length()) {
139
            throw new IndexOutOfBoundsException(
140
                "index=" + index + ", length=" + length() // NOI18N
141
            );
142
        }
143
        if (tokenList == null) { // Should normally not happen
144
            // A bit strange to throw IOOBE but it's more practical since
145
            // TokenHierarchy's dump can overcome IOOBE and deliver a useful debug but not NPEs etc.
146
            throw new IndexOutOfBoundsException("index=" + index + ", length=" + length() +
147
                    " but tokenList==null for token " + dumpInfo(null, null, false, 0));
148
        }
149
        int tokenOffset = tokenList.tokenOffset(this);
150
        return tokenList.inputSourceText().charAt(tokenOffset + index);
151
    }
152
153
    /**
154
     * Implementation of <code>CharSequence.subSequence()</code>
155
     * for case when this token is used as token's text char sequence.
156
     */
157
    public final CharSequence subSequence(int start, int end) {
158
        // Create subsequence of token's text
159
        CharSequence text;
160
        int textLength = tokenLengthOrCachedText.length();
161
        CharSequenceUtilities.checkIndexesValid(start, end, textLength);
162
163
        if (tokenLengthOrCachedText.getClass() == TokenLength.class) {
164
            // If calling this.subSequence() then this.text() was already called
165
            // so the status should be updated already and also the token is not removed.
166
            // For simplicity always make a subsequence of the input source text.
167
            CharSequence inputSourceText = tokenList.inputSourceText();
168
            int tokenOffset = tokenList.tokenOffset(this);
169
            text = new InputSourceSubsequence(this, inputSourceText,
170
                    tokenOffset + start, tokenOffset + end, tokenOffset, tokenOffset + textLength);
171
172
        } else { // tokenLength contains cached text
173
            text = tokenLengthOrCachedText.subSequence(start, end);
174
        }
175
        return text;
176
    }
177
    
178
    /**
179
     * Implementation of <code>CharSequence.toString()</code>
180
     * for case when this token is used as token's text char sequence.
181
     */
182
    @Override
183
    public String toString() {
184
        // In reality this method can either be called as result of calling Token.text().toString()
185
        // or just calling Token.toString() for debugging purposes
186
        String textStr;
187
        if (tokenLengthOrCachedText.getClass() == TokenLength.class) {
188
            if (!isRemoved()) { // Updates status for EmbeddedTokenList; tokenList != null
189
                TokenLength tokenLength = (TokenLength) tokenLengthOrCachedText;
190
                CharSequence inputSourceText = tokenList.inputSourceText();
191
                int nextCacheFactor = tokenLength.nextCacheFactor();
192
                int threshold = (inputSourceText.getClass() == String.class)
193
                            ? LexerUtilsConstants.INPUT_TEXT_STRING_THRESHOLD
194
                            : LexerUtilsConstants.CACHE_TOKEN_TO_STRING_THRESHOLD;
195
                int tokenOffset = tokenList.tokenOffset(this);
196
                textStr = inputSourceText.subSequence(tokenOffset,
197
                        tokenOffset + tokenLength.length()).toString();
198
                if (nextCacheFactor < threshold) {
199
                    tokenLengthOrCachedText = tokenLength.next(nextCacheFactor);
200
                } else { // Should become cached
201
                    tokenLengthOrCachedText = textStr;
202
                }
203
                setTokenLengthOrCachedText(tokenLengthOrCachedText);
204
            } else { // Token already removed
205
                textStr = "<null>";
206
            }
207
208
        } else { // tokenLength contains cached text
209
            textStr = tokenLengthOrCachedText.toString();
210
        }
211
        return textStr;
212
    }
213
214
    synchronized CharSequence tokenLengthOrCachedText() {
215
        return tokenLengthOrCachedText;
216
    }
217
    
218
    synchronized void setTokenLengthOrCachedText(CharSequence tokenLengthOrCachedText) {
219
        this.tokenLengthOrCachedText = tokenLengthOrCachedText;
220
    }
221
    
222
223
    private static final class InputSourceSubsequence implements CharSequence {
224
        
225
        private final DefaultToken token; // (8-super + 4) = 12 bytes
226
        
227
        private final CharSequence inputSourceText; // 16 bytes
228
        
229
        private final int start; // 20 bytes
230
        
231
        private final int end; // 24 bytes
232
        
233
        private final int tokenStart; // 28 bytes
234
        
235
        private final int tokenEnd; // 32 bytes
236
        
237
        public InputSourceSubsequence(DefaultToken token, CharSequence text,
238
                int start, int end, int tokenStart, int tokenEnd
239
        ) {
240
            this.token = token;
241
            this.inputSourceText = text;
242
            this.start = start;
243
            this.end = end;
244
            this.tokenStart = tokenStart;
245
            this.tokenEnd = tokenEnd;
246
        }
247
        
248
        public int length() {
249
            return end - start;
250
        }
251
        
252
        public char charAt(int index) {
253
            CharSequenceUtilities.checkIndexValid(index, length());
254
            return inputSourceText.charAt(start + index);
255
        }
256
257
        public CharSequence subSequence(int start, int end) {
258
            CharSequenceUtilities.checkIndexesValid(this, start, end);
259
            return new InputSourceSubsequence(token, inputSourceText,
260
                    this.start + start, this.start + end, tokenStart, tokenEnd);
261
        }
262
263
        @Override
264
        public String toString() {
265
            String textStr;
266
            // Increase usage
267
            CharSequence tokenLengthOrCachedText = token.tokenLengthOrCachedText();
268
            if (tokenLengthOrCachedText.getClass() == TokenLength.class) {
269
                TokenLength tokenLength = (TokenLength) tokenLengthOrCachedText;
270
                int nextCacheFactor = tokenLength.nextCacheFactor();
271
                int threshold = (inputSourceText.getClass() == String.class)
272
                            ? LexerUtilsConstants.INPUT_TEXT_STRING_THRESHOLD
273
                            : LexerUtilsConstants.CACHE_TOKEN_TO_STRING_THRESHOLD;
274
                if (nextCacheFactor < threshold) {
275
                    textStr = inputSourceText.subSequence(start, end).toString();
276
                    tokenLengthOrCachedText = tokenLength.next(nextCacheFactor);
277
                } else { // Should become cached
278
                    // Create cached text
279
                    String tokenTextString = inputSourceText.subSequence(tokenStart, tokenEnd).toString();
280
                    tokenLengthOrCachedText = tokenTextString;
281
                    // Substring returns this for start == 0 && end == length()
282
                    textStr = tokenTextString.substring(start - tokenStart, end - tokenStart);
283
                }
284
                token.setTokenLengthOrCachedText(tokenLengthOrCachedText);
285
286
            } else { // Already cached text
287
                textStr = tokenLengthOrCachedText.subSequence(start - tokenStart, end - tokenStart).toString();
288
            }
289
            return textStr;
290
        }
291
292
    }
293
93
}
294
}
(-)06a7890f802e (+136 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.token;
43
44
import java.util.List;
45
import org.netbeans.api.lexer.PartType;
46
import org.netbeans.api.lexer.TokenHierarchy;
47
import org.netbeans.api.lexer.TokenId;
48
import org.netbeans.lib.editor.util.ArrayUtilities;
49
import org.netbeans.spi.lexer.TokenPropertyProvider;
50
51
/**
52
 * Token consisting of multiple parts.
53
 *
54
 * @author Miloslav Metelka
55
 * @version 1.00
56
 */
57
58
public final class JoinToken<T extends TokenId> extends PropertyToken<T> {
59
60
    private List<PartToken<T>> joinedParts; // 32 bytes (28-super + 4)
61
    
62
    private int completeLength; // 36 bytes
63
    
64
    /**
65
     * Number of ETLs spanned including empty ETLs except a first part.
66
     */
67
    private int extraTokenListSpanCount; // 40 bytes
68
69
    public JoinToken(T id, int length, TokenPropertyProvider<T> propertyProvider, PartType partType) {
70
        super(id, length, propertyProvider, partType);
71
    }
72
73
    @Override
74
    public List<PartToken<T>> joinedParts() {
75
        return joinedParts;
76
    }
77
    
78
    public void setJoinedParts(List<PartToken<T>> joinedParts, int extraTokenListSpanCount) {
79
        assert (joinedParts != null) : "joinedParts expected to be non-null";
80
        this.joinedParts = joinedParts;
81
        for (PartToken partToken : joinedParts) {
82
            completeLength += partToken.length();
83
        }
84
        this.extraTokenListSpanCount = extraTokenListSpanCount;
85
    }
86
87
    public PartToken<T> lastPart() {
88
        return joinedParts.get(joinedParts.size() - 1);
89
    }
90
91
    public int extraTokenListSpanCount() {
92
        return extraTokenListSpanCount;
93
    }
94
95
    @Override
96
    public int offset(TokenHierarchy<?> tokenHierarchy) {
97
        return joinedParts.get(0).offset(tokenHierarchy);
98
    }
99
    
100
    @Override
101
    public int length() {
102
        return completeLength;
103
    }
104
105
    @Override
106
    public CharSequence text() {
107
        return new JoinTokenText<T>(joinedParts, completeLength);
108
    }
109
110
    @Override
111
    public boolean isRemoved() {
112
        // Check whether last part of token is removed - this needs to be improved
113
        // for the case when token is just partially recreated.
114
        return lastPart().isRemoved();
115
    }
116
117
    @Override
118
    public StringBuilder dumpInfo(StringBuilder sb, TokenHierarchy<?> tokenHierarchy, boolean dumpTokenText, int indent) {
119
        super.dumpInfo(sb, tokenHierarchy, dumpTokenText, indent);
120
        sb.append(", ").append(joinedParts.size()).append(" parts");
121
        int digitCount = String.valueOf(joinedParts.size() - 1).length();
122
        for (int i = 0; i < joinedParts.size(); i++) {
123
            sb.append('\n');
124
            ArrayUtilities.appendSpaces(sb, indent + 2);
125
            ArrayUtilities.appendBracketedIndex(sb, i, digitCount);
126
            joinedParts.get(i).dumpInfo(sb, tokenHierarchy, dumpTokenText, indent + 4);
127
        }
128
        return sb;
129
    }
130
131
    @Override
132
    protected String dumpInfoTokenType() {
133
        return "JoiT"; // NOI18N
134
    }
135
136
}
(-)06a7890f802e (+130 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.token;
43
44
import java.util.List;
45
import java.util.logging.Level;
46
import java.util.logging.Logger;
47
import org.netbeans.api.lexer.TokenId;
48
import org.netbeans.lib.editor.util.CharSequenceUtilities;
49
50
/**
51
 * Char sequence over join token parts.
52
 * 
53
 * @author Miloslav Metelka
54
 */
55
56
public final class JoinTokenText<T extends TokenId> implements CharSequence {
57
    
58
    private static final Logger LOG = Logger.getLogger(JoinTokenText.class.getName());
59
60
    private List<PartToken<T>> joinedParts;
61
62
    private int activePartIndex;
63
    
64
    private CharSequence activeInputText;
65
66
    private int activeStartCharIndex;
67
    
68
    private int activeEndCharIndex;
69
    
70
    private int length;
71
    
72
    public JoinTokenText(List<PartToken<T>> joinedParts, int length) {
73
        this.joinedParts = joinedParts;
74
        this.activeInputText = joinedParts.get(0).text();
75
        // Implicit: this.activeStartCharIndex = 0;
76
        this.activeEndCharIndex = activeInputText.length();
77
        this.length = length;
78
    }
79
80
    public synchronized char charAt(int index) {
81
        if (index < activeStartCharIndex) { // Find non-empty previous
82
            if (index < 0)
83
                throw new IndexOutOfBoundsException("index=" + index + " < 0");
84
            do {
85
                activePartIndex--;
86
                if (activePartIndex < 0) { // Should never happen
87
                    LOG.log(Level.WARNING, "Internal error: index=" + index + ", " + dumpState());
88
                }
89
                activeInputText = joinedParts.get(activePartIndex).text();
90
                int len = activeInputText.length();
91
                activeEndCharIndex = activeStartCharIndex;
92
                activeStartCharIndex -= len;
93
            } while (index < activeStartCharIndex);
94
        } else if (index >= activeEndCharIndex) { // Find non-empty next
95
            if (index >= length)
96
                throw new IndexOutOfBoundsException("index=" + index + " >= length()=" + length);
97
            do {
98
                activePartIndex++;
99
                activeInputText = joinedParts.get(activePartIndex).text();
100
                int len = activeInputText.length();
101
                activeStartCharIndex = activeEndCharIndex;
102
                activeEndCharIndex += len;
103
            } while (index >= activeEndCharIndex);
104
        }
105
106
        // Valid char within current segment
107
        return activeInputText.charAt(index - activeStartCharIndex);
108
    }
109
110
    public int length() {
111
        return length;
112
    }
113
    
114
    public CharSequence subSequence(int start, int end) {
115
        return CharSequenceUtilities.toString(this, start, end);
116
    }
117
    
118
    @Override
119
    public synchronized String toString() {
120
        return CharSequenceUtilities.toString(this);
121
    }
122
    
123
    private String dumpState() {
124
        return "activeTokenListIndex=" + activePartIndex +
125
                ", activeStartCharIndex=" + activeStartCharIndex +
126
                ", activeEndCharIndex=" + activeEndCharIndex +
127
                ", length=" + length;
128
    }
129
130
}
(-)06a7890f802e (+103 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.token;
43
44
import org.netbeans.api.lexer.PartType;
45
import org.netbeans.api.lexer.TokenId;
46
import org.netbeans.lib.lexer.TokenOrEmbedding;
47
import org.netbeans.spi.lexer.TokenPropertyProvider;
48
49
/**
50
 * Part of a {@link JoinToken}.
51
 *
52
 * @author Miloslav Metelka
53
 * @version 1.00
54
 */
55
56
public final class PartToken<T extends TokenId> extends PropertyToken<T> {
57
58
    private TokenOrEmbedding<T> joinTokenOrEmbedding; // 32 bytes (28-super + 4)
59
    
60
    private int partTokenIndex; // Index of this part inside 
61
    
62
    private int partTextOffset; // Offset of this part's text among all parts that comprise the complete token
63
64
    public PartToken(T id, int length, TokenPropertyProvider<T> propertyProvider, PartType partType,
65
            TokenOrEmbedding<T> joinToken, int partTokenIndex, int partTextOffset
66
    ) {
67
        super(id, length, propertyProvider, partType);
68
        setJoinTokenOrEmbedding(joinToken);
69
        this.partTokenIndex = partTokenIndex;
70
        this.partTextOffset = partTextOffset;
71
    }
72
73
    @Override
74
    public JoinToken<T> joinToken() {
75
        return (JoinToken<T>)joinTokenOrEmbedding.token();
76
    }
77
    
78
    public boolean isLastPart() {
79
        return (joinToken().lastPart() == this);
80
    }
81
    
82
    public TokenOrEmbedding<T> joinTokenOrEmbedding() {
83
        return joinTokenOrEmbedding;
84
    }
85
    
86
    public void setJoinTokenOrEmbedding(TokenOrEmbedding<T> joinTokenOrEmbedding) {
87
        this.joinTokenOrEmbedding = joinTokenOrEmbedding;
88
    }
89
90
    public int partTokenIndex() {
91
        return partTokenIndex;
92
    }
93
94
    public int partTextOffset() {
95
        return partTextOffset;
96
    }
97
98
    @Override
99
    protected String dumpInfoTokenType() {
100
        return "ParT[" + partTokenIndex + "]"; // NOI18N
101
    }
102
103
}
(-)06a7890f802e (+112 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.token;
43
44
import java.util.EnumSet;
45
import org.netbeans.api.lexer.PartType;
46
import org.netbeans.api.lexer.Token;
47
import org.netbeans.api.lexer.TokenId;
48
import org.netbeans.spi.lexer.TokenPropertyProvider;
49
50
/**
51
 * Property provider that stores {@link org.netbeans.api.lexer.PartType} information.
52
 *
53
 * @author Miloslav Metelka
54
 * @version 1.00
55
 */
56
57
public final class PartTypePropertyProvider implements TokenPropertyProvider<TokenId> {
58
    
59
    private static final PartTypePropertyProvider[] partTypeOrdinal2Provider
60
            = new PartTypePropertyProvider[PartType.class.getEnumConstants().length];
61
62
    static {
63
        for (PartType partType : EnumSet.allOf(PartType.class)) {
64
            partTypeOrdinal2Provider[partType.ordinal()] = new PartTypePropertyProvider(partType);
65
        }
66
    }
67
    
68
    public static <T extends TokenId> TokenPropertyProvider<T> get(PartType partType) {
69
        return (TokenPropertyProvider<T>)partTypeOrdinal2Provider[partType.ordinal()];
70
    }
71
    
72
    public static <T extends TokenId> TokenPropertyProvider<T> createDelegating(
73
            PartType partType, TokenPropertyProvider<T> delegate
74
    ) {
75
        return new Delegating<T>(partType, delegate);
76
    }
77
    
78
    private PartType partType;
79
80
    public PartTypePropertyProvider(PartType partType) {
81
        this.partType = partType;
82
    }
83
84
    public Object getValue(Token<TokenId> token, Object key) {
85
        if (key == PartType.class) {
86
            return partType;
87
        }
88
        return null;
89
    }
90
    
91
    private static final class Delegating<T extends TokenId> implements TokenPropertyProvider<T> {
92
        
93
        private final PartType partType;
94
        
95
        private final TokenPropertyProvider<T> delegate;
96
        
97
        Delegating(PartType partType, TokenPropertyProvider<T> delegate) {
98
            assert (delegate != null) : "delegate expected to be non-null. Use PartTypePropertyProvider.get() instead."; // NOTICES
99
            this.partType = partType;
100
            this.delegate = delegate;
101
        }
102
103
        public Object getValue(Token<T> token, Object key) {
104
            if (key == PartType.class) {
105
                return partType;
106
            }
107
            return delegate.getValue(token, key);
108
        }
109
110
    }
111
    
112
}
(-)a/lexer/src/org/netbeans/lib/lexer/token/PropertyToken.java (-23 / +16 lines)
Lines 42-77 Link Here
42
package org.netbeans.lib.lexer.token;
42
package org.netbeans.lib.lexer.token;
43
43
44
import org.netbeans.api.lexer.PartType;
44
import org.netbeans.api.lexer.PartType;
45
import org.netbeans.api.lexer.Token;
46
import org.netbeans.api.lexer.TokenId;
45
import org.netbeans.api.lexer.TokenId;
47
import org.netbeans.lib.lexer.LexerUtilsConstants;
48
import org.netbeans.spi.lexer.TokenPropertyProvider;
46
import org.netbeans.spi.lexer.TokenPropertyProvider;
49
47
50
/**
48
/**
51
 * Token that holds information about preprocessed characters.
49
 * Token with associated properties. It may also act as a token part but without
52
 *
50
 * a reference to a complete token e.g. suitable for java's incomplete block comment.
53
 * <p>
54
 * Instances of this token are more costly than other token types
55
 * because in addition to regular information they store preprocessed
56
 * text of the token.
57
 *
51
 *
58
 * @author Miloslav Metelka
52
 * @author Miloslav Metelka
59
 * @version 1.00
53
 * @version 1.00
60
 */
54
 */
61
55
62
public final class PropertyToken<T extends TokenId> extends DefaultToken<T> {
56
public class PropertyToken<T extends TokenId> extends DefaultToken<T> {
63
    
57
64
    private final TokenPropertyProvider propertyProvider; // 28 bytes (24-super + 4)
58
    private final TokenPropertyProvider<T> propertyProvider; // 28 bytes (24-super + 4)
65
    
59
66
    private final PartType partType; // 32 bytes
60
    public PropertyToken(T id, int length, TokenPropertyProvider<T> propertyProvider, PartType partType) {
67
    
68
    public PropertyToken(T id, int length,
69
    TokenPropertyProvider propertyProvider, PartType partType) {
70
        super(id, length);
61
        super(id, length);
71
        this.propertyProvider = propertyProvider;
62
        assert (partType != null);
72
        this.partType = partType;
63
        this.propertyProvider = (propertyProvider != null)
64
                ? PartTypePropertyProvider.createDelegating(partType, propertyProvider)
65
                : PartTypePropertyProvider.<T>get(partType);
73
    }
66
    }
74
    
67
75
    @Override
68
    @Override
76
    public boolean hasProperties() {
69
    public boolean hasProperties() {
77
        return (propertyProvider != null);
70
        return (propertyProvider != null);
Lines 81-95 Link Here
81
    public Object getProperty(Object key) {
74
    public Object getProperty(Object key) {
82
        return (propertyProvider != null) ? propertyProvider.getValue(this, key) : null;
75
        return (propertyProvider != null) ? propertyProvider.getValue(this, key) : null;
83
    }
76
    }
84
    
77
85
    @Override
78
    @Override
86
    public PartType partType() {
79
    public PartType partType() {
87
        return partType;
80
        return (PartType) getProperty(PartType.class);
88
    }
81
    }
89
82
90
    @Override
83
    @Override
91
    protected String dumpInfoTokenType() {
84
    protected String dumpInfoTokenType() {
92
        return "ProT"; // NOI18N "PrepToken"
85
        return "ProT"; // NOI18N
93
    }
86
    }
94
    
87
95
}
88
}
(-)a/lexer/src/org/netbeans/lib/lexer/token/TextToken.java (-8 / +7 lines)
Lines 45-57 Link Here
45
import org.netbeans.lib.lexer.TokenList;
45
import org.netbeans.lib.lexer.TokenList;
46
46
47
/**
47
/**
48
 * Token with an explicit text - either serving as a custom text token
48
 * Token with an explicit text - either serving a flyweight token
49
 * or a flyweight token.
49
 * or a non-flyweight replacement for a flyweight token.
50
 * <br/>
50
 * <br/>
51
 * The represented text can differ from the original content
51
 * The represented text should be the same like the original content
52
 * of the recognized text input portion.
52
 * of the recognized text input portion.
53
 * <br/>
54
 * Token with the custom text cannot be branched by a language embedding.
55
 *
53
 *
56
 * <p>
54
 * <p>
57
 * The text token can act as a flyweight token by calling
55
 * The text token can act as a flyweight token by calling
Lines 74-80 Link Here
74
     * is expected to correspond to the recognized input portion
72
     * is expected to correspond to the recognized input portion
75
     * (i.e. the text is not custom).
73
     * (i.e. the text is not custom).
76
     * <br/>
74
     * <br/>
77
     * The token can be made flyweight by using <code>setRawOffset(-1)</code>.
75
     * The token can be made flyweight by using <code>makeFlyweight()</code>.
78
     *
76
     *
79
     * @param id non-null identification of the token.
77
     * @param id non-null identification of the token.
80
     * @param text non-null text of the token.
78
     * @param text non-null text of the token.
Lines 92-98 Link Here
92
    }
90
    }
93
91
94
    @Override
92
    @Override
95
    public final int length() {
93
    public int length() {
96
        return text.length();
94
        return text.length();
97
    }
95
    }
98
96
Lines 102-108 Link Here
102
    }
100
    }
103
    
101
    
104
    public final TextToken<T> createCopy(TokenList<T> tokenList, int rawOffset) {
102
    public final TextToken<T> createCopy(TokenList<T> tokenList, int rawOffset) {
105
        return new TextToken<T>(id(), tokenList, rawOffset, text());
103
        return new TextToken<T>(id(), tokenList, rawOffset, text);
106
    }
104
    }
107
    
105
    
108
    @Override
106
    @Override
Lines 110-115 Link Here
110
        return isFlyweight() ? "FlyT" : "TexT"; // NOI18N "TextToken" or "FlyToken"
108
        return isFlyweight() ? "FlyT" : "TexT"; // NOI18N "TextToken" or "FlyToken"
111
    }
109
    }
112
110
111
    @Override
113
    public String toString() {
112
    public String toString() {
114
        return text.toString();
113
        return text.toString();
115
    }
114
    }
(-)06a7890f802e (+155 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.token;
43
44
import org.netbeans.lib.lexer.LexerUtilsConstants;
45
46
/**
47
 * Improves performance of doing Token.text().toString().
48
 * by using a cache factor which gets increased by every access to that method.
49
 * <br/>
50
 * Once a cache factor exceeds a threshold the result of Token.text().toString()
51
 * will be cached.
52
 * <br/>
53
 * TBD values of constants used by this class should be reviewed to match 
54
 * a real complexity of the particular operations.
55
 *
56
 * @author Miloslav Metelka
57
 * @version 1.00
58
 */
59
60
public final class TokenLength implements CharSequence {
61
    
62
    private static final TokenLength[][] CACHE = new TokenLength[
63
            LexerUtilsConstants.MAX_CACHED_TOKEN_LENGTH + 1][];
64
    
65
    
66
    public static TokenLength get(int length) {
67
        TokenLength tokenLength;
68
        if (length <= LexerUtilsConstants.MAX_CACHED_TOKEN_LENGTH) {
69
            synchronized (CACHE) {
70
                TokenLength[] tokenLengths = CACHE[length];
71
                if (tokenLengths == null) {
72
                    tokenLengths = new TokenLength[1];
73
                    CACHE[length] = tokenLengths;
74
                }
75
                tokenLength = tokenLengths[0];
76
                if (tokenLength == null) {
77
                    tokenLength = new TokenLength(length, 
78
                            LexerUtilsConstants.CACHE_TOKEN_TO_STRING_THRESHOLD, (short)1);
79
                    tokenLengths[0] = tokenLength;
80
                }
81
            }
82
        } else { // length too high - not cached
83
            tokenLength = new TokenLength(length,
84
                    LexerUtilsConstants.CACHE_TOKEN_TO_STRING_THRESHOLD, (short)1);
85
        }
86
        return tokenLength;
87
    }
88
    
89
    /**
90
     * Length of a token.
91
     */
92
    private final int length; // 12 bytes (8-super + 4)
93
    
94
    /**
95
     * Cache factor of this item.
96
     */
97
    private final short cacheFactor; // 14 bytes
98
    
99
    /**
100
     * Index of a next item in array of token lengths with the same length in CACHE.
101
     */
102
    private final short nextArrayIndex; // 16 bytes
103
    
104
    TokenLength(int length, short cacheFactor, short nextArrayIndex) {
105
        this.length = length;
106
        this.cacheFactor = cacheFactor;
107
        this.nextArrayIndex = nextArrayIndex;
108
    }
109
    
110
    public int length() {
111
        return length;
112
    }
113
    
114
    public short cacheFactor() {
115
        return cacheFactor;
116
    }
117
    
118
    public int nextCacheFactor() {
119
        return cacheFactor + length + LexerUtilsConstants.TOKEN_LENGTH_STRING_CREATION_FACTOR;
120
    }
121
    
122
    public TokenLength next(int nextCacheFactor) {
123
        TokenLength tokenLength;
124
        if (length <= LexerUtilsConstants.MAX_CACHED_TOKEN_LENGTH) {
125
            synchronized (CACHE) {
126
                TokenLength[] tokenLengths = CACHE[length];
127
                if (tokenLengths == null || tokenLengths.length <= nextArrayIndex) {
128
                    TokenLength[] tmp = new TokenLength[nextArrayIndex + 1];
129
                    if (tokenLengths != null) {
130
                        System.arraycopy(tokenLengths, 0, tmp, 0, tokenLengths.length);
131
                    }
132
                    tokenLengths = tmp;
133
                    CACHE[length] = tokenLengths;
134
                }
135
                tokenLength = tokenLengths[nextArrayIndex];
136
                if (tokenLength == null) {
137
                    tokenLength = new TokenLength(length, (short)nextCacheFactor, (short)(nextArrayIndex + 1));
138
                    tokenLengths[nextArrayIndex] = tokenLength;
139
                }
140
            }
141
        } else { // length too high - not cached
142
            tokenLength = new TokenLength(length, (short)nextCacheFactor, (short)(nextArrayIndex + 1));
143
        }
144
        return tokenLength;
145
    }
146
147
    public char charAt(int index) {
148
        throw new IllegalStateException("Should never be called.");
149
    }
150
151
    public CharSequence subSequence(int start, int end) {
152
        throw new IllegalStateException("Should never be called.");
153
    }
154
155
}
(-)a/lexer/src/org/netbeans/spi/lexer/LanguageEmbedding.java (-1 / +2 lines)
Lines 205-213 Link Here
205
        return joinSections;
205
        return joinSections;
206
    }
206
    }
207
    
207
    
208
    @Override
208
    public String toString() {
209
    public String toString() {
209
        return "language: " + language() + ", skip[" + startSkipLength() // NOI18N
210
        return "language: " + language() + ", skip[" + startSkipLength() // NOI18N
210
            + ", " + endSkipLength + "]"; // NOI18N
211
            + ", " + endSkipLength + "];" + (joinSections ? "join" : "no-join"); // NOI18N
211
    }
212
    }
212
    
213
    
213
}
214
}
(-)a/lexer/src/org/netbeans/spi/lexer/LanguageHierarchy.java (-3 / +5 lines)
Lines 48-54 Link Here
48
import org.netbeans.api.lexer.LanguagePath;
48
import org.netbeans.api.lexer.LanguagePath;
49
import org.netbeans.api.lexer.Token;
49
import org.netbeans.api.lexer.Token;
50
import org.netbeans.api.lexer.TokenId;
50
import org.netbeans.api.lexer.TokenId;
51
import org.netbeans.lib.lexer.CharProvider;
52
import org.netbeans.lib.lexer.LexerApiPackageAccessor;
51
import org.netbeans.lib.lexer.LexerApiPackageAccessor;
53
import org.netbeans.lib.lexer.LexerInputOperation;
52
import org.netbeans.lib.lexer.LexerInputOperation;
54
import org.netbeans.lib.lexer.LexerSpiPackageAccessor;
53
import org.netbeans.lib.lexer.LexerSpiPackageAccessor;
Lines 279-284 Link Here
279
    }
278
    }
280
279
281
    /**
280
    /**
281
     * This feature is currently not supported - Token.text()
282
     * will return null for non-flyweight tokens.
283
     * <br/>
282
     * Determine whether the text of the token with the particular id should
284
     * Determine whether the text of the token with the particular id should
283
     * be retained after the token has been removed from the token list
285
     * be retained after the token has been removed from the token list
284
     * because of the underlying mutable input source modification.
286
     * because of the underlying mutable input source modification.
Lines 387-394 Link Here
387
            return languageHierarchy.isRetainTokenText(id);
389
            return languageHierarchy.isRetainTokenText(id);
388
        }
390
        }
389
391
390
        public LexerInput createLexerInput(CharProvider charProvider) {
392
        public LexerInput createLexerInput(LexerInputOperation<?> operation) {
391
            return new LexerInput(charProvider);
393
            return new LexerInput(operation);
392
        }
394
        }
393
395
394
        public Language<?> language(MutableTextInput<?> mti) {
396
        public Language<?> language(MutableTextInput<?> mti) {
(-)a/lexer/src/org/netbeans/spi/lexer/LexerInput.java (-11 / +10 lines)
Lines 42-48 Link Here
42
package org.netbeans.spi.lexer;
42
package org.netbeans.spi.lexer;
43
43
44
import org.netbeans.lib.editor.util.AbstractCharSequence;
44
import org.netbeans.lib.editor.util.AbstractCharSequence;
45
import org.netbeans.lib.lexer.CharProvider;
45
import org.netbeans.lib.lexer.LexerInputOperation;
46
import org.netbeans.lib.lexer.LexerUtilsConstants;
46
import org.netbeans.lib.lexer.LexerUtilsConstants;
47
47
48
/**
48
/**
Lines 81-90 Link Here
81
    public static final int EOF = -1;
81
    public static final int EOF = -1;
82
    
82
    
83
    /**
83
    /**
84
     * Character provider to which this lexer input delegates
84
     * LexerInputOperation on which this lexer input delegates.
85
     * its operation.
86
     */
85
     */
87
    private CharProvider charProvider;
86
    private LexerInputOperation<?> operation;
88
    
87
    
89
    /**
88
    /**
90
     * Character sequence that corresponds
89
     * Character sequence that corresponds
Lines 101-110 Link Here
101
    /**
100
    /**
102
     * Construct instance of the lexer input.
101
     * Construct instance of the lexer input.
103
     *
102
     *
104
     * @param charProvider non-null character provider for this lexer input.
103
     * @param operation non-null character provider for this lexer input.
105
     */
104
     */
106
    LexerInput(CharProvider charProvider) {
105
    LexerInput(LexerInputOperation operation) {
107
        this.charProvider = charProvider;
106
        this.operation = operation;
108
    }
107
    }
109
    
108
    
110
    /**
109
    /**
Lines 116-122 Link Here
116
     *   - all of them will return EOF.
115
     *   - all of them will return EOF.
117
     */
116
     */
118
    public int read() {
117
    public int read() {
119
        int c = charProvider.read();
118
        int c = operation.read();
120
        if (c == EOF) {
119
        if (c == EOF) {
121
            eof = 1;
120
            eof = 1;
122
        }
121
        }
Lines 158-164 Link Here
158
            eof = 0; // backup EOF
157
            eof = 0; // backup EOF
159
            count--;
158
            count--;
160
        }
159
        }
161
        charProvider.backup(count);
160
        operation.backup(count);
162
    }
161
    }
163
    
162
    
164
    /**
163
    /**
Lines 178-184 Link Here
178
     *   If {@link LexerInput#EOF} was read then it is not counted into read length.
177
     *   If {@link LexerInput#EOF} was read then it is not counted into read length.
179
     */
178
     */
180
    public int readLength() {
179
    public int readLength() {
181
        return charProvider.readIndex();
180
        return operation.readLength();
182
    }
181
    }
183
    
182
    
184
    /**
183
    /**
Lines 332-338 Link Here
332
            if (index < 0 || index >= length) {
331
            if (index < 0 || index >= length) {
333
                throw new IndexOutOfBoundsException("index=" + index + ", length=" + length); // NOI18N
332
                throw new IndexOutOfBoundsException("index=" + index + ", length=" + length); // NOI18N
334
            }
333
            }
335
            return charProvider.readExisting(index);
334
            return operation.readExistingAtIndex(index);
336
        }
335
        }
337
        
336
        
338
    }
337
    }
(-)a/lexer/src/org/netbeans/spi/lexer/TokenFactory.java (-126 / +94 lines)
Lines 41-60 Link Here
41
41
42
package org.netbeans.spi.lexer;
42
package org.netbeans.spi.lexer;
43
43
44
import java.util.Set;
45
import org.netbeans.api.lexer.PartType;
44
import org.netbeans.api.lexer.PartType;
46
import org.netbeans.api.lexer.Token;
45
import org.netbeans.api.lexer.Token;
47
import org.netbeans.api.lexer.TokenId;
46
import org.netbeans.api.lexer.TokenId;
48
import org.netbeans.lib.editor.util.CharSequenceUtilities;
49
import org.netbeans.lib.lexer.LanguageOperation;
50
import org.netbeans.lib.lexer.LexerInputOperation;
47
import org.netbeans.lib.lexer.LexerInputOperation;
51
import org.netbeans.lib.lexer.TokenIdImpl;
48
import org.netbeans.lib.lexer.LexerUtilsConstants;
52
import org.netbeans.lib.lexer.token.CustomTextToken;
53
import org.netbeans.lib.lexer.token.DefaultToken;
54
import org.netbeans.lib.lexer.token.ComplexToken;
55
import org.netbeans.lib.lexer.token.ComplexToken;
56
import org.netbeans.lib.lexer.token.PropertyToken;
57
import org.netbeans.lib.lexer.token.TextToken;
58
49
59
/**
50
/**
60
 * Lexer should delegate all the token instances creation to this class.
51
 * Lexer should delegate all the token instances creation to this class.
Lines 67-86 Link Here
67
58
68
public final class TokenFactory<T extends TokenId> {
59
public final class TokenFactory<T extends TokenId> {
69
60
70
    /** Flag for additional correctness checks (may degrade performance). */
71
    private static final boolean testing = Boolean.getBoolean("netbeans.debug.lexer.test");
72
73
    /**
61
    /**
74
     * Token instance that should be returned by the lexer
62
     * Token instance that the token creation methods in this class produce
75
     * if there is an active filtering of certain token ids
63
     * if there is an active filtering of certain token ids
76
     * and the just recognized token-id should be skipped.
64
     * and the just recognized token-id should be skipped.
65
     * Normally lexers do not need to check for this except some specific cases
66
     * in which the {@link #isSkipToken(Token)} is a better typed alternative
67
     * to this field.
68
     *
69
     * @deprecated Use {@link #isSkipToken(Token)} instead.
77
     */
70
     */
78
    public static final Token SKIP_TOKEN
71
    public static final Token SKIP_TOKEN = LexerUtilsConstants.SKIP_TOKEN;
79
        = new TextToken<TokenId>(
80
            new TokenIdImpl("skip-token-id; special id of TokenFactory.SKIP_TOKEN; " + // NOI18N
81
                    " It should never be part of token sequence", 0, null), // NOI18N
82
            "" // empty skip token text NOI18N
83
        );
84
    
72
    
85
    private final LexerInputOperation<T> operation;
73
    private final LexerInputOperation<T> operation;
86
    
74
    
Lines 95-101 Link Here
95
     * @see #createToken(TokenId, int)
83
     * @see #createToken(TokenId, int)
96
     */
84
     */
97
    public Token<T> createToken(T id) {
85
    public Token<T> createToken(T id) {
98
        return createToken(id, operation.readIndex());
86
        return createToken(id, operation.readLength());
99
    }
87
    }
100
88
101
    /**
89
    /**
Lines 111-131 Link Here
111
     *  because of token id filter.
99
     *  because of token id filter.
112
     */
100
     */
113
    public Token<T> createToken(T id, int length) {
101
    public Token<T> createToken(T id, int length) {
114
        if (isSkipToken(id)) {
102
        return operation.createToken(id, length);
115
            operation.tokenRecognized(length, true);
116
            return skipToken();
117
        } else { // Do not skip the token
118
            if (operation.tokenRecognized(length, false)) { // Create preprocessed token
119
//                return new PreprocessedTextToken<T>(id, operation.tokenLength());
120
                return new DefaultToken<T>(id, operation.tokenLength());
121
            } else {
122
                return new DefaultToken<T>(id, operation.tokenLength());
123
            }
124
        }
125
    }
103
    }
126
104
127
    /**
105
    /**
128
     * Create regular token instance with an explicit length and part type.
106
     * Create regular token instance with an explicit length and part type.
107
     * <br/>
108
     * This is suitable e.g. for unfinished block comment when a COMMENT token
109
     * and PartType.START arguments would be used.
129
     *
110
     *
130
     * @param id non-null token id recognized by the lexer.
111
     * @param id non-null token id recognized by the lexer.
131
     * @param length >=0 length of the token to be created. The length must not
112
     * @param length >=0 length of the token to be created. The length must not
Lines 138-158 Link Here
138
     *  because of token id filter.
119
     *  because of token id filter.
139
     */
120
     */
140
    public Token<T> createToken(T id, int length, PartType partType) {
121
    public Token<T> createToken(T id, int length, PartType partType) {
141
        checkPartTypeNonNull(partType);
122
        return operation.createToken(id, length, partType);
142
        if (partType == PartType.COMPLETE)
143
            return createToken(id, length);
144
145
        if (isSkipToken(id)) {
146
            operation.tokenRecognized(length, true);
147
            return skipToken();
148
        } else { // Do not skip the token
149
            if (operation.tokenRecognized(length, false)) { // Create preprocessed token
150
//                return new ComplexToken<T>(id, operation.tokenLength(), null, partType, null);
151
                return new PropertyToken<T>(id, operation.tokenLength(), null, partType);
152
            } else {
153
                return new PropertyToken<T>(id, operation.tokenLength(), null, partType);
154
            }
155
        }
156
    }
123
    }
157
124
158
    /**
125
    /**
Lines 174-221 Link Here
174
     *  because of token id filter.
141
     *  because of token id filter.
175
     */
142
     */
176
    public Token<T> getFlyweightToken(T id, String text) {
143
    public Token<T> getFlyweightToken(T id, String text) {
177
        assert (text.length() <= operation.readIndex());
144
        return operation.getFlyweightToken(id, text);
178
        // Compare each recognized char with the corresponding char in text
179
        if (testing) {
180
            for (int i = 0; i < text.length(); i++) {
181
                if (text.charAt(i) != operation.readExisting(i)) {
182
                    throw new IllegalArgumentException("Flyweight text in " + // NOI18N
183
                            "TokenFactory.getFlyweightToken(" + id + ", \"" + // NOI18N
184
                            CharSequenceUtilities.debugText(text) + "\") " + // NOI18N
185
                            "differs from recognized text: '" + // NOI18N
186
                            CharSequenceUtilities.debugChar(operation.readExisting(i)) +
187
                            "' != '" + CharSequenceUtilities.debugChar(text.charAt(i)) + // NOI18N
188
                            "' at index=" + i // NOI18N
189
                    );
190
                }
191
            }
192
        }
193
194
        // Check whether token with given id should be created
195
        if (isSkipToken(id)) {
196
            operation.tokenRecognized(text.length(), true);
197
            return skipToken();
198
        } else { // Do not skip the token
199
            if (operation.tokenRecognized(text.length(), false)) { // Create preprocessed token
200
//                return new PreprocessedTextToken<T>(id, operation.tokenLength());
201
                return new DefaultToken<T>(id, operation.tokenLength());
202
            } else if (operation.isFlyTokenAllowed()) {
203
                LanguageOperation<T> langOp = operation.languageOperation();
204
                return langOp.getFlyweightToken(id, text);
205
            } else { // return non-flyweight token
206
                return new DefaultToken<T>(id, operation.tokenLength());
207
            }
208
        }
209
    }
145
    }
210
    
146
    
211
    /**
147
    /**
212
     * Create token with properties.
148
     * Create complete token with properties.
213
     *
149
     *
214
     * @param id non-null token id.
150
     * @param id non-null token id.
215
     * @param length >=0 length of the token to be created. The length must not
151
     * @param length >=0 length of the token to be created. The length must not
216
     *  exceed the number of characters read from the lexer input.
152
     *  exceed the number of characters read from the lexer input.
217
     * @param propertyProvider non-null token property provider.
153
     * @param propertyProvider token property provider or null if there are no extra properties.
218
     * @param partType whether this token is complete or just a part of complete token.
219
     *  See {@link TokenPropertyProvider} for examples how this parameter may be used.
154
     *  See {@link TokenPropertyProvider} for examples how this parameter may be used.
220
     * @return non-null property token instance.
155
     * @return non-null property token instance.
221
     *  <br/>
156
     *  <br/>
Lines 223-279 Link Here
223
     *  if tokens for the given token id should be skipped
158
     *  if tokens for the given token id should be skipped
224
     *  because of token id filter.
159
     *  because of token id filter.
225
     */
160
     */
226
    public Token<T> createPropertyToken(T id, int length,
161
    public Token<T> createPropertyToken(T id, int length, TokenPropertyProvider<T> propertyProvider) {
227
    TokenPropertyProvider propertyProvider, PartType partType) {
162
        return operation.createPropertyToken(id, length, propertyProvider, PartType.COMPLETE);
228
        checkPartTypeNonNull(partType);
229
        if (isSkipToken(id)) {
230
            operation.tokenRecognized(length, true);
231
            return skipToken();
232
        } else { // Do not skip the token
233
            if (operation.tokenRecognized(length, false)) { // Create preprocessed token
234
//                return new ComplexToken<T>(id, operation.tokenLength(),
235
//                    propertyProvider, null, partType);
236
                return new PropertyToken<T>(id, operation.tokenLength(),
237
                    propertyProvider, partType);
238
            } else {
239
                return new PropertyToken<T>(id, operation.tokenLength(),
240
                    propertyProvider, partType);
241
            }
242
        }
243
    }
163
    }
244
164
245
    /**
165
    /**
246
     * Create token with a custom text that possibly differs from the text
166
     * Create token with properties.
247
     * represented by the token in the input text.
167
     *
168
     * @param id non-null token id.
169
     * @param length >=0 length of the token to be created. The length must not
170
     *  exceed the number of characters read from the lexer input.
171
     * @param propertyProvider token property provider or null if there are no extra properties.
172
     *  See {@link TokenPropertyProvider} for examples how this parameter may be used.
173
     * @param partType whether this token is complete or just a part of complete token.
174
     *  Null may be passed which implies {@link PartType#COMPLETE}.
175
     * @return non-null property token instance.
176
     *  <br/>
177
     *  {@link #SKIP_TOKEN} will be returned
178
     *  if tokens for the given token id should be skipped
179
     *  because of token id filter.
180
     */
181
    public Token<T> createPropertyToken(T id, int length,
182
    TokenPropertyProvider<T> propertyProvider, PartType partType) {
183
        return operation.createPropertyToken(id, length, propertyProvider, partType);
184
    }
185
186
    /**
187
     * Create token with a custom text that possibly differs in length and content
188
     * from the text represented by the token in the input text.
189
     * <br/>
190
     * <b>Note: This method should not be used. It is planned to be removed completely.</b>
191
     * The custom text tokens no longer
192
     * save space by not refrencing the original characters (when read e.g. from a Reader).
193
     * <br/>
194
     * Having token's text to always match the input's text is more systematic
195
     * and simplifies the lexer module's design.
196
     * <br/>
197
     * Therefore the only benefit of custom text tokens would be if certain tools
198
     * e.g. parsers would require a different text than the one present naturally
199
     * in the token. In such case the token should have a property
200
     * (the key can be e.g. a CharSequence.class) that will return a char sequence
201
     * with the desired text. If the text is a sub sequence of original token's text
202
     * the token property provider can even be made flyweight:
203
     * <pre>
204
     * StripFirstAndLastCharTokenPropertyProvider implements TokenPropertyProvider {
205
     *     public TokenPropertyProvider INSTANCE = new StripFirstAndLastCharTokenPropertyProvider();
206
     *     public Object getValue(Token token, Object key) {
207
     *         if (key == CharSequence.class) {
208
     *             return token.text().subSequence(1, token.length() - 1);
209
     *         }
210
     *         return null;
211
     *     }
212
     * }
213
     * </pre>
214
     * 
215
     * <p>
216
     * </p>
217
     * 
218
     * @param id non-null token id of the token being created.
219
     * @param text non-null custom text assigned to the token.
220
     * @param length recognized characters corresponding to the token being created.
221
     * @param partType should always be null otherwise this method would throw
222
     *  an exception.
223
     * @deprecated This method is deprecated without replacement - see description
224
     *  how a similar effect can be obtained.
248
     */
225
     */
249
    public Token<T> createCustomTextToken(T id, CharSequence text, int length, PartType partType) {
226
    public Token<T> createCustomTextToken(T id, CharSequence text, int length, PartType partType) {
250
        checkPartTypeNonNull(partType);
227
        if (partType != null) {
251
        if (isSkipToken(id)) {
228
            throw new IllegalArgumentException("This method is deprecated and it should" + 
252
            operation.tokenRecognized(length, true);
229
                    " only be used with partType==null (see its javadoc).");
253
            return skipToken();
254
        } else { // Do not skip the token
255
            if (operation.tokenRecognized(length, false)) { // Create preprocessed token
256
                return new CustomTextToken<T>(id, operation.tokenLength(), text, partType);
257
//                return new ComplexToken<T>(id, operation.tokenLength(), null, text, partType);
258
            } else {
259
                return new CustomTextToken<T>(id, operation.tokenLength(), text, partType);
260
            }
261
        }
230
        }
262
    }
231
        return operation.createCustomTextToken(id, length, text);
263
    
264
    private boolean isSkipToken(T id) {
265
        Set<? extends TokenId> skipTokenIds = operation.skipTokenIds();
266
        return (skipTokenIds != null) && skipTokenIds.contains(id);
267
    }
232
    }
268
233
269
    @SuppressWarnings("unchecked") // NOI18N
234
    /**
270
    private Token<T> skipToken() {
235
     * Check whether a token (produced by one of the token creation methods)
271
        return SKIP_TOKEN;
236
     * is a special flyweight token used in cases
237
     * when there is an active filtering of certain token ids (e.g. comments and whitespace)
238
     * and the just recognized token-id should be skipped.
239
     *
240
     * @param token non-null token.
241
     * @return true if the token is a skip-token.
242
     */
243
    public boolean isSkipToken(Token<T> token) {
244
        return token == SKIP_TOKEN;
272
    }
245
    }
273
    
246
274
    private void checkPartTypeNonNull(PartType partType) {
275
        if (partType == null)
276
            throw new IllegalArgumentException("partType must be non-null");
277
    }
278
    
279
}
247
}
(-)a/lexer/src/org/netbeans/spi/lexer/TokenPropertyProvider.java (-2 / +2 lines)
Lines 83-89 Link Here
83
 *         this.value = value;
83
 *         this.value = value;
84
 *     }
84
 *     }
85
 *
85
 *
86
 *     public Object getValue(Token token, Object key) {
86
 *     public Object getValue(Token&lt;T&gt; token, Object key) {
87
 *         if ("key".equals(key)) {
87
 *         if ("key".equals(key)) {
88
 *             return value;
88
 *             return value;
89
 *         }
89
 *         }
Lines 109-114 Link Here
109
     * @param key non-null key for which the value should be retrieved.
109
     * @param key non-null key for which the value should be retrieved.
110
     * @return value of the property or null if there is no value for the given key.
110
     * @return value of the property or null if there is no value for the given key.
111
     */
111
     */
112
    Object getValue(Token token, Object key);
112
    Object getValue(Token<T> token, Object key);
113
113
114
}
114
}
(-)a/lexer/test/unit/src/org/netbeans/api/lexer/CustomTokenClassTest.java (+13 lines)
Lines 41-46 Link Here
41
41
42
package org.netbeans.api.lexer;
42
package org.netbeans.api.lexer;
43
43
44
import java.util.List;
44
import org.netbeans.junit.NbTestCase;
45
import org.netbeans.junit.NbTestCase;
45
46
46
/**
47
/**
Lines 99-104 Link Here
99
        public PartType partType() {
100
        public PartType partType() {
100
            return null;
101
            return null;
101
        }
102
        }
103
104
        public boolean isRemoved() {
105
            return false;
106
        }
107
108
        public Token<T> joinToken() {
109
            return null;
110
        }
111
112
        public List<? extends Token<T>> joinedParts() {
113
            return null;
114
        }
102
        
115
        
103
    }
116
    }
104
117
(-)a/lexer/test/unit/src/org/netbeans/api/lexer/TokenSequenceTest.java (-6 / +27 lines)
Lines 53-61 Link Here
53
import org.netbeans.lib.lexer.TokenList;
53
import org.netbeans.lib.lexer.TokenList;
54
import org.netbeans.lib.lexer.test.LexerTestUtilities;
54
import org.netbeans.lib.lexer.test.LexerTestUtilities;
55
import org.netbeans.lib.lexer.test.ModificationTextDocument;
55
import org.netbeans.lib.lexer.test.ModificationTextDocument;
56
import org.netbeans.lib.lexer.test.simple.*;
57
import org.netbeans.lib.lexer.token.DefaultToken;
56
import org.netbeans.lib.lexer.token.DefaultToken;
58
import org.netbeans.lib.lexer.token.TextToken;
57
import org.netbeans.lib.lexer.token.TextToken;
58
import org.netbeans.lib.lexer.token.TokenLength;
59
59
60
/**
60
/**
61
 * Test methods of token sequence.
61
 * Test methods of token sequence.
Lines 403-409 Link Here
403
    }
403
    }
404
    
404
    
405
    public void testTokenSize() {
405
    public void testTokenSize() {
406
        String text = "abc+";
406
        String text = "abc+def";
407
        TokenHierarchy<?> hi = TokenHierarchy.create(text,TestTokenId.language());
407
        TokenHierarchy<?> hi = TokenHierarchy.create(text,TestTokenId.language());
408
        TokenSequence<?> ts = hi.tokenSequence();
408
        TokenSequence<?> ts = hi.tokenSequence();
409
        
409
        
Lines 411-428 Link Here
411
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "abc", 0);
411
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "abc", 0);
412
        assertTrue(ts.moveNext());
412
        assertTrue(ts.moveNext());
413
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.PLUS, "+", 3);
413
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.PLUS, "+", 3);
414
        assertTrue(ts.moveNext());
415
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "def", 4);
414
        assertFalse(ts.moveNext());
416
        assertFalse(ts.moveNext());
415
        
417
        
416
        TokenList tokenList = LexerTestUtilities.tokenList(ts);
418
        TokenList tokenList = LexerTestUtilities.tokenList(ts);
417
        ts.moveIndex(0); // move before "abc"
419
        ts.moveIndex(0); // move before "abc"
418
        assertTrue(ts.moveNext());
420
        assertTrue(ts.moveNext());
419
        // Test DefaultToken size
421
        // Test DefaultToken size
420
        assertSame(DefaultToken.class, ts.token().getClass());
422
        Token<?> token = ts.token();
421
        assertSize("Token instance too big", Collections.singletonList(ts.token()), 24,new Object[] {  tokenList,TestTokenId.IDENTIFIER});
423
        // Exclude TokenLength since it should be cached - verify later
424
        TokenLength cachedTokenLength = TokenLength.get(token.length());
425
        assertSame(DefaultToken.class, token.getClass());
426
        assertSize("Token instance too big", Collections.singletonList(token), 24,
427
                new Object[] { tokenList, TestTokenId.IDENTIFIER, cachedTokenLength });
428
429
        // Check that TokenLength is cached for small tokens
430
        assertSame("TokenLength instances not cached for small tokens",
431
                cachedTokenLength, TokenLength.get(token.length()));
432
        
422
        // Test TextToken size
433
        // Test TextToken size
423
        assertTrue(ts.moveNext());
434
        assertTrue(ts.moveNext());
424
        assertSame(TextToken.class, ts.token().getClass());
435
        token = ts.token();
425
        assertSize("Token instance too big", Collections.singletonList(ts.token()), 24,new Object[] {  tokenList,TestTokenId.PLUS, "+"});
436
        assertSame(TextToken.class, token.getClass());
437
        assertSize("Token instance too big", Collections.singletonList(token), 24,
438
                new Object[] { tokenList, TestTokenId.PLUS, "+" });
439
440
        // Test DefaultToken size
441
        assertTrue(ts.moveNext());
442
        token = ts.token();
443
        assertSame(DefaultToken.class, token.getClass());
444
        // Verify that the TokenLength is cached for small tokens - use tokenLength3 directly
445
        assertSize("Token instance too big", Collections.singletonList(token), 24,
446
                new Object[] { tokenList, TestTokenId.IDENTIFIER, cachedTokenLength });
426
    }
447
    }
427
448
428
    public void testSubSequenceInUnfinishedTH() throws Exception {
449
    public void testSubSequenceInUnfinishedTH() throws Exception {
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/JoinSectionsTest.java (-226 lines)
Removed Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
package org.netbeans.lib.lexer;
42
43
import java.util.List;
44
import org.netbeans.api.lexer.Language;
45
import org.netbeans.api.lexer.LanguagePath;
46
import org.netbeans.api.lexer.PartType;
47
import org.netbeans.api.lexer.Token;
48
import org.netbeans.api.lexer.TokenHierarchy;
49
import org.netbeans.api.lexer.TokenSequence;
50
import org.netbeans.junit.NbTestCase;
51
import org.netbeans.lib.lexer.lang.TestJoinSectionsTextTokenId;
52
import org.netbeans.lib.lexer.test.LexerTestUtilities;
53
import org.netbeans.lib.lexer.lang.TestJoinSectionsTopTokenId;
54
import org.netbeans.lib.lexer.test.ModificationTextDocument;
55
56
/**
57
 * Test embedded sections that should be lexed together.
58
 *
59
 * @author Miloslav Metelka
60
 */
61
public class JoinSectionsTest extends NbTestCase {
62
    
63
    public JoinSectionsTest(String testName) {
64
        super(testName);
65
    }
66
67
    protected void setUp() throws Exception {
68
    }
69
70
    public void testJoinSections() throws Exception {
71
        // Turn on detailed checking
72
//        Logger.getLogger(TokenHierarchyOperation.class.getName()).setLevel(Level.FINEST);
73
74
        //             000000000011111111112222222222
75
        //             012345678901234567890123456789
76
        String text = "a{b<cd>e}f<gh>i{j<kl>m}n";
77
        ModificationTextDocument doc = new ModificationTextDocument();
78
        doc.insertString(0, text, null);
79
        doc.putProperty(Language.class,TestJoinSectionsTopTokenId.language());
80
        
81
        TokenHierarchy<?> hi = TokenHierarchy.get(doc);
82
        TokenSequence<?> ts = hi.tokenSequence();
83
        assertTrue(ts.moveNext());
84
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "a{b", -1);
85
        assertTrue(ts.moveNext());
86
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TAG, "<cd>", -1);
87
        assertTrue(ts.moveNext());
88
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "e}f", -1);
89
        
90
        // Get embedded tokens within TEXT tokens. There should be "a" then BRACES start "{b" then BRACES end "e}|" then "f"
91
        LanguagePath innerLP = LanguagePath.get(TestJoinSectionsTopTokenId.language()).
92
                embedded(TestJoinSectionsTextTokenId.language());
93
        List<TokenSequence<?>> tsList = hi.tokenSequenceList(innerLP, 0, Integer.MAX_VALUE);
94
        checkInitialTokens(tsList);
95
        
96
        
97
        // Use iterator for fetching token sequences
98
        int i = 0;
99
        for (TokenSequence<?> ts2 : tsList) {
100
            assertSame(ts2, tsList.get(i++));
101
        }
102
103
        LexerTestUtilities.assertConsistency(hi);
104
        
105
        // Check tokenSequenceList() with explicit offsets
106
        // Check correct TSs bounds
107
        tsList = hi.tokenSequenceList(innerLP, 0, 7);
108
        assertEquals(1, tsList.size());
109
        tsList = hi.tokenSequenceList(innerLP, 0, 8);
110
        assertEquals(2, tsList.size());
111
        
112
        
113
        // Do modifications
114
        // Remove second closing brace '}'
115
        doc.remove(8, 1);
116
        LexerTestUtilities.assertConsistency(hi);
117
        //             000000000011111111112222222222
118
        //             012345678901234567890123456789
119
        // before:    "a{b<cd>e}f<gh>i{j<kl>m}n";
120
        // after:     "a{b<cd>ef<gh>i{j<kl>m}n";
121
        tsList = hi.tokenSequenceList(innerLP, 0, Integer.MAX_VALUE);
122
        assertEquals(4, tsList.size()); // 2 sections
123
124
        // 1.section "a{b"
125
        ts = tsList.get(0);
126
        assertTrue(ts.moveNext());
127
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.TEXT, "a", -1);
128
        assertTrue(ts.moveNext());
129
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "{b", -1);
130
        Token<?> token = ts.token();
131
        assertEquals(PartType.START, token.partType());
132
        assertFalse(ts.moveNext());
133
        
134
        // 2.section "ef"
135
        ts = tsList.get(1);
136
        assertTrue(ts.moveNext());
137
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "ef", -1);
138
        token = ts.token();
139
        assertEquals(PartType.MIDDLE, token.partType());
140
        assertFalse(ts.moveNext());
141
        
142
        // 3.section "i{j"
143
        ts = tsList.get(2);
144
        assertTrue(ts.moveNext());
145
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "i{j", -1);
146
        token = ts.token();
147
        assertEquals(PartType.MIDDLE, token.partType());
148
        assertFalse(ts.moveNext());
149
        
150
        // 4.section "m}n"
151
        ts = tsList.get(3);
152
        assertTrue(ts.moveNext());
153
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "m}", -1);
154
        token = ts.token();
155
        assertEquals(PartType.END, token.partType());
156
        assertTrue(ts.moveNext());
157
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.TEXT, "n", -1);
158
        assertFalse(ts.moveNext());
159
        
160
161
        // Re-add second closing brace '}'
162
        doc.insertString(8, "}", null);
163
        LexerTestUtilities.assertConsistency(hi);
164
        //             000000000011111111112222222222
165
        //             012345678901234567890123456789
166
        // before:    "a{b<cd>ef<gh>i{j<kl>m}n";
167
        // after:     "a{b<cd>e}f<gh>i{j<kl>m}n";
168
        tsList = hi.tokenSequenceList(innerLP, 0, Integer.MAX_VALUE);
169
        checkInitialTokens(tsList);
170
        
171
        doc.remove(0, doc.getLength());
172
        LexerTestUtilities.assertConsistency(hi);
173
        ts = hi.tokenSequence();
174
        assertFalse(ts.moveNext());
175
        doc.insertString(0, text, null);
176
177
    }
178
179
    private void checkInitialTokens(List<TokenSequence<?>> tsList) {
180
        //             000000000011111111112222222222
181
        //             012345678901234567890123456789
182
        // text:      "a{b<cd>e}f<gh>i{j<kl>m}n";
183
        assertEquals(4, tsList.size()); // 4 sections
184
185
        // 1.section
186
        TokenSequence<?> ts = tsList.get(0);
187
        assertTrue(ts.moveNext());
188
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.TEXT, "a", -1);
189
        assertTrue(ts.moveNext());
190
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "{b", -1);
191
        Token<?> token = ts.token();
192
        assertEquals(PartType.START, token.partType());
193
        assertFalse(ts.moveNext());
194
        
195
        // 2.section
196
        ts = tsList.get(1);
197
        assertTrue(ts.moveNext());
198
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "e}", -1);
199
        token = ts.token();
200
        assertEquals(PartType.END, token.partType());
201
        assertTrue(ts.moveNext());
202
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.TEXT, "f", -1);
203
        assertFalse(ts.moveNext());
204
        
205
        // 3.section
206
        ts = tsList.get(2);
207
        assertTrue(ts.moveNext());
208
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.TEXT, "i", -1);
209
        assertTrue(ts.moveNext());
210
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "{j", -1);
211
        token = ts.token();
212
        assertEquals(PartType.START, token.partType());
213
        assertFalse(ts.moveNext());
214
        
215
        // 4.section
216
        ts = tsList.get(3);
217
        assertTrue(ts.moveNext());
218
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "m}", -1);
219
        token = ts.token();
220
        assertEquals(PartType.END, token.partType());
221
        assertTrue(ts.moveNext());
222
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.TEXT, "n", -1);
223
        assertFalse(ts.moveNext());
224
    }
225
226
}
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/lang/TestJavadocLexer.java (-1 lines)
Lines 41-47 Link Here
41
41
42
package org.netbeans.lib.lexer.lang;
42
package org.netbeans.lib.lexer.lang;
43
43
44
import org.netbeans.lib.lexer.lang.TestJavadocTokenId;
45
import org.netbeans.api.lexer.Token;
44
import org.netbeans.api.lexer.Token;
46
import org.netbeans.spi.lexer.Lexer;
45
import org.netbeans.spi.lexer.Lexer;
47
import org.netbeans.spi.lexer.LexerInput;
46
import org.netbeans.spi.lexer.LexerInput;
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/lang/TestJoinSectionsTextLexer.java (-1 lines)
Lines 42-48 Link Here
42
package org.netbeans.lib.lexer.lang;
42
package org.netbeans.lib.lexer.lang;
43
43
44
import org.netbeans.api.lexer.PartType;
44
import org.netbeans.api.lexer.PartType;
45
import org.netbeans.lib.lexer.lang.TestJoinSectionsTextTokenId;
46
import org.netbeans.api.lexer.Token;
45
import org.netbeans.api.lexer.Token;
47
import org.netbeans.spi.lexer.Lexer;
46
import org.netbeans.spi.lexer.Lexer;
48
import org.netbeans.spi.lexer.LexerInput;
47
import org.netbeans.spi.lexer.LexerInput;
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/lang/TestJoinSectionsTextTokenId.java (-2 / +2 lines)
Lines 60-67 Link Here
60
 */
60
 */
61
public enum TestJoinSectionsTextTokenId implements TokenId {
61
public enum TestJoinSectionsTextTokenId implements TokenId {
62
    
62
    
63
    TEXT(), // Text except of text within braces
63
    BRACES(), // "{...}" i.e. text within braces
64
    BRACES(); // "{ ... }" i.e. text within braces
64
    TEXT(); // Text except of text within braces
65
65
66
    private TestJoinSectionsTextTokenId() {
66
    private TestJoinSectionsTextTokenId() {
67
    }
67
    }
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/lang/TestJoinSectionsTopTokenId.java (-2 / +2 lines)
Lines 60-67 Link Here
60
 */
60
 */
61
public enum TestJoinSectionsTopTokenId implements TokenId {
61
public enum TestJoinSectionsTopTokenId implements TokenId {
62
    
62
    
63
    TEXT(),
63
    TAG(), // Text enclosed in <..> including '<' and '>'
64
    TAG();
64
    TEXT(); // Any text not enclosed in <...>
65
65
66
    private TestJoinSectionsTopTokenId() {
66
    private TestJoinSectionsTopTokenId() {
67
    }
67
    }
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/test/LexerTestUtilities.java (-47 / +67 lines)
Lines 182-197 Link Here
182
    }
182
    }
183
    
183
    
184
    /**
184
    /**
185
     * @see #assertTokenSequencesEqual(String,TokenSequence,TokenHierarchy,TokenSequence,TokenHierarchy,boolean)
186
     */
187
    public static void assertTokenSequencesEqual(
188
    TokenSequence<?> expected, TokenHierarchy<?> expectedHi,
189
    TokenSequence<?> actual, TokenHierarchy<?> actualHi,
190
    boolean testLookaheadAndState) {
191
        assertTokenSequencesEqual(null, expected, expectedHi, actual, actualHi, testLookaheadAndState);
192
    }
193
194
    /**
195
     * Compare contents of the given token sequences by moving through all their
185
     * Compare contents of the given token sequences by moving through all their
196
     * tokens.
186
     * tokens.
197
     * <br/>
187
     * <br/>
Lines 210-232 Link Here
210
    public static void assertTokenSequencesEqual(String message,
200
    public static void assertTokenSequencesEqual(String message,
211
    TokenSequence<?> expected, TokenHierarchy<?> expectedHi,
201
    TokenSequence<?> expected, TokenHierarchy<?> expectedHi,
212
    TokenSequence<?> actual, TokenHierarchy<?> actualHi,
202
    TokenSequence<?> actual, TokenHierarchy<?> actualHi,
213
    boolean testLookaheadAndState) {
203
    boolean testLookaheadAndState, boolean dumpWholeHi) {
214
        boolean success = false;
204
        String prefix = messagePrefix(message);
215
        try {
205
        TestCase.assertEquals(prefix + "Move previous: ", expected.movePrevious(), actual.movePrevious());
216
            String prefix = messagePrefix(message);
206
        int i = 0;
217
            TestCase.assertEquals(prefix + "Move previous: ", expected.movePrevious(), actual.movePrevious());
207
        while (expected.moveNext()) {
218
            while (expected.moveNext()) {
208
            String prefixI = prefix + "->[" + i + "]";
219
                TestCase.assertTrue(prefix + "Move next: ", actual.moveNext());
209
            TestCase.assertTrue(prefixI + ": Cannot moveNext() in test token sequence", actual.moveNext());
220
                assertTokensEqual(message, expected, expectedHi, actual, actualHi, testLookaheadAndState);
210
            assertTokensEqual(prefixI, expected, expectedHi, actual, actualHi, testLookaheadAndState);
221
            }
211
            i++;
222
            TestCase.assertFalse(prefix + "Move next not disabled", actual.moveNext());
223
            success = true;
224
        } finally {
225
            if (!success) {
226
                System.err.println("Expected token sequence dump:\n" + expected);
227
                System.err.println("Test token sequence dump:\n" + actual);
228
            }
229
        }
212
        }
213
        TestCase.assertFalse(prefix + "moveNext() possible at end of test token sequence", actual.moveNext());
230
    }
214
    }
231
215
232
    private static void assertTokensEqual(String message,
216
    private static void assertTokensEqual(String message,
Lines 339-345 Link Here
339
    }
323
    }
340
    
324
    
341
    public static void incCheck(Document doc, boolean nested) {
325
    public static void incCheck(Document doc, boolean nested) {
342
        TokenHierarchy<?> thInc = TokenHierarchy.get(doc);
326
        TokenHierarchy<?> incHi = TokenHierarchy.get(doc);
327
        assertConsistency(incHi);
328
343
        Language<?> language = (Language<?>)
329
        Language<?> language = (Language<?>)
344
                doc.getProperty(Language.class);
330
                doc.getProperty(Language.class);
345
        String docText = null;
331
        String docText = null;
Lines 349-383 Link Here
349
            e.printStackTrace();
335
            e.printStackTrace();
350
            TestCase.fail("BadLocationException occurred");
336
            TestCase.fail("BadLocationException occurred");
351
        }
337
        }
352
        TokenHierarchy<?> thBatch = TokenHierarchy.create(docText, language);
338
        TokenHierarchy<?> batchHi = TokenHierarchy.create(docText, language);
353
        boolean success = false;
339
        TokenSequence<?> batchTS = batchHi.tokenSequence();
354
        TokenSequence<?> batchTS = thBatch.tokenSequence();
340
        TokenSequence<?> incTS = incHi.tokenSequence();
355
        try {
341
        try {
356
            // Compare lookaheads and states as well
342
            // Compare lookaheads and states as well
357
            assertTokenSequencesEqual(batchTS, thBatch,
343
            assertTokenSequencesEqual("TOP", batchTS, batchHi, incTS, incHi, true, false);
358
                    thInc.tokenSequence(), thInc, true);
344
        } catch (Throwable t) {
359
            success = true;
345
            // Go forward two tokens to have an extra tokens context
360
        } finally {
346
            batchTS.moveNext();
361
            if (!success) {
347
            batchTS.moveNext();
362
                // Go forward two tokens to have an extra tokens context
348
            StringBuilder sb = new StringBuilder(512);
363
                batchTS.moveNext();
349
            sb.append("BATCH token sequence dump:\n").append(batchTS);
364
                batchTS.moveNext();
350
            sb.append("\n\nTEST token sequence dump:\n").append(incTS);
365
                System.err.println("BATCH token sequence dump:\n" + thBatch.tokenSequence());
351
            TokenHierarchy<?> lastHi = (TokenHierarchy<?>)doc.getProperty(LAST_TOKEN_HIERARCHY);
366
                TokenHierarchy<?> lastHi = (TokenHierarchy<?>)doc.getProperty(LAST_TOKEN_HIERARCHY);
352
            if (lastHi != null) {
367
                if (lastHi != null) {
353
//                    System.err.println("PREVIOUS batch token sequence dump:\n" + lastHi.tokenSequence());
368
                    System.err.println("PREVIOUS batch token sequence dump:\n" + lastHi.tokenSequence());
354
            }
369
                }
355
            throw new IllegalStateException(sb.toString(), t);
356
        }
357
        
358
        if (nested) {
359
            batchTS.moveStart();
360
            incTS.moveStart();
361
            try {
362
                incCheckNested("TOP", doc, batchTS, batchHi, incTS, incHi);
363
            } catch (Throwable t) { // Re-throw with hierarchy info
364
                StringBuilder sb = new StringBuilder(512);
365
                sb.append("BATCH token hierarchy:\n").append(batchHi);
366
                sb.append("\n\n\nTEST token hierarchy:\n").append(incHi);
367
                throw new IllegalStateException(sb.toString(), t);
370
            }
368
            }
371
        }
369
        }
372
        
370
373
        // Check the change since last modification
371
        // Check the change since last modification
374
        TokenHierarchy<?> lastHi = (TokenHierarchy<?>)doc.getProperty(LAST_TOKEN_HIERARCHY);
372
        TokenHierarchy<?> lastHi = (TokenHierarchy<?>)doc.getProperty(LAST_TOKEN_HIERARCHY);
375
        if (lastHi != null) {
373
        if (lastHi != null) {
376
            // TODO comparison
374
            // TODO comparison
377
        }
375
        }
378
        doc.putProperty(LAST_TOKEN_HIERARCHY, thBatch); // new last batch token hierarchy
376
        doc.putProperty(LAST_TOKEN_HIERARCHY, batchHi); // new last batch token hierarchy
379
    }
377
    }
380
    
378
379
    public static void incCheckNested(String message, Document doc,
380
            TokenSequence<?> batch, TokenHierarchy<?> batchTH,
381
            TokenSequence<?> inc, TokenHierarchy<?> incTH
382
    ) {
383
        int i = 0;
384
        while (inc.moveNext()) {
385
            TestCase.assertTrue("No more tokens in batch token sequence", batch.moveNext());
386
            TokenSequence<?> batchE = batch.embedded();
387
            TokenSequence<?> incE = inc.embedded();
388
            String messageE = message + "->[" + i + "]";
389
            if (incE != null) {
390
                TestCase.assertNotNull("Inc embedded sequence is null", batchE);
391
                assertTokenSequencesEqual(messageE, batchE, batchTH, incE, incTH, true, true);
392
393
                incCheckNested(messageE, doc, batchE, batchTH, incE, incTH);
394
            } else { // Inc embedded is null
395
                TestCase.assertNull("Batch embedded sequence non-null", batchE);
396
            }
397
            i++;
398
        }
399
    }
400
381
    /**
401
    /**
382
     * Get lookahead for the token to which the token sequence is positioned.
402
     * Get lookahead for the token to which the token sequence is positioned.
383
     * <br/>
403
     * <br/>
Lines 387-393 Link Here
387
        return tokenList(ts).lookahead(ts.index());
407
        return tokenList(ts).lookahead(ts.index());
388
    }
408
    }
389
409
390
    /**
410
        /**
391
     * Get state for the token to which the token sequence is positioned.
411
     * Get state for the token to which the token sequence is positioned.
392
     * <br/>
412
     * <br/>
393
     * The method uses reflection to get reference to tokenList field in token sequence.
413
     * The method uses reflection to get reference to tokenList field in token sequence.
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/test/TestRandomModify.java (-9 / +19 lines)
Lines 47-53 Link Here
47
import javax.swing.text.Document;
47
import javax.swing.text.Document;
48
import org.netbeans.api.lexer.Language;
48
import org.netbeans.api.lexer.Language;
49
import org.netbeans.api.lexer.TokenHierarchy;
49
import org.netbeans.api.lexer.TokenHierarchy;
50
import org.netbeans.api.lexer.TokenId;
51
import org.netbeans.lib.editor.util.CharSequenceUtilities;
50
import org.netbeans.lib.editor.util.CharSequenceUtilities;
52
51
53
52
Lines 58-63 Link Here
58
 * @author mmetelka
57
 * @author mmetelka
59
 */
58
 */
60
public class TestRandomModify {
59
public class TestRandomModify {
60
    
61
    private final Class hostClass;
61
62
62
    private boolean debugOperation;
63
    private boolean debugOperation;
63
64
Lines 77-94 Link Here
77
    
78
    
78
    private List<SnapshotDescription> snapshots = new ArrayList<SnapshotDescription>();
79
    private List<SnapshotDescription> snapshots = new ArrayList<SnapshotDescription>();
79
80
80
    public TestRandomModify() {
81
    public TestRandomModify(Class hostClass) {
81
        this(0);
82
        this(0, hostClass);
82
    }
83
    }
83
    
84
    
84
    public TestRandomModify(long seed) {
85
    public TestRandomModify(long seed, Class hostClass) {
86
        this.hostClass = hostClass;
85
        this.doc = new javax.swing.text.PlainDocument();
87
        this.doc = new javax.swing.text.PlainDocument();
86
88
87
        this.random = new Random();
89
        this.random = new Random();
88
        if (seed == 0) { // Use currentTimeMillis() (btw nanoTime() in 1.5 instead)
90
        if (seed == 0) { // Use currentTimeMillis() (btw nanoTime() in 1.5 instead)
89
            seed = System.currentTimeMillis();
91
            seed = System.currentTimeMillis();
90
        }
92
        }
91
        System.err.println("TestRandomModify with SEED=" + seed + "L");
93
        System.err.println(hostClass.getName() + " with SEED=" + seed + "L");
92
        random.setSeed(seed);
94
        random.setSeed(seed);
93
    }
95
    }
94
    
96
    
Lines 192-200 Link Here
192
    public void insertText(int offset, String text) throws Exception {
194
    public void insertText(int offset, String text) throws Exception {
193
        if (text.length() > 0) {
195
        if (text.length() > 0) {
194
            if (isDebugOperation()) {
196
            if (isDebugOperation()) {
197
                int beforeTextStartOffset = Math.max(offset - 5, 0);
198
                String beforeText = document().getText(beforeTextStartOffset, offset - beforeTextStartOffset);
199
                int afterTextEndOffset = Math.min(offset + 5, document().getLength());
200
                String afterText = doc.getText(offset, afterTextEndOffset - offset);
195
                System.err.println(opIdString() + " INSERT(" + offset +
201
                System.err.println(opIdString() + " INSERT(" + offset +
196
                        ", " + text.length() +"): \""
202
                        ", " + text.length() +"): \"" +
197
                        + CharSequenceUtilities.debugText(text) +"\""
203
                        CharSequenceUtilities.debugText(text) +"\" text-around: \"" +
204
                        CharSequenceUtilities.debugText(beforeText) + '|' + 
205
                        CharSequenceUtilities.debugText(afterText) + "\""
198
                );
206
                );
199
                if (isDebugDocumentText()) {
207
                if (isDebugDocumentText()) {
200
                    StringBuilder sb = new StringBuilder();
208
                    StringBuilder sb = new StringBuilder();
Lines 328-333 Link Here
328
    
336
    
329
    public void clearDocument() throws Exception {
337
    public void clearDocument() throws Exception {
330
        doc.remove(0, doc.getLength());
338
        doc.remove(0, doc.getLength());
339
        // Verify that there are no tokens
340
        LexerTestUtilities.incCheck(doc, false);
331
    }
341
    }
332
    
342
    
333
    public final Language<?> language() {
343
    public final Language<?> language() {
Lines 361-368 Link Here
361
                    System.err.println("Comparing snapshot " + i + " of " + snapshots.size());
371
                    System.err.println("Comparing snapshot " + i + " of " + snapshots.size());
362
                }
372
                }
363
                // Check snapshot without comparing lookaheads and states
373
                // Check snapshot without comparing lookaheads and states
364
                LexerTestUtilities.assertTokenSequencesEqual(bm.tokenSequence(), bm,
374
                LexerTestUtilities.assertTokenSequencesEqual(null, bm.tokenSequence(), bm,
365
                        s.tokenSequence(), s, false);
375
                        s.tokenSequence(), s, false, false);
366
            }
376
            }
367
        }
377
        }
368
    }
378
    }
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/test/dump/TokenDumpCheck.java (-1 / +1 lines)
Lines 58-64 Link Here
58
import org.netbeans.api.lexer.TokenUtilities;
58
import org.netbeans.api.lexer.TokenUtilities;
59
import org.netbeans.junit.NbTestCase;
59
import org.netbeans.junit.NbTestCase;
60
import org.netbeans.lib.editor.util.CharSequenceUtilities;
60
import org.netbeans.lib.editor.util.CharSequenceUtilities;
61
import org.netbeans.lib.lexer.batch.BatchTokenList;
61
import org.netbeans.lib.lexer.BatchTokenList;
62
import org.netbeans.lib.lexer.test.LexerTestUtilities;
62
import org.netbeans.lib.lexer.test.LexerTestUtilities;
63
63
64
/**
64
/**
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/test/dump/TokenDumpLexer.java (-8 / +9 lines)
Lines 43-48 Link Here
43
43
44
import org.netbeans.api.lexer.PartType;
44
import org.netbeans.api.lexer.PartType;
45
import org.netbeans.api.lexer.Token;
45
import org.netbeans.api.lexer.Token;
46
import org.netbeans.api.lexer.TokenId;
46
import org.netbeans.spi.lexer.Lexer;
47
import org.netbeans.spi.lexer.Lexer;
47
import org.netbeans.spi.lexer.LexerInput;
48
import org.netbeans.spi.lexer.LexerInput;
48
import org.netbeans.spi.lexer.LexerRestartInfo;
49
import org.netbeans.spi.lexer.LexerRestartInfo;
Lines 205-211 Link Here
205
                case EOF:
206
                case EOF:
206
                    input.backup(1);
207
                    input.backup(1);
207
                    return tokenFactory.createPropertyToken(id, input.readLength(),
208
                    return tokenFactory.createPropertyToken(id, input.readLength(),
208
                        new UnicodeCharValueProvider(new Character(ch)), PartType.COMPLETE);
209
                        new UnicodeCharValueProvider<TokenDumpTokenId>(new Character(ch)), PartType.COMPLETE);
209
            }
210
            }
210
        }
211
        }
211
        input.backup(1);
212
        input.backup(1);
Lines 219-238 Link Here
219
    public void release() {
220
    public void release() {
220
    }
221
    }
221
222
222
    private static final class UnicodeCharValueProvider implements TokenPropertyProvider {
223
    private static final class UnicodeCharValueProvider<T extends TokenId> implements TokenPropertyProvider<T> {
223
        
224
224
        private Character ch;
225
        private Character ch;
225
        
226
226
        UnicodeCharValueProvider(Character ch) {
227
        UnicodeCharValueProvider(Character ch) {
227
            this.ch = ch;
228
            this.ch = ch;
228
        }
229
        }
229
        
230
230
        public Object getValue(Token token, Object key) {
231
        public Object getValue(Token<T> token, Object key) {
231
            if (TokenDumpTokenId.UNICODE_CHAR_TOKEN_PROPERTY.equals(key))
232
            if (TokenDumpTokenId.UNICODE_CHAR_TOKEN_PROPERTY.equals(key))
232
                return ch;
233
                return ch;
233
            return null; // no non-tokenStore value
234
            return null; // no non-tokenStore value
234
        }
235
        }
235
        
236
236
    }
237
    }
237
    
238
238
}
239
}
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/test/inc/TokenListUpdaterTest.java (-2 / +20 lines)
Lines 41-53 Link Here
41
41
42
package org.netbeans.lib.lexer.test.inc;
42
package org.netbeans.lib.lexer.test.inc;
43
43
44
import java.io.PrintStream;
44
import java.util.ConcurrentModificationException;
45
import java.util.ConcurrentModificationException;
46
import java.util.logging.Level;
47
import java.util.logging.Logger;
45
import javax.swing.text.Document;
48
import javax.swing.text.Document;
46
import junit.framework.TestCase;
49
import junit.framework.TestCase;
47
import org.netbeans.api.lexer.Language;
50
import org.netbeans.api.lexer.Language;
48
import org.netbeans.api.lexer.TokenHierarchy;
51
import org.netbeans.api.lexer.TokenHierarchy;
49
import org.netbeans.api.lexer.TokenId;
50
import org.netbeans.api.lexer.TokenSequence;
52
import org.netbeans.api.lexer.TokenSequence;
53
import org.netbeans.junit.NbTestCase;
51
import org.netbeans.lib.lexer.test.LexerTestUtilities;
54
import org.netbeans.lib.lexer.test.LexerTestUtilities;
52
import org.netbeans.lib.lexer.test.ModificationTextDocument;
55
import org.netbeans.lib.lexer.test.ModificationTextDocument;
53
import org.netbeans.lib.lexer.lang.TestTokenId;
56
import org.netbeans.lib.lexer.lang.TestTokenId;
Lines 57-63 Link Here
57
 *
60
 *
58
 * @author mmetelka
61
 * @author mmetelka
59
 */
62
 */
60
public class TokenListUpdaterTest extends TestCase {
63
public class TokenListUpdaterTest extends NbTestCase {
61
    
64
    
62
    public TokenListUpdaterTest(String testName) {
65
    public TokenListUpdaterTest(String testName) {
63
        super(testName);
66
        super(testName);
Lines 67-72 Link Here
67
    }
70
    }
68
71
69
    protected void tearDown() throws java.lang.Exception {
72
    protected void tearDown() throws java.lang.Exception {
73
    }
74
75
    @Override
76
    public PrintStream getLog() {
77
        return System.out;
78
//        return super.getLog();
79
    }
80
81
    @Override
82
    protected Level logLevel() {
83
        return Level.INFO;
84
//        return super.logLevel();;
70
    }
85
    }
71
86
72
    public void testInsertUnfinishedLexing() throws Exception {
87
    public void testInsertUnfinishedLexing() throws Exception {
Lines 176-182 Link Here
176
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "a", 0);
191
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "a", 0);
177
        
192
        
178
        // Remove "b"
193
        // Remove "b"
194
//        Logger.getLogger(org.netbeans.lib.lexer.inc.TokenListUpdater.class.getName()).setLevel(Level.FINE); // Extra logging
179
        doc.remove(2, 1);
195
        doc.remove(2, 1);
196
//        Logger.getLogger(org.netbeans.lib.lexer.inc.TokenListUpdater.class.getName()).setLevel(Level.WARNING); // End of extra logging
180
        try {
197
        try {
181
            ts.moveNext();
198
            ts.moveNext();
182
            fail("Should not get there");
199
            fail("Should not get there");
Lines 186-191 Link Here
186
203
187
        ts = hi.tokenSequence();
204
        ts = hi.tokenSequence();
188
        assertTrue(ts.moveNext());
205
        assertTrue(ts.moveNext());
206
        CharSequence tokenText = ts.token().text();
189
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "a", 0);
207
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "a", 0);
190
        assertTrue(ts.moveNext());
208
        assertTrue(ts.moveNext());
191
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.PLUS, "+", 1);
209
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.PLUS, "+", 1);
(-)06a7890f802e (+293 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
package org.netbeans.lib.lexer.test.join;
42
43
import java.io.PrintStream;
44
import java.util.List;
45
import java.util.logging.Level;
46
import java.util.logging.Logger;
47
import org.netbeans.api.lexer.Language;
48
import org.netbeans.api.lexer.LanguagePath;
49
import org.netbeans.api.lexer.PartType;
50
import org.netbeans.api.lexer.Token;
51
import org.netbeans.api.lexer.TokenHierarchy;
52
import org.netbeans.api.lexer.TokenSequence;
53
import org.netbeans.junit.NbTestCase;
54
import org.netbeans.lib.lexer.lang.TestJoinSectionsTextTokenId;
55
import org.netbeans.lib.lexer.test.LexerTestUtilities;
56
import org.netbeans.lib.lexer.lang.TestJoinSectionsTopTokenId;
57
import org.netbeans.lib.lexer.test.ModificationTextDocument;
58
59
/**
60
 * Test embedded sections that should be lexed together.
61
 * 
62
 * <p>
63
 * Top lexer recognizes TestJoinSectionsTopTokenId.TAG  (text v zobacich)
64
 *                  and TestJoinSectionsTopTokenId.TEXT (everything else).
65
 *
66
 * TestJoinSectionsTopTokenId.TAG is branched into
67
 *                      TestJoinSectionsTextTokenId.BRACES "{...}"
68
 *                  and TestJoinSectionsTextTokenId.TEXT (everything else)
69
 * 
70
 * </p>
71
 * 
72
 *
73
 * @author Miloslav Metelka
74
 */
75
public class JoinSectionsMod1Test extends NbTestCase {
76
    
77
    public JoinSectionsMod1Test(String testName) {
78
        super(testName);
79
    }
80
81
    protected void setUp() throws Exception {
82
    }
83
84
    @Override
85
    public PrintStream getLog() {
86
        return System.out;
87
//        return super.getLog();
88
    }
89
90
    @Override
91
    protected Level logLevel() {
92
        return Level.INFO;
93
//        return super.logLevel();;
94
    }
95
96
    public void testShortDocMod() throws Exception {
97
        //             000000000011111111112222222222
98
        //             012345678901234567890123456789
99
        String text = "xay<b>zc";
100
        ModificationTextDocument doc = new ModificationTextDocument();
101
        doc.insertString(0, text, null);
102
        doc.putProperty(Language.class, TestJoinSectionsTopTokenId.language());
103
        LexerTestUtilities.incCheck(doc, true); // Ensure the whole embedded hierarchy gets created
104
105
        Logger.getLogger(org.netbeans.lib.lexer.inc.TokenListUpdater.class.getName()).setLevel(Level.FINE); // Extra logging
106
      doc.remove(6, 1);
107
        LexerTestUtilities.incCheck(doc, true);
108
        //             000000000011111111112222222222
109
        //             012345678901234567890123456789
110
        //     text = "xay<b>c";
111
        //                   \yz<uv>hk
112
      doc.insertString(6, "yz<uv>hk", null);
113
        LexerTestUtilities.incCheck(doc, true);
114
        //             000000000011111111112222222222
115
        //             012345678901234567890123456789
116
        //     text = "xay<b>yz<uv>hkc";
117
      doc.remove(12, 3);
118
        LexerTestUtilities.incCheck(doc, true);
119
        //             000000000011111111112222222222
120
        //             012345678901234567890123456789
121
        //     text = "xay<b>yz<uv>";
122
      doc.insertString(12, "hkc", null);
123
        LexerTestUtilities.incCheck(doc, true);
124
        //             000000000011111111112222222222
125
        //             012345678901234567890123456789
126
        //     text = "xay<b>yz<uv>hkc";
127
128
    }
129
130
    public void testJoinSections() throws Exception {
131
        if (true)
132
            return;
133
        // Turn on detailed checking
134
//        Logger.getLogger(TokenHierarchyOperation.class.getName()).setLevel(Level.FINEST);
135
136
        //             000000000011111111112222222222
137
        //             012345678901234567890123456789
138
        String text = "a{b<cd>e}f<gh>i{j<kl>m}n";
139
        ModificationTextDocument doc = new ModificationTextDocument();
140
        doc.insertString(0, text, null);
141
        doc.putProperty(Language.class,TestJoinSectionsTopTokenId.language());
142
        
143
        TokenHierarchy<?> hi = TokenHierarchy.get(doc);
144
        TokenSequence<?> ts = hi.tokenSequence();
145
        assertTrue(ts.moveNext());
146
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "a{b", -1);
147
        assertTrue(ts.moveNext());
148
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TAG, "<cd>", -1);
149
        assertTrue(ts.moveNext());
150
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "e}f", -1);
151
        
152
        // Get embedded tokens within TEXT tokens. There should be "a" then BRACES start "{b" then BRACES end "e}|" then "f"
153
        LanguagePath innerLP = LanguagePath.get(TestJoinSectionsTopTokenId.language()).
154
                embedded(TestJoinSectionsTextTokenId.language());
155
        List<TokenSequence<?>> tsList = hi.tokenSequenceList(innerLP, 0, Integer.MAX_VALUE);
156
        checkInitialTokens(tsList);
157
        
158
        
159
        // Use iterator for fetching token sequences
160
        int i = 0;
161
        for (TokenSequence<?> ts2 : tsList) {
162
            assertSame(ts2, tsList.get(i++));
163
        }
164
165
        LexerTestUtilities.assertConsistency(hi);
166
        
167
        // Check tokenSequenceList() with explicit offsets
168
        // Check correct TSs bounds
169
        tsList = hi.tokenSequenceList(innerLP, 0, 7);
170
        assertEquals(1, tsList.size());
171
        tsList = hi.tokenSequenceList(innerLP, 0, 8);
172
        assertEquals(2, tsList.size());
173
        
174
        
175
        // Do modifications
176
        // Remove second closing brace '}'
177
178
//        Logger.getLogger(org.netbeans.lib.lexer.inc.TokenListUpdater.class.getName()).setLevel(Level.FINE); // Extra logging
179
        doc.remove(8, 1);
180
//        Logger.getLogger(org.netbeans.lib.lexer.inc.TokenListUpdater.class.getName()).setLevel(Level.WARNING); // End of extra logging
181
        LexerTestUtilities.assertConsistency(hi);
182
        LexerTestUtilities.incCheck(doc, true);
183
        //             000000000011111111112222222222
184
        //             012345678901234567890123456789
185
        // before:    "a{b<cd>e}f<gh>i{j<kl>m}n";
186
        // after:     "a{b<cd>ef<gh>i{j<kl>m}n";
187
        //             i0     i1    i2     i3
188
        tsList = hi.tokenSequenceList(innerLP, 0, Integer.MAX_VALUE);
189
        assertEquals(4, tsList.size()); // 2 sections
190
191
        // 1.section "a{b"
192
        ts = tsList.get(0);
193
        assertTrue(ts.moveNext());
194
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.TEXT, "a", -1);
195
        assertTrue(ts.moveNext());
196
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "{b", -1);
197
        Token<?> token = ts.token();
198
        assertEquals(PartType.START, token.partType());
199
        assertFalse(ts.moveNext());
200
        
201
        // 2.section "ef"
202
        ts = tsList.get(1);
203
        assertTrue(ts.moveNext());
204
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "ef", -1);
205
        token = ts.token();
206
        assertEquals(PartType.MIDDLE, token.partType());
207
        assertFalse(ts.moveNext());
208
        
209
        // 3.section "i{j"
210
        ts = tsList.get(2);
211
        assertTrue(ts.moveNext());
212
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "i{j", -1);
213
        token = ts.token();
214
        assertEquals(PartType.MIDDLE, token.partType());
215
        assertFalse(ts.moveNext());
216
        
217
        // 4.section "m}n"
218
        ts = tsList.get(3);
219
        assertTrue(ts.moveNext());
220
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "m}", -1);
221
        token = ts.token();
222
        assertEquals(PartType.END, token.partType());
223
        assertTrue(ts.moveNext());
224
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.TEXT, "n", -1);
225
        assertFalse(ts.moveNext());
226
        
227
228
        // Re-add second closing brace '}'
229
        doc.insertString(8, "}", null);
230
        LexerTestUtilities.assertConsistency(hi);
231
        //             000000000011111111112222222222
232
        //             012345678901234567890123456789
233
        // before:    "a{b<cd>ef<gh>i{j<kl>m}n";
234
        // after:     "a{b<cd>e}f<gh>i{j<kl>m}n";
235
        tsList = hi.tokenSequenceList(innerLP, 0, Integer.MAX_VALUE);
236
        checkInitialTokens(tsList);
237
        
238
        doc.remove(0, doc.getLength());
239
        LexerTestUtilities.assertConsistency(hi);
240
        ts = hi.tokenSequence();
241
        assertFalse(ts.moveNext());
242
        doc.insertString(0, text, null);
243
244
    }
245
246
    private void checkInitialTokens(List<TokenSequence<?>> tsList) {
247
        //             000000000011111111112222222222
248
        //             012345678901234567890123456789
249
        // text:      "a{b<cd>e}f<gh>i{j<kl>m}n";
250
        assertEquals(4, tsList.size()); // 4 sections
251
252
        // 1.section
253
        TokenSequence<?> ts = tsList.get(0);
254
        assertTrue(ts.moveNext());
255
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.TEXT, "a", -1);
256
        assertTrue(ts.moveNext());
257
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "{b", -1);
258
        Token<?> token = ts.token();
259
        assertEquals(PartType.START, token.partType());
260
        assertFalse(ts.moveNext());
261
        
262
        // 2.section
263
        ts = tsList.get(1);
264
        assertTrue(ts.moveNext());
265
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "e}", -1);
266
        token = ts.token();
267
        assertEquals(PartType.END, token.partType());
268
        assertTrue(ts.moveNext());
269
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.TEXT, "f", -1);
270
        assertFalse(ts.moveNext());
271
        
272
        // 3.section
273
        ts = tsList.get(2);
274
        assertTrue(ts.moveNext());
275
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.TEXT, "i", -1);
276
        assertTrue(ts.moveNext());
277
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "{j", -1);
278
        token = ts.token();
279
        assertEquals(PartType.START, token.partType());
280
        assertFalse(ts.moveNext());
281
        
282
        // 4.section
283
        ts = tsList.get(3);
284
        assertTrue(ts.moveNext());
285
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.BRACES, "m}", -1);
286
        token = ts.token();
287
        assertEquals(PartType.END, token.partType());
288
        assertTrue(ts.moveNext());
289
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTextTokenId.TEXT, "n", -1);
290
        assertFalse(ts.moveNext());
291
    }
292
293
}
(-)06a7890f802e (+178 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.test.join;
43
44
import java.util.List;
45
import org.netbeans.api.lexer.Language;
46
import org.netbeans.api.lexer.LanguagePath;
47
import org.netbeans.api.lexer.PartType;
48
import org.netbeans.lib.lexer.lang.TestJoinSectionsTopTokenId;
49
import org.netbeans.lib.lexer.lang.TestTokenId;
50
import org.netbeans.api.lexer.TokenChange;
51
import org.netbeans.api.lexer.TokenHierarchy;
52
import org.netbeans.api.lexer.TokenHierarchyEvent;
53
import org.netbeans.api.lexer.TokenHierarchyListener;
54
import org.netbeans.api.lexer.TokenSequence;
55
import org.netbeans.junit.NbTestCase;
56
import org.netbeans.lib.lexer.lang.TestJoinSectionsTextTokenId;
57
import org.netbeans.lib.lexer.test.LexerTestUtilities;
58
import org.netbeans.lib.lexer.lang.TestPlainTokenId;
59
import org.netbeans.lib.lexer.test.ModificationTextDocument;
60
import org.netbeans.spi.lexer.LanguageEmbedding;
61
62
/**
63
 * Test several simple lexer impls.
64
 *
65
 * <p>
66
 * Top lexer recognizes TestJoinSectionsTopTokenId.TAG  (text v zobacich)
67
 *                  and TestJoinSectionsTopTokenId.TEXT (everything else).
68
 *
69
 * TestJoinSectionsTopTokenId.TAG is branched into
70
 *                      TestJoinSectionsTextTokenId.BRACES "{...}"
71
 *                  and TestJoinSectionsTextTokenId.TEXT (everything else)
72
 * 
73
 * </p>
74
 *
75
 * @author mmetelka
76
 */
77
public class JoinSectionsMod2Test extends NbTestCase {
78
    
79
    public JoinSectionsMod2Test(String testName) {
80
        super(testName);
81
    }
82
    
83
    protected void setUp() throws java.lang.Exception {
84
    }
85
86
    protected void tearDown() throws java.lang.Exception {
87
    }
88
89
    public void testMove() throws Exception { // TokenSequence.move() and moveIndex()
90
        //             000000000011111111112222222222
91
        //             012345678901234567890123456789
92
        String text = "a{b<cd>e}f<gh>i{}{j<kl>m}n<o>p<q>r";
93
        ModificationTextDocument doc = new ModificationTextDocument();
94
        doc.insertString(0, text, null);
95
        doc.putProperty(Language.class,TestJoinSectionsTopTokenId.language());
96
        
97
        TokenHierarchy<?> hi = TokenHierarchy.get(doc);
98
        TokenSequence<?> ts = hi.tokenSequence();
99
        assertTrue(ts.moveNext());
100
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "a{b", 0);
101
        assertTrue(ts.moveNext());
102
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TAG, "<cd>", 3);
103
        assertTrue(ts.moveNext());
104
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "e}f", 7);
105
        assertTrue(ts.moveNext());
106
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TAG, "<gh>", 10);
107
        assertTrue(ts.moveNext());
108
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "i{}{j", 14);
109
        assertTrue(ts.moveNext());
110
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TAG, "<kl>", 19);
111
        assertTrue(ts.moveNext());
112
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "m}n", 23);
113
        assertTrue(ts.moveNext());
114
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TAG, "<o>", -1);
115
        assertTrue(ts.moveNext());
116
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "p", -1);
117
        assertTrue(ts.moveNext());
118
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TAG, "<q>", -1);
119
        assertTrue(ts.moveNext());
120
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "r", -1);
121
        assertFalse(ts.moveNext());
122
        
123
        // Check regular TS.embedded()
124
        ts.moveStart();
125
        assertTrue(ts.moveNext()); // Over "a{b"
126
        TokenSequence<?> tse = ts.embedded();
127
        assertTrue(tse.moveNext());
128
        LexerTestUtilities.assertTokenEquals(tse,TestJoinSectionsTextTokenId.TEXT, "a", 0);
129
        assertTrue(tse.moveNext());
130
        LexerTestUtilities.assertTokenEquals(tse,TestJoinSectionsTextTokenId.BRACES, "{b", 1);
131
        assertEquals(tse.token().partType(), PartType.START);
132
        assertFalse(tse.moveNext());
133
        
134
        assertTrue(ts.moveNext()); // Over "<cd>"
135
        assertTrue(ts.moveNext()); // Over "e}f"
136
        TokenSequence<?> tse2 = ts.embedded();
137
        assertTrue(tse2.moveNext());
138
        LexerTestUtilities.assertTokenEquals(tse2,TestJoinSectionsTextTokenId.BRACES, "e}", 7);
139
        assertEquals(tse2.token().partType(), PartType.END);
140
        assertTrue(tse2.moveNext());
141
        LexerTestUtilities.assertTokenEquals(tse2,TestJoinSectionsTextTokenId.TEXT, "f", 9);
142
        assertEquals(tse2.token().partType(), PartType.START);
143
        assertFalse(tse2.moveNext());
144
        
145
        assertTrue(ts.moveNext()); // Over "<gh>"
146
        assertTrue(ts.moveNext()); // Over "i{}{j"
147
        TokenSequence<?> tse3 = ts.embedded();
148
        assertTrue(tse3.moveNext());
149
        LexerTestUtilities.assertTokenEquals(tse3,TestJoinSectionsTextTokenId.TEXT, "i", 14);
150
        assertEquals(tse3.token().partType(), PartType.END);
151
        assertTrue(tse3.moveNext());
152
        LexerTestUtilities.assertTokenEquals(tse3,TestJoinSectionsTextTokenId.BRACES, "{}", 15);
153
        assertEquals(tse3.token().partType(), PartType.COMPLETE);
154
        assertTrue(tse3.moveNext());
155
        LexerTestUtilities.assertTokenEquals(tse3,TestJoinSectionsTextTokenId.BRACES, "{j", 17);
156
        assertEquals(tse3.token().partType(), PartType.START);
157
        assertFalse(tse3.moveNext());
158
159
160
        // Check TS.embeddedJoin()
161
        TokenSequence<?> tsej = ts.embeddedJoined();
162
        assertEquals(2, tsej.index());
163
        assertTrue(tsej.moveNext());
164
        int o = tsej.offset();
165
        LexerTestUtilities.assertTokenEquals(tsej,TestJoinSectionsTextTokenId.TEXT, "fi", 9);
166
        assertEquals(9, tsej.token().offset(null)); // Assert also token.offset() besides TS.offset()
167
        assertEquals(tsej.token().partType(), PartType.COMPLETE);
168
        assertTrue(tsej.moveNext());
169
        LexerTestUtilities.assertTokenEquals(tsej,TestJoinSectionsTextTokenId.BRACES, "{}", 15);
170
        assertEquals(tsej.token().partType(), PartType.COMPLETE);
171
        assertTrue(tsej.moveNext());
172
        LexerTestUtilities.assertTokenEquals(tsej,TestJoinSectionsTextTokenId.BRACES, "{jm}", 17);
173
        assertEquals(tsej.token().partType(), PartType.COMPLETE);
174
//        assertFalse(tsej.moveNext());
175
        
176
    }
177
        
178
}
(-)06a7890f802e (+250 lines)
Added Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.lib.lexer.test.join;
43
44
import java.util.List;
45
import org.netbeans.api.lexer.Language;
46
import org.netbeans.api.lexer.LanguagePath;
47
import org.netbeans.api.lexer.PartType;
48
import org.netbeans.lib.lexer.lang.TestJoinSectionsTopTokenId;
49
import org.netbeans.lib.lexer.lang.TestTokenId;
50
import org.netbeans.api.lexer.TokenChange;
51
import org.netbeans.api.lexer.TokenHierarchy;
52
import org.netbeans.api.lexer.TokenHierarchyEvent;
53
import org.netbeans.api.lexer.TokenHierarchyListener;
54
import org.netbeans.api.lexer.TokenSequence;
55
import org.netbeans.junit.NbTestCase;
56
import org.netbeans.lib.lexer.lang.TestJoinSectionsTextTokenId;
57
import org.netbeans.lib.lexer.test.LexerTestUtilities;
58
import org.netbeans.lib.lexer.lang.TestPlainTokenId;
59
import org.netbeans.lib.lexer.test.ModificationTextDocument;
60
import org.netbeans.spi.lexer.LanguageEmbedding;
61
62
/**
63
 * Test several simple lexer impls.
64
 *
65
 * <p>
66
 * Top lexer recognizes TestJoinSectionsTopTokenId.TAG  (text v zobacich)
67
 *                  and TestJoinSectionsTopTokenId.TEXT (everything else).
68
 *
69
 * TestJoinSectionsTopTokenId.TAG is branched into
70
 *                      TestJoinSectionsTextTokenId.BRACES "{...}"
71
 *                  and TestJoinSectionsTextTokenId.TEXT (everything else)
72
 * 
73
 * </p>
74
 *
75
 * @author mmetelka
76
 */
77
public class JoinSectionsPositioningTest extends NbTestCase {
78
    
79
    public JoinSectionsPositioningTest(String testName) {
80
        super(testName);
81
    }
82
    
83
    protected void setUp() throws java.lang.Exception {
84
    }
85
86
    protected void tearDown() throws java.lang.Exception {
87
    }
88
89
    private ModificationTextDocument initDocument() throws Exception {
90
        //             000000000011111111112222222222
91
        //             012345678901234567890123456789
92
        String text = "a{b<cd>e}f<gh>i{}{j<kl>m}n<o>p<q>r";
93
        ModificationTextDocument doc = new ModificationTextDocument();
94
        doc.insertString(0, text, null);
95
        doc.putProperty(Language.class,TestJoinSectionsTopTokenId.language());
96
        return doc;
97
    }
98
    
99
    public void testTokensAndEmbeddings() throws Exception { // TokenSequence.move() and moveIndex()
100
        ModificationTextDocument doc = initDocument();
101
        TokenHierarchy<?> hi = TokenHierarchy.get(doc);
102
        TokenSequence<?> ts = hi.tokenSequence();
103
        assertTrue(ts.moveNext());
104
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "a{b", 0);
105
        assertTrue(ts.moveNext());
106
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TAG, "<cd>", 3);
107
        assertTrue(ts.moveNext());
108
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "e}f", 7);
109
        assertTrue(ts.moveNext());
110
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TAG, "<gh>", 10);
111
        assertTrue(ts.moveNext());
112
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "i{}{j", 14);
113
        assertTrue(ts.moveNext());
114
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TAG, "<kl>", 19);
115
        assertTrue(ts.moveNext());
116
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "m}n", 23);
117
        assertTrue(ts.moveNext());
118
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TAG, "<o>", -1);
119
        assertTrue(ts.moveNext());
120
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "p", -1);
121
        assertTrue(ts.moveNext());
122
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TAG, "<q>", -1);
123
        assertTrue(ts.moveNext());
124
        LexerTestUtilities.assertTokenEquals(ts,TestJoinSectionsTopTokenId.TEXT, "r", -1);
125
        assertFalse(ts.moveNext());
126
        assertEquals(11, ts.tokenCount());
127
        
128
        // Check regular TS.embedded()
129
        ts.moveStart();
130
        assertTrue(ts.moveNext()); // Over "a{b"
131
        TokenSequence<?> tse = ts.embedded();
132
        assertTrue(tse.moveNext());
133
        LexerTestUtilities.assertTokenEquals(tse,TestJoinSectionsTextTokenId.TEXT, "a", 0);
134
        assertTrue(tse.moveNext());
135
        LexerTestUtilities.assertTokenEquals(tse,TestJoinSectionsTextTokenId.BRACES, "{b", 1);
136
        assertEquals(tse.token().partType(), PartType.START);
137
        assertFalse(tse.moveNext());
138
        
139
        assertTrue(ts.moveNext()); // Over "<cd>"
140
        assertTrue(ts.moveNext()); // Over "e}f"
141
        TokenSequence<?> tse2 = ts.embedded();
142
        assertTrue(tse2.moveNext());
143
        LexerTestUtilities.assertTokenEquals(tse2,TestJoinSectionsTextTokenId.BRACES, "e}", 7);
144
        assertEquals(tse2.token().partType(), PartType.END);
145
        assertTrue(tse2.moveNext());
146
        LexerTestUtilities.assertTokenEquals(tse2,TestJoinSectionsTextTokenId.TEXT, "f", 9);
147
        assertEquals(tse2.token().partType(), PartType.START);
148
        assertFalse(tse2.moveNext());
149
        
150
        assertTrue(ts.moveNext()); // Over "<gh>"
151
        assertTrue(ts.moveNext()); // Over "i{}{j"
152
        TokenSequence<?> tse3 = ts.embedded();
153
        assertTrue(tse3.moveNext());
154
        LexerTestUtilities.assertTokenEquals(tse3,TestJoinSectionsTextTokenId.TEXT, "i", 14);
155
        assertEquals(tse3.token().partType(), PartType.END);
156
        assertTrue(tse3.moveNext());
157
        LexerTestUtilities.assertTokenEquals(tse3,TestJoinSectionsTextTokenId.BRACES, "{}", 15);
158
        assertEquals(tse3.token().partType(), PartType.COMPLETE);
159
        assertTrue(tse3.moveNext());
160
        LexerTestUtilities.assertTokenEquals(tse3,TestJoinSectionsTextTokenId.BRACES, "{j", 17);
161
        assertEquals(tse3.token().partType(), PartType.START);
162
        assertFalse(tse3.moveNext());
163
164
165
        // Check TS.embeddedJoin()
166
        TokenSequence<?> tsej = ts.embeddedJoined();
167
        assertEquals(2, tsej.index());
168
        assertTrue(tsej.moveNext());
169
        LexerTestUtilities.assertTokenEquals(tsej,TestJoinSectionsTextTokenId.TEXT, "fi", 9);
170
        assertEquals(9, tsej.token().offset(null)); // Assert also token.offset() besides TS.offset()
171
        assertEquals(tsej.token().partType(), PartType.COMPLETE);
172
        assertTrue(tsej.moveNext());
173
        LexerTestUtilities.assertTokenEquals(tsej,TestJoinSectionsTextTokenId.BRACES, "{}", 15);
174
        assertEquals(tsej.token().partType(), PartType.COMPLETE);
175
        assertTrue(tsej.moveNext());
176
        LexerTestUtilities.assertTokenEquals(tsej,TestJoinSectionsTextTokenId.BRACES, "{jm}", 17);
177
        assertEquals(tsej.token().partType(), PartType.COMPLETE);
178
179
        tsej.moveStart();
180
        assertTrue(tsej.moveNext());
181
        LexerTestUtilities.assertTokenEquals(tsej,TestJoinSectionsTextTokenId.TEXT, "a", 0);
182
        assertEquals(0, tsej.token().offset(null)); // Assert also token.offset() besides TS.offset()
183
        assertEquals(tsej.token().partType(), PartType.COMPLETE);
184
        assertTrue(tsej.moveNext());
185
        LexerTestUtilities.assertTokenEquals(tsej,TestJoinSectionsTextTokenId.BRACES, "{be}", 1);
186
        assertEquals(1, tsej.token().offset(null)); // Assert also token.offset() besides TS.offset()
187
        assertEquals(tsej.token().partType(), PartType.COMPLETE);
188
//        assertFalse(tsej.moveNext());
189
        
190
    }
191
192
    public void testTSMove() throws Exception {
193
        ModificationTextDocument doc = initDocument();
194
        TokenHierarchy<?> hi = TokenHierarchy.get(doc);
195
        TokenSequence<?> ts = hi.tokenSequence();
196
        assertEquals(1, ts.move(8));
197
        ts.moveStart();
198
        assertTrue(ts.moveNext());
199
        
200
        // Test TS.move() on embeddedJoin()
201
        TokenSequence<?> tsej = ts.embeddedJoined();
202
        assertEquals(-3, tsej.move(-3)); // Token starts at offset == 1
203
        assertEquals(0, tsej.index());
204
205
        assertEquals(7, tsej.move(8)); // Token starts at offset == 1
206
        assertEquals(1, tsej.index());
207
208
        assertEquals(6, tsej.move(7)); // Token starts at offset == 1
209
        assertEquals(1, tsej.index());
210
        
211
        assertEquals(5, tsej.move(6)); // Token starts at offset == 1
212
        assertEquals(1, tsej.index());
213
        
214
        assertEquals(2, tsej.move(3)); // Token starts at offset == 1
215
        assertEquals(1, tsej.index());
216
        
217
        assertEquals(1, tsej.move(2)); // Token starts at offset == 1
218
        assertEquals(1, tsej.index());
219
        
220
        assertEquals(1, tsej.move(2)); // Token starts at offset == 1
221
        assertEquals(1, tsej.index());
222
223
        assertEquals(1, tsej.move(2)); // Token starts at offset == 1
224
        assertEquals(1, tsej.index());
225
226
        assertEquals(1, tsej.move(16)); // Token starts at offset == 15
227
        assertEquals(3, tsej.index());
228
        
229
        assertEquals(0, tsej.move(15)); // Token starts at offset == 15
230
        assertEquals(3, tsej.index());
231
        
232
        assertEquals(0, tsej.move(17)); // Token starts at offset == 15
233
        assertEquals(4, tsej.index());
234
        
235
    }
236
237
    public void testShortDoc() throws Exception {
238
        //             000000000011111111112222222222
239
        //             012345678901234567890123456789
240
        String text = "a<b>c";
241
        ModificationTextDocument doc = new ModificationTextDocument();
242
        doc.insertString(0, text, null);
243
        doc.putProperty(Language.class, TestJoinSectionsTopTokenId.language());
244
        TokenHierarchy<?> hi = TokenHierarchy.get(doc);
245
        TokenSequence<?> ts = hi.tokenSequence();
246
        assertTrue(ts.moveNext());
247
        ts.embedded(); // Creates JTL
248
    }
249
250
}
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/test/simple/CustomEmbeddingTest.java (-2 / +2 lines)
Lines 106-112 Link Here
106
        assertEquals(0, etc.embeddedChangeCount());
106
        assertEquals(0, etc.embeddedChangeCount());
107
        
107
        
108
        // Test the contents of the embedded sequence
108
        // Test the contents of the embedded sequence
109
        TokenSequence<?> ets = ts.embedded();
109
        TokenSequence<?> ets = ts.embedded(); // Over "// line comment"
110
        assertTrue(ets.moveNext());
110
        assertTrue(ets.moveNext());
111
        LexerTestUtilities.assertTokenEquals(ets,TestTokenId.IDENTIFIER, "line", 18);
111
        LexerTestUtilities.assertTokenEquals(ets,TestTokenId.IDENTIFIER, "line", 18);
112
        assertTrue(ets.moveNext());
112
        assertTrue(ets.moveNext());
Lines 158-164 Link Here
158
158
159
        
159
        
160
        // Check token sequence list
160
        // Check token sequence list
161
        // Create custm embedding again
161
        // Create custom embedding again
162
        assertTrue(ts.createEmbedding(TestTokenId.language(), 2, 2));
162
        assertTrue(ts.createEmbedding(TestTokenId.language(), 2, 2));
163
        LanguagePath lpe = LanguagePath.get(TestTokenId.language()).embedded(TestTokenId.language());
163
        LanguagePath lpe = LanguagePath.get(TestTokenId.language()).embedded(TestTokenId.language());
164
        List<TokenSequence<?>> tsl = hi.tokenSequenceList(lpe, 0, Integer.MAX_VALUE);
164
        List<TokenSequence<?>> tsl = hi.tokenSequenceList(lpe, 0, Integer.MAX_VALUE);
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/test/simple/SimpleLexerBatchTest.java (-1 / +1 lines)
Lines 174-180 Link Here
174
        }
174
        }
175
        tm = System.currentTimeMillis() - tm;
175
        tm = System.currentTimeMillis() - tm;
176
        assertTrue("Timeout tm = " + tm + "msec", tm < 1000); // Should be fast
176
        assertTrue("Timeout tm = " + tm + "msec", tm < 1000); // Should be fast
177
        System.out.println("Lexed input " + text.length()
177
        System.out.println("SimpleLexerBatchTest.testPerf(): Lexed input " + text.length()
178
                + " chars long and created " + cntr + " tokens in " + tm + " ms.");
178
                + " chars long and created " + cntr + " tokens in " + tm + " ms.");
179
    }
179
    }
180
    
180
    
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/test/simple/SimpleLexerIncTest.java (-12 / +39 lines)
Lines 41-54 Link Here
41
41
42
package org.netbeans.lib.lexer.test.simple;
42
package org.netbeans.lib.lexer.test.simple;
43
43
44
import java.io.PrintStream;
45
import java.util.logging.Level;
44
import org.netbeans.lib.lexer.lang.TestTokenId;
46
import org.netbeans.lib.lexer.lang.TestTokenId;
45
import java.util.ConcurrentModificationException;
47
import java.util.ConcurrentModificationException;
48
import java.util.logging.Logger;
46
import javax.swing.text.Document;
49
import javax.swing.text.Document;
47
import junit.framework.TestCase;
48
import org.netbeans.api.lexer.Language;
50
import org.netbeans.api.lexer.Language;
49
import org.netbeans.api.lexer.TokenHierarchy;
51
import org.netbeans.api.lexer.TokenHierarchy;
50
import org.netbeans.api.lexer.TokenId;
51
import org.netbeans.api.lexer.TokenSequence;
52
import org.netbeans.api.lexer.TokenSequence;
53
import org.netbeans.junit.NbTestCase;
52
import org.netbeans.lib.lexer.test.LexerTestUtilities;
54
import org.netbeans.lib.lexer.test.LexerTestUtilities;
53
import org.netbeans.lib.lexer.test.ModificationTextDocument;
55
import org.netbeans.lib.lexer.test.ModificationTextDocument;
54
56
Lines 57-72 Link Here
57
 *
59
 *
58
 * @author mmetelka
60
 * @author mmetelka
59
 */
61
 */
60
public class SimpleLexerIncTest extends TestCase {
62
public class SimpleLexerIncTest extends NbTestCase {
61
    
63
    
62
    public SimpleLexerIncTest(String testName) {
64
    public SimpleLexerIncTest(String testName) {
63
        super(testName);
65
        super(testName);
64
    }
66
    }
65
    
67
    
68
    @Override
66
    protected void setUp() throws java.lang.Exception {
69
    protected void setUp() throws java.lang.Exception {
67
    }
70
    }
68
71
72
    @Override
69
    protected void tearDown() throws java.lang.Exception {
73
    protected void tearDown() throws java.lang.Exception {
74
    }
75
76
    @Override
77
    public PrintStream getLog() {
78
        return System.out;
79
//        return super.getLog();
80
    }
81
82
    @Override
83
    protected Level logLevel() {
84
        return Level.INFO;
85
//        return super.logLevel();;
70
    }
86
    }
71
87
72
    public void test() throws Exception {
88
    public void test() throws Exception {
Lines 75-85 Link Here
75
        doc.putProperty(Language.class,TestTokenId.language());
91
        doc.putProperty(Language.class,TestTokenId.language());
76
        TokenHierarchy<?> hi = TokenHierarchy.get(doc);
92
        TokenHierarchy<?> hi = TokenHierarchy.get(doc);
77
        assertNotNull("Null token hierarchy for document", hi);
93
        assertNotNull("Null token hierarchy for document", hi);
94
95
        // Check insertion of text that produces token with LA=0
96
        doc.insertString(0, "+", null);
97
        LexerTestUtilities.incCheck(doc, false);
98
        doc.remove(0, doc.getLength());
99
        LexerTestUtilities.incCheck(doc, false);
100
        
78
        TokenSequence<?> ts = hi.tokenSequence();
101
        TokenSequence<?> ts = hi.tokenSequence();
79
        assertFalse(ts.moveNext());
102
        assertFalse(ts.moveNext());
80
        
103
        
81
        // Insert text into document
104
        // Insert text into document
82
        String commentText = "/* test comment  */";
105
        String commentText = "/* test comment  */";
106
        //             0123456789
83
        String text = "abc+uv-xy +-+" + commentText + "def";
107
        String text = "abc+uv-xy +-+" + commentText + "def";
84
        int commentTextStartOffset = 13;
108
        int commentTextStartOffset = 13;
85
        doc.insertString(0, text, null);
109
        doc.insertString(0, text, null);
Lines 114-120 Link Here
114
        assertTrue(ts.moveNext());
138
        assertTrue(ts.moveNext());
115
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "def", offset);
139
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "def", offset);
116
        assertFalse(ts.moveNext());
140
        assertFalse(ts.moveNext());
117
118
        LexerTestUtilities.incCheck(doc, false);
141
        LexerTestUtilities.incCheck(doc, false);
119
        
142
        
120
        // Check TokenSequence.move()
143
        // Check TokenSequence.move()
Lines 133-139 Link Here
133
        assertTrue(ts.moveNext());
156
        assertTrue(ts.moveNext());
134
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "abc", 0);
157
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "abc", 0);
135
158
136
        relOffset = ts.move(5); // to first token "abc"
159
        relOffset = ts.move(5); // to "uv"
137
        assertEquals(relOffset, 1);
160
        assertEquals(relOffset, 1);
138
        assertTrue(ts.moveNext());
161
        assertTrue(ts.moveNext());
139
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "uv", 4);
162
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "uv", 4);
Lines 148-153 Link Here
148
        } catch (ConcurrentModificationException e) {
171
        } catch (ConcurrentModificationException e) {
149
            // Expected exception
172
            // Expected exception
150
        }
173
        }
174
        LexerTestUtilities.incCheck(doc, false);
175
        
151
        
176
        
152
        ts = hi.tokenSequence();
177
        ts = hi.tokenSequence();
153
        assertTrue(ts.moveNext());
178
        assertTrue(ts.moveNext());
Lines 156-161 Link Here
156
        
181
        
157
        // Remove added 'd' to become "abc" again
182
        // Remove added 'd' to become "abc" again
158
        doc.remove(2, 1); // should be "abc" again
183
        doc.remove(2, 1); // should be "abc" again
184
        LexerTestUtilities.incCheck(doc, false);
185
        
159
186
160
        ts = hi.tokenSequence();
187
        ts = hi.tokenSequence();
161
        assertTrue(ts.moveNext());
188
        assertTrue(ts.moveNext());
Lines 165-186 Link Here
165
        
192
        
166
        // Now insert right at the end of first token - identifier with lookahead 1
193
        // Now insert right at the end of first token - identifier with lookahead 1
167
        doc.insertString(3, "x", null); // should become "abcx"
194
        doc.insertString(3, "x", null); // should become "abcx"
195
        LexerTestUtilities.incCheck(doc, false);
168
        
196
        
169
        ts = hi.tokenSequence();
197
        ts = hi.tokenSequence();
170
        assertTrue(ts.moveNext());
198
        assertTrue(ts.moveNext());
171
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "abcx", 0);
199
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "abcx", 0);
172
        LexerTestUtilities.incCheck(doc, false);
173
200
174
        doc.remove(3, 1); // return back to "abc"
201
        doc.remove(3, 1); // return back to "abc"
202
        LexerTestUtilities.incCheck(doc, false);
175
203
176
        ts = hi.tokenSequence();
204
        ts = hi.tokenSequence();
177
        assertTrue(ts.moveNext());
205
        assertTrue(ts.moveNext());
178
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "abc", 0);
206
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "abc", 0);
179
        LexerTestUtilities.incCheck(doc, false);
180
207
181
        
208
        
182
        // Now insert right at the end of "+" token - operator with lookahead 1 (because of "+-+" operator)
209
        // Now insert right at the end of "+" token - operator with lookahead 1 (because of "+-+" operator)
183
        doc.insertString(4, "z", null); // should become "abc" "+" "zuv"
210
        doc.insertString(4, "z", null); // should become "abc" "+" "zuv"
211
        LexerTestUtilities.incCheck(doc, false);
184
        
212
        
185
        ts = hi.tokenSequence();
213
        ts = hi.tokenSequence();
186
        assertTrue(ts.moveNext());
214
        assertTrue(ts.moveNext());
Lines 191-204 Link Here
191
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "zuv", 4);
219
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "zuv", 4);
192
        assertTrue(ts.moveNext());
220
        assertTrue(ts.moveNext());
193
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.MINUS, "-", 7);
221
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.MINUS, "-", 7);
194
        LexerTestUtilities.incCheck(doc, false);
222
        
195
196
        doc.remove(4, 1); // return back to "abc" "+" "uv"
223
        doc.remove(4, 1); // return back to "abc" "+" "uv"
197
198
        LexerTestUtilities.incCheck(doc, false);
224
        LexerTestUtilities.incCheck(doc, false);
199
225
200
        // Now insert right after "-" - operator with lookahead 0
226
        // Now insert right after "-" - operator with lookahead 0
201
        doc.insertString(7, "z", null);
227
        doc.insertString(7, "z", null);
228
        LexerTestUtilities.incCheck(doc, false);
202
        
229
        
203
        ts = hi.tokenSequence();
230
        ts = hi.tokenSequence();
204
        assertTrue(ts.moveNext());
231
        assertTrue(ts.moveNext());
Lines 211-220 Link Here
211
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.MINUS, "-", 6);
238
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.MINUS, "-", 6);
212
        assertTrue(ts.moveNext());
239
        assertTrue(ts.moveNext());
213
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "zxy", 7);
240
        LexerTestUtilities.assertTokenEquals(ts,TestTokenId.IDENTIFIER, "zxy", 7);
214
        LexerTestUtilities.incCheck(doc, false);
215
241
216
        doc.remove(7, 1); // return back to "abc" "+" "uv"
242
        doc.remove(7, 1); // return back to "abc" "+" "uv"
217
218
        LexerTestUtilities.incCheck(doc, false);
243
        LexerTestUtilities.incCheck(doc, false);
219
244
220
        // Now insert between "+-" and "+" in "+-+" - operator with lookahead 0
245
        // Now insert between "+-" and "+" in "+-+" - operator with lookahead 0
Lines 227-233 Link Here
227
        doc.insertString(doc.getLength(), "-", null);
252
        doc.insertString(doc.getLength(), "-", null);
228
        LexerTestUtilities.incCheck(doc, false);
253
        LexerTestUtilities.incCheck(doc, false);
229
        // Insert again "-" at the end of the document (now lookahead of preceding is zero)
254
        // Insert again "-" at the end of the document (now lookahead of preceding is zero)
255
//        Logger.getLogger(org.netbeans.lib.lexer.inc.TokenListUpdater.class.getName()).setLevel(Level.FINE); // Extra logging
230
        doc.insertString(doc.getLength(), "-", null);
256
        doc.insertString(doc.getLength(), "-", null);
257
//        Logger.getLogger(org.netbeans.lib.lexer.inc.TokenListUpdater.class.getName()).setLevel(Level.WARNING); // End of extra logging
231
        LexerTestUtilities.incCheck(doc, false);
258
        LexerTestUtilities.incCheck(doc, false);
232
        // Insert again "+-+" at the end of the document (now lookahead of preceding is zero)
259
        // Insert again "+-+" at the end of the document (now lookahead of preceding is zero)
233
        doc.insertString(doc.getLength(), "+-+", null);
260
        doc.insertString(doc.getLength(), "+-+", null);
(-)a/lexer/test/unit/src/org/netbeans/lib/lexer/test/simple/SimpleLexerRandomTest.java (-19 / +66 lines)
Lines 41-48 Link Here
41
41
42
package org.netbeans.lib.lexer.test.simple;
42
package org.netbeans.lib.lexer.test.simple;
43
43
44
import java.io.PrintStream;
45
import java.util.logging.Level;
46
import java.util.logging.Logger;
47
import org.netbeans.junit.NbTestCase;
44
import org.netbeans.lib.lexer.lang.TestTokenId;
48
import org.netbeans.lib.lexer.lang.TestTokenId;
45
import junit.framework.TestCase;
46
import org.netbeans.lib.lexer.test.FixedTextDescriptor;
49
import org.netbeans.lib.lexer.test.FixedTextDescriptor;
47
import org.netbeans.lib.lexer.test.LexerTestUtilities;
50
import org.netbeans.lib.lexer.test.LexerTestUtilities;
48
import org.netbeans.lib.lexer.test.RandomCharDescriptor;
51
import org.netbeans.lib.lexer.test.RandomCharDescriptor;
Lines 55-61 Link Here
55
 *
58
 *
56
 * @author mmetelka
59
 * @author mmetelka
57
 */
60
 */
58
public class SimpleLexerRandomTest extends TestCase {
61
public class SimpleLexerRandomTest extends NbTestCase {
59
62
60
    public SimpleLexerRandomTest(String testName) {
63
    public SimpleLexerRandomTest(String testName) {
61
        super(testName);
64
        super(testName);
Lines 69-74 Link Here
69
    protected void tearDown() throws java.lang.Exception {
72
    protected void tearDown() throws java.lang.Exception {
70
    }
73
    }
71
74
75
    @Override
76
    public PrintStream getLog() {
77
        return System.out;
78
//        return super.getLog();
79
    }
80
81
    @Override
82
    protected Level logLevel() {
83
        return Level.INFO;
84
//        return super.logLevel();;
85
    }
86
72
    public void testRandom() throws Exception {
87
    public void testRandom() throws Exception {
73
        test(0);
88
        test(0);
74
    }
89
    }
Lines 78-84 Link Here
78
    }
93
    }
79
    
94
    
80
    private void test(long seed) throws Exception {
95
    private void test(long seed) throws Exception {
81
        TestRandomModify randomModify = new TestRandomModify(seed);
96
        TestRandomModify randomModify = new TestRandomModify(seed, this.getClass());
82
        randomModify.setLanguage(TestTokenId.language());
97
        randomModify.setLanguage(TestTokenId.language());
83
        
98
        
84
        //randomModify.setDebugOperation(true);
99
        //randomModify.setDebugOperation(true);
Lines 87-114 Link Here
87
102
88
        // Check for incorrect lookahead counting problem
103
        // Check for incorrect lookahead counting problem
89
        // after one of the larger updates of the LexerInputOperation's code
104
        // after one of the larger updates of the LexerInputOperation's code
90
        randomModify.insertText(0, "+--+"); // "+"[2]; "-"[0]; "-"[0]; "+"[1];
105
        randomModify.insertText(0, "+--+"); // "+"[2]; "-"[1]; "-"[0]; "+"[1];
91
        randomModify.removeText(2, 1); // "+-+": "+-+"[0];
106
        randomModify.removeText(2, 1); // "+-+": "+-+"[0];
92
        
93
        randomModify.clearDocument();
107
        randomModify.clearDocument();
94
108
95
        // Check that token list updater respects that the lookaheads
109
        // Check for error with querying laState.lookahead(-1) after implementing lexer input 
110
//        Logger.getLogger(org.netbeans.lib.lexer.inc.TokenListUpdater.class.getName()).setLevel(Level.FINE); // Extra logging
111
        randomModify.insertText(0, "--");
112
        randomModify.insertText(0, "-");
113
//        Logger.getLogger(org.netbeans.lib.lexer.inc.TokenListUpdater.class.getName()).setLevel(Level.WARNING); // End of extra logging
114
        randomModify.clearDocument();
115
116
        // Check for incorrect backward elimination of extra relexed tokens.
117
        // This required to establish lowestMatchIndex in TokenListUpdater.relex().
118
        randomModify.insertText(0, "+ +");
119
        randomModify.insertText(2, "\n-\n");
120
        randomModify.clearDocument();
121
122
123
        // -------------------- SAME-LOOKAHEAD REQUIREMENTS -------------------------
124
        // Check that the token list updater respects the rule
125
        // that the lookahead of the incrementally created tokens
126
        // is the same like in a regular batch lexing.
127
        // This may not be strictly necessary for correctness but very beneficial because 
128
        // then the incrementally patched tokens can be compared after each modification
129
        // with a token list obtained by batch lexing and everything including
130
        // lookaheads and states can be required to be the same.
131
        //
132
        // -------------------- SAME-LOOKAHEAD REQUIREMENT #1 -------------------------
133
        // Check that when token list updater finds a match (token boundary and states match)
134
        // that also the lookahead of 
96
        // of subsequent tokens (already present in the token list)
135
        // of subsequent tokens (already present in the token list)
97
        // correspond to the lookahead of the relexed token.
136
        // correspond to the lookahead of the relexed token.
98
        // In the following example a "+-+" token must be created.
137
        // In the following example a "+-+" token must be created.
99
        randomModify.insertText(0, "---+"); // "-"[0]; "-"[0]; "-"[0]; "+"[1];
138
        randomModify.insertText(0, "---+"); // "-"[0]; "-"[0]; "-"[0]; "+"[1]; <- see the second token LA=0
100
        randomModify.insertText(1, "+"); 
139
        randomModify.insertText(1, "+"); // "-+--+": "-"[0]; "+"[2]; "-"[1]; "-"[0]; "+"[1]; <- seems only "+"[2] was added
101
        randomModify.removeText(3, 1);
140
        // BUT note the next token "-"[1] has to be relexed too since the original had LA=0
102
        
141
142
        // Now in addition check that "+-+" will be created.
143
        randomModify.removeText(3, 1); // "-+-+": "-"[0]; "+-+"[0]
103
        randomModify.clearDocument();
144
        randomModify.clearDocument();
104
145
105
        // Check that the token list updater checks respects the rule
106
        // that the lookahead of the incrementally created tokens
107
        // is the same like in a regular batch lexing.
108
        randomModify.insertText(0, "+--+--");
109
        randomModify.removeText(2, 1);
110
        
146
        
147
        // -------------------- SAME-LOOKAHEAD REQUIREMENT #2 -------------------------
148
        // Here check that an original token after the match point would not have unnecesarily high LA.
149
        // This could happen if the original token before the match point would have LA longer than
150
        // the length of the token that follows it (the one right after the match point) and so it would affect
151
        // LA of that next token too. Now if the newly relexed token (before match point) would have small LA
152
        // the retained token after match point would still hold the extra LA unnecesarilly.
153
        randomModify.insertText(0, "+--+--"); // "+"[2]; "-"[1]; "-"[0]; "+"[2]; "-"[1]; "-"[0]
154
        randomModify.removeText(2, 1); // "+-+--": Without extra check: "+-+"[0]; "-"[1]; "-"[0]
111
        randomModify.clearDocument();
155
        randomModify.clearDocument();
156
        // BUT in batch lexing the second token would have LA=0.
157
        // A potential fix is to check that when lexing stops that the 
158
        
159
        
112
   
160
   
113
        // Check for the previous case but this time the relexing would normally
161
        // Check for the previous case but this time the relexing would normally
114
        // be skipped but this would lead to lookahead 1 for the "-" token
162
        // be skipped but this would lead to lookahead 1 for the "-" token
Lines 116-136 Link Here
116
        randomModify.insertText(0, "-+--"); // "-"[0]; "+"[2]; "-"[1]; "-"[0];
164
        randomModify.insertText(0, "-+--"); // "-"[0]; "+"[2]; "-"[1]; "-"[0];
117
        // Without extra care it would become "-"[0]; "-"[1]; "-"[0];
165
        // Without extra care it would become "-"[0]; "-"[1]; "-"[0];
118
        randomModify.removeText(1, 1); // "---": "-"[0]; "-"[0]; "-"[0]; 
166
        randomModify.removeText(1, 1); // "---": "-"[0]; "-"[0]; "-"[0]; 
119
        
120
        randomModify.clearDocument();
167
        randomModify.clearDocument();
121
        
168
        
122
        // Similar case to the previous one but with more tokens
169
        // Similar case to the previous one but with more tokens
123
        randomModify.insertText(0, "-+-++--");
170
        randomModify.insertText(0, "-+-++--");
124
        randomModify.removeText(1, 4);
171
        randomModify.removeText(1, 4);
125
        
126
        randomModify.clearDocument();
172
        randomModify.clearDocument();
173
174
127
175
128
        // Check for the case when token validation cannot be performed
176
        // Check for the case when token validation cannot be performed
129
        // because although the lenghth of the removal is less than
177
        // because although the lenghth of the removal is less than
130
        // the "+-+" token's length the removal spans token boundaries
178
        // the "+-+" token's length the removal spans token boundaries
131
        randomModify.insertText(0, "-+-+ --");
179
        randomModify.insertText(0, "-+-+ --");
132
        randomModify.removeText(3, 2);
180
        randomModify.removeText(3, 2);
133
        
134
        randomModify.clearDocument();
181
        randomModify.clearDocument();
135
182
136
183
Lines 161-167 Link Here
161
208
162
        randomModify.test(
209
        randomModify.test(
163
            new RandomModifyDescriptor[] {
210
            new RandomModifyDescriptor[] {
164
                new RandomModifyDescriptor(200, plusMinusTextProvider,
211
                new RandomModifyDescriptor(200, textProvider,
165
                        0.2, 0.2, 0.1,
212
                        0.2, 0.2, 0.1,
166
                        0.2, 0.2,
213
                        0.2, 0.2,
167
                        0.0, 0.0), // snapshots create/destroy
214
                        0.0, 0.0), // snapshots create/destroy

Return to bug 117450