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) {
buf.append(new String(b, 0, len))72                 buf.append(new String(b, 0, len));
73             }
74 
in.close()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      */
getEditor()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      */
getEditor(URL url)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      */
isModified()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      */
getText()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      */
getURL()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      */
getTemplate()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      */
getExtension()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  context     The context in which to execute the script
176      * @param  entry       The metadata describing the script
177      */
edit(final XScriptContext context, ScriptMetaData entry)178     public void edit(final XScriptContext context, ScriptMetaData entry)
179     {
180         try {
181             String sUrl = entry.getParcelLocation();
182             if ( !sUrl.endsWith( "/" ) )
183             {
184                 sUrl += "/";
185             }
186             sUrl +=  entry.getLanguageName();
187             final URL url = entry.getSourceURL();
188             SwingInvocation.invoke(
189                 new Runnable() {
190                     public void run() {
191                         synchronized (BEING_EDITED) {
192                             ScriptEditorForJavaScript editor =
193                                 (ScriptEditorForJavaScript) BEING_EDITED.get(
194                                     url);
195                             if (editor == null) {
196                                 editor = new ScriptEditorForJavaScript(
197                                     context, url);
198                                 BEING_EDITED.put(url, editor);
199                             }
200                         }
201                         assert rhinoWindow != null;
202                         rhinoWindow.showScriptWindow(url);
203                         rhinoWindow.toFront();
204                     }
205                 });
206         }
207         catch ( IOException e )
208         {
209             LogUtils.DEBUG("Caught exception: " + e);
210             LogUtils.DEBUG(LogUtils.getTrace(e));
211         }
212     }
213 
214     // Ensures that new instances of this class can only be created using
215     // the factory methods
ScriptEditorForJavaScript()216     private ScriptEditorForJavaScript()
217     {
218     }
219 
ScriptEditorForJavaScript(XScriptContext context, URL url)220     private ScriptEditorForJavaScript(XScriptContext context, URL url)
221     {
222         initUI();
223         Scriptable scope = getScope( context );
224         this.rhinoWindow.openFile(url, scope, new closeHandler( url ) );
225 
226 
227         this.scriptURL = url;
228     }
229 
230     /**
231      *  Executes the script edited by the editor
232      *
233      */
234 
execute()235     public Object execute() throws Exception
236     {
237         rhinoWindow.toFront();
238 
239         return this.rhinoWindow.runScriptWindow( scriptURL );
240     }
241 
242     /**
243      *  Indicates the line where error occurred
244      *
245      */
indicateErrorLine( int lineNum )246     public void indicateErrorLine( int lineNum )
247     {
248         this.rhinoWindow.toFront();
249         this.rhinoWindow.highlighLineInScriptWindow( scriptURL, lineNum );
250     }
251     // This code is based on the main method of the Rhino Debugger Main class
252     // We pass in the XScriptContext in the global scope for script execution
initUI()253     private void initUI() {
254         try {
255             synchronized ( ScriptEditorForJavaScript.class )
256             {
257                 if ( this.rhinoWindow != null )
258                 {
259                     return;
260                 }
261 
262                 final Main sdb = new Main("Rhino JavaScript Debugger");
263                 org.mozilla.javascript.tools.shell.ShellContextFactory contextFactory =
264                     new org.mozilla.javascript.tools.shell.ShellContextFactory();
265                 sdb.attachTo(contextFactory);
266                 contextFactory.setLanguageVersion(Context.VERSION_1_8);
267                 contextFactory.setOptimizationLevel(9);
268                 sdb.pack();
269                 sdb.setSize(640, 640);
270                 sdb.setVisible(true);
271                 sdb.setExitAction(new Runnable() {
272                     public void run() {
273                         sdb.clearAllBreakpoints();
274                         sdb.dispose();
275                         shutdown();
276                     }
277                 });
278                 /*
279                 Context.addContextListener(sdb);
280                 sdb.setScopeProvider(new ScopeProvider() {
281                     public Scriptable getScope() {
282                         return org.mozilla.javascript.tools.shell.Main.getScope();
283                     }
284                 });
285                 */
286                 sdb.addWindowListener( new WindowAdapter() {
287                     public void windowClosing(WindowEvent e) {
288                         shutdown();
289                     }
290                 });
291                 this.rhinoWindow = sdb;
292             }
293         } catch (Exception exc) {
294             LogUtils.DEBUG( LogUtils.getTrace( exc ) );
295         }
296     }
297 
shutdown()298     private void shutdown()
299     {
300         // dereference Rhino Debugger window
301         this.rhinoWindow = null;
302         this.scriptURL = null;
303         // remove all scripts from BEING_EDITED
304         synchronized( BEING_EDITED )
305         {
306             java.util.Iterator iter = BEING_EDITED.keySet().iterator();
307             java.util.Vector keysToRemove = new java.util.Vector();
308             while ( iter.hasNext() )
309             {
310 
311                 URL key = (URL)iter.next();
312                 keysToRemove.add( key );
313             }
314             for ( int i=0; i<keysToRemove.size(); i++ )
315             {
316                 BEING_EDITED.remove( keysToRemove.elementAt( i ) );
317             }
318             keysToRemove = null;
319         }
320 
321     }
getScope(XScriptContext xsctxt )322     private Scriptable getScope(XScriptContext xsctxt )
323     {
324         Context ctxt = Context.enter();
325         ImporterTopLevel scope = new ImporterTopLevel(ctxt);
326 
327         Scriptable jsCtxt = Context.toObject(xsctxt, scope);
328         scope.put("XSCRIPTCONTEXT", scope, jsCtxt);
329 
330         Scriptable jsArgs = Context.toObject(
331            new Object[0], scope);
332         scope.put("ARGUMENTS", scope, jsArgs);
333 
334         Context.exit();
335         return scope;
336     }
337 
338     class closeHandler implements Runnable
339     {
340         URL url;
closeHandler( URL url )341         closeHandler( URL url )
342         {
343             this.url = url;
344         }
run()345         public void run()
346         {
347             synchronized( BEING_EDITED )
348             {
349                 Object o = BEING_EDITED.remove( this.url );
350             }
351         }
352     }
353 }
354