blob: 9c2579d2c00a689d576cdc7027313a3a3084cb69 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*
*************************************************************/
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.text.Document;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import drafts.com.sun.star.script.framework.runtime.XScriptContext;
import bsh.Interpreter;
public class OOBeanShellDebugger implements OOScriptDebugger, ActionListener, DocumentListener {
private JFrame frame;
private JTextArea ta;
private GlyphGutter gg;
private XScriptContext context;
private int currentPosition = -1;
private int linecount;
private Interpreter sessionInterpreter;
private Thread execThread = null;
private String filename = null;
/* Entry point for script execution */
public void go(XScriptContext context, String filename) {
if (filename != null && filename != "") {
try {
FileInputStream fis = new FileInputStream(filename);
this.filename = filename;
go(context, fis);
}
catch (IOException ioe) {
JOptionPane.showMessageDialog(frame,
"Error loading file: " + ioe.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
}
}
/* Entry point for script execution */
public void go(XScriptContext context, InputStream in) {
this.context = context;
initUI();
if (in != null) {
try {
loadFile(in);
}
catch (IOException ioe) {
JOptionPane.showMessageDialog(frame,
"Error loading stream: " + ioe.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
}
}
public void loadFile(InputStream in) throws IOException {
/* Remove ourselves as a DocumentListener while loading the file
so we don't get a storm of DocumentEvents during loading */
ta.getDocument().removeDocumentListener(this);
byte[] contents = new byte[1024];
int len = 0, pos = 0;
while ((len = in.read(contents, 0, 1024)) != -1) {
ta.insert(new String(contents, 0, len), pos);
pos += len;
}
try {
in.close();
}
catch (IOException ignore) {
}
/* Update the GlyphGutter and add back the DocumentListener */
gg.update();
ta.getDocument().addDocumentListener(this);
}
private void initUI() {
frame = new JFrame("BeanShell Debug Window");
ta = new JTextArea();
ta.setRows(15);
ta.setColumns(40);
ta.setLineWrap(false);
linecount = ta.getLineCount();
gg = new GlyphGutter(this);
final JScrollPane sp = new JScrollPane();
sp.setViewportView(ta);
sp.setRowHeaderView(gg);
ta.getDocument().addDocumentListener(this);
String[] labels = {"Run", "Clear", "Save", "Close"};
JPanel p = new JPanel();
p.setLayout(new FlowLayout());
for (int i = 0; i < labels.length; i++) {
JButton b = new JButton(labels[i]);
b.addActionListener(this);
p.add(b);
if (labels[i].equals("Save") && filename == null) {
b.setEnabled(false);
}
}
frame.getContentPane().add(sp, "Center");
frame.getContentPane().add(p, "South");
frame.pack();
frame.show();
}
/* Implementation of DocumentListener interface */
public void insertUpdate(DocumentEvent e) {
doChanged(e);
}
public void removeUpdate(DocumentEvent e) {
doChanged(e);
}
public void changedUpdate(DocumentEvent e) {
doChanged(e);
}
/* If the number of lines in the JTextArea has changed then update the
GlyphGutter */
public void doChanged(DocumentEvent e) {
if (linecount != ta.getLineCount()) {
gg.update();
linecount = ta.getLineCount();
}
}
private void startExecution() {
execThread = new Thread() {
public void run() {
Interpreter interpreter = new Interpreter();
interpreter.getNameSpace().clear();
// reset position and repaint gutter so no red arrow appears
currentPosition = -1;
gg.repaint();
try {
interpreter.set("context", context);
interpreter.eval(ta.getText());
}
catch (bsh.EvalError err) {
currentPosition = err.getErrorLineNumber() - 1;
try {
// scroll to line of the error
int line = ta.getLineStartOffset(currentPosition);
Rectangle rect = ta.modelToView(line);
ta.scrollRectToVisible(rect);
}
catch (Exception e) {
// couldn't scroll to line, do nothing
}
gg.repaint();
JOptionPane.showMessageDialog(frame, "Error at line " +
String.valueOf(err.getErrorLineNumber()) +
"\n\n: " + err.getErrorText(),
"Error", JOptionPane.ERROR_MESSAGE);
}
catch (Exception e) {
JOptionPane.showMessageDialog(frame,
"Error: " + e.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
}
};
execThread.start();
}
private void promptForSaveName() {
JFileChooser chooser = new JFileChooser();
chooser.setFileFilter(new javax.swing.filechooser.FileFilter() {
public boolean accept(File f) {
if (f.isDirectory() || f.getName().endsWith(".bsh")) {
return true;
}
return false;
}
public String getDescription() {
return ("BeanShell files: *.bsh");
}
});
int ret = chooser.showSaveDialog(frame);
if (ret == JFileChooser.APPROVE_OPTION) {
filename = chooser.getSelectedFile().getAbsolutePath();
if (!filename.endsWith(".bsh")) {
filename += ".bsh";
}
}
}
private void saveTextArea() {
if (filename == null) {
promptForSaveName();
}
FileOutputStream fos = null;
if (filename != null) {
try {
File f = new File(filename);
fos = new FileOutputStream(f);
String s = ta.getText();
fos.write(s.getBytes(), 0, s.length());
}
catch (IOException ioe) {
JOptionPane.showMessageDialog(frame,
"Error saving file: " + ioe.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
finally {
if (fos != null) {
try {
fos.close();
}
catch (IOException ignore) {
}
}
}
}
}
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("Run")) {
startExecution();
}
else if (e.getActionCommand().equals("Close")) {
frame.dispose();
}
else if (e.getActionCommand().equals("Save")) {
saveTextArea();
}
else if (e.getActionCommand().equals("Clear")) {
ta.setText("");
}
}
public JTextArea getTextArea() {
return ta;
}
public int getCurrentPosition() {
return currentPosition;
}
}
class GlyphGutter extends JComponent {
private OOBeanShellDebugger debugger;
private final String DUMMY_STRING = "99";
GlyphGutter(OOBeanShellDebugger debugger) {
this.debugger = debugger;
update();
}
public void update() {
JTextArea textArea = debugger.getTextArea();
Font font = textArea.getFont();
setFont(font);
FontMetrics metrics = getFontMetrics(font);
int h = metrics.getHeight();
int lineCount = textArea.getLineCount() + 1;
String dummy = Integer.toString(lineCount);
if (dummy.length() < 2) {
dummy = DUMMY_STRING;
}
Dimension d = new Dimension();
d.width = metrics.stringWidth(dummy) + 16;
d.height = lineCount * h + 100;
setPreferredSize(d);
setSize(d);
}
public void paintComponent(Graphics g) {
JTextArea textArea = debugger.getTextArea();
Font font = textArea.getFont();
g.setFont(font);
FontMetrics metrics = getFontMetrics(font);
Rectangle clip = g.getClipBounds();
g.setColor(getBackground());
g.fillRect(clip.x, clip.y, clip.width, clip.height);
int ascent = metrics.getMaxAscent();
int h = metrics.getHeight();
int lineCount = textArea.getLineCount() + 1;
int startLine = clip.y / h;
int endLine = (clip.y + clip.height) / h + 1;
int width = getWidth();
if (endLine > lineCount) {
endLine = lineCount;
}
for (int i = startLine; i < endLine; i++) {
String text;
text = Integer.toString(i + 1) + " ";
int w = metrics.stringWidth(text);
int y = i * h;
g.setColor(Color.blue);
g.drawString(text, 0, y + ascent);
int x = width - ascent;
// if currentPosition is not -1 then a red arrow will be drawn
if (i == debugger.getCurrentPosition()) {
drawArrow(g, ascent, x, y);
}
}
}
private void drawArrow(Graphics g, int ascent, int x, int y) {
Polygon arrow = new Polygon();
int dx = x;
y += ascent - 10;
int dy = y;
arrow.addPoint(dx, dy + 3);
arrow.addPoint(dx + 5, dy + 3);
for (x = dx + 5; x <= dx + 10; x++, y++) {
arrow.addPoint(x, y);
}
for (x = dx + 9; x >= dx + 5; x--, y++) {
arrow.addPoint(x, y);
}
arrow.addPoint(dx + 5, dy + 7);
arrow.addPoint(dx, dy + 7);
g.setColor(Color.red);
g.fillPolygon(arrow);
g.setColor(Color.black);
g.drawPolygon(arrow);
}
};