Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 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
//
//===----------------------------------------------------------------------===//

#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif
import SwiftJava

public func compareDates(date1: Date, date2: Date) -> Bool {
return date1 == date2
}

public func dateFromSeconds(_ seconds: Double) -> Date {
return Date(timeIntervalSince1970: seconds)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 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;

import org.junit.jupiter.api.Test;

import java.time.Instant;

import static org.junit.jupiter.api.Assertions.*;

public class DateTest {
@Test
void date() {
assertEquals(Instant.ofEpochSecond(1000), MySwiftLibrary.dateFromSeconds(1000.0));
assertEquals(Instant.ofEpochSecond(1000, 500_000_000), MySwiftLibrary.dateFromSeconds(1000.50));
assertTrue(MySwiftLibrary.compareDates(Instant.ofEpochSecond(5000), Instant.ofEpochSecond(5000)));
assertFalse(MySwiftLibrary.compareDates(Instant.ofEpochSecond(4999, 500_000_000), Instant.ofEpochSecond(5000)));
assertTrue(MySwiftLibrary.compareDates(MySwiftLibrary.dateFromSeconds(1000.5), Instant.ofEpochSecond(1000, 500_000_000)));

var date = MySwiftLibrary.dateFromSeconds(50000.5);
assertEquals(50_000, date.getEpochSecond());
assertEquals(500_000_000, date.getNano());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ extension SwiftKnownTypeDeclKind {
case .void: .void
case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
.unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .foundationData, .foundationDataProtocol,
.essentialsData, .essentialsDataProtocol, .optional:
.essentialsData, .essentialsDataProtocol, .optional, .foundationDate, .essentialsDate:
nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ extension FFMSwift2JavaGenerator {

""")

printSwiftThunkImports(&printer)
self.lookupContext.symbolTable.printImportedModules(&printer)

for thunk in stt.renderGlobalThunks() {
printer.print(thunk)
Expand Down Expand Up @@ -140,67 +140,13 @@ extension FFMSwift2JavaGenerator {
"""
)

printSwiftThunkImports(&printer)
self.lookupContext.symbolTable.printImportedModules(&printer)

for thunk in stt.renderThunks(forType: ty) {
printer.print("\(thunk)")
printer.print("")
}
}

func printSwiftThunkImports(_ printer: inout CodePrinter) {
let mainSymbolSourceModules = Set(
self.lookupContext.symbolTable.importedModules.values.filter { $0.alternativeModules?.isMainSourceOfSymbols ?? false }.map(\.moduleName)
)

for module in self.lookupContext.symbolTable.importedModules.keys.sorted() {
guard module != "Swift" else {
continue
}

guard let alternativeModules = self.lookupContext.symbolTable.importedModules[module]?.alternativeModules else {
printer.print("import \(module)")
continue
}

// Try to print only on main module from relation chain as it has every other module.
guard !mainSymbolSourceModules.isDisjoint(with: alternativeModules.moduleNames) || alternativeModules.isMainSourceOfSymbols else {
if !alternativeModules.isMainSourceOfSymbols {
printer.print("import \(module)")
}
continue
}

var importGroups: [String: [String]] = [:]
for name in alternativeModules.moduleNames {
guard let otherModule = self.lookupContext.symbolTable.importedModules[name] else { continue }

let groupKey = otherModule.requiredAvailablityOfModuleWithName ?? otherModule.moduleName
importGroups[groupKey, default: []].append(otherModule.moduleName)
}

for (index, group) in importGroups.keys.sorted().enumerated() {
if index > 0 && importGroups.keys.count > 1 {
printer.print("#elseif canImport(\(group))")
} else {
printer.print("#if canImport(\(group))")
}

for groupModule in importGroups[group] ?? [] {
printer.print("import \(groupModule)")
}
}

if (importGroups.keys.isEmpty) {
printer.print("import \(module)")
} else {
printer.print("#else")
printer.print("import \(module)")
printer.print("#endif")
}
}
printer.println()
}
}

struct SwiftThunkTranslator {
Expand Down
2 changes: 1 addition & 1 deletion Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ enum JNIJavaTypeTranslator {
.unsafePointer, .unsafeMutablePointer,
.unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
.unsafeBufferPointer, .unsafeMutableBufferPointer,
.optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol, .array:
.optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol, .array, .foundationDate, .essentialsDate:
return nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,12 @@ extension JNISwift2JavaGenerator {
parameterName: parameterName
)

case .foundationDate, .essentialsDate:
return TranslatedParameter(
parameter: JavaParameter(name: parameterName, type: .javaTimeInstant, annotations: parameterAnnotations),
conversion: .instantToDouble
)

default:
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else {
throw JavaTranslationError.unsupportedSwiftType(swiftType)
Expand Down Expand Up @@ -671,6 +677,14 @@ extension JNISwift2JavaGenerator {
elementType: elementType
)

case .foundationDate, .essentialsDate:
return TranslatedResult(
javaType: .javaTimeInstant,
annotations: resultAnnotations,
outParameters: [],
conversion: .doubleToInstant
)

default:
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else {
throw JavaTranslationError.unsupportedSwiftType(swiftType)
Expand Down Expand Up @@ -1109,6 +1123,12 @@ extension JNISwift2JavaGenerator {
.method(.constant("Arrays"), function: "stream", arguments: [argument])
}

/// Convert a `java.time.Instant` to a seconds double
case instantToDouble

/// Convert a double to `java.time.Instant`
case doubleToInstant

/// Returns the conversion string applied to the placeholder.
func render(_ printer: inout CodePrinter, _ placeholder: String) -> String {
// NOTE: 'printer' is used if the conversion wants to cause side-effects.
Expand Down Expand Up @@ -1242,13 +1262,26 @@ extension JNISwift2JavaGenerator {
case .requireNonNull(let inner, let message):
let inner = inner.render(&printer, placeholder)
return #"Objects.requireNonNull(\#(inner), "\#(message)")"#

case .instantToDouble:
return "(\(placeholder).getEpochSecond() + (\(placeholder).getNano() / 1_000_000_000.0))"

case .doubleToInstant:
printer.print(
"""
double $instant = \(placeholder);
long $seconds = (long) $instant;
long $nanos = (long) (($instant - $seconds) * 1_000_000_000);
"""
)
return "java.time.Instant.ofEpochSecond($seconds, $nanos)"
}
}

/// Whether the conversion uses SwiftArena.
var requiresSwiftArena: Bool {
switch self {
case .placeholder, .constant, .isOptionalPresent, .combinedName:
case .placeholder, .constant, .isOptionalPresent, .combinedName, .instantToDouble, .doubleToInstant:
return false

case .constructSwiftValue, .wrapMemoryAddressUnsafe:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ extension JNISwift2JavaGenerator {
}
return try translateArrayParameter(elementType: elementType, parameterName: parameterName)

case .foundationDate, .essentialsDate:
return NativeParameter(
parameters: [
JavaParameter(name: parameterName, type: .double)
],
conversion: .method(
.constant("Date"),
function: "init",
arguments: [
("timeIntervalSince1970", .initFromJNI(.placeholder, swiftType: self.knownTypes.double))
]
)
)

default:
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config),
javaType.implementsJavaValue else {
Expand Down Expand Up @@ -484,6 +498,13 @@ extension JNISwift2JavaGenerator {
}
return try translateArrayResult(elementType: elementType, resultName: resultName)

case .foundationDate, .essentialsDate:
return NativeResult(
javaType: .double,
conversion: .getJNIValue(.member(.placeholder, member: "timeIntervalSince1970")),
outParameters: []
)

default:
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else {
throw JavaTranslationError.unsupportedSwiftType(swiftResult.type)
Expand Down Expand Up @@ -1111,4 +1132,4 @@ extension JNISwift2JavaGenerator {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,8 @@ extension JNISwift2JavaGenerator {

"""
)

self.lookupContext.symbolTable.printImportedModules(&printer)
}

private func printTypeMetadataAddressThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) {
Expand Down
4 changes: 4 additions & 0 deletions Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,8 @@ extension JavaType {
static func future(_ T: JavaType) -> JavaType {
.class(package: "java.util.concurrent", name: "Future", typeParameters: [T.boxedType])
}

static var javaTimeInstant: JavaType {
.class(package: "java.time", name: "Instant")
}
}
2 changes: 2 additions & 0 deletions Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ private let foundationEssentialsSourceFile: SourceFileSyntax = """
public var count: Int { get }
public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void)
}

public struct Date {}
"""

private var foundationSourceFile: SourceFileSyntax {
Expand Down
2 changes: 2 additions & 0 deletions Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ enum SwiftKnownTypeDeclKind: String, Hashable {
case essentialsDataProtocol = "FoundationEssentials.DataProtocol"
case foundationData = "Foundation.Data"
case essentialsData = "FoundationEssentials.Data"
case foundationDate = "Foundation.Date"
case essentialsDate = "FoundationEssentials.Date"

var moduleAndName: (module: String, name: String) {
let qualified = self.rawValue
Expand Down
56 changes: 56 additions & 0 deletions Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,59 @@ extension SwiftSymbolTable {
return found
}
}

extension SwiftSymbolTable {
func printImportedModules(_ printer: inout CodePrinter) {
let mainSymbolSourceModules = Set(
self.importedModules.values.filter { $0.alternativeModules?.isMainSourceOfSymbols ?? false }.map(\.moduleName)
)

for module in self.importedModules.keys.sorted() {
guard module != "Swift" else {
continue
}

guard let alternativeModules = self.importedModules[module]?.alternativeModules else {
printer.print("import \(module)")
continue
}

// Try to print only on main module from relation chain as it has every other module.
guard !mainSymbolSourceModules.isDisjoint(with: alternativeModules.moduleNames) || alternativeModules.isMainSourceOfSymbols else {
if !alternativeModules.isMainSourceOfSymbols {
printer.print("import \(module)")
}
continue
}

var importGroups: [String: [String]] = [:]
for name in alternativeModules.moduleNames {
guard let otherModule = self.importedModules[name] else { continue }

let groupKey = otherModule.requiredAvailablityOfModuleWithName ?? otherModule.moduleName
importGroups[groupKey, default: []].append(otherModule.moduleName)
}

for (index, group) in importGroups.keys.sorted().enumerated() {
if index > 0 && importGroups.keys.count > 1 {
printer.print("#elseif canImport(\(group))")
} else {
printer.print("#if canImport(\(group))")
}

for groupModule in importGroups[group] ?? [] {
printer.print("import \(groupModule)")
}
}

if (importGroups.keys.isEmpty) {
printer.print("import \(module)")
} else {
printer.print("#else")
printer.print("import \(module)")
printer.print("#endif")
}
}
printer.println()
}
}
Loading
Loading