Skip to content

Java Library Adapter ​

Java 应用声明投影见 JavaStubPlanner,类型化集合字段与方法的真实跨语言契约见 PlainObjectJavaBridgeTest。

目标 ​

Norm Module 可以暂时使用一个 Java JAR 及其运行依赖实现公开 Norm API,也可以在后续版本移除该实现并改写为纯 Norm。模块名、导出边界、依赖方式和发布坐标不暴露实现来源。

text
Norm API → optional JAR binding → Java dependency graph
Norm API → Norm Core

两条路径生成同一种 Module artifact。消费者只依赖 Module。

文件身份 ​

所有 .norm 文件都是 Norm 源码。顶层 Module module() 决定模块声明;独立的 module.norm 是多文件模块的惯用布局,单文件应用可以让它与业务声明共存。模块声明随辅助入口经过同一套解析、类型检查、Core 降级和执行流程;JAR 声明只是该函数返回的普通 Norm 对象。生成的适配代码同样只使用公开的 Norm 语法、类型和函数。编译器携带不可由源码伪造的生成来源集合,只向这些文档开放冻结的 Binding intrinsic。内容区分负责语言与模块语义,不承担宿主权限认证。

模块边界 ​

Module module() 是模块声明、依赖与发布配置的唯一写入口。工作目录不是依赖或发布单位,不定义 Project manifest。

一个 Module 最多包含一个可选的 jarBinding,根可以是 JAR 或 JDK 模块。jarBinding.api 可选择该根依赖图中的公开 Java 类型;已由依赖 Norm Module 公开的类型复用其唯一声明。支持元数据入口见 JavaApiScanInput。

jarType(name: "jakarta.persistence.EntityManager", alias: "internal.Store", members: []) 显式指定模块内的声明路径。省略 alias 时使用 Java 短类名,嵌套类名连写。exports 只选择公开源码文件,不参与绑定命名;省略或传入空列表时,绑定保留在模块内部。命名与可见性的规范入口分别是 BindingPlanner 和模块系统。

普通 Norm 源码、生成声明和纯 Norm 实现共享同一个导出表。模块可将 Java 适配声明放入 internal package,只导出调用它们的 Norm API。

声明模型 ​

Java 注解适配将 jakarta.inject.Inject 的字段初始化责任投影为标准库 ManagedField 契约。前端按解析后的注解契约决定构造参数,不依赖第三方注解短名称;已有对象的注入仍由 DI 容器执行。

直接带有 io.micronaut.aop.Introduction 元注解的 Java 注解投影为 ManagedImplementation 契约,适配后的注解保留其原有目标及保留策略。该契约标识类型的外部实现提供者;契约识别与方法实现连接是不同阶段,未连接的方法不能降级为空实现。入口见 JavaAnnotationContract。

norm
Module module() {
  return module(
    name: "commons.lang",
    version: 1,
    exports: ["StringUtils"],
    dependencies: [],
    binding: jarBinding(
      target: mavenJar(
        group: "org.apache.commons",
        artifact: "commons-lang3",
        version: "3.20.0",
        resolution: sha256("...")
      ),
      api: [
        jarType(
          name: "StringUtils",
          members: ["isBlank", "isNotBlank", "reverse", "split", "trim"]
        )
      ]
    )
  )
}

声明与默认值的唯一实现见 bootstrap/module.norm。源码和 NAR 的内部绑定隔离验收见 ModulePackagerTest。

本地 JAR 使用 localJar(path, integrity)。norm resolve 负责解析并原子填入缺失摘要;已声明摘要不匹配时直接失败,需要更新依赖的作者先修改声明。norm run、norm package 和 CI 只验证已声明内容,不接受依赖漂移。不使用独立锁文件。

内置的 java.base 模块 唯一声明 JDK java.base 引用类型。标准库及其他绑定模块通过普通模块读取边与 Java 调用复用这些声明。最小标量投影由 JavaPlatformTypes 定义,其余 Java 类型保留名义身份;其他绑定模块不能再次声明这些 JDK 类型的归属。 日历与精确小数绑定的真实执行验证见 JavaBaseValueIntegrationTest。

