/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the Kowari Metadata Store.
*
* The Initial Developer of the Original Code is Plugged In Software Pty
* Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
* Contributor(s): N/A.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
* should use the text of this Exhibit A rather than the text found in the
* Original Code Source Code for Your Modifications.]
*/
package org.mulgara.itql;
/**
* Swing based iTQL session command line shell.
*
* @created 2004-01-15
*
* @author Andrew Newman
*
* @version $Revision: 1.8 $
*
* @modified $Date: 2005/01/05 04:58:15 $ by $Author: newmana $
*
* @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A>
*
* @copyright ©2004 <a href="http://www.pisoftware.com/">Plugged In
* Software Pty Ltd</a>
*
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import org.apache.log4j.*;
import org.jrdf.graph.Node;
import org.mulgara.query.Answer;
public class ItqlSessionUI extends JScrollPane implements Runnable,
KeyListener, java.awt.event.MouseListener, ActionListener {
/** Serialization ID */
private static final long serialVersionUID = 6713691768040570333L;
/**
* The logging category to log to
*/
private final static Logger log = Logger.getLogger(ItqlSessionUI.class);
/**
* Used to pipe input.
*/
private InputStream inPipe;
/**
* Used to pipe output.
*/
private OutputStream outPipe;
/**
* The iTQL session to send queries and used to send results.
*/
private ItqlSession itqlSession;
/**
* The list of history items.
*/
private ArrayList<String> history = new ArrayList<String>();
/**
* Current index into the history.
*/
private int historyIndex = 0;
/**
* Current cursor position.
*/
private int cursorPosition = 0;
/**
* The UI widget for displaying all text.
*/
private JTextPane text;
/**
* The default styled document.
*/
private DefaultStyledDocument doc;
/**
* Popup menu for Windows users.
*/
private JPopupMenu popupMenu = new JPopupMenu();
/**
* Whether we are running a command still.
*/
private volatile boolean runningCommand = false;
/**
* Create a new UI representation.
*
* @param newItqlSession the itql session to call when we receive commands and
* when we want to display them.
*/
public ItqlSessionUI(ItqlSession newItqlSession) {
super();
itqlSession = newItqlSession;
doc = new DefaultStyledDocument();
text = new PasteablePane(doc);
text.setFont(new Font("Monospaced", Font.PLAIN, 12));
text.setMargin(new Insets(5, 5, 5, 5));
text.addKeyListener(this);
text.addMouseListener(this);
setViewportView(text);
// Consume middle click to handle properly for Unix/Linux
Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.addAWTEventListener(new MouseListener(), AWTEvent.MOUSE_EVENT_MASK);
// Add popup menu for Windows users.
JMenuItem copyItem = new JMenuItem("Copy");
JMenuItem pasteItem = new JMenuItem("Paste");
popupMenu.add(copyItem);
popupMenu.add(pasteItem);
copyItem.addActionListener(this);
pasteItem.addActionListener(this);
outPipe = new PipedOutputStream();
try {
new PipedInputStream((PipedOutputStream) outPipe);
} catch (IOException e) {
log.error("Error creating input stream", e);
}
PipedOutputStream pout = new PipedOutputStream();
new PrintStream(pout);
try {
inPipe = new PipedInputStream(pout);
} catch (IOException e) {
log.error("Error creating input pipe", e);
}
// Start the inpipe watcher
new Thread(this).start();
requestFocus();
}
public void requestFocus() {
super.requestFocus();
text.requestFocus();
}
/**
* Handle key pressed event.
*
* @param e the key that was pressed.
*/
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
// Enter pressed
case (KeyEvent.VK_ENTER):
if (e.getID() == KeyEvent.KEY_PRESSED) {
if (!runningCommand) {
enterPressed();
cursorPosition = textLength();
text.setCaretPosition(cursorPosition);
}
}
e.consume();
text.repaint();
break;
// Up history
case (KeyEvent.VK_UP):
if (e.getID() == KeyEvent.KEY_PRESSED) {
historyUp();
}
e.consume();
break;
// Down history
case (KeyEvent.VK_DOWN):
if (e.getID() == KeyEvent.KEY_PRESSED) {
historyDown();
}
e.consume();
break;
// Left or delete.
case (KeyEvent.VK_LEFT):
case (KeyEvent.VK_DELETE):
if (text.getCaretPosition() <= cursorPosition) {
e.consume();
}
break;
// Go right.
case (KeyEvent.VK_RIGHT):
if (text.getCaretPosition() < cursorPosition) {
// move caret first!
}
text.repaint();
break;
// Control-A go to start of line.
case (KeyEvent.VK_A):
if ((e.getModifiers() & InputEvent.CTRL_MASK) > 0) {
text.setCaretPosition(cursorPosition);
e.consume();
}
break;
// Control-C copy the text.
case (KeyEvent.VK_C):
if (text.getSelectedText() == null) {
text.copy();
e.consume();
}
break;
// Control-E go to end of line.
case (KeyEvent.VK_E):
if ((e.getModifiers() & InputEvent.CTRL_MASK) > 0) {
text.setCaretPosition(textLength());
e.consume();
}
break;
// Control-U remove line
case (KeyEvent.VK_U):
if ((e.getModifiers() & InputEvent.CTRL_MASK) > 0) {
replaceText("", cursorPosition, textLength());
historyIndex = 0;
e.consume();
}
break;
// Home go to start of line
case (KeyEvent.VK_HOME):
text.setCaretPosition(cursorPosition);
e.consume();
break;
// Go to end of line
case (KeyEvent.VK_END):
text.setCaretPosition(textLength());
e.consume();
break;
// Ignore modifiers
case (KeyEvent.VK_ALT):
case (KeyEvent.VK_CAPS_LOCK):
case (KeyEvent.VK_CONTROL):
case (KeyEvent.VK_ESCAPE):
case (KeyEvent.VK_F1):
case (KeyEvent.VK_F2):
case (KeyEvent.VK_F3):
case (KeyEvent.VK_F4):
case (KeyEvent.VK_F5):
case (KeyEvent.VK_F6):
case (KeyEvent.VK_F7):
case (KeyEvent.VK_F8):
case (KeyEvent.VK_F9):
case (KeyEvent.VK_F10):
case (KeyEvent.VK_F11):
case (KeyEvent.VK_F12):
case (KeyEvent.VK_INSERT):
case (KeyEvent.VK_META):
case (KeyEvent.VK_PAUSE):
case (KeyEvent.VK_PRINTSCREEN):
case (KeyEvent.VK_SHIFT):
case (KeyEvent.VK_SCROLL_LOCK):
// Do nothing.
break;
// Handle normal characters
default:
if ( (e.getModifiers() & (InputEvent.ALT_MASK | InputEvent.CTRL_MASK |
InputEvent.META_MASK)) == 0) {
if (text.getCaretPosition() < cursorPosition) {
text.setCaretPosition(textLength());
}
text.repaint();
}
// Handle back space - don't let it go too far back.
if (e.paramString().indexOf("Backspace") != -1) {
if (text.getCaretPosition() <= cursorPosition) {
e.consume();
break;
}
}
break;
}
}
public void keyTyped(KeyEvent e) {
if (e.paramString().indexOf("Backspace") != -1) {
if (text.getCaretPosition() <= cursorPosition) {
e.consume();
}
}
}
public void keyReleased(KeyEvent e) {
// Do nothing.
}
public void mouseClicked(MouseEvent e) {
// Do nothing.
}
public void mouseEntered(MouseEvent e) {
// Do nothing.
}
public void mouseExited(MouseEvent e) {
// Do nothing.
}
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
popupMenu.show(e.getComponent(), e.getX(), e.getY());
}
}
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
popupMenu.show(e.getComponent(), e.getX(), e.getY());
}
}
public void actionPerformed(ActionEvent event) {
String eventOccurred = event.getActionCommand();
if (eventOccurred.equals("Copy")) {
text.copy();
} else if (eventOccurred.equals("Paste")) {
text.paste();
}
}
/**
* Returns the length of the current text buffer.
*
* @return length of the current text buffer.
*/
private int textLength() {
return text.getDocument().getLength();
}
/**
* Replaces the given string to a position in the currently displayed line.
*
* @param newString the string to add.
* @param start the starting position.
* @param end the end position.
*/
private void replaceText(String newString, int start, int end) {
text.select(start, end);
text.replaceSelection(newString);
}
/**
* When the enter key has been pressed process the current command.
*/
private void enterPressed() {
String command = getCommand();
// Create null command.
if (command.length() != 0) {
// Put the command at the end of the array.
history.add(command);
command = command + System.getProperty("line.separator");
// If the array gets too large remove the last entry.
if (history.size() > 30) {
history.remove(0);
}
// Indicate that we are running a command.
runningCommand = true;
// Create a new thread and start it.
ExecutionThread execThread = new ExecutionThread("execThread", command);
execThread.start();
} else {
// We've just hit enter so print the prompt.
printPrompt();
}
}
/**
* Prints out the prompt.
*/
public void printPrompt() {
println();
print(ItqlSession.PROMPT);
historyIndex = 0;
text.repaint();
}
/**
* Returns the current command.
*
* @return the current command.
*/
private String getCommand() {
String command = "";
try {
command = text.getText(cursorPosition, textLength() - cursorPosition);
} catch (BadLocationException e) {
log.error("Failed to get text command at position: " + cursorPosition,
e);
}
return command;
}
/**
* Display the next command in the history buffer.
*/
private void historyUp() {
// Ensure there's a history and that the index never goes above the array
// size.
if ((history.size() != 0) && (historyIndex != history.size())) {
historyIndex++;
displayHistoryLine();
}
}
/**
* Display the previous command in the history buffer.
*/
private void historyDown() {
// Ensure there's a history and that the index is initially above 1.
if ((history.size() != 0) && (historyIndex > 1)) {
historyIndex--;
displayHistoryLine();
}
}
/**
* Displays the history line to the screen.
*/
private void displayHistoryLine() {
String showline = (String) history.get(history.size() - historyIndex);
replaceText(showline, cursorPosition, textLength());
text.setCaretPosition(textLength());
text.repaint();
}
/**
* Prints a message to the UI with a line separator.
*
* @param message the message to display.
*/
public void println(String message) {
print(message + System.getProperty("line.separator"));
text.repaint();
}
/**
* Prints empty line.
*/
public void println() {
print(System.getProperty("line.separator"));
text.repaint();
}
/**
* Prints a message to the UI.
*
* @param message the message to display.
*/
public void print(final String message) {
invokeAndWait(new Runnable() {
public void run() {
append(message);
cursorPosition = textLength();
text.setCaretPosition(cursorPosition);
}
});
}
/**
* Print out an error message to the UI.
*
* @param errorMessage the error message to display.
*/
public void error(String errorMessage) {
print(errorMessage, Color.red);
}
/**
* Print out the message with the given color using the current font.
*
* @param message the message to display.
* @param color the color to use.
*/
public void print(String message, Color color) {
print(message, null, color);
}
/**
* Print out the message with the given font and colour. Uses invoke and
* wait.
*
* @param message the message to display.
* @param font the font to use.
* @param color the color to use.
*/
public void print(final String message, final Font font, final Color color) {
invokeAndWait(new Runnable() {
public void run() {
try {
AttributeSet oldStyle = text.getCharacterAttributes();
setStyle(font, color);
append(message);
cursorPosition = textLength();
text.setCaretPosition(cursorPosition);
text.setCharacterAttributes(oldStyle, true);
} catch (Exception e) {
log.error("Error when printing: " + message, e);
}
}
});
}
/**
* Sets the new style of a font and color to the text.
*
* @param font the new font.
* @param color the new color.
* @return the attributes of the given font and color.
*/
private AttributeSet setStyle(Font font, Color color) {
MutableAttributeSet attr = new SimpleAttributeSet();
StyleConstants.setForeground(attr, color);
// Don't set if null
if (font != null) {
if (font.isBold()) {
StyleConstants.setBold(attr, true);
} else {
StyleConstants.setBold(attr, false);
}
StyleConstants.setFontFamily(attr, font.getFamily());
StyleConstants.setFontSize(attr, font.getSize());
}
text.setCharacterAttributes(attr, false);
return text.getCharacterAttributes();
}
/**
* Append the given string to the existing string.
*
* @param newString the string to append to.
*/
private void append(String newString) {
int length = textLength();
text.select(length, length);
text.replaceSelection(newString);
}
/**
* Thread that runs while waiting for input.
*/
public void run() {
try {
byte[] buffer = new byte[255];
int read;
while ((read = inPipe.read(buffer)) != -1) {
print(new String(buffer, 0, read));
}
} catch (IOException e) {
log.error("Error reading input", e);
}
}
/**
* If not in the event thread run via SwingUtilities.invokeAndWait()
*/
private void invokeAndWait(Runnable runnable) {
if (!SwingUtilities.isEventDispatchThread()) {
try {
SwingUtilities.invokeAndWait(runnable);
} catch (Exception e) {
log.error("Error while executing invoke and wait", e);
}
} else {
runnable.run();
}
}
/**
* Extension to JTextPane to put all pastes at the end of the command line.
*/
@SuppressWarnings("serial")
class PasteablePane extends JTextPane {
public PasteablePane(StyledDocument doc) {
super(doc);
}
public void paste() {
super.paste();
}
}
class MouseListener implements AWTEventListener {
public void eventDispatched(AWTEvent event) {
MouseEvent me = (MouseEvent) event;
if (me.getButton() == MouseEvent.BUTTON2) {
me.consume();
}
}
}
/**
* Executes the command in a separate thread and display the results.
*/
class ExecutionThread extends Thread {
/**
* The command to execute.
*/
private String command;
/**
* Create a new execution thread.
*
* @param threadName the name of the thread.
* @param newCommand the iTQL command to execute.
*/
public ExecutionThread(String threadName, String newCommand) {
super(threadName);
command = newCommand;
}
/**
* Run the command and display answer results.
*/
public void run() {
itqlSession.executeCommand(command);
println();
java.util.List<Answer> answers = itqlSession.getLastAnswers();
java.util.List<String> messages = itqlSession.getLastMessages();
int answerIndex = 0;
String lastMessage;
while (answerIndex < answers.size()) {
lastMessage = messages.get(answerIndex);
try {
// Assume the same number of answers and messages
Answer answer = answers.get(answerIndex);
// If there's more than one answer print a heading.
if (answers.size() > 1) {
println();
// If there's more than one answer add an extra line before the
// heading.
print("Executing Query " + (answerIndex+1),
new Font("Monospaced", Font.BOLD, 12), Color.BLACK);
println();
}
// print the results
if (answer != null) {
boolean hasAnswers = true;
answer.beforeFirst();
long rowCount = 0;
if (answer.isUnconstrained()) {
println("[ true ]");
rowCount = 1;
} else {
if (!answer.next()) {
print("No results returned.",
new Font("Monospaced", Font.BOLD, 12), Color.BLACK);
hasAnswers = false;
} else {
do {
rowCount++;
print("[ ");
for (int index = 0; index < answer.getNumberOfVariables();
index++) {
Object object = answer.getObject(index);
assert(object instanceof Answer) ||
(object instanceof Node ) ||
(object == null);
print(String.valueOf(object));
if (index < (answer.getNumberOfVariables() - 1)) {
print(", ");
}
}
println(" ]");
}
while (answer.next());
}
}
if (hasAnswers) {
print(rowCount + " rows returned.",
new Font("Monospaced", Font.BOLD, 12), Color.BLACK);
}
answer.close();
}
} catch (Exception te) {
// Failed to iterate over or retrieve the answer.
log.fatal("Failed to retrieve or iterate over answer", te);
error("Failed to get answer");
}
if ((lastMessage != null) && (!lastMessage.equals(""))) {
print(lastMessage, new Font("Monospaced", Font.BOLD, 12),
Color.BLACK);
}
// If there's more than one answer add a new line.
if (answers.size() > 1) {
println();
}
// Increment index
answerIndex++;
}
// Signal that the command has finished and display prompt
runningCommand = false;
printPrompt();
}
};
}