diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml new file mode 100644 index 0000000..0bc2be0 --- /dev/null +++ b/.github/workflows/gradle-publish.yml @@ -0,0 +1,45 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created +# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle + +name: Gradle Package + +on: + release: + types: [created] + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file + + - name: Build with Gradle + uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 + with: + arguments: build + + # The USERNAME and TOKEN need to correspond to the credentials environment variables used in + # the publishing section of your build.gradle + - name: Publish to GitHub Packages + uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 + with: + arguments: publish + env: + USERNAME: ${{ github.actor }} + TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..e80a31a --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,31 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Build with Gradle + uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 + with: + arguments: build diff --git a/.gitignore b/.gitignore index 5a4e65d..fc35ac0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ # 项目排除路径 /.gradle/ +/.idea/ /build/ -/build/classes/java/Test/ \ No newline at end of file +/build/classes/java/Test/ +core/build +usage_sample/build +baseimpl/build +annotations/build diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 5ff7480..5ed69ea 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -5,14 +5,49 @@ - - - - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + - + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 611e7c8..17e45db 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,11 +4,15 @@ \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index a4c702e..3fe69a3 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - @@ -12,11 +11,16 @@ + + + + + - + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..249a2ae --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/apt/JavaDynamilizer.apt.main.iml b/.idea/modules/apt/JavaDynamilizer.apt.main.iml new file mode 100644 index 0000000..bc6842f --- /dev/null +++ b/.idea/modules/apt/JavaDynamilizer.apt.main.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 906522b..4ba89ae 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,26 @@ # JavaDynamilizer -可简写为`JDER`,一个java语言的动态化支持框架,允许对java类型进行动态委托并构造动态实例,以类似脚本语言的“函数”和“变量”来替代“方法”和“字段”,它们是可以变化的,动态实例可以委托自任意可用的java类(非final且具有至少对子类可见的构造器),动态实例可以分配给被委托的java类。 +一个java语言的动态化支持框架,允许对java类型进行动态委托并构造动态实例,以类似脚本语言的“函数”和“变量”来替代“方法”和“字段”,它们是可以变化的,动态实例可以委托自任意可用的java类(非final且具有至少对子类可见的构造器),动态实例可以分配给被委托的java类。 **** -要引用JavaDynamilizer部署项目,你只需要在项目依赖中添加对此仓库的依赖: +要引用JavaDynamilizer部署项目,需要在项目依赖中添加对此仓库的依赖: dependencies { ...... - implementation 'com.github.EB-wilson:JavaDynamilizer:$version' + implementation 'com.github.EB-wilson.JavaDynamilizer:core:$version' + implementation 'com.github.EB-wilson.JavaDynamilizer:baseimpl:$version'//基础的内部实现,若您需要自定义实现抽象层的话,这个模块就并不是必要的 ...... } +> 当前这个项目还尚不成熟和健壮,需要更多的使用与建议来进一步完善和优化,如果您有能力还请不吝参与此框架的开发。 + +您可以参照项目中给出的一些使用[案例](https://github.com/EB-wilson/JavaDynamilizer/tree/master/usage_sample/src/main/java/com/github/ebwilson/sample)和javadoc了解更多关于这个仓库的使用方法。下面是基本使用方法简述: + ## 基本使用 -创建一个最简单的动态实例,它委托自java类HashMap: +创建一个最简单的全委托动态实例,它委托自java类HashMap: - DynamicClass Demo = DynamicClass.get("Demo"); - HashMap map = DynamicMaker.getDefault().newInstance(HashMap.class, Demo); + DynamicMaker maker = DynamicFactory.getDefault(); + DynamicClass Sample = DynamicClass.get("Sample"); + + HashMap map = maker.newInstance(HashMap.class, Sample).objSelf(); 其中,map对象具有所有hashMap的行为并且可以分配给HashMap字段或者参数。而本工具的重点在于动态化实例,上面创建的map实例已经被动态化,它具有`dynamilize.DynamicObject`的特征,对于此对象已经可以进行行为变更和代码插桩了。 @@ -23,7 +30,7 @@ dyMap.setFunc("put", (self, args) -> { self.superPointer().invokeFunc("put", args); System.out.println("map putted, key: " + args.get(0) + ", value: " + args.get(1) + "."); - }, Object.class, Objec.class); + }, Object.class, Object.class); 这时,如果执行`map.put("first", "hello world")`,那么会在系统输出流中收到如下信息: @@ -32,10 +39,10 @@ 另外,在构建一个动态实例时,您还可以令实例实现一组接口,同样的,实例可以被正确的分配给这个接口的类型: DynamicClass Demo = DynamicClass.get("Demo"); - DynamicObject dyObj = DynamicMaker.getDefault().newInstance(new Class[]{Map.class}, Demo); + DynamicObject dyObj = DynamicFactory.getDefault().newInstance(new Class[]{Map.class}, Demo); Map map = (Map)dyObj; -但是实现接口你必须使用`setFunc`方法对所有的接口抽象方法进行实现,否则对此方法的引用会抛出致命异常。 +但是在实现接口时,您必须使用`setFunc`方法来对所有的接口抽象方法进行实现,否则对此方法的引用会抛出致命异常。 ## 动态类型 @@ -65,15 +72,16 @@ 例如声明一个类型用作样版: public class Template{ - public static void helloWorld(){ + public static void run(){ System.out.println("hello world"); } } 那么用动态类型去访问: - Sample.visitClass(Template.class); - DynamicObject dyObj = DynamicMaker.getDefault().newInstance(Sample); + DynamicMaker maker = DynamicFactory.getDefault(); + Sample.visitClass(Template.class, maker.getHelper()); + DynamicObject dyObj = maker.newInstance(Sample); dyObj.invokeFunc("run"); 这将会在系统输出流中打印`hello world`。 @@ -100,8 +108,9 @@ 如果访问动态对象的变量`var1`: - Sample.visitClass(Template.class); - DynamicObject dyObj = DynamicMaker.getDefault().newInstance(Sample); + DynamicMaker maker = DynamicFactory.getDefault(); + Sample.visitClass(Template.class, maker.getHelper()); + DynamicObject dyObj = maker.newInstance(Sample); System.out.println(dyObject.getVar("var1")); 这会在系统输出流种打印`hello world`。 @@ -131,11 +140,4 @@ 则会使得对象的`time`变量与对象被创建时的系统纳秒计数一致。 函数与常量模式参数尾部的布尔值是确定此变量是否为**常量**,若为true,则此变量不可被改变。 **** -动态类型的行为声明是可变的,但这个可变对于实例的影响有一定的范围,以下列举主要的情况: -- 在一个动态对象被创建之前的所有对动态类型的变更,都对新创建的动态对象有效 -- 对于一个已被创建的动态对象,在动态类型中声明新的函数和变量,以及修改动态类型中的变量初始值都不会对已存在的对象产生直接影响 -- 对于一个已被创建的动态对象,若它的某一函数没有通过对象的`setFunc`方法变更,则对动态类型的此方法进行变更将会改变这个对象的该函数行为 -- 若一个已存在的动态对象的某一函数已经被`setFunc`方法变更,则其动态类型的行为变更对该对象的此函数行为没有直接影响 -- 对象的变量初始值只和被创建时有关,比如对于变量的样版,变量初始值或者初始值函数只和被创建时,该字段的值或函数有关;对于函数,变量初始值的函数只在被创建时调用并返回值。 - -您可以参照项目中给出的一些使用例子和javadoc了解更多关于这个仓库的东西。 +- 动态类型声明的变量初始值只在对象被创建时有效,任何时候改变类的变量初始值都不会对已有实例造成影响 diff --git a/annotations/build.gradle b/annotations/build.gradle new file mode 100644 index 0000000..e0526ad --- /dev/null +++ b/annotations/build.gradle @@ -0,0 +1,38 @@ +plugins { + id 'java' +} +apply plugin: 'maven-publish' + +sourceSets.main.java.srcDirs = ["src/main/java"] +sourceSets.test.java.srcDirs = ["src/test/java"] + +repositories { + mavenCentral() + maven{ url 'https://www.jitpack.io' } +} + +sourceCompatibility = 17 +targetCompatibility = 8 + +tasks.withType(JavaCompile).configureEach { + options.compilerArgs.addAll([ + "--release", "8" + ]) +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.github.EB-wilson.JavaDynamilizer' + artifactId = 'annotations' + version = "$JDERVersion" + from components.java + } + } +} + +dependencies { + compileOnly project(":core") + annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2' + compileOnly 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2' +} diff --git a/annotations/src/main/java/dynamilize/annotations/ContextMaker.java b/annotations/src/main/java/dynamilize/annotations/ContextMaker.java new file mode 100644 index 0000000..9e6c4c5 --- /dev/null +++ b/annotations/src/main/java/dynamilize/annotations/ContextMaker.java @@ -0,0 +1,16 @@ +package dynamilize.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/**动态工厂上下文标记器,用于确定在一个{@linkplain DynamicContext 动态语义上下文}中所使用的动态工厂 + *

具体来说,动态上下文的诸多行为会依赖于一个动态工厂,此注解即应用于一个字段或者局部变量,亦或者是一个方法参数之上,其上下文语义与java变量,字段和方法参数无异, + * 请注意,当注解用于java静态字段时,此标记同样会具有静态上下文,反之亦然,即您不能在静态上下文内访问非静态上下文工厂标识 + * + *

