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