JDK 根使用 jdkModule(name, resolution)。JdkModuleArchive 定义锁定的元数据快照,快照不进入运行时类路径。JavaApiScanInput 显式携带根与支持元数据;已发布绑定的兼容性契约由 PublishedJarBinding 定义。

第一版使用 ​

标量与 Java 接口的关系由 JavaScalarConformances 从 JDK 签名派生,并进入现有内置类型接口实现通道。真实执行覆盖见 JavaScalarInterfaceIntegrationTest。

本地 JAR 放在 Module 目录内,例如 lib/tools.jar。jarType 中的名字对应根 JAR 中唯一的公开类,members 选择构造函数、方法或字段名称,并包含该名称的稳定公开重载;构造函数使用 new。签名涉及的根 JAR 类型自动形成最小声明闭包。编译器为选中的 API 生成普通 Norm 声明,例如 StringUtils.reverse 生成 stringUtilsReverse。

norm
Module module() {
  return module(
    name: "example.tools",
    version: 1,
    binding: jarBinding(
      target: localJar(path: "lib/tools.jar"),
      api: [jarType(name: "Tools", members: ["new", "convert"])]
    )
  )
}

首次解析将摘要写回原声明:

text
norm resolve path/to/example/tools
norm run path/to/example/tools/Main.norm

Maven 根制品使用上方声明模型。另一个 Module 只声明普通 Norm 依赖并导入生成函数:

norm
import commons.lang.stringUtilsReverse

Void main() {
  printLine(stringUtilsReverse("Norm") ?? "")
}

发布前先执行 norm resolve,再生成可直接作为 Maven 仓库目录使用的产物:

text
norm package path/to/commons/lang --output path/to/repository

仓库坐标与制品名由 Module 身份派生,规则见包管理器。Maven 和 Gradle 都可消费生成的 NAR 与 POM。另一个 Norm 项目的 dependency(repository, name, version?) 使用同一坐标解析,无需 POM、Gradle 文件或锁文件。

可运行目录见 Apache Commons Lang 示例。

内容身份 ​

路径和 Maven 坐标只负责定位。实现使用以下派生身份:

  • JarContentId:完整 JAR 字节;
  • JavaApiId:规范化的可绑定公开 API;
  • ResolvedJarGraphId:制品内容与依赖边;
  • BindingArtifactId:依赖图、映射策略和 Binding ABI;
  • ModuleApiId:对外 Norm 声明;
  • ModuleImplementationId:Norm Core 与可选 Binding 实现。

相同内容共享扫描和 Binding 缓存。JAR 实现变化必须重新链接;公开 Norm API 不变时,消费者源码保持有效。

发布模型 ​

norm package 生成 NAR,以及由 module.norm 派生的 POM。归档版本以 ModuleArchiveFormat 为准。所有 Module 都保存已求值的 manifest、完整生产源码与资源;exports 只定义公开 API,不选择制品文件。Java Binding Module 同时保存 API 报告和 PublishedJarBinding 定义的稳定绑定产物。消费端验证绑定 ABI、制品摘要、模块描述、固定依赖图、公开类型归属和归档源码,直接链接发布产物。应用专属的回调类、注解处理与可达性裁剪仍属于应用构建。Java 图制品由 ModulePackager 保存;消费者不执行远程 module.norm;纯 Norm 与 Java 适配使用同一包模型。归档与跨模块验收见 ModulePackagerTest 和 CrossModuleJarBindingTest。

绑定 ABI 同时约束绑定数据结构、序列化格式和运行时约定。改变这些契约必须更新 PublishedJarBinding.ABI 并重新发布适配包;编译器其他实现变化不要求重新发布绑定。

所有发布模块同时携带 CompiledModule 定义的 Core 产物。Core 载荷的摘要与 ABI 由 manifest 校验,导入规则与编译工作量验收见编译器架构。改变该载荷的数据结构或序列化约定必须更新 CompiledModule.ABI;Core 与语言语义版本使用现有身份契约。

