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 
24 package com.sun.star.script.framework.provider.javascript;
25 
26 import org.mozilla.javascript.Context;
27 import org.mozilla.javascript.Scriptable;
28 import org.mozilla.javascript.ImporterTopLevel;
29 import org.mozilla.javascript.tools.debugger.Main;
30 import org.mozilla.javascript.tools.debugger.ScopeProvider;
31 
32 import com.sun.star.script.provider.XScriptContext;
33 import com.sun.star.script.framework.container.ScriptMetaData;
34 import com.sun.star.script.framework.provider.ScriptEditor;
35 import com.sun.star.script.framework.provider.SwingInvocation;
36 import com.sun.star.script.framework.log.LogUtils;
37 
38 import java.io.InputStream;
39 import java.io.IOException;
40 import java.net.URL;
41 
42 import java.util.Map;
43 import java.util.HashMap;
44 
45 import java.awt.event.WindowAdapter;
46 import java.awt.event.WindowEvent;
47 
48 public class ScriptEditorForJavaScript implements ScriptEditor
49 {
50     // global ScriptEditorForJavaScript instance
51     private static ScriptEditorForJavaScript theScriptEditorForJavaScript;
52 
53     // template for JavaScript scripts
54     private static String JSTEMPLATE;
55 
56     static private Main rhinoWindow;
57     private URL scriptURL;
58     // global list of ScriptEditors, key is URL of file being edited
59     private static Map BEING_EDITED = new HashMap();
60 
61     static {
62         try {
63             URL url =
64                 ScriptEditorForJavaScript.class.getResource("template.js");
65 
66             InputStream in = url.openStream();
67             StringBuffer buf = new StringBuffer();
68             byte[] b = new byte[1024];
69             int len = 0;
70 
71             while ((len = in.read(b)) != -1) {
72                 buf.append(new String(b, 0, len));
73             }
74 
75             in.close();
76 
77             JSTEMPLATE = buf.toString();
78         }
79         catch (IOException ioe) {
80             JSTEMPLATE = "// JavaScript script";
81         }
82         catch (Exception e) {
83             JSTEMPLATE = "// JavaScript script";
84         }
85     }
86 
87     /**
88      *  Returns the global ScriptEditorForJavaScript instance.
89      */
90     public static ScriptEditorForJavaScript getEditor()
91     {
92         if (theScriptEditorForJavaScript == null)
93         {
94             synchronized(ScriptEditorForJavaScript.class)
95             {
96                 if (theScriptEditorForJavaScript == null)
97                 {
98                     theScriptEditorForJavaScript =
99                         new ScriptEditorForJavaScript();
100                 }
101             }
102         }
103         return theScriptEditorForJavaScript;
104     }
105 
106     /**
107      *  Get the ScriptEditorForJavaScript instance for this URL
108      *
109      * @param  url         The URL of the script source file
110      *
111      * @return             The ScriptEditorForJavaScript associated with
112      *                     the given URL if one exists, otherwise null.
113      */
114     public static ScriptEditorForJavaScript getEditor(URL url)
115     {
116         synchronized (BEING_EDITED) {
117             return (ScriptEditorForJavaScript)BEING_EDITED.get(url);
118         }
119     }
120 
121     /**
122      *  Returns whether or not the script source being edited in this
123      *  ScriptEditorForJavaScript has been modified
124      */
125     public boolean isModified()
126     {
127         return rhinoWindow.isModified( scriptURL );
128     }
129 
130     /**
131      *  Returns the text being displayed in this ScriptEditorForJavaScript
132      *
133      *  @return            The text displayed in this ScriptEditorForJavaScript
134      */
135     public String getText()
136     {
137         return rhinoWindow.getText( scriptURL );
138     }
139 
140     /**
141      *  Returns the Rhino Debugger url of this ScriptEditorForJavaScript
142      *
143      *  @return            The url of this ScriptEditorForJavaScript
144      */
145     public String getURL()
146     {
147         return scriptURL.toString();
148     }
149 
150     /**
151      *  Returns the template text for JavaScript scripts
152      *
153      *  @return            The template text for JavaScript scripts
154      */
155     public String getTemplate()
156     {
157         return JSTEMPLATE;
158     }
159 
160     /**
161      *  Returns the default extension for JavaScript scripts
162      *
163      *  @return            The default extension for JavaScript scripts
164      */
165     public String getExtension()
166     {
167         return "js";
168     }
169 
170     /**
171      *  Opens an editor window for the specified ScriptMetaData.
172      *  If an editor window is already open for that data it will be
173      *  moved to the front.
174      *
175      * @param  metadata    The metadata describing the script
176      * @param  context     The context in which to execute the script
177      *
178      */
179     public void edit(final XScriptContext context, ScriptMetaData entry)
180     {
181         try {
182             String sUrl = entry.getParcelLocation();
183             if ( !sUrl.endsWith( "/" ) )
184             {
185                 sUrl += "/";
186             }
187             sUrl +=  entry.getLanguageName();
188             final URL url = entry.getSourceURL();
189             SwingInvocation.invoke(
190                 new Runnable() {
191                     public void run() {
192                         synchronized (BEING_EDITED) {
193                             ScriptEditorForJavaScript editor =
194                                 (ScriptEditorForJavaScript) BEING_EDITED.get(
195                                     url);
196                             if (editor == null) {
197                                 editor = new ScriptEditorForJavaScript(
198                                     context, url);
199                                 BEING_EDITED.put(url, editor);
200                             }
201                         }
202                         assert rhinoWindow != null;
203                         rhinoWindow.showScriptWindow(url);
204                         rhinoWindow.toFront();
205                     }
206                 });
207         }
208         catch ( IOException e )
209         {
210             LogUtils.DEBUG("Caught exception: " + e);
211             LogUtils.DEBUG(LogUtils.getTrace(e));
212         }
213     }
214 
215     // Ensures that new instances of this class can only be created using
216     // the factory methods
217     private ScriptEditorForJavaScript()
218     {
219     }
220 
221     private ScriptEditorForJavaScript(XScriptContext context, URL url)
222     {
223         initUI();
224         Scriptable scope = getScope( context );
225         this.rhinoWindow.openFile(url, scope, new closeHandler( url ) );
226 
227 
228         this.scriptURL = url;
229     }
230 
231     /**
232      *  Executes the script edited by the editor
233      *
234      */
235 
236     public Object execute() throws Exception
237     {
238         rhinoWindow.toFront();
239 
240         return this.rhinoWindow.runScriptWindow( scriptURL );
241     }
242 
243     /**
244      *  Indicates the line where error occured
245      *
246      */
247     public void indicateErrorLine( int lineNum )
248     {
249         this.rhinoWindow.toFront();
250         this.rhinoWindow.highlighLineInScriptWindow( scriptURL, lineNum );
251     }
252     // This code is based on the main method of the Rhino Debugger Main class
253     // We pass in the XScriptContext in the global scope for script execution
254     private void initUI() {
255         try {
256             synchronized ( ScriptEditorForJavaScript.class )
257             {
258                 if ( this.rhinoWindow != null )
259                 {
260                     return;
261                 }
262 
263                 final Main sdb = new Main("Rhino JavaScript Debugger");
264                 org.mozilla.javascript.tools.shell.ShellContextFactory contextFactory =
265                     new org.mozilla.javascript.tools.shell.ShellContextFactory();
266                 sdb.attachTo(contextFactory);
267                 contextFactory.setLanguageVersion(Context.VERSION_1_8);
268                 contextFactory.setOptimizationLevel(9);
269                 sdb.pack();
270                 sdb.setSize(640, 640);
271                 sdb.setVisible(true);
272                 sdb.setExitAction(new Runnable() {
273                     public void run() {
274                         sdb.clearAllBreakpoints();
275                         sdb.dispose();
276                         shutdown();
277                     }
278                 });
279                 /*
280                 Context.addContextListener(sdb);
281                 sdb.setScopeProvider(new ScopeProvider() {
282                     public Scriptable getScope() {
283                         return org.mozilla.javascript.tools.shell.Main.getScope();
284                     }
285                 });
286                 */
287                 sdb.addWindowListener( new WindowAdapter() {
288                     public void windowClosing(WindowEvent e) {
289                         shutdown();
290                     }
291                 });
292                 this.rhinoWindow = sdb;
293             }
294         } catch (Exception exc) {
295             LogUtils.DEBUG( LogUtils.getTrace( exc ) );
296         }
297     }
298 
299     private void shutdown()
300     {
301         // dereference Rhino Debugger window
302         this.rhinoWindow = null;
303         this.scriptURL = null;
304         // remove all scripts from BEING_EDITED
305         synchronized( BEING_EDITED )
306         {
307             java.util.Iterator iter = BEING_EDITED.keySet().iterator();
308             java.util.Vector keysToRemove = new java.util.Vector();
309             while ( iter.hasNext() )
310             {
311 
312                 URL key = (URL)iter.next();
313                 keysToRemove.add( key );
314             }
315             for ( int i=0; i<keysToRemove.size(); i++ )
316             {
317                 BEING_EDITED.remove( keysToRemove.elementAt( i ) );
318             }
319             keysToRemove = null;
320         }
321 
322     }
323     private Scriptable getScope(XScriptContext xsctxt )
324     {
325         Context ctxt = Context.enter();
326         ImporterTopLevel scope = new ImporterTopLevel(ctxt);
327 
328         Scriptable jsCtxt = Context.toObject(xsctxt, scope);
329         scope.put("XSCRIPTCONTEXT", scope, jsCtxt);
330 
331         Scriptable jsArgs = Context.toObject(
332            new Object[0], scope);
333         scope.put("ARGUMENTS", scope, jsArgs);
334 
335         Context.exit();
336         return scope;
337     }
338 
339     class closeHandler implements Runnable
340     {
341         URL url;
342         closeHandler( URL url )
343         {
344             this.url = url;
345         }
346         public void run()
347         {
348             synchronized( BEING_EDITED )
349             {
350                 Object o = BEING_EDITED.remove( this.url );
351             }
352         }
353     }
354 }
355