package org.chartsy.stockscanpro.lexer;
import java.util.List;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.chartsy.stockscanpro.lexer.api.ScanTokenId;
import org.netbeans.api.lexer.Language;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.spi.editor.bracesmatching.BracesMatcher;
import org.netbeans.spi.editor.bracesmatching.BracesMatcherFactory;
import org.netbeans.spi.editor.bracesmatching.MatcherContext;
import org.netbeans.spi.editor.bracesmatching.support.BracesMatcherSupport;
/**
*
* @author Viorel
*/
public final class ScanBraceMatcher implements BracesMatcher, BracesMatcherFactory
{
private static final char [] PAIRS = new char [] { '(', ')', '[', ']' };
private static final ScanTokenId [] PAIR_TOKEN_IDS
= new ScanTokenId []
{
ScanTokenId.LPAREN, ScanTokenId.RPAREN,
ScanTokenId.LBRACKET, ScanTokenId.RBRACKET
};
private final MatcherContext context;
private int originOffset;
private char originChar;
private char matchingChar;
private boolean backward;
private List<TokenSequence<?>> sequences;
public ScanBraceMatcher()
{
this(null);
}
private ScanBraceMatcher(MatcherContext context)
{
this.context = context;
}
public int[] findOrigin()
throws BadLocationException, InterruptedException
{
((AbstractDocument) context.getDocument()).readLock();
try
{
int [] origin = BracesMatcherSupport.findChar
(
context.getDocument(),
context.getSearchOffset(),
context.getLimitOffset(),
PAIRS
);
if (origin != null)
{
originOffset = origin[0];
originChar = PAIRS[origin[1]];
matchingChar = PAIRS[origin[1] + origin[2]];
backward = origin[2] < 0;
TokenHierarchy<Document> th = TokenHierarchy.get(context.getDocument());
sequences = getEmbeddedTokenSequences(th, originOffset, backward, ScanTokenId.language());
if (!sequences.isEmpty())
{
TokenSequence<?> seq = sequences.get(sequences.size() - 1);
seq.move(originOffset);
}
return new int [] { originOffset, originOffset + 1 };
} else {
return null;
}
}
finally
{
((AbstractDocument) context.getDocument()).readUnlock();
}
}
public int[] findMatches()
throws InterruptedException, BadLocationException
{
((AbstractDocument) context.getDocument()).readLock();
try
{
if (!sequences.isEmpty())
{
TokenSequence<?> seq = sequences.get(sequences.size() - 1);
seq.move(originOffset);
TokenHierarchy<Document> th = TokenHierarchy.get(context.getDocument());
List<TokenSequence<?>> list;
if (backward)
{
list = th.tokenSequenceList(seq.languagePath(), 0, originOffset);
}
else
{
list = th.tokenSequenceList(seq.languagePath(), originOffset + 1, context.getDocument().getLength());
}
ScanTokenId originId = getTokenId(originChar);
ScanTokenId lookingForId = getTokenId(matchingChar);
int counter = 0;
for(TokenSequenceIterator tsi = new TokenSequenceIterator(list, backward); tsi.hasMore(); )
{
TokenSequence<?> sq = tsi.getSequence();
if (originId == sq.token().id())
{
counter++;
}
else if (lookingForId == sq.token().id())
{
if (counter == 0)
return new int [] { sq.offset(), sq.offset() + sq.token().length() };
else
counter--;
}
}
}
return null;
}
finally
{
((AbstractDocument) context.getDocument()).readUnlock();
}
}
private ScanTokenId getTokenId(char ch)
{
for(int i = 0; i < PAIRS.length; i++)
{
if (PAIRS[i] == ch)
return PAIR_TOKEN_IDS[i];
}
return null;
}
public static List<TokenSequence<?>> getEmbeddedTokenSequences(
TokenHierarchy<?> th, int offset, boolean backwardBias,
Language<?> language)
{
List<TokenSequence<?>> sequences
= th.embeddedTokenSequences(offset, backwardBias);
for (int i = sequences.size() - 1; i >= 0; i--)
{
TokenSequence<?> seq = sequences.get(i);
if (seq.language() == language)
break;
else
sequences.remove(i);
}
return sequences;
}
private static final class TokenSequenceIterator
{
private final List<TokenSequence<?>> list;
private final boolean backward;
private int index;
public TokenSequenceIterator(List<TokenSequence<?>> list, boolean backward)
{
this.list = list;
this.backward = backward;
this.index = -1;
}
public boolean hasMore()
{
return backward ? hasPrevious() : hasNext();
}
public TokenSequence<?> getSequence()
{
assert index >= 0 && index < list.size() :
"No sequence available, call hasMore() first.";
return list.get(index);
}
private boolean hasPrevious()
{
boolean anotherSeq = false;
if (index == -1)
{
index = list.size() - 1;
anotherSeq = true;
}
for( ; index >= 0; index--)
{
TokenSequence<?> seq = list.get(index);
if (anotherSeq)
seq.moveEnd();
if (seq.movePrevious())
return true;
anotherSeq = true;
}
return false;
}
private boolean hasNext()
{
boolean anotherSeq = false;
if (index == -1)
{
index = 0;
anotherSeq = true;
}
for( ; index < list.size(); index++)
{
TokenSequence<?> seq = list.get(index);
if (anotherSeq)
seq.moveStart();
if (seq.moveNext())
return true;
anotherSeq = true;
}
return false;
}
}
public BracesMatcher createMatcher(MatcherContext context)
{
return new ScanBraceMatcher(context);
}
}