POM 声明根 Java 制品及其普通 Maven 依赖。依赖方解析 Norm Module 时同时获得所需 Java 图。发布本地 JAR 时必须为它声明可解析的发布坐标;同一次发布产生 Java artifact 和依赖它的 Norm artifact。

现有版本的 Binding artifact 不会被纯 Norm 版本原地替换。实现迁移通过同一 Module 的新版本发布。

强制约束 ​

  • Module 不具有 Java 专用种类;
  • 每个 Module 最多绑定一个 Java 根:JAR 或 JDK 模块;
  • 不生成传递依赖的公开可调用 API;
  • 不提供任意宿主类查找、反射调用或无类型宿主对象;
  • Java 对象在 Norm 中是具有确定声明身份的不透明引用;
  • 多个 Module 显式绑定同一 Java artifact 的不同版本时必须失败;传递版本由统一类路径解析器选择,显式根制品优先,其余使用 Maven 版本顺序,并只保留选中版本的依赖闭包;
  • Java artifact 的固定坐标出现不同内容,以及 API 指纹不匹配时必须失败;
  • 公开适配面中的未支持签名产生确定诊断;
  • 远程 artifact 携带已编译模块描述,消费者不执行发布者的配置源码;
  • Maven POM、摘要清单和生成声明均为派生产物;Gradle 直接消费同一 Maven 元数据。

当前绑定面 ​

默认模块根来自显式 module-info(含多版本 JAR);仅声明 Automatic-Module-Name 的框架 JAR 留在 classpath。显式模块的 requires 与 Native 的 ForceOnModulePath 仍可选择自动模块。 Java 模块识别与根 JAR 的 JPMS 依赖选择由 JavaModulePath 统一提供,JVM、准备制品和 Native 共享模块根身份。JVM 模块资源与类加载沿用同一应用执行域,资源流由应用加载器在退出时完成关闭。实现入口为 JvmJarBindingRuntime、PreparedApplication 与 NativeApplicationExecutable,模块选择、身份、资源读取和文件释放验证见 JavaModuleLoadingTest。编译器依赖、服务资源与 JDK 平台边界的隔离验证见 ApplicationClassLoaderIsolationTest。依赖的 Native 可用性仍需实际应用验收。

当前实现覆盖静态与实例方法、构造函数、静态与实例字段、基本与盒装标量、字符串、Number、不透明对象、Object 上界泛型和 JAR 内泛型继承投影。具体组件类型的 Java 数组映射为生成的 identity wrapper,提供固定长度、读取、原位更新和构造能力;基本类型数组与盒装类型数组保持不同名义类型,不映射为具有值语义的 Norm Array<T>。Java T[] 与 T... 使用按擦除组件区分的 reified 数组,可变参数调用固定为单个数组参数。

Java 对象保持其名义类型与宿主身份;调用抛出的异常及资源生命周期由执行层处理。入口见 JavaValueAdapter、ResourceScope。运行期子类型覆盖见 JarReferenceProjectionTest。

根 JAR 的公开 Java interface 生成为普通 Norm interface,并保留可投影的泛型继承关系;生成的具体 class 实现对应 interface。接口方法是普通 Norm 方法,接口返回对象由私有绑定载体保持 JVM identity。该映射由统一类型关系驱动,用户源码只使用 Norm 的 interface、class 与方法调用。

公开 class 经过包私有父类继承到的公开 interface 会被还原到生成声明,泛型实参沿完整 Java 继承链代入。Java 无界通配符投影为 Norm 存在类型 ?,因此 Iterable<String> 可安全传给 Iterable<?> 参数。

成员选择使用完整公开继承面,父类类型变量在导出 class 上完成代入;调用继续指向可公开链接的声明 owner,包私有声明则通过导出 class 链接。census 只记录真实声明,继承视图不重复写入报告。依赖 JAR 的公开类型参与继承和 SAM 识别,发布适配面由 jarBinding.api 显式选择已解析依赖图中的类型。

