/*
* Copyright 2003-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.eclipse.refactoring.formatter;
import groovyjarjarantlr.Token;
import groovyjarjarantlr.TokenStream;
import groovyjarjarantlr.TokenStreamException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Vector;
import org.codehaus.greclipse.GroovyTokenTypeBridge;
import org.codehaus.groovy.antlr.parser.GroovyLexer;
import org.codehaus.groovy.eclipse.core.GroovyCore;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
/**
* This subclass of GroovyDocumentScanner overrides ensureScanned, to produce
* exactly the same tokens as the Vector<Token> that was in Mike Klenk's
* Formatter implementation. It also keeps track of a Vector<Vector<Token>>
* in exactly the same way that M. Klenk likes it (the line numbers of the file
* do not match with the indexes of the lines vector).
* <p>
* This allows us to make use of the nice operations of GroovyDocumentScanner, without
* storing two copies of all the tokens, and without having to port all Mike's
* code all at once.
*
* @author kdvolder
* @created 2010-06-06
*/
public class KlenkDocumentScanner extends GroovyDocumentScanner {
/** Tokens split up into lines */
private Vector<Vector<Token>> tokenLines; // FIXKDV:
// If we must have something like this, it could be much cheaper (memory
// wise) represented
// by only keeping an array of indexes that give the position of the first
// line.
// (Note, we could not use the document's line info for this purpose,
// because Mike
// Klenk's lines are not actually the same as document lines (some lines
// containing
// multiline stuff are skipped, so indexes in this vector do not correspond
// to line numbers
// in any meaningful way.
public KlenkDocumentScanner(IDocument doc) {
super(doc);
}
@Override
protected void ensureScanned(int end) {
if (tokens != null)
return; // already scanned!
// Code in this method copied from Mike Klenk's
// DefaultGroovyFormatter.initCodeBase.
// This is copied as much as possible unchanged, to ensure identical
// behaviour, and avoid
// breaking Mike's formatter,
Reader input = new StringReader(getDocument().get());
GroovyLexer lexer = new GroovyLexer(input);
lexer.setWhitespaceIncluded(true);
TokenStream stream = lexer.plumb();
Token token = null;
tokens = new ArrayList<Token>();
tokenLines = new Vector<Vector<Token>>();
Vector<Token> line = new Vector<Token>();
try {
while ((token = stream.nextToken()).getType() != GroovyTokenTypeBridge.EOF) {
if (token.getType() != GroovyTokenTypeBridge.WS) {
// Ignore Tokens inside a String
if (token.getType() == GroovyTokenTypeBridge.STRING_CTOR_START) {
tokens.add(token);
Token prevToken = token;
inner: while ((token = stream.nextToken()).getType() != GroovyTokenTypeBridge.STRING_CTOR_END) {
if (equalTokens(prevToken, token)) {
break inner;
}
prevToken = token;
}
}
tokens.add(token);
line.add(token);
if (token.getType() == GroovyTokenTypeBridge.NLS) {
tokenLines.add(line);
line = new Vector<Token>();
}
}
}
} catch (TokenStreamException e) {
GroovyCore.logException("Scanning tokens threw an exception", e);
}
// Adding last Line with EOF at End
tokens.add(token);
line.add(token);
tokenLines.add(line);
}
private boolean equalTokens(Token t1, Token t2) {
return t1.getType() == t2.getType() && t1.getColumn() == t2.getColumn() && t1.getLine() == t2.getLine()
&& nullEquals(t1.getFilename(), t2.getFilename()) && nullEquals(t1.getText(), t2.getText());
}
private boolean nullEquals(String s1, String s2) {
if (s1 == null && s2 == null) {
return true;
} else if (s1 == null || s2 == null) {
return false;
}
return s1.equals(s2);
}
@Deprecated
public Vector<Vector<Token>> getLineTokensVector() {
ensureScanned(Integer.MAX_VALUE);
return tokenLines;
}
/**
* This method is deprecated. It provided for the more easy porting of Mike
* Klenk's
* code, which keeps a vector of tokens to operate on. It uses this to
* iterate
* over the tokens. This class provides more convenient ways to iterate over
* tokens,
* but we provide this method to make it possible to gradually get rid of
* the
* vector-indexing based logic.
* <p>
* Eventually all such indexing should be replaced with more high-level
* operations supported by this class.
*
* @return Return the i-th token in the document. Returns null if there is
* no such token.
*/
@Deprecated()
public Token get(int i) {
ensureScanned(Integer.MAX_VALUE);
if (i < tokens.size()) {
return tokens.get(i);
} else {
return null;
}
}
/**
* This method is deprecated. It is provided for the more easy porting of
* Mike Klenk's code, which keeps a vector of tokens to operate on. It uses
* this to iterate over the tokens. This class provides more convenient ways
* to iterate over tokens, but we provide this method to make it possible to
* gradually get rid of
* the
* vector-indexing based logic.
* <p>
* Eventually all such indexing should be replaced with more high-level
* operations supported by this class.
* <p>
* Note that using this method is undesirable since it cannot be implemented
* in an efficient manner (the whole file must be scanned to count the
* number of tokens in the file). Very often this number is really only used
* to determine when the end of file is reached, and another method of
* iteration not based on "vector-indexing" logic would not require
*
* @return Return the number of tokens in the file.
*/
@Deprecated
public int size() {
ensureScanned(Integer.MAX_VALUE);
return tokens.size();
}
/**
* This method is deprecated. It is provided for the more easy porting of
* Mike Klenk's code, which keeps a vector of tokens to operate on. It uses
* this to
*
* @param token
* @return
* @throws BadLocationException
*/
@Deprecated
public int indexOf(Token token) throws BadLocationException {
int pos = findTokenFrom(getOffset(token));
Assert.isTrue(token == tokens.get(pos));
return pos;
}
}