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) {
84                 buf.append(new String(b, 0, len));
85             }
86 
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      */
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      */
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      */
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      */
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      */
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      */
166     public String getExtension() {
167         return "bsh";
168     }
169 
170 
171     /**
172      *  Indicates the line where error occured
173      *
174      */
175     public void indicateErrorLine( int lineNum )
176     {
177         model.indicateErrorLine( lineNum );
178     }
179     /**
180      *  Executes the script edited by the editor
181      *
182      */
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  metadata    The metadata describing the script
193      * @param  context     The context in which to execute the script
194      *
195      */
196     public void edit(final XScriptContext context, ScriptMetaData entry) {
197         if (entry != null ) {
198             try {
199                 ClassLoader cl = null;
200                 try {
201                     cl = ClassLoaderFactory.getURLClassLoader( entry );
202                 }
203                 catch (Exception ignore) // TODO re-examine error handling
204                 {
205                 }
206                 final ClassLoader theCl = cl;
207                 String sUrl = entry.getParcelLocation();
208                 if ( !sUrl.endsWith( "/" ) )
209                 {
210                     sUrl += "/";
211                 }
212                 sUrl +=  entry.getLanguageName();
213                 final URL url = entry.getSourceURL();
214                 SwingInvocation.invoke(
215                     new Runnable() {
216                         public void run() {
217                             ScriptEditorForBeanShell editor;
218                             synchronized (BEING_EDITED) {
219                                 editor = (ScriptEditorForBeanShell)
220                                     BEING_EDITED.get(url);
221                                 if (editor == null) {
222                                     editor = new ScriptEditorForBeanShell(
223                                         context, theCl, url);
224                                     BEING_EDITED.put(url, editor);
225                                 }
226                             }
227                             editor.frame.toFront();
228                         }
229                     });
230             }
231             catch (IOException ioe) {
232                 showErrorMessage( "Error loading file: " + ioe.getMessage() );
233             }
234         }
235     }
236 
237     private ScriptEditorForBeanShell() {
238     }
239 
240     private ScriptEditorForBeanShell(XScriptContext context, ClassLoader cl,
241         URL url)
242     {
243         this.context   = context;
244         this.scriptURL = url;
245         this.model     = new ScriptSourceModel(url);
246         this.filename  = url.getFile();
247         this.cl = cl;
248         try {
249             Class c = Class.forName(
250                 "org.openoffice.netbeans.editor.NetBeansSourceView");
251 
252             Class[] types = new Class[] { ScriptSourceModel.class };
253 
254             java.lang.reflect.Constructor ctor = c.getConstructor(types);
255 
256             if (ctor != null) {
257                 Object[] args = new Object[] { this.model };
258                 this.view = (ScriptSourceView) ctor.newInstance(args);
259             }
260             else {
261                 this.view = new PlainSourceView(model);
262             }
263         }
264         catch (java.lang.Error err) {
265             this.view = new PlainSourceView(model);
266         }
267         catch (Exception e) {
268             this.view = new PlainSourceView(model);
269         }
270 
271         this.model.setView(this.view);
272         initUI();
273         frame.show();
274     }
275 
276     private void showErrorMessage(String message) {
277         JOptionPane.showMessageDialog(frame, message,
278             "Error", JOptionPane.ERROR_MESSAGE);
279     }
280 
281     private void initUI() {
282         frame = new JFrame("BeanShell Debug Window: " + filename);
283         frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
284 
285         frame.addWindowListener(
286             new WindowAdapter()
287             {
288                 public void windowClosing(WindowEvent e) {
289                     doClose();
290                 }
291             }
292         );
293 
294         String[] labels = {"Run", "Clear", "Save", "Close"};
295         JPanel p = new JPanel();
296         p.setLayout(new FlowLayout());
297 
298         for (int i = 0; i < labels.length; i++) {
299             JButton b = new JButton(labels[i]);
300             b.addActionListener(this);
301             p.add(b);
302 
303             if (labels[i].equals("Save") && filename == null) {
304                 b.setEnabled(false);
305             }
306         }
307 
308         frame.getContentPane().add((JComponent)view, "Center");
309         frame.getContentPane().add(p, "South");
310         frame.pack();
311         frame.setSize(590, 480);
312         frame.setLocation(300, 200);
313     }
314 
315     private void doClose() {
316         if (view.isModified()) {
317             int result = JOptionPane.showConfirmDialog(frame,
318                 "The script has been modified. " +
319                 "Do you want to save the changes?");
320 
321             if (result == JOptionPane.CANCEL_OPTION)
322             {
323                 // don't close the window, just return
324                 return;
325             }
326             else if (result == JOptionPane.YES_OPTION)
327             {
328                 boolean saveSuccess = saveTextArea();
329                 if (saveSuccess == false)
330                 {
331                     return;
332                 }
333             }
334         }
335         frame.dispose();
336         shutdown();
337     }
338 
339     private boolean saveTextArea() {
340         boolean result = true;
341 
342         if (!view.isModified()) {
343             return true;
344         }
345 
346         OutputStream fos = null;
347         try {
348             String s = view.getText();
349             fos = scriptURL.openConnection().getOutputStream();
350             if ( fos  != null) {
351                 fos.write(s.getBytes());
352             }
353             else
354             {
355                 showErrorMessage(
356                     "Error saving script: Could not open stream for file" );
357                 result = false;
358             }
359             view.setModified(false);
360        }
361        catch (IOException ioe) {
362            showErrorMessage( "Error saving script: " + ioe.getMessage() );
363            result = false;
364        }
365        catch (Exception e) {
366            showErrorMessage( "Error saving script: " + e.getMessage() );
367            result = false;
368        }
369         finally {
370             if (fos != null) {
371                 try {
372                     fos.flush();
373                     if ( fos != null )
374                     {
375                         fos.close();
376                     }
377                 }
378                 catch (IOException ignore) {
379                 }
380             }
381         }
382         return result;
383     }
384 
385     private void shutdown()
386     {
387         synchronized (BEING_EDITED) {
388             BEING_EDITED.remove(scriptURL);
389         }
390     }
391 
392     public void actionPerformed(ActionEvent e) {
393         if (e.getActionCommand().equals("Run")) {
394             try
395             {
396                 execute();
397             }
398             catch (Exception invokeException ) {
399                 showErrorMessage(invokeException.getMessage());
400             }
401         }
402         else if (e.getActionCommand().equals("Close")) {
403             doClose();
404         }
405         else if (e.getActionCommand().equals("Save")) {
406             saveTextArea();
407         }
408         else if (e.getActionCommand().equals("Clear")) {
409             view.clear();
410         }
411     }
412 }
413