1 /*************************************************************************
2 *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26 ************************************************************************/
27 package com.sun.star.script.framework.provider.beanshell;
28 
29 import javax.swing.JComponent;
30 import javax.swing.JFrame;
31 import javax.swing.JPanel;
32 import javax.swing.JButton;
33 import javax.swing.JOptionPane;
34 
35 import java.awt.FlowLayout;
36 import java.awt.event.ActionListener;
37 import java.awt.event.ActionEvent;
38 import java.awt.event.WindowAdapter;
39 import java.awt.event.WindowEvent;
40 
41 import java.io.InputStream;
42 import java.io.OutputStream;
43 import java.io.IOException;
44 import java.net.URL;
45 import java.util.Map;
46 import java.util.HashMap;
47 
48 import com.sun.star.script.provider.XScriptContext;
49 import com.sun.star.script.framework.provider.ScriptEditor;
50 import com.sun.star.script.framework.provider.SwingInvocation;
51 import com.sun.star.script.framework.container.ScriptMetaData;
52 import com.sun.star.script.framework.provider.ClassLoaderFactory;
53 
54 public class ScriptEditorForBeanShell
55     implements ScriptEditor, ActionListener
56 {
57     private JFrame frame;
58     private String filename;
59 
60     private ScriptSourceModel model;
61     private ScriptSourceView view;
62 
63     private XScriptContext context;
64     private URL scriptURL = null;
65     private ClassLoader  cl = null;
66 
67     // global ScriptEditorForBeanShell returned for getEditor() calls
68     private static ScriptEditorForBeanShell theScriptEditorForBeanShell;
69 
70     // global list of ScriptEditors, key is URL of file being edited
71     private static Map BEING_EDITED = new HashMap();
72 
73     // template for new BeanShell scripts
74     private static String BSHTEMPLATE;
75 
76     // try to load the template for BeanShell scripts
77     static {
78         try {
79             URL url =
80                 ScriptEditorForBeanShell.class.getResource("template.bsh");
81 
82             InputStream in = url.openStream();
83             StringBuffer buf = new StringBuffer();
84             byte[] b = new byte[1024];
85             int len = 0;
86 
87             while ((len = in.read(b)) != -1) {
88                 buf.append(new String(b, 0, len));
89             }
90 
91             in.close();
92 
93             BSHTEMPLATE = buf.toString();
94         }
95         catch (IOException ioe) {
96             BSHTEMPLATE = "// BeanShell script";
97         }
98         catch (Exception e) {
99             BSHTEMPLATE = "// BeanShell script";
100         }
101     }
102 
103     /**
104      *  Returns the global ScriptEditorForBeanShell instance.
105      */
106     public static ScriptEditorForBeanShell getEditor()
107     {
108         if (theScriptEditorForBeanShell == null)
109         {
110             synchronized(ScriptEditorForBeanShell.class)
111             {
112                 if (theScriptEditorForBeanShell == null)
113                 {
114                     theScriptEditorForBeanShell =
115                         new ScriptEditorForBeanShell();
116                 }
117             }
118         }
119         return theScriptEditorForBeanShell;
120     }
121 
122     /**
123      *  Get the ScriptEditorForBeanShell instance for this URL
124      *
125      * @param  url         The URL of the script source file
126      *
127      * @return             The ScriptEditorForBeanShell associated with
128      *                     the given URL if one exists, otherwise null.
129      */
130     public static ScriptEditorForBeanShell getEditor(URL url)
131     {
132         synchronized (BEING_EDITED) {
133             return (ScriptEditorForBeanShell)BEING_EDITED.get(url);
134         }
135     }
136 
137     /**
138      *  Returns whether or not the script source being edited in this
139      *  ScriptEditorForBeanShell has been modified
140      */
141     public boolean isModified()
142     {
143         return view.isModified();
144     }
145 
146     /**
147      *  Returns the text being displayed in this ScriptEditorForBeanShell
148      *
149      *  @return            The text displayed in this ScriptEditorForBeanShell
150      */
151     public String getText()
152     {
153         return view.getText();
154     }
155 
156     /**
157      *  Returns the template text for BeanShell scripts
158      *
159      *  @return            The template text for BeanShell scripts
160      */
161     public String getTemplate() {
162         return BSHTEMPLATE;
163     }
164 
165     /**
166      *  Returns the default extension for BeanShell scripts
167      *
168      *  @return            The default extension for BeanShell scripts
169      */
170     public String getExtension() {
171         return "bsh";
172     }
173 
174 
175     /**
176      *  Indicates the line where error occured
177      *
178      */
179     public void indicateErrorLine( int lineNum )
180     {
181         model.indicateErrorLine( lineNum );
182     }
183     /**
184      *  Executes the script edited by the editor
185      *
186      */
187     public Object execute() throws Exception {
188         frame.toFront();
189         return model.execute( context, cl );
190     }
191     /**
192      *  Opens an editor window for the specified ScriptMetaData.
193      *  If an editor window is already open for that data it will be
194      *  moved to the front.
195      *
196      * @param  metadata    The metadata describing the script
197      * @param  context     The context in which to execute the script
198      *
199      */
200     public void edit(final XScriptContext context, ScriptMetaData entry) {
201         if (entry != null ) {
202             try {
203                 ClassLoader cl = null;
204                 try {
205                     cl = ClassLoaderFactory.getURLClassLoader( entry );
206                 }
207                 catch (Exception ignore) // TODO re-examine error handling
208                 {
209                 }
210                 final ClassLoader theCl = cl;
211                 String sUrl = entry.getParcelLocation();
212                 if ( !sUrl.endsWith( "/" ) )
213                 {
214                     sUrl += "/";
215                 }
216                 sUrl +=  entry.getLanguageName();
217                 final URL url = entry.getSourceURL();
218                 SwingInvocation.invoke(
219                     new Runnable() {
220                         public void run() {
221                             ScriptEditorForBeanShell editor;
222                             synchronized (BEING_EDITED) {
223                                 editor = (ScriptEditorForBeanShell)
224                                     BEING_EDITED.get(url);
225                                 if (editor == null) {
226                                     editor = new ScriptEditorForBeanShell(
227                                         context, theCl, url);
228                                     BEING_EDITED.put(url, editor);
229                                 }
230                             }
231                             editor.frame.toFront();
232                         }
233                     });
234             }
235             catch (IOException ioe) {
236                 showErrorMessage( "Error loading file: " + ioe.getMessage() );
237             }
238         }
239     }
240 
241     private ScriptEditorForBeanShell() {
242     }
243 
244     private ScriptEditorForBeanShell(XScriptContext context, ClassLoader cl,
245         URL url)
246     {
247         this.context   = context;
248         this.scriptURL = url;
249         this.model     = new ScriptSourceModel(url);
250         this.filename  = url.getFile();
251         this.cl = cl;
252         try {
253             Class c = Class.forName(
254                 "org.openoffice.netbeans.editor.NetBeansSourceView");
255 
256             Class[] types = new Class[] { ScriptSourceModel.class };
257 
258             java.lang.reflect.Constructor ctor = c.getConstructor(types);
259 
260             if (ctor != null) {
261                 Object[] args = new Object[] { this.model };
262                 this.view = (ScriptSourceView) ctor.newInstance(args);
263             }
264             else {
265                 this.view = new PlainSourceView(model);
266             }
267         }
268         catch (java.lang.Error err) {
269             this.view = new PlainSourceView(model);
270         }
271         catch (Exception e) {
272             this.view = new PlainSourceView(model);
273         }
274 
275         this.model.setView(this.view);
276         initUI();
277         frame.show();
278     }
279 
280     private void showErrorMessage(String message) {
281         JOptionPane.showMessageDialog(frame, message,
282             "Error", JOptionPane.ERROR_MESSAGE);
283     }
284 
285     private void initUI() {
286         frame = new JFrame("BeanShell Debug Window: " + filename);
287         frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
288 
289         frame.addWindowListener(
290             new WindowAdapter()
291             {
292                 public void windowClosing(WindowEvent e) {
293                     doClose();
294                 }
295             }
296         );
297 
298         String[] labels = {"Run", "Clear", "Save", "Close"};
299         JPanel p = new JPanel();
300         p.setLayout(new FlowLayout());
301 
302         for (int i = 0; i < labels.length; i++) {
303             JButton b = new JButton(labels[i]);
304             b.addActionListener(this);
305             p.add(b);
306 
307             if (labels[i].equals("Save") && filename == null) {
308                 b.setEnabled(false);
309             }
310         }
311 
312         frame.getContentPane().add((JComponent)view, "Center");
313         frame.getContentPane().add(p, "South");
314         frame.pack();
315         frame.setSize(590, 480);
316         frame.setLocation(300, 200);
317     }
318 
319     private void doClose() {
320         if (view.isModified()) {
321             int result = JOptionPane.showConfirmDialog(frame,
322                 "The script has been modified. " +
323                 "Do you want to save the changes?");
324 
325             if (result == JOptionPane.CANCEL_OPTION)
326             {
327                 // don't close the window, just return
328                 return;
329             }
330             else if (result == JOptionPane.YES_OPTION)
331             {
332                 boolean saveSuccess = saveTextArea();
333                 if (saveSuccess == false)
334                 {
335                     return;
336                 }
337             }
338         }
339         frame.dispose();
340         shutdown();
341     }
342 
343     private boolean saveTextArea() {
344         boolean result = true;
345 
346         if (!view.isModified()) {
347             return true;
348         }
349 
350         OutputStream fos = null;
351         try {
352             String s = view.getText();
353             fos = scriptURL.openConnection().getOutputStream();
354             if ( fos  != null) {
355                 fos.write(s.getBytes());
356             }
357             else
358             {
359                 showErrorMessage(
360                     "Error saving script: Could not open stream for file" );
361                 result = false;
362             }
363             view.setModified(false);
364        }
365        catch (IOException ioe) {
366            showErrorMessage( "Error saving script: " + ioe.getMessage() );
367            result = false;
368        }
369        catch (Exception e) {
370            showErrorMessage( "Error saving script: " + e.getMessage() );
371            result = false;
372        }
373         finally {
374             if (fos != null) {
375                 try {
376                     fos.flush();
377                     if ( fos != null )
378                     {
379                         fos.close();
380                     }
381                 }
382                 catch (IOException ignore) {
383                 }
384             }
385         }
386         return result;
387     }
388 
389     private void shutdown()
390     {
391         synchronized (BEING_EDITED) {
392             BEING_EDITED.remove(scriptURL);
393         }
394     }
395 
396     public void actionPerformed(ActionEvent e) {
397         if (e.getActionCommand().equals("Run")) {
398             try
399             {
400                 execute();
401             }
402             catch (Exception invokeException ) {
403                 showErrorMessage(invokeException.getMessage());
404             }
405         }
406         else if (e.getActionCommand().equals("Close")) {
407             doClose();
408         }
409         else if (e.getActionCommand().equals("Save")) {
410             saveTextArea();
411         }
412         else if (e.getActionCommand().equals("Clear")) {
413             view.clear();
414         }
415     }
416 }
417