Java Class<T> 映射为 Norm Class<T>?。Binding 生成器为公开包装声明和数组包装派生 JVM descriptor;运行时用声明 identity 双向解析真实 java.lang.Class,返回值存在多个合法擦除视图时由调用点的 Class<T> 消歧。没有 Binding 映射的普通 Norm 类型不会被字符串类名或宿主反射旁路解析。

类令牌的身份投影由 JavaTypeProjector 定义;Java 泛型上界保留为普通 Norm 泛型约束。

根 JAR 中的 Java enum 生成为封闭的 Norm enum,公开常量成为无 payload variant。Java 静态方法生成普通函数,实例方法生成以 enum 值为首参数的普通函数;参数、返回值和 enum 数组元素在边界两侧按声明 identity 与常量 identity 双向转换。Java 标识符超出 Norm 标识符集合时,生成器保存稳定、可逆的 variant 映射。

Java 领域类型、集合共享身份、泛型元素、Optional 空状态与流资源的行为覆盖见 JarBindingRuntimeIntegrationTest;类型归属契约见 JavaBaseDomainBindingTest。

Java 标准函数接口和根 JAR 中的公开 SAM interface 映射为 Norm 原生 Function<R(P...)>。标准接口的 ? super 输入与 ? extends 输出在投影时消解,根 JAR SAM 的泛型参数按使用点代入;Norm lambda、捕获闭包和函数引用由运行时生成真实 Java interface 实例。宿主回调在调用它的线程进入 Norm,嵌套宿主调用保留该线程的事务等上下文。宿主代码执行期间释放 Norm 的独占执行权,返回后重新取得;嵌套宿主调用阻塞等待其他线程回调时,同样遵循这一边界。同步、异步和 Java 内部等待回调共享同一条参数、返回值与异常传播边界;调度入口见 GuestCallbackScheduler。

每次打包写入的 binding/java-api.json 是完整声明与适配状态的机器可读 census,module.json 中的 jar.api 是发布公开面的机器可读契约。发布门禁要求公开适配面全部生成并通过行为测试。

Java Annotation 会生成普通强类型 Norm Annotation;Norm 应用上的 Annotation 在 JVM 应用边界恢复为真实 Java Annotation。需要编译期处理的 Module 将官方 JSR 269 Processor 声明为普通依赖,应用构建自动生成隔离 Java 输入并运行 Processor。生成的应用类型保留 Norm 泛型继承,并在 JVM 应用外观中提供托管实例分配入口;框架创建的实体或组件会关联回同一个 Norm 对象。入口 Module 与包含框架支持源码的纯 Norm 依赖参与处理;生成的 Binding 声明不进入应用处理面。Norm 异常和枚举值穿过 DI、事务等 Java 代理后保持原有语言语义。真实框架验收入口见 Micronaut BBS。

模块 resources 可作为注解处理器的编译输入。资源准备入口见 ApplicationCompiler,模板更新与删除的回归验证见 AnnotationProcessorResourcesTest。

隐式构造的全部输入都有默认值时,Java 应用外观提供优先的无参构造入口;它执行 Norm 初始化逻辑,完整参数入口仍可使用。构造与私有状态的跨语言验证见 JavaAnnotationBindingIntegrationTest。

接口默认实现由 Core conformance witness 关联到 Java default 方法,多个实现类共享同一入口;方法体仍通过 Norm 运行时执行。没有 Java 表示的标准接口父类型不生成 extends Object。默认方法继承与接收者分派也由上述跨语言测试验证。

Java 应用外观中的零、一、二参数函数按参数数量和 Void 返回值映射为 JDK 函数式接口,泛型实参递归使用同一套类型投影。JavaFunctionShape统一声明与运行时适配;跨语言测试覆盖双向调用、函数身份、装箱与异常传播。Java 传入的函数不具有 Norm 源码声明元数据。

Java 方法索引分别保留声明身份和执行实现,允许不同方法共享规范化后的函数体;共享默认生命周期由跨语言调用验证。

托管 class 方法签名投影为 Java 抽象方法,类的抽象性由继承后的分派目标决定。方法与参数注解、泛型返回类型和参数名由真实 javac 及反射验证;入口见 JavaManagedMethodProjectionTest。抽象声明进入宿主方法索引,不进入本地执行入口集合。

