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