diff --git a/Sources/JavaStdlib/JavaUtil/JavaExtensions/Collections+Sequence.swift b/Sources/JavaStdlib/JavaUtil/JavaExtensions/Collections+Sequence.swift new file mode 100644 index 000000000..b59d1798d --- /dev/null +++ b/Sources/JavaStdlib/JavaUtil/JavaExtensions/Collections+Sequence.swift @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJava + +extension ArrayList: JavaCollectionProtocol { + public typealias Element = E +} + +extension ArrayDeque: JavaCollectionProtocol { + public typealias Element = E +} + +extension HashSet: JavaCollectionProtocol { + public typealias Element = E +} + +extension TreeSet: JavaCollectionProtocol { + public typealias Element = E +} + +extension PriorityQueue: JavaCollectionProtocol { + public typealias Element = E +} + +extension Queue: JavaCollectionProtocol { + public typealias Element = E +} diff --git a/Sources/SwiftJava/JavaExtensions/JavaCollection+Sequence.swift b/Sources/SwiftJava/JavaExtensions/JavaCollection+Sequence.swift new file mode 100644 index 000000000..b8e6c9e5b --- /dev/null +++ b/Sources/SwiftJava/JavaExtensions/JavaCollection+Sequence.swift @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +extension JavaCollection: JavaCollectionProtocol { + public typealias Element = E +} diff --git a/Sources/SwiftJava/JavaExtensions/JavaCollectionProtocol.swift b/Sources/SwiftJava/JavaExtensions/JavaCollectionProtocol.swift new file mode 100644 index 000000000..4e73f7183 --- /dev/null +++ b/Sources/SwiftJava/JavaExtensions/JavaCollectionProtocol.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJavaJNICore + +/// A common protocol for Java collection and iterable types that can be iterated as a Swift `Sequence`. +public protocol JavaCollectionProtocol: Sequence where Iterator == JavaIterator { + associatedtype Element: AnyJavaObject + + /// Returns a Java iterator over the elements in this collection. + func iterator() -> JavaIterator! +} + +extension JavaCollectionProtocol { + public func makeIterator() -> JavaIterator { + self.iterator() + } +} diff --git a/Sources/SwiftJava/JavaExtensions/JavaIterator+Iterator.swift b/Sources/SwiftJava/JavaExtensions/JavaIterator+Iterator.swift index 49d429fc8..3db4bc0a2 100644 --- a/Sources/SwiftJava/JavaExtensions/JavaIterator+Iterator.swift +++ b/Sources/SwiftJava/JavaExtensions/JavaIterator+Iterator.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -extension JavaIterator: IteratorProtocol { +extension JavaIterator: IteratorProtocol, Sequence { public typealias Element = E @_implements(IteratorProtocol,next()) @@ -23,4 +23,8 @@ extension JavaIterator: IteratorProtocol { return nil } + + public func makeIterator() -> JavaIterator { + self + } } diff --git a/Sources/SwiftJava/JavaExtensions/JavaSet+Sequence.swift b/Sources/SwiftJava/JavaExtensions/JavaSet+Sequence.swift new file mode 100644 index 000000000..f8e1412d1 --- /dev/null +++ b/Sources/SwiftJava/JavaExtensions/JavaSet+Sequence.swift @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +extension JavaSet: JavaCollectionProtocol { + public typealias Element = E +} diff --git a/Sources/SwiftJava/JavaExtensions/List+Sequence.swift b/Sources/SwiftJava/JavaExtensions/List+Sequence.swift index 2cbcd74c9..d50073f59 100644 --- a/Sources/SwiftJava/JavaExtensions/List+Sequence.swift +++ b/Sources/SwiftJava/JavaExtensions/List+Sequence.swift @@ -12,11 +12,6 @@ // //===----------------------------------------------------------------------===// -extension List: Sequence { +extension List: JavaCollectionProtocol { public typealias Element = E - public typealias Iterator = JavaIterator - - public func makeIterator() -> Iterator { - self.iterator() - } } diff --git a/Sources/SwiftJava/JavaExtensions/ListIterator+Iterator.swift b/Sources/SwiftJava/JavaExtensions/ListIterator+Iterator.swift new file mode 100644 index 000000000..eab48baf9 --- /dev/null +++ b/Sources/SwiftJava/JavaExtensions/ListIterator+Iterator.swift @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +extension ListIterator: IteratorProtocol, Sequence { + public typealias Element = E + + @_implements(IteratorProtocol,next()) + public mutating func swiftNext() -> E? { + if hasNext() { + return next() as E + } + + return nil + } + + public func makeIterator() -> ListIterator { + self + } +} diff --git a/Tests/SwiftJavaTests/CollectionsSequenceTests.swift b/Tests/SwiftJavaTests/CollectionsSequenceTests.swift new file mode 100644 index 000000000..c00d24588 --- /dev/null +++ b/Tests/SwiftJavaTests/CollectionsSequenceTests.swift @@ -0,0 +1,203 @@ +//===----------------------------------------------------------------------===// +// +// 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 JavaUtil +import SwiftJava +import XCTest + +class CollectionsSequenceTests: XCTestCase { + func testJavaCollectionSequence() throws { + let environment = try jvm.environment() + let integerClass = try JavaClass(environment: environment) + + let arrayList = ArrayList(environment: environment) + _ = arrayList.add(integerClass.valueOf(10)) + _ = arrayList.add(integerClass.valueOf(20)) + _ = arrayList.add(integerClass.valueOf(30)) + + let collection: JavaCollection = try XCTUnwrap(arrayList.as(JavaCollection.self)) + + // Test for..in loop + var collected: [Int32] = [] + for item in collection { + collected.append(item.intValue()) + } + XCTAssertEqual(collected, [10, 20, 30]) + + // Test Sequence standard library methods + XCTAssertEqual(collection.map { $0.intValue() }, [10, 20, 30]) + XCTAssertEqual(collection.filter { $0.intValue() > 15 }.map { $0.intValue() }, [20, 30]) + XCTAssertTrue(collection.contains { $0.intValue() == 20 }) + XCTAssertFalse(collection.contains { $0.intValue() == 99 }) + } + + func testJavaSetSequence() throws { + let environment = try jvm.environment() + let integerClass = try JavaClass(environment: environment) + + let hashSet = HashSet(environment: environment) + _ = hashSet.add(integerClass.valueOf(10)) + _ = hashSet.add(integerClass.valueOf(20)) + _ = hashSet.add(integerClass.valueOf(30)) + + let set: JavaSet = try XCTUnwrap(hashSet.as(JavaSet.self)) + + var collected: Set = [] + for item in set { + collected.insert(item.intValue()) + } + XCTAssertEqual(collected, [10, 20, 30]) + + XCTAssertEqual(Set(set.map { $0.intValue() }), [10, 20, 30]) + XCTAssertEqual(set.filter { $0.intValue() > 15 }.count, 2) + } + + func testListSequence() throws { + let environment = try jvm.environment() + let integerClass = try JavaClass(environment: environment) + + let arrayList = ArrayList(environment: environment) + _ = arrayList.add(integerClass.valueOf(1)) + _ = arrayList.add(integerClass.valueOf(2)) + _ = arrayList.add(integerClass.valueOf(3)) + + let list: List = try XCTUnwrap(arrayList.as(List.self)) + + XCTAssertEqual(list.map { $0.intValue() }, [1, 2, 3]) + let sum = list.reduce(0) { $0 + $1.intValue() } + XCTAssertEqual(sum, 6) + } + + func testJavaIteratorSequence() throws { + let environment = try jvm.environment() + let integerClass = try JavaClass(environment: environment) + + let arrayList = ArrayList(environment: environment) + _ = arrayList.add(integerClass.valueOf(100)) + _ = arrayList.add(integerClass.valueOf(200)) + + let iterator = try XCTUnwrap(arrayList.iterator()) + + // JavaIterator conforms to Sequence and IteratorProtocol + var values: [Int32] = [] + for item in iterator { + values.append(item.intValue()) + } + XCTAssertEqual(values, [100, 200]) + } + + func testListIteratorSequence() throws { + let environment = try jvm.environment() + let integerClass = try JavaClass(environment: environment) + + let arrayList = ArrayList(environment: environment) + _ = arrayList.add(integerClass.valueOf(1)) + _ = arrayList.add(integerClass.valueOf(2)) + + let list: List = try XCTUnwrap(arrayList.as(List.self)) + let listIterator = try XCTUnwrap(list.listIterator()) + + // ListIterator conforms to Sequence and IteratorProtocol + var values: [Int32] = [] + for item in listIterator { + values.append(item.intValue()) + } + XCTAssertEqual(values, [1, 2]) + } + + func testJavaUtilConcreteCollectionsSequence() throws { + let environment = try jvm.environment() + let integerClass = try JavaClass(environment: environment) + + // ArrayList as Sequence directly + let arrayList = ArrayList(environment: environment) + _ = arrayList.add(integerClass.valueOf(1)) + _ = arrayList.add(integerClass.valueOf(2)) + _ = arrayList.add(integerClass.valueOf(3)) + XCTAssertEqual(arrayList.map { $0.intValue() }, [1, 2, 3]) + + // ArrayDeque as Sequence directly + let arrayDeque = ArrayDeque(environment: environment) + _ = arrayDeque.add(integerClass.valueOf(10)) + _ = arrayDeque.add(integerClass.valueOf(20)) + XCTAssertEqual(arrayDeque.map { $0.intValue() }, [10, 20]) + + // HashSet as Sequence directly + let hashSet = HashSet(environment: environment) + _ = hashSet.add(integerClass.valueOf(7)) + _ = hashSet.add(integerClass.valueOf(8)) + XCTAssertEqual(Set(hashSet.map { $0.intValue() }), [7, 8]) + + // TreeSet as Sequence directly (natural sorted order) + let treeSet = TreeSet(environment: environment) + _ = treeSet.add(integerClass.valueOf(30)) + _ = treeSet.add(integerClass.valueOf(10)) + _ = treeSet.add(integerClass.valueOf(20)) + XCTAssertEqual(treeSet.map { $0.intValue() }, [10, 20, 30]) + + // PriorityQueue as Sequence directly + let priorityQueue = PriorityQueue(environment: environment) + _ = priorityQueue.add(integerClass.valueOf(42)) + _ = priorityQueue.add(integerClass.valueOf(99)) + XCTAssertEqual(priorityQueue.map { $0.intValue() }.count, 2) + } + + func testJavaCollectionProtocolGeneric() throws { + let environment = try jvm.environment() + let integerClass = try JavaClass(environment: environment) + + func summarize(_ collection: C) -> (count: Int, sum: Int32) where C.Element == JavaInteger { + var count = 0 + var sum: Int32 = 0 + for item in collection { + count += 1 + sum += item.intValue() + } + return (count, sum) + } + + let arrayList = ArrayList(environment: environment) + _ = arrayList.add(integerClass.valueOf(10)) + _ = arrayList.add(integerClass.valueOf(20)) + _ = arrayList.add(integerClass.valueOf(30)) + + let list: List = try XCTUnwrap(arrayList.as(List.self)) + let collection: JavaCollection = try XCTUnwrap(arrayList.as(JavaCollection.self)) + + XCTAssertEqual(summarize(arrayList).count, 3) + XCTAssertEqual(summarize(arrayList).sum, 60) + + XCTAssertEqual(summarize(list).count, 3) + XCTAssertEqual(summarize(list).sum, 60) + + XCTAssertEqual(summarize(collection).count, 3) + XCTAssertEqual(summarize(collection).sum, 60) + } + + func testEmptyCollectionsSequence() throws { + let environment = try jvm.environment() + + let emptyArrayList = ArrayList(environment: environment) + XCTAssertEqual(emptyArrayList.map { $0.intValue() }, []) + + let emptyCollection: JavaCollection = try XCTUnwrap(emptyArrayList.as(JavaCollection.self)) + XCTAssertEqual(emptyCollection.map { $0.intValue() }, []) + + var iterator = emptyCollection.makeIterator() + XCTAssertNil(iterator.swiftNext()) + + let emptySet = HashSet(environment: environment) + XCTAssertEqual(emptySet.map { $0.intValue() }, []) + } +}