对于同一个上下文内的多次设置上下文标记,则在访问动态工厂时,会选择在此之前的最后一个标记使用*/ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.PARAMETER}) +public @interface ContextMaker { +} diff --git a/annotations/src/main/java/dynamilize/annotations/DynamicContext.java b/annotations/src/main/java/dynamilize/annotations/DynamicContext.java new file mode 100644 index 0000000..77e44f9 --- /dev/null +++ b/annotations/src/main/java/dynamilize/annotations/DynamicContext.java @@ -0,0 +1,32 @@ +package dynamilize.annotations; + +import dynamilize.DynamicClass; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/**动态上下文标记,携带此注解的类型会被视为一个具有动态化语义的上下文 + *

具体来说,这个注解是类型内局部动态语义的最外层标记,所有局部动态语义声明的注解都需要其所在的环境具有此注解标记的外层 + *

本注解本身没有实际作用,关于这个注解支持的局部语义注解可参见:: + *

    + *
  • @{@link ContextMaker} - 上下文动态工厂标记
  • + *
  • @{@link NewDynamic} - new语句动态化委托
  • + *
+ * + * 关于注解的详细行为请参阅各个受支持的注解*/ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface DynamicContext { + + interface DynamilizedContext{ + DynamilizedContextHandles $ = new DynamilizedContextHandles(); + } + + class DynamilizedContextHandles{ + public void by(DynamicClass dynamicClass){} + public void with(Class... aspects){} + public void impl(Class... interfaces){} + } +} diff --git a/annotations/src/main/java/dynamilize/annotations/DynamilizeClass.java b/annotations/src/main/java/dynamilize/annotations/DynamilizeClass.java new file mode 100644 index 0000000..6faa801 --- /dev/null +++ b/annotations/src/main/java/dynamilize/annotations/DynamilizeClass.java @@ -0,0 +1,78 @@ +package dynamilize.annotations; + +import dynamilize.*; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/**此注解用于将一个一般的java类型转化为动态类型记录 + *

具体来说,一个具备此注解的类,其中声明的各种方法语句的上下文都会被调整为适配到{@link DynamicClass#visitClass(Class, JavaHandleHelper)}的语义, + * 并保存其对应的动态类型对象及名称。该注解并不会递归的处理被标记类中的内部类,但是被标记的类中不能有任何非静态的内部类 + *

此标记转换的动态类类名由DynamilizeClass.{@link DynamilizeClass#className()}给出,默认使用标记的类类名,细节请参阅这个注解属性 + * + *
+ *
+ *

警告:此注解转换的动态类型标记可能并不能可靠的加载,在您使用这个注解生成的动态类之前,请通过调用类的任意字段/方法来确保此类型已加载

+ *

特别注意,在注解{@link NewDynamic}中传递该类的NAME常量并不能使jvm加载这个类,因为NAME会被转化为常量提供给注解参数, + * 因此在通过上下文注解引用的该动态类并不能可靠的加载此动态类

+ *
+ * + *

以下是注解的工作细节: + *

    + *
  • 首先,检查该类型中的声明是否合法,而后在其中搜索{@linkplain JavaHandleHelper 辅助器},向其之后添加两个字段 + * {@code public static final DynamicClass INSTANCE}和{@code public static final String NAME}分别保存对动态类实例的引用和动态类类名, + * 这两个字段都会添加{@link dynamilize.runtimeannos.Exclude}以排除,其中INSTANCE的初始化在后文会提到
  • + *
  • 接着,注解处理器会在此类上下文中定位每一个字段引用和方法引用,确定这些引用的目标是否为this或者super,若引用的目标满足且是一个隐式成员引用, + * 那么会将this和super添加为显式声明
  • + *
  • 遍历此类所有方法,将所有成员方法全部变为静态方法,并在参数前两位添加符合{@link DynamicClass#visitMethod(Method, JavaHandleHelper)}规则的self参数和sup参数, + * 而对于声明的静态方法则添加{@link dynamilize.runtimeannos.Exclude}注解从声明中排除
  • + *
  • 然后,将所有符合规则的成员方法中对本类上下文引用的语句转换为通过self参数传入的动态对象的方法调用,例如{@code this.run()}会被替换为{@code self.invokeFunc("run")}, + * 但是默认情况下这个对run的调用会添加方法签名以快速定位函数,关于签名选项,请参阅{@link DynamilizeClass#signatureMethods()}
  • + *
  • 最后,处理所有声明的字段,对成员字段会按照符合{@link DynamicClass#visitField(Field)}的规范变换,若其具有初始化数据, + * 当初始化数据为常量值时会直接将字段设置为static,否则,字段的初始化语句会被包装为{@link dynamilize.Initializer.Producer},且字段类型也会同步调整。 + * 对于静态字段则会简单的像是对静态方法一样添加注解进行排除
  • + *
+ * + *

此外,被标记为动态类型记录的类也可以扩展一个动态类型记录,这会对INSTANCE字段的初始化产生影响,具体来说,初始化语句都是调用{@link DynamicClass#visit(String, Class, DynamicClass, JavaHandleHelper)} + * 来直接获取动态类型实例,而扩展会影响的就是其最后一个参数,直观来看

{@code
+ * //一个没有扩展的动态类型记录中,INSTANCE的等价初始化语句是这样的:
+ * public static final DynamicClass INSTANCE = DynamicClass.visit(NAME, .class, null, $helper$);
+ * //而有扩展一个父类的动态类型记录中,INSTANCE的等价初始化语句是这样的:
+ * public static final DynamicClass INSTANCE = DynamicClass.visit(NAME, .class, .INSTANCE, $helper$);
+ * }
+ * 值得关注的是其中的字段{@code $helper$},这个字段是保存用于访问java类使用的辅助器的静态字段, + * 您同样必须在原始的类型定义中创建指定名称的java静态字段,否则将无法通过编译,关于选择这个字段的细节,请参阅DynamilizeClass.{@link DynamilizeClass#helperField()} + * + *

另外,在您使用这个注解标记类型时,将类型标记为abstract并实现{@link DynamilizedClass}接口是一种不错的选择,该接口包含了INSTANCE字段和NAME字段, + * 这是一种魔法操作,例如,在java中,您可以直接对被标记的类调用INSTANCE字段和NAME字段,但这是通过类转移的调用,实际上会指向{@link DynamilizedClass}接口的字段, + * 但是在注解处理后,被标记类中会被添加这两个字段,从外部访问这个类型的INSTANCE和NAME字段的目标就会被确定而非重定向到DynamilizedClass接口, + * 具体细节请参阅{@link DynamilizedClass}*/ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface DynamilizeClass { + /**应用于动态类的名称,这会为此类的NAME字段设置值,默认在不设置这个名称的情况下,注解处理器会使用被标记的类的完整类名作为该动态类的名称*/ + String className() default ""; + /**此类型动态化时所使用的静态java辅助器存储字段名称,在将类型声明转化为动态类的过程中,会在类的一级声明中搜索这个字段, + * 并将INSTANCE字段添加在其后方以确保有效的调用此字段,默认为"$helper$",您可以自行指定字段名称*/ + String helperField() default "$helper$"; + /**是否对所有动态化的方法调用添加方法签名,关于方法签名的性能影响,可参阅{@link DataPool#select(String, FunctionType)},若您将此设置为true, + * 那么所有从方法调用转换来的动态方法引用都会标记其调用的方法签名类型,并在调用时传入以提升性能 + *

您可以根据需要设置此选项,假如内存性能十分重要,则推荐设为false,否则通常来说设为true都能提升时间性能,尤其是在动态类型多次扩展嵌套的情况下*/ + boolean signatureMethods() default true; + + /**辅助类型动态声明的魔法接口,通常在您对一个类型标记为{@link DynamilizeClass}时,建议将类型设置为abstract并添加实现此接口 + *

接口扩展自{@link DynamicObject},以便在您描述类型行为时可以直接使用动态对象的方法,值得注意的是, + * 您在此上下文中调用属于{@link DynamicObject}的API是不会被再次转化为动态调用动态方法的,即这些API会被注解处理器略过,仅仅将调用对象重定向到self参数 + *

此外,该接口中定义了两个常量字段和一个方法,字段用作在java语义中直接引用该类型的INSTANCE字段和NAME字段,在经过注解处理器处理后这两个字段不会被重定向到该接口。 + *

方法{@link DynamilizedClass#super$()}则是对动态super指针的虚拟引用,以便您可以以动态描述的方法那样调用super池的行为,对这个方法的调用在注解处理后会被转化为对sup参数的引用*/ + interface DynamilizedClass extends DynamicObject { + DynamicClass INSTANCE = DynamicClass.get(""); + String NAME = ""; + + default DataPool.ReadOnlyPool super$(){ return null; } + } +} diff --git a/annotations/src/main/java/dynamilize/annotations/NewDynamic.java b/annotations/src/main/java/dynamilize/annotations/NewDynamic.java new file mode 100644 index 0000000..7714fd6 --- /dev/null +++ b/annotations/src/main/java/dynamilize/annotations/NewDynamic.java @@ -0,0 +1,61 @@ +package dynamilize.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/**此注解依赖{@linkplain DynamicContext 动态上下文标识}!您必须在所在环境外层添加动态上下文标识才能使用此注解 + *
实例化变量的动态化语句标志,用于声明一个java语义的动态对象实例化语句 + *

具体来讲,此注解的作用即将形如

{@code
+ * [modifiers] Type variableName = new Type()
+ * }
的语句转换为创建一个动态代理对象,这可以是字段声明,亦可以是方法中的局部变量声明,其中等号右侧必须是一个new语句。 + *
注意,右侧的new语句不得携带匿名扩展块,在未来的更新中可能会添加对此的支持 + *
假如您实例化的是一个非静态内部类,形如{@code outer.new Inner()}同样也是有效的 + * + *

实例化动态代理对象声明的参数从注解的属性内提供,此注解的参数: + *

    + *
  • {@link NewDynamic#refDynamicMaker()} - 引用{@linkplain dynamilize.DynamicMaker 动态工厂}的语句
  • + *
  • {@link NewDynamic#dynClassName()} - 此动态对象所用的{@linkplain dynamilize.DynamicClass 动态类型}名称
  • + *
  • {@link NewDynamic#impls()} - 该动态委托应用的额外接口表
  • + *
  • {@link NewDynamic#aspects()} - 该动态委托的目标{@linkplain dynamilize.runtimeannos.AspectInterface 切面接口}
  • + *
+ * 使用上述的参数,此注解会将一个满足条件的语句变换为创建动态对象,例如: + *
{@code
+ * //假设,在上文已经标记了一个动态工厂"maker"
+ * @NewDynamic(dynClassName = "SampleDyc", impls = {Collection.class, Runnable.class})
+ * Object dyObj = new Object();
+ * }
+ * 那么,经过处理编译后的等效代码是这样的: + *
{@code
+ * Object dyObj = maker.newInstance(Object.class, new Class[]{Collection.class, Runnable.class}, (Class[])null, DynamicClass.get("SampleDyc")).objSelf();
+ * }
+ * 如果在new调用构造函数时有传入参数,参数会正确的添加在上述的语句最后一个参数处。一个比较特殊的地方在于,为了内存性能, + * 您传递的接口列表(额外接口和切面接口)都会被处理为一个存在于类中的静态常量以供重复调用,对于列表接口类型相同的一组接口会复用同一个常量接口表 + *
+ *
+ *

另外,如果您声明的变量类型是{@link dynamilize.DynamicObject},同时将右侧表达式添加类型转换到DynamicObject,此时生成的语句将直接获取原始的动态对象, + * 而不调用其{@code objSelf()}方法,举例来说,假设: + *

{@code
+ * @NewDynamic(dynClassName = "SampleDyc", impls = {Collection.class, Runnable.class})
+ * DynamicObject dyObj = (DynamicObject)new Object();
+ * }
+ * 那么,经过处理编译后的等效代码是这样的:
+ * 
{@code
+ * DynamicObject dyObj = maker.newInstance(Object.class, new Class[]{Collection.class, Runnable.class}, (Class[])null, DynamicClass.get("SampleDyc"));
+ * }
+ * 简单来说这个注解的作用就是让您更便捷的使用java语句创建一个动态代理对象,而不需要使用maker的{@code newInstance(...)}方法
+ * 

关于此注解的作用细节,请参阅本注解的参数说明*/ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE}) +public @interface NewDynamic { + /**创建动态对象使用的{@linkplain dynamilize.DynamicMaker 动态工厂}引用语句,这会直接被整合为一段java语句插入到newInstance调用中, + *

默认使用动态上下文中被标记的动态工厂,并且通常情况下也不推荐设置直接引用语句*/ + String refDynamicMaker() default ""; + /**此动态代理对象的动态类型名称,在生成的语句中直接调用{@code DynamicClass.get(...)}方法来获取指定的动态类型*/ + String dynClassName(); + /**动态委托要实现的额外接口列表*/ + Class[] impls() default {}; + /**动态委托要实现的{@linkplain dynamilize.runtimeannos.AspectInterface 切面接口}列表*/ + Class[] aspects() default {void.class}; +} diff --git a/apt/.gitignore b/apt/.gitignore new file mode 100644 index 0000000..b63da45 --- /dev/null +++ b/apt/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/apt/build.gradle b/apt/build.gradle new file mode 100644 index 0000000..6f04d0c --- /dev/null +++ b/apt/build.gradle @@ -0,0 +1,51 @@ +plugins { + id 'java' +} +apply plugin: 'maven-publish' + +sourceSets.main.java.srcDirs = ["src/main/java"] +sourceSets.test.java.srcDirs = ["src/test/java"] + +group = 'com.github.EB-wilson' + +repositories { + mavenCentral() + maven{ url 'https://www.jitpack.io' } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = "UTF-8" + + sourceCompatibility = 17 + targetCompatibility = 17 + + options.compilerArgs.addAll([ + "--add-exports", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.resources=ALL-UNNAMED", + ]) +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.github.EB-wilson.JavaDynamilizer' + artifactId = 'apt' + version = "$JDERVersion" + from components.java + } + } +} + +dependencies { + implementation project(":annotations") + + compileOnly 'com.google.auto.service:auto-service:1.0.1' + + annotationProcessor 'com.google.auto.service:auto-service:1.0.1' +} diff --git a/apt/src/main/java/dynamilize/annoprocessor/BaseProcessor.java b/apt/src/main/java/dynamilize/annoprocessor/BaseProcessor.java new file mode 100644 index 0000000..3324823 --- /dev/null +++ b/apt/src/main/java/dynamilize/annoprocessor/BaseProcessor.java @@ -0,0 +1,503 @@ +package dynamilize.annoprocessor; + +import com.sun.source.tree.Tree; +import com.sun.source.util.TreePath; +import com.sun.tools.javac.api.JavacTrees; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.comp.*; +import com.sun.tools.javac.parser.ParserFactory; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Names; +import sun.misc.Unsafe; + +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Elements; +import javax.tools.StandardLocation; +import java.io.*; +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.util.*; + +@SuppressWarnings({"unchecked"}) +public abstract class BaseProcessor extends AbstractProcessor{ + private static final long fieldFilterOffset = 112L; + + private static final Field opensField; + private static final Field exportField; + + private static final Method exportNative; + + final HashMap> processedMap = new HashMap<>(); + + Context context; + JavacTrees trees; + Attr attr; + TreeMaker maker; + Names names; + Types types; + ParserFactory parsers; + Symtab symtab; + Annotate annotates; + Enter enter; + Check check; + + Elements elements; + Filer filer; + + Messager messager; + + Type DynamicMakerType; + Type DynamicClassType; + Type DynamicObjectType; + Type ReadOnlyPoolType; + + static final Unsafe unsafe; + + private static final String[] opensModule = { + "com.sun.tools.javac.api", + "com.sun.tools.javac.code", + "com.sun.tools.javac.parser", + "com.sun.tools.javac.processing", + "com.sun.tools.javac.tree", + "com.sun.tools.javac.util", + "com.sun.tools.javac.comp", + }; + + //使用此模块可替代jabel,使程序进行降级编译(编译到JDK8) + static{ + try{ + Constructor cstr = Unsafe.class.getDeclaredConstructor(); + cstr.setAccessible(true); + unsafe = cstr.newInstance(); + + Class clazz = Class.forName("jdk.internal.reflect.Reflection"); + Map, Set> map = (Map, Set>) unsafe.getObject(clazz, fieldFilterOffset); + map.clear(); + + opensField = Module.class.getDeclaredField("openPackages"); + exportField = Module.class.getDeclaredField("exportedPackages"); + + makeModuleOpen(Module.class.getModule(), "java.lang", BaseProcessor.class.getModule()); + + exportNative = Module.class.getDeclaredMethod("addExports0", Module.class, String.class, Module.class); + exportNative.setAccessible(true); + exportNative.invoke(null, Module.class.getModule(), "java.lang", BaseProcessor.class.getModule()); + + for (String pack : opensModule) { + makeModuleOpen(Tree.class.getModule(), pack, BaseProcessor.class.getModule()); + } + + Field minLevel = Source.Feature.class.getDeclaredField("minLevel"); + long off = unsafe.objectFieldOffset(minLevel); + + for (Source.Feature feature : Source.Feature.values()) { + if (!feature.allowedInSource(Source.JDK8)) { + unsafe.putObject(feature, off, Source.JDK8); + } + } + } catch (NoSuchFieldException | ClassNotFoundException | InvocationTargetException | NoSuchMethodException | + InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + private static void makeModuleOpen(Module from, String pac, Module to){ + try { + if (exportNative != null) exportNative.invoke(null, from, pac, to); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + + Map> opensMap = (Map>) unsafe.getObjectVolatile(from, unsafe.objectFieldOffset(opensField)); + if(opensMap == null){ + opensMap = new HashMap<>(); + unsafe.putObjectVolatile(from, unsafe.objectFieldOffset(opensField), opensMap); + } + + Map> exportsMap = (Map>) unsafe.getObjectVolatile(from, unsafe.objectFieldOffset(exportField)); + if(exportsMap == null){ + exportsMap = new HashMap<>(); + unsafe.putObjectVolatile(from, unsafe.objectFieldOffset(exportField), exportsMap); + } + + Set opens = opensMap.computeIfAbsent(pac, e -> new HashSet<>()); + Set exports = exportsMap.computeIfAbsent(pac, e -> new HashSet<>()); + + try{ + opens.add(to); + }catch(UnsupportedOperationException e){ + ArrayList lis = new ArrayList<>(opens); + lis.add(to); + opensMap.put(pac, new HashSet<>(lis)); + } + + try{ + exports.add(to); + }catch(UnsupportedOperationException e){ + ArrayList lis = new ArrayList<>(exports); + lis.add(to); + exportsMap.put(pac, new HashSet<>(lis)); + } + } + + @Override + public synchronized void init(ProcessingEnvironment processingEnv){ + super.init(processingEnv); + elements = processingEnv.getElementUtils(); + filer = processingEnv.getFiler(); + messager = processingEnv.getMessager(); + + context = ((JavacProcessingEnvironment)processingEnv).getContext(); + attr = Attr.instance(context); + trees = JavacTrees.instance(context); + maker = TreeMaker.instance(context); + names = Names.instance(context); + types = Types.instance(context); + parsers = ParserFactory.instance(context); + symtab = Symtab.instance(context); + enter = Enter.instance(context); + annotates = Annotate.instance(context); + check = Check.instance(context); + + DynamicMakerType = ((Type) elements.getTypeElement("dynamilize.DynamicMaker").asType()); + DynamicClassType = ((Type) elements.getTypeElement("dynamilize.DynamicClass").asType()); + DynamicObjectType = ((Type) elements.getTypeElement("dynamilize.DynamicObject").asType()); + ReadOnlyPoolType = ((Type) elements.getTypeElement("dynamilize.DataPool.ReadOnlyPool").asType()); + } + + @Override + public boolean process(Set set, RoundEnvironment roundEnvironment){ + PrintStream writer = null; + try{ + String pac = getClass().getSimpleName() + ".log"; + + String p = filer.getResource(StandardLocation.SOURCE_OUTPUT, "", "null.java").toUri().toURL().getFile(); + File logDir = new File(p.replace("/build/generated/sources/annotationProcessor/java/main/null.java", "/processorLog/").replace("/", File.separator)); + if(logDir.exists()) logDir.delete(); + logDir.mkdirs(); + + File file = new File(logDir, pac); + + writer = new PrintStream(file); + writer.print("processor: "); + writer.print(getClass().getSimpleName()); + writer.println(" (full class name: " + getClass().getCanonicalName() + ")"); + + writer.print("time: "); + writer.println(new Date()); + writer.println(); + + for(Map.Entry> entry: processedMap.entrySet()){ + writer.println("-----------------------------------------"); + writer.print("annotation: "); + writer.println(entry.getKey().getQualifiedName()); + writer.println("-----------------------------------------\n"); + for(JCTree.JCCompilationUnit type: entry.getValue()){ + + writer.println("> class: " + type.sourcefile.getName()); + + String javaStr = type.toString(); + int blankPad = (int) Math.log10(javaStr.split("\n").length) + 1; + BufferedReader reader = new BufferedReader(new StringReader(javaStr)); + String line; + int count = 0; + while((line = reader.readLine()) != null){ + count++; + int blank = blankPad - ((int)Math.log10(count) + 1); + for(int i = 0; i < blank; i++){ + writer.print(" "); + } + writer.print(count); + writer.print("| "); + writer.println(line); + } + + writer.println(); + writer.flush(); + } + } + }catch(IOException e){ + e.printStackTrace(); + }finally{ + if(writer != null) writer.close(); + } + + return false; + } + + @Override + public SourceVersion getSupportedSourceVersion(){ + return SourceVersion.latest(); + } + + protected boolean isAssignable(Type a, Type b){ + if(a instanceof Type.ClassType && b instanceof Type.ClassType){ + return b.tsym.isSubClass(a.tsym, types); + } + else if(a instanceof Type.JCPrimitiveType){ + return a.equals(b); + } + return false; + } + + protected boolean equalOrSub(Symbol.MethodSymbol m1, Symbol.MethodSymbol m2){ + if(!m1.getQualifiedName().equals(m2.getQualifiedName())) return false; + + Symbol.VarSymbol[] param1 = m1.params().toArray(new Symbol.VarSymbol[0]), param2 = m2.params().toArray(new Symbol.VarSymbol[0]); + if(param1.length != param2.length) return false; + + for(int i = 0; i < param1.length; i++){ + if(!param1[i].type.tsym.getQualifiedName().equals(param2[i].type.tsym.getQualifiedName())) return false; + } + return m2.getReturnType().getKind() == m1.getReturnType().getKind() + && (m1.getReturnType().getKind() == TypeKind.VOID + || (isAssignable(m1.getReturnType(), m2.getReturnType()) || isAssignable(m2.getReturnType(), m1.getReturnType()))); + } + + protected boolean containMethod(HashMap> map, Symbol.MethodSymbol symbol){ + HashSet set = map.get(symbol.getSimpleName().toString()); + + if (set == null) return false; + + for (Symbol.MethodSymbol methodSymbol : set) { + if (equalOrSub(symbol, methodSymbol)) return true; + } + + return false; + } + + protected boolean addMethod(HashMap> map, Symbol.MethodSymbol symbol){ + return addMethod(map, symbol, false); + } + + protected boolean addMethod(HashMap> map, Symbol.MethodSymbol symbol, boolean override){ + HashSet set = map.computeIfAbsent(symbol.getQualifiedName().toString(), e -> new HashSet<>()); + for(Symbol.MethodSymbol m : set){ + if(equalOrSub(m, symbol)){ + if(override){ + set.remove(m); + set.add(symbol); + } + return false; + } + } + set.add(symbol); + return true; + } + + protected RawAnnotationMirrors getAnnotationParams(List annos, Class annoClazz, JCTree.JCCompilationUnit unit){ + return getAnnotationParams(annos, annoClazz, unit, Map.of()); + } + + protected RawAnnotationMirrors getAnnotationParams(List annos, Class annoClazz, JCTree.JCCompilationUnit unit, Map defaults){ + RawAnnotationMirrors mirrors = new RawAnnotationMirrors(); + boolean existed = false; + + for (JCTree.JCAnnotation anno : annos) { + TreePath path = trees.getPath(unit, anno); + Symbol.ClassSymbol an = (Symbol.ClassSymbol) trees.getElement(path); + + if (an.getQualifiedName().equals(names.fromString(annoClazz.getCanonicalName()))) { + existed = true; + + for (Method attr : annoClazz.getDeclaredMethods()) { + if (Modifier.isStatic(attr.getModifiers())) continue; + Object val = defaults.get(attr.getName()); + if (val == null) continue; + + mirrors.attributes.put(attr.getName(), getAttr(val, attr.getReturnType())); + } + + Env env = enter.getTopLevelEnv(unit); + Attribute.Compound cmp = annotates.attributeAnnotation( + anno, + (Type) elements.getTypeElement(names.fromString(annoClazz.getCanonicalName())).asType(), + env + ); + + for (JCTree.JCExpression arg : anno.args) { + JCTree.JCAssign as = ((JCTree.JCAssign) arg); + Name key = ((JCTree.JCIdent) as.lhs).name; + + mirrors.attributes.put(key.toString(), cmp.member(key)); + mirrors.rawTree.put(key.toString(), as.rhs); + } + } + } + + return existed? mirrors: null; + } + + private Attribute getAttr(Object val, Class type) { + Type t = ((Symbol.ClassSymbol) elements.getTypeElement((type.isArray()? type.componentType(): type).getCanonicalName())).type; + if (type.isArray() && val.getClass().isArray()){ + int len = Array.getLength(val); + Attribute[] res = new Attribute[len]; + for (int i = 0; i < len; i++) { + res[i] = getAttr(Array.get(val, i), type.componentType()); + } + + return new Attribute.Array(types.makeArrayType(t), res); + } + else if (type == String.class || type.isPrimitive()){ + return new Attribute.Constant(t, val); + } + else if (type == Class.class && val instanceof Class c){ + if (c.isPrimitive()){ + return switch (c.getName()){ + case "void" -> new Attribute.Class(types, symtab.voidType.tsym.type); + case "boolean" -> new Attribute.Class(types, symtab.booleanType.tsym.type); + case "byte" -> new Attribute.Class(types, symtab.byteType.tsym.type); + case "short" -> new Attribute.Class(types, symtab.shortType.tsym.type); + case "int" -> new Attribute.Class(types, symtab.intType.tsym.type); + case "long" -> new Attribute.Class(types, symtab.longType.tsym.type); + case "float" -> new Attribute.Class(types, symtab.floatType.tsym.type); + case "double" -> new Attribute.Class(types, symtab.doubleType.tsym.type); + case "char" -> new Attribute.Class(types, symtab.charType.tsym.type); + default -> throw new IllegalStateException("Unexpected value: " + c.getName()); + }; + } + return new Attribute.Class(types, ((Symbol.ClassSymbol) elements.getTypeElement(c.getCanonicalName())).type); + } + + throw new RuntimeException("unknown constant value"); + } + + protected AnnotationMirrors getAnnotationParams(Symbol sym, Class annoType){ + AnnotationMirrors mirrors = new AnnotationMirrors(); + boolean existed = false; + + for(Attribute.Compound mirror: sym.getAnnotationMirrors()){ + if(((TypeElement)mirror.getAnnotationType().asElement()).getQualifiedName().equals(names.fromString(annoType.getCanonicalName()))){ + existed = true; + elements.getElementValuesWithDefaults(mirror).forEach((e, v) -> { + mirrors.attributes.put(e.getSimpleName().toString(), (Attribute) v); + }); + } + } + + return existed? mirrors: null; + } + + protected void genLog(TypeElement annotationType, JCTree.JCCompilationUnit unit){ + processedMap.computeIfAbsent(annotationType, e -> new ArrayList<>()).add(unit); + } + + @SuppressWarnings("unchecked") + public static class AnnotationMirrors{ + protected final HashMap attributes = new HashMap<>(); + + public T get(String key){ + return (T) attributes.get(key).getValue(); + } + + public Object getConstant(String key){ + Attribute attr = attributes.get(key); + if(!(attr instanceof Attribute.Constant)) throw new UnsupportedOperationException("type of key: " + key + " is not a constant"); + return attr.getValue(); + } + + public char getChar(String key){ + Object obj = getConstant(key); + if(!(obj instanceof Character)) throw new UnsupportedOperationException("type of key: " + key + " is not a string"); + return (char) obj; + } + + public String getString(String key){ + Object obj = getConstant(key); + if(!(obj instanceof String)) throw new UnsupportedOperationException("type of key: " + key + " is not a string"); + return (String) obj; + } + + public boolean getBoolean(String key){ + Object obj = getConstant(key); + if(!(obj instanceof Boolean)) throw new UnsupportedOperationException("type of key: " + key + " is not a boolean value"); + return (boolean) obj; + } + + public Number getNumber(String key){ + Object obj = getConstant(key); + if(!(obj instanceof Number)) throw new UnsupportedOperationException("type of key: " + key + " is not a number"); + return (Number) obj; + } + + public int getInt(String key){ + return getNumber(key).intValue(); + } + + public float getFloat(String key){ + return getNumber(key).floatValue(); + } + + public byte getByte(String key){ + return getNumber(key).byteValue(); + } + + public short getShort(String key){ + return getNumber(key).shortValue(); + } + + public long getLong(String key){ + return getNumber(key).longValue(); + } + + public double getDouble(String key){ + return getNumber(key).doubleValue(); + } + + public Type getType(String key){ + Attribute attr = attributes.get(key); + if(!(attr instanceof Attribute.Class)) throw new UnsupportedOperationException("type of key: " + key + " is not a class"); + return ((Attribute.Class) attr).getValue(); + } + + public > E getEnum(String key){ + Attribute attr = attributes.get(key); + if(!(attr instanceof Attribute.Enum)) throw new UnsupportedOperationException("type of key: " + key + " is not an Enum"); + return (E) ((Attribute.Enum) attr).getValue().getConstantValue(); + } + + public Attribute[] getArr(String key){ + Attribute attr = attributes.get(key); + if(!(attr instanceof Attribute.Array)) throw new UnsupportedOperationException("type of key: " + key + " is not an array"); + return ((Attribute.Array) attr).getValue().toArray(new Attribute[0]); + } + + public T[] getArr(String key, T[] typeArr){ + Attribute[] attr = getArr(key); + typeArr = Arrays.copyOf(typeArr, attr.length); + + for(int i = 0; i < attr.length; i++){ + typeArr[i] = (T) attr[i].getValue(); + } + + return typeArr; + } + } + + protected static class RawAnnotationMirrors extends AnnotationMirrors{ + private final HashMap rawTree = new HashMap<>(); + + public JCTree getTree(String key){ + return rawTree.get(key); + } + + public JCTree[] getArrTree(String key){ + JCTree tree = rawTree.get(key); + if (tree instanceof JCTree.JCNewArray n){return n.elems.toArray(new JCTree[0]);} + else if (tree == null) return null; + + throw new RuntimeException("attribute " + key + " was not a array"); + } + } +} diff --git a/apt/src/main/java/dynamilize/annoprocessor/ClassDynamilizeProcessor.java b/apt/src/main/java/dynamilize/annoprocessor/ClassDynamilizeProcessor.java new file mode 100644 index 0000000..de8cf5e --- /dev/null +++ b/apt/src/main/java/dynamilize/annoprocessor/ClassDynamilizeProcessor.java @@ -0,0 +1,391 @@ +package dynamilize.annoprocessor; + +import com.google.auto.service.AutoService; +import com.sun.source.util.TreePath; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeScanner; +import com.sun.tools.javac.tree.TreeTranslator; +import com.sun.tools.javac.util.List; +import dynamilize.annotations.DynamilizeClass; + +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Set; + +@AutoService(Processor.class) +public class ClassDynamilizeProcessor extends BaseProcessor{ + @Override + public boolean process(Set set, RoundEnvironment roundEnvironment) { + Type RawType = new Type.ClassType( + DynamicObjectType.tsym.owner.type, + List.of(new Type.WildcardType(symtab.objectType.tsym.type, BoundKind.UNBOUND, DynamicObjectType.tsym)), + DynamicObjectType.tsym + ); + + Type ThisType = ((Type) elements.getTypeElement("dynamilize.runtimeannos.This").asType()); + Type SuperType = ((Type) elements.getTypeElement("dynamilize.runtimeannos.Super").asType()); + Type ExcludeType = ((Type) elements.getTypeElement("dynamilize.runtimeannos.Exclude").asType()); + Type ProducterType = ((Type) elements.getTypeElement("dynamilize.Initializer.Producer").asType()); + Type functionType = (Type) elements.getTypeElement("dynamilize.FunctionType").asType(); + + for (TypeElement anno : set) { + for (Element element : roundEnvironment.getElementsAnnotatedWith(anno)) { + JCTree.JCClassDecl root = (JCTree.JCClassDecl) trees.getTree(element); + JCTree.JCCompilationUnit unit = (JCTree.JCCompilationUnit) trees.getPath(root.sym).getCompilationUnit(); + + if (element.getEnclosingElement().getKind() != ElementKind.PACKAGE && !Flags.isStatic(root.sym)) + throw new RuntimeException("Dynamilize class must be top level class or static enclosed class"); + + Type superClass = root.sym.getSuperclass(); + if (superClass.equals(symtab.objectType.tsym.type)) superClass = null; + + DynamilizeClass annotation = element.getAnnotation(DynamilizeClass.class); + String className = annotation.className().equals("")? root.sym.type.tsym.getQualifiedName().toString(): annotation.className(); + String helperField = annotation.helperField(); + if (helperField.isBlank() || Character.isDigit(helperField.trim().charAt(0)) || helperField.contains("-")) + throw new IllegalArgumentException("illegal field name: " + helperField); + + Symbol.VarSymbol makeHelper = null; + for (JCTree def : root.defs) { + if (def instanceof JCTree.JCVariableDecl f){ + if (f.name.equals(names.fromString("INSTANCE")) || f.name.equals(names.fromString("NAME"))) throw new RuntimeException("used field name: " + f.getName()); + else if (f.name.equals(names.fromString(helperField))){ + if (!Flags.isStatic(f.sym)) + throw new RuntimeException("helper field must be static"); + + f.sym.appendAttributes(List.of(new Attribute.Compound(ExcludeType, List.nil()))); + JCTree.JCAnnotation an = maker.Annotation(maker.Type(ExcludeType), List.nil()); + an.type = ExcludeType; + + f.mods.annotations = f.mods.annotations.append(an); + + makeHelper = f.sym; + break; + } + } + } + if (makeHelper == null) + throw new RuntimeException("helper store field was not found, please add a field to store helper (now set helper field name: \"" + helperField + "\")"); + + Symbol.VarSymbol NAME = new Symbol.VarSymbol( + Flags.STATIC | Flags.PUBLIC | Flags.FINAL | Flags.HASINIT, + names.fromString("NAME"), + symtab.stringType.tsym.type.constType(className), + root.sym + ); + NAME.setDeclarationAttributes(List.of(new Attribute.Compound(ExcludeType, List.nil()))); + Symbol.VarSymbol INSTANCE = new Symbol.VarSymbol( + Flags.STATIC | Flags.PUBLIC | Flags.FINAL | Flags.HASINIT, + names.fromString("INSTANCE"), + DynamicClassType, + root.sym + ); + INSTANCE.setDeclarationAttributes(List.of(new Attribute.Compound(ExcludeType, List.nil()))); + + root.sym.members_field.enter(NAME); + root.sym.members_field.enter(INSTANCE); + + ArrayList list = new ArrayList<>(root.defs); + boolean methodDecled = false; + boolean foundHelper = false; + for (int i = 0; i < list.size(); i++) { + if (list.get(i) instanceof JCTree.JCVariableDecl v && v.name.equals(names.fromString(helperField))) foundHelper = true; + else if (list.get(i) instanceof JCTree.JCMethodDecl m && !m.sym.isConstructor()) methodDecled = true; + + if (methodDecled && foundHelper){ + maker.at(list.get(i).getEndPosition(unit.endPositions)); + + List args = List.of( + maker.Literal(className), + maker.ClassLiteral(root.sym.type), + superClass != null? maker.Select(maker.Type(superClass), names.fromString("INSTANCE")): maker.Literal(TypeTag.BOT, null), + maker.Ident(makeHelper) + ); + JCTree.JCVariableDecl nameDecl = maker.VarDef(NAME, maker.Literal(className)); + JCTree.JCAnnotation an = maker.Annotation(maker.Type(ExcludeType), List.nil()); + an.type = ExcludeType; + nameDecl.mods.annotations = List.of(an); + + JCTree.JCVariableDecl instDecl = maker.VarDef(INSTANCE, maker.Apply( + List.nil(), + maker.Select(maker.Type(DynamicClassType), names.fromString("visit")), + args + )); + JCTree.JCAnnotation ana = maker.Annotation(maker.Type(ExcludeType), List.nil()); + ana.type = ExcludeType; + instDecl.mods.annotations = List.of(ana); + + list.add(i, instDecl); + list.add(i, nameDecl); + break; + } + } + root.defs = List.from(list); + + root.accept(new TreeScanner(){ + final HashMap signatures = new HashMap<>(); + + @Override + public void visitClassDef(JCTree.JCClassDecl tree) { + if (tree == root) super.visitClassDef(tree);//不处理任何内部类 + else if (!Flags.isStatic(tree.sym)) throw new RuntimeException("dynamilized class cannot contains a non-static internal class"); + } + + @Override + public void visitMethodDef(JCTree.JCMethodDecl meth) { + if (!Flags.isStatic(meth.sym) && (Flags.PUBLIC & meth.mods.flags) != 0 && !meth.sym.isConstructor()){ + meth.mods.flags = meth.mods.flags | Flags.STATIC; + meth.mods.annotations = List.nil(); + meth.sym.resetAnnotations(); + + Symbol.VarSymbol self = new Symbol.VarSymbol(Flags.PARAMETER, names.fromString("self"), RawType, meth.sym); + Symbol.VarSymbol sup = new Symbol.VarSymbol(Flags.PARAMETER, names.fromString("sup"), ReadOnlyPoolType, meth.sym); + + self.setDeclarationAttributes(List.of(new Attribute.Compound(ThisType, List.nil()))); + sup.setDeclarationAttributes(List.of(new Attribute.Compound(SuperType, List.nil()))); + + meth.sym.params = meth.sym.params.prepend(sup); + meth.sym.params = meth.sym.params.prepend(self); + + JCTree.JCVariableDecl s = maker.VarDef(self, null); + JCTree.JCVariableDecl su = maker.VarDef(sup, null); + meth.params = meth.params.prepend(su); + meth.params = meth.params.prepend(s); + + meth.sym.type = new Type.MethodType(List.from(meth.params.map(d -> d.sym.type)), meth.sym.getReturnType(), meth.sym.getThrownTypes(), meth.sym.type.tsym); + + meth.body.accept(new TreeTranslator() { + @Override + public void visitIdent(JCTree.JCIdent tree) { + if (tree.name.equals(names._super) || tree.name.equals(names._this)){ + super.visitIdent(tree); + return; + } + + TreePath path = trees.getPath(unit, tree); + Symbol sym = trees.getElement(path); + + if (sym != null && sym.getKind() != ElementKind.PARAMETER + && (sym instanceof Symbol.MethodSymbol || (sym instanceof Symbol.VarSymbol v && v.owner instanceof Symbol.ClassSymbol)) && !Flags.isStatic(sym)) { + JCTree.JCFieldAccess res = maker.Select(maker.Ident(names._this), sym.name); + res.sym = sym; + result = res; + } + else super.visitIdent(tree); + } + }); + meth.body.accept(new TreeTranslator(){ + @Override + public void visitApply(JCTree.JCMethodInvocation tree) { + if (tree.meth instanceof JCTree.JCFieldAccess ac + && ac.name.equals(names.fromString("super$")) && tree.args.isEmpty()){ + JCTree.JCIdent res = maker.Ident(sup); + res.sym = ac.sym; + result = res; + } + else super.visitApply(tree); + } + + @Override + public void visitAssign(JCTree.JCAssign tree) { + if (tree.lhs instanceof JCTree.JCFieldAccess ac && isSelfRef(ac)){ + result = maker.Apply( + List.nil(), + maker.Select(maker.Ident(self), names.fromString("setVar")), + List.of(maker.Literal(ac.name.toString()), tree.rhs) + ); + } + else super.visitAssign(tree); + } + + @Override + public void visitAssignop(JCTree.JCAssignOp tree) { + if (tree.lhs instanceof JCTree.JCFieldAccess ac && ac.sym instanceof Symbol.VarSymbol v && isSelfRef(ac)) { + Symbol.ParamSymbol arg = new Symbol.ParamSymbol(0, names.fromString("val"), v.type, meth.sym); + + tree.lhs = maker.Ident(arg); + + result = maker.Apply( + List.nil(), + maker.Select(maker.Ident(self), names.fromString("calculateVar")), + List.of( + maker.Literal(ac.name.toString()), + maker.Lambda( + List.of(maker.Param(arg.name, arg.type, meth.sym)), + tree + ) + ) + ); + } + else super.visitAssignop(tree); + } + + @Override + public void visitUnary(JCTree.JCUnary tree) { + if (tree.arg instanceof JCTree.JCFieldAccess ac && ac.sym instanceof Symbol.VarSymbol v && isSelfRef(ac)) { + Symbol.ParamSymbol arg = new Symbol.ParamSymbol(0, names.fromString("val"), v.type, meth.sym); + + tree.arg = maker.Ident(arg); + + result = maker.Apply( + List.nil(), + maker.Select(maker.Ident(self), names.fromString("calculateVar")), + List.of( + maker.Literal(ac.name.toString()), + maker.Lambda( + List.of(maker.Param(arg.name, arg.type, meth.sym)), + tree + ) + ) + ); + } + else super.visitUnary(tree); + } + }); + meth.body.accept(new TreeTranslator(){ + @Override + public void visitSelect(JCTree.JCFieldAccess tree) { + if (tree.sym instanceof Symbol.VarSymbol var && isSelfRef(tree)) { + Object def = switch(var.type.getTag()){ + case BYTE, INT, DOUBLE, FLOAT, LONG, SHORT, CHAR -> 0; + case BOOLEAN -> false; + default -> null; + }; + + result = maker.Apply( + List.of(maker.Type(var.type)), + maker.Select(maker.Ident(self), names.fromString("getVar")), + List.from( + def != null? new JCTree.JCExpression[]{ + maker.Literal(tree.name.toString()), + maker.Literal(var.type.getTag(), def) + }: + new JCTree.JCExpression[]{ + maker.Literal(tree.name.toString()) + } + ) + ); + } + else super.visitSelect(tree); + + } + }); + meth.body.accept(new TreeScanner(){ + @Override + public void visitApply(JCTree.JCMethodInvocation tree){ + if (tree.meth instanceof JCTree.JCFieldAccess ac && ac.sym instanceof Symbol.MethodSymbol m && !m.isConstructor() + && !m.owner.type.equals(DynamicObjectType) && isSelfRef(ac)){ + Symbol.VarSymbol signature = annotation.signatureMethods()? signatures.computeIfAbsent(m, met -> { + Symbol.VarSymbol sym = new Symbol.VarSymbol( + Flags.PRIVATE | Flags.STATIC | Flags.FINAL, + names.fromString("signature#" + signatures.size()), + functionType, + root.sym + ); + sym.setDeclarationAttributes(List.of(new Attribute.Compound(ExcludeType, List.nil()))); + + ArrayList list = new ArrayList<>(); + for (Symbol.VarSymbol param : m.params) { + list.add(maker.ClassLiteral(new Type.ClassType(param.type, List.nil(), param.type.tsym))); + } + root.defs = root.defs.prepend(maker.VarDef( + sym, + maker.Apply( + List.nil(), + maker.Select( + maker.Type(functionType), + names.fromString("inst") + ), + List.from(list) + ) + )); + + return sym; + }): null; + + tree.args = tree.args.prepend(maker.Literal(ac.name.toString())); + tree.typeargs = List.of(maker.Type(m.getReturnType())); + if (signature != null) tree.args = tree.args.prepend(maker.Ident(signature)); + ac.name = names.fromString("invokeFunc"); + } + + super.visitApply(tree); + } + + @Override + public void visitIdent(JCTree.JCIdent tree) { + if (tree.name.equals(names._this)) tree.name = self.name; + else if (tree.name.equals(names._super)) tree.name = sup.name; + } + }); + } + else if (meth.sym != null && meth.sym.getAnnotationMirrors().stream().noneMatch(e -> e.type.equals(ExcludeType))){ + meth.sym.appendAttributes(List.of(new Attribute.Compound(ExcludeType, List.nil()))); + JCTree.JCAnnotation anno = maker.Annotation(maker.Type(ExcludeType), List.nil()); + anno.type = ExcludeType; + meth.mods.annotations = meth.mods.annotations.append(anno); + } + } + }); + + for (JCTree def : root.defs) { + if (def instanceof JCTree.JCVariableDecl field){ + if (!Flags.isStatic(field.sym)){ + field.mods.flags = (field.mods.flags | Flags.STATIC | Flags.PUBLIC) & ~(Flags.PRIVATE | Flags.PROTECTED); + + if (!(field.init instanceof JCTree.JCLiteral) && field.init != null){ + field.sym.type = new Type.ClassType( + new Type.ClassType(ProducterType.tsym.owner.type, List.nil(), ProducterType.tsym.owner.type.tsym), + List.of(new Type.WildcardType(field.sym.type, BoundKind.UNBOUND, ProducterType.tsym)), + ProducterType.tsym + ); + field.vartype = maker.Type(field.sym.type); + field.type = field.sym.type; + field.init = maker.Lambda( + List.nil(), + field.init + ); + } + } + else if (field.sym != null && field.sym.getAnnotationMirrors().stream().noneMatch(e -> e.type.equals(ExcludeType))){ + field.sym.appendAttributes(List.of(new Attribute.Compound(ExcludeType, List.nil()))); + JCTree.JCAnnotation a = maker.Annotation(maker.Type(ExcludeType), List.nil()); + a.type = ExcludeType; + field.mods.annotations = field.mods.annotations.append(a); + } + } + } + + //Static blocks aren't actually that pretty... What do you think about? + /*root.defs = root.defs.append(maker.Block(Flags.STATIC, List.of( + maker.Exec( + maker.Apply( + List.nil(), + maker.Select(maker.Ident(INSTANCE), names.fromString("visitClass")), + List.of(maker.ClassLiteral(root.sym.type), maker.Ident(makeHelper)) + ) + ) + )));*/ + + genLog(anno, unit); + } + } + return super.process(set, roundEnvironment); + } + + private boolean isSelfRef(JCTree.JCFieldAccess ac) { + return ac.selected instanceof JCTree.JCIdent i + && (i.name.equals(names._this) || i.name.equals(names._super)); + } + + @Override + public Set getSupportedAnnotationTypes() { + return Set.of(DynamilizeClass.class.getCanonicalName()); + } +} diff --git a/apt/src/main/java/dynamilize/annoprocessor/DynamicContextProcessor.java b/apt/src/main/java/dynamilize/annoprocessor/DynamicContextProcessor.java new file mode 100644 index 0000000..7d147d3 --- /dev/null +++ b/apt/src/main/java/dynamilize/annoprocessor/DynamicContextProcessor.java @@ -0,0 +1,297 @@ +package dynamilize.annoprocessor; + +import com.google.auto.service.AutoService; +import com.sun.source.util.TreePath; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeScanner; +import com.sun.tools.javac.util.List; +import dynamilize.annotations.ContextMaker; +import dynamilize.annotations.DynamicContext; +import dynamilize.annotations.NewDynamic; + +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +@AutoService(Processor.class) +public class DynamicContextProcessor extends BaseProcessor { + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + for(TypeElement element : annotations) { + for (Element c : roundEnv.getElementsAnnotatedWith(element)) { + JCTree.JCClassDecl root = (JCTree.JCClassDecl) trees.getTree(c); + JCTree.JCCompilationUnit unit = (JCTree.JCCompilationUnit) trees.getPath(c).getCompilationUnit(); + + final HashMap constLists = new HashMap<>(); + + root.accept(new MakerContextScanner(unit){ + @Override + public void visitNewClass(JCTree.JCNewClass tree) { + scan(tree.encl); + scan(tree.typeargs); + scan(tree.clazz); + scan(tree.args); + //scan(tree.def); 不处理匿名块的内容 + } + + @Override + public void visitVarDefSub(JCTree.JCVariableDecl var) { + RawAnnotationMirrors newDynamicAnno = getAnnotationParams(var.mods.annotations, NewDynamic.class, unit, Map.of("refDynamicMaker", "", "impls", new Class[0], "aspects", new Class[]{void.class})); + + if (newDynamicAnno != null){ + if (newDynamicAnno.getString("refDynamicMaker").equals("") && getMaker() == null) + throw new RuntimeException("can not find usable DynamicMaker in context, please identify a field or variable of type DynamicMaker or its subclass, and add annotation @ContextMaker to it"); + + if (!(var.init instanceof JCTree.JCNewClass) && !(var.init instanceof JCTree.JCTypeCast c && c.expr instanceof JCTree.JCNewClass)) + throw new RuntimeException("annotate @NewDynamic on field or variable require initial it by create instance"); + + JCTree.JCTypeCast c = var.init instanceof JCTree.JCTypeCast ca? ca: null; + JCTree.JCNewClass n = (JCTree.JCNewClass) (c != null? c.expr: var.init); + + //TODO: make it workable + if (n.def != null) throw new RuntimeException("anonymous extends is illegal in new dynamic object context"); + + TreePath path = trees.getPath(unit, n); + Symbol sym = trees.getElement(path); + + boolean isEncl = sym instanceof Symbol.MethodSymbol msy && msy.isConstructor() && sym.owner.isInner(); + if (isEncl && n.encl == null){ + n.encl = maker.Select( + maker.Type(sym.owner.owner.type), + names._this + ); + } + + JCTree.JCExpression baseClassRef = maker.Select(n.clazz, names._class); + + String dyc = newDynamicAnno.getString("dynClassName"); + JCTree[] inters = newDynamicAnno.getArrTree("impls"); + JCTree[] asp = newDynamicAnno.getArrTree("aspects"); + inters = inters == null? new JCTree[0]: inters; + asp = asp == null? new JCTree[0]: asp; + + ArrayList args = new ArrayList<>(); + args.add(baseClassRef); + + Attribute[] i = newDynamicAnno.getArr("impls"); + JCTree[] finalInters = inters; + args.add(maker.Ident(constLists.computeIfAbsent( + new TypeCompound(Arrays.stream(i).map(e -> ((Attribute.Class) e).classType).toArray(Type[]::new)), + ts -> makeConstField(root, constLists.size(), finalInters) + ))); + + Attribute[] a = newDynamicAnno.getArr("aspects"); + if (a.length > 0 && ((Attribute.Class) a[0]).classType.getKind() != TypeKind.VOID) { + JCTree[] finalAsp = asp; + args.add(maker.Ident(constLists.computeIfAbsent( + new TypeCompound(Arrays.stream(a).map(e -> ((Attribute.Class) e).classType).toArray(Type[]::new)), + ts -> makeConstField(root, constLists.size(), finalAsp) + ))); + } + else args.add(maker.TypeCast( + maker.TypeArray(maker.Ident(symtab.classType.tsym)), + maker.Literal(TypeTag.BOT, null) + )); + + args.add(maker.Apply( + List.nil(), + maker.Select( + maker.Type(DynamicClassType), + names.fromString("get") + ), + List.of(maker.Literal(dyc)) + )); + + if (isEncl) args.add(n.encl); + args.addAll(n.args); + + JCTree.JCExpression invoke; + if (getMaker() != null){ + invoke = maker.Apply( + List.nil(), + maker.Select(maker.Ident(getMaker()), names.fromString("newInstance")), + List.from(args) + ); + } + else { + invoke = maker.Apply( + List.nil(), + maker.Select( + parsers.newParser(newDynamicAnno.getString("refDynamicMaker"), false, true, false).parseExpression(), + names.fromString("newInstance") + ), + List.from(args) + ); + } + + var.init = c == null || !isAssignable(DynamicObjectType, c.type)? maker.Apply( + List.nil(), + maker.Select( + invoke, + names.fromString("objSelf") + ), + List.nil() + ): invoke; + } + + super.visitVarDefSub(var); + } + }); + + genLog(element, unit); + } + } + return super.process(annotations, roundEnv); + } + + private Symbol.VarSymbol makeConstField(JCTree.JCClassDecl root, int id, JCTree[] aspects) { + Symbol.VarSymbol sym = new Symbol.VarSymbol( + Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL, + names.fromString("$TYPE_LIST_CONST@" + id), + types.makeArrayType(new Type.ClassType(symtab.classType.tsym.owner.type, List.nil(), symtab.classType.tsym)), + root.sym + ); + + root.defs = root.defs.prepend(maker.VarDef(sym, maker.NewArray( + maker.Ident(symtab.classType.tsym), + List.nil(), + List.from(Arrays.stream(aspects).map(t -> ((JCTree.JCExpression) t)).toList()) + ))); + + return sym; + } + + @Override + public Set getSupportedAnnotationTypes() { + return Set.of(DynamicContext.class.getCanonicalName()); + } + + private static class TypeCompound { + private final Type[] types; + private final int hash; + + private TypeCompound(Type[] types) { + this.types = types; + for (Type type : types) { + if (!type.isInterface()) throw new IllegalArgumentException("interface only"); + } + hash = Arrays.hashCode(types); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TypeCompound that = (TypeCompound) o; + return hash == that.hash && Arrays.equals(types, that.types); + } + + @Override + public int hashCode() { + int result = Objects.hash(hash); + result = 31 * result + Arrays.hashCode(types); + return result; + } + } + + class MakerContextScanner extends TreeScanner{ + final JCTree.JCCompilationUnit unit; + + boolean isStatic; + + LinkedList> contextMakerStack = new LinkedList<>(); + LinkedList> staticMakerStack = new LinkedList<>(); + + MakerContextScanner(JCTree.JCCompilationUnit unit) { + this.unit = unit; + } + + public void setTopSym(Symbol.VarSymbol sym){ + if (isStatic){ + if (staticMakerStack.isEmpty()) throw new RuntimeException("no maker found"); + staticMakerStack.peek().set(sym); + } + if (contextMakerStack.isEmpty()) throw new RuntimeException("no maker found"); + contextMakerStack.peek().set(sym); + } + + public Symbol.VarSymbol getMaker(){ + LinkedList> stack = isStatic? staticMakerStack: contextMakerStack; + for (AtomicReference ref : stack) { + if (ref.get() != null) return ref.get(); + } + + return null; + } + + @Override + public final void visitClassDef(JCTree.JCClassDecl tree) { + isStatic = false; + contextMakerStack.push(new AtomicReference<>()); + staticMakerStack.push(new AtomicReference<>()); + visitClassDefSub(tree); + contextMakerStack.pop(); + staticMakerStack.pop(); + } + + public void visitClassDefSub(JCTree.JCClassDecl tree){ + super.visitClassDef(tree); + } + + @Override + public final void visitBlock(JCTree.JCBlock tree) { + boolean tmp = isStatic; + isStatic = isStatic || ((tree.flags & Flags.STATIC) != 0); + staticMakerStack.push(new AtomicReference<>()); + contextMakerStack.push(new AtomicReference<>()); + visitBlockSub(tree); + staticMakerStack.pop(); + contextMakerStack.pop(); + isStatic = tmp; + } + + public void visitBlockSub(JCTree.JCBlock tree) { + super.visitBlock(tree); + } + + @Override + public final void visitMethodDef(JCTree.JCMethodDecl tree) { + boolean tmp = isStatic; + isStatic = tree.sym.isStatic(); + contextMakerStack.push(new AtomicReference<>()); + staticMakerStack.push(new AtomicReference<>()); + visitMethodDefSub(tree); + contextMakerStack.pop(); + staticMakerStack.pop(); + isStatic = tmp; + } + + public void visitMethodDefSub(JCTree.JCMethodDecl tree) { + super.visitMethodDef(tree); + } + + @Override + public final void visitVarDef(JCTree.JCVariableDecl tree) { + boolean tmp = isStatic; + isStatic = (tree.sym != null && tree.sym.isStatic()) || tmp; + if (getAnnotationParams(tree.mods.annotations, ContextMaker.class, unit) != null) { + if (tree.vartype.type.getKind() != TypeKind.DECLARED || !isAssignable(DynamicMakerType, tree.vartype.type)) + throw new RuntimeException("illegal annotation using! @ContextMaker can only annotate on var and field that type was DynamicMaker or it subclass"); + + setTopSym(tree.sym); + } + visitVarDefSub(tree); + isStatic = tmp; + } + + public void visitVarDefSub(JCTree.JCVariableDecl tree){ + super.visitVarDef(tree); + } + } +} diff --git a/baseimpl/.gitignore b/baseimpl/.gitignore new file mode 100644 index 0000000..b63da45 --- /dev/null +++ b/baseimpl/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/baseimpl/build.gradle b/baseimpl/build.gradle new file mode 100644 index 0000000..2b6f728 --- /dev/null +++ b/baseimpl/build.gradle @@ -0,0 +1,44 @@ +plugins { + id 'java' +} +apply plugin: 'maven-publish' + +sourceSets.main.java.srcDirs = ["src/main/java"] +sourceSets.test.java.srcDirs = ["src/test/java"] + +group = 'com.github.EB-wilson' + +sourceCompatibility = 16 +targetCompatibility = 16 + +repositories { + mavenCentral() + maven{ url 'https://www.jitpack.io' } +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.github.EB-wilson.JavaDynamilizer' + artifactId = 'baseimpl' + version = "$JDERVersion" + from components.java + } + } +} + +dependencies { + compileOnly project(":core") + + annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2' + compileOnly 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2' +} + +tasks.withType(JavaCompile).configureEach { + sourceCompatibility = 9 + targetCompatibility = 9 + + options.compilerArgs.addAll([ + "--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED" + ]) +} diff --git a/baseimpl/src/main/java/dynamilize/Demodulator.java b/baseimpl/src/main/java/dynamilize/Demodulator.java new file mode 100644 index 0000000..8c5358e --- /dev/null +++ b/baseimpl/src/main/java/dynamilize/Demodulator.java @@ -0,0 +1,120 @@ +package dynamilize; + +import sun.misc.Unsafe; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +/**反模块化工具, 仅提供了一个主要方法{@link Demodulator#makeModuleOpen(Module, Package, Module)}用于强制对需要的模块开放模块的软件包。 + *

此类行为可能完全打破模块化的访问保护,本身是不安全的,若不是必要情况,请尽量避免使用该类 + *

此类仅在JDK9之后可用,避免在更早的版本引用此类的方法,且此类仅在desktop平台可用,安卓平台不可使用此类的任何行为 + * + * @author EBwilson */ +@SuppressWarnings({"unchecked"}) +public class Demodulator{ + private static final long fieldFilterOffset = 112L; + + private static final Unsafe unsafe; + + private static final Field opensField; + private static final Field exportField; + + private static final Method exportNative; + + static{ + try{ + Constructor cstr = Unsafe.class.getDeclaredConstructor(); + cstr.setAccessible(true); + unsafe = cstr.newInstance(); + + ensureFieldOpen(); + + opensField = Module.class.getDeclaredField("openPackages"); + exportField = Module.class.getDeclaredField("exportedPackages"); + + makeModuleOpen(Module.class.getModule(), "java.lang", Demodulator.class.getModule()); + + exportNative = Module.class.getDeclaredMethod("addExports0", Module.class, String.class, Module.class); + exportNative.setAccessible(true); + exportNative.invoke(null, Module.class.getModule(), "java.lang", Demodulator.class.getModule()); + }catch(NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchFieldException e){ + throw new RuntimeException(e); + } + } + + public static void makeModuleOpen(Module from, Class clazz, Module to){ + if (clazz.isArray()){ + makeModuleOpen(from, clazz.getComponentType(), to); + } + else makeModuleOpen(from, clazz.getPackage(), to); + } + + public static void makeModuleOpen(Module from, Package pac, Module to){ + if(checkModuleOpen(from, pac, to)) return; + + makeModuleOpen(from, pac.getName(), to); + } + + @SuppressWarnings("unchecked") + public static void makeModuleOpen(Module from, String pac, Module to){ + try { + if (exportNative != null) exportNative.invoke(null, from, pac, to); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + + Map> opensMap = (Map>) unsafe.getObjectVolatile(from, unsafe.objectFieldOffset(opensField)); + if(opensMap == null){ + opensMap = new HashMap<>(); + unsafe.putObjectVolatile(from, unsafe.objectFieldOffset(opensField), opensMap); + } + + Map> exportsMap = (Map>) unsafe.getObjectVolatile(from, unsafe.objectFieldOffset(exportField)); + if(exportsMap == null){ + exportsMap = new HashMap<>(); + unsafe.putObjectVolatile(from, unsafe.objectFieldOffset(exportField), exportsMap); + } + + Set opens = opensMap.computeIfAbsent(pac, e -> new HashSet<>()); + Set exports = exportsMap.computeIfAbsent(pac, e -> new HashSet<>()); + + try{ + opens.add(to); + }catch(UnsupportedOperationException e){ + ArrayList lis = new ArrayList<>(opens); + lis.add(to); + opensMap.put(pac, new HashSet<>(lis)); + } + + try{ + exports.add(to); + }catch(UnsupportedOperationException e){ + ArrayList lis = new ArrayList<>(exports); + lis.add(to); + exportsMap.put(pac, new HashSet<>(lis)); + } + } + + public static boolean checkModuleOpen(Module from, Package pac, Module to){ + Objects.requireNonNull(from); + Objects.requireNonNull(to); + + if(pac == null) return true; + + return from.isOpen(pac.getName(), to); + } + + public static void ensureFieldOpen(){ + try{ + Class clazz = Class.forName("jdk.internal.reflect.Reflection"); + Map, Set> map = (Map, Set>) unsafe.getObject(clazz, fieldFilterOffset); + map.clear(); + }catch(ClassNotFoundException e){ + throw new RuntimeException(e); + } + } +} diff --git a/baseimpl/src/main/java/dynamilize/DynamicFactory.java b/baseimpl/src/main/java/dynamilize/DynamicFactory.java new file mode 100644 index 0000000..90b3ddc --- /dev/null +++ b/baseimpl/src/main/java/dynamilize/DynamicFactory.java @@ -0,0 +1,161 @@ +package dynamilize; + +import dynamilize.classmaker.ASMGenerator; +import dynamilize.classmaker.AbstractClassGenerator; +import dynamilize.classmaker.BaseClassLoader; +import dynamilize.classmaker.ByteClassLoader; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Objects; + +/**用于创建{@linkplain DynamicMaker 动态生成器}的工厂类,内部提供了默认生成器的声明 + * + * @author EBwilson + * @since 1.6*/ +public class DynamicFactory { + protected PackageAccHandler handler; + protected AbstractClassGenerator generator; + protected JavaHandleHelper helper; + + private static final DynamicFactory DEFAULT = new DynamicFactory(){{ + setDefaultGenerator(); + setDefaultHelper(); + setPackageAccHandler(new UnsafePackageAccHandler(generator)); + }}; + + /**直接获取默认的通用实现实例,该方法获取的动态生成器等价于: + *

{@code
+   * new DynamicFactory().setDefaultGenerator().setDefaultHelper().getMaker();
+   * }
+ * + * @see DynamicFactory#setDefaultGenerator() + * @see DynamicFactory#setDefaultHelper() + * @return 一个由默认声明生产的动态生成器*/ + public static DynamicMaker getDefault(){ + return DEFAULT.getMaker(); + } + + /**获取{@linkplain DynamicMaker 动态生成器}实例 + * + * @throws NullPointerException 若有必要的属性尚未设置*/ + public DynamicMaker getMaker(){ + PackageAccHandler handler = this.handler; + + AbstractClassGenerator generator = this.generator; + + Objects.requireNonNull(helper); + Objects.requireNonNull(generator); + + return handler == null? new DynamicMaker(helper) { + @Override + protected Class generateClass(Class baseClass, Class[] interfaces, Class[] aspects) { + return makeClassInfo(baseClass, interfaces, aspects).generate(generator); + } + }: new DynamicMaker(helper) { + @Override + protected Class generateClass(Class baseClass, Class[] interfaces, Class[] aspects) { + return makeClassInfo(baseClass, interfaces, aspects).generate(generator); + } + + @Override + protected Class handleBaseClass(Class baseClass) { + return handler.handle(baseClass); + } + }; + } + + /**设置{@linkplain PackageAccHandler 包私有访问处理器},若不设置则动态生成器不对超类的包私有方法进行委托 + * + * @see PackageAccHandler*/ + public DynamicFactory setPackageAccHandler(PackageAccHandler handler){ + this.handler = handler; + return this; + } + + /**设置将{@linkplain dynamilize.classmaker.ClassInfo 类型描述}生成为Java类对象的{@linkplain AbstractClassGenerator 类型生成器},必要! + * + * @see AbstractClassGenerator*/ + public DynamicFactory setGenerator(AbstractClassGenerator generator){ + this.generator = generator; + return this; + } + + /**设置对Java层行为进行处理的{@linkplain JavaHandleHelper Java行为支持器},必要! + * + * @see JavaHandleHelper*/ + public DynamicFactory setJavaHandleHelper(JavaHandleHelper helper){ + this.helper = helper; + return this; + } + + /**使用{@linkplain DynamicFactory#setDefaultGenerator(ByteClassLoader, int) 默认配置}设置类型生成器,字节码加载器使用默认实现{@link BaseClassLoader},目标字节码版本为152(Java1.8) + * + * @see DynamicFactory#setDefaultGenerator(ByteClassLoader, int) + * @see BaseClassLoader*/ + public DynamicFactory setDefaultGenerator(){ + return setDefaultGenerator(new BaseClassLoader(DynamicFactory.class.getClassLoader()), 52); + } + + /**将类型生成器设置为{@link ASMGenerator}的实现,该实现适用于绝大多数情况的JVM + * + * @param loader 加载字节码为类对象的类加载器 + * @param byteLevel 目标字节码级别,最低支持50(Java1.8) + * + * @see ASMGenerator*/ + public DynamicFactory setDefaultGenerator(ByteClassLoader loader, int byteLevel){ + if (byteLevel < 52 || byteLevel > 255) + throw new IllegalArgumentException("illegal byte code level: " + byteLevel); + + generator = new ASMGenerator(loader, byteLevel); + + return this; + } + + /**设置{@link JavaHandleHelper}的默认内部实现,该实现直接引用{@link JavaVariable}和{@link JavaMethodEntry}以及默认的反模块化访问 + * + * @see JavaVariable + * @see JavaMethodEntry*/ + public DynamicFactory setDefaultHelper(){ + helper = new JavaHandleHelper() { + @Override + public void makeAccess(Object object) { + if (object instanceof Executable ext){ + if (ext instanceof Method method) Demodulator.makeModuleOpen(method.getReturnType().getModule(), method.getReturnType(), DynamicMaker.class.getModule()); + + if (ext instanceof Constructor cstr) Demodulator.makeModuleOpen(cstr.getDeclaringClass().getModule(), cstr.getDeclaringClass(), DynamicMaker.class.getModule()); + + for (Class type : ext.getParameterTypes()) { + Demodulator.makeModuleOpen(type.getModule(), type, DynamicMaker.class.getModule()); + } + + ext.setAccessible(true); + } + else if (object instanceof Field field){ + Demodulator.makeModuleOpen(field.getType().getModule(), field.getType(), DynamicMaker.class.getModule()); + + field.setAccessible(true); + } + else if (object instanceof Class clazz){ + Demodulator.makeModuleOpen(clazz.getModule(), clazz.getPackage(), DynamicMaker.class.getModule()); + } + } + + @Override + public IVariable genJavaVariableRef(Field field) { + makeAccess(field); + return new JavaVariable(field); + } + + @Override + public IFunctionEntry genJavaMethodRef(Method method) { + makeAccess(method); + return new JavaMethodEntry(method); + } + }; + + return this; + } +} diff --git a/baseimpl/src/main/java/dynamilize/JavaMethodEntry.java b/baseimpl/src/main/java/dynamilize/JavaMethodEntry.java new file mode 100644 index 0000000..63a588e --- /dev/null +++ b/baseimpl/src/main/java/dynamilize/JavaMethodEntry.java @@ -0,0 +1,46 @@ +package dynamilize; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/**对{@linkplain DynamicClass#visitClass(Class, JavaHandleHelper) 行为样版}中方法描述的入口,在动态类中描述子实例的某一函数行为。 + *

方法入口的运行实际上是对样版方法的引用,因此需要确保样版方法所在的类始终有效,方法入口会生成这个方法的入口函数提供给动态对象使用 + * + * @author EBwilson */ +@SuppressWarnings("unchecked") +public class JavaMethodEntry implements IFunctionEntry{ + private final String name; + private final FunctionType type; + private final Function defFunc; + + /**直接通过目标方法创建方法入口,并生成对方法引用的句柄提供给匿名函数以描述此函数行为 + * + * @param invokeMethod 样版方法*/ + public JavaMethodEntry(Method invokeMethod){ + this.name = invokeMethod.getName(); + this.type = FunctionType.inst(invokeMethod); + + defFunc = (self, args) -> { + try { + return invokeMethod.invoke(self.objSelf(), args.args()); + } catch (InvocationTargetException|IllegalAccessException e) { + throw new RuntimeException(e); + } + }; + } + + @Override + public String getName(){ + return name; + } + + @Override + public Function getFunction(){ + return (Function) defFunc; + } + + @Override + public FunctionType getType(){ + return type; + } +} diff --git a/baseimpl/src/main/java/dynamilize/JavaVariable.java b/baseimpl/src/main/java/dynamilize/JavaVariable.java new file mode 100644 index 0000000..fad6f1c --- /dev/null +++ b/baseimpl/src/main/java/dynamilize/JavaVariable.java @@ -0,0 +1,183 @@ +package dynamilize; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; + +/**对java字段的引用对象,基于{@linkplain MethodHandle 方法句柄}的默认内部实现 + * + * @author EBwilson */ +public class JavaVariable implements IVariable{ + private final Field field; + + private final MethodHandle getter; + private final MethodHandle setter; + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + public JavaVariable(Field field){ + this.field = field; + + try { + getter = LOOKUP.unreflectGetter(field); + setter = LOOKUP.unreflectSetter(field); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public String name(){ + return field.getName(); + } + + @Override + public void init(DynamicObject object) { /*no action*/ } + + @SuppressWarnings("unchecked") + @Override + public T get(DynamicObject obj){ + try{ + return (T) getter.invoke(obj); + }catch(ClassCastException e){ + throw e; + }catch(Throwable e){ + throw new IllegalHandleException(e); + } + } + + @Override + public void set(DynamicObject obj, Object value){ + try{ + setter.invoke(obj, value); + }catch(Throwable e){ + throw new IllegalHandleException(e); + } + } + + @Override + public boolean get(DynamicObject obj, boolean def) { + try { + return field.getBoolean(obj); + } catch (IllegalAccessException e) { + throw new IllegalHandleException(e); + } + } + + @Override + public byte get(DynamicObject obj, byte def) { + try { + return field.getByte(obj); + } catch (IllegalAccessException e) { + throw new IllegalHandleException(e); + } + } + + @Override + public short get(DynamicObject obj, short def) { + try { + return field.getShort(obj); + } catch (IllegalAccessException e) { + throw new IllegalHandleException(e); + } + } + + @Override + public int get(DynamicObject obj, int def) { + try { + return field.getInt(obj); + } catch (IllegalAccessException e) { + throw new IllegalHandleException(e); + } + } + + @Override + public long get(DynamicObject obj, long def) { + try { + return field.getLong(obj); + } catch (IllegalAccessException e) { + throw new IllegalHandleException(e); + } + } + + @Override + public float get(DynamicObject obj, float def) { + try { + return field.getFloat(obj); + } catch (IllegalAccessException e) { + throw new IllegalHandleException(e); + } + } + + @Override + public double get(DynamicObject obj, double def) { + try { + return field.getDouble(obj); + } catch (IllegalAccessException e) { + throw new IllegalHandleException(e); + } + } + + @Override + public void set(DynamicObject obj, boolean value) { + try { + field.setBoolean(obj, value); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public void set(DynamicObject obj, byte value) { + try { + field.setByte(obj, value); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public void set(DynamicObject obj, short value) { + try { + field.setShort(obj, value); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public void set(DynamicObject obj, int value) { + try { + field.setInt(obj, value); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public void set(DynamicObject obj, long value) { + try { + field.setLong(obj, value); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public void set(DynamicObject obj, float value) { + try { + field.setFloat(obj, value); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public void set(DynamicObject obj, double value) { + try { + field.setDouble(obj, value); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/baseimpl/src/main/java/dynamilize/UnsafePackageAccHandler.java b/baseimpl/src/main/java/dynamilize/UnsafePackageAccHandler.java new file mode 100644 index 0000000..37a63e4 --- /dev/null +++ b/baseimpl/src/main/java/dynamilize/UnsafePackageAccHandler.java @@ -0,0 +1,44 @@ +package dynamilize; + +import dynamilize.classmaker.AbstractClassGenerator; +import dynamilize.classmaker.ClassInfo; +import jdk.internal.misc.Unsafe; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/**基于{@link jdk.internal.misc.Unsafe}实现的强制保护域类加载处理程序 + * + * @author EBwilson + * @since 1.6*/ +public class UnsafePackageAccHandler extends PackageAccHandler{ + private static final Object unsafe; + + static{ + try{ + Demodulator.makeModuleOpen(Object.class.getModule(), "jdk.internal.misc", UnsafePackageAccHandler.class.getModule()); + + Constructor cstr = Unsafe.class.getDeclaredConstructor(); + cstr.setAccessible(true); + unsafe = cstr.newInstance(); + }catch(NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e){ + throw new RuntimeException(e); + } + } + + private final AbstractClassGenerator generator; + + public UnsafePackageAccHandler(AbstractClassGenerator generator){ + this.generator = generator; + } + + @Override + protected Class loadClass(ClassInfo clazz, Class baseClass) { + return defineClass(clazz.name(), generator.genByteCode(clazz), baseClass.getClassLoader()); + } + + @SuppressWarnings("unchecked") + static Class defineClass(String name, byte[] bytes, ClassLoader loader){ + return (Class) ((Unsafe)unsafe).defineClass (name, bytes, 0, bytes.length, loader, null); + } +} diff --git a/build.gradle b/build.gradle index 874ff01..c1b3fe0 100644 --- a/build.gradle +++ b/build.gradle @@ -2,26 +2,21 @@ plugins { id 'java' id 'com.github.johnrengelman.shadow' version '2.0.4' } -apply plugin: 'maven-publish' -group 'com.github.EB-wilson' -version '1.4-B1' +apply plugin: 'maven-publish' -repositories { - mavenCentral() - maven{ url 'https://www.jitpack.io' } +ext{ + JDERVersion = 'V1.9-A1' } -dependencies { - implementation 'org.ow2.asm:asm:9.3' - - annotationProcessor "com.github.Anuken:jabel:0.7.0" -} +group 'com.github.EB-wilson' +version JDERVersion publishing { publications { maven(MavenPublication) { from components.java + org.gradle.api.internal.tasks.compile.JdkJavaCompiler } } } @@ -35,7 +30,9 @@ generateMetadataFileForMavenPublication{ } allprojects{ - tasks.withType(JavaCompile){ - options.compilerArgs.addAll(['--release', '8']) + tasks.withType(JavaCompile).tap { + configureEach { + options.encoding = "UTF-8" + } } } diff --git a/core/build.gradle b/core/build.gradle new file mode 100644 index 0000000..088c356 --- /dev/null +++ b/core/build.gradle @@ -0,0 +1,40 @@ +plugins { + id 'java' +} +apply plugin: 'maven-publish' + +sourceSets.main.java.srcDirs = ["src/main/java"] +sourceSets.test.java.srcDirs = ["src/test/java"] + +repositories { + mavenCentral() + maven{ url 'https://www.jitpack.io' } +} + +sourceCompatibility = 17 +targetCompatibility = 8 + +tasks.withType(JavaCompile).configureEach { + options.compilerArgs.addAll([ + "--release", "8" + ]) +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.github.EB-wilson.JavaDynamilizer' + artifactId = 'core' + version = "$JDERVersion" + from components.java + } + } +} + +dependencies { + implementation 'org.ow2.asm:asm:9.3' + apiElements 'org.ow2.asm:asm:9.3' + + annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2' + compileOnly 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2' +} diff --git a/src/main/java/dynamilize/ArgumentList.java b/core/src/main/java/dynamilize/ArgumentList.java similarity index 100% rename from src/main/java/dynamilize/ArgumentList.java rename to core/src/main/java/dynamilize/ArgumentList.java diff --git a/core/src/main/java/dynamilize/Calculator.java b/core/src/main/java/dynamilize/Calculator.java new file mode 100644 index 0000000..f9a4df0 --- /dev/null +++ b/core/src/main/java/dynamilize/Calculator.java @@ -0,0 +1,17 @@ +package dynamilize; + +/**变量计算器,函数式接口,用于对变量的当前值进行一系列处理后提供回调 + * + * @author EBwilson */ +@FunctionalInterface +public interface Calculator{ + Type calculate(Type input); + + @FunctionalInterface interface BoolCalculator{ boolean calculate(boolean input); } + @FunctionalInterface interface ByteCalculator{ byte calculate(byte input); } + @FunctionalInterface interface ShortCalculator{ short calculate(short input); } + @FunctionalInterface interface IntCalculator{ int calculate(int input); } + @FunctionalInterface interface LongCalculator{ long calculate(long input); } + @FunctionalInterface interface FloatCalculator{ float calculate(float input); } + @FunctionalInterface interface DoubleCalculator{ double calculate(double input); } +} diff --git a/src/main/java/dynamilize/ClassImplements.java b/core/src/main/java/dynamilize/ClassImplements.java similarity index 61% rename from src/main/java/dynamilize/ClassImplements.java rename to core/src/main/java/dynamilize/ClassImplements.java index e44ed24..3149020 100644 --- a/src/main/java/dynamilize/ClassImplements.java +++ b/core/src/main/java/dynamilize/ClassImplements.java @@ -3,16 +3,21 @@ import java.util.Arrays; import java.util.Objects; +/**保存类的超类及实现接口的容器,用于快速确认委托的构造类型 + * + * @author EBwilson */ class ClassImplements{ final Class base; final Class[] interfaces; + final Class[] aspects; private int hash; - public ClassImplements(Class base, Class[] interfaces){ + public ClassImplements(Class base, Class[] interfaces, Class[] aspects){ this.base = base; this.interfaces = interfaces; + this.aspects = aspects; this.hash = Objects.hash(base); - hash = 31*hash + Arrays.hashCode(interfaces); + hash = 31*hash + Arrays.hashCode(interfaces)^Arrays.hashCode(aspects); } @Override @@ -24,7 +29,7 @@ public String toString(){ public boolean equals(Object o){ if(this == o) return true; if(!(o instanceof ClassImplements that)) return false; - return base.equals(that.base) && interfaces.length == that.interfaces.length && hash == ((ClassImplements) o).hash; + return base.equals(that.base) && interfaces.length == that.interfaces.length && (aspects != null && that.aspects != null && aspects.length == that.aspects.length) && hash == that.hash; } @Override diff --git a/src/main/java/dynamilize/DataPool.java b/core/src/main/java/dynamilize/DataPool.java similarity index 94% rename from src/main/java/dynamilize/DataPool.java rename to core/src/main/java/dynamilize/DataPool.java index e79bf95..c5ef53b 100644 --- a/src/main/java/dynamilize/DataPool.java +++ b/core/src/main/java/dynamilize/DataPool.java @@ -23,7 +23,7 @@ public class DataPool{ private final Map varPool = new HashMap<>(); /**创建一个池对象并绑定到父池,父池可为null,这种情况下此池应当为被委托类型的方法/字段引用。 - *

你不应该在外部使用时调用此类型 + *

通常来说你不应该在{@link DynamicMaker}之外的任何地方实例化此类型 * * @param superPool 此池的父池*/ public DataPool(DataPool superPool){ @@ -35,8 +35,10 @@ public void init(DynamicObject self, Object... args){ HashSet varSetted = new HashSet<>(); while(curr != null){ - for(Map.Entry> entry: curr.getVarInit().entrySet()){ - if(varSetted.add(entry.getKey())) self.setVar(entry.getKey(), entry.getValue()); + for (IVariable var : curr.getVariables()) { + if (varSetted.add(var.name())){ + var.init(self); + } } curr = curr.superDyClass(); @@ -79,19 +81,16 @@ public void setConstructor(Function function, Class... argType){ public void setFunction(String name, Function function, Class... argsType){ FunctionType type = FunctionType.inst(argsType); funcPool.computeIfAbsent(name, n -> new HashMap<>()) - .put(type, new FunctionEntry<>(name, true, function, type, this)); + .put(type, new FunctionEntry<>(name, function, type)); } public void setFunction(String name, Function.SuperGetFunction func, Class[] argTypes){ FunctionType type = FunctionType.inst(argTypes); funcPool.computeIfAbsent(name, n -> new HashMap<>()) - .put(type, new FunctionEntry<>(name, true, func, type, this)); + .put(type, new FunctionEntry<>(name, func, type, this)); } public void setFunction(IFunctionEntry functionEntry){ - if(functionEntry.owner() != this) - throw new IllegalHandleException("function owner pool must equal added pool"); - funcPool.computeIfAbsent(functionEntry.getName(), e -> new HashMap<>()).put(functionEntry.getType(), functionEntry); } diff --git a/src/main/java/dynamilize/Func.java b/core/src/main/java/dynamilize/Delegate.java similarity index 70% rename from src/main/java/dynamilize/Func.java rename to core/src/main/java/dynamilize/Delegate.java index 0c3387a..4da3e6e 100644 --- a/src/main/java/dynamilize/Func.java +++ b/core/src/main/java/dynamilize/Delegate.java @@ -1,6 +1,8 @@ package dynamilize; -public interface Func{ +/**经过包装的委托调用函数接口,与{@linkplain Function 函数}不同,委托不接收this指针*/ +@FunctionalInterface +public interface Delegate{ R invoke(ArgumentList args); default R invoke(Object... args){ diff --git a/src/main/java/dynamilize/DynamicClass.java b/core/src/main/java/dynamilize/DynamicClass.java similarity index 67% rename from src/main/java/dynamilize/DynamicClass.java rename to core/src/main/java/dynamilize/DynamicClass.java index 4a89856..70b8ca1 100644 --- a/src/main/java/dynamilize/DynamicClass.java +++ b/core/src/main/java/dynamilize/DynamicClass.java @@ -1,12 +1,15 @@ package dynamilize; -import dynamilize.annotation.Exclude; +import dynamilize.runtimeannos.Exclude; +import dynamilize.runtimeannos.Super; +import dynamilize.runtimeannos.This; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.util.ArrayList; import java.util.HashMap; -import java.util.Map; /**保存动态对象行为信息的动态类型,描述了对象的共有行为和变量信息。 *

在{@link DynamicMaker}的构造实例方法里使用动态类型构造动态对象,动态对象会具有其类型描述的行为,对于基类与动态类中描述的同一方法会正常的处理覆盖关系。 @@ -16,7 +19,7 @@ *

  • 当动态类的超类的方法行为发生变更时,若类的行为中引用了超类方法,则类的行为会随之改变 *

    仅在动态对象的方法来自动态类描述的行为时,上述变更才会生效 * - *

    描述动态类的行为需要{@linkplain DynamicClass#visitClass(Class) 行为样版}或者函数表达式,以增量模式编辑类型行为,方法和默认变量只能新增/变更,不可删除 + *

    描述动态类的行为需要{@linkplain DynamicClass#visitClass(Class,JavaHandleHelper) 行为样版}或者函数表达式,以增量模式编辑类型行为,方法和默认变量只能新增/变更,不可删除 *

    {@code
      * 下面是一个简单的样例:
      * public class Template{
    @@ -28,24 +31,16 @@
      * }
      *
      * 引用:
    + * DynamicMaker maker = DynamicMaker.getDefault();
      * DynamicClass dyClass = DynamicClass.get("Sample");
    - * dyClass.visitClass(Template.class);
    - * DynamicObject dyObject = DynamicMaker.getDefault().newInstance(dyClass);
    + * dyClass.visitClass(Template.class, maker.getHelper());
    + * DynamicObject dyObject = maker.newInstance(dyClass);
      * dyObject.invokeFunc("method", "str");
      *
      * >>> string0
      * }
      *
    - * 
    - * 注意,动态类型的行为变更是有时效的,这遵循以下规则:
    - * 
      - *
    • 在一个动态对象被创建之前的所有对动态类型的变更,都对新创建的动态对象有效 - *
    • 对于一个已被创建的动态对象,在动态类型中声明新的函数和变量,以及修改动态类型中的变量初始值都不会对已存在的对象产生直接影响 - *
    • 对于一个已被创建的动态对象,若它的某一函数没有通过对象的{@link DynamicObject#setFunc(String, Function, Class[])}方法变更,则对动态类型的此方法进行变更将会改变这个对象的该函数行为 - *
    • 若一个已存在的动态对象的某一函数已经被{@link DynamicObject#setFunc(String, Function, Class[])}方法变更,则其动态类型的行为变更对该对象的此函数行为没有直接影响 - *
    • 对象的变量初始值只和被创建时有关,比如对于变量的样版,变量初始值或者初始值函数只和被创建时,该字段的值或函数有关;对于函数,变量初始值的函数只在被创建时调用并返回值。 - *
    - *
    + * 动态类型声明的变量初始值只在对象被创建时有效,任何时候改变类的变量初始值都不会对已有实例造成影响 * * @see DynamicMaker#newInstance(Class, Class[], DynamicClass, Object...) * @see DynamicMaker#newInstance(DynamicClass) @@ -64,7 +59,6 @@ public class DynamicClass{ private final DynamicClass superDyClass; private final DataPool data; - private final Map> varInit = new HashMap<>(); /**废弃标记,在类型已废弃后,不可再实例化此类型*/ private boolean isObsoleted; @@ -94,8 +88,14 @@ public static DynamicClass get(String name){ return classPool.computeIfAbsent(name, n -> new DynamicClass(n, null)); } + public static DynamicClass visit(String name, Class clazz, DynamicClass superDyClass, JavaHandleHelper helper){ + DynamicClass res = superDyClass == null? get(name): declare(name, superDyClass); + res.visitClass(clazz, helper); + return res; + } + /**创建类型实例,不应从外部调用此方法构造实例*/ - private DynamicClass(String name, DynamicClass superDyClass){ + DynamicClass(String name, DynamicClass superDyClass){ this.name = name; this.superDyClass = superDyClass; this.data = new DataPool(superDyClass == null? null: superDyClass.data); @@ -128,10 +128,6 @@ public DynamicClass superDyClass(){ return superDyClass; } - public Map> getVarInit(){ - return varInit; - } - public DataPool genPool(DataPool basePool){ return new DataPool(data){ @Override @@ -164,20 +160,18 @@ public IVariable[] getVariables(){ *
      *
    • 方法:为动态类型描述实例共有方法,对于同名同参数的方法若重复传入,则旧的方法会被新的覆盖。 *

      方法样版会创建为方法入口,当实例的此方法被调用时实际上调用会被转入这个样版方法,this指针会作为一个参数被传递(可选) - *

      要接收this指针,你需要使用{@link dynamilize.annotation.This}注解标记样版方法的第一个参数且参数应当为final, + *

      要接收this指针,你需要使用{@link dynamilize.runtimeannos.This}注解标记样版方法的第一个参数且参数应当为final, * 被标记为this指针的参数类型必须为可分配类型(动态实例可确保已实现了接口DynamicObject),此参数不会占据参数表匹配位置: *

      例如方法

      {@code sample(@This final DynamicObject self, String str)}
      可以正确的匹配到对象的函数
      {@code sample(String str)}
      - * 若方法带有final修饰符(尽管这可能会让你收到IDE环境的警告),则此方法在类的行为中将变得不可变更,但是对于对象的此函数依然可以正常替换 *

      仅有被替换的方法可以改变实例的行为,对于新增的行为将不会影响已存在的实例的行为,新增行为只会使新产生的实例具有此默认函数 * - *

    • 字段:为动态类型描述默认变量表,并以字段的当前值作为函数的默认初始化数值。 + *
    • 字段:为动态类型描述默认变量表,并以字段在动态对象实例化时存储的值作为该变量的初始数据。 *

      若字段的类型为{@link dynamilize.Initializer.Producer},则会将此函数作为值的工厂,初始化动态实例时以函数生产的数据作为变量默认值。 - *

      如果字段携带final修饰符,那么此变量将被标记为常量,不可变更。 *

      除作为行为样版被访问之外,其他任何时机变量的值变化都不会对类型的行为产生直接影响 *

    * 如果模板里存在不希望被作为样版的字段或者方法,你可以使用{@link Exclude}注解标记此目标以排除。 *

    所有描述动态类行为的方法和变量都必须具有public static修饰符*/ - public void visitClass(Class template){ + public void visitClass(Class template, JavaHandleHelper helper){ checkFinalized(); for(Method method: template.getDeclaredMethods()){ @@ -185,7 +179,7 @@ public void visitClass(Class template){ if(!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers())) continue; - data.setFunction(new JavaMethodEntry(method, data)); + setFunctionWithMethod(helper, method); } for(Field field: template.getDeclaredFields()){ @@ -197,19 +191,19 @@ public void visitClass(Class template){ } } - /**访问一个方法样版,不同于{@link DynamicClass#visitClass(Class)},此方法只访问一个单独的方法并创建其行为样版。 - *

    关于此方法的具体行为,请参阅访问行为样版类型的{@linkplain DynamicClass#visitClass(Class) 方法部分} + /**访问一个方法样版,不同于{@link DynamicClass#visitClass(Class,JavaHandleHelper)},此方法只访问一个单独的方法并创建其行为样版。 + *

    关于此方法的具体行为,请参阅访问行为样版类型的{@linkplain DynamicClass#visitClass(Class,JavaHandleHelper) 方法部分} * * @param method 访问的方法样版*/ - public void visitMethod(Method method){ + public void visitMethod(Method method, JavaHandleHelper helper){ if(!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers())) throw new IllegalHandleException("method template must be public and static"); - data.setFunction(new JavaMethodEntry(method, data)); + setFunctionWithMethod(helper, method); } - /**访问一个字段样版,不同于{@link DynamicClass#visitClass(Class)},此方法只访问一个单独的字段并创建其行为样版。 - *

    关于此方法的具体行为,请参阅访问行为样版类型的{@linkplain DynamicClass#visitClass(Class) 字段部分} + /**访问一个字段样版,不同于{@link DynamicClass#visitClass(Class,JavaHandleHelper)},此方法只访问一个单独的字段并创建其行为样版。 + *

    关于此方法的具体行为,请参阅访问行为样版类型的{@linkplain DynamicClass#visitClass(Class,JavaHandleHelper) 字段部分} * * @param field 访问的字段样版*/ public void visitField(Field field){ @@ -219,7 +213,7 @@ public void visitField(Field field){ setVariableWithField(field); } - /**以lambda模式设置函数,使用匿名函数描述类型行为,对类型行为变更的效果与{@link DynamicClass#visitClass(Class)}的方法部分相同 + /**以lambda模式设置函数,使用匿名函数描述类型行为,对类型行为变更的效果与{@link DynamicClass#visitClass(Class,JavaHandleHelper)}的方法部分相同 * * @param name 函数名称 * @param func 描述函数行为的匿名函数 @@ -248,22 +242,80 @@ public void setFunction(String name, Function.NonRetSuperGetFunc func, Cl }, argTypes); } - /**常量模式设置变量初始值,行为与{@link DynamicClass#visitClass(Class)}字段部分相同 + /**常量模式设置变量初始值,行为与{@link DynamicClass#visitClass(Class,JavaHandleHelper)}字段部分相同 * * @param name 变量名称 - * @param value 常量值 - * @param isConst 此变量是否是一个不可变常量*/ - public void setVariable(String name, Object value, boolean isConst){ - setVariable(name, () -> value, isConst); + * @param value 常量值*/ + public void setVariable(String name, Object value){ + setVariable(name, () -> value); } - /**函数模式设置变量初始化工厂,行为与{@link DynamicClass#visitClass(Class)}字段部分相同 + /**函数模式设置变量初始化工厂,行为与{@link DynamicClass#visitClass(Class,JavaHandleHelper)}字段部分相同 * * @param name 变量名称 - * @param prov 生产变量初始值的工厂函数 - * @param isConst 此变量是否是一个不可变常量*/ - public void setVariable(String name, Initializer.Producer prov, boolean isConst){ - varInit.put(name, new Initializer<>(prov, isConst)); + * @param prov 生产变量初始值的工厂函数*/ + public void setVariable(String name, Initializer.Producer prov){ + data.setVariable(new Variable(name, new Initializer<>(prov))); + } + + private void setFunctionWithMethod(JavaHandleHelper helper, Method method) { + if(!Modifier.isStatic(method.getModifiers())) + throw new IllegalHandleException("cannot assign a non-static method to function"); + + Parameter[] parameters = method.getParameters(); + ArrayList> arg = new ArrayList<>(); + + boolean thisPointer = false, superPointer = false; + for(int i = 0; i < parameters.length; i++){ + Parameter param = parameters[i]; + if(param.getAnnotation(This.class) != null){ + if(thisPointer) + throw new IllegalHandleException("only one self-pointer can exist"); + if(i != 0) + throw new IllegalHandleException("self-pointer must be the first in parameters"); + + thisPointer = true; + } + else if(param.getAnnotation(Super.class) != null){ + if(superPointer) + throw new IllegalHandleException("only one super-pointer can exist"); + if(i != (thisPointer? 1: 0)) + throw new IllegalHandleException("super-pointer must be the first in parameters or the next of self-pointer(if self pointer was existed)"); + + superPointer = true; + } + else arg.add(param.getType()); + } + + boolean thisP = thisPointer; + boolean superP = superPointer; + + Function v = helper.genJavaMethodRef(method).getFunction(); + + int offset = thisP? superP? 2: 1: 0; + FunctionType type = FunctionType.inst(method); + + data.setFunction(name, (self, args) -> { + Object[] argsArray = args.args(); + Object[] realArgArr = ArgumentList.getList(argsArray.length + offset); + + if(thisP) realArgArr[0] = self; + + DataPool.ReadOnlyPool superPool = null; + if(thisP && superP) realArgArr[1] = data.getSuper(self, superPool = self.baseSuperPointer()); + else if(!thisP && superP) realArgArr[0] = data.getSuper(self, superPool = self.baseSuperPointer()); + + if(argsArray.length != 0) System.arraycopy(argsArray, 0, realArgArr, offset, argsArray.length); + + try{ + Object res = v.invoke(self, type, realArgArr); + ArgumentList.recycleList(realArgArr); + if(superPool != null) superPool.recycle(); + return res; + }catch(Throwable e){ + throw new RuntimeException(e); + } + }, arg.toArray(new Class[0])); } @SuppressWarnings({"unchecked"}) @@ -275,8 +327,13 @@ private void setVariableWithField(Field field){ throw new RuntimeException(e); } - varInit.put(field.getName(), new Initializer<>(value instanceof Initializer.Producer? (Initializer.Producer) value: () -> value, - Modifier.isFinal(field.getModifiers()))); + data.setVariable(new Variable(field.getName(), new Initializer<>(value instanceof Initializer.Producer? (Initializer.Producer) value: () -> { + try { + return field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }))); } private void checkFinalized(){ @@ -286,6 +343,6 @@ private void checkFinalized(){ @Override public String toString(){ - return "dyC:" + name; + return "dynamic class:" + name; } } diff --git a/core/src/main/java/dynamilize/DynamicMaker.java b/core/src/main/java/dynamilize/DynamicMaker.java new file mode 100644 index 0000000..9b03db2 --- /dev/null +++ b/core/src/main/java/dynamilize/DynamicMaker.java @@ -0,0 +1,1350 @@ +package dynamilize; + +import dynamilize.classmaker.*; +import dynamilize.classmaker.code.*; +import dynamilize.classmaker.code.annotation.AnnotationType; +import dynamilize.runtimeannos.AspectInterface; +import dynamilize.runtimeannos.FuzzyMatch; + +import java.lang.annotation.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Pattern; + +import static dynamilize.classmaker.ClassInfo.*; +import static dynamilize.classmaker.CodeBlock.stack; +import static dynamilize.runtimeannos.FuzzyMatch.Exact.INSTANCE; + +/** + * 动态类型运作的核心工厂类型,用于将传入的动态类型与委托基类等构造出动态委托类型以及其实例。 + *

    定义了基于{@link ASMGenerator}的默认生成器实现 + *

    若需要特殊的实现,则需要重写/实现此类的方法,主要的方法: + *

      + *
    • {@link DynamicMaker#makeClassInfo(Class, Class[], Class[])}:决定此工厂如何构造委托类的描述信息 + *
    • {@link DynamicMaker#generateClass(Class, Class[], Class[])}:决定如何解析描述信息生成字节码以及如何加载字节码为java类 + *
    + * 实施时必须按照方法的描述给出基本的实现,不应该改变原定的最低上下文请求。 + *
    + * 使用方法可参阅:
    + *   {@link DynamicClass}
    + * DynamicMaker的方法:
    + *   {@link DynamicMaker#newInstance(DynamicClass)}
    + *   {@link DynamicMaker#newInstance(Class[], Class[], DynamicClass)}
    + *   {@link DynamicMaker#newInstance(Class, Class[], DynamicClass, Object...)}
    + *   {@link DynamicMaker#newInstance(Class, Class[], Class[], DynamicClass, Object...)}
    + * 
    + * + * @author EBwilson + * @see DynamicClass + * @see AspectInterface + * @see DynamicObject + * @see DataPool + */ +@SuppressWarnings({"rawtypes", "DuplicatedCode"}) +public abstract class DynamicMaker { + public static final String CALLSUPER = "$super"; + + private static final HashSet INTERNAL_FIELD = new HashSet<>(Arrays.asList( + "$dynamic_type$", + "$datapool$", + "$varValuePool$", + "$superbasepointer$" + )); + + public static final ClassInfo DYNAMIC_CLASS_TYPE = asType(DynamicClass.class); + public static final ClassInfo DYNAMIC_OBJECT_TYPE = asType(DynamicObject.class); + public static final ClassInfo DATA_POOL_TYPE = asType(DataPool.class); + public static final ClassInfo FUNCTION_TYPE_TYPE = asType(FunctionType.class); + public static final MethodInfo TYPE_INST = FUNCTION_TYPE_TYPE.getMethod( + FUNCTION_TYPE_TYPE, + "inst", + CLASS_TYPE.asArray()); + public static final ClassInfo FUNCTION_TYPE = asType(Function.class); + public static final ClassInfo READONLY_POOL_TYPE = asType(DataPool.ReadOnlyPool.class); + public static final ClassInfo INTEGER_CLASS_TYPE = asType(Integer.class); + public static final ClassInfo HASH_MAP_TYPE = asType(HashMap.class); + public static final ClassInfo VAR_TYPE = ClassInfo.asType(IVariable.class); + public static final ClassInfo NOSUCH_METHOD = asType(NoSuchMethodException.class); + public static final ClassInfo ARG_LIST_TYPE = asType(ArgumentList.class); + public static final ClassInfo SUPER_GET_FUNC_TYPE = ClassInfo.asType(Function.SuperGetFunction.class); + public static final ClassInfo FUNC_ENTRY_TYPE = ClassInfo.asType(IFunctionEntry.class); + + public static final IMethod GET_READER = DATA_POOL_TYPE.getMethod(READONLY_POOL_TYPE, "getReader", DYNAMIC_OBJECT_TYPE); + public static final IMethod MAP_GET = HASH_MAP_TYPE.getMethod(OBJECT_TYPE, "get", OBJECT_TYPE); + public static final IMethod MAP_GET_DEF = HASH_MAP_TYPE.getMethod(OBJECT_TYPE, "getOrDefault", OBJECT_TYPE, OBJECT_TYPE); + public static final IMethod VALUE_OF = INTEGER_CLASS_TYPE.getMethod(INTEGER_CLASS_TYPE, "valueOf", INT_TYPE); + public static final IMethod MAP_PUT = HASH_MAP_TYPE.getMethod(OBJECT_TYPE, "put", OBJECT_TYPE, OBJECT_TYPE); + public static final IMethod GET_VAR = DATA_POOL_TYPE.getMethod(VAR_TYPE, "getVariable", STRING_TYPE); + public static final IMethod SET_VAR = DATA_POOL_TYPE.getMethod(ClassInfo.VOID_TYPE, "setVariable", VAR_TYPE); + public static final IMethod SETFUNC = DATA_POOL_TYPE.getMethod(ClassInfo.VOID_TYPE, "setFunction", STRING_TYPE, FUNCTION_TYPE, ClassInfo.CLASS_TYPE.asArray()); + public static final IMethod SETFUNC2 = DATA_POOL_TYPE.getMethod(ClassInfo.VOID_TYPE, "setFunction", STRING_TYPE, SUPER_GET_FUNC_TYPE, ClassInfo.CLASS_TYPE.asArray()); + public static final IMethod SELECT = DATA_POOL_TYPE.getMethod(FUNC_ENTRY_TYPE, "select", STRING_TYPE, FUNCTION_TYPE_TYPE); + public static final IMethod INIT = DATA_POOL_TYPE.getMethod(VOID_TYPE, "init", DYNAMIC_OBJECT_TYPE, OBJECT_TYPE.asArray()); + public static final IMethod INVOKE = DYNAMIC_OBJECT_TYPE.getMethod(OBJECT_TYPE, "invokeFunc", FUNCTION_TYPE_TYPE, STRING_TYPE, OBJECT_TYPE.asArray()); + public static final IMethod GET_LIST = ARG_LIST_TYPE.getMethod(OBJECT_TYPE.asArray(), "getList", INT_TYPE); + public static final IMethod RECYCLE_LIST = ARG_LIST_TYPE.getMethod(VOID_TYPE, "recycleList", OBJECT_TYPE.asArray()); + + private static final Map> OVERRIDES = new HashMap<>(); + private static final Map> FINALS = new HashMap<>(); + private static final Map> ABSTRACTS = new HashMap<>(); + private static final Set> INTERFACE_TEMP = new HashSet<>(); + private static final Stack> INTERFACE_STACK = new Stack<>(); + private static final Class[] EMPTY_CLASSES = new Class[0]; + public static final ILocal[] LOCALS_EMP = new ILocal[0]; + public static final HashSet EMP_SET = new HashSet<>(); + public static final HashMap EMP_MAP = new HashMap<>(); + public static final String ANY = "ANY"; + private final JavaHandleHelper helper; + + private final HashMap, Class> classPool = new HashMap<>(); + private final HashMap, DataPool> classPoolsMap = new HashMap<>(); + private final HashMap, HashMap>> constructors = new HashMap<>(); + + private final HashMap, DataPool> wrapClassPoolMap = new HashMap<>(); + + /** + * 创建一个实例,并传入其要使用的{@linkplain JavaHandleHelper java行为支持器},子类引用此构造器可能直接设置默认的行为支持器而无需外部传入 + */ + protected DynamicMaker(JavaHandleHelper helper) { + this.helper = helper; + } + + public void clearAllCache(){ + classPool.clear(); + classPoolsMap.clear(); + constructors.clear(); + wrapClassPoolMap.clear(); + } + + /**将传入的对象包装为一个{@link WrappedObject}*/ + public DynamicObject wrapInstance(T object){ + Class curr = object.getClass(); + + LinkedList> l = new LinkedList<>(); + while (curr != null) { + if (curr.getAnnotation(DynamicType.class) != null) + throw new IllegalHandleException("Cannot wrap a dynamic type instance"); + + l.addFirst(curr); + curr = curr.getSuperclass(); + } + + DataPool res = null; + for (Class clazz : l){ + DataPool fr = res; + res = wrapClassPoolMap.computeIfAbsent(clazz, c -> { + DataPool r = new DataPool(fr); + + for (Method method : clazz.getDeclaredMethods()) { + if (Modifier.isStatic(method.getModifiers())) continue; + + r.setFunction(helper.genJavaMethodRef(method)); + } + + for (Field field : clazz.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) continue; + + r.setVariable(helper.genJavaVariableRef(field)); + } + + return r; + }); + } + + return new WrappedObject<>(object, res); + } + + /** + * 构造一个派生自{@link Object}的全委托动态实例 + * + * @see DynamicMaker#newInstance(Class, Class[], Class[], DynamicClass, Object...) + */ + public DynamicObject newInstance(DynamicClass dynamicClass) { + return newInstance(Object.class, dynamicClass); + } + + /** + * 从给出的参数列表构造一个全委托动态实例 + * + * @see DynamicMaker#newInstance(Class, Class[], Class[], DynamicClass, Object...) + */ + public DynamicObject newInstance(Class[] interfaces, DynamicClass dynamicClass) { + return newInstance(Object.class, interfaces, (Class[]) null, dynamicClass); + } + + /** + * 用给出的接口列表构造动态实例 + *

    给出的{@linkplain AspectInterface 切面接口}表会决定这个实例被动态化的行为,若为null(非空数组,空数组表明不执行委托)则为全委托。 + * + * @see DynamicMaker#newInstance(Class, Class[], Class[], DynamicClass, Object...) + */ + public DynamicObject newInstance(Class[] interfaces, Class[] aspects, DynamicClass dynamicClass) { + return newInstance(Object.class, interfaces, aspects, dynamicClass); + } + + /** + * 用给出的构造函数参数构造全委托动态类的实例,参数表必须可以在委托的java类型中存在匹配的可用构造器。实例无额外接口,类型委托由参数确定 + * + * @see DynamicMaker#newInstance(Class, Class[], Class[], DynamicClass, Object...) + */ + public DynamicObject newInstance(Class base, DynamicClass dynamicClass, Object... args) { + return newInstance(base, EMPTY_CLASSES, null, dynamicClass, args); + } + + /** + * 用给出的构造函数参数构造动态类的实例,参数表必须可以在委托的java类型中存在匹配的可用构造器。实例无额外接口,类型委托由参数确定 + *

    给出的{@linkplain AspectInterface 切面接口}表会决定这个实例被动态化的行为,若为null(非空数组,空数组表明不执行委托)则为全委托。 + * + * @see DynamicMaker#newInstance(Class, Class[], Class[], DynamicClass, Object...) + */ + public DynamicObject newInstance(Class base, Class[] aspects, DynamicClass dynamicClass, Object... args) { + return newInstance(base, EMPTY_CLASSES, aspects, dynamicClass, args); + } + + /** + * 用给出的构造函数参数构造动态类的实例,参数表必须可以在委托的java类型中存在匹配的可用构造器。实例实现给出的接口列表,类型委托由参数确定 + *

    给出的{@linkplain AspectInterface 切面接口}表会决定这个实例被动态化的行为,若为null(非空数组,空数组表明不执行委托)则为全委托。 + *

    详见{@link AspectInterface} + * + *

    注意,在您使用切面接口限定委托范围时,必须保证所有的抽象方法(来自抽象类委托及实现的接口)都在切面范围内,否则会抛出错误 + * + * @param base 执行委托的java类型,这将决定此实例可分配到的类型 + * @param interfaces 实例实现的接口列表 + * @param aspects 切面接口列表 + * @param dynamicClass 用于实例化的动态类型 + * @param args 构造函数实参 + * @return 构造出的动态实例 + * @throws IllegalHandleException 若构造函数实参无法匹配到相应的构造器,或者有抽象方法未被处理 + * @see AspectInterface + */ + @SuppressWarnings("unchecked") + public DynamicObject newInstance(Class base, Class[] interfaces, Class[] aspects, DynamicClass dynamicClass, Object... args) { + checkBase(base); + + Class clazz = getDynamicBase(base, interfaces, aspects); + try { + List argsLis = new ArrayList<>(Arrays.asList( + dynamicClass, + genPool(clazz, dynamicClass), + classPoolsMap.get(clazz) + )); + argsLis.addAll(Arrays.asList(args)); + + Constructor cstr = null; + for (Constructor constructor : clazz.getDeclaredConstructors()) { + FunctionType t; + if ((t = FunctionType.from(constructor)).match(argsLis.toArray())) { + cstr = constructor; + break; + } + t.recycle(); + } + if (cstr == null) + throw new NoSuchMethodError("no matched constructor found with parameter " + Arrays.toString(args)); + + FunctionType type = FunctionType.inst(cstr.getParameterTypes()); + Constructor c = cstr; + DynamicObject inst = (DynamicObject) constructors.computeIfAbsent(clazz, e -> new HashMap<>()) + .computeIfAbsent(type, t -> { + helper.makeAccess(c); + return c; + }).newInstance(argsLis.toArray()); + + type.recycle(); + + return inst; + } catch (Throwable e) { + throw new IllegalHandleException(e); + } + } + + /** + * 获取此maker的{@linkplain JavaHandleHelper java行为支持器} + */ + public JavaHandleHelper getHelper() { + return helper; + } + + /** + * 检查委托基类的可用性,基类若为final或者基类为接口则抛出异常 + */ + protected void checkBase(Class base) { + if (base.isInterface()) + throw new IllegalHandleException("interface cannot use to derive a dynamic class, please write it in parameter \"interfaces\" to implement this interface"); + + if (Modifier.isFinal(base.getModifiers())) + throw new IllegalHandleException("cannot derive a dynamic class with a final class"); + } + + /** + * 生成动态类型相应的数据池,数据池完成动态类型向上对超类的迭代逐级分配数据池信息,同时对委托产生的动态类型生成所有方法和字段的函数/变量入口并放入数据池 + * + * @param base 动态委托类 + * @param dynamicClass 描述行为的动态类型 + * @return 生成的动态类型数据池 + */ + protected DataPool genPool(Class base, DynamicClass dynamicClass) { + DataPool basePool = classPoolsMap.computeIfAbsent(base, clazz -> { + AtomicBoolean immutable = new AtomicBoolean(); + DataPool res = new DataPool(null) { + @Override + public void setFunction(String name, Function function, Class... argsType) { + if (immutable.get()) + throw new IllegalHandleException("immutable pool"); + + super.setFunction(name, function, argsType); + } + + @Override + public void setVariable(IVariable var) { + if (immutable.get()) + throw new IllegalHandleException("immutable pool"); + + super.setVariable(var); + } + }; + + Class curr = clazz; + while (curr != null) { + if (curr.getAnnotation(DynamicType.class) != null) { + for (Method method : curr.getDeclaredMethods()) { + DynamicMethod callSuper = method.getAnnotation(DynamicMethod.class); + if (callSuper != null) { + String signature = FunctionType.signature(method.getName(), method.getParameterTypes()); + res.setFunction( + method.getName(), + (self, args) -> ((SuperInvoker) self).invokeSuper(signature, args.args()), + method.getParameterTypes() + ); + } + } + curr = curr.getSuperclass(); + continue; + } + + for (Field field : curr.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers()) || isInternalField(field.getName())) continue; + + res.setVariable(helper.genJavaVariableRef(field)); + } + curr = curr.getSuperclass(); + } + + immutable.set(true); + + return res; + }); + + return dynamicClass.genPool(basePool); + } + + private static boolean isInternalField(String name) { + return INTERNAL_FIELD.contains(name); + } + + /** + * 根据委托基类和实现的接口获取生成的动态类型实例的类型,类型会生成并放入池,下一次获取会直接从池中取出该类型 + * + * @param base 委托的基类 + * @param interfaces 需要实现的接口列表 + */ + @SuppressWarnings("unchecked") + protected Class getDynamicBase(Class base, Class[] interfaces, Class[] aspects) { + return (Class) classPool.computeIfAbsent(new ClassImplements<>(base, interfaces, aspects), e -> { + Class c = base; + + while (c != null) { + helper.makeAccess(c); + c = c.getSuperclass(); + } + + return generateClass(handleBaseClass(base), interfaces, aspects); + }); + } + + /** + * 由基类与接口列表建立动态类的打包名称,打包名称具有唯一性(或者足够高的离散性,不应出现频繁的碰撞)和不变性 + * + * @param baseClass 基类 + * @param interfaces 接口列表 + * @return 由基类名称与全部接口名称的哈希值构成的打包名称 + */ + public static String getDynamicName(Class baseClass, Class... interfaces) { + return ensurePackage(baseClass.getName()) + "$dynamic$" + FunctionType.typeNameHash(interfaces); + } + + private static String ensurePackage(String name) { + if (name.startsWith("java.")) { + return name.replaceFirst("java\\.", "lava."); + } + return name; + } + + /** + * 对动态委托产生的类型进行继续委托创建代码的方法,应当将首要处理过程向上传递给超类。 + * 与{@link DynamicMaker#makeClassInfo(Class, Class[], Class[])}的行为相似,但需要将部分行为适应到已经具备委托行为的超类 + */ + @SuppressWarnings({"unchecked"}) + protected ClassInfo makeClassInfoOnDynamic(Class baseClass, Class[] interfaces, Class[] aspects) { + LinkedHashSet> inter = new LinkedHashSet<>(); + inter.add(asType(DynamicObject.class)); + inter.add(asType(SuperInvoker.class)); + + for (Class i : interfaces) { + inter.add(asType(i)); + } + + INTERFACE_STACK.clear(); + INTERFACE_TEMP.clear(); + HashMap> aspectPoints = new HashMap<>(); + if (aspects != null) { + for (Class i : aspects) { + inter.add(asType(i)); + INTERFACE_STACK.push(i); + } + + while (!INTERFACE_STACK.isEmpty()) { + Class i = INTERFACE_STACK.pop(); + if (i.getAnnotation(AspectInterface.class) == null) + throw new IllegalHandleException("aspect interfaces must has AspectInterface annotated, but " + i + " doesn't"); + + if (INTERFACE_TEMP.add(i)) { + for (Class ai : i.getInterfaces()) { + INTERFACE_STACK.push(ai); + } + + for (Method method : i.getMethods()) { + if (method.isDefault()) + throw new IllegalHandleException("aspect interface must be full-abstract, but method " + method + " in " + i + " was implemented"); + + aspectPoints.computeIfAbsent(method.getName(), e -> new HashMap()) + .computeIfAbsent(FunctionType.from(method), e -> { + FuzzyMatch match = method.getAnnotation(FuzzyMatch.class); + java.lang.reflect.Parameter[] parameters = method.getParameters(); + Annotation[] argMatchers = new Annotation[parameters.length]; + + if (match != null) { + for (int l = 0; l < parameters.length; l++) { + Annotation[] alternatives = parameters[l].getAnnotations(); + for (Annotation ann : alternatives) { + if (ann instanceof FuzzyMatch.AnyType || ann instanceof FuzzyMatch.TypeAssignable || ann instanceof FuzzyMatch.Exact) { + if (argMatchers[l] != null) + throw new IllegalHandleException("cannot declare parameter matcher twice on a parameter"); + + argMatchers[l] = ann; + } + else argMatchers[l] = INSTANCE; + } + } + } + + return new FuzzyMatcher(parameters, match, argMatchers); + }); + } + } + } + } else { + aspectPoints.put(ANY, null); + } + + ClassInfo classInfo = new ClassInfo<>( + Modifier.PUBLIC, + ensurePackage(baseClass.getName()) + "$" + FunctionType.typeNameHash(interfaces), + asType(baseClass), + inter.toArray(new ClassInfo[0]) + ); + FieldInfo methodIndex = classInfo.declareField( + Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL, + "$methodIndex$", + HASH_MAP_TYPE, + null + ); + + CodeBlock clinit = classInfo.getClinitBlock(); + ILocal caseIndex = clinit.local(HASH_MAP_TYPE); + clinit.newInstance( + asType(HashMap.class).getConstructor(), + caseIndex + ); + clinit.assign(null, caseIndex, methodIndex); + + HashMap, Integer> callSuperCaseMap = new HashMap<>(); + + // public (*parameters*){ + // super(*parameters*); + // } + for (Constructor cstr : baseClass.getDeclaredConstructors()) { + if ((cstr.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0) continue; + if (Modifier.isFinal(cstr.getModifiers())) continue; + + ArrayList> args = new ArrayList<>(); + for (Class type : cstr.getParameterTypes()) { + args.add(ClassInfo.asType(type)); + } + IClass[] argArr = args.toArray(new IClass[0]); + IMethod constructor = classInfo.superClass().getConstructor(argArr); + + CodeBlock code = classInfo.declareConstructor(Modifier.PUBLIC, Parameter.trans(argArr)); + code.invokeSuper(code.getThis(), constructor, null, code.getParamList().toArray(new ILocal[0])); + } + + OVERRIDES.clear(); + FINALS.clear(); + + //排除已被标识为final的方法,由于继续委托的类型不向上迭代,提前将final方法排除 + Class curr = baseClass; + while (curr != null) { + for (Method method : curr.getDeclaredMethods()) { + filterMethod(method); + } + + curr = curr.getSuperclass(); + } + + OVERRIDES.clear(); + + //仅处理新实现的接口以及切面接口 + ArrayList> lis = new ArrayList<>(Arrays.asList(interfaces)); + if (aspects != null) lis.addAll(Arrays.asList(aspects)); + + for (Class interf : lis) { + ClassInfo typeClass = asType(interf); + for (Method method : interf.getDeclaredMethods()) { + if (!filterMethod(method)) continue; + + String methodName = method.getName(); + ClassInfo returnType = asType(method.getReturnType()); + + MethodInfo superMethod = !Modifier.isAbstract(method.getModifiers()) || (interf.isInterface() && method.isDefault()) ? typeClass.getMethod( + returnType, + methodName, + Arrays.stream(method.getParameterTypes()).map(ClassInfo::asType).toArray(ClassInfo[]::new) + ) : null; + + if (!aspectPoints.containsKey(ANY) && !aspectPoints.getOrDefault(method.getName(), EMP_MAP).containsKey(FunctionType.from(method)) + && aspectPoints.getOrDefault(method.getName(), ((HashMap) EMP_MAP)).values().stream().noneMatch(e -> e.match(method, + INTERFACE_TEMP.contains(interf), + superMethod == null)) + ){ + if (superMethod == null) + throw new IllegalHandleException("method " + method + " in " + interf + " was abstract, but no aspects handle this action"); + + continue; + } + + if (superMethod != null) callSuperCaseMap.put(superMethod, callSuperCaseMap.size()); + + String typeF = methodName + "$" + FunctionType.typeNameHash(method.getParameterTypes()); + FieldInfo funType = classInfo.declareField( + Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL, + typeF, + FUNCTION_TYPE_TYPE, + null + ); + + // private static final FunctionType FUNCTION_TYPE$*name*; + // static { + // ... + // FUNCTION_TYPE$*signature* = FunctionType.as(*paramTypes*); + // methodIndex.put(*signature*, *index*); + // ... + // } + Integer index = callSuperCaseMap.get(superMethod); + genCinit(method, clinit, funType, methodIndex, index == null? -1: index); + + // @DynamicMethod + // public *returnType* *name*(*parameters*){ + // *[return]* this.invokeFunc(FUNCTION_TYPE$*signature* ,"*name*", parameters); + // } + invokeProxy(classInfo, method, methodName, returnType, funType); + } + } + + //switch super + // public Object invokeSuper(String signature, Object... args);{ + // Integer ind = methodIndex.get(signature); + // if(ind == null) return super.invokeSuper(signature, args) + // switch(ind){ + // ... + // case *index*: super.*method*(args[0], args[1],...); break; + // ... + // } + // } + { + CodeBlock code = classInfo.declareMethod( + Modifier.PUBLIC, + "invokeSuper", + OBJECT_TYPE, + new ClassInfo[]{ + ClassInfo.asType(NoSuchMethodException.class) + }, + Parameter.trans( + STRING_TYPE, + OBJECT_TYPE.asArray() + ) + ); + + IMethod superCaller = (IMethod) classInfo.superClass().getMethod(code.owner().returnType(), code.owner().name(), + code.owner().parameters().stream().map(Parameter::getType).toArray(IClass[]::new)); + + Label end = code.label(); + code.assign(null, methodIndex, stack(HASH_MAP_TYPE)); + code.assign(code.getRealParam(0), stack(OBJECT_TYPE)); + code.loadConstant(stack(INT_TYPE), -1); + code.invokeStatic(VALUE_OF, stack(INTEGER_CLASS_TYPE), stack(INT_TYPE)); + + code.invoke( + stack(HASH_MAP_TYPE), + MAP_GET_DEF, + stack(OBJECT_TYPE), + stack(OBJECT_TYPE) + ); + + code.cast(stack(OBJECT_TYPE), stack(INTEGER_CLASS_TYPE)); + code.invoke( + stack(INTEGER_CLASS_TYPE), + INTEGER_CLASS_TYPE.getMethod(INT_TYPE, "intValue"), + stack(INT_TYPE) + ); + + ISwitch iSwitch = code.switchDef(stack(INT_TYPE), end); + + ILocal args = code.getRealParam(1); + makeSwitch(classInfo, callSuperCaseMap, code, iSwitch, args); + + code.markLabel(end); + code.assign(code.getThis(), stack(classInfo)); + + code.invokeSuper(stack(classInfo), superCaller, stack(superCaller.returnType()), code.getParamList().toArray(new ILocal[0])); + code.returnValue(stack(OBJECT_TYPE)); + } + + return classInfo; + } + + /** + * 创建动态实例类型的类型标识,这应当覆盖所有委托目标类的方法和实现的接口中的方法,若超类的某一成员方法不是抽象的,需保留对超类方法的入口, + * 再重写本方法,对超类方法的入口需要有一定的标识以供生成基类数据池的引用函数时使用。 + *

    对于给定的基类和接口列表,生成的动态实例基类的名称是唯一的(或者足够的离散以至于几乎不可能碰撞)。 + *

    关于此方法实现需要完成的工作,请参阅{@link DynamicObject}中的描述 + * + * @param baseClass 委托基类 + * @param interfaces 实现的接口列表 + * @return 完成了所有必要描述的类型标识 + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + protected ClassInfo makeClassInfo(Class baseClass, Class[] interfaces, Class[] aspects) { + if (baseClass.getAnnotation(DynamicType.class) != null) + return makeClassInfoOnDynamic(baseClass, interfaces, aspects); + + LinkedHashSet> inter = new LinkedHashSet<>(); + inter.add(asType(DynamicObject.class)); + inter.add(asType(SuperInvoker.class)); + + for (Class i : interfaces) { + inter.add(asType(i)); + } + + INTERFACE_STACK.clear(); + INTERFACE_TEMP.clear(); + HashMap> aspectPoints = new HashMap<>(); + if (aspects != null) { + for (Class i : aspects) { + inter.add(asType(i)); + INTERFACE_STACK.push(i); + } + + while (!INTERFACE_STACK.isEmpty()) { + Class i = INTERFACE_STACK.pop(); + if (!i.isInterface()) + throw new IllegalHandleException("aspects must be interface, but find class: " + i); + + if (i.getAnnotation(AspectInterface.class) == null) + throw new IllegalHandleException("aspect interfaces must has AspectInterface annotated, but " + i + " doesn't"); + + if (INTERFACE_TEMP.add(i)) { + for (Class ai : i.getInterfaces()) { + INTERFACE_STACK.push(ai); + } + + for (Method method : i.getMethods()) { + if (method.isDefault()) + throw new IllegalHandleException("aspect interface must be full-abstract, but method " + method + " in " + i + " was implemented"); + + aspectPoints.computeIfAbsent(method.getName(), e -> new HashMap()) + .computeIfAbsent(FunctionType.from(method), e -> { + FuzzyMatch match = method.getAnnotation(FuzzyMatch.class); + java.lang.reflect.Parameter[] parameters = method.getParameters(); + Annotation[] argMatchers = new Annotation[parameters.length]; + + if (match != null) { + for (int l = 0; l < parameters.length; l++) { + Annotation[] alternatives = parameters[l].getAnnotations(); + for (Annotation ann : alternatives) { + if (ann instanceof FuzzyMatch.AnyType || ann instanceof FuzzyMatch.TypeAssignable || ann instanceof FuzzyMatch.Exact) { + if (argMatchers[l] != null) + throw new IllegalHandleException("cannot declare parameter matcher twice on a parameter"); + + argMatchers[l] = ann; + } + else argMatchers[l] = INSTANCE; + } + } + } + + return new FuzzyMatcher(parameters, match, argMatchers); + }); + } + } + } + } else { + aspectPoints.put(ANY, null); + } + + ClassInfo classInfo = new ClassInfo<>( + Modifier.PUBLIC, + getDynamicName(baseClass, interfaces), + asType(baseClass), + inter.toArray(new ClassInfo[0]) + ); + + FieldInfo dyType = classInfo.declareField( + Modifier.PRIVATE | Modifier.FINAL, + "$dynamic_type$", + DYNAMIC_CLASS_TYPE, + null + ); + FieldInfo dataPool = classInfo.declareField( + Modifier.PRIVATE | Modifier.FINAL, + "$datapool$", + DATA_POOL_TYPE, + null + ); + FieldInfo> varPool = (FieldInfo) classInfo.declareField( + Modifier.PRIVATE | Modifier.FINAL, + "$varValuePool$", + HASH_MAP_TYPE, + null + ); + FieldInfo basePoolPointer = classInfo.declareField( + Modifier.PRIVATE | Modifier.FINAL, + "$superbasepointer$", + READONLY_POOL_TYPE, + null + ); + FieldInfo methodIndex = classInfo.declareField( + Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL, + "$methodIndex$", + HASH_MAP_TYPE, + null + ); + + CodeBlock clinit = classInfo.getClinitBlock(); + ILocal caseIndex = clinit.local(HASH_MAP_TYPE); + clinit.newInstance( + asType(HashMap.class).getConstructor(), + caseIndex + ); + clinit.assign(null, caseIndex, methodIndex); + + HashMap, Integer> callSuperCaseMap = new HashMap<>(); + + // public (DynamicClass $dyC$, DataPool $datP$, DataPool.ReadOnlyPool $basePool$, *parameters*){ + // this.$dynamic_type$ = $dyC$; + // this.$datapool$ = $datP$; + // this.$varValuePool$ = new HashMap<>(); + // super(*parameters*); + // this.$superbasepointer$ = $datapool$.getReader(this); + // + // this.$datapool$.init(this, *parameters*); + // } + for (Constructor cstr : baseClass.getDeclaredConstructors()) { + if ((cstr.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0) continue; + if (Modifier.isFinal(cstr.getModifiers())) continue; + + List> params = new ArrayList<>(Arrays.asList(Parameter.as( + 0, DynamicClass.class, "$dyc$", + 0, DataPool.class, "$datP$", + 0, DataPool.class, "$basePool$" + ))); + List> superParams = Arrays.asList(Parameter.asParameter(cstr.getParameters())); + params.addAll(superParams); + + IMethod constructor = classInfo.superClass().getConstructor(superParams.stream().map(Parameter::getType).toArray(IClass[]::new)); + + CodeBlock code = classInfo.declareConstructor(Modifier.PUBLIC, params.toArray(new Parameter[0])); + List> l = code.getParamList(); + ILocal self = code.getThis(); + + ILocal dyC = code.getParam(1); + ILocal datP = code.getParam(2); + code.assign(self, dyC, dyType); + code.assign(self, datP, dataPool); + + code.assign(self, stack(classInfo)); + code.newInstance(HASH_MAP_TYPE.getConstructor(), stack(HASH_MAP_TYPE)); + code.assign(stack(classInfo), stack(HASH_MAP_TYPE), varPool); + + code.invokeSuper(self, constructor, null, l.subList(3, l.size()).toArray(LOCALS_EMP)); + + code.assign(self, stack(classInfo)); + code.invoke(code.getParam(3), GET_READER, stack(READONLY_POOL_TYPE), self); + code.assign(stack(classInfo), stack(READONLY_POOL_TYPE), basePoolPointer); + + ILocal argList = code.local(OBJECT_TYPE.asArray()); + code.loadConstant(stack(INT_TYPE), cstr.getParameterCount()); + code.invoke(null, GET_LIST, argList, stack(INT_TYPE)); + if (cstr.getParameterCount() > 0) { + for (int i = 3; i < code.getParamList().size(); i++) { + code.assign(argList, stack(OBJECT_TYPE.asArray())); + code.loadConstant(stack(INT_TYPE), i - 3); + code.arrayPut(stack(OBJECT_TYPE.asArray()), stack(INT_TYPE), code.getRealParam(i)); + } + } + code.invoke(datP, INIT, null, self, argList); + + code.invoke(null, RECYCLE_LIST, null, argList); + } + + OVERRIDES.clear(); + FINALS.clear(); + INTERFACE_TEMP.clear(); + + ArrayList> lis = new ArrayList<>(Arrays.asList(interfaces)); + if (aspects != null) lis.addAll(Arrays.asList(aspects)); + + for (Class ic : lis) { + INTERFACE_STACK.push(ic); + INTERFACE_TEMP.add(ic); + } + + Class curr = baseClass; + while (curr != null || !INTERFACE_STACK.empty()) { + if (curr != null) { + for (Class i : curr.getInterfaces()) { + if (INTERFACE_TEMP.add(i)) INTERFACE_STACK.push(i); + } + } else curr = INTERFACE_STACK.pop(); + + ClassInfo typeClass = asType(curr); + for (Method method : curr.getDeclaredMethods()) { + if (!filterMethod(method)) continue; + + String methodName = method.getName(); + ClassInfo returnType = asType(method.getReturnType()); + + MethodInfo superMethod = !Modifier.isAbstract(method.getModifiers()) || (curr.isInterface() && method.isDefault()) ? typeClass.getMethod( + returnType, + methodName, + Arrays.stream(method.getParameterTypes()).map(ClassInfo::asType).toArray(ClassInfo[]::new) + ) : null; + + Class finalCurr = curr; + if (!aspectPoints.containsKey(ANY) && !aspectPoints.getOrDefault(method.getName(), EMP_MAP).containsKey(FunctionType.from(method)) + && aspectPoints.getOrDefault(method.getName(), ((HashMap) EMP_MAP)).values().stream().noneMatch(e -> e.match(method, + finalCurr == baseClass || INTERFACE_TEMP.contains(finalCurr), + superMethod == null + )) + ) { + if (superMethod == null) + throw new IllegalHandleException("method " + method + " in " + curr + " was abstract, but no aspects handle this action"); + + continue; + } + + if (superMethod != null) callSuperCaseMap.put(superMethod, callSuperCaseMap.size()); + + String typeF = methodName + "$" + FunctionType.typeNameHash(method.getParameterTypes()); + FieldInfo funType = classInfo.declareField( + Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL, + typeF, + FUNCTION_TYPE_TYPE, + null + ); + + // private static final FunctionType FUNCTION_TYPE$*name*; + // static { + // ... + // FUNCTION_TYPE$*signature* = FunctionType.as(*paramTypes*); + // methodIndex.put(*signature*, *index*); + // ... + // } + Integer index = callSuperCaseMap.get(superMethod); + genCinit(method, clinit, funType, methodIndex, index == null? -1: index); + + // @DynamicMethod + // public *returnType* *name*(*parameters*){ + // *[return]* this.invokeFunc(FUNCTION_TYPE$*signature* ,"*name*", parameters); + // } + invokeProxy(classInfo, method, methodName, returnType, funType); + } + + if (!curr.isInterface()) { + curr = curr.getSuperclass(); + } else curr = null; + } + + // public Object invokeSuper(String signature, Object... args);{ + // return switch(methodIndex.get(signature)){ + // ... + // case *index* -> super.*method*(args[0], args[1],...); + // ... + // } + // } + genCallSuper(classInfo, methodIndex, callSuperCaseMap); + + // public DataPool.ReadOnlyPool baseSuperPool(){ + // return this.$superbasepointer$; + // } + { + CodeBlock code = classInfo.declareMethod( + Modifier.PUBLIC, + "baseSuperPointer", + READONLY_POOL_TYPE + ); + code.assign(code.getThis(), basePoolPointer, stack(READONLY_POOL_TYPE)); + code.returnValue(stack(READONLY_POOL_TYPE)); + } + + // public DynamicClass getDyClass(){ + // return this.$dynamic_type$; + // } + { + CodeBlock code = classInfo.declareMethod( + Modifier.PUBLIC, + "getDyClass", + DYNAMIC_CLASS_TYPE + ); + code.assign(code.getThis(), dyType, stack(DYNAMIC_CLASS_TYPE)); + code.returnValue(stack(DYNAMIC_CLASS_TYPE)); + } + + // public T varValueGet(String name){ + // return this.$varValuePool$.get(name); + // } + { + CodeBlock code = classInfo.declareMethod( + Modifier.PUBLIC, + "varValueGet", + OBJECT_TYPE, + Parameter.trans(STRING_TYPE) + ); + code.assign(code.getThis(), varPool, stack(HASH_MAP_TYPE)); + code.invoke(stack(HASH_MAP_TYPE), MAP_GET, stack(OBJECT_TYPE), code.getRealParam(0)); + code.returnValue(stack(OBJECT_TYPE)); + } + + // public varValueSet(String name, Object value){ + // return this.$varValuePool$.put(name, value); + // } + { + CodeBlock code = classInfo.declareMethod( + Modifier.PUBLIC, + "varValueSet", + VOID_TYPE, + Parameter.trans( + STRING_TYPE, + OBJECT_TYPE + ) + ); + code.assign(code.getThis(), varPool, stack(HASH_MAP_TYPE)); + code.invoke(stack(HASH_MAP_TYPE), MAP_PUT, null, code.getRealParam(0), code.getRealParam(1)); + } + + // public IVariable getVariable(String name){ + // return this.$datapool$.getVariable(name); + // } + { + CodeBlock code = classInfo.declareMethod( + Modifier.PUBLIC, + "getVariable", + VAR_TYPE, + Parameter.as(0, STRING_TYPE, "name") + ); + code.assign(code.getThis(), dataPool, stack(DATA_POOL_TYPE)); + code.invoke(stack(DATA_POOL_TYPE), GET_VAR, stack(VAR_TYPE), code.getParam(1)); + code.returnValue(stack(VAR_TYPE)); + } + + // public void setVariable(IVariable var){ + // this.$datapool$.setVariable(var); + // } + { + CodeBlock code = classInfo.declareMethod( + Modifier.PUBLIC, + "setVariable", + VOID_TYPE, + Parameter.as(0, VAR_TYPE, "var") + ); + code.assign(code.getThis(), dataPool, stack(DATA_POOL_TYPE)); + code.invoke(stack(DATA_POOL_TYPE), SET_VAR, null, code.getParam(1)); + code.returnVoid(); + } + + // public IFunctionEntry getFunc(String name, FunctionType type){ + // return this.$datapool$.select(name, type); + // } + { + CodeBlock code = classInfo.declareMethod( + Modifier.PUBLIC, + "getFunc", + FUNC_ENTRY_TYPE, + Parameter.as( + 0, STRING_TYPE, "name", + 0, FUNCTION_TYPE_TYPE, "type" + ) + ); + code.assign(code.getThis(), dataPool, stack(DATA_POOL_TYPE)); + + code.invoke(stack(DATA_POOL_TYPE), SELECT, stack(FUNC_ENTRY_TYPE), code.getParam(1), code.getParam(2)); + code.returnValue(stack(FUNC_ENTRY_TYPE)); + } + + // public void setFunc(String name, Function func, Class... argTypes){ + // this.$datapool$.set(name, func, argTypes); + // } + { + CodeBlock code = classInfo.declareMethod( + Modifier.PUBLIC, + "setFunc", + VOID_TYPE, + Parameter.as( + 0, STRING_TYPE, "name", + 0, FUNCTION_TYPE, "func", + 0, CLASS_TYPE.asArray(), "argTypes" + ) + ); + + code.assign(code.getThis(), dataPool, stack(DATA_POOL_TYPE)); + code.invoke(stack(DATA_POOL_TYPE), SETFUNC, null, code.getParam(1), code.getParam(2), code.getParam(3)); + } + + // public void setFunc(String name, Function func, Class... argTypes){ + // this.$datapool$.set(name, func, argTypes); + // } + { + CodeBlock code = classInfo.declareMethod( + Modifier.PUBLIC, + "setFunc", + VOID_TYPE, + Parameter.as( + 0, STRING_TYPE, "name", + 0, SUPER_GET_FUNC_TYPE, "func", + 0, CLASS_TYPE.asArray(), "argTypes" + ) + ); + + ILocal pool = code.local(DATA_POOL_TYPE); + code.assign(code.getThis(), dataPool, pool); + + code.invoke(pool, SETFUNC2, null, code.getParam(1), code.getParam(2), code.getParam(3)); + } + + AnnotationType dycAnno = AnnotationType.asAnnotationType(DynamicType.class); + dycAnno.annotateTo(classInfo, null); + + return classInfo; + } + + @SuppressWarnings("unchecked") + private void genCallSuper(ClassInfo classInfo, FieldInfo methodIndex, HashMap, Integer> callSuperCaseMap) { + CodeBlock code = classInfo.declareMethod( + Modifier.PUBLIC, + "invokeSuper", + OBJECT_TYPE, + new ClassInfo[]{ + ClassInfo.asType(NoSuchMethodException.class) + }, + Parameter.trans( + STRING_TYPE, + OBJECT_TYPE.asArray() + ) + ); + + Label end = code.label(); + code.assign(null, methodIndex, stack(HASH_MAP_TYPE)); + code.assign(code.getRealParam(0), stack(OBJECT_TYPE)); + code.loadConstant(stack(INT_TYPE), -1); + code.invokeStatic(VALUE_OF, stack(INTEGER_CLASS_TYPE), stack(INT_TYPE)); + + code.invoke( + stack(HASH_MAP_TYPE), + MAP_GET_DEF, + stack(OBJECT_TYPE), + stack(OBJECT_TYPE) + ); + + code.cast(stack(OBJECT_TYPE), stack(INTEGER_CLASS_TYPE)); + code.invoke( + stack(INTEGER_CLASS_TYPE), + INTEGER_CLASS_TYPE.getMethod(INT_TYPE, "intValue"), + stack(INT_TYPE) + ); + + ISwitch iSwitch = code.switchDef(stack(INT_TYPE), end); + + ILocal args = code.getRealParam(1); + makeSwitch(classInfo, callSuperCaseMap, code, iSwitch, args); + + code.markLabel(end); + + ILocal msg = code.local(STRING_TYPE); + code.loadConstant(stack(STRING_TYPE), "no such method in baseclass signature with "); + code.operate(stack(STRING_TYPE), IOperate.OPCode.ADD, code.getRealParam(0), msg); + code.newInstance( + NOSUCH_METHOD.getConstructor(STRING_TYPE), + stack(NOSUCH_METHOD), + msg + ); + code.thr(stack(NOSUCH_METHOD)); + } + + private static void genCinit(Method method, CodeBlock clinit, FieldInfo funType, FieldInfo methodIndex, int callSuperIndex) { + String signature = FunctionType.signature(method); + clinit.loadConstant(stack(INT_TYPE), method.getParameterCount()); + clinit.newArray( + CLASS_TYPE, + stack(CLASS_TYPE.asArray()), + stack(INT_TYPE) + ); + + Class[] paramTypes = method.getParameterTypes(); + for (int i = 0; i < paramTypes.length; i++) { + clinit.assign(stack(CLASS_TYPE.asArray()), stack(CLASS_TYPE.asArray())); + clinit.loadConstant(stack(INT_TYPE), i); + clinit.loadConstant(stack(CLASS_TYPE), paramTypes[i]); + + clinit.arrayPut(stack(CLASS_TYPE.asArray()), stack(INT_TYPE), stack(CLASS_TYPE)); + } + + clinit.invoke( + null, + TYPE_INST, + stack(FUNCTION_TYPE_TYPE), + stack(CLASS_TYPE.asArray()) + ); + + clinit.assign(null, stack(FUNCTION_TYPE_TYPE), funType); + + if (callSuperIndex == -1) return; + + clinit.assign(null, methodIndex, stack(HASH_MAP_TYPE)); + clinit.loadConstant(stack(STRING_TYPE), signature); + + clinit.loadConstant(stack(INT_TYPE), callSuperIndex); + clinit.invoke( + null, + VALUE_OF, + stack(INTEGER_CLASS_TYPE), + stack(INT_TYPE) + ); + + clinit.invoke( + stack(HASH_MAP_TYPE), + MAP_PUT, + null, + stack(OBJECT_TYPE) + ); + } + + @SuppressWarnings("unchecked") + private static void invokeProxy(ClassInfo classInfo, Method method, String methodName, ClassInfo returnType, FieldInfo funType) { + CodeBlock code = classInfo.declareMethod( + Modifier.PUBLIC, + methodName, + returnType, + Parameter.asParameter(method.getParameters()) + ); + AnnotationDef anno = new AnnotationDef<>( + ClassInfo.asType(DynamicMethod.class).asAnnotation(EMP_MAP), + code.owner(), + EMP_MAP + ); + code.owner().addAnnotation(anno); + + code.loadConstant(stack(INT_TYPE), method.getParameterCount()); + code.invoke(null, GET_LIST, stack(OBJECT_TYPE.asArray()), stack(INT_TYPE)); + + if (method.getParameterCount() > 0) { + for (int i = 0; i < code.getParamList().size(); i++) { + code.assign(stack(OBJECT_TYPE), stack(OBJECT_TYPE.asArray())); + code.loadConstant(stack(INT_TYPE), i); + code.arrayPut(stack(OBJECT_TYPE.asArray()), stack(INT_TYPE), code.getRealParam(i)); + } + } + + ILocal argList = code.local(OBJECT_TYPE.asArray()); + code.assign(stack(OBJECT_TYPE.asArray()), argList); + + if (returnType != VOID_TYPE) { + code.assign(code.getThis(), stack(classInfo)); + code.assign(null, funType, stack(FUNCTION_TYPE_TYPE)); + code.loadConstant(stack(STRING_TYPE), method.getName()); + code.assign(argList, stack(OBJECT_TYPE.asArray())); + + code.invoke(stack(classInfo), INVOKE, stack(OBJECT_TYPE), stack(OBJECT_TYPE)); + code.invoke(null, RECYCLE_LIST, null, argList); + code.cast(stack(OBJECT_TYPE), stack(returnType)); + code.returnValue(stack((IClass) returnType)); + } else { + code.assign(code.getThis(), stack(classInfo)); + code.assign(null, funType, stack(FUNCTION_TYPE_TYPE)); + code.loadConstant(stack(STRING_TYPE), method.getName()); + code.assign(argList, stack(OBJECT_TYPE.asArray())); + + code.invoke(stack(classInfo), INVOKE, null, stack(OBJECT_TYPE)); + code.invoke(null, RECYCLE_LIST, null, argList); + } + } + + private static boolean filterMethod(Method method) { + //对于已经被声明为final的方法将被添加到排除列表 + if (Modifier.isFinal(method.getModifiers())) { + DynamicMaker.FINALS.computeIfAbsent(method.getName(), e -> new HashSet<>()).add(FunctionType.from(method)); + return false; + } + + // 如果方法是静态的,或者方法不对子类可见则不重写此方法 + if (Modifier.isStatic(method.getModifiers())) return false; + if ((method.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0) return false; + + return !DynamicMaker.FINALS.computeIfAbsent(method.getName(), e -> new HashSet<>()).contains(FunctionType.from(method)) + && DynamicMaker.OVERRIDES.computeIfAbsent(method.getName(), e -> new HashSet<>()).add(FunctionType.from(method)); + } + + protected void makeSwitch(IClass owner, HashMap, Integer> callSuperCaseMap, CodeBlock code, ISwitch iSwitch, ILocal args) { + for (Map.Entry, Integer> entry : callSuperCaseMap.entrySet()) { + Label l = code.label(); + code.markLabel(l); + + iSwitch.addCase(entry.getValue(), l); + + IMethod superMethod = entry.getKey(); + if (Modifier.isInterface(superMethod.owner().modifiers())) { + IClass c = code.owner().owner().superClass(); + boolean found = false; + t: + while (c != null && c != OBJECT_TYPE) { + for (IClass interf : c.interfaces()) { + if (interf == superMethod.owner()) { + found = true; + c = interf; + break t; + } + } + + c = c.superClass(); + } + + if (found) { + c.getMethod( + superMethod.returnType(), + superMethod.name(), + superMethod.parameters().stream().map(Parameter::getType).toArray(IClass[]::new) + ); + } + } + + code.assign(code.getThis(), stack(owner)); + for (int in = 0; in < superMethod.parameters().size(); in++) { + code.assign(args, stack(OBJECT_TYPE.asArray())); + code.loadConstant(stack(INT_TYPE), in); + code.arrayGet(stack(OBJECT_TYPE.asArray()), stack(INT_TYPE), stack(OBJECT_TYPE)); + code.cast(stack(OBJECT_TYPE), stack(superMethod.parameters().get(in).getType())); + } + + if (superMethod.returnType() != VOID_TYPE) { + code.invokeSuper(stack(owner), superMethod, stack(OBJECT_TYPE), stack(OBJECT_TYPE)); + code.returnValue(stack(OBJECT_TYPE)); + } else { + code.invokeSuper(stack(owner), superMethod, null, stack(OBJECT_TYPE)); + code.loadConstant(stack(OBJECT_TYPE), null); + code.returnValue(stack(OBJECT_TYPE)); + } + } + } + + /** + * 对委托基类的映射处理,通常来说直接返回类型本身,对此方法的覆写在需要进行包私有访问时才需要 + *
    具体来说,该映射在委托包私有方法时需要创建一串将包私有方法访问修饰符提升的类层次结构,分别去访问基类的各个超类所在包的保护域 + *
    默认的实现是非包私有委托的 + * + * @param baseClass 进行动态委托的基类 + * @return 对基类进行映射处理的类,默认情况下就是类本身 + */ + protected Class handleBaseClass(Class baseClass) { + return baseClass; + } + + /** + * 生成委托自基类并实现了给出的接口列表的类型,而类的行为描述请参考{@link DynamicMaker#makeClassInfo(Class, Class[], Class[])},类型描述会在此方法产出。 + *

    该方法需要做的事通常是将makeClassInfo获得的类型标识进行生成并加载其表示的java类型 + * + * @param baseClass 委托基类 + * @param interfaces 实现的接口列表 + * @return 对全方法进行动态委托的类型 + */ + protected abstract Class generateClass(Class baseClass, Class[] interfaces, Class[] aspects); + + protected static class FuzzyMatcher{ + java.lang.reflect.Parameter[] parameters; + FuzzyMatch matcher; + Annotation[] argsMatcher; + + public FuzzyMatcher(java.lang.reflect.Parameter[] parameters, FuzzyMatch match, Annotation[] argMatchers) { + this.parameters = parameters; + this.matcher = match; + this.argsMatcher = argMatchers; + } + + public boolean match(Method method, boolean inBase, boolean isAbstract) { + if (matcher == null) return false; + + if (!((matcher.inBaseClass() && inBase) || (matcher.inSuperClass() && !inBase))) return false; + if (matcher.abstractOnly() && !isAbstract) return false; + if (matcher.anySameName()) return true; + + java.lang.reflect.Parameter[] params = method.getParameters(); + if (argsMatcher.length != params.length) return false; + for (int i = 0; i < params.length; i++) { + if (argsMatcher[i] instanceof FuzzyMatch.AnyType a){ + if (!Pattern.matches(a.value(), method.getName())) return false; + } + else if (argsMatcher[i] instanceof FuzzyMatch.TypeAssignable a){ + if (!Pattern.matches(a.value(), method.getName())) return false; + if (!parameters[i].getType().isAssignableFrom(params[i].getType())) return false; + } + else if (argsMatcher[i] instanceof FuzzyMatch.Exact e){ + if (parameters[i].getType() != params[i].getType()) return false; + } + else throw new IllegalHandleException("what?"); + } + + return true; + } + } + + /** + * 动态委托类型标识,由此工厂生成的动态委托类型都会具有此注解标识 + */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface DynamicType { + } + + /** + * 动态方法标识,所有被动态委托的方法会携带此注解,用于标识动态类行为的基础入口点 + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @interface DynamicMethod { + } + + public interface SuperInvoker { + Object invokeSuper(String signature, Object... args); + } +} diff --git a/src/main/java/dynamilize/DynamicObject.java b/core/src/main/java/dynamilize/DynamicObject.java similarity index 58% rename from src/main/java/dynamilize/DynamicObject.java rename to core/src/main/java/dynamilize/DynamicObject.java index b83623b..9a52c68 100644 --- a/src/main/java/dynamilize/DynamicObject.java +++ b/core/src/main/java/dynamilize/DynamicObject.java @@ -1,7 +1,7 @@ package dynamilize; /**所有动态对象依赖的接口,描述了动态对象具有的基本行为,关于接口的实现应当由生成器生成。 - *

    实现此接口通常不应该从外部进行,而应当通过{@link DynamicMaker#makeClassInfo(Class, Class[])}生成,对于生成器生成的实现类应当满足下列行为: + *

    实现此接口通常不应该从外部进行,而应当通过{@link DynamicMaker#makeClassInfo(Class, Class[], Class[])}生成,对于生成器生成的实现类应当满足下列行为: *