1 import javax.swing.JFrame; 2 import javax.swing.JTextArea; 3 import javax.swing.JPanel; 4 import javax.swing.JScrollPane; 5 import javax.swing.JButton; 6 import javax.swing.JComponent; 7 import javax.swing.JFileChooser; 8 import javax.swing.JOptionPane; 9 import javax.swing.text.Document; 10 import javax.swing.event.DocumentListener; 11 import javax.swing.event.DocumentEvent; 12 13 import java.awt.FlowLayout; 14 import java.awt.Graphics; 15 import java.awt.Color; 16 import java.awt.Font; 17 import java.awt.FontMetrics; 18 import java.awt.Polygon; 19 import java.awt.Rectangle; 20 import java.awt.Dimension; 21 import java.awt.event.ActionListener; 22 import java.awt.event.ActionEvent; 23 24 import java.io.File; 25 import java.io.InputStream; 26 import java.io.FileInputStream; 27 import java.io.FileOutputStream; 28 import java.io.IOException; 29 30 import drafts.com.sun.star.script.framework.runtime.XScriptContext; 31 import bsh.Interpreter; 32 33 public class OOBeanShellDebugger implements OOScriptDebugger, ActionListener, DocumentListener { 34 35 private JFrame frame; 36 private JTextArea ta; 37 private GlyphGutter gg; 38 private XScriptContext context; 39 private int currentPosition = -1; 40 private int linecount; 41 private Interpreter sessionInterpreter; 42 private Thread execThread = null; 43 private String filename = null; 44 45 /* Entry point for script execution */ 46 public void go(XScriptContext context, String filename) { 47 if (filename != null && filename != "") { 48 try { 49 FileInputStream fis = new FileInputStream(filename); 50 this.filename = filename; 51 go(context, fis); 52 } 53 catch (IOException ioe) { 54 JOptionPane.showMessageDialog(frame, 55 "Error loading file: " + ioe.getMessage(), 56 "Error", JOptionPane.ERROR_MESSAGE); 57 } 58 } 59 } 60 61 /* Entry point for script execution */ 62 public void go(XScriptContext context, InputStream in) { 63 this.context = context; 64 initUI(); 65 66 if (in != null) { 67 try { 68 loadFile(in); 69 } 70 catch (IOException ioe) { 71 JOptionPane.showMessageDialog(frame, 72 "Error loading stream: " + ioe.getMessage(), 73 "Error", JOptionPane.ERROR_MESSAGE); 74 } 75 } 76 } 77 78 public void loadFile(InputStream in) throws IOException { 79 80 /* Remove ourselves as a DocumentListener while loading the file 81 so we don't get a storm of DocumentEvents during loading */ 82 ta.getDocument().removeDocumentListener(this); 83 84 byte[] contents = new byte[1024]; 85 int len = 0, pos = 0; 86 87 while ((len = in.read(contents, 0, 1024)) != -1) { 88 ta.insert(new String(contents, 0, len), pos); 89 pos += len; 90 } 91 92 try { 93 in.close(); 94 } 95 catch (IOException ignore) { 96 } 97 98 /* Update the GlyphGutter and add back the DocumentListener */ 99 gg.update(); 100 ta.getDocument().addDocumentListener(this); 101 } 102 103 private void initUI() { 104 frame = new JFrame("BeanShell Debug Window"); 105 ta = new JTextArea(); 106 ta.setRows(15); 107 ta.setColumns(40); 108 ta.setLineWrap(false); 109 linecount = ta.getLineCount(); 110 111 gg = new GlyphGutter(this); 112 113 final JScrollPane sp = new JScrollPane(); 114 sp.setViewportView(ta); 115 sp.setRowHeaderView(gg); 116 117 ta.getDocument().addDocumentListener(this); 118 String[] labels = {"Run", "Clear", "Save", "Close"}; 119 JPanel p = new JPanel(); 120 p.setLayout(new FlowLayout()); 121 122 for (int i = 0; i < labels.length; i++) { 123 JButton b = new JButton(labels[i]); 124 b.addActionListener(this); 125 p.add(b); 126 127 if (labels[i].equals("Save") && filename == null) { 128 b.setEnabled(false); 129 } 130 } 131 132 frame.getContentPane().add(sp, "Center"); 133 frame.getContentPane().add(p, "South"); 134 frame.pack(); 135 frame.show(); 136 } 137 138 /* Implementation of DocumentListener interface */ 139 public void insertUpdate(DocumentEvent e) { 140 doChanged(e); 141 } 142 143 public void removeUpdate(DocumentEvent e) { 144 doChanged(e); 145 } 146 147 public void changedUpdate(DocumentEvent e) { 148 doChanged(e); 149 } 150 151 /* If the number of lines in the JTextArea has changed then update the 152 GlyphGutter */ 153 public void doChanged(DocumentEvent e) { 154 if (linecount != ta.getLineCount()) { 155 gg.update(); 156 linecount = ta.getLineCount(); 157 } 158 } 159 160 private void startExecution() { 161 execThread = new Thread() { 162 public void run() { 163 Interpreter interpreter = new Interpreter(); 164 interpreter.getNameSpace().clear(); 165 166 // reset position and repaint gutter so no red arrow appears 167 currentPosition = -1; 168 gg.repaint(); 169 170 try { 171 interpreter.set("context", context); 172 interpreter.eval(ta.getText()); 173 } 174 catch (bsh.EvalError err) { 175 currentPosition = err.getErrorLineNumber() - 1; 176 try { 177 // scroll to line of the error 178 int line = ta.getLineStartOffset(currentPosition); 179 Rectangle rect = ta.modelToView(line); 180 ta.scrollRectToVisible(rect); 181 } 182 catch (Exception e) { 183 // couldn't scroll to line, do nothing 184 } 185 gg.repaint(); 186 187 JOptionPane.showMessageDialog(frame, "Error at line " + 188 String.valueOf(err.getErrorLineNumber()) + 189 "\n\n: " + err.getErrorText(), 190 "Error", JOptionPane.ERROR_MESSAGE); 191 } 192 catch (Exception e) { 193 JOptionPane.showMessageDialog(frame, 194 "Error: " + e.getMessage(), 195 "Error", JOptionPane.ERROR_MESSAGE); 196 } 197 } 198 }; 199 execThread.start(); 200 } 201 202 private void promptForSaveName() { 203 JFileChooser chooser = new JFileChooser(); 204 chooser.setFileFilter(new javax.swing.filechooser.FileFilter() { 205 public boolean accept(File f) { 206 if (f.isDirectory() || f.getName().endsWith(".bsh")) { 207 return true; 208 } 209 return false; 210 } 211 212 public String getDescription() { 213 return ("BeanShell files: *.bsh"); 214 } 215 }); 216 217 int ret = chooser.showSaveDialog(frame); 218 219 if (ret == JFileChooser.APPROVE_OPTION) { 220 filename = chooser.getSelectedFile().getAbsolutePath(); 221 if (!filename.endsWith(".bsh")) { 222 filename += ".bsh"; 223 } 224 } 225 226 } 227 228 private void saveTextArea() { 229 if (filename == null) { 230 promptForSaveName(); 231 } 232 233 FileOutputStream fos = null; 234 if (filename != null) { 235 try { 236 File f = new File(filename); 237 fos = new FileOutputStream(f); 238 String s = ta.getText(); 239 fos.write(s.getBytes(), 0, s.length()); 240 } 241 catch (IOException ioe) { 242 JOptionPane.showMessageDialog(frame, 243 "Error saving file: " + ioe.getMessage(), 244 "Error", JOptionPane.ERROR_MESSAGE); 245 } 246 finally { 247 if (fos != null) { 248 try { 249 fos.close(); 250 } 251 catch (IOException ignore) { 252 } 253 } 254 } 255 } 256 } 257 258 public void actionPerformed(ActionEvent e) { 259 if (e.getActionCommand().equals("Run")) { 260 startExecution(); 261 } 262 else if (e.getActionCommand().equals("Close")) { 263 frame.dispose(); 264 } 265 else if (e.getActionCommand().equals("Save")) { 266 saveTextArea(); 267 } 268 else if (e.getActionCommand().equals("Clear")) { 269 ta.setText(""); 270 } 271 } 272 273 public JTextArea getTextArea() { 274 return ta; 275 } 276 277 public int getCurrentPosition() { 278 return currentPosition; 279 } 280 } 281 282 class GlyphGutter extends JComponent { 283 284 private OOBeanShellDebugger debugger; 285 private final String DUMMY_STRING = "99"; 286 287 GlyphGutter(OOBeanShellDebugger debugger) { 288 this.debugger = debugger; 289 update(); 290 } 291 292 public void update() { 293 JTextArea textArea = debugger.getTextArea(); 294 Font font = textArea.getFont(); 295 setFont(font); 296 297 FontMetrics metrics = getFontMetrics(font); 298 int h = metrics.getHeight(); 299 int lineCount = textArea.getLineCount() + 1; 300 301 String dummy = Integer.toString(lineCount); 302 if (dummy.length() < 2) { 303 dummy = DUMMY_STRING; 304 } 305 306 Dimension d = new Dimension(); 307 d.width = metrics.stringWidth(dummy) + 16; 308 d.height = lineCount * h + 100; 309 setPreferredSize(d); 310 setSize(d); 311 } 312 313 public void paintComponent(Graphics g) { 314 JTextArea textArea = debugger.getTextArea(); 315 316 Font font = textArea.getFont(); 317 g.setFont(font); 318 319 FontMetrics metrics = getFontMetrics(font); 320 Rectangle clip = g.getClipBounds(); 321 322 g.setColor(getBackground()); 323 g.fillRect(clip.x, clip.y, clip.width, clip.height); 324 325 int ascent = metrics.getMaxAscent(); 326 int h = metrics.getHeight(); 327 int lineCount = textArea.getLineCount() + 1; 328 329 int startLine = clip.y / h; 330 int endLine = (clip.y + clip.height) / h + 1; 331 int width = getWidth(); 332 if (endLine > lineCount) { 333 endLine = lineCount; 334 } 335 336 for (int i = startLine; i < endLine; i++) { 337 String text; 338 text = Integer.toString(i + 1) + " "; 339 int w = metrics.stringWidth(text); 340 int y = i * h; 341 g.setColor(Color.blue); 342 g.drawString(text, 0, y + ascent); 343 int x = width - ascent; 344 345 // if currentPosition is not -1 then a red arrow will be drawn 346 if (i == debugger.getCurrentPosition()) { 347 drawArrow(g, ascent, x, y); 348 } 349 } 350 } 351 352 private void drawArrow(Graphics g, int ascent, int x, int y) { 353 Polygon arrow = new Polygon(); 354 int dx = x; 355 y += ascent - 10; 356 int dy = y; 357 arrow.addPoint(dx, dy + 3); 358 arrow.addPoint(dx + 5, dy + 3); 359 for (x = dx + 5; x <= dx + 10; x++, y++) { 360 arrow.addPoint(x, y); 361 } 362 for (x = dx + 9; x >= dx + 5; x--, y++) { 363 arrow.addPoint(x, y); 364 } 365 arrow.addPoint(dx + 5, dy + 7); 366 arrow.addPoint(dx, dy + 7); 367 368 g.setColor(Color.red); 369 g.fillPolygon(arrow); 370 g.setColor(Color.black); 371 g.drawPolygon(arrow); 372 } 373 }; 374 375