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