diff --git a/boot-script/pom.xml b/boot-script/pom.xml index 5a73f5a88..066ac90b9 100644 --- a/boot-script/pom.xml +++ b/boot-script/pom.xml @@ -55,7 +55,7 @@ org.apache.maven.plugins maven-compiler-plugin - 1.8 + 19 1.8 diff --git a/boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java b/boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java index a5882dacd..911df581c 100644 --- a/boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java +++ b/boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java @@ -19,6 +19,7 @@ package net.java.html.boot.script; import java.io.Closeable; +import java.io.File; import java.io.IOException; import java.io.Reader; import java.lang.ref.WeakReference; @@ -28,6 +29,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; @@ -37,6 +39,7 @@ import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; +import javax.script.SimpleScriptContext; import net.java.html.boot.script.impl.Callback; import net.java.html.boot.script.impl.PromisePolyfill; import org.netbeans.html.boot.spi.Fn; @@ -157,7 +160,18 @@ public void displayPage(URL page, Runnable onPageLoad) { @Override public void loadScript(Reader code) throws Exception { - eng.eval(code); + var ctx = eng.getContext(); + if (code instanceof Callable moreInfo && moreInfo.call() instanceof URL u) { + try { + var file = new File(u.toURI()); + if (file.exists()) { + ctx.setAttribute(ScriptEngine.FILENAME, file.getPath(), ScriptContext.ENGINE_SCOPE); + } + } catch (IllegalArgumentException ex) { + // ignore URLs not representing a file + } + } + eng.eval(code, ctx); } // diff --git a/boot-script/src/test/java/net/java/html/boot/script/ScriptEngineCase.java b/boot-script/src/test/java/net/java/html/boot/script/ScriptEngineCase.java index 409839a59..204aa63cf 100644 --- a/boot-script/src/test/java/net/java/html/boot/script/ScriptEngineCase.java +++ b/boot-script/src/test/java/net/java/html/boot/script/ScriptEngineCase.java @@ -23,7 +23,6 @@ import java.lang.reflect.Method; import net.java.html.js.JavaScriptBody; import org.netbeans.html.boot.spi.Fn; -import org.netbeans.html.boot.impl.FnContext; import org.testng.IHookCallBack; import org.testng.IHookable; import org.testng.ITest; diff --git a/boot-script/src/test/java/net/java/html/boot/script/ScriptEngineEvalTest.java b/boot-script/src/test/java/net/java/html/boot/script/ScriptEngineEvalTest.java new file mode 100644 index 000000000..dd023a071 --- /dev/null +++ b/boot-script/src/test/java/net/java/html/boot/script/ScriptEngineEvalTest.java @@ -0,0 +1,71 @@ +/** + * 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 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package net.java.html.boot.script; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.regex.Pattern; +import net.java.html.js.JavaScriptBody; +import net.java.html.js.JavaScriptResource; +import org.netbeans.html.boot.spi.Fn; +import org.testng.Assert; +import org.testng.annotations.Test; + +@JavaScriptResource("throw.js") +public final class ScriptEngineEvalTest { + public ScriptEngineEvalTest() { + } + + @Test + public void fileNameIsDefinedWhenEvaluatingAResourceFile() throws Exception { + try (var p = Fn.activate(Scripts.newPresenter().build())) { + yieldError("Everything is OK!"); + } catch (Exception ex) { + assertStackContains(ex, + "Caused by.*Exception: Everything is OK!", + "at .yieldError.*throw.js:[0-9]+", + "at .*anonymous.*eval.*[0-9]+" + ); + } + } + + @JavaScriptBody(args = { "msg" }, body = """ + yieldError(msg); + """) + private static void yieldError(String msg) { + throw new AssertionError(msg); + } + + private void assertStackContains(Exception ex, String... patterns) { + var w = new StringWriter(); + ex.printStackTrace(new PrintWriter(w)); + var lines = w.toString().split("\n"); + + FOUND: for (var p : patterns) { + var c = Pattern.compile(p); + for (var l : lines) { + if (c.matcher(l).find()) { + continue FOUND; + } + } + Assert.fail("Cannot find " + p + " in:\n" + w); + } + } + +} diff --git a/boot-script/src/test/resources/net/java/html/boot/script/throw.js b/boot-script/src/test/resources/net/java/html/boot/script/throw.js new file mode 100644 index 000000000..93e26f813 --- /dev/null +++ b/boot-script/src/test/resources/net/java/html/boot/script/throw.js @@ -0,0 +1,22 @@ +/** + * 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 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +function yieldError(msg) { + throw msg; +} diff --git a/boot/src/main/java/org/netbeans/html/boot/spi/Fn.java b/boot/src/main/java/org/netbeans/html/boot/spi/Fn.java index a225b2bcf..1bd9df7f6 100644 --- a/boot/src/main/java/org/netbeans/html/boot/spi/Fn.java +++ b/boot/src/main/java/org/netbeans/html/boot/spi/Fn.java @@ -20,7 +20,6 @@ import java.io.Closeable; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.lang.ref.Reference; @@ -30,20 +29,21 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.Executor; import net.java.html.js.JavaScriptBody; import org.netbeans.html.boot.impl.FnContext; -/** Represents single JavaScript function that can be invoked. +/** Represents single JavaScript function that can be invoked. * Created via {@link Presenter#defineFn(java.lang.String, java.lang.String...)}. * * @author Jaroslav Tulach */ public abstract class Fn { private final Ref presenter; - + /** - * @deprecated Ineffective as of 0.6. + * @deprecated Ineffective as of 0.6. * Provide a presenter via {@link #Fn(org.netbeans.html.boot.spi.Fn.Presenter)} * constructor */ @@ -51,11 +51,11 @@ public abstract class Fn { protected Fn() { this(null); } - + /** Creates new function object and associates it with given presenter. - * + * * @param presenter the browser presenter associated with this function - * @since 0.6 + * @since 0.6 */ protected Fn(Presenter presenter) { this.presenter = ref(presenter); @@ -63,17 +63,17 @@ protected Fn(Presenter presenter) { /** True, if currently active presenter is the same as presenter this * function has been created for via {@link #Fn(org.netbeans.html.boot.spi.Fn.Presenter)}. - * + * * @return true, if proper presenter is used */ public final boolean isValid() { return FnContext.currentPresenter(false) == presenter(); } - + /** Helper method to check if the provided instance is valid function. * Checks if the parameter is non-null and if so, does {@link #isValid()} * check. - * + * * @param fnOrNull function or null * @return true if the parameter is non-null and valid * @since 0.7 @@ -84,7 +84,7 @@ public static boolean isValid(Fn fnOrNull) { /** Helper method to find current presenter and ask it to define new * function by calling {@link Presenter#defineFn(java.lang.String, java.lang.String...)}. - * + * * @param caller the class who wishes to define the function * @param code the body of the function (can reference this and names variables) * @param names names of individual parameters @@ -98,7 +98,7 @@ public static Fn define(Class caller, String code, String... names) { /** Helper method to find current presenter and ask it to define new * function. - * + * * @param caller the class who wishes to define the function * @param keepParametersAlive whether Java parameters should survive in JavaScript * after the method invocation is over @@ -127,14 +127,14 @@ public static Fn define(Class caller, boolean keepParametersAlive, String cod } return p.defineFn(code, names); } - + /** Wraps function to ensure that the script represented by resource * gets loaded into the browser environment before the function fn * is executed. - * + * * @param fn original function to call (if null returns null) * @param caller the class who wishes to define/call the function - * @param resource resources (accessible via {@link ClassLoader#getResource(java.lang.String)}) + * @param resource resources (accessible via {@link ClassLoader#getResource(java.lang.String)}) * with a JavaScript that is supposed to loaded into the browser * environment * @return function that ensures the script is loaded and then delegates @@ -148,25 +148,25 @@ public static Fn preload(final Fn fn, final Class caller, final String resour return new Preload(fn.presenter(), fn, resource, caller); } - + /** The currently active presenter. - * + * * @return the currently active presenter or null * @since 0.7 */ public static Presenter activePresenter() { return FnContext.currentPresenter(false); } - - /** Activates given presenter. Used to associate the native - * JavaScript code specified by + + /** Activates given presenter. Used to associate the native + * JavaScript code specified by * {@link JavaScriptBody} annotation with certain presenter: *
      * try ({@link Closeable} c = Fn.activate(presenter)) {
      *   doCallsInPresenterContext();
      * }
      * 
- * + * * @param p the presenter that should be active until closable is closed * @return the closable to close * @throws NullPointerException if the {@code p} is {@code null} @@ -201,10 +201,10 @@ public static Ref ref(final Presenter p) { } return new FallbackIdentity(p); } - + /** Invokes the defined function with specified this and * appropriate arguments. - * + * * @param thiz the meaning of this inside of the JavaScript * function - can be null * @param args arguments for the function @@ -214,9 +214,9 @@ public static Ref ref(final Presenter p) { public abstract Object invoke(Object thiz, Object... args) throws Exception; /** Invokes the defined function with specified this and - * appropriate arguments asynchronously. The invocation may be + * appropriate arguments asynchronously. The invocation may be * happen "later". - * + * * @param thiz the meaning of this inside of the JavaScript * function - can be null * @param args arguments for the function @@ -226,17 +226,17 @@ public static Ref ref(final Presenter p) { public void invokeLater(Object thiz, Object... args) throws Exception { invoke(thiz, args); } - + /** Provides the function implementation access to the presenter provided * in {@link #Fn(org.netbeans.html.boot.spi.Fn.Presenter) the constructor}. - * + * * @return presenter passed in the constructor (may be, but should not be null) * @since 0.7 */ protected final Presenter presenter() { return presenter == null ? null : presenter.presenter(); } - + /** The representation of a presenter - usually a browser window. * Should be provided by a library included in the application and registered * in META-INF/services, for example with @@ -246,7 +246,7 @@ protected final Presenter presenter() { * one as illustrated at: *

* {@codesnippet net.java.html.boot.script.ScriptEngineJavaScriptTCK} - * + * * Since 0.7 a presenter may implement {@link Executor} interface, in case * it supports single threaded execution environment. The executor's * {@link Executor#execute(java.lang.Runnable)} method is then supposed @@ -256,97 +256,97 @@ protected final Presenter presenter() { */ public interface Presenter { /** Creates new function with given parameter names and provided body. - * + * * @param code the body of the function. Can refer to variables named * as names - * @param names names of parameters of the function - these will be + * @param names names of parameters of the function - these will be * available when the code body executes - * + * * @return function that can be later invoked */ public Fn defineFn(String code, String... names); - + /** Opens the browser, loads provided page and when the * page is ready, it calls back to the provider runnable. - * + * * @param page the URL for the page to display * @param onPageLoad callback when the page is ready */ public void displayPage(URL page, Runnable onPageLoad); - - /** Loads a script into the browser JavaScript interpreter and + + /** Loads a script into the browser JavaScript interpreter and * executes it. * @param code the script to execute * @throws Exception if something goes wrong, throw an exception */ public void loadScript(Reader code) throws Exception; } - + /** Additional interface to be implemented by {@link Presenter}s that - * wish to control what objects are passed into the JavaScript virtual + * wish to control what objects are passed into the JavaScript virtual * machine. *

- * If a JavaScript engine makes callback to Java method that returns + * If a JavaScript engine makes callback to Java method that returns * a value, the {@link #toJavaScript(java.lang.Object)} method is * consulted to convert the Java value to something reasonable inside * JavaScript VM. *

* Note: The implementation based on JavaFX WebView * uses this interface to convert Java arrays to JavaScript ones. - * + * * @see Presenter * @since 0.7 */ public interface ToJavaScript { /** Convert a Java return value into some object suitable for * JavaScript virtual machine. - * + * * @param toReturn the Java object to be returned * @return the replacement value to return instead */ public Object toJavaScript(Object toReturn); } - + /** Additional interface to be implemented by {@link Presenter}s that - * need to convert JavaScript object (usually array) to Java object + * need to convert JavaScript object (usually array) to Java object * when calling back from JavaScript to Java. *

* Note: The implementation based on JavaFX * WebView uses this interface to convert JavaScript arrays to * Java ones. - * + * * @since 0.7 */ public interface FromJavaScript { /** Convert a JavaScript object into suitable Java representation * before a Java method is called with this object as an argument. - * + * * @param js the JavaScript object - * @return replacement object for + * @return replacement object for */ public Object toJava(Object js); } /** Additional interface to {@link Presenter} to control more precisely - * garbage collection behavior of individual parameters. See + * garbage collection behavior of individual parameters. See * {@link JavaScriptBody#keepAlive()} attribute for description of the * actual behavior of the interface. - * + * * @since 1.1 */ public interface KeepAlive { /** Creates new function with given parameter names and provided body. - * + * * @param code the body of the function. Can refer to variables named * as names - * @param names names of parameters of the function - these will be + * @param names names of parameters of the function - these will be * available when the code body executes * @param keepAlive array of booleans describing for each parameter * whether it should be kept alive or not. Length of the array * must be the same as length of names array. The * array may be null to signal that all parameters * should be kept alive. - * + * * @return function that can be later invoked */ public Fn defineFn(String code, String[] names, boolean[] keepAlive); @@ -356,7 +356,7 @@ public interface KeepAlive { * in Java. A promise is a microtask to be executed as soon as current * "runnable" finishes. At that moment the {@link #compute()} method * is invoked and the result is remebered. - * + * * @since 1.8 */ public static abstract class Promise { @@ -410,7 +410,7 @@ protected Promise(Fn.Presenter p) { /** * Schedules the promise invocation and returns JavaScript representation * of the promise. - * + * * @return JavaScript {@code Promise} object * @since 1.8 */ @@ -426,7 +426,7 @@ public final Object schedule() { /** Implement in subclasses to perform the "promised" Java operation. * Once this method finishes the system resolves the * {@link #schedule() scheduled promise}. - * + * * @return the value to resolve the promise with * @throws Throwable the exception to resolve the promise with * @since 1.8 @@ -446,7 +446,7 @@ private void resolve() { } } catch (Exception ex) { throw new IllegalStateException(ex); - } + } } } @@ -543,23 +543,34 @@ private void loadResource() throws Exception { LOADED.put(resource, there); } if (there.add(id)) { - final ClassLoader l = caller.getClassLoader(); - InputStream is = l.getResourceAsStream(resource); - if (is == null && resource.startsWith("/")) { - is = l.getResourceAsStream(resource.substring(1)); + var l = caller.getClassLoader(); + var u = l.getResource(resource); + if (u == null) { + u = l.getResource(resource.substring(1)); } - if (is == null) { + if (u == null) { throw new IOException("Cannot find " + resource + " in " + l); } - try { - InputStreamReader r = new InputStreamReader(is, "UTF-8"); + try (var r = new ReaderWithURL(u)) { realPresenter.loadScript(r); - } finally { - is.close(); } } } } + + private static class ReaderWithURL extends InputStreamReader implements Callable { + private final URL u; + + ReaderWithURL(URL u) throws IOException { + super(u.openStream(), "UTF-8"); + this.u = u; + } + + @Override + public URL call() { + return u; + } + } } } diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html index c9e0b57e0..9486d97ad 100644 --- a/src/main/javadoc/overview.html +++ b/src/main/javadoc/overview.html @@ -167,6 +167,8 @@

New in version 1.8.3

{@link net.java.html.sound.AudioClip} plays music again via internal browser audio system - see PR #56 for details. + Debugging of (mostly) tests written in JavaScript improved by + PR #59.

New in version 1.8.2