From 4872dd137a35684cce36bb55acb7157545a765f1 Mon Sep 17 00:00:00 2001
From: Volker Berlin <volker.berlin@googlemail.com>
Date: Sun, 19 Jan 2020 15:15:01 +0100
Subject: [PATCH] Add support for partial classes

---
 .../inetsoftware/classparser/ClassFile.java   | 50 +++++++++++++++++--
 .../inetsoftware/classparser/MethodInfo.java  | 13 ++++-
 .../jwebassembly/JWebAssembly.java            |  5 ++
 .../jwebassembly/module/ClassFileLoader.java  | 22 ++++++--
 .../jwebassembly/module/ModuleGenerator.java  |  8 +++
 5 files changed, 89 insertions(+), 9 deletions(-)

diff --git a/src/de/inetsoftware/classparser/ClassFile.java b/src/de/inetsoftware/classparser/ClassFile.java
index a4ced98..aa4db6a 100644
--- a/src/de/inetsoftware/classparser/ClassFile.java
+++ b/src/de/inetsoftware/classparser/ClassFile.java
@@ -19,6 +19,8 @@ package de.inetsoftware.classparser;
 import java.io.DataInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Map;
 
@@ -48,9 +50,9 @@ public class ClassFile {
 
     private final ConstantClass[] interfaces;
 
-    private final FieldInfo[]     fields;
+    private FieldInfo[]           fields;
 
-    private final MethodInfo[]    methods;
+    private MethodInfo[]          methods;
 
     private final Attributes      attributes;
 
@@ -136,16 +138,27 @@ public class ClassFile {
         methods = classFile.methods;
         attributes = classFile.attributes;
 
+        patchConstantPool( classFile.thisClass.getName(), thisClass );
+    }
+
+    /**
+     * Replace the reference to the Class in the the constant pool.
+     * 
+     * @param origClassName
+     *            the class name that should be replaced.
+     * @param thisClass
+     *            the reference of the class that should be used.
+     */
+    private void patchConstantPool( String origClassName, ConstantClass thisClass ) {
         // patch constant pool
-        String origClassName = classFile.thisClass.getName();
         for( int i = 0; i < constantPool.size(); i++ ) {
             Object obj = constantPool.get( i );
             if( obj instanceof ConstantClass ) {
                 if( ((ConstantClass)obj).getName().equals( origClassName ) ) {
                     constantPool.set( i, thisClass );
                 }
-            } else if( obj instanceof ConstantFieldRef ) {
-                ConstantFieldRef ref = (ConstantFieldRef)obj;
+            } else if( obj instanceof ConstantRef ) {
+                ConstantRef ref = (ConstantRef)obj;
                 if( ref.getClassName().equals( origClassName ) ) {
                     ConstantNameAndType nameAndType = new ConstantNameAndType( ref.getName(), ref.getType() );
                     constantPool.set( i, new ConstantFieldRef( thisClass, nameAndType ) );
@@ -301,4 +314,31 @@ public class ClassFile {
     public static enum Type {
         Class, Interface, Enum;
     }
+
+    /**
+     * Extends this class with the methods and fields of the partial class.
+     * 
+     * @param partialClassFile
+     *            extension of the class
+     */
+    public void partial( ClassFile partialClassFile ) {
+        ArrayList<MethodInfo> allMethods = new ArrayList<>( Arrays.asList( methods ) );
+        for( MethodInfo m : partialClassFile.methods ) {
+            if( getMethod( m.getName(), m.getType() ) == null ) {
+                m.setDeclaringClassFile( this );
+                allMethods.add( m );
+            }
+        }
+        methods = allMethods.toArray( methods );
+
+        ArrayList<FieldInfo> allFields = new ArrayList<>( Arrays.asList( fields ) );
+        for( FieldInfo field : partialClassFile.fields ) {
+            if( getField( field.getName() ) == null ) {
+                allFields.add( field );
+            }
+        }
+        fields = allFields.toArray( fields );
+
+        partialClassFile.patchConstantPool( partialClassFile.thisClass.getName(), thisClass );
+    }
 }
diff --git a/src/de/inetsoftware/classparser/MethodInfo.java b/src/de/inetsoftware/classparser/MethodInfo.java
index 66d314a..27de2b5 100644
--- a/src/de/inetsoftware/classparser/MethodInfo.java
+++ b/src/de/inetsoftware/classparser/MethodInfo.java
@@ -1,5 +1,5 @@
 /*
-   Copyright 2011 - 2019 Volker Berlin (i-net software)
+   Copyright 2011 - 2020 Volker Berlin (i-net software)
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.io.IOException;
 import java.util.Collections;
 import java.util.Map;
 
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import de.inetsoftware.classparser.Attributes.AttributeInfo;
@@ -200,4 +201,14 @@ public class MethodInfo implements Member {
     public ConstantPool getConstantPool() {
         return constantPool;
     }
+
+    /**
+     * Replace the reference to the ClassFile
+     * 
+     * @param classFile
+     *            the new value
+     */
+    void setDeclaringClassFile( @Nonnull ClassFile classFile ) {
+        this.classFile = classFile;
+    }
 }
diff --git a/src/de/inetsoftware/jwebassembly/JWebAssembly.java b/src/de/inetsoftware/jwebassembly/JWebAssembly.java
index 86723cd..ed969dd 100644
--- a/src/de/inetsoftware/jwebassembly/JWebAssembly.java
+++ b/src/de/inetsoftware/jwebassembly/JWebAssembly.java
@@ -88,6 +88,11 @@ public class JWebAssembly {
      */
     public static final String REPLACE_ANNOTATION =  "de.inetsoftware.jwebassembly.api.annotation.Replace";
 
+    /**
+     * The name of the annotation for partial class another class of the Java runtime. 
+     */
+    public static final String PARTIAL_ANNOTATION =  "de.inetsoftware.jwebassembly.api.annotation.Partial";
+
     /**
      * If the GC feature of WASM should be use or the GC of the JavaScript host. If true use the GC instructions of WASM.
      */
diff --git a/src/de/inetsoftware/jwebassembly/module/ClassFileLoader.java b/src/de/inetsoftware/jwebassembly/module/ClassFileLoader.java
index 4945c7c..95b74e2 100644
--- a/src/de/inetsoftware/jwebassembly/module/ClassFileLoader.java
+++ b/src/de/inetsoftware/jwebassembly/module/ClassFileLoader.java
@@ -104,15 +104,31 @@ public class ClassFileLoader {
     }
 
     /**
-     * Replace the class in the cache with the given instance.
+     * Replace the class in the cache with the given instance to the loader cache.
      * 
      * @param className
      *            the name of the class to replace
      * @param classFile
-     *            the replasing ClassFile
+     *            the replacing ClassFile
      */
-    public void replace( String className, ClassFile classFile ) {
+    void replace( String className, ClassFile classFile ) {
         classFile = new ClassFile( className, classFile );
         replace.put( className, classFile );
     }
+
+    /**
+     * Add a partial class with the given instance to the loader cache.
+     * 
+     * @param className
+     *            the name of the class to replace
+     * @param partialClassFile
+     *            the partial ClassFile
+     * @throws IOException
+     *             If any I/O error occur
+     */
+    void partial( String className, ClassFile partialClassFile ) throws IOException {
+        ClassFile classFile = get( className );
+        replace.put( className, classFile );
+        classFile.partial( partialClassFile );
+    }
 }
diff --git a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java
index bba9122..3f43540 100644
--- a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java
+++ b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java
@@ -172,6 +172,14 @@ public class ModuleGenerator {
             }
         }
 
+        // check if this class extends another class with partial code
+        if( (annotationValues = classFile.getAnnotation( JWebAssembly.PARTIAL_ANNOTATION )) != null ) {
+            String signatureName = (String)annotationValues.get( "value" );
+            if( signatureName != null ) {
+                classFileLoader.partial( signatureName, classFile );
+            }
+        }
+
         iterateMethods( classFile, m -> prepareMethod( m ) );
     }