Norm 发起的宿主调用保留接收者与方法的具体类型参数,继承视图复用 CoreTypeRelations。Java 外观回调到泛型父类的普通方法时,从已关联的 Norm 对象恢复接收者类型;应用对象身份、字段同步与回调协调入口为 JavaApplicationDispatch。Java 直接发起带方法类型参数的 Norm 调用及直接构造未具体化的泛型类仍不支持。

Java 集合签名使用 java.base 中的名义类型。JavaAnnotationBindingIntegrationTest 验证嵌套 Java 列表、可空元素及应用回调中的对象身份。Norm 注解能否导出为 Java 注解由 JavaAnnotationShape 定义;原生元数据独立于 Java 导出保留。

JavaHostSurface 定义应用导出范围。JavaApplicationLinkage 携带生成类型与调用清单;JavaValueAdapter 统一标量转换。

字段、参数、返回值及泛型实参的可空性使用标准 JSpecify Nullable 类型注解投影。Java 注解处理环境显式提供 JSpecify 依赖,框架无需从装箱类型猜测可空性。嵌套集合与可空类型变量的真实反射验证见 JavaManagedMethodProjectionTest。

验收 ​

  • 源码树不存在 lock.norm、手写 POM 或 Gradle 配置;
  • 同一个 module(...) 工厂同时表达纯 Norm 和 JAR 支撑的 Module;
  • 类型结构无法为单个 Module 声明两个 Java 根;
  • Maven JAR、本地 JAR 与 JDK 模块共用同一 Binding pipeline;
  • 替换相同路径下的 JAR 会触发摘要不匹配;
  • 相同 JAR 内容可以跨路径复用 Binding artifact;
  • Commons Lang 的固定版本可以从 Maven 仓库解析并从 Norm 调用;
  • 打包后的 Module 能在另一个项目中作为普通 Module 依赖使用,并解析其 Java 依赖;
  • 移除 Binding、提供相同 Norm 导出源码后,消费端 import 和调用形态不变。

应用级 Java 资源通过标准 ServiceLoader 注册 JavaApplicationResource,生命周期由 JvmJarBindingRuntime 管理,在应用类加载器释放前关闭。窗口等子资源的关闭不代表应用级运行时终止。

JAR 资源 URL 对应的缓存句柄由 JarResourceScope 持有至最后一个运行时释放,资源 URL 经字符串重建仍适用。资源流关闭、共享运行时与文件释放验证见 JvmJarBindingRuntimeTest。

JPA 的 jakarta.persistence.Id 与 jakarta.persistence.EmbeddedId 映射为 std.annotation.IdentityField 标记,保留原 Java 注解身份。映射入口为 JavaAnnotationContract,反射查询与字段身份规则见声明引用。

公开 Java class 的父类关系保留在生成声明中,包私有中间类的泛型参数沿继承链代入;跨模块源码和发布产物验证见 CrossModuleJarBindingTest。绑定构造令牌由标准库 ABI 统一提供。

同一 Norm 函数投影为相同 SAM 类型时保留宿主对象身份,弱引用缓存随应用执行域隔离;跨调用身份与回调执行验证见 JarBindingConcurrencyIntegrationTest。

发布包的 Java 依赖封装与校验入口:BundledJarGraphs、ModulePackagerTest。

宿主资源所有权 ​

jarType(..., borrowed: ["child"]) 声明接收者拥有的引用 getter。构造器与未标记资源返回只为首次出现的宿主身份建立执行域所有者;别名保留已有所有者。借用视图持有接收者,不允许自行关闭或转移资源。通过 std.io 的 ownResourceInContext(value) 将 owned Java 资源登记到当前 ResourceOwner。这种组合保留 Java 值及其唯一生命周期,Java 绑定不依赖标准库所有者协议。显式关闭释放登记,并在所有别名上保留首次关闭失败。

声明见 module.norm,所有者 API 见 ownership.norm。HostResourceOwnershipTest 覆盖源码与打包适配器;PublishedJarBinding 定义支持的制品 ABI。

Norm 0.25