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