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