diff --git a/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/Add.java b/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/Add.java
new file mode 100644
index 000000000..703f06803
--- /dev/null
+++ b/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/Add.java
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+package com.example.swift;
+
+/** Permitted subclass of {@link Op}; carries an add-flavored value. */
+public final class Add implements Op {
+ private final int value;
+ public Add(int value) { this.value = value; }
+ @Override public int eval() { return value; }
+ @Override public Op combine(Op other) { return new Add(value + other.eval()); }
+}
diff --git a/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/Mul.java b/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/Mul.java
new file mode 100644
index 000000000..30036f103
--- /dev/null
+++ b/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/Mul.java
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+package com.example.swift;
+
+/** Permitted subclass of {@link Op}; carries a mul-flavored value. */
+public final class Mul implements Op {
+ private final int value;
+ public Mul(int value) { this.value = value; }
+ @Override public int eval() { return value; }
+ @Override public Op combine(Op other) { return new Mul(value * other.eval()); }
+}
diff --git a/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/Op.java b/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/Op.java
new file mode 100644
index 000000000..98ce6e8dc
--- /dev/null
+++ b/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/Op.java
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+package com.example.swift;
+
+/**
+ * Java sealed interface used to smoke-test wrap-java's enum-based
+ * translation of {@code sealed interface}. Verifies that:
+ *
+ * - the parent interface is wrapped as a Swift {@code enum} with
+ * a case per permitted subclass,
+ * - methods declared on the parent interface remain callable on
+ * the enum itself (JNI virtual-dispatches to the underlying
+ * concrete Java class),
+ * - returning the parent interface type from Java produces a
+ * Swift enum value whose case matches the actual runtime class.
+ *
+ */
+public sealed interface Op permits Add, Mul {
+ int eval();
+ Op combine(Op other);
+
+ static Op makeAdd(int x) { return new Add(x); }
+ static Op makeMul(int x) { return new Mul(x); }
+}
diff --git a/Samples/JavaKitSampleApp/Sources/JavaKitExample/swift-java.config b/Samples/JavaKitSampleApp/Sources/JavaKitExample/swift-java.config
index d9e8e090d..d1a1ce85d 100644
--- a/Samples/JavaKitSampleApp/Sources/JavaKitExample/swift-java.config
+++ b/Samples/JavaKitSampleApp/Sources/JavaKitExample/swift-java.config
@@ -5,6 +5,9 @@
"com.example.swift.HelloJavaKitArrays" : "HelloJavaKitArrays",
"com.example.swift.JavaKitSampleMain" : "JavaKitSampleMain",
"com.example.swift.Point" : "Point",
- "com.example.swift.ThreadSafeHelperClass" : "ThreadSafeHelperClass"
+ "com.example.swift.ThreadSafeHelperClass" : "ThreadSafeHelperClass",
+ "com.example.swift.Op" : "Op",
+ "com.example.swift.Add" : "Add",
+ "com.example.swift.Mul" : "Mul"
}
}
diff --git a/Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaSealedInterfaceRuntimeTests.swift b/Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaSealedInterfaceRuntimeTests.swift
new file mode 100644
index 000000000..77aad42c2
--- /dev/null
+++ b/Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaSealedInterfaceRuntimeTests.swift
@@ -0,0 +1,144 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import JavaKitExample
+import SwiftJava
+import Testing
+
+/// Runtime tests for wrap-java's enum-based translation of Java
+/// `sealed interface`. These exercise the code paths that only run
+/// when a real JVM is present: JNI method lookup on the underlying
+/// concrete class, and the enum's `init(javaHolder:)` dispatch that
+/// wraps a returned parent-typed object back into the correct case.
+@Suite
+struct JavaSealedInterfaceRuntimeTests {
+
+ let jvm = try JavaKitSampleJVM.shared
+
+ /// Constructing an `Add` and wrapping it into the sealed enum via
+ /// `init(javaHolder:)` picks the `.add` case (not `.unknown`).
+ @Test
+ func initJavaHolderDispatchesToAddCase() throws {
+ let env = try jvm.environment()
+
+ let add = Add(3, environment: env)
+ let op = Op(javaHolder: add.javaHolder)
+
+ switch op {
+ case .add: break // expected
+ case .mul(let v): Issue.record("expected .add, got .mul(\(v.eval()))")
+ case .unknown(let v):
+ Issue.record("expected .add, got .unknown(\(v.javaClass.getName()))")
+ }
+
+ // The enum's own `eval()` method (declared on the sealed interface)
+ // must virtual-dispatch to Add.eval via JNI.
+ #expect(op.eval() == 3)
+ }
+
+ /// Same as above but for `Mul`; guards against a broken always-first-case
+ /// dispatch in the enum's `init(javaHolder:)`.
+ @Test
+ func initJavaHolderDispatchesToMulCase() throws {
+ let env = try jvm.environment()
+
+ let mul = Mul(7, environment: env)
+ let op = Op(javaHolder: mul.javaHolder)
+
+ switch op {
+ case .mul: break // expected
+ case .add(let v): Issue.record("expected .mul, got .add(\(v.eval()))")
+ case .unknown(let v):
+ Issue.record("expected .mul, got .unknown(\(v.javaClass.getName()))")
+ }
+
+ #expect(op.eval() == 7)
+ }
+
+ /// The static Java factories return the sealed interface type `Op`;
+ /// wrap-java exposes them on `JavaClass`. The returned Swift value
+ /// is an `Op!` enum whose case reflects the actual runtime class.
+ @Test
+ func staticFactoryReturnsCorrectEnumCase() throws {
+ let env = try jvm.environment()
+
+ let opClass = try JavaClass(environment: env)
+
+ guard let add = opClass.makeAdd(10) else {
+ Issue.record("makeAdd returned nil")
+ return
+ }
+ switch add {
+ case .add: break
+ default: Issue.record("expected .add from makeAdd, got \(add)")
+ }
+ #expect(add.eval() == 10)
+
+ guard let mul = opClass.makeMul(5) else {
+ Issue.record("makeMul returned nil")
+ return
+ }
+ switch mul {
+ case .mul: break
+ default: Issue.record("expected .mul from makeMul, got \(mul)")
+ }
+ #expect(mul.eval() == 5)
+ }
+
+ /// `combine(Op) -> Op` is declared on the sealed interface, so wrap-java
+ /// emits it directly on the enum. Verifies that:
+ /// 1. calling the method on the enum reaches the concrete Java subclass
+ /// (Add.combine adds, Mul.combine multiplies),
+ /// 2. the returned enum value's case matches the concrete Java subclass
+ /// that Java produced (Add returns an Add, Mul returns a Mul).
+ @Test
+ func combineOnEnumDispatchesToConcreteSubclass() throws {
+ let env = try jvm.environment()
+
+ let opClass = try JavaClass(environment: env)
+ let three = opClass.makeAdd(3)!
+ let two = opClass.makeMul(2)!
+
+ // 3 + 2 = 5, and the result should be an Add (Add.combine returns Add)
+ let sum = three.combine(two)!
+ #expect(sum.eval() == 5)
+ switch sum {
+ case .add: break
+ default: Issue.record("expected .add from Add.combine, got \(sum)")
+ }
+
+ // 2 * 3 = 6, and the result should be a Mul (Mul.combine returns Mul)
+ let product = two.combine(three)!
+ #expect(product.eval() == 6)
+ switch product {
+ case .mul: break
+ default: Issue.record("expected .mul from Mul.combine, got \(product)")
+ }
+ }
+
+ /// The macro-generated `@JavaMethod` body on the enum uses
+ /// `self.javaThis` from `self.javaHolder`, then `GetObjectClass` to
+ /// resolve the method — so `eval()` on `.add(v)` must return the
+ /// same value as `v.eval()` called on the concrete wrapper directly.
+ @Test
+ func methodOnEnumMatchesMethodOnConcreteSubclass() throws {
+ let env = try jvm.environment()
+
+ let add = Add(42, environment: env)
+ let asOp: Op = .add(add)
+
+ #expect(asOp.eval() == add.eval())
+ #expect(asOp.eval() == 42)
+ }
+}
diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift
index 00e2170d4..0cf17940d 100644
--- a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift
+++ b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift
@@ -2,7 +2,10 @@
import SwiftJava
import SwiftJavaJNICore
-@JavaClass("java.lang.reflect.Executable", implements: GenericDeclaration.self)
+/// Java `sealed class`, permits:
+/// - ``Constructor`` (`java.lang.reflect.Constructor`)
+/// - ``Method`` (`java.lang.reflect.Method`)
+@JavaClass(.sealed, "java.lang.reflect.Executable", implements: GenericDeclaration.self)
open class Executable: AccessibleObject {
/// Java method `getAnnotatedExceptionTypes`.
///
diff --git a/Sources/SwiftJava/JavaClassModifier.swift b/Sources/SwiftJava/JavaClassModifier.swift
new file mode 100644
index 000000000..44e7195fd
--- /dev/null
+++ b/Sources/SwiftJava/JavaClassModifier.swift
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+/// Java class-level modifiers surfaced as an option set for the ``JavaClass``,
+/// ``JavaRecord``, and ``JavaInterface`` attached macros.
+///
+/// ```swift
+/// @JavaClass(.sealed, "com.example.Shape")
+/// @JavaClass(.final, "com.example.Leaf")
+/// ```
+///
+/// Java sealed types don't carry an explicit permitted-subclass list in Swift:
+/// the generator models the hierarchy directly in the type system. Sealed
+/// interfaces become Swift enums with one case per permitted subclass; sealed
+/// classes remain Swift classes and the permitted subclasses are just the
+/// declared subclasses reachable via the enclosing type.
+public struct JavaClassModifier: OptionSet, Sendable, Hashable {
+ public let rawValue: Int
+
+ public init(rawValue: Int) {
+ self.rawValue = rawValue
+ }
+
+ /// Java `sealed` type. Has a specific set of subclasses in Java.
+ public static let sealed = JavaClassModifier(rawValue: 1 << 0)
+
+ /// Java `final` type. Cannot be extended in Java.
+ public static let `final` = JavaClassModifier(rawValue: 1 << 1)
+
+ /// Java `abstract` type. Cannot be instantiated directly.
+ public static let abstract = JavaClassModifier(rawValue: 1 << 2)
+}
diff --git a/Sources/SwiftJava/Macros.swift b/Sources/SwiftJava/Macros.swift
index 20a911233..f5db573a1 100644
--- a/Sources/SwiftJava/Macros.swift
+++ b/Sources/SwiftJava/Macros.swift
@@ -12,6 +12,9 @@
//
//===----------------------------------------------------------------------===//
+// ==== -----------------------------------------------------------------------
+// MARK: @JavaClass
+
/// Attached macro that declares that a particular `struct` type is a wrapper around a Java class.
///
/// Use this macro to describe a type that was implemented as a class in Java. The
@@ -45,6 +48,40 @@ public macro JavaClass(
implements: (any AnyJavaObject.Type)?...
) = #externalMacro(module: "SwiftJavaMacros", type: "JavaClassMacro")
+/// Variant of ``JavaClass(_:extends:implements:)`` that carries Java
+/// class modifiers (e.g. `.sealed`, `.final`) as an option-set first positional
+/// argument.
+///
+/// ```swift
+/// @JavaClass(.sealed, "com.example.Shape")
+/// @JavaClass(.final, "com.example.Leaf")
+/// @JavaClass([.sealed, .abstract], "com.example.Base")
+/// ```
+///
+/// Note: Java `sealed` types don't carry an explicit permitted-subclass list
+/// in Swift because the generator models the hierarchy directly in the type
+/// system (permitted subclasses become nested types, or enum cases when the
+/// sealed type is an interface). The `.sealed` marker is preserved for
+/// documentation and reflection-style tooling.
+@attached(
+ member,
+ names: named(fullJavaClassName),
+ named(javaHolder),
+ named(init(javaHolder:)),
+ named(JavaSuperclass),
+ named(`as`)
+)
+@attached(extension, conformances: AnyJavaObject)
+public macro JavaClass(
+ _ modifiers: JavaClassModifier,
+ _ fullClassName: String,
+ extends: (any AnyJavaObject.Type)? = nil,
+ implements: (any AnyJavaObject.Type)?...
+) = #externalMacro(module: "SwiftJavaMacros", type: "JavaClassMacro")
+
+// ==== -----------------------------------------------------------------------
+// MARK: @JavaRecord
+
/// Attached macro that declares that a particular Swift type is a wrapper around a Java `record`.
///
/// This behaves identically to ``JavaClass`` today (records are wrapped as
@@ -85,6 +122,26 @@ public macro JavaRecord(
implements: (any AnyJavaObject.Type)?...
) = #externalMacro(module: "SwiftJavaMacros", type: "JavaClassMacro")
+/// Modifier-carrying variant of ``JavaRecord(_:extends:implements:)``.
+@attached(
+ member,
+ names: named(fullJavaClassName),
+ named(javaHolder),
+ named(init(javaHolder:)),
+ named(JavaSuperclass),
+ named(`as`)
+)
+@attached(extension, conformances: AnyJavaObject)
+public macro JavaRecord(
+ _ modifiers: JavaClassModifier,
+ _ fullClassName: String,
+ extends: (any AnyJavaObject.Type)? = nil,
+ implements: (any AnyJavaObject.Type)?...
+) = #externalMacro(module: "SwiftJavaMacros", type: "JavaClassMacro")
+
+// ==== -----------------------------------------------------------------------
+// MARK: @JavaInterface
+
/// Attached macro that declares that a particular `struct` type is a wrapper around a Java interface.
///
/// Use this macro to describe a type that was implemented as an
@@ -113,8 +170,29 @@ public macro JavaRecord(
named(`as`)
)
@attached(extension, conformances: AnyJavaObject)
-public macro JavaInterface(_ fullClassName: String, extends: (any AnyJavaObject.Type)?...) =
- #externalMacro(module: "SwiftJavaMacros", type: "JavaClassMacro")
+public macro JavaInterface(
+ _ fullClassName: String,
+ extends: (any AnyJavaObject.Type)?...
+) = #externalMacro(module: "SwiftJavaMacros", type: "JavaClassMacro")
+
+/// Modifier-carrying variant of ``JavaInterface(_:extends:)``.
+@attached(
+ member,
+ names: named(fullJavaClassName),
+ named(javaHolder),
+ named(init(javaHolder:)),
+ named(JavaSuperclass),
+ named(`as`)
+)
+@attached(extension, conformances: AnyJavaObject)
+public macro JavaInterface(
+ _ modifiers: JavaClassModifier,
+ _ fullClassName: String,
+ extends: (any AnyJavaObject.Type)?...
+) = #externalMacro(module: "SwiftJavaMacros", type: "JavaClassMacro")
+
+// ==== -----------------------------------------------------------------------
+// MARK: @JavaField / @JavaStaticField
/// Attached macro that turns a Swift property into one that accesses a Java field on the underlying Java object.
///
@@ -145,6 +223,9 @@ public macro JavaField(_ javaFieldName: String? = nil, isFinal: Bool = false) =
public macro JavaStaticField(_ javaFieldName: String? = nil, isFinal: Bool = false) =
#externalMacro(module: "SwiftJavaMacros", type: "JavaFieldMacro")
+// ==== -----------------------------------------------------------------------
+// MARK: @JavaMethod / @JavaStaticMethod
+
/// Attached macro that turns a Swift method into one that wraps a Java method on the underlying Java object.
///
/// The macro must be used in an AnyJavaObject-conforming type.
@@ -207,6 +288,9 @@ public macro JavaMethod(
public macro JavaStaticMethod(_ javaMethodName: String? = nil) =
#externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro")
+// ==== -----------------------------------------------------------------------
+// MARK: @JavaImplementation
+
/// Macro that marks extensions to specify that all of the @JavaMethod
/// methods are implementations of Java methods spelled as `native`.
///
@@ -233,6 +317,9 @@ public macro JavaStaticMethod(_ javaMethodName: String? = nil) =
public macro JavaImplementation(_ fullClassName: String) =
#externalMacro(module: "SwiftJavaMacros", type: "JavaImplementationMacro")
+// ==== -----------------------------------------------------------------------
+// MARK: @JavaExport
+
/// Marker macro that forces a Swift declaration to be exported to Java via jextract.
///
/// When applied to a `typealias`, it registers a specialization entry for generic types:
diff --git a/Sources/SwiftJava/generated/ByteBuffer.swift b/Sources/SwiftJava/generated/ByteBuffer.swift
index 4b872aa47..030223237 100644
--- a/Sources/SwiftJava/generated/ByteBuffer.swift
+++ b/Sources/SwiftJava/generated/ByteBuffer.swift
@@ -38,7 +38,10 @@ extension JavaClass {
@JavaStaticMethod
public func wrap(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) -> ByteBuffer!
}
-@JavaClass("java.nio.ByteBuffer")
+/// Java `sealed class`, permits:
+/// - `java.nio.HeapByteBuffer`
+/// - `java.nio.MappedByteBuffer`
+@JavaClass(.sealed, "java.nio.ByteBuffer")
open class ByteBuffer: JavaObject {
/// Java method `alignedSlice`.
///
diff --git a/Sources/SwiftJava/generated/JavaThread.swift b/Sources/SwiftJava/generated/JavaThread.swift
index 6d4319c79..1fcf0d9ca 100644
--- a/Sources/SwiftJava/generated/JavaThread.swift
+++ b/Sources/SwiftJava/generated/JavaThread.swift
@@ -383,8 +383,41 @@ open class JavaThread: JavaObject {
open override func toString() -> String
}
extension JavaThread {
- @JavaInterface("java.lang.Thread$Builder")
- public struct Builder {
+ /// Java `sealed interface`, permits:
+ /// - ``JavaThread.Builder.OfPlatform`` (`java.lang.Thread$Builder$OfPlatform`)
+ /// - ``JavaThread.Builder.OfVirtual`` (`java.lang.Thread$Builder$OfVirtual`)
+ @JavaInterface(.sealed, "java.lang.Thread$Builder")
+ public enum Builder {
+ case ofPlatform(JavaThread.Builder.OfPlatform)
+
+ case ofVirtual(JavaThread.Builder.OfVirtual)
+
+ case unknown(JavaObject)
+
+ public var javaHolder: JavaObjectHolder {
+ switch self {
+ case .ofPlatform(let v):
+ return v.javaHolder
+ case .ofVirtual(let v):
+ return v.javaHolder
+ case .unknown(let v):
+ return v.javaHolder
+ }
+ }
+
+ public init(javaHolder: JavaObjectHolder) {
+ let raw = JavaObject(javaHolder: javaHolder)
+ if let v = raw.as(JavaThread.Builder.OfPlatform.self) {
+ self = .ofPlatform(v)
+ return
+ }
+ if let v = raw.as(JavaThread.Builder.OfVirtual.self) {
+ self = .ofVirtual(v)
+ return
+ }
+ self = .unknown(raw)
+ }
+
/// Java method `inheritInheritableThreadLocals`.
///
/// ### Java method signature
@@ -423,7 +456,9 @@ extension JavaThread {
}
}
extension JavaThread.Builder {
- @JavaInterface("java.lang.Thread$Builder$OfPlatform", extends: JavaThread.Builder.self)
+ /// Java `sealed interface`, permits:
+ /// - `java.lang.ThreadBuilders$PlatformThreadBuilder`
+ @JavaInterface(.sealed, "java.lang.Thread$Builder$OfPlatform", extends: JavaThread.Builder.self)
public struct OfPlatform {
/// Java method `daemon`.
///
@@ -499,7 +534,9 @@ extension JavaThread.Builder {
}
}
extension JavaThread.Builder {
- @JavaInterface("java.lang.Thread$Builder$OfVirtual", extends: JavaThread.Builder.self)
+ /// Java `sealed interface`, permits:
+ /// - `java.lang.ThreadBuilders$VirtualThreadBuilder`
+ @JavaInterface(.sealed, "java.lang.Thread$Builder$OfVirtual", extends: JavaThread.Builder.self)
public struct OfVirtual {
/// Java method `inheritInheritableThreadLocals`.
///
diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md
index f30149069..b951d96bd 100644
--- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md
+++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md
@@ -34,7 +34,7 @@ Java `native` functions. SwiftJava simplifies the type conversions
| Java `abstract class` | TODO |
| Java `enum` | ❌ |
| Java `record` (Java 16+) | ✅ `@JavaRecord` |
-| Java `sealed class` / `sealed interface` (Java 17+) | 🟡 recognized, but missing special handling of `permits` list |
+| Java `sealed class` / `sealed interface` (Java 17+) | ✅ `.sealed` modifier; sealed interfaces are wrapped as Swift enums with a case per permitted subclass plus an `unknown` catch-all |
| **This list is very work in progress** | |
### JExtract – calling Swift from Java
diff --git a/Sources/SwiftJavaMacros/JavaClassMacro.swift b/Sources/SwiftJavaMacros/JavaClassMacro.swift
index 6797f61de..71a336d78 100644
--- a/Sources/SwiftJavaMacros/JavaClassMacro.swift
+++ b/Sources/SwiftJavaMacros/JavaClassMacro.swift
@@ -31,9 +31,14 @@ extension JavaClassMacro: MemberMacro {
}
let swiftName = namedDecl.name.text
- // Dig out the Java class name.
- guard case .argumentList(let arguments) = node.arguments,
- let wrapperTypeNameExpr = arguments.first?.expression,
+ // Dig out the Java class name. The class name is the first unlabeled
+ // string-literal positional argument; a leading modifier option-set
+ // (e.g. `@JavaClass(.sealed, "com.example.Foo", ...)`) can precede it.
+ guard case .argumentList(let arguments) = node.arguments else {
+ throw MacroErrors.classNameNotStringLiteral
+ }
+ let unlabeledPositional = arguments.filter { $0.label == nil }
+ guard let wrapperTypeNameExpr = unlabeledPositional.first(where: { $0.expression.is(StringLiteralExprSyntax.self) })?.expression,
let stringLiteral = wrapperTypeNameExpr.as(StringLiteralExprSyntax.self),
stringLiteral.segments.count == 1,
case let .stringSegment(classNameSegment)? = stringLiteral.segments.first
@@ -42,24 +47,37 @@ extension JavaClassMacro: MemberMacro {
}
// Determine whether we're exposing the Java class as a Swift class, which
- // changes how we generate some of the members.
+ // changes how we generate some of the members. Swift enums are also
+ // supported for Java `sealed interface` types (see JavaClassTranslator):
+ // the enum's case list carries the permitted-subclass information, so
+ // the parent's macro attribute no longer needs `permits:`, breaking the
+ // mutual `@JavaInterface` macro-expansion cycle. The macro emits only
+ // `fullJavaClassName` for enums; the generator supplies `javaHolder`,
+ // `init(javaHolder:)` and per-subclass dispatch, since it (unlike the
+ // macro) knows the permitted-subclass list.
let isSwiftClass: Bool
+ let isSwiftEnum: Bool
let isJavaLangObject: Bool
let specifiedSuperclass: String?
if let classDecl = declaration.as(ClassDeclSyntax.self) {
isSwiftClass = true
+ isSwiftEnum = false
isJavaLangObject = classDecl.isJavaLangObject
// Retrieve the superclass, if there is one.
specifiedSuperclass = classDecl.inheritanceClause?.inheritedTypes.first?.trimmedDescription
+ } else if declaration.is(EnumDeclSyntax.self) {
+ isSwiftClass = false
+ isSwiftEnum = true
+ isJavaLangObject = false
+ specifiedSuperclass = nil
} else {
isSwiftClass = false
+ isSwiftEnum = false
isJavaLangObject = false
// Dig out the "extends" argument from the attribute.
- if let superclassArg = arguments.dropFirst().first,
- let superclassArgLabel = superclassArg.label,
- superclassArgLabel.text == "extends",
+ if let superclassArg = arguments.first(where: { $0.label?.text == "extends" }),
let superclassMemberAccess = superclassArg.expression.as(MemberAccessExprSyntax.self),
superclassMemberAccess.declName.trimmedDescription == "self",
let superclassMemberBase = superclassMemberAccess.base
@@ -105,6 +123,15 @@ extension JavaClassMacro: MemberMacro {
"""
)
+ // Enums (used for Java `sealed interface` wrappers) can't have stored
+ // properties, and their `javaHolder` / `init(javaHolder:)` / `_dispatch`
+ // implementations depend on the permitted-subclass list, which only the
+ // generator knows. Emit only `fullJavaClassName` here and let the
+ // generator supply the rest inside the enum body.
+ if isSwiftEnum {
+ return members
+ }
+
// struct wrappers need a JavaSuperclass type.
if !isSwiftClass {
members.append(
diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift
index bbb796da7..2173e2f64 100644
--- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift
+++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift
@@ -368,7 +368,6 @@ extension JavaClassTranslator {
}
}
-/// MARK: Rendering of Java class members as Swift declarations.
extension JavaClassTranslator {
/// Render the Swift declarations that will express this Java class in Swift.
package func render() -> [DeclSyntax] {
@@ -393,13 +392,26 @@ extension JavaClassTranslator {
}
if javaClass.isSealed() {
let kind = javaClass.isInterface() ? "sealed interface" : "sealed class"
- let permits = javaClass.getPermittedSubclasses().compactMap { $0?.getName() }
- if permits.isEmpty {
+ let permittedSubclasses = javaClass.getPermittedSubclasses().compactMap { $0 }
+ if permittedSubclasses.isEmpty {
lines.append("/// Java `\(kind)`")
} else {
- lines.append("/// Java `\(kind)`, permits: \(permits.map({ "`\($0)`" }).joined(separator: ", "))")
+ // One permitted subclass per line so the DocC-rendered output stays
+ // readable even for hierarchies with many alternatives.
+ lines.append("/// Java `\(kind)`, permits:")
+ for permitted in permittedSubclasses {
+ let javaName = permitted.getName()
+ if let swiftName = translator.translatedClasses[javaName]?.swiftType {
+ lines.append("/// - ``\(swiftName)`` (`\(javaName)`)")
+ } else {
+ lines.append("/// - `\(javaName)`")
+ }
+ }
}
- log.info("Wrap Java \(kind): `\(javaClass.getName())`, permits: \(permits.map({ "`\($0)`" }).joined(separator: ", ")))")
+ let logPermittedNames = permittedSubclasses.map { $0.getName() }
+ log.info(
+ "Wrap Java \(kind): `\(javaClass.getName())`, permits: \(logPermittedNames.map({ "`\($0)`" }).joined(separator: ", ")))"
+ )
}
if lines.isEmpty {
return ""
@@ -435,8 +447,30 @@ extension JavaClassTranslator {
}
}
+ // A Java `sealed interface` is emitted as a Swift enum whose cases carry
+ // the permitted subclasses' Swift wrapper types. See
+ // `renderSealedInterfaceEnumMembers()` for the case list and the
+ // computed `javaHolder` / `init(javaHolder:)` synthesis.
+ //
+ // The interface's own instance methods are still emitted (as
+ // `@JavaMethod` funcs on the enum): the enum conforms to
+ // `AnyJavaObject` via the extension-macro branch, so the macro-supplied
+ // body's `dynamicJavaMethodCall` sees a valid `self.javaThis`. When such
+ // a method returns the enum itself, our `init(javaHolder:)` runs on the
+ // JNI result and picks the correct case via `.as(_:)` dispatch.
+ //
+ // Only take the enum route when at least one permitted subclass has a
+ // Swift wrapper: otherwise the enum has only a `unknown(JavaObject)`
+ // case, which is strictly worse than the plain struct form that still
+ // carries the Java interface's methods.
+ let renderAsSealedInterfaceEnum: Bool = {
+ guard javaClass.isSealed() && javaClass.isInterface() else { return false }
+ let permittedSubclasses = javaClass.getPermittedSubclasses().compactMap { $0 }
+ return permittedSubclasses.contains { translator.translatedClasses[$0.getName()] != nil }
+ }()
+
// Render all of the instance methods in Swift.
- let instanceMethods = methods.methods.sortedForEmission().compactMap { method in
+ let instanceMethods: [DeclSyntax] = methods.methods.sortedForEmission().compactMap { method in
do {
return try renderMethod(method, implementedInSwift: false)
} catch {
@@ -455,8 +489,16 @@ extension JavaClassTranslator {
DeclSyntax("public typealias \(raw: javaDecl.getName()) = \(raw: swiftName)")
}
+ // For a sealed interface, the enum body carries a case per permitted
+ // Swift subclass, a computed `javaHolder`, and an `init(javaHolder:)`
+ // that dispatches via `.as(_:)` down to the concrete wrapper.
+ let sealedInterfaceEnumMembers: [DeclSyntax] =
+ renderAsSealedInterfaceEnum ? renderSealedInterfaceEnumMembers() : []
+
// Collect all of the members of this type.
- let members = genericParameterTypeAliases + initializers + properties + enumDecls + instanceMethods
+ let members =
+ genericParameterTypeAliases + initializers + properties + enumDecls
+ + sealedInterfaceEnumMembers + instanceMethods
// Compute the "extends" clause for the superclass (of the struct
// formulation) or the inheritance clause (for the class
@@ -492,6 +534,15 @@ extension JavaClassTranslator {
interfacesStr = ", \(prefix): \(swiftInterfaces.map { "\($0).self" }.joined(separator: ", "))"
}
+ // Compute the `.sealed` marker for Java `sealed` types. The permitted-
+ // subclass list is no longer emitted as a `permits:` macro argument
+ // because we model the hierarchy directly in the Swift type system
+ // (sealed interfaces are wrapped as Swift enums with cases per
+ // permitted subclass; sealed classes rely on the emitted subclass
+ // decls being findable via the enclosing type's members).
+ let sealedModifierPrefix: String =
+ javaClass.isSealed() ? ".sealed, " : ""
+
let genericParameterClause =
if swiftGenericParameterNames.isEmpty {
""
@@ -510,7 +561,14 @@ extension JavaClassTranslator {
} else {
"JavaClass"
}
- let introducer = translateAsClass ? "open class" : "public struct"
+ let introducer =
+ if translateAsClass {
+ "open class"
+ } else if renderAsSealedInterfaceEnum {
+ "public enum"
+ } else {
+ "public struct"
+ }
let classAvailableAttributes = swiftAvailableAttributes(
from: annotations,
runtimeInvisibleAnnotations: self.runtimeInvisibleAnnotations.classAnnotations,
@@ -519,7 +577,7 @@ extension JavaClassTranslator {
let javaKindDocs = renderJavaKindDocc()
var classDecl: DeclSyntax =
"""
- \(raw: javaKindDocs)\(raw: classAvailableAttributes.render())@\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extendsClause)\(raw: interfacesStr))
+ \(raw: javaKindDocs)\(raw: classAvailableAttributes.render())@\(raw: classOrInterface)(\(raw: sealedModifierPrefix)\(literal: javaClass.getName())\(raw: extendsClause)\(raw: interfacesStr))
\(raw: introducer) \(raw: swiftInnermostTypeName)\(raw: genericParameterClause)\(raw: inheritanceClause) {
\(raw: members.map { $0.description }.joined(separator: "\n\n"))
}
@@ -540,6 +598,92 @@ extension JavaClassTranslator {
return classDecl.formatted(using: translator.format).cast(DeclSyntax.self)
}
+ /// Render the enum body for a Java `sealed interface` wrapper.
+ ///
+ /// Emits one case per Swift-translated permitted subclass plus a trailing
+ /// ``unknown(JavaObject)`` catch-all for Java subclasses whose Swift wrapper
+ /// has not been generated. Also emits the computed ``javaHolder`` and
+ /// ``init(javaHolder:)`` required by ``AnyJavaObject``.
+ ///
+ /// The permitted-subclass list lives in the enum's case declarations rather
+ /// than in a `permits:` macro argument. That's what breaks the mutual
+ /// `@JavaInterface` macro-expansion cycle: the parent's macro attribute no
+ /// longer names the children, and case declarations are resolved at member
+ /// lookup, well after both parent and child macros have expanded.
+ private func renderSealedInterfaceEnumMembers() -> [DeclSyntax] {
+ let permittedSubclasses = javaClass.getPermittedSubclasses().compactMap { $0 }
+
+ // Only permitted subclasses whose Swift wrapper is known can become
+ // typed enum cases. Unknown permitted subclasses are still handled at
+ // runtime by the `unknown` case.
+ struct SealedCase {
+ let caseName: String
+ let swiftType: String
+ }
+ var cases: [SealedCase] = []
+ var seenCaseNames = Set()
+ for permittedSubclass in permittedSubclasses {
+ guard let swiftType = translator.translatedClasses[permittedSubclass.getName()]?.swiftType else {
+ continue
+ }
+ // Case name is the last Swift name segment, lowercased first letter.
+ let lastSegment = swiftType.split(separator: ".").last.map(String.init) ?? swiftType
+ var caseName = lastSegment
+ if let firstChar = caseName.first {
+ caseName = firstChar.lowercased() + caseName.dropFirst()
+ }
+ // Guard against name clashes if two permitted subclasses share the
+ // same simple name in different parents.
+ guard seenCaseNames.insert(caseName).inserted else {
+ continue
+ }
+ cases.append(SealedCase(caseName: caseName, swiftType: swiftType))
+ }
+
+ var members: [DeclSyntax] = []
+ for c in cases {
+ members.append("case \(raw: c.caseName)(\(raw: c.swiftType))")
+ }
+ // Catch-all for permitted subclasses whose Swift wrapper has not been
+ // generated. The wrapped `JavaObject` still reflects the real runtime
+ // class -- callers can query it via `.javaClass.getName()`.
+ members.append("case unknown(JavaObject)")
+
+ let holderArms: String =
+ (cases.map { c in
+ "case .\(c.caseName)(let v): return v.javaHolder"
+ } + ["case .unknown(let v): return v.javaHolder"]).joined(separator: "\n ")
+ members.append(
+ """
+ public var javaHolder: JavaObjectHolder {
+ switch self {
+ \(raw: holderArms)
+ }
+ }
+ """
+ )
+
+ let initArms: String = cases.map { c in
+ """
+ if let v = raw.as(\(c.swiftType).self) {
+ self = .\(c.caseName)(v)
+ return
+ }
+ """
+ }.joined(separator: "\n ")
+ members.append(
+ """
+ public init(javaHolder: JavaObjectHolder) {
+ let raw = JavaObject(javaHolder: javaHolder)
+ \(raw: initArms)
+ self = .unknown(raw)
+ }
+ """
+ )
+
+ return members
+ }
+
/// Render any nested classes that will not be rendered separately.
func renderNestedClasses() -> [DeclSyntax] {
nestedClasses
diff --git a/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift
index dad2b2db6..5953e0183 100644
--- a/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift
+++ b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift
@@ -13,8 +13,10 @@
//===----------------------------------------------------------------------===//
import SwiftJavaMacros
+import SwiftParser
import SwiftSyntax
import SwiftSyntaxBuilder
+import SwiftSyntaxMacroExpansion
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import XCTest
@@ -23,6 +25,7 @@ class JavaKitMacroTests: XCTestCase {
static let javaKitMacros: [String: any Macro.Type] = [
"JavaClass": JavaClassMacro.self,
"JavaRecord": JavaClassMacro.self,
+ "JavaInterface": JavaClassMacro.self,
"JavaMethod": JavaMethodMacro.self,
"JavaField": JavaFieldMacro.self,
"JavaStaticMethod": JavaMethodMacro.self,
@@ -527,6 +530,48 @@ class JavaKitMacroTests: XCTestCase {
)
}
+ func testJavaClassSealedWithoutPermits() throws {
+ assertMacroExpansion(
+ """
+ @JavaClass(.sealed, "com.example.Foo")
+ open class Foo: JavaObject {
+ }
+ """,
+ expectedChunks: [],
+ notExpectedChunks: [
+ "public func as"
+ ]
+ )
+ }
+
+ func testJavaInterfaceOnEnum() throws {
+ // A Java `sealed interface` is wrapped as a Swift enum. The macro emits
+ // only `fullJavaClassName` for enum declarations -- the case list,
+ // `javaHolder`, and `init(javaHolder:)` are supplied by the generator
+ // (which has the permitted-subclass list). The extension macro still
+ // adds `AnyJavaObject` conformance on top.
+ assertMacroExpansion(
+ """
+ @JavaInterface(.sealed, "com.example.Op")
+ public enum Op {
+ case add(Add)
+ case mul(Mul)
+ }
+ """,
+ expectedChunks: [
+ "public static var fullJavaClassName: String",
+ #""com.example.Op""#,
+ ],
+ notExpectedChunks: [
+ // Enum branch must not synthesize a stored `javaHolder` (illegal
+ // on enums) or the struct-shaped `init(javaHolder:)`.
+ "public var javaHolder: JavaObjectHolder",
+ "public required init(javaHolder: JavaObjectHolder)",
+ "public typealias JavaSuperclass",
+ ]
+ )
+ }
+
func testJavaGenericClassGenericMethodParameter() throws {
assertMacroExpansion(
"""
@@ -577,3 +622,47 @@ class JavaKitMacroTests: XCTestCase {
)
}
}
+
+private func assertMacroExpansion(
+ _ source: String,
+ expectedChunks: [String],
+ notExpectedChunks: [String] = [],
+ file: StaticString = #fileID,
+ line: UInt = #line
+) {
+ let expanded = expandMacros(in: source)
+ let expandedCollapsed = expanded.replacing(" ", with: "")
+ for chunk in expectedChunks {
+ let chunkCollapsed = chunk.replacing(" ", with: "")
+ XCTAssertTrue(
+ expandedCollapsed.contains(chunkCollapsed),
+ "Expected chunk:\n\(chunk)\n\nnot found in expanded source:\n\(expanded)",
+ file: file,
+ line: line
+ )
+ }
+ for chunk in notExpectedChunks {
+ let chunkCollapsed = chunk.replacing(" ", with: "")
+ XCTAssertFalse(
+ expandedCollapsed.contains(chunkCollapsed),
+ "Unexpected chunk:\n\(chunk)\n\nfound in expanded source:\n\(expanded)",
+ file: file,
+ line: line
+ )
+ }
+}
+
+private func expandMacros(in source: String) -> String {
+ let origSourceFile = Parser.parse(source: source)
+ let context = BasicMacroExpansionContext(
+ sourceFiles: [origSourceFile: .init(moduleName: "TestModule", fullFilePath: "test.swift")]
+ )
+ let expanded = origSourceFile.expand(
+ macros: JavaKitMacroTests.javaKitMacros,
+ contextGenerator: { syntax in
+ BasicMacroExpansionContext(sharingWith: context, lexicalContext: syntax.allMacroLexicalContexts())
+ },
+ indentationWidth: .spaces(2)
+ )
+ return expanded.description
+}
diff --git a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift
index 1fb48fc42..a0762b583 100644
--- a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift
+++ b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift
@@ -124,17 +124,17 @@ func assertWrapJavaOutput(
}
javaClasses.append(javaClass)
- // FIXME: deduplicate this with SwiftJava.WrapJavaCommand.runCommand !!!
- // TODO: especially because nested classes
- // WrapJavaCommand().
-
let swiftUnqualifiedName =
classNameMappings[javaClassName]
?? javaClassName.javaClassNameToCanonicalName.defaultSwiftNameForJavaClass
translator.translatedClasses[javaClassName] =
.init(module: nil, name: swiftUnqualifiedName)
+ }
+
+ try translator.validateClassConfiguration()
- try translator.validateClassConfiguration()
+ for javaClass in javaClasses {
+ // FIXME: deduplicate this with SwiftJava.WrapJavaCommand.runCommand
let swiftClassDecls = try translator.translateClass(javaClass)
let importDecls = translator.getImportDecls()
diff --git a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift
index 374d2b332..91c682fe5 100644
--- a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift
+++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift
@@ -654,7 +654,7 @@ class Java2SwiftTests: XCTestCase {
expectedChunks: [
"import JavaNio",
"""
- @JavaClass("java.nio.ByteBuffer")
+ @JavaClass(.sealed, "java.nio.ByteBuffer")
open class NIOByteBuffer: NIOBuffer {
""",
"""
diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests/RecordSealedWrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests/RecordSealedWrapJavaTests.swift
new file mode 100644
index 000000000..140c0042d
--- /dev/null
+++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests/RecordSealedWrapJavaTests.swift
@@ -0,0 +1,196 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import Foundation
+import JavaNet
+import JavaUtilJar
+import Subprocess
+@_spi(Testing) import SwiftJava
+import SwiftJavaConfigurationShared
+import SwiftJavaShared
+import SwiftJavaToolLib
+import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43
+
+final class RecordSealedWrapJavaTests: XCTestCase {
+
+ func test_wrapJava_javaRecord_isMarkedInDocs() async throws {
+ let classpathURL = try await compileJava(
+ """
+ package com.example;
+
+ public record Point(int x, int y) {}
+ """
+ )
+
+ try assertWrapJavaOutput(
+ javaClassNames: [
+ "com.example.Point"
+ ],
+ classpath: [classpathURL],
+ expectedChunks: [
+ """
+ /// Java record: `com.example.Point`
+ @JavaRecord("com.example.Point")
+ open class Point: JavaObject {
+ @JavaMethod
+ @_nonoverride public convenience init(_ x: Int32, _ y: Int32, environment: JNIEnvironment? = nil)
+ """
+ ]
+ )
+ }
+
+ func test_wrapJava_javaSealed_isMarkedInDocs() async throws {
+ let classpathURL = try await compileJava(
+ """
+ package com.example;
+
+ public sealed class Shape permits Circle, Square {}
+ final class Circle extends Shape {}
+ final class Square extends Shape {}
+ """
+ )
+
+ try assertWrapJavaOutput(
+ javaClassNames: [
+ "com.example.Shape",
+ "com.example.Circle",
+ "com.example.Square",
+ ],
+ classpath: [classpathURL],
+ expectedChunks: [
+ """
+ /// Java `sealed class`, permits:
+ /// - ``Circle`` (`com.example.Circle`)
+ /// - ``Square`` (`com.example.Square`)
+ @JavaClass(.sealed, "com.example.Shape")
+ open class Shape:
+ """
+ ]
+ )
+ }
+
+ func test_wrapJava_javaSealedInterface_isTranslatedAsSwiftEnum() async throws {
+ let classpathURL = try await compileJava(
+ """
+ package com.example;
+
+ public sealed interface Op permits Add, Mul {
+ Op combine(Op other);
+ }
+ final class Add implements Op {
+ @Override public Op combine(Op other) { return this; }
+ }
+ final class Mul implements Op {
+ @Override public Op combine(Op other) { return this; }
+ }
+ """
+ )
+
+ try assertWrapJavaOutput(
+ javaClassNames: [
+ "com.example.Op",
+ "com.example.Add",
+ "com.example.Mul",
+ ],
+ classpath: [classpathURL],
+ expectedChunks: [
+ // Sealed interface becomes a Swift enum. The permitted subclasses
+ // are modelled as typed cases plus an `unknown` catch-all for any
+ // Java subclass whose Swift wrapper hasn't been generated. The
+ // interface's own methods are emitted on the enum itself,
+ // so callers can dispatch without pattern-matching first. The
+ // macro-generated body invokes `dynamicJavaMethodCall` on the
+ // enum (which conforms to `AnyJavaObject` via the extension
+ // macro) and wraps the result back into the enum via
+ // `init(javaHolder:)`.
+ """
+ /// Java `sealed interface`, permits:
+ /// - ``Add`` (`com.example.Add`)
+ /// - ``Mul`` (`com.example.Mul`)
+ @JavaInterface(.sealed, "com.example.Op")
+ public enum Op {
+ case add(Add)
+
+ case mul(Mul)
+
+ case unknown(JavaObject)
+
+ public var javaHolder: JavaObjectHolder {
+ switch self {
+ case .add(let v):
+ return v.javaHolder
+ case .mul(let v):
+ return v.javaHolder
+ case .unknown(let v):
+ return v.javaHolder
+ }
+ }
+
+ public init(javaHolder: JavaObjectHolder) {
+ let raw = JavaObject(javaHolder: javaHolder)
+ if let v = raw.as(Add.self) {
+ self = .add(v)
+ return
+ }
+ if let v = raw.as(Mul.self) {
+ self = .mul(v)
+ return
+ }
+ self = .unknown(raw)
+ }
+
+ /// Java method `combine`.
+ ///
+ /// ### Java method signature
+ /// ```java
+ /// public abstract com.example.Op com.example.Op.combine(com.example.Op)
+ /// ```
+ @JavaMethod
+ public func combine(_ arg0: Op?) -> Op!
+ }
+ """,
+ // The Java `Op combine(Op)` method is inherited by each permitted
+ // subclass; wrap-java re-emits it on the concrete `Add` / `Mul`
+ // wrappers too, so callers holding a concrete case can call
+ // `.combine(...)` without pattern matching.
+ """
+ @JavaClass("com.example.Add", implements: Op.self)
+ open class Add: JavaObject {
+ /// Java method `combine`.
+ ///
+ /// ### Java method signature
+ /// ```java
+ /// public com.example.Op com.example.Add.combine(com.example.Op)
+ /// ```
+ @JavaMethod
+ open func combine(_ arg0: Op?) -> Op!
+ }
+ """,
+ """
+ @JavaClass("com.example.Mul", implements: Op.self)
+ open class Mul: JavaObject {
+ /// Java method `combine`.
+ ///
+ /// ### Java method signature
+ /// ```java
+ /// public com.example.Op com.example.Mul.combine(com.example.Op)
+ /// ```
+ @JavaMethod
+ open func combine(_ arg0: Op?) -> Op!
+ }
+ """,
+ ]
+ )
+ }
+}