1+ /*-
2+ * #%L
3+ * Json Migration Helper
4+ * %%
5+ * Copyright (C) 2025 - 2026 Flowing Code
6+ * %%
7+ * Licensed under the Apache License, Version 2.0 (the "License");
8+ * you may not use this file except in compliance with the License.
9+ * You may obtain a copy of the License at
10+ *
11+ * http://www.apache.org/licenses/LICENSE-2.0
12+ *
13+ * Unless required by applicable law or agreed to in writing, software
14+ * distributed under the License is distributed on an "AS IS" BASIS,
15+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+ * See the License for the specific language governing permissions and
17+ * limitations under the License.
18+ * #L%
19+ */
20+ package com .flowingcode .vaadin .jsonmigration ;
21+
22+ import java .nio .file .Files ;
23+ import java .nio .file .Path ;
24+ import java .nio .file .Paths ;
25+ import java .util .Arrays ;
26+ import java .util .Optional ;
27+ import lombok .NonNull ;
28+ import lombok .SneakyThrows ;
29+ import org .objectweb .asm .ClassReader ;
30+ import org .objectweb .asm .ClassVisitor ;
31+ import org .objectweb .asm .ClassWriter ;
32+ import org .objectweb .asm .Opcodes ;
33+ import org .objectweb .asm .Type ;
34+ import org .objectweb .asm .signature .SignatureReader ;
35+ import org .objectweb .asm .signature .SignatureVisitor ;
36+
37+ /**
38+ * Dynamically modifies the class header to implement the JSON interface specified in the
39+ * UnsupportedJsonValueImpl<T> generic argument.
40+ */
41+ public class ElementalNodeAsmPostProcessor {
42+
43+ public static void main (String [] args ) throws Exception {
44+ for (String arg : args ) {
45+ Path classPath = Paths .get (arg );
46+ byte [] b = Files .readAllBytes (classPath );
47+
48+ ClassReader cr = new ClassReader (b );
49+ ClassWriter cw = new ClassWriter (cr , 0 );
50+ ClassVisitorImpl transformer = new ClassVisitorImpl (cw );
51+
52+ cr .accept (transformer , 0 );
53+
54+ if (transformer .modified ) {
55+ Files .write (classPath , cw .toByteArray ());
56+ System .out .println ("Successfully patched: " + classPath .getFileName ());
57+ }
58+ }
59+ }
60+
61+ private static class ClassVisitorImpl extends ClassVisitor {
62+
63+ private final static String TARGET_INTERFACE =
64+ Type .getInternalName (UnsupportedJsonValueImpl .class );
65+
66+ boolean modified ;
67+
68+ public ClassVisitorImpl (ClassVisitor cv ) {
69+ super (Opcodes .ASM9 , cv );
70+ }
71+
72+ @ Override
73+ @ SneakyThrows
74+ public void visit (int version , int access , String name , String signature , String superName ,
75+ String [] interfaces ) {
76+ String detectedInterface = detectInterface (signature );
77+
78+ for (String intf : interfaces ) {
79+ if (intf .equals (detectedInterface )) {
80+ return ;
81+ }
82+ }
83+
84+ modified = true ;
85+ interfaces = Arrays .copyOf (interfaces , interfaces .length + 1 );
86+ interfaces [interfaces .length - 1 ] = detectedInterface ;
87+ super .visit (version , access , name , signature , superName , interfaces );
88+ }
89+
90+ private String detectInterface (@ NonNull String signature ) {
91+ // Extracts the internal name of the specific generic type argument 'T' from
92+ // the class signature implementing UnsupportedJsonValueImpl<T>.
93+ String [] detectedInterface = new String [1 ];
94+ SignatureReader reader = new SignatureReader (signature );
95+ reader .accept (new SignatureVisitor (Opcodes .ASM9 ) {
96+ private boolean insideTargetInterface = false ;
97+
98+ @ Override
99+ public SignatureVisitor visitTypeArgument (char wildcard ) {
100+ // Move into the <T> block
101+ return super .visitTypeArgument (wildcard );
102+ }
103+
104+ @ Override
105+ public SignatureVisitor visitInterface () {
106+ return this ;
107+ }
108+
109+ @ Override
110+ public void visitClassType (String name ) {
111+ if (name .equals (TARGET_INTERFACE )) {
112+ insideTargetInterface = true ;
113+ } else if (insideTargetInterface && detectedInterface [0 ] == null ) {
114+ // This is the first class type found AFTER UnsupportedJsonValueImpl
115+ // which represents the generic argument T
116+ detectedInterface [0 ] = name ;
117+ insideTargetInterface = false ; // Stop looking
118+ }
119+ }
120+
121+ @ Override
122+ public void visitEnd () {
123+ insideTargetInterface = false ;
124+ super .visitEnd ();
125+ }
126+
127+ });
128+ return Optional .ofNullable (detectedInterface [0 ])
129+ .orElseThrow (() -> new IllegalArgumentException ("Failed to extract interface" ));
130+ }
131+
132+ @ Override
133+ public void visitEnd () {
134+ super .visitEnd ();
135+ }
136+
137+ }
138+
139+ }
0 commit comments