From 44c79a125702cb71ecfdf0884c45262e4f9e8f71 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 10 Jan 2025 17:52:58 +0100 Subject: [PATCH 01/13] Change retention of @JavaScriptBody or @JavaScriptResource --- .../html/bootagent/JavaScriptBodyTst.java | 20 ++++++++ .../org/netbeans/html/bootagent/empty.js | 21 +++++++++ .../org/netbeans/html/boot/impl/FnUtils.java | 47 +++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty.js diff --git a/boot-agent-test/src/test/java/org/netbeans/html/bootagent/JavaScriptBodyTst.java b/boot-agent-test/src/test/java/org/netbeans/html/bootagent/JavaScriptBodyTst.java index 0c4c8df3..5e34bf16 100644 --- a/boot-agent-test/src/test/java/org/netbeans/html/bootagent/JavaScriptBodyTst.java +++ b/boot-agent-test/src/test/java/org/netbeans/html/bootagent/JavaScriptBodyTst.java @@ -19,11 +19,13 @@ package org.netbeans.html.bootagent; import net.java.html.js.JavaScriptBody; +import net.java.html.js.JavaScriptResource; /** * * @author Jaroslav Tulach */ +@JavaScriptResource("empty.js") public class JavaScriptBodyTst { public JavaScriptBodyTst() { @@ -33,7 +35,25 @@ public void assert42() { int v = mul(7, 6); assert v == 42 : "Really 42: " + v; } + + public void assertEmptySymbolDefined() { + assert Boolean.TRUE.equals(eval("empty")) : "empty.js should defined empty global symbol"; + } + + public void assertJavaScriptBodyAnnotationPresentInRuntime() throws Exception { + var mul = JavaScriptBodyTst.class.getDeclaredMethod("mul", int.class, int.class); + var ann = mul.getAnnotation(JavaScriptBody.class); + assert ann != null : "JavaScriptBody annotation must be found in runtime"; + } + public void assertJavaScriptResourceAnnotationPresentInRuntime() throws Exception { + var ann = JavaScriptBodyTst.class.getAnnotation(JavaScriptResource.class); + assert ann != null : "JavaScriptResource annotation must be found in runtime"; + } + @JavaScriptBody(args = { "x", "y" }, body = "return x * y;") private static native int mul(int x, int y); + + @JavaScriptBody(args = { "code" }, body = "return eval(code);") + private static native Object eval(String code); } diff --git a/boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty.js b/boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty.js new file mode 100644 index 00000000..b4525b4f --- /dev/null +++ b/boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty.js @@ -0,0 +1,21 @@ +/* + + 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. + +*/ +this.empty = true; diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java index 0071cdd9..0bfefacf 100644 --- a/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java +++ b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java @@ -133,10 +133,15 @@ public void visit(int version, int access, String name, String signature, String @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { final AnnotationVisitor del = super.visitAnnotation(desc, visible); + if ("Ljava/lang/annotation/Retention;".equals(desc)) { + return new RetentionChange(del); + } if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) { + super.visitAnnotation(desc, true); return new LoadResource(del); } if ("Lnet/java/html/js/JavaScriptResource$Group;".equals(desc)) { + super.visitAnnotation(desc, true); return new LoadResource(del); } return del; @@ -179,6 +184,7 @@ public FindInMethod(int access, String name, String desc, MethodVisitor mv) { @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if ("Lnet/java/html/js/JavaScriptBody;".equals(desc)) { // NOI18N + super.visitAnnotation(desc, true); found++; return new FindInAnno(); } @@ -592,6 +598,42 @@ public AnnotationVisitor visitAnnotation(String name, String desc) { return new LoadResource(super.visitAnnotation(name, desc)); } } + + private final class RetentionChange extends AnnotationVisitor { + public RetentionChange(AnnotationVisitor av) { + super(Opcodes.ASM5, av); + } + + @Override + public void visit(String attrName, Object value) { + super.visit(attrName, value); + } + + @Override + public AnnotationVisitor visitArray(String name) { + return super.visitArray(name); + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String desc) { + return super.visitAnnotation(desc, desc); + } + + @Override + public void visitEnum(String value, String retentionPolicy, String type) { + if ("Ljava/lang/annotation/RetentionPolicy;".equals(retentionPolicy)) { + found++; + super.visitEnum(value, retentionPolicy, "RUNTIME"); + } else { + super.visitEnum(value, retentionPolicy, type); + } + } + + @Override + public void visitEnd() { + super.visitEnd(); + } + } } private static class ClassWriterEx extends ClassWriter { @@ -724,6 +766,11 @@ protected Class findClass(String name) throws ClassNotFoundException { is = null; if (JsPkgCache.process(this, name)) { arr = FnUtils.transform(arr, this); + } else switch (name) { + case "net.java.html.js.JavaScriptBody" -> arr = FnUtils.transform(arr, this); + case "net.java.html.js.JavaScriptResource" -> arr = FnUtils.transform(arr, this); + case "net.java.html.js.JavaScriptResource$Group" -> arr = FnUtils.transform(arr, this); + default -> {} } return defineClass(name, arr, 0, arr.length); } catch (IOException ex) { From 649633dbde170dd9de5cde6e9a4009bb4bda599d Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 11 Jan 2025 06:20:30 +0100 Subject: [PATCH 02/13] Duplicating boot-agent-test into dynamic-loader-test --- boot-agent-test/pom.xml | 2 +- dynamic-loader-test/pom.xml | 96 +++++++++++++++++ .../dynamicloader/DynamicClassLoaderTest.java | 102 ++++++++++++++++++ .../html/dynamicloader/JavaScriptBodyTst.java | 59 ++++++++++ .../org/netbeans/html/dynamicloader/KOFx.java | 98 +++++++++++++++++ .../netbeans/html/dynamicloader/empty.html | 31 ++++++ .../org/netbeans/html/dynamicloader/empty.js | 21 ++++ pom.xml | 1 + 8 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 dynamic-loader-test/pom.xml create mode 100644 dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/DynamicClassLoaderTest.java create mode 100644 dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/JavaScriptBodyTst.java create mode 100644 dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/KOFx.java create mode 100644 dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty.html create mode 100644 dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty.js diff --git a/boot-agent-test/pom.xml b/boot-agent-test/pom.xml index 54c4e4a4..e967ce8a 100644 --- a/boot-agent-test/pom.xml +++ b/boot-agent-test/pom.xml @@ -28,7 +28,7 @@ boot-agent-test jar - Dynamic Boot Test + Agent Boot Test ${skipJavaFXTests} diff --git a/dynamic-loader-test/pom.xml b/dynamic-loader-test/pom.xml new file mode 100644 index 00000000..a7ecfcf9 --- /dev/null +++ b/dynamic-loader-test/pom.xml @@ -0,0 +1,96 @@ + + + + 4.0.0 + + org.netbeans.html + pom + 2.0-SNAPSHOT + + dynamic-loader-test + jar + Dynamic ClassLoader Test + + ${skipJavaFXTests} + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-enforcer-plugin + + true + + + + + + + org.testng + testng + test + + + org.netbeans.html + net.java.html + ${project.version} + test + jar + + + org.netbeans.html + net.java.html.boot + ${project.version} + test + jar + + + org.netbeans.api + org-openide-util-lookup + test + jar + + + ${project.groupId} + net.java.html.boot.fx + ${project.version} + test + + + org.openjfx + javafx-web + test + + + org.ow2.asm + asm + test + + + \ No newline at end of file diff --git a/dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/DynamicClassLoaderTest.java b/dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/DynamicClassLoaderTest.java new file mode 100644 index 00000000..95dd4f6b --- /dev/null +++ b/dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/DynamicClassLoaderTest.java @@ -0,0 +1,102 @@ +/** + * 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 org.netbeans.html.dynamicloader; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import net.java.html.BrwsrCtx; +import net.java.html.boot.BrowserBuilder; +import org.netbeans.html.boot.spi.Fn; +import org.testng.Assert; +import static org.testng.Assert.assertNotSame; +import static org.testng.Assert.assertSame; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class DynamicClassLoaderTest { + private static Class browserClass; + private static Fn.Presenter browserPresenter; + + public DynamicClassLoaderTest() { + } + + @Factory public static Object[] compatibilityTests() throws Exception { + final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(DynamicClassLoaderTest.class). + loadPage("empty.html"). + invoke("initialized"); + + Executors.newSingleThreadExecutor().submit(new Runnable() { + @Override + public void run() { + bb.showAndWait(); + } + }); + + List res = new ArrayList(); + + Class[] arr = new Class[] { loadClass() }; + for (Class c : arr) { + for (Method m : c.getDeclaredMethods()) { + if ((m.getModifiers() & Modifier.PUBLIC) != 0) { + res.add(new KOFx(browserPresenter, m)); + } + } + } + return res.toArray(); + } + + static synchronized Class loadClass() throws InterruptedException { + while (browserClass == null) { + DynamicClassLoaderTest.class.wait(); + } + return browserClass; + } + + public static void ready(Class browserCls) throws Exception { + Class origClazz = ClassLoader.getSystemClassLoader().loadClass(DynamicClassLoaderTest.class.getName()); + final Field f1 = origClazz.getDeclaredField("browserClass"); + f1.setAccessible(true); + f1.set(null, browserCls); + final Field f2 = origClazz.getDeclaredField("browserPresenter"); + f2.setAccessible(true); + f2.set(null, Fn.activePresenter()); + synchronized (origClazz) { + origClazz.notifyAll(); + } + } + + public static void initialized() throws Exception { + BrwsrCtx b1 = BrwsrCtx.findDefault(DynamicClassLoaderTest.class); + assertNotSame(b1, BrwsrCtx.EMPTY, "Browser context is not empty"); + BrwsrCtx b2 = BrwsrCtx.findDefault(DynamicClassLoaderTest.class); + assertSame(b1, b2, "Browser context remains stable"); + Assert.assertNotSame(DynamicClassLoaderTest.class.getClassLoader(), + ClassLoader.getSystemClassLoader(), + "Should use special classloader, not system one" + ); + DynamicClassLoaderTest.ready(JavaScriptBodyTst.class); + } +} diff --git a/dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/JavaScriptBodyTst.java b/dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/JavaScriptBodyTst.java new file mode 100644 index 00000000..fa4ab1e8 --- /dev/null +++ b/dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/JavaScriptBodyTst.java @@ -0,0 +1,59 @@ +/** + * 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 org.netbeans.html.dynamicloader; + +import net.java.html.js.JavaScriptBody; +import net.java.html.js.JavaScriptResource; + +/** + * + * @author Jaroslav Tulach + */ +@JavaScriptResource("empty.js") +public class JavaScriptBodyTst { + + public JavaScriptBodyTst() { + } + + public void assert42() { + int v = mul(7, 6); + assert v == 42 : "Really 42: " + v; + } + + public void assertEmptySymbolDefined() { + assert Boolean.TRUE.equals(eval("empty")) : "empty.js should defined empty global symbol"; + } + + public void assertJavaScriptBodyAnnotationPresentInRuntime() throws Exception { + var mul = JavaScriptBodyTst.class.getDeclaredMethod("mul", int.class, int.class); + var ann = mul.getAnnotation(JavaScriptBody.class); + assert ann != null : "JavaScriptBody annotation must be found in runtime"; + } + + public void assertJavaScriptResourceAnnotationPresentInRuntime() throws Exception { + var ann = JavaScriptBodyTst.class.getAnnotation(JavaScriptResource.class); + assert ann != null : "JavaScriptResource annotation must be found in runtime"; + } + + @JavaScriptBody(args = { "x", "y" }, body = "return x * y;") + private static native int mul(int x, int y); + + @JavaScriptBody(args = { "code" }, body = "return eval(code);") + private static native Object eval(String code); +} diff --git a/dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/KOFx.java b/dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/KOFx.java new file mode 100644 index 00000000..0d8196c0 --- /dev/null +++ b/dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/KOFx.java @@ -0,0 +1,98 @@ +/** + * 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 org.netbeans.html.dynamicloader; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javafx.application.Platform; +import org.netbeans.html.boot.spi.Fn; +import org.testng.IHookCallBack; +import org.testng.IHookable; +import org.testng.ITest; +import org.testng.ITestResult; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public final class KOFx implements ITest, IHookable, Runnable { + private final Fn.Presenter p; + private final Method m; + private Object result; + private Object inst; + + KOFx(Fn.Presenter p, Method m) { + this.p = p; + this.m = m; + } + + @Override + public String getTestName() { + return m.getName(); + } + + @Test + public synchronized void executeTest() throws Exception { + if (result == null) { + Platform.runLater(this); + wait(); + } + if (result instanceof Exception) { + throw (Exception)result; + } + if (result instanceof Error) { + throw (Error)result; + } + } + + @Override + public synchronized void run() { + boolean notify = true; + try (var ctx = Fn.activate(p)) { + if (inst == null) { + inst = m.getDeclaringClass().newInstance(); + } + result = m.invoke(inst); + if (result == null) { + result = this; + } + } catch (InvocationTargetException ex) { + Throwable r = ex.getTargetException(); + if (r instanceof InterruptedException) { + notify = false; + Platform.runLater(this); + return; + } + result = r; + } catch (Exception ex) { + result = ex; + } finally { + if (notify) { + notifyAll(); + } + } + } + + @Override + public void run(IHookCallBack ihcb, ITestResult itr) { + ihcb.runTestMethod(itr); + } + +} diff --git a/dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty.html b/dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty.html new file mode 100644 index 00000000..31e63db5 --- /dev/null +++ b/dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty.html @@ -0,0 +1,31 @@ + + + + + Bootstrap Dynamically + + + + +
Bootstrap Dynamically
+ + diff --git a/dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty.js b/dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty.js new file mode 100644 index 00000000..b4525b4f --- /dev/null +++ b/dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty.js @@ -0,0 +1,21 @@ +/* + + 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. + +*/ +this.empty = true; diff --git a/pom.xml b/pom.xml index de3c2ecd..87c8f623 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,7 @@ + dynamic-loader-test boot-agent-test xhr4j From f9a542d7c78022dd6dd67097ba02c9f2d705d75a Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 11 Jan 2025 06:46:23 +0100 Subject: [PATCH 03/13] Using Scripts.newPresenter() to execute agent tests --- boot-agent-test/pom.xml | 10 +- .../html/bootagent/AgentBootstrapTest.java | 53 +++++++++ .../bootagent/DynamicClassLoaderTest.java | 102 ------------------ .../org/netbeans/html/bootagent/KOFx.java | 6 +- 4 files changed, 64 insertions(+), 107 deletions(-) create mode 100644 boot-agent-test/src/test/java/org/netbeans/html/bootagent/AgentBootstrapTest.java delete mode 100644 boot-agent-test/src/test/java/org/netbeans/html/bootagent/DynamicClassLoaderTest.java diff --git a/boot-agent-test/pom.xml b/boot-agent-test/pom.xml index e967ce8a..f9fbb0f6 100644 --- a/boot-agent-test/pom.xml +++ b/boot-agent-test/pom.xml @@ -78,7 +78,7 @@ ${project.groupId} - net.java.html.boot.fx + net.java.html.boot.script ${project.version} test @@ -92,5 +92,13 @@ asm test + + org.graalvm.js + js + + + org.graalvm.js + js-scriptengine + \ No newline at end of file diff --git a/boot-agent-test/src/test/java/org/netbeans/html/bootagent/AgentBootstrapTest.java b/boot-agent-test/src/test/java/org/netbeans/html/bootagent/AgentBootstrapTest.java new file mode 100644 index 00000000..7854566d --- /dev/null +++ b/boot-agent-test/src/test/java/org/netbeans/html/bootagent/AgentBootstrapTest.java @@ -0,0 +1,53 @@ +/** + * 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 org.netbeans.html.bootagent; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import net.java.html.boot.script.Scripts; +import static org.testng.AssertJUnit.assertNotNull; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class AgentBootstrapTest { + public AgentBootstrapTest() { + } + + @Factory + public static Object[] compatibilityTests() throws Exception { + var presenter = Scripts.newPresenter().build(); + assertNotNull("Presenter has been initialized", presenter); + + var res = new ArrayList(); + + Class[] arr = new Class[] { JavaScriptBodyTst.class }; + for (Class c : arr) { + for (Method m : c.getDeclaredMethods()) { + if ((m.getModifiers() & Modifier.PUBLIC) != 0) { + res.add(new KOFx(presenter, m)); + } + } + } + return res.toArray(); + } +} diff --git a/boot-agent-test/src/test/java/org/netbeans/html/bootagent/DynamicClassLoaderTest.java b/boot-agent-test/src/test/java/org/netbeans/html/bootagent/DynamicClassLoaderTest.java deleted file mode 100644 index b9fbc809..00000000 --- a/boot-agent-test/src/test/java/org/netbeans/html/bootagent/DynamicClassLoaderTest.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * 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 org.netbeans.html.bootagent; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executors; -import net.java.html.BrwsrCtx; -import net.java.html.boot.BrowserBuilder; -import org.netbeans.html.boot.spi.Fn; -import org.testng.Assert; -import static org.testng.Assert.assertNotSame; -import static org.testng.Assert.assertSame; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class DynamicClassLoaderTest { - private static Class browserClass; - private static Fn.Presenter browserPresenter; - - public DynamicClassLoaderTest() { - } - - @Factory public static Object[] compatibilityTests() throws Exception { - final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(DynamicClassLoaderTest.class). - loadPage("empty.html"). - invoke("initialized"); - - Executors.newSingleThreadExecutor().submit(new Runnable() { - @Override - public void run() { - bb.showAndWait(); - } - }); - - List res = new ArrayList(); - - Class[] arr = new Class[] { loadClass() }; - for (Class c : arr) { - for (Method m : c.getDeclaredMethods()) { - if ((m.getModifiers() & Modifier.PUBLIC) != 0) { - res.add(new KOFx(browserPresenter, m)); - } - } - } - return res.toArray(); - } - - static synchronized Class loadClass() throws InterruptedException { - while (browserClass == null) { - DynamicClassLoaderTest.class.wait(); - } - return browserClass; - } - - public static void ready(Class browserCls) throws Exception { - Class origClazz = ClassLoader.getSystemClassLoader().loadClass(DynamicClassLoaderTest.class.getName()); - final Field f1 = origClazz.getDeclaredField("browserClass"); - f1.setAccessible(true); - f1.set(null, browserCls); - final Field f2 = origClazz.getDeclaredField("browserPresenter"); - f2.setAccessible(true); - f2.set(null, Fn.activePresenter()); - synchronized (origClazz) { - origClazz.notifyAll(); - } - } - - public static void initialized() throws Exception { - BrwsrCtx b1 = BrwsrCtx.findDefault(DynamicClassLoaderTest.class); - assertNotSame(b1, BrwsrCtx.EMPTY, "Browser context is not empty"); - BrwsrCtx b2 = BrwsrCtx.findDefault(DynamicClassLoaderTest.class); - assertSame(b1, b2, "Browser context remains stable"); - Assert.assertNotSame(DynamicClassLoaderTest.class.getClassLoader(), - ClassLoader.getSystemClassLoader(), - "Should use special classloader, not system one" - ); - DynamicClassLoaderTest.ready(JavaScriptBodyTst.class); - } -} diff --git a/boot-agent-test/src/test/java/org/netbeans/html/bootagent/KOFx.java b/boot-agent-test/src/test/java/org/netbeans/html/bootagent/KOFx.java index e65b880f..60bc3f55 100644 --- a/boot-agent-test/src/test/java/org/netbeans/html/bootagent/KOFx.java +++ b/boot-agent-test/src/test/java/org/netbeans/html/bootagent/KOFx.java @@ -20,7 +20,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import javafx.application.Platform; import org.netbeans.html.boot.spi.Fn; import org.testng.IHookCallBack; import org.testng.IHookable; @@ -51,8 +50,7 @@ public String getTestName() { @Test public synchronized void executeTest() throws Exception { if (result == null) { - Platform.runLater(this); - wait(); + run(); } if (result instanceof Exception) { throw (Exception)result; @@ -77,7 +75,7 @@ public synchronized void run() { Throwable r = ex.getTargetException(); if (r instanceof InterruptedException) { notify = false; - Platform.runLater(this); + run(); return; } result = r; From d3b498be8dea6de878d918ca9747a5ad03f74d80 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 11 Jan 2025 07:09:09 +0100 Subject: [PATCH 04/13] Don't warn about Graal.js running in interpreter only mode --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 87c8f623..0b291935 100644 --- a/pom.xml +++ b/pom.xml @@ -264,6 +264,7 @@ org.netbeans.html.boot.impl:org.netbeans.html.boot.fx:org.netbeans.html.context. true + false From 6e0259fe5c74f63b45c226432c5203cae3398032 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 11 Jan 2025 07:32:10 +0100 Subject: [PATCH 05/13] Handle both dots and slashes in class names --- .../org/netbeans/html/boot/impl/FnUtils.java | 5 --- .../netbeans/html/boot/impl/JsPkgCache.java | 39 ++++++++++++------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java index 0bfefacf..fbeb9230 100644 --- a/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java +++ b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java @@ -766,11 +766,6 @@ protected Class findClass(String name) throws ClassNotFoundException { is = null; if (JsPkgCache.process(this, name)) { arr = FnUtils.transform(arr, this); - } else switch (name) { - case "net.java.html.js.JavaScriptBody" -> arr = FnUtils.transform(arr, this); - case "net.java.html.js.JavaScriptResource" -> arr = FnUtils.transform(arr, this); - case "net.java.html.js.JavaScriptResource$Group" -> arr = FnUtils.transform(arr, this); - default -> {} } return defineClass(name, arr, 0, arr.length); } catch (IOException ex) { diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java b/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java index 6d68d7dc..0e715f4e 100644 --- a/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java @@ -36,14 +36,27 @@ * @author Jaroslav Tulach */ final class JsPkgCache { - private final Map> props = new WeakHashMap>(); - private static final Map CACHE = new WeakHashMap(); + private static final Logger LOG = Logger.getLogger(JsPkgCache.class.getName()); + private final Map> props = new WeakHashMap<>(); + private static final Map CACHE = new WeakHashMap<>(); private static final Set NONE = Collections.emptySet(); - public static boolean process(ClassLoader l, String className) { - if (className.equals("org.netbeans.html.boot.impl.Test")) { // NOI18N - return true; + public static boolean process(ClassLoader loader, String dotOrSlashClassName) { + if (loader == null) { + return false; } + var slashClassName = dotOrSlashClassName.replace('.', '/'); + var className = dotOrSlashClassName.replace('/', '.'); + return switch (className) { + case "net.java.html.js.JavaScriptBody" -> true; // NOI18N + case "net.java.html.js.JavaScriptResource" -> true; // NOI18N + case "net.java.html.js.JavaScriptResource$Group" -> true; // NOI18N; + case "org.netbeans.html.boot.impl.Test" -> true; // NOI18N + default -> packageCheck(loader, slashClassName, className); + }; + } + + private static boolean packageCheck(ClassLoader l, String slashClassName, String dotClassName) { Set p; JsPkgCache c; String pkgName; @@ -53,17 +66,17 @@ public static boolean process(ClassLoader l, String className) { c = new JsPkgCache(); CACHE.put(l, c); } - int lastDot = className.lastIndexOf('.'); - pkgName = className.substring(0, lastDot + 1).replace('.', '/'); + int lastDot = slashClassName.lastIndexOf('/'); + pkgName = slashClassName.substring(0, lastDot + 1); p = c.props.get(pkgName); if (p == NONE) { return false; } else if (p != null) { - return p.contains(className); + return p.contains(dotClassName); } } final String res = pkgName + "net.java.html.js.classes"; - + Enumeration en; try { en = l.getResources(res); @@ -76,7 +89,7 @@ public static boolean process(ClassLoader l, String className) { } try { - Set arr = new TreeSet(); + Set arr = new TreeSet<>(); while (en.hasMoreElements()) { URL u = en.nextElement(); BufferedReader r = new BufferedReader( @@ -96,12 +109,10 @@ public static boolean process(ClassLoader l, String className) { LOG.log(Level.WARNING, "Can't read " + res, ex); p = NONE; } - + synchronized (CACHE) { c.props.put(pkgName, p); - return p.contains(className); + return p.contains(dotClassName); } - } - private static final Logger LOG = Logger.getLogger(JsPkgCache.class.getName()); } From 4f4abfbf5cefcb3af71ee9e86ad58800adad0f42 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 11 Jan 2025 07:37:28 +0100 Subject: [PATCH 06/13] Enable net.java.html.boot.jar as -javaagent when running boot-agent-tests --- boot-agent-test/pom.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/boot-agent-test/pom.xml b/boot-agent-test/pom.xml index f9fbb0f6..34fceaf8 100644 --- a/boot-agent-test/pom.xml +++ b/boot-agent-test/pom.xml @@ -48,6 +48,23 @@ true + + maven-dependency-plugin + + + + properties + + + + + + maven-surefire-plugin + 3.2.5 + + -javaagent:${org.netbeans.html:net.java.html.boot:jar} + + From 3905182c74f3fde279c87aa83b8ece7822cd5d90 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 11 Jan 2025 08:53:49 +0100 Subject: [PATCH 07/13] -javaagent copies content of all the annotations. DynamicClassLoader doesn't change the retention. --- .../html/bootagent/JavaScriptBodyTst.java | 34 ++++++- .../org/netbeans/html/bootagent/empty_2.js | 21 ++++ .../org/netbeans/html/bootagent/empty_3.js | 21 ++++ .../org/netbeans/html/boot/impl/FnUtils.java | 97 ++++++++++++++++--- .../org/netbeans/html/boot/impl/JsAgent.java | 2 +- .../html/dynamicloader/JavaScriptBodyTst.java | 25 ++++- .../netbeans/html/dynamicloader/empty_2.js | 21 ++++ .../netbeans/html/dynamicloader/empty_3.js | 21 ++++ .../html/mojo/ProcessJsAnnotations.java | 2 - .../java/html/js/tests/ResourceOrderTest.java | 17 +++- 10 files changed, 237 insertions(+), 24 deletions(-) create mode 100644 boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty_2.js create mode 100644 boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty_3.js create mode 100644 dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty_2.js create mode 100644 dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty_3.js diff --git a/boot-agent-test/src/test/java/org/netbeans/html/bootagent/JavaScriptBodyTst.java b/boot-agent-test/src/test/java/org/netbeans/html/bootagent/JavaScriptBodyTst.java index 5e34bf16..1d5ebbe9 100644 --- a/boot-agent-test/src/test/java/org/netbeans/html/bootagent/JavaScriptBodyTst.java +++ b/boot-agent-test/src/test/java/org/netbeans/html/bootagent/JavaScriptBodyTst.java @@ -18,6 +18,7 @@ */ package org.netbeans.html.bootagent; +import java.util.Arrays; import net.java.html.js.JavaScriptBody; import net.java.html.js.JavaScriptResource; @@ -39,16 +40,39 @@ public void assert42() { public void assertEmptySymbolDefined() { assert Boolean.TRUE.equals(eval("empty")) : "empty.js should defined empty global symbol"; } + public void assertEmpty2SymbolDefined() { + MultiResource.loadIt(); + assert Boolean.TRUE.equals(eval("empty2")) : "empty.js should defined empty global symbol"; + } + public void assertEmpty3SymbolDefined() { + MultiResource.loadIt(); + assert Boolean.TRUE.equals(eval("empty3")) : "empty.js should defined empty global symbol"; + } public void assertJavaScriptBodyAnnotationPresentInRuntime() throws Exception { var mul = JavaScriptBodyTst.class.getDeclaredMethod("mul", int.class, int.class); var ann = mul.getAnnotation(JavaScriptBody.class); assert ann != null : "JavaScriptBody annotation must be found in runtime"; + assert ann.args().length == 2 : "Two arguments"; + assert "x".equals(ann.args()[0]) : "First argument: " + Arrays.toString(ann.args()); + assert "y".equals(ann.args()[1]) : "Second argument: " + Arrays.toString(ann.args()); + assert "return x * y;".equals(ann.body()) : "Body argument: " + ann.body(); + } + + private static void assertResource(JavaScriptResource ann, String file) { + assert ann != null : "JavaScriptResource annotation must be found in runtime"; + assert file.equals(ann.value()) : "Expecting " + file + " but got " + ann.value(); } public void assertJavaScriptResourceAnnotationPresentInRuntime() throws Exception { var ann = JavaScriptBodyTst.class.getAnnotation(JavaScriptResource.class); - assert ann != null : "JavaScriptResource annotation must be found in runtime"; + assertResource(ann, "empty.js"); + } + + public void assertJavaScriptResourceGroupAnnotationPresentInRuntime() throws Exception { + var ann = MultiResource.class.getAnnotation(JavaScriptResource.Group.class); + assert ann != null : "JavaScriptResource.Group annotation must be found in runtime"; + assert ann.value().length == 2 : "Two empty*.js resources, was: " + Arrays.toString(ann.value()); } @JavaScriptBody(args = { "x", "y" }, body = "return x * y;") @@ -56,4 +80,12 @@ public void assertJavaScriptResourceAnnotationPresentInRuntime() throws Exceptio @JavaScriptBody(args = { "code" }, body = "return eval(code);") private static native Object eval(String code); + + @JavaScriptResource("empty_2.js") + @JavaScriptResource("empty_3.js") + static final class MultiResource { + @JavaScriptBody(args = {}, body = "") + static void loadIt() { + } + } } diff --git a/boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty_2.js b/boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty_2.js new file mode 100644 index 00000000..ceb668f1 --- /dev/null +++ b/boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty_2.js @@ -0,0 +1,21 @@ +/* + + 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. + +*/ +this.empty2 = true; diff --git a/boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty_3.js b/boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty_3.js new file mode 100644 index 00000000..ddecd53b --- /dev/null +++ b/boot-agent-test/src/test/resources/org/netbeans/html/bootagent/empty_3.js @@ -0,0 +1,21 @@ +/* + + 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. + +*/ +this.empty3 = true; diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java index fbeb9230..2b39d661 100644 --- a/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java +++ b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java @@ -21,6 +21,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.URL; import java.util.ArrayList; import java.util.Collections; @@ -62,6 +64,21 @@ private FnUtils() { * @since 0.7 */ public static byte[] transform(byte[] bytecode, ClassLoader loader) { + return transform(bytecode, loader, false); + } + /** Seeks for {@link JavaScriptBody} and {@link JavaScriptResource} annotations + * in the bytecode and converts them into real code. Used by Maven plugin + * postprocessing classes. + * + * @param bytecode the original bytecode with javascript specific annotations + * @param loader the loader to load resources (scripts and classes) when needed + * @param makeVisible {@code true} if the {@link Retention} of the + * {@link JavaScriptBody} and {@link JavaScriptResource} annotations + * should be changed to {@link RetentionPolicy#RUNTIME} + * @return the transformed bytecode + * @since 1.8.2 + */ + public static byte[] transform(byte[] bytecode, ClassLoader loader, boolean makeVisible) { ClassReader cr = new ClassReader(bytecode) { // to allow us to compile with -profile compact1 on // JDK8 while processing the class as JDK7, the highest @@ -75,11 +92,11 @@ public short readShort(int index) { return s; } }; - FindInClass tst = new FindInClass(loader, null); + FindInClass tst = new FindInClass(loader, null, makeVisible); cr.accept(tst, 0); if (tst.found > 0) { ClassWriter w = new ClassWriterEx(loader, cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - FindInClass fic = new FindInClass(loader, w); + FindInClass fic = new FindInClass(loader, w, makeVisible); cr.accept(fic, 0); bytecode = w.toByteArray(); } @@ -119,9 +136,11 @@ private static final class FindInClass extends ClassVisitor { private int found; private int resourcesCnt = 0; private final String[] resources = new String[256]; + private final boolean makeVisible; - public FindInClass(ClassLoader l, ClassVisitor cv) { + FindInClass(ClassLoader l, ClassVisitor cv, boolean makeVisible) { super(Opcodes.ASM5, cv); + this.makeVisible = makeVisible; } @Override @@ -134,15 +153,15 @@ public void visit(int version, int access, String name, String signature, String public AnnotationVisitor visitAnnotation(String desc, boolean visible) { final AnnotationVisitor del = super.visitAnnotation(desc, visible); if ("Ljava/lang/annotation/Retention;".equals(desc)) { - return new RetentionChange(del); + return makeVisible ? new RetentionChange(del) : del; } if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) { - super.visitAnnotation(desc, true); - return new LoadResource(del); + var visibleDel = makeVisible ? super.visitAnnotation(desc, true) : null; + return new LoadResource(DoubleVisitor.combine(del, visibleDel)); } if ("Lnet/java/html/js/JavaScriptResource$Group;".equals(desc)) { - super.visitAnnotation(desc, true); - return new LoadResource(del); + var visibleDel = makeVisible ? super.visitAnnotation(desc, true) : null; + return new LoadResource(DoubleVisitor.combine(del, visibleDel)); } return del; } @@ -184,9 +203,9 @@ public FindInMethod(int access, String name, String desc, MethodVisitor mv) { @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if ("Lnet/java/html/js/JavaScriptBody;".equals(desc)) { // NOI18N - super.visitAnnotation(desc, true); + var visibleDel = makeVisible ? super.visitAnnotation(desc, true) : null; found++; - return new FindInAnno(); + return DoubleVisitor.combine(new FindInAnno(), visibleDel); } return super.visitAnnotation(desc, visible); } @@ -510,7 +529,7 @@ private final class FindInAnno extends AnnotationVisitor { Boolean wait4java; Boolean keepAlive; - public FindInAnno() { + FindInAnno() { super(Opcodes.ASM5); } @@ -570,8 +589,60 @@ boolean keepAlive() { } } + private static final class DoubleVisitor extends AnnotationVisitor { + private final AnnotationVisitor a2; + + private DoubleVisitor(AnnotationVisitor a1, AnnotationVisitor a2) { + super(Opcodes.ASM5, a1); + assert a2 != null; + this.a2 = a2; + } + + static AnnotationVisitor combine(AnnotationVisitor a1, AnnotationVisitor a2) { + if (a1 == null) { + return a2; + } else if (a2 == null) { + return a1; + } else { + return new DoubleVisitor(a1, a2); + } + } + + @Override + public void visitEnd() { + super.visitEnd(); + a2.visitEnd(); + } + + @Override + public AnnotationVisitor visitArray(String name) { + var v1 = super.visitArray(name); + var v2 = a2.visitArray(name); + return DoubleVisitor.combine(v1, v2); + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String desc) { + var v1 = super.visitAnnotation(name, desc); + var v2 = a2.visitAnnotation(name, desc); + return DoubleVisitor.combine(v1, v2); + } + + @Override + public void visitEnum(String name, String desc, String value) { + super.visitEnum(name, desc, value); + a2.visitEnum(name, desc, value); + } + + @Override + public void visit(String name, Object value) { + super.visit(name, value); + a2.visit(name, value); + } + } + private final class LoadResource extends AnnotationVisitor { - public LoadResource(AnnotationVisitor av) { + LoadResource(AnnotationVisitor av) { super(Opcodes.ASM5, av); } @@ -600,7 +671,7 @@ public AnnotationVisitor visitAnnotation(String name, String desc) { } private final class RetentionChange extends AnnotationVisitor { - public RetentionChange(AnnotationVisitor av) { + RetentionChange(AnnotationVisitor av) { super(Opcodes.ASM5, av); } diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java b/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java index a2df117a..8e2af0e8 100644 --- a/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java @@ -40,7 +40,7 @@ public static void agentmain(String args, Instrumentation instr) { public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { try { if (JsPkgCache.process(loader, className)) { - return FnUtils.transform(classfileBuffer, loader); + return FnUtils.transform(classfileBuffer, loader, true); } else { return classfileBuffer; } diff --git a/dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/JavaScriptBodyTst.java b/dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/JavaScriptBodyTst.java index fa4ab1e8..b9423679 100644 --- a/dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/JavaScriptBodyTst.java +++ b/dynamic-loader-test/src/test/java/org/netbeans/html/dynamicloader/JavaScriptBodyTst.java @@ -39,16 +39,29 @@ public void assert42() { public void assertEmptySymbolDefined() { assert Boolean.TRUE.equals(eval("empty")) : "empty.js should defined empty global symbol"; } + public void assertEmpty2SymbolDefined() { + MultiResource.loadIt(); + assert Boolean.TRUE.equals(eval("empty2")) : "empty.js should defined empty global symbol"; + } + public void assertEmpty3SymbolDefined() { + MultiResource.loadIt(); + assert Boolean.TRUE.equals(eval("empty3")) : "empty.js should defined empty global symbol"; + } public void assertJavaScriptBodyAnnotationPresentInRuntime() throws Exception { var mul = JavaScriptBodyTst.class.getDeclaredMethod("mul", int.class, int.class); var ann = mul.getAnnotation(JavaScriptBody.class); - assert ann != null : "JavaScriptBody annotation must be found in runtime"; + assert ann == null : "DynamicClassLoader doesn't modify retention unlike -javaagent usage"; } public void assertJavaScriptResourceAnnotationPresentInRuntime() throws Exception { var ann = JavaScriptBodyTst.class.getAnnotation(JavaScriptResource.class); - assert ann != null : "JavaScriptResource annotation must be found in runtime"; + assert ann == null : "DynamicClassLoader doesn't modify retention unlike -javaagent usage"; + } + + public void assertJavaScriptResourceGroupAnnotationPresentInRuntime() throws Exception { + var ann = MultiResource.class.getAnnotation(JavaScriptResource.Group.class); + assert ann == null : "DynamicClassLoader doesn't modify retention unlike -javaagent usage"; } @JavaScriptBody(args = { "x", "y" }, body = "return x * y;") @@ -56,4 +69,12 @@ public void assertJavaScriptResourceAnnotationPresentInRuntime() throws Exceptio @JavaScriptBody(args = { "code" }, body = "return eval(code);") private static native Object eval(String code); + + @JavaScriptResource("empty_2.js") + @JavaScriptResource("empty_3.js") + static final class MultiResource { + @JavaScriptBody(args = {}, body = "") + static void loadIt() { + } + } } diff --git a/dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty_2.js b/dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty_2.js new file mode 100644 index 00000000..ceb668f1 --- /dev/null +++ b/dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty_2.js @@ -0,0 +1,21 @@ +/* + + 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. + +*/ +this.empty2 = true; diff --git a/dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty_3.js b/dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty_3.js new file mode 100644 index 00000000..ddecd53b --- /dev/null +++ b/dynamic-loader-test/src/test/resources/org/netbeans/html/dynamicloader/empty_3.js @@ -0,0 +1,21 @@ +/* + + 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. + +*/ +this.empty3 = true; diff --git a/html4j-maven-plugin/src/main/java/org/netbeans/html/mojo/ProcessJsAnnotations.java b/html4j-maven-plugin/src/main/java/org/netbeans/html/mojo/ProcessJsAnnotations.java index 43f4eba0..d9db453e 100644 --- a/html4j-maven-plugin/src/main/java/org/netbeans/html/mojo/ProcessJsAnnotations.java +++ b/html4j-maven-plugin/src/main/java/org/netbeans/html/mojo/ProcessJsAnnotations.java @@ -38,8 +38,6 @@ import java.util.List; import java.util.Set; import java.util.TreeSet; -import java.util.logging.Level; -import java.util.logging.Logger; import org.objectweb.asm.ClassReader; abstract class ProcessJsAnnotations { diff --git a/json-tck/src/test/java/net/java/html/js/tests/ResourceOrderTest.java b/json-tck/src/test/java/net/java/html/js/tests/ResourceOrderTest.java index 59fbec85..f9728d20 100644 --- a/json-tck/src/test/java/net/java/html/js/tests/ResourceOrderTest.java +++ b/json-tck/src/test/java/net/java/html/js/tests/ResourceOrderTest.java @@ -27,6 +27,7 @@ import org.objectweb.asm.Opcodes; import org.testng.Assert; import static org.testng.Assert.assertEquals; +import static org.testng.AssertJUnit.assertTrue; import org.testng.annotations.Test; public class ResourceOrderTest { @@ -39,7 +40,8 @@ public void testLoadData() throws Exception { InputStream is = ResourceOrder.class.getResourceAsStream("ResourceOrder.class"); int[] valueCount = { 0 }; - List values = new ArrayList<>(); + List visibleValues = new ArrayList<>(); + List hiddenValues = new ArrayList<>(); ClassReader r = new ClassReader(is); r.accept(new ClassVisitor(Opcodes.ASM5) { @@ -101,6 +103,7 @@ public void visitEnum(String name, String desc, String value) { public void visit(String name, Object value) { Assert.assertEquals(name, "value"); Assert.assertTrue(value instanceof String, "It is a string: " + value); + var values = visible ? visibleValues : hiddenValues; values.add((String) value); } }; @@ -124,10 +127,14 @@ public void visitEnd() { }; } }, 0); + assertValues("Hidden values found", hiddenValues); + assertTrue("No visible visible values after processing with Maven plugin: " + visibleValues, visibleValues.isEmpty()); + } - assertEquals(values.size(), 3, "There are there elements: " + values); - assertEquals(values.get(0), "initArray.js"); - assertEquals(values.get(1), "addHello.js"); - assertEquals(values.get(2), "addWorld.js"); + private void assertValues(String prefix, List values) { + assertEquals(values.size(), 3, prefix + ": There are there elements: " + values); + assertEquals(values.get(0), "initArray.js", prefix + " 0"); + assertEquals(values.get(1), "addHello.js", prefix + " 1"); + assertEquals(values.get(2), "addWorld.js", prefix + " 2"); } } From 7f81ea6adc08a4855fce3f64193458ec84f3116c Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 11 Jan 2025 09:27:19 +0100 Subject: [PATCH 08/13] Documenting that -javaagent changes retention of @JavaScriptBody & co. annotations --- .../net/java/html/boot/BrowserBuilder.java | 6 +- .../main/java/net/java/html/js/package.html | 210 ++++++++++-------- 2 files changed, 118 insertions(+), 98 deletions(-) diff --git a/boot/src/main/java/net/java/html/boot/BrowserBuilder.java b/boot/src/main/java/net/java/html/boot/BrowserBuilder.java index c732dc22..9636398d 100644 --- a/boot/src/main/java/net/java/html/boot/BrowserBuilder.java +++ b/boot/src/main/java/net/java/html/boot/BrowserBuilder.java @@ -103,9 +103,9 @@ private BrowserBuilder(Object[] context) { * {@link Contexts#newBuilder(java.lang.Object...) created} * and can influence the selection * of available technologies - * (like {@link org.netbeans.html.json.spi.Technology}, - * {@link org.netbeans.html.json.spi.Transfer} or - * {@link org.netbeans.html.json.spi.WSTransfer}) by name. + * (like {@code org.netbeans.html.json.spi.Technology}, + * {@code org.netbeans.html.json.spi.Transfer} or + * {@code org.netbeans.html.json.spi.WSTransfer}) by name. * * @param context any instances that should be available to the builder - * implementation dependant diff --git a/boot/src/main/java/net/java/html/js/package.html b/boot/src/main/java/net/java/html/js/package.html index 4523d234..84805938 100644 --- a/boot/src/main/java/net/java/html/js/package.html +++ b/boot/src/main/java/net/java/html/js/package.html @@ -25,10 +25,10 @@ Mix your Java and JavaScript code seamlessly - perform calls from Java to JavaScript and back with as much freedom as JavaScript gives you and as much type safety you can get from Java. Execute your code - in a headless testing environment or in a - JavaFX WebView. + in a headless testing environment or in a + JavaFX WebView. When done, deploy to real browsers. - +

Simple Meaning of World

The whole support is build around @JavaScriptBody annotation. Use it to create parametrised JavaScript snippet easily @@ -42,7 +42,7 @@

Simple Meaning of World

The meaning method now becomes a properly typed Java surface to your JavaScript code which can be directly called from the rest of your Java code: -
        
+
 public static void main(String... args) {
   assert 42 == meaning(40, 2) : "Meaning of World should be 42!";
 }
@@ -53,24 +53,24 @@ 

Simple Meaning of World

Bodies.

Editing hint: one can see the list of arguments of the - meaning is now duplicated - it is once specified in Java, - and once inside of the {@link net.java.html.js.JavaScriptBody} - array of args. This is necessary to keep the names of + meaning is now duplicated - it is once specified in Java, + and once inside of the {@link net.java.html.js.JavaScriptBody} + array of args. This is necessary to keep the names of arguments accessible during runtime. However don't despair - there is a code completion for the value of args attribute! Just type the Java signature first and then press Ctrl+Space and the right parameter names will be inserted for you. - +

Including JavaScript Libraries

Large amount of JavaScript code is easier to be delivered in whole files rather than {@link net.java.html.js.JavaScriptBody small code snippets} - that is also possible thanks to {@link net.java.html.js.JavaScriptResource} annotation. Imagine file mul.js with following content: -
 
+
 function mul(x, y) { return x * y; }
 
- Place the file next to your class and reference it with + Place the file next to your class and reference it with {@link net.java.html.js.JavaScriptResource the annotation}:
 {@code @}{@link net.java.html.js.JavaScriptResource}("mul.js") class Mul {
@@ -90,38 +90,38 @@ 

Simple Meaning of World

Real code tip: this - is the way + is the way the knockout.js library is included in its ko4j library. - +

Callback to Java

- + Often JavaScript code needs to call back into the Java classes. For example when a button in a browser is pressed and your code would like to invoke a runnable to handle such situation: -
 
-{@code @}{@link net.java.html.js.JavaScriptBody}(args = {"id", "r"}, {@link net.java.html.js.JavaScriptBody#javacall() javacall} = true, body = "\n" + 
-"       document.getElementById(id).onclick = function() {\n" + 
-"        r.{@code @}java.lang.Runnable::run()();\n" + 
-"       };\n" + 
+
+{@code @}{@link net.java.html.js.JavaScriptBody}(args = {"id", "r"}, {@link net.java.html.js.JavaScriptBody#javacall() javacall} = true, body = "\n" +
+"       document.getElementById(id).onclick = function() {\n" +
+"        r.{@code @}java.lang.Runnable::run()();\n" +
+"       };\n" +
 "    ")
 public static native void onClick(String id, Runnable r);
 
- As can be seen, there is a special syntax (starting with @) to + As can be seen, there is a special syntax (starting with @) to properly identify the right Java method to call on a Java object passed into the JavaScript interpreter. The syntax starts with a fully - qualified name of the class, followed by :: and name of the + qualified name of the class, followed by :: and name of the method including signature of its parameters. In case of runnable, this is just () as the method has no parameters, but the signature can be more complicated. For example in case of following method
static int compare(int i1, String s1, int i2, String s2)
-
+
it would be (ILjava/lang/String;ILjava/lang/String;) (btw. the - return type is not included in the signature). The actual parameters + return type is not included in the signature). The actual parameters then follows. The JavaScript call to such compare method would then look like:
{@code @}the.pkg.Clazz::compare(ILjava/lang/String;ILjava/lang/String;)(1, 'One', 2, 'Two');
-
+
This syntax gives enough flexibility, helps to properly select one of overloaded methods and follows the tradition of previous attempts to provide JavaScript to Java calling conventions. @@ -133,15 +133,15 @@

Callback to Java

[instance.]@classname::methodname(signature)(arguments)
    -
  • instance - must be present when calling an - instance method and must be absent when calling a +
  • instance - must be present when calling an + instance method and must be absent when calling a static method
  • -
  • classname - fully qualified name of the class in - which the method is declared +
  • classname - fully qualified name of the class in + which the method is declared
  • -
  • signature - internal JVM method signature - (as specified at - JNI type Signatures) +
  • signature - internal JVM method signature + (as specified at + JNI type Signatures) without the trailing signature of the method return type
  • arguments - the actual values to pass to the called Java method
  • @@ -149,9 +149,9 @@

    Callback to Java

    Here is the JNI type signatures table one can use to convert - Java parameters to JVM's internal letter based + Java parameters to JVM's internal letter based representation:

    - + @@ -201,19 +201,19 @@

    Callback to Java

    Type Signature

    Editing hint: The callback syntax may seem complicated at - first, however there is an associated annotation processor + first, however there is an associated annotation processor that checks the syntax and verifies the referenced class and method with the requested signature exist. If it does not, the - compilation fails offering correct alternatives. + compilation fails offering correct alternatives. Thus don't despair seeing the syntax, make sure you get the - fully qualified name of the callback class right. - You'll get warning and help + fully qualified name of the callback class right. + You'll get warning and help if there is a typo in the specified signature then - during compilation of your code. - +

    Overloaded Methods

    - - Specifying the actual callback signature is important in case of + + Specifying the actual callback signature is important in case of overloaded methods. Imagine a class:
     package x.y.z;
    @@ -227,8 +227,8 @@ 

    Overloaded Methods

    int pulse(long evenMore) { return (int) (5 + evenMore); } -}
    - you then need to choose in {@link net.java.html.js.JavaScriptBody} +}
+ you then need to choose in {@link net.java.html.js.JavaScriptBody} the appropriate method to call:
 {@code @}{@link net.java.html.js.JavaScriptBody}(args = { "h" }, javacall = true, // you want to process the @ syntax
@@ -242,17 +242,17 @@ 

Overloaded Methods

}

- To avoid ambiguity, the specification of the correct signature is + To avoid ambiguity, the specification of the correct signature is required on every call. However, to simplify the development, there is an annotation processor to verify the signature really refers to an existing method.

- +

Arrays by Copy

- + It is possible to exchange arrays between Java and JavaScript. Some implementations can pass arrays by reference, however in some systems - this is hard to achieve. To choose the least common denominator, + this is hard to achieve. To choose the least common denominator, the TCK for behavior of {@link net.java.html.js.JavaScriptBody} requires the arrays to be always transfered by a copy. As such following code:
@@ -262,33 +262,33 @@ 

Arrays by Copy

String[] hello = { "Hello", "World!" }; uselessModify(arr); System.out.println(arr[0] + " " + arr[1]); -} +}
will still print Hello World! in spite the JavaScript code sets the 0-th array element to null. Because the array is passed as a copy, such assignment has no effect on the Java array.

- In case one needs to modify an array in a JavaScript and use its + In case one needs to modify an array in a JavaScript and use its values in Java, one has to return the array back as a return value: -
        
+        
 {@code @}{@link net.java.html.js.JavaScriptBody}(args = {"arr"}, body = "arr[0] = 'Ahoy'; return arr;")
 private static native Object[] usefulModify(String[] arr);
 public static void main(String... args) {
   String[] hello = { "Hello", "World!" };
   Object[] ret = usefulModify(arr);
   System.out.println(ret[0] + " " + ret[1]);
-}            
+}
 
now the program prints Ahoy World! as the modified array is returned back and converted (by a copy) into a Java Object[] (but of course the ret != hello). Usually the copy based - passing of arrays works OK. It is however good to keep it in mind to + passing of arrays works OK. It is however good to keep it in mind to avoid unwanted surprises. - +

Instance Reference to JavaScript Object

- + When writing wrappers around existing JavaScript libraries, it may be - useful to hold a reference to some JavaScript object from a Java + useful to hold a reference to some JavaScript object from a Java instance and use it later.
 class WrapperAroundJsObj {
@@ -309,12 +309,12 @@ 

Instance Reference to JavaScript Object

args = { "js", "v" }, body = "js.value = v;", wait4js = false ) private static native void setValue(Object js, int v); -} -
- The type of the Java reference is {@link java.lang.Object}. +} +
+ The type of the Java reference is {@link java.lang.Object}. From a Java perspective it has no additional methods or fields, however its properties can be manipulated from JavaScript. Send the object back - to JavaScript by passing it as a parameter of some method + to JavaScript by passing it as a parameter of some method (like the setValue one) and perform necessary JavaScript calls or changes on it. @@ -340,22 +340,26 @@

undefined === null

}
This is the behavior since version 1.4. - +

Post Process Classes

Classes with {@link net.java.html.js.JavaScriptBody} annotated methods need to be post processed before they can be used - e.g. their native - body needs to be generated to call into JavaScript (btw. the code is performed + body needs to be generated to call into JavaScript (btw. the call is performed via {@link org.netbeans.html.boot.spi.Fn}). There are three ways - such post processing can happen. + such a post processing can be achieved.

- Compile time processing - this is the preferred method that - most of the Html Java APIs are using. + +

Compile time

+ + + The Compile time is the preferred method of post-processing that + most of the Html Java APIs are using. Just include following plugin configuration into your pom.xml and your classes will be ready for execution as soon as process-classes Maven phase is over: -
 
+
 <plugin>
     <groupId>org.netbeans.html</groupId>
     <artifactId>html4j-maven-plugin</artifactId>
@@ -370,33 +374,49 @@ 

Post Process Classes

</executions> </plugin>
- This plugin works in orchestration with + This plugin works in orchestration with annotation processor associated with {@link net.java.html.js.JavaScriptBody} and {@link net.java.html.js.JavaScriptResource} - the processor creates - list of files that need post-processing. The + list of files that need post-processing. The Maven - plugin reads these files, processes classes mentioned in them and + plugin reads these files, processes classes mentioned in them and modifies (and deletes at the end) the files to not include classes already processed.

- Instrumentation Agent - one can do processing in runtime + +

Instrumentation Agent

+ + + Instrumentation Agent can perform the post processing in runtime using JDK's {@link java.lang.instrument.ClassFileTransformer instrumentation} - abilities. The JAR artifact of org.netbeans.html:net.java.html.boot - contains an Agent-Class and Premain-Class - definitions in its manifest. As such one can launch the Java virtual + abilities. The JAR artifact of + org.netbeans.html:net.java.html.boot + contains an Agent-Class and Premain-Class + attributes in its manifest. As such one can launch the Java virtual machine with
 $ java -javaagent:jarpath=net.java.html.boot-x.y.jar
-
- and the runtime will take care of processing bytecode of classes - not yet processed in compile time before they are loaded into the - virtual machine. +
+ and the runtime will take care of processing bytecode of classes + before they are loaded into the + virtual machine. Since version 1.8.2 this agent also changes retention + of {@link net.java.html.js.JavaScriptBody} + and {@link net.java.html.js.JavaScriptResource} annotations + to {@link java.lang.annotation.RetentionPolicy#RUNTIME}. + As such technologies that need + access to these annotations during runtime can do so by using the + -javaagent JVM switch.

- Special classloading - when booting your application with + +

Special classloading

+ + + There is a special implementation of dynamic classloader. + When booting your application with {@link net.java.html.boot.BrowserBuilder} there is a 3rd option of processing the classes. If there are some classes not yet processed - (remember the files listing them generated by the + (remember the files listing them generated by the annotation processor), the {@link net.java.html.boot.BrowserBuilder#showAndWait() launching method} will create a special classloader to that does the processing before @@ -406,16 +426,16 @@

Post Process Classes

processing needs to also include asm-5.0.jar on application classpath), it is recommended to perform the compile time processing. - +

Getting Started

- - There are many ways to start developing - Html for Java application. + + There are many ways to start developing + Html for Java application. However to be sure one chooses the most recent setup, it is recommended - to switch to good old command line and use a + to switch to good old command line and use a Maven archetype associated with every version of this project. Just type: -
      
+
 $ mvn archetype:generate \
  -DarchetypeGroupId=org.apidesign.html \
  -DarchetypeArtifactId=knockout4j-archetype \
@@ -426,10 +446,10 @@ 

Getting Started

$ cd myfirstbrwsrpage $ mvn process-classes exec:java
- In a few seconds (or minutes if + In a few seconds (or minutes if Maven - decides to download the whole Internet of dependencies) you should - see a sample Hello World application. It is basically composed from one + decides to download the whole Internet of dependencies) you should + see a sample Hello World application. It is basically composed from one Java and one HTML file:
 $ ls src/main/java/**/DataModel.java
@@ -437,22 +457,22 @@ 

Getting Started

Play with them, modify them and enjoy Html for Java! - +

Mixed Java/JavaScript Debugging

- +

- The following video shows how easy it is to use - NetBeans 8.0, JDK8 to debug an application that intermixes Java - and JavaScript calls. One can put breakpoints into Java part, - as well as JavaScript source code, inspect Java as well - as JavaScript variables and switch between these two + The following video shows how easy it is to use + NetBeans 8.0, JDK8 to debug an application that intermixes Java + and JavaScript calls. One can put breakpoints into Java part, + as well as JavaScript source code, inspect Java as well + as JavaScript variables and switch between these two languages without any restrictions.

- - From c6153f5ad6b20a69a7fc490ca1bf860263081b03 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 11 Jan 2025 09:55:23 +0100 Subject: [PATCH 09/13] Avoid initialization of Logger. It can deadlock. --- .../main/java/org/netbeans/html/boot/impl/JsPkgCache.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java b/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java index 0e715f4e..49c5f116 100644 --- a/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java @@ -28,15 +28,12 @@ import java.util.Set; import java.util.TreeSet; import java.util.WeakHashMap; -import java.util.logging.Level; -import java.util.logging.Logger; /** * * @author Jaroslav Tulach */ final class JsPkgCache { - private static final Logger LOG = Logger.getLogger(JsPkgCache.class.getName()); private final Map> props = new WeakHashMap<>(); private static final Map CACHE = new WeakHashMap<>(); private static final Set NONE = Collections.emptySet(); @@ -106,7 +103,8 @@ private static boolean packageCheck(ClassLoader l, String slashClassName, String } p = arr; } catch (IOException ex) { - LOG.log(Level.WARNING, "Can't read " + res, ex); + System.err.println("Cannot read: " + res); + ex.printStackTrace(); p = NONE; } From e7e932f2344ed2ba61ac0bc7bccc18315900d725 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 11 Jan 2025 11:30:38 +0100 Subject: [PATCH 10/13] Let JsAgent process all suspected classes and carry its own copy of ASM --- .gitignore | 1 + boot-agent-test/pom.xml | 16 ---------- boot/pom.xml | 30 +++++++++++++++++-- .../org/netbeans/html/boot/impl/JsAgent.java | 20 +++++++++++-- dynamic-loader-test/pom.xml | 11 ------- pom.xml | 1 + 6 files changed, 48 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 89717446..57bb5cea 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /target/ *.orig */nb-configuration.xml +*/dependency-reduced-pom.xml */nbactions.xml /html4j-maven-plugin/src/test/resources/org/netbeans/html/mojo/gradle*/build/* /html4j-maven-plugin/src/test/resources/org/netbeans/html/mojo/gradle*/.gradle/* diff --git a/boot-agent-test/pom.xml b/boot-agent-test/pom.xml index 34fceaf8..188742a3 100644 --- a/boot-agent-test/pom.xml +++ b/boot-agent-test/pom.xml @@ -87,28 +87,12 @@ test jar - - org.netbeans.api - org-openide-util-lookup - test - jar - ${project.groupId} net.java.html.boot.script ${project.version} test - - org.openjfx - javafx-web - test - - - org.ow2.asm - asm - test - org.graalvm.js js diff --git a/boot/pom.xml b/boot/pom.xml index 19968a5b..6ef37757 100644 --- a/boot/pom.xml +++ b/boot/pom.xml @@ -50,6 +50,34 @@ + + org.apache.maven.plugins + maven-enforcer-plugin + + true + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + package + + shade + + + + + org.objectweb.asm + org.netbeans.html.boot.asm5 + + + + + + @@ -57,8 +85,6 @@ org.ow2.asm asm jar - provided - true org.testng diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java b/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java index 8e2af0e8..8f16b444 100644 --- a/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java @@ -21,6 +21,7 @@ import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; +import java.nio.charset.StandardCharsets; import java.security.ProtectionDomain; /** @@ -39,13 +40,28 @@ public static void agentmain(String args, Instrumentation instr) { @Override public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { try { - if (JsPkgCache.process(loader, className)) { + if (containsJavaBodies(classfileBuffer)) { return FnUtils.transform(classfileBuffer, loader, true); } else { return classfileBuffer; } - } catch (Exception ex) { + } catch (Error | Exception ex) { + System.err.println("Error transforming " + className); + ex.printStackTrace(); return classfileBuffer; } } + + private static final byte[] PATTERN = new String("net/java/html/js/").getBytes(StandardCharsets.UTF_8); + private static boolean containsJavaBodies(byte[] arr) { + NOT_FOUND: for (var i = 0; i < arr.length - PATTERN.length; i++) { + for (var j = 0; j < PATTERN.length; j++) { + if (arr[i + j] != PATTERN[j]) { + continue NOT_FOUND; + } + } + return true; + } + return false; + } } diff --git a/dynamic-loader-test/pom.xml b/dynamic-loader-test/pom.xml index a7ecfcf9..0b873797 100644 --- a/dynamic-loader-test/pom.xml +++ b/dynamic-loader-test/pom.xml @@ -70,12 +70,6 @@ test jar - - org.netbeans.api - org-openide-util-lookup - test - jar - ${project.groupId} net.java.html.boot.fx @@ -87,10 +81,5 @@ javafx-web test - - org.ow2.asm - asm - test - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0b291935..519dfd6d 100644 --- a/pom.xml +++ b/pom.xml @@ -226,6 +226,7 @@ org.netbeans.html.boot.impl:org.netbeans.html.boot.fx:org.netbeans.html.context. **/.maven/** **/*.sigtest **/nb-configuration.xml + **/dependency-reduced-pom.xml **/nbactions.xml README.md DEPENDENCIES From 1451316115c74d7d242f7bc42ec695f8daa1e0ad Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 11 Jan 2025 11:50:20 +0100 Subject: [PATCH 11/13] Excluding (to be) shared ASM dependency from all projects that depend on net.java.html.boot --- boot-fx/pom.xml | 6 ++++++ boot-script/pom.xml | 6 ++++++ generic/pom.xml | 6 ++++++ geo/pom.xml | 6 ++++++ ko4j/pom.xml | 6 ++++++ renderer/pom.xml | 6 ++++++ sound/pom.xml | 6 ++++++ webkit/pom.xml | 6 ++++++ 8 files changed, 48 insertions(+) diff --git a/boot-fx/pom.xml b/boot-fx/pom.xml index 25e510f7..2a5379d5 100644 --- a/boot-fx/pom.xml +++ b/boot-fx/pom.xml @@ -66,6 +66,12 @@ net.java.html.boot ${project.version} jar + + + org.ow2.asm + asm + + org.testng diff --git a/boot-script/pom.xml b/boot-script/pom.xml index 692225b2..a630aa73 100644 --- a/boot-script/pom.xml +++ b/boot-script/pom.xml @@ -72,6 +72,12 @@ net.java.html.boot ${project.version} jar + + + org.ow2.asm + asm + + org.testng diff --git a/generic/pom.xml b/generic/pom.xml index 670365ff..e481335b 100644 --- a/generic/pom.xml +++ b/generic/pom.xml @@ -46,6 +46,12 @@ net.java.html.boot jar ${project.version} + + + org.ow2.asm + asm + + org.testng diff --git a/geo/pom.xml b/geo/pom.xml index 928ece31..2acd1cc4 100644 --- a/geo/pom.xml +++ b/geo/pom.xml @@ -64,6 +64,12 @@ net.java.html.boot ${project.version} jar + + + org.ow2.asm + asm + + Find out where your Java program running in an HTML page is! diff --git a/ko4j/pom.xml b/ko4j/pom.xml index e1b99507..a2eca98a 100644 --- a/ko4j/pom.xml +++ b/ko4j/pom.xml @@ -96,6 +96,12 @@ net.java.html.boot ${project.version} jar + + + org.ow2.asm + asm + + ${project.groupId} diff --git a/renderer/pom.xml b/renderer/pom.xml index 3438c8ec..619afad6 100644 --- a/renderer/pom.xml +++ b/renderer/pom.xml @@ -64,6 +64,12 @@ org.netbeans.html net.java.html.boot ${project.version} + + + org.ow2.asm + asm + + \ No newline at end of file diff --git a/sound/pom.xml b/sound/pom.xml index 705ed211..777e8e5a 100644 --- a/sound/pom.xml +++ b/sound/pom.xml @@ -95,6 +95,12 @@ net.java.html.boot ${project.version} jar + + + org.ow2.asm + asm + + org.netbeans.api diff --git a/webkit/pom.xml b/webkit/pom.xml index 92ffd2d9..154946e9 100644 --- a/webkit/pom.xml +++ b/webkit/pom.xml @@ -100,6 +100,12 @@ net.java.html.boot ${project.version} jar + + + org.ow2.asm + asm + + org.netbeans.api From 1c2d653ac59e4f770648ac55dc9b93e218445171 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 11 Jan 2025 12:13:07 +0100 Subject: [PATCH 12/13] macos-10.15 image label should be updated to ... up to macOS-13 with intel --- .github/workflows/mac.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 4d78c435..a70fd01c 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -32,7 +32,7 @@ jobs: strategy: matrix: java: [ '8', '11', '17', '21' ] - os: [ macos-10.15 ] + os: [ macos-13 ] steps: - uses: actions/checkout@v2 From 8a0b704a5174186dc8a9946287b8bf5b737dad2f Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 11 Jan 2025 14:03:22 +0100 Subject: [PATCH 13/13] What's new in 1.8.2? --- src/main/javadoc/overview.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html index 90d1b209..182e2fc7 100644 --- a/src/main/javadoc/overview.html +++ b/src/main/javadoc/overview.html @@ -163,6 +163,17 @@

Try it

vm.loadClass('org.apidesign.demo.minesweeper.MainBrwsr'); +

New in version 1.8.2

+

+ When post-processing classes with -javaagent instrumenting agent + - read the + details here + - the RetentionType of + {@link net.java.html.js.JavaScriptBody} and related annotations + is changed to RUNTIME. Implemented by + PR #54. +

+

New in version 1.8.1

This is a bugfix release focused on improving