diff options
Diffstat (limited to 'scripting/java/com/sun/star/script/framework/provider/javascript')
4 files changed, 705 insertions, 0 deletions
diff --git a/scripting/java/com/sun/star/script/framework/provider/javascript/MANIFEST.MF b/scripting/java/com/sun/star/script/framework/provider/javascript/MANIFEST.MF new file mode 100644 index 000000000..fe2f543c0 --- /dev/null +++ b/scripting/java/com/sun/star/script/framework/provider/javascript/MANIFEST.MF @@ -0,0 +1,2 @@ +RegistrationClassName: com.sun.star.script.framework.provider.javascript.ScriptProviderForJavaScript +UNO-Type-Path: diff --git a/scripting/java/com/sun/star/script/framework/provider/javascript/ScriptEditorForJavaScript.java b/scripting/java/com/sun/star/script/framework/provider/javascript/ScriptEditorForJavaScript.java new file mode 100644 index 000000000..1f0e8fd72 --- /dev/null +++ b/scripting/java/com/sun/star/script/framework/provider/javascript/ScriptEditorForJavaScript.java @@ -0,0 +1,319 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package com.sun.star.script.framework.provider.javascript; + +import com.sun.star.script.framework.container.ScriptMetaData; +import com.sun.star.script.framework.log.LogUtils; +import com.sun.star.script.framework.provider.ScriptEditorBase; +import com.sun.star.script.framework.provider.SwingInvocation; +import com.sun.star.script.provider.XScriptContext; + +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import java.io.IOException; +import java.io.InputStream; + +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import org.mozilla.javascript.Context; +import org.mozilla.javascript.ImporterTopLevel; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.tools.debugger.Main; +import org.mozilla.javascript.tools.debugger.ScopeProvider; + +public class ScriptEditorForJavaScript extends ScriptEditorBase { + + // global ScriptEditorForJavaScript instance + private static ScriptEditorForJavaScript theScriptEditorForJavaScript; + + // template for JavaScript scripts + private static String JSTEMPLATE; + + private static Main rhinoWindow; + private URL scriptURL; + // global list of ScriptEditors, key is [external form of URL] of file being edited + private static Map<String, ScriptEditorForJavaScript> BEING_EDITED = new + HashMap<String, ScriptEditorForJavaScript>(); + + static { + JSTEMPLATE = "// JavaScript script"; + try { + URL url = ScriptEditorForJavaScript.class.getResource("template.js"); + if (url != null) { + InputStream in = url.openStream(); + StringBuilder buf = new StringBuilder(); + byte[] b = new byte[1024]; + int len; + + while ((len = in.read(b)) != -1) { + buf.append(new String(b, 0, len)); + } + + in.close(); + + JSTEMPLATE = buf.toString(); + } + } catch (IOException ioe) { + } catch (Exception e) { + } + } + + /** + * Returns the global ScriptEditorForJavaScript instance. + */ + + public static synchronized ScriptEditorForJavaScript getEditor() { + if (theScriptEditorForJavaScript == null) { + theScriptEditorForJavaScript = new ScriptEditorForJavaScript(); + } + + return theScriptEditorForJavaScript; + } + + /** + * Get the ScriptEditorForJavaScript instance for this URL + * + * @param url The URL of the script source file + * + * @return The ScriptEditorForJavaScript associated with + * the given URL if one exists, otherwise null. + */ + public static ScriptEditorForJavaScript getEditor(URL url) { + synchronized (BEING_EDITED) { + return BEING_EDITED.get(url.toExternalForm()); + } + } + + /** + * Returns whether or not the script source being edited in this + * ScriptEditorForJavaScript has been modified + */ + public boolean isModified() { + return rhinoWindow.isModified(scriptURL); + } + + /** + * Returns the text being displayed in this ScriptEditorForJavaScript + * + * @return The text displayed in this ScriptEditorForJavaScript + */ + public String getText() { + return rhinoWindow.getText(scriptURL); + } + + /** + * Returns the Rhino Debugger url of this ScriptEditorForJavaScript + * + * @return The url of this ScriptEditorForJavaScript + */ + public String getURL() { + return scriptURL.toString(); + } + + /** + * Returns the template text for JavaScript scripts + * + * @return The template text for JavaScript scripts + */ + public String getTemplate() { + return JSTEMPLATE; + } + + /** + * Returns the default extension for JavaScript scripts + * + * @return The default extension for JavaScript scripts + */ + public String getExtension() { + return "js"; + } + + /** + * Opens an editor window for the specified ScriptMetaData. + * If an editor window is already open for that data it will be + * moved to the front. + * + * @param context The context in which to execute the script + * @param entry The metadata describing the script + */ + public void edit(final XScriptContext context, ScriptMetaData entry) { + try { + String sUrl = entry.getParcelLocation(); + + if (!sUrl.endsWith("/")) { + sUrl += "/"; + } + + sUrl += entry.getLanguageName(); + final URL url = entry.getSourceURL(); + SwingInvocation.invoke( + new Runnable() { + public void run() { + synchronized (BEING_EDITED) { + ScriptEditorForJavaScript editor = BEING_EDITED.get(url.toExternalForm()); + + if (editor == null) { + editor = new ScriptEditorForJavaScript(context, url); + BEING_EDITED.put(url.toExternalForm(), editor); + } + } + + assert rhinoWindow != null; + rhinoWindow.showScriptWindow(url); + rhinoWindow.toFront(); + } + }); + } catch (IOException e) { + LogUtils.DEBUG("Caught exception: " + e); + LogUtils.DEBUG(LogUtils.getTrace(e)); + } + } + + // Ensures that new instances of this class can only be created using + // the factory methods + private ScriptEditorForJavaScript() { + } + + private ScriptEditorForJavaScript(XScriptContext context, URL url) { + setContext(context); + // Need to check that before showing the window. Checking in execute() has no effect. + if (!isMacroExecutionEnabled()) { + showErrorMessage("Macro Execution has been disabled."); + return ; + } + initUI(); + Scriptable scope = getScope(context); + rhinoWindow.openFile(url, scope, new CloseHandler(url)); + this.scriptURL = url; + } + + /** + * Executes the script edited by the editor + * + */ + + public Object execute() throws Exception { + rhinoWindow.toFront(); + + return rhinoWindow.runScriptWindow(scriptURL); + } + + /** + * Indicates the line where error occurred + * + */ + public void indicateErrorLine(int lineNum) { + rhinoWindow.toFront(); + rhinoWindow.highlighLineInScriptWindow(scriptURL, lineNum); + } + // This code is based on the main method of the Rhino Debugger Main class + // We pass in the XScriptContext in the global scope for script execution + private void initUI() { + try { + synchronized (ScriptEditorForJavaScript.class) { + if (rhinoWindow != null) { + return; + } + + final Main sdb = new Main("Rhino JavaScript Debugger"); + sdb.pack(); + sdb.setSize(640, 640); + sdb.setVisible(true); + sdb.setExitAction(new Runnable() { + public void run() { + sdb.clearAllBreakpoints(); + sdb.dispose(); + shutdown(); + } + }); + Context.addContextListener(sdb); + sdb.setScopeProvider(new ScopeProvider() { + @Override + public Scriptable getScope() { + return org.mozilla.javascript.tools.shell.Main.getScope(); + } + }); + sdb.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + shutdown(); + } + }); + rhinoWindow = sdb; + } + } catch (Exception exc) { + LogUtils.DEBUG(LogUtils.getTrace(exc)); + } + } + + private void shutdown() { + // dereference Rhino Debugger window + rhinoWindow = null; + this.scriptURL = null; + + // remove all scripts from BEING_EDITED + synchronized (BEING_EDITED) { + java.util.Iterator<String> iter = BEING_EDITED.keySet().iterator(); + java.util.ArrayList<String> keysToRemove = new java.util.ArrayList<String>(); + + while (iter.hasNext()) { + String key = iter.next(); + keysToRemove.add(key); + } + + for (int i = 0; i < keysToRemove.size(); i++) { + BEING_EDITED.remove(keysToRemove.get(i)); + } + + keysToRemove = null; + } + + } + private Scriptable getScope(XScriptContext xsctxt) { + Context ctxt = Context.enter(); + ImporterTopLevel scope = new ImporterTopLevel(ctxt); + + Scriptable jsCtxt = Context.toObject(xsctxt, scope); + scope.put("XSCRIPTCONTEXT", scope, jsCtxt); + + Scriptable jsArgs = Context.toObject(new Object[0], scope); + scope.put("ARGUMENTS", scope, jsArgs); + + Context.exit(); + return scope; + } + + private static class CloseHandler implements Runnable { + + private final URL url; + + private CloseHandler(URL url) { + this.url = url; + } + + public void run() { + synchronized (BEING_EDITED) { + BEING_EDITED.remove(this.url.toExternalForm()); + } + } + } +} diff --git a/scripting/java/com/sun/star/script/framework/provider/javascript/ScriptProviderForJavaScript.java b/scripting/java/com/sun/star/script/framework/provider/javascript/ScriptProviderForJavaScript.java new file mode 100644 index 000000000..9465a646f --- /dev/null +++ b/scripting/java/com/sun/star/script/framework/provider/javascript/ScriptProviderForJavaScript.java @@ -0,0 +1,330 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package com.sun.star.script.framework.provider.javascript; + +import com.sun.star.comp.loader.FactoryHelper; + +import com.sun.star.document.XScriptInvocationContext; + +import com.sun.star.frame.XModel; + +import com.sun.star.lang.XMultiComponentFactory; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.reflection.InvocationTargetException; + +import com.sun.star.registry.XRegistryKey; + +import com.sun.star.script.framework.container.ScriptMetaData; +import com.sun.star.script.framework.log.LogUtils; +import com.sun.star.script.framework.provider.ClassLoaderFactory; +import com.sun.star.script.framework.provider.ScriptContext; +import com.sun.star.script.framework.provider.ScriptEditor; +import com.sun.star.script.framework.provider.ScriptProvider; +import com.sun.star.script.provider.ScriptExceptionRaisedException; +import com.sun.star.script.provider.ScriptFrameworkErrorException; +import com.sun.star.script.provider.ScriptFrameworkErrorType; +import com.sun.star.script.provider.XScript; + +import com.sun.star.uno.XComponentContext; + +import java.net.URL; + +import org.mozilla.javascript.Context; +import org.mozilla.javascript.ImporterTopLevel; +import org.mozilla.javascript.JavaScriptException; +import org.mozilla.javascript.Scriptable; + +public class ScriptProviderForJavaScript { + + public static class ScriptProviderForJavaScript_2 extends ScriptProvider { + + public ScriptProviderForJavaScript_2(XComponentContext ctx) { + super(ctx, "JavaScript"); + } + + @Override + public XScript getScript(/*IN*/String scriptURI) + throws com.sun.star.uno.RuntimeException, ScriptFrameworkErrorException { + ScriptMetaData scriptData = null; + + try { + scriptData = getScriptData(scriptURI); + ScriptImpl script = new ScriptImpl(m_xContext, scriptData, m_xModel, + m_xInvocContext); + return script; + } catch (com.sun.star.uno.RuntimeException re) { + throw new ScriptFrameworkErrorException( + "Failed to create script object: " + re.getMessage(), + null, scriptData.getLanguageName(), language, ScriptFrameworkErrorType.UNKNOWN); + } + } + + @Override + public boolean hasScriptEditor() { + return true; + } + + @Override + public ScriptEditor getScriptEditor() { + return ScriptEditorForJavaScript.getEditor(); + } + } + + /** + * Returns a factory for creating the service. + * This method is called by the <code>JavaLoader</code> + * <p> + * + * @param implName the name of the implementation for which a service is desired + * @param multiFactory the service manager to be used if needed + * @param regKey the registryKey + * @return returns a <code>XSingleServiceFactory</code> for creating + * the component + * @see com.sun.star.comp.loader.JavaLoader + */ + public static XSingleServiceFactory __getServiceFactory(String implName, + XMultiServiceFactory multiFactory, XRegistryKey regKey) { + + XSingleServiceFactory xSingleServiceFactory = null; + + if (implName.equals( + ScriptProviderForJavaScript.ScriptProviderForJavaScript_2.class.getName())) { + + xSingleServiceFactory = + FactoryHelper.getServiceFactory( + ScriptProviderForJavaScript.ScriptProviderForJavaScript_2.class, + "com.sun.star.script.provider.ScriptProviderForJavaScript", + multiFactory, regKey); + + } + + return xSingleServiceFactory; + } +} + +class ScriptImpl implements XScript { + + private final ScriptMetaData metaData; + private final XComponentContext m_xContext; + private XMultiComponentFactory m_xMultiComponentFactory; + private final XModel m_xModel; + private final XScriptInvocationContext m_xInvocContext; + + ScriptImpl(XComponentContext ctx, ScriptMetaData metaData, XModel xModel, + XScriptInvocationContext xInvocContext) throws + com.sun.star.uno.RuntimeException { + + this.metaData = metaData; + this.m_xContext = ctx; + this.m_xModel = xModel; + this.m_xInvocContext = xInvocContext; + + try { + this.m_xMultiComponentFactory = m_xContext.getServiceManager(); + } catch (Exception e) { + throw new com.sun.star.uno.RuntimeException(e); + } + + LogUtils.DEBUG("ScriptImpl [javascript] script data = " + metaData); + } + + /** + * The invoke method of the ScriptProviderForJavaScript runs the + * JavaScript script specified in the URI + * + * + * + * @param params All parameters; pure, out params are + * undefined in sequence, i.e., the value + * has to be ignored by the callee + * + * @param aOutParamIndex Out indices + * + * @param aOutParam Out parameters + * + * @return The value returned from the function + * being invoked + * + * @throws ScriptFrameworkErrorException If there is no matching script name + * + * + * @throws InvocationTargetException If the running script throws + * an exception this information + * is captured and rethrown as + * ScriptErrorRaisedException or + * ScriptExceptionRaisedException + */ + public Object invoke( + /*IN*/Object[] params, + /*OUT*/short[][] aOutParamIndex, + /*OUT*/Object[][] aOutParam) + throws ScriptFrameworkErrorException, InvocationTargetException { + + // Initialise the out parameters - not used at the moment + aOutParamIndex[0] = new short[0]; + aOutParam[0] = new Object[0]; + + ClassLoader cl = null; + + try { + cl = ClassLoaderFactory.getURLClassLoader(metaData); + metaData.getSourceURL(); + } catch (java.net.MalformedURLException mfu) { + throw new ScriptFrameworkErrorException( + mfu.getMessage(), null, + metaData.getLanguageName(), metaData.getLanguage(), + ScriptFrameworkErrorType.MALFORMED_URL); + } + + Context ctxt = null; + + try { + Object result = null; + + ScriptEditorForJavaScript editor = + ScriptEditorForJavaScript.getEditor(metaData.getSourceURL()); + + if (editor != null) { + result = editor.execute(); + + if (result != null && + result.getClass().getName().equals("org.mozilla.javascript.Undefined")) { + // Always return a string + // TODO revisit + return Context.toString(result); + } + + } + + String source; + + if (editor != null && editor.isModified()) { + LogUtils.DEBUG("GOT A MODIFIED SOURCE"); + source = editor.getText(); + } else { + metaData.loadSource(); + source = metaData.getSource(); + + } + + if (source == null || source.length() == 0) { + throw new ScriptFrameworkErrorException( + "Failed to read source data for script", null, + metaData.getLanguageName(), metaData.getLanguage(), + ScriptFrameworkErrorType.UNKNOWN); + } + + /* Set the context ClassLoader on the current thread to + be our custom ClassLoader. This is the suggested method + for setting up a ClassLoader to be used by the Rhino + interpreter + */ + if (cl != null) { + Thread.currentThread().setContextClassLoader(cl); + } + + // Initialize a Rhino Context object + ctxt = Context.enter(); + + /* The ImporterTopLevel ensures that importClass and + importPackage statements work in Javascript scripts + Make the XScriptContext available as a global variable + to the script + */ + ImporterTopLevel scope = new ImporterTopLevel(ctxt); + + Scriptable jsCtxt = + Context.toObject( + ScriptContext.createContext( + m_xModel, m_xInvocContext, m_xContext, + m_xMultiComponentFactory), + scope); + + scope.put("XSCRIPTCONTEXT", scope, jsCtxt); + + Scriptable jsArgs = Context.toObject(params, scope); + scope.put("ARGUMENTS", scope, jsArgs); + + result = ctxt.evaluateString(scope, source, "<stdin>", 1, null); + result = Context.toString(result); + return result; + } catch (JavaScriptException jse) { + LogUtils.DEBUG("Caught JavaScriptException exception for JavaScript type = " + + jse.getClass()); + String message = jse.getMessage(); + Object wrap = jse.getValue(); + LogUtils.DEBUG("\t message " + message); + LogUtils.DEBUG("\t wrapped type " + wrap.getClass()); + LogUtils.DEBUG("\t wrapped toString " + wrap.toString()); + ScriptExceptionRaisedException se = new + ScriptExceptionRaisedException(message); + se.lineNum = -1; + se.language = "JavaScript"; + se.scriptName = metaData.getLanguageName(); + se.exceptionType = wrap.getClass().getName(); + se.language = metaData.getLanguage(); + LogUtils.DEBUG("ExceptionRaised exception "); + LogUtils.DEBUG("\t message " + se.getMessage()); + LogUtils.DEBUG("\t lineNum " + se.lineNum); + LogUtils.DEBUG("\t language " + se.language); + LogUtils.DEBUG("\t scriptName " + se.scriptName); + raiseEditor(se.lineNum); + throw new InvocationTargetException( + "JavaScript uncaught exception" + metaData.getLanguageName(), null, se); + } catch (Exception ex) { + LogUtils.DEBUG("Caught Exception " + ex); + LogUtils.DEBUG("rethrowing as ScriptFramework error"); + throw new ScriptFrameworkErrorException( + ex.getMessage(), null, metaData.getLanguageName(), + metaData.getLanguage(), ScriptFrameworkErrorType.UNKNOWN); + } finally { + if (ctxt != null) { + Context.exit(); + } + } + } + + private void raiseEditor(int lineNum) { + try { + URL sourceUrl = metaData.getSourceURL(); + + ScriptEditorForJavaScript editor = + ScriptEditorForJavaScript.getEditor(sourceUrl); + + if (editor == null) { + editor = ScriptEditorForJavaScript.getEditor(); + + editor.edit( + ScriptContext.createContext(m_xModel, m_xInvocContext, + m_xContext, m_xMultiComponentFactory), + metaData); + + editor = ScriptEditorForJavaScript.getEditor(sourceUrl); + } + + if (editor != null) { + System.out.println("** Have raised IDE for JavaScript, calling indicateErrorLine for line " + + lineNum); + editor.indicateErrorLine(lineNum); + } + } catch (java.net.MalformedURLException ignore) { + } + } +} diff --git a/scripting/java/com/sun/star/script/framework/provider/javascript/template.js b/scripting/java/com/sun/star/script/framework/provider/javascript/template.js new file mode 100644 index 000000000..939ba339c --- /dev/null +++ b/scripting/java/com/sun/star/script/framework/provider/javascript/template.js @@ -0,0 +1,54 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +// Hello World in JavaScript +// Import standard OpenOffice.org API classes. For more information on +// these classes and the OpenOffice.org API, see the OpenOffice.org +// Developers Guide at: +// https://api.libreoffice.org/ + +importClass(Packages.com.sun.star.uno.UnoRuntime); +importClass(Packages.com.sun.star.text.XTextDocument); +importClass(Packages.com.sun.star.text.XText); +importClass(Packages.com.sun.star.text.XTextRange); +importClass(Packages.com.sun.star.frame.XModel); + +// Import XScriptContext class. An instance of this class is available +// to all JavaScript scripts in the global variable "XSCRIPTCONTEXT". This +// variable can be used to access the document for which this script +// was invoked. + +// Methods available are: + +// XSCRIPTCONTEXT.getDocument() returns XModel +// XSCRIPTCONTEXT.getInvocationContext() returns XScriptInvocationContext or NULL +// XSCRIPTCONTEXT.getDesktop() returns XDesktop +// XSCRIPTCONTEXT.getComponentContext() returns XComponentContext + +// For more information on using this class see the scripting +// developer guides at: + +// https://api.libreoffice.org/docs/DevelopersGuide/ScriptingFramework/ScriptingFramework.xhtml + + +oDoc = UnoRuntime.queryInterface(XModel,XSCRIPTCONTEXT.getInvocationContext()); +if ( !oDoc ) + oDoc = XSCRIPTCONTEXT.getDocument(); +xTextDoc = UnoRuntime.queryInterface(XTextDocument,oDoc); +xText = xTextDoc.getText(); +xTextRange = xText.getEnd(); +xTextRange.setString( "Hello World (in JavaScript)" ); |