380 lines
12 KiB
Java
380 lines
12 KiB
Java
//This file is part of SECONDO.
|
|
|
|
//Copyright (C) 2004, University in Hagen, Department of Computer Science,
|
|
//Database Systems for New Applications.
|
|
|
|
//SECONDO is free software; you can redistribute it and/or modify
|
|
//it under the terms of the GNU General Public License as published by
|
|
//the Free Software Foundation; either version 2 of the License, or
|
|
//(at your option) any later version.
|
|
|
|
//SECONDO is distributed in the hope that it will be useful,
|
|
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
//GNU General Public License for more details.
|
|
|
|
//You should have received a copy of the GNU General Public License
|
|
//along with SECONDO; if not, write to the Free Software
|
|
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
package ui;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.BufferedWriter;
|
|
import java.io.FileReader;
|
|
import java.io.FileWriter;
|
|
import java.io.IOException;
|
|
import java.math.BigDecimal;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.nio.file.StandardCopyOption;
|
|
import java.util.ArrayList;
|
|
import java.util.StringTokenizer;
|
|
import java.util.regex.Pattern;
|
|
|
|
import javax.swing.text.AttributeSet;
|
|
import javax.swing.text.DocumentFilter.FilterBypass;
|
|
|
|
import sj.lang.ListExpr;
|
|
import ui.console.ConsoleDocumentFilterInterceptor;
|
|
import util.domain.EditorEvent;
|
|
import util.domain.EditorEventListener;
|
|
import util.domain.enums.EditorCommand;
|
|
import util.secondo.SecondoFacade;
|
|
|
|
/**
|
|
* Handles all the events of the {@link InteractiveQueryEditorFrame} and the utilized components
|
|
* @author D.Merle
|
|
*/
|
|
public class InteractiveQueryEditorController implements ConsoleDocumentFilterInterceptor {
|
|
private final String historyFileName = ".iqe_secondo_history";
|
|
private final int maxHistoryFileEntries = 200;
|
|
private final ArrayList<EditorEventListener> listeners;
|
|
private final InteractiveQueryEditorFrame frame;
|
|
private final InteractiveQueryEditorModel model;
|
|
|
|
public InteractiveQueryEditorController(final InteractiveQueryEditorFrame frame, final InteractiveQueryEditorModel model) {
|
|
listeners = new ArrayList<>();
|
|
this.frame = frame;
|
|
this.model = model;
|
|
initalizeHistory();
|
|
}
|
|
|
|
@Override
|
|
public void replace(final FilterBypass fb, final int offset, final int length, final String string, final AttributeSet attr) {
|
|
processDocumentEvent(string);
|
|
}
|
|
|
|
@Override
|
|
public void remove(final FilterBypass fb, final int offset, final int length) {
|
|
processDocumentEvent("");
|
|
}
|
|
|
|
private void processDocumentEvent(final String text) {
|
|
if ("\n".equals(text)) {
|
|
handleEnter();
|
|
} else if ("\t".equals(text)) {
|
|
handleTab();
|
|
} else {
|
|
final String currentCommandLine = frame.getEditor().getLineFromEndOfDocument(1);
|
|
model.setCommandFragment(currentCommandLine);
|
|
fireEditorEvent(EditorEvent.COMMAND_UPDATED);
|
|
}
|
|
}
|
|
|
|
private void handleEnter() {
|
|
String currentCommandLine;
|
|
currentCommandLine = frame.getEditor().getLineFromEndOfDocument(2);//An Enter events is immediately processed by a JTextPane. Therefore we need to get the second to last line of text.
|
|
model.setCommandFragment(currentCommandLine);
|
|
if (!currentCommandLine.equals("")) {
|
|
model.appendCurrentCommand(currentCommandLine);
|
|
}
|
|
|
|
String command = model.getCurrentCommand().trim();
|
|
if (command.endsWith(";") || currentCommandLine.equals("")) {
|
|
if (command.endsWith(";")) {
|
|
command = removeCommandDelimiter(command);
|
|
}
|
|
if (!command.equals("")) {
|
|
model.addCommandToHistory(command);
|
|
writeToHistoryFile(command);
|
|
}
|
|
executeCommand(command);
|
|
model.setPrompt("Secondo=>");
|
|
model.resetCurrentCommand();
|
|
fireEditorEvent(EditorEvent.NEW_COMMAND);
|
|
} else {
|
|
model.appendCurrentCommand(" ");
|
|
model.setPrompt("Secondo->");
|
|
}
|
|
}
|
|
|
|
private void handleTab() {
|
|
String currentCommandLine = frame.getEditor().getLineFromEndOfDocument(1);
|
|
String enteredText = "";
|
|
int beginIndex = currentCommandLine.indexOf("\t");
|
|
final Pattern onlyAplhabetic = Pattern.compile("\\w");
|
|
|
|
if (beginIndex > 0) {
|
|
String character = currentCommandLine.substring(beginIndex - 1, beginIndex);
|
|
while (onlyAplhabetic.matcher(character).find()) {
|
|
enteredText = character + enteredText;
|
|
beginIndex--;
|
|
if (beginIndex > 0) {
|
|
character = currentCommandLine.substring(beginIndex - 1, beginIndex);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
final ArrayList<String> foundKeywords = new ArrayList<>();
|
|
String[] keywords = null;
|
|
keywords = model.getKeywords();
|
|
for (int i = 0; i < keywords.length; i++) {
|
|
if (keywords[i].startsWith(enteredText)) {
|
|
foundKeywords.add(keywords[i]);
|
|
}
|
|
}
|
|
|
|
if (foundKeywords.size() == 0) {
|
|
frame.getEditor().replaceLastLine(currentCommandLine.replace("\t", ""));
|
|
} else if (foundKeywords.size() == 1) {
|
|
frame.getEditor().replaceLastLine(currentCommandLine.replace(enteredText + "\t", foundKeywords.get(0)));
|
|
} else {
|
|
String greatestCommonSubstring = "";
|
|
final String firstFoundWord = foundKeywords.get(0);
|
|
for (int i = 0; i < firstFoundWord.length(); i++) {
|
|
final String part = firstFoundWord.substring(0, i +1);
|
|
boolean fitsAll = true;
|
|
for (int j = 0; j < foundKeywords.size(); j++) {
|
|
if (!foundKeywords.get(j).startsWith(part)) {
|
|
fitsAll = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fitsAll) {
|
|
greatestCommonSubstring = part;
|
|
}
|
|
}
|
|
|
|
if (greatestCommonSubstring.equals(enteredText)) {
|
|
for (int i = 0; i < foundKeywords.size(); i++) {
|
|
if (i % 3 == 0) {
|
|
System.out.printf("%n");
|
|
}
|
|
System.out.printf("%-19s", foundKeywords.get(i));
|
|
}
|
|
System.out.printf("%n");
|
|
}
|
|
frame.getEditor().replaceLastLine(currentCommandLine.replace(enteredText + "\t", greatestCommonSubstring));
|
|
}
|
|
|
|
currentCommandLine = frame.getEditor().getLineFromEndOfDocument(1);
|
|
model.setCommandFragment(currentCommandLine);
|
|
fireEditorEvent(EditorEvent.COMMAND_UPDATED);
|
|
}
|
|
|
|
public void setPreviousCommand() {
|
|
frame.getEditor().replaceLastLine(model.getPreviousCommand());
|
|
}
|
|
|
|
public void setNextCommand() {
|
|
frame.getEditor().replaceLastLine(model.getNextCommand());
|
|
}
|
|
|
|
private void executeCommand(String command) {
|
|
final EditorCommand editorCommand = checkInputForEditorCommand(command);
|
|
if (editorCommand != null) {
|
|
handleEditorCommand(editorCommand, command);
|
|
if (editorCommand == EditorCommand.REPEAT) {//Repeat ist eine Ausnahme, weil repeat ein query beinhaltet, das auch mehrzeilig angegeben werden kann
|
|
command = removeRepeatFromCommand(command);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
final int repeatCommandCounter = model.getRepeatCommandCounter();
|
|
for (int i = 0; i < repeatCommandCounter; i++) {
|
|
final StringTokenizer tokenizer = new StringTokenizer(command, "|");
|
|
while (tokenizer.hasMoreTokens()) {
|
|
final String singleCommand = tokenizer.nextToken().trim();
|
|
final ListExpr result = processCommand(singleCommand);
|
|
model.setResult(result);
|
|
if (result != null) {
|
|
if (singleCommand.startsWith("open database")) {
|
|
fireEditorEvent(EditorEvent.INIT_DB_CONNECTION);
|
|
} else if (command.startsWith("close database")) {
|
|
fireEditorEvent(EditorEvent.CLOSE_DB_CONNECTION);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private String removeRepeatFromCommand(final String command) {
|
|
final Pattern pattern = Pattern.compile("\\Arepeat \\d", Pattern.CASE_INSENSITIVE);
|
|
return pattern.matcher(command).replaceFirst("");
|
|
}
|
|
|
|
private void handleEditorCommand(final EditorCommand command, final String input) {
|
|
switch (command) {
|
|
case HELP:
|
|
showHelpMessage();
|
|
break;
|
|
case REPEAT:
|
|
model.setRepeatCommandCounter(parseRepeatCounter(input));
|
|
break;
|
|
case DEBUG:
|
|
setDebugLevel(parseDebugLevel(input));
|
|
break;
|
|
case QUIT:
|
|
exitProgramm();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void showHelpMessage() {
|
|
model.showHelpMessage();
|
|
}
|
|
|
|
private int parseRepeatCounter(final String input) {
|
|
final StringTokenizer tokenizer = new StringTokenizer(input);
|
|
tokenizer.nextToken();
|
|
final String counter = tokenizer.nextToken();
|
|
return new BigDecimal(counter).intValue();
|
|
}
|
|
|
|
private int parseDebugLevel(final String input) {
|
|
final String purgedCommand = input.replaceAll("[^0-9]", "");
|
|
final BigDecimal debugLevel = new BigDecimal(purgedCommand);
|
|
return debugLevel.intValue();
|
|
}
|
|
|
|
private void setDebugLevel(final int debugLevel) {
|
|
model.setDebugLevel(debugLevel);
|
|
SecondoFacade.setDebugLevel(debugLevel);
|
|
}
|
|
|
|
private void exitProgramm() {
|
|
System.exit(0);
|
|
}
|
|
|
|
/**
|
|
* Die Methode entfernt das letzte Zeichen eines Strings.
|
|
* Wird dazu genutzt ";" oder "\n" aus dem eingegebenen Befehl zu entfernen.
|
|
* @param command
|
|
* @return
|
|
*/
|
|
private String removeCommandDelimiter(final String command) {
|
|
if(command.length() >= 1) {
|
|
return command.substring(0, command.length()-1);
|
|
}
|
|
return command;
|
|
}
|
|
|
|
private ListExpr processCommand(final String command) {
|
|
return SecondoFacade.query(command, true);
|
|
}
|
|
|
|
private EditorCommand checkInputForEditorCommand(final String input) {
|
|
for (final EditorCommand editorCommand : EditorCommand.values()) {
|
|
for (final String command : editorCommand.getCommandList()) {
|
|
final StringTokenizer commandTokenizer = new StringTokenizer(command);
|
|
if (commandTokenizer.countTokens() > 1) {
|
|
if (input.toLowerCase().startsWith(commandTokenizer.nextToken().toLowerCase())) {
|
|
return editorCommand;
|
|
}
|
|
} else if(commandTokenizer.countTokens() == 1) {
|
|
final StringTokenizer inputTokenizer = new StringTokenizer(input);
|
|
if (inputTokenizer.hasMoreTokens() && inputTokenizer.nextToken().equalsIgnoreCase(command.toLowerCase())) {
|
|
return editorCommand;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void initalizeHistory() {
|
|
try {
|
|
final Path historyFile = Paths.get(historyFileName);
|
|
if (!Files.exists(historyFile)) {
|
|
Files.createFile(historyFile);
|
|
}
|
|
|
|
final ArrayList<String> historyCommands = new ArrayList<>();
|
|
try (FileReader fileReader = new FileReader(historyFile.toFile())) {
|
|
try (BufferedReader bufferedReader = new BufferedReader(fileReader)) {
|
|
String line = bufferedReader.readLine();
|
|
while (line != null) {
|
|
historyCommands.add(line);
|
|
model.addCommandToHistory(line);
|
|
line = bufferedReader.readLine();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (historyCommands.size() > maxHistoryFileEntries) {
|
|
clearHistory(historyFile, historyCommands);
|
|
}
|
|
} catch (final IOException e) {
|
|
e.printStackTrace();//TODO log?
|
|
}
|
|
}
|
|
|
|
private void clearHistory(final Path historyFile, final ArrayList<String> historyCommands) {
|
|
try {
|
|
final Path newHistoryFile = Paths.get(".iqe_secondo_history_temp");
|
|
if (!Files.exists(newHistoryFile)) {
|
|
Files.createFile(newHistoryFile);
|
|
}
|
|
try (FileWriter fileWriter = new FileWriter(newHistoryFile.toFile())) {
|
|
try (BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) {
|
|
for (int i = historyCommands.size() - maxHistoryFileEntries; i < historyCommands.size(); i++) {
|
|
bufferedWriter.write(historyCommands.get(i) + "\n");
|
|
}
|
|
}
|
|
}
|
|
Files.move(newHistoryFile, newHistoryFile.resolveSibling(historyFile.getFileName()), StandardCopyOption.REPLACE_EXISTING);
|
|
} catch (final IOException e) {
|
|
e.printStackTrace();//TODO log?
|
|
}
|
|
}
|
|
|
|
private void writeToHistoryFile(final String command) {
|
|
try {
|
|
final Path historyFile = Paths.get(historyFileName);
|
|
if (!Files.exists(historyFile)) {
|
|
Files.createFile(historyFile);
|
|
}
|
|
|
|
final FileWriter fileWriter = new FileWriter(historyFile.toFile(), true);
|
|
fileWriter.write(command + "\n");
|
|
fileWriter.close();
|
|
} catch (final IOException e) {
|
|
// TODO log?
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
public boolean addEditorEventListener(final EditorEventListener listener) {
|
|
return listeners.add(listener);
|
|
}
|
|
|
|
/**
|
|
* Methode zum Dispatch von Ereignissen an alle Listener.
|
|
*/
|
|
private void fireEditorEvent(final String eventType) {
|
|
final String command = model.getCurrentCommand() + model.getCommandFragment();
|
|
final EditorEvent event = new EditorEvent(this, eventType, command);
|
|
for (final EditorEventListener listener : listeners) {
|
|
listener.handleEditorEvent(event);
|
|
}
|
|
}
|
|
} |