/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* 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.jkiss.dbeaver.ui.editors.sql.syntax;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.rules.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.themes.IThemeManager;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.sql.SQLConstants;
import org.jkiss.dbeaver.model.sql.SQLDialect;
import org.jkiss.dbeaver.model.sql.SQLSyntaxManager;
import org.jkiss.dbeaver.registry.sql.SQLCommandHandlerDescriptor;
import org.jkiss.dbeaver.registry.sql.SQLCommandsRegistry;
import org.jkiss.dbeaver.ui.editors.EditorUtils;
import org.jkiss.dbeaver.ui.editors.sql.syntax.rules.LineCommentRule;
import org.jkiss.dbeaver.ui.editors.sql.syntax.rules.SQLDelimiterRule;
import org.jkiss.dbeaver.ui.editors.sql.syntax.rules.SQLDelimiterSetRule;
import org.jkiss.dbeaver.ui.editors.sql.syntax.rules.SQLParameterRule;
import org.jkiss.dbeaver.ui.editors.sql.syntax.tokens.*;
import org.jkiss.dbeaver.ui.editors.text.TextWhiteSpaceDetector;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.Pair;
import java.io.File;
import java.util.*;
/**
* SQLSyntaxManager.
* <p/>
* Contains information about some concrete datasource underlying database syntax.
* Support runtime change of datasource (reloads syntax information)
*/
public class SQLRuleManager extends RuleBasedScanner {
private static final long MAX_FILE_LENGTH_FOR_RULES = 5000000;
@NotNull
private final IThemeManager themeManager;
@NotNull
private SQLSyntaxManager syntaxManager;
@NotNull
private TreeMap<Integer, SQLScriptPosition> positions = new TreeMap<>();
private Set<SQLScriptPosition> addedPositions = new HashSet<>();
private Set<SQLScriptPosition> removedPositions = new HashSet<>();
private boolean evalMode;
public SQLRuleManager(@NotNull SQLSyntaxManager syntaxManager)
{
this.syntaxManager = syntaxManager;
this.themeManager = PlatformUI.getWorkbench().getThemeManager();
}
public boolean isEvalMode() {
return evalMode;
}
public void startEval() {
this.evalMode = true;
}
public void endEval() {
this.evalMode = false;
for (IRule rule : fRules) {
if (rule instanceof SQLDelimiterRule) {
((SQLDelimiterRule) rule).changeDelimiter(null);
}
}
}
public void dispose()
{
}
@NotNull
public Collection<? extends Position> getPositions(int offset, int length)
{
return positions.subMap(offset, offset + length).values();
}
@NotNull
public synchronized Set<SQLScriptPosition> getRemovedPositions(boolean clear)
{
Set<SQLScriptPosition> posList = removedPositions;
if (clear) {
removedPositions = new HashSet<>();
}
return posList;
}
@NotNull
public synchronized Set<SQLScriptPosition> getAddedPositions(boolean clear)
{
Set<SQLScriptPosition> posList = addedPositions;
if (clear) {
addedPositions = new HashSet<>();
}
return posList;
}
public void refreshRules(@Nullable DBPDataSource dataSource, IEditorInput editorInput)
{
boolean minimalRules = false;
File file = EditorUtils.getLocalFileFromInput(editorInput);
if (file != null && file.length() > MAX_FILE_LENGTH_FOR_RULES) {
minimalRules = true;
}
/*final Color backgroundColor = null;unassigned || dataSource != null ?
getColor(SQLConstants.CONFIG_COLOR_BACKGROUND, SWT.COLOR_WHITE) :
getColor(SQLConstants.CONFIG_COLOR_DISABLED, SWT.COLOR_WIDGET_LIGHT_SHADOW);*/
final IToken keywordToken = new Token(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_KEYWORD), null, SWT.BOLD));
final IToken typeToken = new Token(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_DATATYPE), null, SWT.BOLD));
final IToken stringToken = new Token(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_STRING), null, SWT.NORMAL));
final IToken quotedToken = new Token(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_DATATYPE), null, SWT.NORMAL));
final IToken numberToken = new Token(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_NUMBER), null, SWT.NORMAL));
final IToken commentToken = new SQLCommentToken(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_COMMENT), null, SWT.NORMAL));
final SQLDelimiterToken delimiterToken = new SQLDelimiterToken(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_DELIMITER, SWT.COLOR_RED), null, SWT.NORMAL));
final SQLParameterToken parameterToken = new SQLParameterToken(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_PARAMETER, SWT.COLOR_DARK_BLUE), null, SWT.BOLD));
final IToken otherToken = new Token(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_TEXT), null, SWT.NORMAL));
final SQLBlockHeaderToken blockHeaderToken = new SQLBlockHeaderToken(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_KEYWORD), null, SWT.BOLD));
final SQLBlockBeginToken blockBeginToken = new SQLBlockBeginToken(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_KEYWORD), null, SWT.BOLD));
final SQLBlockEndToken blockEndToken = new SQLBlockEndToken(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_KEYWORD), null, SWT.BOLD));
final SQLBlockToggleToken blockToggleToken = new SQLBlockToggleToken(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_DELIMITER), null, SWT.BOLD));
setDefaultReturnToken(otherToken);
List<IRule> rules = new ArrayList<>();
SQLDialect dialect = syntaxManager.getDialect();
// Add rule for single-line comments.
for (String lineComment : dialect.getSingleLineComments()) {
if (lineComment.startsWith("^")) {
rules.add(new LineCommentRule(lineComment, commentToken)); //$NON-NLS-1$
} else {
rules.add(new EndOfLineRule(lineComment, commentToken)); //$NON-NLS-1$
}
}
{
final SQLControlToken controlToken = new SQLControlToken(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_COMMAND), null, SWT.BOLD));
String commandPrefix = syntaxManager.getControlCommandPrefix();
// Control rules
for (SQLCommandHandlerDescriptor controlCommand : SQLCommandsRegistry.getInstance().getCommandHandlers()) {
rules.add(new EndOfLineRule(commandPrefix + controlCommand.getId(), controlToken)); //$NON-NLS-1$
}
}
// Add rules for delimited identifiers and string literals.
//char escapeChar = syntaxManager.getEscapeChar();
String quoteSymbol = syntaxManager.getQuoteSymbol();
if (quoteSymbol != null) {
rules.add(new SingleLineRule(quoteSymbol, quoteSymbol, quotedToken, (char)0));
}
if (quoteSymbol == null || !quoteSymbol.equals(SQLConstants.STR_QUOTE_SINGLE)) {
rules.add(new MultiLineRule(SQLConstants.STR_QUOTE_SINGLE, SQLConstants.STR_QUOTE_SINGLE, stringToken, (char)0));
}
if (quoteSymbol == null || !quoteSymbol.equals(SQLConstants.STR_QUOTE_DOUBLE)) {
rules.add(new MultiLineRule(SQLConstants.STR_QUOTE_DOUBLE, SQLConstants.STR_QUOTE_DOUBLE, quotedToken, (char)0));
}
// Add rules for multi-line comments
Pair<String, String> multiLineComments = dialect.getMultiLineComments();
if (multiLineComments != null) {
rules.add(new MultiLineRule(multiLineComments.getFirst(), multiLineComments.getSecond(), commentToken, (char) 0, true));
}
// Add generic whitespace rule.
rules.add(new WhitespaceRule(new TextWhiteSpaceDetector()));
if (!minimalRules) {
// Add numeric rule
rules.add(new NumberRule(numberToken));
}
SQLDelimiterRule delimRule = new SQLDelimiterRule(syntaxManager.getStatementDelimiters(), delimiterToken);
rules.add(delimRule);
{
// Delimiter redefine
String delimRedefine = dialect.getScriptDelimiterRedefiner();
if (!CommonUtils.isEmpty(delimRedefine)) {
final SQLSetDelimiterToken setDelimiterToken = new SQLSetDelimiterToken(
new TextAttribute(getColor(SQLConstants.CONFIG_COLOR_COMMAND), null, SWT.BOLD));
rules.add(new SQLDelimiterSetRule(delimRedefine, setDelimiterToken, delimRule));
}
}
if (!minimalRules) {
// Add word rule for keywords, types, and constants.
WordRule wordRule = new WordRule(new SQLWordDetector(), otherToken, true);
for (String reservedWord : dialect.getReservedWords()) {
wordRule.addWord(reservedWord, keywordToken);
}
if (dataSource != null) {
for (String function : dialect.getFunctions(dataSource)) {
wordRule.addWord(function, typeToken);
}
for (String type : dialect.getDataTypes(dataSource)) {
wordRule.addWord(type, typeToken);
}
}
final String blockHeaderString = dialect.getBlockHeaderString();
if (!CommonUtils.isEmpty(blockHeaderString)) {
wordRule.addWord(blockHeaderString, blockHeaderToken);
}
String[][] blockBounds = dialect.getBlockBoundStrings();
if (blockBounds != null) {
for (String[] block : blockBounds) {
if (block.length != 2) {
continue;
}
wordRule.addWord(block[0], blockBeginToken);
wordRule.addWord(block[1], blockEndToken);
}
}
rules.add(wordRule);
}
final String blockToggleString = dialect.getBlockToggleString();
if (!CommonUtils.isEmpty(blockToggleString)) {
int divPos = blockToggleString.indexOf(SQLConstants.KEYWORD_PATTERN_CHARS);
if (divPos != -1) {
String prefix = blockToggleString.substring(0, divPos);
String postfix = blockToggleString.substring(divPos + SQLConstants.KEYWORD_PATTERN_CHARS.length());
WordPatternRule blockToggleRule = new WordPatternRule(new SQLWordDetector(), prefix, postfix, blockToggleToken);
rules.add(blockToggleRule);
} else {
WordRule blockToggleRule = new WordRule(getWordOrSymbolDetector(blockToggleString), Token.UNDEFINED, true);
blockToggleRule.addWord(blockToggleString, blockToggleToken);
rules.add(blockToggleRule);
}
}
if (!minimalRules) {
// Parameter rule
rules.add(new SQLParameterRule(syntaxManager, parameterToken));
}
IRule[] result = new IRule[rules.size()];
rules.toArray(result);
setRules(result);
}
public Color getColor(String colorKey)
{
return getColor(colorKey, SWT.COLOR_BLACK);
}
public Color getColor(String colorKey, int colorDefault)
{
ITheme currentTheme = themeManager.getCurrentTheme();
Color color = currentTheme.getColorRegistry().get(colorKey);
if (color == null) {
color = Display.getDefault().getSystemColor(colorDefault);
}
return color;
}
private static IWordDetector getWordOrSymbolDetector(String word) {
if (Character.isLetterOrDigit(word.charAt(0))) {
return new SQLWordDetector();
} else {
// Default delim rule
return new SymbolSequenceDetector(word);
}
}
private static class SymbolSequenceDetector implements IWordDetector {
private final String delimiter;
public SymbolSequenceDetector(String delimiter) {
this.delimiter = delimiter;
}
@Override
public boolean isWordStart(char c) {
return delimiter.charAt(0) == c;
}
@Override
public boolean isWordPart(char c) {
return delimiter.indexOf(c) != -1;
}
}
}