0001: /*
0002: * Copyright 2007 Roy van der Kuil (roy@vanderkuil.nl) and Stefan Rotman (stefan@rotman.net)
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package nl.improved.sqlclient;
0017:
0018: import java.io.*;
0019: import java.sql.Connection;
0020: import java.sql.ResultSet;
0021: import java.sql.SQLException;
0022: import java.util.Arrays;
0023: import java.util.ArrayList;
0024: import java.util.Iterator;
0025: import java.util.List;
0026: import java.util.Map;
0027: import java.util.LinkedHashMap;
0028: import java.util.logging.Level;
0029: import java.util.logging.Logger;
0030: import jcurses.widgets.Window;
0031: import jcurses.system.InputChar;
0032: import jcurses.system.Toolkit;
0033: import jcurses.system.CharColor;
0034: import jcurses.widgets.PopUpMenu;
0035: import nl.improved.sqlclient.commands.*;
0036:
0037: /**
0038: * The SQLShell main class.
0039: * This class provides a (textbased) window for entering sql commands.
0040: */
0041: public class SQLShell extends Window {
0042:
0043: /**
0044: * The current command thread executing a SQLShell command.
0045: */
0046: private CommandThread commandThread;
0047: /**
0048: * True when the prompt should be visible.
0049: * Normally it is not visible during the execution of a command
0050: */
0051: private boolean showPrompt = true;
0052:
0053: /**
0054: * The (default) maximum matches to show in a selection dialog.
0055: */
0056: private static final int MAX_MATCH_SIZE = 15;
0057: private int MAX_LINE_LENGTH; // not static and final.. since it can change on window resize
0058:
0059: /**
0060: * Page up count 0 means were at the bottom of our screen.
0061: * Increasement of the pageup count moves the visible part of the screen up.
0062: */
0063: private int pageUpCount = 0;
0064: /**
0065: * All lines in a screen.
0066: */
0067: private List<CharSequence> screenBuffer = new LimitedArrayList<CharSequence>(
0068: 1000);
0069: /**
0070: * The current command lines.
0071: */
0072: private SQLCommand commandLines;
0073: /**
0074: * All commands in history.
0075: */
0076: private List<SQLCommand> commandHistory = new LimitedArrayList<SQLCommand>(
0077: 50);
0078: /**
0079: * Index for browsing commands.
0080: */
0081: private int commandIndex = 0;
0082: /**
0083: * An empty line for easy line completion (fill with spaces).
0084: */
0085: private String emptyLine;
0086: /**
0087: * The cursor position in the command lines list.
0088: * 0,0 means it is at the first line at the first character
0089: * 10,5 means it is at the 11th character at the 6th line
0090: */
0091: private Point cursorPosition = new Point(0, 0);
0092: /**
0093: * The prompt to show when entering sql commands.
0094: */
0095: private static final String PROMPT = "SQL";
0096: /**
0097: * Some debug info holding the last trace of an exception.
0098: */
0099: private String lastExceptionDetails;
0100:
0101: /**
0102: * The output file when spool is on.
0103: */
0104: private Writer spoolWriter;
0105: /**
0106: * A manager for available commands.
0107: */
0108: private CommandManager commands = new CommandManager();
0109:
0110: /**
0111: * A map of string key representation to a action that should be executed when a specific key is pressed.
0112: */
0113: private Map<String, KeyAction> actionKeys = new LinkedHashMap<String, KeyAction>();
0114:
0115: /**
0116: * Constructor.
0117: */
0118: public SQLShell() {
0119: super (Toolkit.getScreenWidth(), Toolkit.getScreenHeight(),
0120: true, "SQLShell");
0121: char[] emptyLineChar = new char[Toolkit.getScreenWidth()];
0122: Arrays.fill(emptyLineChar, ' ');
0123: emptyLine = new String(emptyLineChar);
0124:
0125: // initialize the command shell
0126: commandLines = new SQLCommand();
0127: commandHistory.add(commandLines);
0128: newLine();
0129:
0130: // Register all known commands
0131: commands.register("CONNECT[\\s]*.*", new ConnectCommand());
0132: commands.register("DISCONNECT[\\s]*", new DisConnectCommand());
0133: commands
0134: .register(
0135: "SHOW[\\s]+[A-Z]+(|[A-Z]|_|\\.)*(|[\\s]+HAVING[\\s][A-Z]*.*)",
0136: new ShowCommand());
0137: commands.register("DESC[\\s]+[A-Z]+(|[A-Z]|_|\\.)*",
0138: new DescCommand());
0139: commands.register("WINDOW[\\s]+[A-Z]+", new WindowCommand());
0140: commands.register("INFO[\\s]*", new InfoCommand());
0141: commands.register("HELP[\\s]*.*", new HelpCommand());
0142: commands.register("HISTORY[\\s]*.*", new HistoryCommand());
0143: commands.register("SPOOL[\\s]*.*", new SpoolCommand());
0144: commands.register("QUIT[\\s]*", new QuitCommand("quit"));
0145: commands.register("EXIT[\\s]*", new QuitCommand("exit"));
0146: //commands.register("\\\\Q[\\s]*", new QuitCommand("\\q"));
0147: commands.register("@.*", new ExecuteBatchCommand());
0148: commands.register(
0149: "(SELECT|UPDATE|ALTER|INSERT|DELETE).*;[\\s]*",
0150: new QueryCommand());
0151:
0152: // keys
0153: actionKeys.put(Integer.toString(InputChar.KEY_LEFT),
0154: new KeyAction() {
0155: public void execute() {
0156: if (cursorPosition.x > 0) {
0157: cursorPosition.x--;
0158: } else if (cursorPosition.y > 0) {
0159: cursorPosition.y--;
0160: cursorPosition.x = commandLines.getLines()
0161: .get(cursorPosition.y).length();
0162: }
0163: }
0164:
0165: public CharSequence getHelp() {
0166: return "Arrow Left:\tMove cursor to the left";
0167: }
0168: });
0169: actionKeys.put(Integer.toString(InputChar.KEY_RIGHT),
0170: new KeyAction() {
0171: public void execute() {
0172: CharSequence tmp = commandLines.getLines().get(
0173: cursorPosition.y);
0174: if (cursorPosition.x < tmp.length()) {
0175: cursorPosition.x++;
0176: } else if (cursorPosition.y < commandLines
0177: .getLines().size() - 1) {
0178: cursorPosition.x = 0;
0179: cursorPosition.y++;
0180: }
0181: }
0182:
0183: public CharSequence getHelp() {
0184: return "Arrow Right:\tMove cursor to the right";
0185: }
0186: });
0187: actionKeys.put(Integer.toString(InputChar.KEY_UP),
0188: new KeyAction() {
0189: public void execute() {
0190: if (commandIndex > 0) {
0191: commandLines = commandHistory
0192: .get(--commandIndex);
0193: cursorPosition.y = commandLines.getLines()
0194: .size() - 1;
0195: CharSequence lineBuffer = commandLines
0196: .getLines().get(cursorPosition.y);
0197: cursorPosition.x = lineBuffer.length();
0198: }
0199: }
0200:
0201: public CharSequence getHelp() {
0202: return "Arrow Up:\tBrowse to previous command in the history";
0203: }
0204: });
0205: actionKeys.put(Integer.toString(InputChar.KEY_DOWN),
0206: new KeyAction() {
0207: public void execute() {
0208: if (commandIndex < commandHistory.size() - 1) {
0209: commandLines = commandHistory
0210: .get(++commandIndex);
0211: cursorPosition.y = commandLines.getLines()
0212: .size() - 1;
0213: CharSequence lineBuffer = commandLines
0214: .getLines().get(cursorPosition.y);
0215: cursorPosition.x = lineBuffer.length();
0216: }
0217: }
0218:
0219: public CharSequence getHelp() {
0220: return "Arrow Down:\tBrowse to next command in the history";
0221: }
0222: });
0223: actionKeys.put(Integer.toString(InputChar.KEY_END),
0224: new KeyAction() {
0225: public void execute() {
0226: int curLineEnd = commandLines.getLines().get(
0227: cursorPosition.y).length();
0228: if (cursorPosition.x == curLineEnd) {
0229: cursorPosition.y = commandLines.getLines()
0230: .size() - 1;
0231: CharSequence lineBuffer = commandLines
0232: .getLines().get(cursorPosition.y);
0233: cursorPosition.x = lineBuffer.length();
0234: } else {
0235: cursorPosition.x = curLineEnd;
0236: }
0237: }
0238:
0239: public CharSequence getHelp() {
0240: return "End:\tMove the cursor to the end of the line, of if already there to the end of the command";
0241: }
0242: });
0243: actionKeys.put(Integer.toString(InputChar.KEY_HOME),
0244: new KeyAction() {
0245: public void execute() {
0246: if (cursorPosition.x == 0) {
0247: cursorPosition.y = 0;
0248: }
0249: cursorPosition.x = 0;
0250: }
0251:
0252: public CharSequence getHelp() {
0253: return "Home:\tMove the cursor to the start of the line, of if already there to the start of the command";
0254: }
0255: });
0256: actionKeys.put(Integer.toString(InputChar.KEY_BACKSPACE),
0257: new KeyAction() {
0258: public void execute() {
0259: if (cursorPosition.x == 0) {
0260: if (cursorPosition.y > 0) {
0261: StringBuilder line = getEditableCommand()
0262: .getEditableLines().remove(
0263: cursorPosition.y);
0264: cursorPosition.y--;
0265: StringBuilder lineBuffer = commandLines
0266: .getEditableLines().get(
0267: cursorPosition.y);
0268: cursorPosition.x = lineBuffer.length();
0269: lineBuffer.append(line);
0270: }
0271: } else {
0272: StringBuilder tmp = getEditableCommand()
0273: .getEditableLines().get(
0274: cursorPosition.y);
0275: if (cursorPosition.x > 0) {
0276: tmp.deleteCharAt(cursorPosition.x - 1);
0277: cursorPosition.x--;
0278: }
0279: }
0280: }
0281:
0282: public CharSequence getHelp() {
0283: return "Backspace:\tRemove the character before the cursor position";
0284: }
0285: });
0286: actionKeys.put(Integer.toString(InputChar.KEY_DC),
0287: new KeyAction() {
0288: public void execute() {
0289: StringBuilder lineBuffer = commandLines
0290: .getEditableLines().get(
0291: cursorPosition.y);
0292: if (cursorPosition.x < lineBuffer.length()) {
0293: StringBuilder tmp = getEditableCommand()
0294: .getEditableLines().get(
0295: cursorPosition.y);
0296: tmp.deleteCharAt(cursorPosition.x);
0297: }
0298: }
0299:
0300: public CharSequence getHelp() {
0301: return "Del:\tDelete the charactor at the current cursor position";
0302: }
0303: });
0304: actionKeys.put("", new KeyAction() {
0305: public void execute() {
0306: StringBuilder lineBuffer = commandLines
0307: .getEditableLines().get(cursorPosition.y);
0308: int previousBreak = SQLUtil
0309: .getLastBreakIndex(lineBuffer.substring(0,
0310: cursorPosition.x));
0311: lineBuffer.delete(previousBreak, cursorPosition.x);
0312: cursorPosition.x = previousBreak;
0313: }
0314:
0315: public CharSequence getHelp() {
0316: return "Control-W:\tRemove word before cursor position";
0317: }
0318: });
0319: actionKeys.put("", new KeyAction() { // ctrl+u
0320: public void execute() {
0321: if (cursorPosition.x > 0) {
0322: StringBuilder lineBuffer = commandLines
0323: .getEditableLines().get(
0324: cursorPosition.y);
0325: lineBuffer.delete(0, cursorPosition.x);
0326: cursorPosition.x = 0;
0327: } else if (cursorPosition.y > 0) {
0328: StringBuilder lineBuffer = commandLines
0329: .getEditableLines().get(
0330: cursorPosition.y);
0331: if (lineBuffer.length() == 0) {
0332: commandLines.getEditableLines().remove(
0333: cursorPosition.y);
0334: }
0335: cursorPosition.y--;
0336: lineBuffer = commandLines
0337: .getEditableLines().get(
0338: cursorPosition.y);
0339: lineBuffer.delete(0, lineBuffer.length());
0340: }
0341: }
0342:
0343: public CharSequence getHelp() {
0344: return "Control-U:\tRemove all characters before the cursor position";
0345: }
0346: });
0347: actionKeys.put(Integer.toString(InputChar.KEY_PPAGE),
0348: new KeyAction() {
0349: public void execute() {
0350: if ((screenBuffer.size()
0351: + commandLines.getLines().size() - (Toolkit
0352: .getScreenHeight() / 2)
0353: * pageUpCount) > 0) {
0354: pageUpCount++;
0355:
0356: }
0357: }
0358:
0359: public CharSequence getHelp() {
0360: return "PageUp:\tMove back in screen history";
0361: }
0362: });
0363: actionKeys.put(Integer.toString(InputChar.KEY_NPAGE),
0364: new KeyAction() {
0365: public void execute() {
0366: if (pageUpCount > 0) {
0367: pageUpCount--;
0368: }
0369: }
0370:
0371: public CharSequence getHelp() {
0372: return "PageDown:\tMove forward in screen history";
0373: }
0374: });
0375: actionKeys.put("", new KeyAction() { // ctrl+a
0376: public void execute() {
0377: output("Abort requested");
0378: if (commandThread.isAlive()
0379: && commandThread.getCommand().abort()) {
0380: output("Abort done..");
0381: }
0382: }
0383:
0384: public CharSequence getHelp() {
0385: return "Control-A:\tAbort current command (if it is supported by that command)";
0386: }
0387: });
0388: actionKeys.put("", new KeyAction() { //Ctrl+D
0389: public void execute() {
0390: if (commandLines.getCommandString().length() == 0) { //Quit on empty commandline, ignore otherwise
0391: executeCommand(new InputCommand("quit"));
0392: }
0393: }
0394:
0395: public CharSequence getHelp() {
0396: return "Control-D:\tExit sqlshell";
0397: }
0398: });
0399:
0400: MAX_LINE_LENGTH = Toolkit.getScreenWidth()
0401: - (PROMPT.length() + 2 + 1); // prompt + "> "
0402:
0403: output("Welcome to the SQLShell client.");
0404: output("Type 'help' to get a list of available commands other then the default sql input.");
0405: }
0406:
0407: /**
0408: * Returns the connection to the database
0409: * @return the connection to the database
0410: */
0411: public Connection getConnection() {
0412: return DBConnector.getInstance().getConnection();
0413: }
0414:
0415: /**
0416: * Shows this window.
0417: * Override to make sure paint content is called.
0418: */
0419: @Override
0420: public void show() {
0421: super .show();
0422: paint();
0423: }
0424:
0425: /**
0426: * Hides this window.
0427: * Override to make sure the spool writer is closed when it was open
0428: */
0429: @Override
0430: public void hide() {
0431: try {
0432: if (spoolWriter != null) {
0433: try {
0434: spoolWriter.close();
0435: spoolWriter = null;
0436: } catch (Exception e) {/*ignore*/
0437: }
0438: }
0439: } finally {
0440: super .hide();
0441: }
0442: }
0443:
0444: /**
0445: * Returns a string representation of the current command.
0446: * @return a string representation of the current command.
0447: */
0448: private SQLCommand getCommand() {
0449: return commandLines;
0450: }
0451:
0452: /**
0453: * Add a new line to the command lines buffer.
0454: */
0455: private StringBuilder newLine() {
0456: cursorPosition.x = 0;
0457: StringBuilder newLine = new StringBuilder();
0458: getEditableCommand().getEditableLines().add(newLine);
0459: cursorPosition.y = commandLines.getLines().size() - 1;
0460: return newLine;
0461: }
0462:
0463: /**
0464: * Handle key input.
0465: * @param inp the character that is being pressed by the user.
0466: */
0467: protected void handleInput(InputChar inp) {
0468: //debug("Input: " + inp.getCode());
0469: try {
0470: if (inp.getCode() != InputChar.KEY_PPAGE
0471: && inp.getCode() != InputChar.KEY_NPAGE) {
0472: pageUpCount = 0; // some character entered, so reset pageup count
0473: }
0474: if (inp.isSpecialCode()) {
0475: KeyAction ke = actionKeys.get(Integer.toString(inp
0476: .getCode()));// || inp.toString().equals("") || inp.toString().equals("")) {
0477: if (ke != null) {
0478: ke.execute();
0479: } else {
0480: debug("Unknown character: " + inp.getCode());
0481: }
0482: } else {
0483: KeyAction ke = actionKeys.get(inp.toString());
0484: if (ke != null) {
0485: ke.execute();
0486: } else {
0487: if (inp.getCharacter() == '\n') { // newline... see if the command can be executed
0488: // execute the command
0489: SQLCommand sqlCommand = getCommand();
0490: String command = sqlCommand.getCommandString();
0491: // search command...
0492: if (command.length() > 0
0493: && command.charAt(0) == '/') { // search in history
0494: String matchPattern = ".*"
0495: + command.substring(1, command
0496: .length()) + ".*";
0497: for (int cIndex = commandHistory.size() - 1; cIndex >= 0; cIndex--) {
0498: if (cIndex == commandIndex) {
0499: continue; // skip current command
0500: }
0501: SQLCommand newSqlCommand = commandHistory
0502: .get(cIndex);
0503: String commandString = newSqlCommand
0504: .getCommandString();
0505: if (commandString.matches(matchPattern)) {
0506: commandHistory.remove(commandIndex);
0507: commandIndex = commandHistory
0508: .indexOf(newSqlCommand);
0509: commandLines = newSqlCommand;
0510: cursorPosition.y = 0;
0511: cursorPosition.x = 0;
0512: paint(); // force repaint
0513: return;
0514: }
0515: }
0516: Toolkit.beep(); // TODO clear search??
0517: return;
0518: } else if (executeCommand(sqlCommand)) {
0519: // clear command history
0520: if (commandIndex != commandHistory.size() - 1) {
0521: SQLCommand tmpLines = commandLines;
0522: commandLines = commandHistory
0523: .get(commandHistory.size() - 1);
0524: if (commandLines.getCommandString()
0525: .equals("")) {
0526: commandHistory.add(commandHistory
0527: .size() - 1, tmpLines);
0528: } else {
0529: commandHistory.add(tmpLines);
0530: commandLines = tmpLines;
0531: }
0532: }
0533: if (!commandLines.getCommandString()
0534: .equals("")) {
0535: commandLines = new SQLCommand();
0536: commandHistory.add(commandLines);
0537: newLine();
0538: }
0539: commandIndex = commandHistory.size() - 1;
0540: cursorPosition.y = commandLines.getLines()
0541: .size() - 1;
0542: cursorPosition.x = commandLines.getLines()
0543: .get(cursorPosition.y).length();
0544: paint(); // force repaint
0545: return;
0546: }
0547: }
0548: CharSequence newText;
0549: if (inp.getCharacter() == '\t') {
0550: try {
0551: newText = getTabCompletion(commandLines,
0552: cursorPosition);
0553: } catch (IllegalStateException e) {
0554: output(getCommand().getCommandString()); // add command as well...
0555: error(e);
0556: return;
0557: }
0558: } else {
0559: newText = Character
0560: .toString(inp.getCharacter());
0561: }
0562: if (newText.equals("\n")) {
0563: newLine(); // TODO Fix return in middle of an other line
0564: } else {
0565: List<StringBuilder> editableLines = getEditableCommand()
0566: .getEditableLines();
0567: StringBuilder currentLine = editableLines
0568: .get(cursorPosition.y);
0569: currentLine.insert(cursorPosition.x, newText);
0570: cursorPosition.x += newText.length();
0571: // check if the new line is becoming too long
0572: if (currentLine.length() > MAX_LINE_LENGTH) {
0573: // TODO search for lastspace that is not between '' ??
0574: int lastSpace = currentLine
0575: .lastIndexOf(" ");
0576: // check if there are enough 'next' lines
0577: // if not.. add one
0578: if (editableLines.size() - 1 == cursorPosition.y) {
0579: StringBuilder newLine = new StringBuilder();
0580: editableLines.add(newLine);
0581: }
0582: // check if the nextline has enough room for the new word
0583: // if not.. add a new line
0584: if (editableLines.get(cursorPosition.y + 1)
0585: .length()
0586: + (currentLine.length() - lastSpace + 1) > MAX_LINE_LENGTH) {
0587: StringBuilder newLine = new StringBuilder();
0588: editableLines.add(cursorPosition.y + 1,
0589: newLine);
0590: }
0591: // fetch the next line
0592: StringBuilder nextLine = editableLines
0593: .get(cursorPosition.y + 1);
0594: // if the nextline already has some text.. add a space in front of it
0595: if (nextLine.length() > 0) {
0596: nextLine.insert(0, ' ');
0597: }
0598: // insert the new text at the beginning
0599: nextLine.insert(0, currentLine
0600: .subSequence(lastSpace + 1,
0601: currentLine.length()));
0602: currentLine.delete(lastSpace, currentLine
0603: .length());
0604: // check if the cursor postition > the new line length
0605: // calculate new x and go to nextline
0606: if (cursorPosition.x >= lastSpace) {
0607: cursorPosition.x = cursorPosition.x
0608: - (lastSpace + 1);
0609: cursorPosition.y++;
0610: }
0611: }
0612: }
0613: }
0614: }
0615: } catch (Throwable t) {
0616: error(t);
0617: }
0618: paint();
0619: }
0620:
0621: /**
0622: * Return the editable version of the commandlines.
0623: * If editing a previous command clone it and return the clone
0624: * @return the editable version of the commandlines.
0625: */
0626: protected SQLCommand getEditableCommand() {
0627: if (commandHistory.indexOf(commandLines) != commandHistory
0628: .size() - 1) {
0629: List<? extends CharSequence> tmp = commandLines.getLines();
0630: if (commandHistory.get(commandHistory.size() - 1)
0631: .getLines().size() == 1
0632: && commandHistory.get(commandHistory.size() - 1)
0633: .getLines().get(0).length() == 0) {
0634: commandLines = commandHistory
0635: .get(commandHistory.size() - 1);
0636: commandLines.getEditableLines().remove(0);
0637: } else {
0638: commandLines = new SQLCommand();
0639: commandHistory.add(commandLines);
0640: }
0641: for (int i = 0; i < tmp.size(); i++) {
0642: commandLines.getEditableLines().add(
0643: new StringBuilder(tmp.get(i)));
0644: }
0645: commandIndex = commandHistory.size() - 1;
0646: }
0647: return commandLines;
0648: }
0649:
0650: /**
0651: * Output data to the screen.
0652: * @param data the data to print to the screen.
0653: */
0654: protected synchronized void output(CharSequence data) {
0655: screenBuffer.addAll(getLines(data));
0656: if (spoolWriter != null) {
0657: try {
0658: spoolWriter.write(data.toString());
0659: spoolWriter.write("\n");
0660: } catch (IOException e) {
0661: screenBuffer
0662: .add("WARNING: Could not write to spool file");
0663: error(e);
0664: }
0665: }
0666: }
0667:
0668: /**
0669: * Output error exception to the screen.
0670: * @param e the error to print to the screen
0671: */
0672: protected synchronized void error(Throwable e) {
0673: output(e.getMessage() == null ? e.toString() : e.getMessage());
0674: StringWriter sw = new StringWriter();
0675: e.printStackTrace(new PrintWriter(sw));
0676: sw.flush();
0677: lastExceptionDetails = sw.toString();
0678: }
0679:
0680: /**
0681: * Return a list of table names available for the current connection.
0682: * @return a list of table names available for the current connection.
0683: */
0684: protected List<String> getTableNames() {
0685: List<String> returnValue = new ArrayList<String>();
0686: try {
0687: ResultSet rs = getConnection().getMetaData().getTables(
0688: getConnection().getCatalog(),
0689: DBConnector.getInstance().getSchema(), null,
0690: new String[] { "TABLE" });
0691: while (rs.next()) {
0692: if (!returnValue.contains(rs.getString("TABLE_NAME"))) {
0693: returnValue.add(rs.getString("TABLE_NAME"));
0694: }
0695: }
0696: return returnValue;
0697: } catch (SQLException ex) {
0698: ex.printStackTrace();
0699: Logger.getLogger(SQLShell.class.getName()).log(
0700: Level.SEVERE, null, ex);
0701: return null;
0702: }
0703: }
0704:
0705: /**
0706: * Return a list of column names for the specified list of table names.
0707: * @param tableNames a list of tableNames
0708: * @return a list of column names for the specified list of table names.
0709: */
0710: protected List<String> getColumnNames(List<String> tableNames) {
0711: List<String> returnValues = new ArrayList<String>();
0712: Iterator<String> iTableNames = tableNames.iterator();
0713: while (iTableNames.hasNext()) {
0714: String tableName = DBConnector.getInstance()
0715: .translateDbVar(iTableNames.next().trim());
0716: try {
0717: ResultSet rs = getConnection().getMetaData()
0718: .getColumns(getConnection().getCatalog(),
0719: DBConnector.getInstance().getSchema(),
0720: tableName, "%");
0721: while (rs.next()) {
0722: if (!returnValues.contains(rs
0723: .getString("COLUMN_NAME"))) {
0724: returnValues.add(rs.getString("COLUMN_NAME"));
0725: }
0726: }
0727: } catch (SQLException ex) {
0728: throw new IllegalStateException(
0729: "Failed to find columnnames for table: "
0730: + tableName, ex);
0731: }
0732: }
0733: return returnValues;
0734: }
0735:
0736: /**
0737: * Try to find a match in the provided list starging with sub.
0738: * If more matches apply show the user a dialog to choose a match or simply display all matches in the window
0739: * @param values all possible values to choose from (not limited with the sub parameter)
0740: * @param sub the start of the match
0741: * @return the match starting with sub minus the sub itself in the correct casing
0742: */
0743: protected CharSequence findMatch(List<String> values, String sub) {
0744: List<String> matches = new ArrayList<String>();
0745: Iterator<String> iValues = values.iterator();
0746: while (iValues.hasNext()) {
0747: String value = iValues.next();
0748: if (value.toUpperCase().startsWith(sub.toUpperCase())) {
0749: matches.add(value);
0750: }
0751: }
0752: //debug("Matches found: "+ matches.size() +" --> "+ sub +" / "+ values);
0753: String match = null;
0754: if (matches.size() == 1) {
0755: match = matches.get(0);
0756: } else if (matches.size() > 1
0757: && matches.size() < MAX_MATCH_SIZE) {
0758: int y;
0759: if (screenBuffer.size() + cursorPosition.y > Toolkit
0760: .getScreenHeight() - 3) {
0761: y = Toolkit.getScreenHeight() - 4;
0762: } else {
0763: y = screenBuffer.size() + cursorPosition.y;
0764: }
0765: PopUpMenu menu = new PopUpMenu(cursorPosition.x, Math.max(
0766: 2, y - matches.size()), "Find match");
0767: Iterator<String> iMatches = matches.iterator();
0768: while (iMatches.hasNext()) {
0769: menu.add(iMatches.next());
0770: }
0771: menu.show();
0772: match = menu.getSelectedItem();
0773: } else if (matches.size() > 1) {
0774: output("\n" + toColumns(matches));
0775: }
0776: if (match != null) {
0777: if (sub.length() > 0) {
0778: if (Character.isUpperCase(sub.charAt(0))) {
0779: match = match.toUpperCase();
0780: } else {
0781: match = match.toLowerCase();
0782: }
0783: }
0784: return match.substring(sub.length());
0785: }
0786: return null;
0787: }
0788:
0789: /**
0790: * Simple method to change a null value to an empty charsequence.
0791: * @param s the charsequence to convert to empty if it is null
0792: * @return empty when s was null or return s when s is not null
0793: */
0794: protected CharSequence nullToEmpty(CharSequence s) {
0795: if (s == null) {
0796: return "";
0797: }
0798: return s;
0799: }
0800:
0801: /**
0802: * return the tab completion value.
0803: * @param commandLines the current command
0804: * @param cursorPosition the position where the tab completion was invocated
0805: * @return the tab completion value.
0806: */
0807: protected CharSequence getTabCompletion(SQLCommand commandLines,
0808: Point cursorPosition) {
0809: TabCompletionInfo info = null;
0810: String cmd = commandLines.getCommandString();
0811: if (cmd.length() > 0) {
0812: if (cmd.indexOf(' ') > 0) {
0813: cmd = cmd.substring(0, cmd.indexOf(' ')).trim();
0814: }
0815: Command tmpCommand = commands.findCommand(cmd);
0816: if (tmpCommand == null) {
0817: for (Command c : commands.getCommands()) {
0818: if (cmd.equalsIgnoreCase(c.getCommandString()
0819: .toString())) {
0820: tmpCommand = c;
0821: break;
0822: }
0823: }
0824: }
0825: if (tmpCommand != null) {
0826: info = tmpCommand.getTabCompletionInfo(commandLines,
0827: cursorPosition);
0828: }
0829: }
0830: if (info == null) {
0831: info = SQLUtil.getTabCompletionInfo(commandLines,
0832: cursorPosition);
0833: }
0834: if (info.getMatchType() == TabCompletionInfo.MatchType.SQL_KEYWORD) {
0835: return nullToEmpty(findMatch(info.getPossibleMatches(),
0836: info.getStart()));
0837: }
0838: if (info.getMatchType() == TabCompletionInfo.MatchType.TABLE_NAMES) {
0839: //debug("table completion for \""+info.getStart()+"\"");
0840: return nullToEmpty(findMatch(getTableNames(), info
0841: .getStart()));
0842: }
0843: if (info.getMatchType() == TabCompletionInfo.MatchType.COLUMN_NAMES) {
0844: return nullToEmpty(findMatch(getColumnNames(info
0845: .getPossibleMatches()), info.getStart()));
0846: }
0847: if (info.getMatchType() == TabCompletionInfo.MatchType.UNKNOWN) {
0848: return nullToEmpty(findMatch(info.getPossibleMatches(),
0849: info.getStart()));
0850: }
0851: Toolkit.beep();
0852: return "";
0853: }
0854:
0855: /**
0856: * (Try) to execute a command) and return true if it succeeded.
0857: * @param command the command to try and execute
0858: * @return true if it succeeded.
0859: */
0860: protected boolean executeCommand(SQLCommand sqlCommand) {
0861: return executeCommand(sqlCommand, false);
0862: }
0863:
0864: private boolean executeCommand(final SQLCommand sqlCommand,
0865: boolean direct) {
0866: final String commandString = sqlCommand.getCommandString();
0867: if (commandString.equalsIgnoreCase("printStackTrace")) {
0868: if (lastExceptionDetails == null) {
0869: output("No known last exception to print");
0870: } else {
0871: output(lastExceptionDetails);
0872: }
0873: return true;
0874: }
0875: Command command = createCommand(commandString); // first try to find a match without ;
0876: if (command == null) {
0877: command = createCommand(sqlCommand
0878: .getUntrimmedCommandString()); // then with ; for sql statements...
0879: }
0880: if (command == null) {
0881: return false;
0882: }
0883: // make sure only one command is run at once
0884: if (commandThread != null && commandThread.isAlive()) {
0885: try {
0886: commandThread.join();
0887: } catch (InterruptedException ex) {
0888: Logger.getLogger(SQLShell.class.getName()).log(
0889: Level.SEVERE, null, ex);
0890: }
0891: }
0892: output(commandString);
0893: if (direct || !command.backgroundProcessSupported()) {
0894: output(command.execute(sqlCommand));
0895: } else {
0896: commandThread = new CommandThread(command) {
0897: @Override
0898: void execute() {
0899: output(getCommand().execute(sqlCommand));
0900: }
0901: };
0902: commandThread.start();
0903: }
0904: return true;
0905: }
0906:
0907: private Command createCommand(String commandString) {
0908: Command command = commands.findCommand(commandString);
0909: if (command != null) {
0910: return command;
0911: }
0912: if (commandString.matches(".*;[\\s]*")) {
0913: return new QueryCommand(); // TODO is this ever reached???
0914: }
0915: return null;
0916: }
0917:
0918: /**
0919: * Paint the screen.
0920: */
0921: @Override
0922: protected synchronized void paint() {
0923: CharColor color = new CharColor(CharColor.BLACK,
0924: CharColor.WHITE, CharColor.BOLD, CharColor.BOLD);
0925:
0926: List<CharSequence> tmpList = new ArrayList<CharSequence>();
0927: tmpList.addAll(screenBuffer);
0928:
0929: //add prompt
0930: List<? extends CharSequence> currentLines = commandLines
0931: .getLines();
0932: for (int i = 0; i < currentLines.size(); i++) {
0933: if (i == 0 && showPrompt) {
0934: tmpList.add(PROMPT + "> " + currentLines.get(i));
0935: } else {
0936: String nrI = Integer.toString(i + 1);
0937: tmpList.add(emptyLine.substring(0, PROMPT.length()
0938: - nrI.length())
0939: + nrI + "> " + currentLines.get(i));
0940: }
0941: }
0942: int startLine;
0943: if (tmpList.size() > Toolkit.getScreenHeight() - 1) {
0944: startLine = tmpList.size()
0945: - (Toolkit.getScreenHeight() - 1);
0946: if (pageUpCount > 0) {
0947: startLine -= (pageUpCount * Toolkit.getScreenHeight() / 2);
0948: if (startLine < 0) {
0949: startLine = 0;
0950: }
0951: }
0952: } else {
0953: startLine = 0;
0954: }
0955: int lineNr;
0956: for (lineNr = startLine; lineNr < tmpList.size()
0957: && lineNr - startLine < Toolkit.getScreenHeight() - 1; lineNr++) {
0958: CharSequence linePart = tmpList.get(lineNr);
0959: String line = linePart
0960: + emptyLine.substring(linePart.length());
0961: Toolkit.printString(line, 0, lineNr - startLine, color);
0962: }
0963: for (int lNr = lineNr; lNr < Toolkit.getScreenHeight(); lNr++) {
0964: Toolkit.printString(emptyLine, 0, lNr, color);
0965: }
0966:
0967: // paint cursor
0968: color = new CharColor(CharColor.BLACK, CharColor.WHITE,
0969: CharColor.REVERSE, CharColor.REVERSE);
0970: String cursorChar = " ";
0971: if (commandLines.getLines().size() > 0) {
0972: String tmp = commandLines.getLines().get(cursorPosition.y)
0973: .toString();
0974: if (cursorPosition.x < tmp.length()) {
0975: cursorChar = tmp.substring(cursorPosition.x,
0976: cursorPosition.x + 1);
0977: }
0978: }
0979: Toolkit.printString(cursorChar, PROMPT.length() + "> ".length()
0980: + cursorPosition.x, lineNr
0981: - (commandLines.getLines().size() - cursorPosition.y)
0982: - startLine, color);
0983: }
0984:
0985: private void debug(String debug) {
0986: CharColor color = new CharColor(CharColor.BLUE,
0987: CharColor.YELLOW);
0988: Toolkit.printString(debug, 1, Toolkit.getScreenHeight() - 1,
0989: color);
0990: }
0991:
0992: /**
0993: * Method to convert a long string to a displayable list of strings with a max with of the screen width.
0994: * @param text a (long) string
0995: * @return the text devided into multiple lines to match the screen width
0996: */
0997: private static List<CharSequence> getLines(CharSequence text) {
0998: int maxWidth = Toolkit.getScreenWidth();
0999: List<CharSequence> list = new ArrayList<CharSequence>();
1000: StringBuilder buffer = new StringBuilder();
1001: for (int i = 0; i < text.length(); i++) {
1002: char c = text.charAt(i);
1003: if (c == '\n' || buffer.length() == maxWidth) {
1004: String line = buffer.toString();
1005: list.add(line);
1006: buffer = new StringBuilder();
1007: if (c != '\n') {
1008: buffer.append(c);
1009: }
1010: } else if (c == '\r') {
1011: //ignore
1012: } else {
1013: buffer.append(c);
1014: }
1015: }
1016: if (buffer.length() > 0) {
1017: list.add(buffer.toString());
1018: }
1019: return list;
1020: }
1021:
1022: /**
1023: * Convert a list to a presentable text devided into multiple columns.
1024: */
1025: private StringBuilder toColumns(List<? extends CharSequence> values) {
1026: int maxWidth = 0;
1027: Iterator<? extends CharSequence> iValues = values.iterator();
1028: while (iValues.hasNext()) {
1029: maxWidth = Math.max(maxWidth, iValues.next().length());
1030: }
1031: maxWidth += 2;// add extra space
1032: int nrOfColumns = (Toolkit.getScreenWidth()) / maxWidth;
1033: StringBuilder returnValue = new StringBuilder();
1034: for (int row = 0; row < values.size(); row += nrOfColumns) {
1035: for (int col = 0; col < nrOfColumns
1036: && row + col < values.size(); col++) {
1037: returnValue.append(values.get(row + col)
1038: + emptyLine.substring(0, maxWidth
1039: - values.get(row + col).length()));
1040: }
1041: returnValue.append('\n');
1042: }
1043: return returnValue;
1044: }
1045:
1046: /**
1047: * A list which contains a limited number of lines.
1048: */
1049: private class LimitedArrayList<E> extends ArrayList<E> {
1050: private int maxSize;
1051:
1052: /**
1053: * Constructor.
1054: * @param maxSize the maximum number of lines the list may contain
1055: */
1056: public LimitedArrayList(int maxSize) {
1057: super (maxSize); // some sane default
1058: this .maxSize = maxSize;
1059: }
1060:
1061: @Override
1062: public boolean add(E object) {
1063: if (size() == maxSize) {
1064: remove(0);
1065: }
1066: return super .add(object);
1067: }
1068:
1069: @Override
1070: public void add(int index, E object) {
1071: if (size() == maxSize) {
1072: remove(0);
1073: }
1074: super .add(index, object);
1075: }
1076: }
1077:
1078: /**
1079: * Connect command for setting up a connection to a database.
1080: */
1081: private static class ConnectCommand implements Command {
1082: /**
1083: * Execute the connection command and return a readable result.
1084: * @param command the command string for setting up a connection
1085: * @return a readable result of the execution of this command.
1086: */
1087: @Override
1088: public CharSequence execute(SQLCommand cmd) {
1089: String command = cmd.getCommandString();
1090: try {
1091: String cmdString = command
1092: .substring("connect".length()).trim();
1093: if (cmdString.length() > 0
1094: && cmdString.charAt(cmdString.length() - 1) == ';') {
1095: cmdString = cmdString.substring(0, cmdString
1096: .length() - 1);
1097: }
1098: DBConnector.getInstance().connect(cmdString);
1099: return "Connected.\n\n";
1100: } catch (SQLException e) {
1101: throw new IllegalStateException("Failed to connect: "
1102: + e.getMessage(), e);
1103: }
1104: }
1105:
1106: @Override
1107: public CharSequence getCommandString() {
1108: return "connect";
1109: }
1110:
1111: /**
1112: * Returns some tab completion info for the specified command.
1113: * @param commandInfo the command lines
1114: * @param commandPoint the cursor position
1115: * @return some tab completion info for the specified command.
1116: */
1117: @Override
1118: public TabCompletionInfo getTabCompletionInfo(
1119: SQLCommand command, Point commandPoint) {
1120: return null;
1121: }
1122:
1123: @Override
1124: public CharSequence getHelp() {
1125: StringBuffer buf = new StringBuffer();
1126: Iterator<String> idents = DBConnector.getInstance()
1127: .getPredefinedConnectionIdentifiers().iterator();
1128: while (idents.hasNext()) {
1129: buf.append(' ');
1130: String ident = idents.next();
1131: buf.append(ident);
1132: if (ident.equals(DBConnector.getInstance()
1133: .getDefaultIdentifier())) {
1134: buf.append(" *");
1135: }
1136: if (idents.hasNext()) {
1137: buf.append('\n');
1138: }
1139: }
1140: return "Create a conection to the database\n"
1141: + " user/pass@ident -> connect the user with password pass to the ident\n"
1142: + " user/@ident -> connect the user with an empty password pass to the ident\n"
1143: + " user@ident -> connect the user to the ident and prompt for password\n"
1144: + " @ident -> connect to the ident connection.\n"
1145: + " If default user and/or password are specified these will be used, \n"
1146: + " otherwise you will be prompted for a username and/or password.\n"
1147: + "Currently configured connection identifiers:\n"
1148: + buf.toString();
1149: }
1150:
1151: @Override
1152: public boolean abort() {
1153: return false;// not implemented
1154: }
1155:
1156: @Override
1157: public boolean backgroundProcessSupported() {
1158: return false;
1159: }
1160: }
1161:
1162: /**
1163: * Command that enables the user to close a connection.
1164: */
1165: private static class DisConnectCommand implements Command {
1166: @Override
1167: public CharSequence execute(SQLCommand cmd) {
1168: try {
1169: DBConnector.getInstance().disconnect();
1170: return "Disconnected.\n\n";
1171: } catch (SQLException e) {
1172: throw new IllegalStateException(
1173: "Failed to disconnect: " + e.getMessage(), e);
1174: }
1175: }
1176:
1177: @Override
1178: public CharSequence getCommandString() {
1179: return "disconnect";
1180: }
1181:
1182: /**
1183: * Returns some tab completion info for the specified command.
1184: * @param commandInfo the command lines
1185: * @param commandPoint the cursor position
1186: * @return some tab completion info for the specified command.
1187: */
1188: @Override
1189: public TabCompletionInfo getTabCompletionInfo(
1190: SQLCommand command, Point commandPoint) {
1191: return null;
1192: }
1193:
1194: @Override
1195: public CharSequence getHelp() {
1196: return "Close the current conection to the database";
1197: }
1198:
1199: @Override
1200: public boolean abort() {
1201: return false;// not implemented
1202: }
1203:
1204: @Override
1205: public boolean backgroundProcessSupported() {
1206: return false;
1207: }
1208: }
1209:
1210: /**
1211: * Some basic window settings like resize.
1212: */
1213: private class WindowCommand implements Command {
1214: @Override
1215: public CharSequence execute(SQLCommand cmd) {
1216: String command = cmd.getCommandString();
1217: String argument = command.trim().substring(
1218: "window".length()).trim();
1219: if (argument.equalsIgnoreCase("resize")) {
1220: resize(Toolkit.getScreenWidth(), Toolkit
1221: .getScreenHeight());
1222: return "Window resized to " + Toolkit.getScreenWidth()
1223: + "x" + Toolkit.getScreenHeight();
1224: }
1225: return "Uknown command '" + argument + "'";
1226: }
1227:
1228: @Override
1229: public CharSequence getCommandString() {
1230: return "window";
1231: }
1232:
1233: /**
1234: * Returns some tab completion info for the specified command.
1235: * @param commandInfo the command lines
1236: * @param commandPoint the cursor position
1237: * @return some tab completion info for the specified command.
1238: */
1239: @Override
1240: public TabCompletionInfo getTabCompletionInfo(
1241: SQLCommand command, Point commandPoint) {
1242: return null;
1243: }
1244:
1245: @Override
1246: public CharSequence getHelp() {
1247: return "window resize: notify the sql client of a screen resize";
1248: }
1249:
1250: @Override
1251: public boolean abort() {
1252: return false;// not implemented
1253: }
1254:
1255: @Override
1256: public boolean backgroundProcessSupported() {
1257: return false;
1258: }
1259: }
1260:
1261: /**
1262: * Provide a list of commands executed.
1263: */
1264: private class HistoryCommand implements Command {
1265: @Override
1266: public CharSequence execute(SQLCommand command) {
1267: StringBuilder returnValue = new StringBuilder();
1268: Iterator<SQLCommand> iCommands = commandHistory.iterator();
1269: while (iCommands.hasNext()) {
1270: SQLCommand cmd = iCommands.next();
1271: returnValue.append(cmd.getCommandString());
1272: returnValue.append('\n');
1273: }
1274: return returnValue;
1275: }
1276:
1277: @Override
1278: public CharSequence getCommandString() {
1279: return "history";
1280: }
1281:
1282: /**
1283: * Returns some tab completion info for the specified command.
1284: * @param commandInfo the command lines
1285: * @param commandPoint the cursor position
1286: * @return some tab completion info for the specified command.
1287: */
1288: @Override
1289: public TabCompletionInfo getTabCompletionInfo(
1290: SQLCommand command, Point commandPoint) {
1291: return null;
1292: }
1293:
1294: @Override
1295: public CharSequence getHelp() {
1296: return "Show history of executed statements\n"
1297: + "By using '/<search>' you can search the command history";
1298: }
1299:
1300: @Override
1301: public boolean abort() {
1302: return false;// not implemented
1303: }
1304:
1305: @Override
1306: public boolean backgroundProcessSupported() {
1307: return false;
1308: }
1309: }
1310:
1311: /**
1312: * Exit the client.
1313: */
1314: private class QuitCommand implements Command {
1315: private String cmd;
1316:
1317: public QuitCommand(String cmd) {
1318: this .cmd = cmd;
1319: }
1320:
1321: @Override
1322: public CharSequence execute(SQLCommand command) {
1323: hide(); // quit
1324: return "Application terminated.";
1325: }
1326:
1327: @Override
1328: public CharSequence getHelp() {
1329: return "Quit(exit) the application.";
1330: }
1331:
1332: @Override
1333: public CharSequence getCommandString() {
1334: return cmd;
1335: }
1336:
1337: /**
1338: * Returns some tab completion info for the specified command.
1339: * @param commandInfo the command lines
1340: * @param commandPoint the cursor position
1341: * @return some tab completion info for the specified command.
1342: */
1343: @Override
1344: public TabCompletionInfo getTabCompletionInfo(
1345: SQLCommand command, Point commandPoint) {
1346: return null;
1347: }
1348:
1349: @Override
1350: public boolean abort() {
1351: return false;// not implemented
1352: }
1353:
1354: @Override
1355: public boolean backgroundProcessSupported() {
1356: return false;
1357: }
1358: }
1359:
1360: /**
1361: * Provide help to the user.
1362: */
1363: private class HelpCommand implements Command {
1364: @Override
1365: public CharSequence execute(SQLCommand sqlCommand) {
1366: // the execution of help consists of:
1367: // 1. is general help..
1368: // 2. is detailed help about a specific command
1369: // 3. help -k something --> search help for a specific keyword
1370: String command = sqlCommand.getCommandString().trim();
1371: String cmdString = command.substring("help".length())
1372: .trim();
1373: if (cmdString.endsWith(";")) {
1374: cmdString = cmdString.substring(0,
1375: cmdString.length() - 1);
1376: }
1377: List<CharSequence> availableCommands = new ArrayList<CharSequence>();
1378: if (cmdString.length() > 0) {
1379: if (cmdString.startsWith("-k")) {
1380: String keyword = cmdString.subSequence(
1381: cmdString.indexOf(" "), cmdString.length())
1382: .toString().trim();
1383: StringBuilder returnValue = new StringBuilder();
1384: Iterator<Command> iCommands = commands
1385: .getCommands().iterator();
1386: while (iCommands.hasNext()) {
1387: Command cmd = iCommands.next();
1388: if (cmd.getHelp().toString().indexOf(keyword) >= 0
1389: || cmd.getCommandString().toString()
1390: .indexOf(keyword) >= 0) {
1391: if (returnValue.length() == 0) {
1392: returnValue
1393: .append("See the following commands for more details:\n");
1394: }
1395: returnValue.append(" ");
1396: returnValue.append(cmd.getCommandString());
1397: returnValue.append("\n");
1398: }
1399: }
1400: if (returnValue.length() == 0) {
1401: return "Don't know what you mean by '"
1402: + keyword + "'";
1403: }
1404: return returnValue;
1405: } else {
1406: Iterator<Command> iCommands = commands
1407: .getCommands().iterator();
1408: while (iCommands.hasNext()) {
1409: Command cmd = iCommands.next();
1410: if (cmd.getCommandString().equals(cmdString)) {
1411: return cmd.getCommandString()
1412: + ": "
1413: + cmd
1414: .getHelp()
1415: .toString()
1416: .replaceAll(
1417: "\n",
1418: "\n"
1419: + emptyLine
1420: .substring(
1421: 0,
1422: cmd
1423: .getCommandString()
1424: .length() + 3));
1425: }
1426: }
1427: }
1428: return "Unkown command '" + cmdString + "'";
1429: }
1430: // default print all commands
1431: // TODO iterate
1432: StringBuilder returnValue = new StringBuilder();
1433: returnValue.append("Available key mappings:\n");
1434: int max = 0;
1435: Iterator<KeyAction> iKeyActions = actionKeys.values()
1436: .iterator();
1437: while (iKeyActions.hasNext()) {
1438: max = Math.max(max, iKeyActions.next().getHelp()
1439: .toString().indexOf('\t'));
1440: }
1441: iKeyActions = actionKeys.values().iterator();
1442: while (iKeyActions.hasNext()) {
1443: returnValue.append(" ");
1444: //returnValue.append(iKeyActions.next().getHelp());
1445: String help = iKeyActions.next().getHelp().toString();
1446: int index = help.indexOf('\t');
1447: returnValue.append(help.substring(0, index));
1448: for (int i = index; i < max; i++) {
1449: returnValue.append(' ');
1450: }
1451: returnValue.append(help.substring(index));
1452: returnValue.append('\n');
1453: }
1454: returnValue.append("\n\nAvailable commands:\n");
1455: Iterator<Command> iCommands = commands.getCommands()
1456: .iterator();
1457: while (iCommands.hasNext()) {
1458: Command cmd = iCommands.next();
1459: availableCommands.add(cmd.getCommandString());
1460: }
1461: returnValue.append(toColumns(availableCommands));
1462: String helpHeader = "\nHelp for SQLShell client "
1463: + SQLProperties.getProperty(
1464: SQLProperties.PropertyName.VERSION,
1465: "SVN Snapshot")
1466: + "\n"
1467: + "Here you find a list of available commands. "
1468: + "To get more information about a specific command enter:\n"
1469: + " help command (for example 'help help')\n\n"
1470: + "If the list is not sufficient enough you could try searching help using:\n"
1471: + " help -k searchstring (for example help -k column)\n"
1472: + "This results in a list of commands matching the searchstring\n\n";
1473: returnValue.insert(0, helpHeader);
1474: return returnValue;
1475: }
1476:
1477: @Override
1478: public CharSequence getCommandString() {
1479: return "help";
1480: }
1481:
1482: /**
1483: * Returns some tab completion info for the specified command.
1484: * @param commandInfo the command lines
1485: * @param commandPoint the cursor position
1486: * @return some tab completion info for the specified command.
1487: */
1488: @Override
1489: public TabCompletionInfo getTabCompletionInfo(
1490: SQLCommand command, Point commandPoint) {
1491: return null;
1492: }
1493:
1494: @Override
1495: public CharSequence getHelp() {
1496: return "this command. Please use 'help' to get a list of available commands you can use.";
1497: }
1498:
1499: @Override
1500: public boolean abort() {
1501: return false;// not implemented
1502: }
1503:
1504: @Override
1505: public boolean backgroundProcessSupported() {
1506: return false;
1507: }
1508: }
1509:
1510: /**
1511: * Convert '~/' to the username dir.
1512: * @param fileName the filename to convert
1513: * @return the converted filename
1514: */
1515: private static String toFileName(String fileName) {
1516: if (fileName.startsWith("~/")) {
1517: return System.getProperty("user.home")
1518: + fileName.substring(1);
1519: }
1520: return fileName;
1521: }
1522:
1523: /**
1524: * Writes in/output to a file.
1525: */
1526: private class SpoolCommand implements Command {
1527: private String fileName;
1528:
1529: @Override
1530: public CharSequence execute(SQLCommand cmd) {
1531: String command = cmd.getCommandString();
1532: String nextPart = command.substring("spool".length())
1533: .trim();
1534: if (nextPart.equalsIgnoreCase("off")) {
1535: if (spoolWriter != null) {
1536: try {
1537: spoolWriter.close();
1538: } catch (Exception e) {/*ignore*/
1539: }
1540: spoolWriter = null;
1541: return "Spool closed.";
1542: } else {
1543: return "No spool to close.";
1544: }
1545: } else {
1546: try {
1547: File f = new File(toFileName(nextPart.trim()));
1548: fileName = f.getAbsolutePath();
1549: if ((f.exists() && !f.canWrite())
1550: || (!f.exists() && !f.createNewFile())) {
1551: throw new IllegalStateException(
1552: "Failed to create spool to file: '"
1553: + fileName + "'");
1554: }
1555: spoolWriter = new FileWriter(fileName);
1556: } catch (IOException e) {
1557: throw new IllegalStateException(
1558: "Failed to create spool (" + fileName
1559: + "): " + e.toString(), e);
1560: }
1561: return "Spool to " + fileName + " created.";
1562: }
1563: }
1564:
1565: @Override
1566: public CharSequence getCommandString() {
1567: return "spool";
1568: }
1569:
1570: /**
1571: * Returns some tab completion info for the specified command.
1572: * @param commandInfo the command lines
1573: * @param commandPoint the cursor position
1574: * @return some tab completion info for the specified command.
1575: */
1576: @Override
1577: public TabCompletionInfo getTabCompletionInfo(
1578: SQLCommand command, Point commandPoint) {
1579: return null;
1580: }
1581:
1582: @Override
1583: public CharSequence getHelp() {
1584: return "filename: Spool all output and queries to the specified file\n"
1585: + "off : Stop spooling data to the file.\n"
1586: + "Current status:"
1587: + (spoolWriter != null ? "on, writing to '"
1588: + fileName + "'" : "off");
1589: }
1590:
1591: @Override
1592: public boolean abort() {
1593: return false;// not implemented
1594: }
1595:
1596: @Override
1597: public boolean backgroundProcessSupported() {
1598: return false;
1599: }
1600: }
1601:
1602: private class ExecuteBatchCommand implements Command {
1603: private boolean cancelled;
1604: private Command currentCommand;
1605:
1606: @Override
1607: public CharSequence execute(SQLCommand sqlCommand) {
1608: cancelled = false;
1609: currentCommand = null;
1610: String command = sqlCommand.getCommandString();
1611: // read file from file system and execute
1612: FileInputStream fin = null;
1613: try {
1614: fin = new FileInputStream(toFileName(command
1615: .substring(1)));
1616: output("Reading file: "
1617: + toFileName(command.substring(1)));
1618: BufferedReader reader = new BufferedReader(
1619: new InputStreamReader(fin));
1620: StringBuilder cmd = new StringBuilder();
1621: String line;
1622: while (((line = reader.readLine()) != null)) {
1623: if (cancelled) {
1624: return "Aborted";
1625: }
1626: if (line.startsWith("--")) {
1627: continue;
1628: }
1629: cmd.append(line);
1630: if (line.endsWith(";")) {
1631: // Exec cmd
1632: String commandString = cmd.toString();
1633: currentCommand = createCommand(commandString);
1634: output(commandString);
1635: output(currentCommand.execute(new InputCommand(
1636: commandString)));
1637: cmd = new StringBuilder();
1638: new Thread() {
1639: public void run() {
1640: paint();
1641: }
1642: }.start();
1643: }
1644: }
1645: } catch (IOException e) {
1646: error(e);
1647: } finally {
1648: if (fin != null)
1649: try {
1650: fin.close();
1651: } catch (Exception e) {/*ignore*/
1652: }
1653: if (cancelled) {
1654: return "Execution of file '"
1655: + toFileName(command.substring(1))
1656: + "' aborted....";
1657: }
1658: return "File '" + toFileName(command.substring(1))
1659: + "' executed successfully.";
1660: }
1661: }
1662:
1663: @Override
1664: public CharSequence getCommandString() {
1665: return "@";
1666: }
1667:
1668: /**
1669: * Returns some tab completion info for the specified command.
1670: * @param commandInfo the command lines
1671: * @param commandPoint the cursor position
1672: * @return some tab completion info for the specified command.
1673: */
1674: @Override
1675: public TabCompletionInfo getTabCompletionInfo(
1676: SQLCommand command, Point commandPoint) {
1677: String fileName = command.getCommandString().substring(1)
1678: .trim(); // cutoff '@'
1679: String dirName;
1680: if (fileName.equals("")) {
1681: fileName = ".";
1682: dirName = ".";
1683: } else {
1684: fileName = toFileName(fileName);
1685: if (fileName.indexOf('/') >= 0) {
1686: File file = new File(fileName);
1687: if (file.isDirectory()) {
1688: fileName = "";
1689: dirName = file.getAbsolutePath() + "/";
1690: } else {
1691: fileName = file.getName();
1692: dirName = file.getParent();
1693: }
1694: } else {
1695: dirName = ".";
1696: }
1697: }
1698: return new TabCompletionInfo(
1699: TabCompletionInfo.MatchType.UNKNOWN, Arrays
1700: .asList(new File(dirName).list()), fileName);
1701: }
1702:
1703: @Override
1704: public CharSequence getHelp() {
1705: return "Specify filename to execute a 'batch' command.\n"
1706: + "For example '@mystatements.sql' executes all statements part of the mystatements.sql file in the current directory."
1707: + "Note that all statements must be terminated with ';' (sql statements as well as connect statements or spool)";
1708: }
1709:
1710: @Override
1711: public boolean abort() {
1712: cancelled = true;
1713: if (currentCommand != null) {
1714: currentCommand.abort();
1715: }
1716: return true;
1717: }
1718:
1719: @Override
1720: public boolean backgroundProcessSupported() {
1721: return true;
1722: }
1723: }
1724:
1725: /**
1726: * Command class to execute a 'custom command'.
1727: * this makes it possible to have 'automated' commands executed.
1728: * E.g.:
1729: * executeCommand(new InputCommand("connect"));
1730: * will eventually execute the Connect command.
1731: */
1732: private static class InputCommand extends SQLCommand {
1733: private StringBuilder command;
1734:
1735: public InputCommand(String command) {
1736: this .command = new StringBuilder(command);
1737: }
1738:
1739: public InputCommand(StringBuilder command) {
1740: this .command = command;
1741: }
1742:
1743: @Override
1744: public String getUntrimmedCommandString() {
1745: return command.toString();
1746: }
1747:
1748: @Override
1749: public List<StringBuilder> getEditableLines() {
1750: return Arrays.asList(new StringBuilder[] { command });
1751: }
1752:
1753: @Override
1754: public List<? extends CharSequence> getLines() {
1755: return Arrays.asList(new StringBuilder[] { command });
1756: }
1757: }
1758:
1759: private abstract class CommandThread extends Thread {
1760: private Command cmd;
1761:
1762: public CommandThread(Command cmd) {
1763: this .cmd = cmd;
1764: }
1765:
1766: public final void run() {
1767: showPrompt = false;
1768: try {
1769: execute();
1770: } finally {
1771: showPrompt = true;
1772: paint();
1773: }
1774: }
1775:
1776: abstract void execute();
1777:
1778: Command getCommand() {
1779: return cmd;
1780: }
1781: }
1782:
1783: private class QueryCommand implements Command {
1784:
1785: /**
1786: * Executor for SQL Statements
1787: */
1788: private StatementExecutor statementExecutor;
1789:
1790: @Override
1791: public CharSequence execute(SQLCommand cmd) {
1792: try {
1793: String command = cmd.getCommandString();
1794: if (command.length() > "select".length()
1795: && "select".equalsIgnoreCase(command
1796: .subSequence(0, "create".length())
1797: .toString())) {
1798: return DBConnector.getInstance().getQueryExecutor()
1799: .executeQuery(command);
1800: }
1801: if (statementExecutor == null) {
1802: statementExecutor = new StatementExecutor();
1803: }
1804: return statementExecutor.execute(command);
1805: } catch (SQLException e) {
1806: error(e);
1807: return "";
1808: } catch (IllegalStateException e) {
1809: error(e);
1810: return "";
1811: }
1812: }
1813:
1814: @Override
1815: public CharSequence getCommandString() {
1816: return "";
1817: }
1818:
1819: @Override
1820: public TabCompletionInfo getTabCompletionInfo(
1821: SQLCommand commandInfo, Point commandPoint) {
1822: // TODO call SQLUTil..
1823: return null;
1824: }
1825:
1826: @Override
1827: public CharSequence getHelp() {
1828: return "";
1829: }
1830:
1831: @Override
1832: public boolean abort() {
1833: //DBConnector.getInstance().getStatement().cancel();
1834: output(DBConnector.getInstance().getQueryExecutor()
1835: .cancel());
1836: return true;
1837: }
1838:
1839: @Override
1840: public boolean backgroundProcessSupported() {
1841: return true;
1842: }
1843:
1844: }
1845:
1846: private interface KeyAction {
1847: void execute();
1848:
1849: CharSequence getHelp();
1850: }
1851:
1852: public static void main(String[] args) {
1853: SQLShell shell = new SQLShell();
1854: shell.show();
1855:
1856: //Interpret first argument as a connect argument
1857: if (args.length > 0) {
1858: shell
1859: .executeCommand(new InputCommand("connect "
1860: + args[0]));
1861: }
1862: }
1863: }
|