/**
* This file Copyright (c) 2005-2008 Aptana, Inc. This program is
* dual-licensed under both the Aptana Public License and the GNU General
* Public license. You may elect to use one or the other of these licenses.
*
* This program is distributed in the hope that it will be useful, but
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
* NONINFRINGEMENT. Redistribution, except as permitted by whichever of
* the GPL or APL you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or modify this
* program under the terms of the GNU General Public License,
* Version 3, as published by the Free Software Foundation. You should
* have received a copy of the GNU General Public License, Version 3 along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Aptana provides a special exception to allow redistribution of this file
* with certain Eclipse Public Licensed code and certain additional terms
* pursuant to Section 7 of the GPL. You may view the exception and these
* terms on the web at http://www.aptana.com/legal/gpl/.
*
* 2. For the Aptana Public License (APL), this program and the
* accompanying materials are made available under the terms of the APL
* v1.0 which accompanies this distribution, and is available at
* http://www.aptana.com/legal/apl/.
*
* You may view the GPL, Aptana's exception and additional terms, and the
* APL in the file titled license.html at the root of the corresponding
* plugin containing this source file.
*
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.ide.logging.coloring;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import com.aptana.ide.core.IdeLog;
import com.aptana.ide.lexer.ILexer;
import com.aptana.ide.lexer.Lexeme;
import com.aptana.ide.lexer.LexerException;
import com.aptana.ide.logging.LoggingPlugin;
import com.aptana.ide.logging.LoggingPreferences;
/**
* Lexeme manager that manages file lexemes.
* @author Denis Denisenko
*/
public class LoggingLexemeManager
{
/**
* Lexemes.
*/
private Map<Integer, List<Lexeme>> lexemes;
/**
* Document.
*/
private final IDocument document;
/**
* Max number of regexp lines.
*/
private final int regexpMaxLines;
/**
* Top lines.
*/
private List<String> topLines = new ArrayList<String>();
/**
* LoggingLexemeManager constructor.
* @param document - document.
* @param preferences - logging preferences.
*/
public LoggingLexemeManager(IDocument document,
LoggingPreferences preferences)
{
lexemes = new HashMap<Integer, List<Lexeme>>();
regexpMaxLines = preferences.getRegexpMaxLines();
this.document = document;
}
public Lexeme[] getLexemes(int lineNumber)
{
List<Lexeme> cached = lexemes.get(lineNumber);
if (cached == null)
{
synchronized (lexemes)
{
try
{
prefetch(lineNumber);
} catch (BadLocationException e)
{
IdeLog.logError(LoggingPlugin.getDefault(), Messages.LoggingLexemeManager_ERR_Exception, e);
return new Lexeme[]{};
}
cached = lexemes.get(lineNumber);
if (cached == null)
{
return new Lexeme[]{};
}
}
}
Lexeme[] result = new Lexeme[cached.size()];
cached.toArray(result);
return result;
}
/**
* Make a prefetch for current line and several nearby lines
* @param lineNumber - line number.
* @throws BadLocationException
*/
private void prefetch(int lineNumber) throws BadLocationException
{
//getting first line to start lexing from
int lineToStart = lineNumber - regexpMaxLines + 1;
if (lineToStart < -topLines.size())
{
lineToStart = 0;
}
//checking for cached lexemes.
for (int i = lineToStart; i <= lineNumber; i++)
{
List<Lexeme> lineLexemes = lexemes.get(i);
if (lineLexemes == null)
{
lineToStart = i;
break;
}
}
String toParse = buildContentToParse(lineToStart, lineNumber);
clearLines(lineToStart, lineNumber);
try
{
getLexer().setLanguage(TokenTypes.LANGUAGE);
} catch (LexerException e1)
{
IdeLog.logError(LoggingPlugin.getDefault(), Messages.LoggingLexemeManager_ERR_Exception, e1);
}
getLexer().setSource(toParse);
while(true)
{
Lexeme lexeme = getLexer().getNextLexeme();
if (lexeme == null)
{
break;
}
addLexeme(lexeme, lineToStart);
}
}
/**
* Gets lexer.
* @return lexer
*/
private ILexer getLexer()
{
return TokenTypes.getLexerFactory().getLexer();
}
/**
* Clears lines cache.
* @param startLine - start line.
* @param endLine - end line.
*/
private void clearLines(int startLine, int endLine)
{
for (int i = startLine; i <= endLine; i++)
{
lexemes.put(i, null);
}
}
/**
* Builds content to parse.
* @param firstLine - first line to parse.
* @param lastLine - last line to parse.
* @return built content.
* @throws BadLocationException
*/
private String buildContentToParse(int firstLine, int lastLine) throws BadLocationException
{
int startLine = firstLine;
StringBuilder builder = new StringBuilder();
if (startLine < 0)
{
for (int i = startLine; i <= 0; i++)
{
builder.append(topLines.get(i + topLines.size()));
}
startLine = 0;
}
for (int lineNumber = startLine; lineNumber <= lastLine; lineNumber++)
{
int lineOffset = document.getLineOffset(lineNumber);
int lineLength = document.getLineLength(lineNumber);
builder.append(document.get(lineOffset, lineLength));
}
return builder.toString();
}
/**
* {@inheritDoc}
*/
public void dataAvailable(List<String> topLines)
{
synchronized (lexemes)
{
lexemes.clear();
fillTopLines(topLines);
}
}
/**
* Clears lexeme cache.
*/
public void clearCache()
{
lexemes.clear();
}
/**
* Fills toplines info.
* @param topLines - new toplines.
*/
private void fillTopLines(List<String> topLines)
{
this.topLines.clear();
this.topLines.addAll(topLines);
}
/**
* Registers cached lexeme.
* @param lexeme - lexeme to register
* @param baseLine - line, lexeme offset is counted beginning from.
* @throws BadLocationException
*/
private void addLexeme(Lexeme lexeme, int baseLine) throws BadLocationException
{
int effectiveLineOffset = 0;
if (baseLine < 0)
{
effectiveLineOffset = getTopLineStartOffset(baseLine);
}
else
{
effectiveLineOffset = document.getLineOffset(baseLine);
}
lexeme.adjustOffset(effectiveLineOffset);
int lexemeStartLine = getLineByOffset(lexeme.getStartingOffset());
List<Lexeme> lst = getModifiableLineLexemes(lexemeStartLine);
lst.add(lexeme);
int lexemeEndLine = getLineByOffset(lexeme.getEndingOffset());
if (lexemeEndLine != lexemeStartLine)
{
if (lexemeEndLine == lexemeStartLine + 1 && lexemeEndsWithNewLine(lexeme.getText()))
{
return;
}
lst = getModifiableLineLexemes(lexemeEndLine);
lst.add(lexeme);
}
}
/**
* Checks whether lexeme ends with new line.
* @param text - lexeme text.
* @return true if ends with new line, false otherwsie.
*/
private boolean lexemeEndsWithNewLine(String text)
{
return text.endsWith("\r") || text.endsWith("\n") || text.endsWith("\r\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/**
* Gets topline offset.
* @param line - line.
* @return topline offset
*/
private int getTopLineStartOffset(int line)
{
int result = 0;
for (int i = line; i < 0; i++)
{
result += topLines.get(i).length();
}
return result;
}
private int getLineByOffset(int offset) throws BadLocationException
{
return document.getLineOfOffset(offset);
}
/**
* Gets modifiable lexemes list for line.
* @param lineNumber - line number.
* @return list
*/
private List<Lexeme> getModifiableLineLexemes(int lineNumber)
{
List<Lexeme> toReturn = lexemes.get(lineNumber);
if (toReturn == null)
{
toReturn = new ArrayList<Lexeme>(1);
lexemes.put(lineNumber, toReturn);
}
return toReturn;
}
}