diff --git a/pom.xml b/pom.xml index 926b2f2..323d646 100644 --- a/pom.xml +++ b/pom.xml @@ -136,6 +136,30 @@ + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + process-classes + + java + + + com.flowingcode.vaadin.jsonmigration.ElementalNodeAsmPostProcessor + + ${project.build.outputDirectory}/com/flowingcode/vaadin/jsonmigration/ElementalArrayNode.class + ${project.build.outputDirectory}/com/flowingcode/vaadin/jsonmigration/ElementalBooleanNode.class + ${project.build.outputDirectory}/com/flowingcode/vaadin/jsonmigration/ElementalNullNode.class + ${project.build.outputDirectory}/com/flowingcode/vaadin/jsonmigration/ElementalNumberNode.class + ${project.build.outputDirectory}/com/flowingcode/vaadin/jsonmigration/ElementalObjectNode.class + ${project.build.outputDirectory}/com/flowingcode/vaadin/jsonmigration/ElementalStringNode.class + + + + + org.apache.maven.plugins maven-jar-plugin @@ -216,6 +240,9 @@ true none true + + **/ElementalNodeAsmPostProcessor.java + https://javadoc.io/doc/com.vaadin/vaadin-platform-javadoc/${vaadin.version} @@ -229,6 +256,8 @@ META-INF/VAADIN/config/flow-build-info.json + com/flowingcode/vaadin/jsonmigration/ElementalNodeAsmPostProcessor.class + com/flowingcode/vaadin/jsonmigration/ElementalNodeAsmPostProcessor$*.class diff --git a/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalArrayNode.java b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalArrayNode.java index cd05efd..121199e 100644 --- a/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalArrayNode.java +++ b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalArrayNode.java @@ -2,7 +2,7 @@ * #%L * Json Migration Helper * %% - * Copyright (C) 2025 Flowing Code + * Copyright (C) 2025 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import tools.jackson.databind.node.JsonNodeFactory; @SuppressWarnings("serial") -class ElementalArrayNode extends ArrayNode implements UnsupportedJsonValueImpl { +class ElementalArrayNode extends ArrayNode implements UnsupportedJsonValueImpl { public ElementalArrayNode(JsonArray a) { super(JsonNodeFactory.instance, children(a)); diff --git a/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalBooleanNode.java b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalBooleanNode.java index ab0ed4c..b2e9d64 100644 --- a/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalBooleanNode.java +++ b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalBooleanNode.java @@ -2,7 +2,7 @@ * #%L * Json Migration Helper * %% - * Copyright (C) 2025 Flowing Code + * Copyright (C) 2025 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,12 @@ */ package com.flowingcode.vaadin.jsonmigration; +import elemental.json.JsonBoolean; import elemental.json.JsonType; import tools.jackson.databind.node.BooleanNode; @SuppressWarnings("serial") -class ElementalBooleanNode extends BooleanNode implements UnsupportedJsonValueImpl { +class ElementalBooleanNode extends BooleanNode implements UnsupportedJsonValueImpl { public ElementalBooleanNode(boolean value) { super(value); diff --git a/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalNodeAsmPostProcessor.java b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalNodeAsmPostProcessor.java new file mode 100644 index 0000000..25db747 --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalNodeAsmPostProcessor.java @@ -0,0 +1,139 @@ +/*- + * #%L + * Json Migration Helper + * %% + * Copyright (C) 2025 - 2026 Flowing Code + * %% + * Licensed 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. + * #L% + */ +package com.flowingcode.vaadin.jsonmigration; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Optional; +import lombok.NonNull; +import lombok.SneakyThrows; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; + +/** + * Dynamically modifies the class header to implement the JSON interface specified in the + * UnsupportedJsonValueImpl generic argument. + */ +public class ElementalNodeAsmPostProcessor { + + public static void main(String[] args) throws Exception { + for (String arg : args) { + Path classPath = Paths.get(arg); + byte[] b = Files.readAllBytes(classPath); + + ClassReader cr = new ClassReader(b); + ClassWriter cw = new ClassWriter(cr, 0); + ClassVisitorImpl transformer = new ClassVisitorImpl(cw); + + cr.accept(transformer, 0); + + if (transformer.modified) { + Files.write(classPath, cw.toByteArray()); + System.out.println("Successfully patched: " + classPath.getFileName()); + } + } + } + + private static class ClassVisitorImpl extends ClassVisitor { + + private final static String TARGET_INTERFACE = + Type.getInternalName(UnsupportedJsonValueImpl.class); + + boolean modified; + + public ClassVisitorImpl(ClassVisitor cv) { + super(Opcodes.ASM9, cv); + } + + @Override + @SneakyThrows + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + String detectedInterface = detectInterface(signature); + + for (String intf : interfaces) { + if (intf.equals(detectedInterface)) { + return; + } + } + + modified = true; + interfaces = Arrays.copyOf(interfaces, interfaces.length + 1); + interfaces[interfaces.length - 1] = detectedInterface; + super.visit(version, access, name, signature, superName, interfaces); + } + + private String detectInterface(@NonNull String signature) { + // Extracts the internal name of the specific generic type argument 'T' from + // the class signature implementing UnsupportedJsonValueImpl. + String[] detectedInterface = new String[1]; + SignatureReader reader = new SignatureReader(signature); + reader.accept(new SignatureVisitor(Opcodes.ASM9) { + private boolean insideTargetInterface = false; + + @Override + public SignatureVisitor visitTypeArgument(char wildcard) { + // Move into the block + return super.visitTypeArgument(wildcard); + } + + @Override + public SignatureVisitor visitInterface() { + return this; + } + + @Override + public void visitClassType(String name) { + if (name.equals(TARGET_INTERFACE)) { + insideTargetInterface = true; + } else if (insideTargetInterface && detectedInterface[0] == null) { + // This is the first class type found AFTER UnsupportedJsonValueImpl + // which represents the generic argument T + detectedInterface[0] = name; + insideTargetInterface = false; // Stop looking + } + } + + @Override + public void visitEnd() { + insideTargetInterface = false; + super.visitEnd(); + } + + }); + return Optional.ofNullable(detectedInterface[0]) + .orElseThrow(() -> new IllegalArgumentException("Failed to extract interface")); + } + + @Override + public void visitEnd() { + super.visitEnd(); + } + + } + +} \ No newline at end of file diff --git a/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalNullNode.java b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalNullNode.java index 67c9d69..1009a2f 100644 --- a/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalNullNode.java +++ b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalNullNode.java @@ -2,7 +2,7 @@ * #%L * Json Migration Helper * %% - * Copyright (C) 2025 Flowing Code + * Copyright (C) 2025 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,12 @@ */ package com.flowingcode.vaadin.jsonmigration; +import elemental.json.JsonNull; import elemental.json.JsonType; import tools.jackson.databind.node.NullNode; @SuppressWarnings("serial") -class ElementalNullNode extends NullNode implements UnsupportedJsonValueImpl { +class ElementalNullNode extends NullNode implements UnsupportedJsonValueImpl { public ElementalNullNode() { super(); diff --git a/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalNumberNode.java b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalNumberNode.java index fa7bcb7..339d7f5 100644 --- a/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalNumberNode.java +++ b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalNumberNode.java @@ -2,7 +2,7 @@ * #%L * Json Migration Helper * %% - * Copyright (C) 2025 Flowing Code + * Copyright (C) 2025 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,12 @@ */ package com.flowingcode.vaadin.jsonmigration; +import elemental.json.JsonNumber; import elemental.json.JsonType; import tools.jackson.databind.node.DoubleNode; @SuppressWarnings("serial") -class ElementalNumberNode extends DoubleNode implements UnsupportedJsonValueImpl { +class ElementalNumberNode extends DoubleNode implements UnsupportedJsonValueImpl { public ElementalNumberNode(double value) { super(value); diff --git a/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalObjectNode.java b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalObjectNode.java index eba4cac..cd09fa5 100644 --- a/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalObjectNode.java +++ b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalObjectNode.java @@ -2,7 +2,7 @@ * #%L * Json Migration Helper * %% - * Copyright (C) 2025 Flowing Code + * Copyright (C) 2025 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ import tools.jackson.databind.node.ObjectNode; @SuppressWarnings("serial") -class ElementalObjectNode extends ObjectNode implements UnsupportedJsonValueImpl { +class ElementalObjectNode extends ObjectNode implements UnsupportedJsonValueImpl { public ElementalObjectNode(JsonObject o) { super(JsonNodeFactory.instance, children(o)); diff --git a/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalStringNode.java b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalStringNode.java index 7c33b09..b7ec8ca 100644 --- a/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalStringNode.java +++ b/src/main/java/com/flowingcode/vaadin/jsonmigration/ElementalStringNode.java @@ -2,7 +2,7 @@ * #%L * Json Migration Helper * %% - * Copyright (C) 2025 Flowing Code + * Copyright (C) 2025 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,12 @@ */ package com.flowingcode.vaadin.jsonmigration; +import elemental.json.JsonString; import elemental.json.JsonType; import tools.jackson.databind.node.StringNode; @SuppressWarnings("serial") -class ElementalStringNode extends StringNode implements UnsupportedJsonValueImpl { +class ElementalStringNode extends StringNode implements UnsupportedJsonValueImpl { public ElementalStringNode(String value) { super(value); diff --git a/src/main/java/com/flowingcode/vaadin/jsonmigration/UnsupportedJsonValueImpl.java b/src/main/java/com/flowingcode/vaadin/jsonmigration/UnsupportedJsonValueImpl.java index 9b81252..71932f9 100644 --- a/src/main/java/com/flowingcode/vaadin/jsonmigration/UnsupportedJsonValueImpl.java +++ b/src/main/java/com/flowingcode/vaadin/jsonmigration/UnsupportedJsonValueImpl.java @@ -2,7 +2,7 @@ * #%L * Json Migration Helper * %% - * Copyright (C) 2025 Flowing Code + * Copyright (C) 2025 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import elemental.json.JsonValue; import tools.jackson.databind.JsonNode; -interface UnsupportedJsonValueImpl extends JsonValue { +interface UnsupportedJsonValueImpl extends JsonValue { @Override default boolean asBoolean() { diff --git a/src/test/java/com/flowingcode/vaadin/jsonmigration/JsonMigrationHelper25Test.java b/src/test/java/com/flowingcode/vaadin/jsonmigration/JsonMigrationHelper25Test.java new file mode 100644 index 0000000..fea722e --- /dev/null +++ b/src/test/java/com/flowingcode/vaadin/jsonmigration/JsonMigrationHelper25Test.java @@ -0,0 +1,93 @@ +/*- + * #%L + * Json Migration Helper + * %% + * Copyright (C) 2025 - 2026 Flowing Code + * %% + * Licensed 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. + * #L% + */ +package com.flowingcode.vaadin.jsonmigration; + +import static org.junit.Assert.assertTrue; +import elemental.json.Json; +import elemental.json.JsonArray; +import elemental.json.JsonBoolean; +import elemental.json.JsonNull; +import elemental.json.JsonNumber; +import elemental.json.JsonObject; +import elemental.json.JsonString; +import elemental.json.JsonValue; +import org.junit.Test; + +public class JsonMigrationHelper25Test { + + private final JsonMigrationHelper25 helper = new JsonMigrationHelper25(); + + @Test + public void testConvertToClientCallableResult_JsonObject() { + JsonObject input = Json.createObject(); + input.put("key", "value"); + + JsonValue result = helper.convertToClientCallableResult(input); + + assertTrue("Result should be an instance of JsonObject", result instanceof JsonObject); + } + + @Test + public void testConvertToClientCallableResult_JsonArray() { + JsonArray input = Json.createArray(); + input.set(0, "value"); + + JsonValue result = helper.convertToClientCallableResult(input); + + assertTrue("Result should be an instance of JsonArray", result instanceof JsonArray); + } + + @Test + public void testConvertToClientCallableResult_JsonBoolean() { + JsonBoolean input = Json.create(true); + + JsonValue result = helper.convertToClientCallableResult(input); + + assertTrue("Result should be an instance of JsonBoolean", result instanceof JsonBoolean); + } + + @Test + public void testConvertToClientCallableResult_JsonString() { + JsonString input = Json.create("test"); + + JsonValue result = helper.convertToClientCallableResult(input); + + assertTrue("Result should be an instance of JsonString", result instanceof JsonString); + } + + @Test + public void testConvertToClientCallableResult_JsonNumber() { + JsonNumber input = Json.create(42); + + JsonValue result = helper.convertToClientCallableResult(input); + + assertTrue("Result should be an instance of JsonNumber", result instanceof JsonNumber); + } + + @Test + public void testConvertToClientCallableResult_JsonNull() { + JsonNull input = Json.createNull(); + + JsonValue result = helper.convertToClientCallableResult(input); + + assertTrue("Result should be an instance of JsonNull", result instanceof JsonNull); + } + +}